com.hazelcast.samples.amazon.elasticbeanstalk.HazelcastInstanceFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.hazelcast.samples.amazon.elasticbeanstalk.HazelcastInstanceFactory.java

Source

/*
 *
 *  Copyright (c) 2008-2016, Hazelcast, Inc. All Rights Reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package com.hazelcast.samples.amazon.elasticbeanstalk;

import com.amazonaws.AmazonClientException;
import com.amazonaws.auth.InstanceProfileCredentialsProvider;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.DescribeTagsRequest;
import com.amazonaws.services.ec2.model.DescribeTagsResult;
import com.amazonaws.services.ec2.model.Filter;
import com.amazonaws.util.EC2MetadataUtils;
import com.hazelcast.config.Config;
import com.hazelcast.config.XmlConfigBuilder;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.spring.context.SpringManagedContext;
import com.hazelcast.util.MD5Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;

/**
 * @author Lszl Csontos
 */
public class HazelcastInstanceFactory extends AbstractFactoryBean<HazelcastInstance> {

    public static final String ELASTICBEANSTALK_ENVIRONMENT_NAME = "elasticbeanstalk:environment-name";
    public static final String ELASTICBEANSTALK_EC2_ROLE_NAME = "aws-elasticbeanstalk-ec2-role";
    public static final String HAZELCAST_ENVIRONMENT_NAME = "hazelcast.environment.name";
    public static final String HAZELCAST_ENVIRONMENT_PASSWORD = "hazelcast.environment.password";
    public static final String HAZELCAST_AWS_IAM_ROLE = "hazelcast.aws.iam-role";
    public static final String HAZELCAST_AWS_REGION = "hazelcast.aws.region";
    public static final String HAZELCAST_LOGGING_TYPE = "hazelcast.logging.type";

    private static final Logger LOGGER = LoggerFactory.getLogger(HazelcastInstanceFactory.class);

    private AmazonEC2 amazonEC2;
    private boolean localOnly;

    @Value("classpath:/hazelcast-aws.xml")
    private Resource hazelcastAWSConfig;

    @Value("classpath:/hazelcast-local.xml")
    private Resource hazelcastLocalConfig;

    @Autowired
    private SpringManagedContext springManagedContext;

    public HazelcastInstanceFactory() {
        try {
            amazonEC2 = new AmazonEC2Client(new InstanceProfileCredentialsProvider());
        } catch (AmazonClientException ace) {
            LOGGER.error("Couldn't authenticate with AWS; local cluster configuration will be used.", ace);
        }
    }

    @Override
    public Class<HazelcastInstance> getObjectType() {
        return HazelcastInstance.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    protected HazelcastInstance createInstance() throws Exception {
        Config config = getConfig();
        config.setProperty(HAZELCAST_LOGGING_TYPE, "slf4j");

        // Enabling @SpringAware by default was causing performance hit so it had been disabled as of 3.5-EA.
        // Although <hz:spring-aware /> makes it possible to enable @SpringAware, it's not an option for programmatic
        // configuration.
        // See https://github.com/hazelcast/hazelcast/issues/5323#issuecomment-103033381
        // See https://github.com/hazelcast/hazelcast/issues/6514#issuecomment-180394893
        config.setManagedContext(springManagedContext);

        return Hazelcast.newHazelcastInstance(config);
    }

    @Override
    protected void destroyInstance(HazelcastInstance hazelcastInstance) throws Exception {
        hazelcastInstance.shutdown();
        shutdownAmazonEC2();
    }

    protected Config getConfig() throws IOException {
        Properties awsProperties = null;
        if (amazonEC2 != null) {
            try {
                awsProperties = getAwsProperties();
            } catch (RuntimeException re) {
                shutdownAmazonEC2();
                LOGGER.error("Auto-detecting cluster membership has failed; falling back to local configuration.",
                        re);
            }
        }

        Resource hazelcastConfig = (amazonEC2 != null) ? hazelcastAWSConfig : hazelcastLocalConfig;
        LOGGER.info("Using {} for cluster configuration.", hazelcastConfig.getFilename());

        XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(hazelcastConfig.getInputStream());
        if (amazonEC2 != null) {
            xmlConfigBuilder.setProperties(awsProperties);
        }
        return xmlConfigBuilder.build();
    }

    protected Properties getAwsProperties() {
        EC2MetadataUtils.InstanceInfo instanceInfo = EC2MetadataUtils.getInstanceInfo();
        String instanceId = instanceInfo.getInstanceId();

        // EB sets the environment ID and name as the elasticbeanstalk:environment-id and
        // elasticbeanstalk:environment-name EC2 tags on all of the parts of an EB app environment: load balancer,
        // EC2 instances, security groups, etc. Surprisingly, EC2 tags aren't available to instances through the
        // instance metadata interface, but they are available through the normal AWS APIs DescribeTags call.
        Collection<Filter> filters = new ArrayList<Filter>();
        filters.add(new Filter("resource-type").withValues("instance"));
        filters.add(new Filter("resource-id").withValues(instanceId));
        filters.add(new Filter("key").withValues(ELASTICBEANSTALK_ENVIRONMENT_NAME));

        DescribeTagsRequest describeTagsRequest = new DescribeTagsRequest();
        describeTagsRequest.setFilters(filters);
        DescribeTagsResult describeTagsResult = amazonEC2.describeTags(describeTagsRequest);

        if (describeTagsResult == null || describeTagsResult.getTags().isEmpty()) {
            throw new IllegalStateException(
                    "No tag " + ELASTICBEANSTALK_ENVIRONMENT_NAME + " found for instance " + instanceId + ".");
        }

        String environmentName = describeTagsResult.getTags().get(0).getValue();
        String environmentPassword = MD5Util.toMD5String(environmentName);

        Properties properties = new Properties();
        properties.setProperty(HAZELCAST_ENVIRONMENT_NAME, environmentName);
        properties.setProperty(HAZELCAST_ENVIRONMENT_PASSWORD, environmentPassword);
        properties.setProperty(HAZELCAST_AWS_IAM_ROLE, ELASTICBEANSTALK_EC2_ROLE_NAME);
        properties.setProperty(HAZELCAST_AWS_REGION, instanceInfo.getRegion());

        return properties;
    }

    protected void shutdownAmazonEC2() {
        if (amazonEC2 == null) {
            return;
        }
        amazonEC2.shutdown();
        amazonEC2 = null;
    }
}