package com.netflix.appinfo.providers; import com.netflix.appinfo.AmazonInfo; import com.netflix.appinfo.AmazonInfoConfig; import com.netflix.appinfo.Archaius2AmazonInfoConfig; import com.netflix.appinfo.Ec2EurekaArchaius2InstanceConfig; import com.netflix.appinfo.EurekaArchaius2InstanceConfig; import com.netflix.appinfo.EurekaInstanceConfig; import com.netflix.archaius.api.Config; import com.netflix.archaius.api.annotations.ConfigurationSource; import com.netflix.discovery.CommonConstants; import com.netflix.discovery.DiscoveryManager; import com.netflix.discovery.internal.util.AmazonInfoUtils; import com.netflix.discovery.internal.util.InternalPrefixedConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Singleton; import java.net.SocketTimeoutException; import java.net.URL; /** * A factory for {@link com.netflix.appinfo.EurekaInstanceConfig} that can provide either * {@link com.netflix.appinfo.Ec2EurekaArchaius2InstanceConfig} or * {@link com.netflix.appinfo.EurekaArchaius2InstanceConfig} based on some selection strategy. * * If no config based override is applied, this Factory will automatically detect whether the * current deployment environment is EC2 or not, and create the appropriate Config instances. * * Setting the property <b>eureka.instanceDeploymentEnvironment=ec2</b> will force the instantiation * of {@link com.netflix.appinfo.Ec2EurekaArchaius2InstanceConfig}, regardless of what the * automatic environment detection says. * * Setting the property <b>eureka.instanceDeploymentEnvironment={a non-null, non-ec2 string}</b> * will force the instantiation of {@link com.netflix.appinfo.EurekaArchaius2InstanceConfig}, * regardless of what the automatic environment detection says. * * Why define the {@link com.netflix.appinfo.providers.EurekaInstanceConfigFactory} instead * of using {@link javax.inject.Provider} instead? Provider does not work due to the fact that * Guice treats Providers specially. * * @author David Liu */ @Singleton @ConfigurationSource(CommonConstants.CONFIG_FILE_NAME) public class CompositeInstanceConfigFactory implements EurekaInstanceConfigFactory { private static final Logger logger = LoggerFactory.getLogger(CompositeInstanceConfigFactory.class); private static final String DEPLOYMENT_ENVIRONMENT_OVERRIDE_KEY = "instanceDeploymentEnvironment"; private final String namespace; private final Config configInstance; private final InternalPrefixedConfig prefixedConfig; private EurekaInstanceConfig eurekaInstanceConfig; @Inject public CompositeInstanceConfigFactory(Config configInstance, String namespace) { this.configInstance = configInstance; this.namespace = namespace; this.prefixedConfig = new InternalPrefixedConfig(configInstance, namespace); } @Override public synchronized EurekaInstanceConfig get() { if (eurekaInstanceConfig == null) { // create the amazonInfoConfig before we can determine if we are in EC2, as we want to use the amazonInfoConfig for // that determination. This is just the config however so is cheap to do and does not have side effects. AmazonInfoConfig amazonInfoConfig = new Archaius2AmazonInfoConfig(configInstance, namespace); if (isInEc2(amazonInfoConfig)) { eurekaInstanceConfig = new Ec2EurekaArchaius2InstanceConfig(configInstance, amazonInfoConfig, namespace); logger.info("Creating EC2 specific instance config"); } else { eurekaInstanceConfig = new EurekaArchaius2InstanceConfig(configInstance, namespace); logger.info("Creating generic instance config"); } // TODO: Remove this when DiscoveryManager is finally no longer used DiscoveryManager.getInstance().setEurekaInstanceConfig(eurekaInstanceConfig); } return eurekaInstanceConfig; } private boolean isInEc2(AmazonInfoConfig amazonInfoConfig) { String deploymentEnvironmentOverride = getDeploymentEnvironmentOverride(); if (deploymentEnvironmentOverride == null) { return autoDetectEc2(amazonInfoConfig); } else if ("ec2".equalsIgnoreCase(deploymentEnvironmentOverride)) { logger.info("Assuming EC2 deployment environment due to config override"); return true; } else { return false; } } // best effort try to determine if we are in ec2 by trying to read the instanceId from metadata url private boolean autoDetectEc2(AmazonInfoConfig amazonInfoConfig) { try { URL url = AmazonInfo.MetaDataKey.instanceId.getURL(null, null); String id = AmazonInfoUtils.readEc2MetadataUrl( AmazonInfo.MetaDataKey.instanceId, url, amazonInfoConfig.getConnectTimeout(), amazonInfoConfig.getReadTimeout() ); if (id != null) { logger.info("Auto detected EC2 deployment environment, instanceId = {}", id); return true; } else { logger.info("Auto detected non-EC2 deployment environment, instanceId from metadata url is null"); return false; } } catch (SocketTimeoutException e) { logger.info("Auto detected non-EC2 deployment environment, connection to ec2 instance metadata url failed."); } catch (Exception e) { logger.warn("Failed to auto-detect whether we are in EC2 due to unexpected exception", e); } return false; } private String getDeploymentEnvironmentOverride() { return prefixedConfig.getString(DEPLOYMENT_ENVIRONMENT_OVERRIDE_KEY, null); } }