/* * * 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 László 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 API’s 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; } }