/* * Copyright 2012 Netflix, Inc. *f * 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.netflix.appinfo; import javax.inject.Inject; import javax.inject.Singleton; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.netflix.appinfo.InstanceInfo.InstanceStatus; import com.netflix.appinfo.providers.EurekaConfigBasedInstanceInfoProvider; import com.netflix.discovery.StatusChangeEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The class that initializes information required for registration with * <tt>Eureka Server</tt> and to be discovered by other components. * * <p> * The information required for registration is provided by the user by passing * the configuration defined by the contract in {@link EurekaInstanceConfig} * }.AWS clients can either use or extend {@link CloudInstanceConfig * }. Other non-AWS clients can use or extend either * {@link MyDataCenterInstanceConfig} or very basic * {@link AbstractInstanceConfig}. * </p> * * * @author Karthik Ranganathan, Greg Kim * */ @Singleton public class ApplicationInfoManager { private static final Logger logger = LoggerFactory.getLogger(ApplicationInfoManager.class); private static final InstanceStatusMapper NO_OP_MAPPER = new InstanceStatusMapper() { @Override public InstanceStatus map(InstanceStatus prev) { return prev; } }; private static ApplicationInfoManager instance = new ApplicationInfoManager(null, null, null); protected final Map<String, StatusChangeListener> listeners; private final InstanceStatusMapper instanceStatusMapper; private InstanceInfo instanceInfo; private EurekaInstanceConfig config; public static class OptionalArgs { private InstanceStatusMapper instanceStatusMapper; @com.google.inject.Inject(optional = true) public void setInstanceStatusMapper(InstanceStatusMapper instanceStatusMapper) { this.instanceStatusMapper = instanceStatusMapper; } InstanceStatusMapper getInstanceStatusMapper() { return instanceStatusMapper == null ? NO_OP_MAPPER : instanceStatusMapper; } } /** * public for DI use. This class should be in singleton scope so do not create explicitly. * Either use DI or create this explicitly using one of the other public constructors. */ @Inject public ApplicationInfoManager(EurekaInstanceConfig config, InstanceInfo instanceInfo, OptionalArgs optionalArgs) { this.config = config; this.instanceInfo = instanceInfo; this.listeners = new ConcurrentHashMap<String, StatusChangeListener>(); if (optionalArgs != null) { this.instanceStatusMapper = optionalArgs.getInstanceStatusMapper(); } else { this.instanceStatusMapper = NO_OP_MAPPER; } // Hack to allow for getInstance() to use the DI'd ApplicationInfoManager instance = this; } public ApplicationInfoManager(EurekaInstanceConfig config, /* nullable */ OptionalArgs optionalArgs) { this(config, new EurekaConfigBasedInstanceInfoProvider(config).get(), optionalArgs); } public ApplicationInfoManager(EurekaInstanceConfig config, InstanceInfo instanceInfo) { this(config, instanceInfo, null); } /** * @deprecated 2016-09-19 prefer {@link #ApplicationInfoManager(EurekaInstanceConfig, com.netflix.appinfo.ApplicationInfoManager.OptionalArgs)} */ @Deprecated public ApplicationInfoManager(EurekaInstanceConfig config) { this(config, (OptionalArgs) null); } /** * @deprecated please use DI instead */ @Deprecated public static ApplicationInfoManager getInstance() { return instance; } public void initComponent(EurekaInstanceConfig config) { try { this.config = config; this.instanceInfo = new EurekaConfigBasedInstanceInfoProvider(config).get(); } catch (Throwable e) { throw new RuntimeException("Failed to initialize ApplicationInfoManager", e); } } /** * Gets the information about this instance that is registered with eureka. * * @return information about this instance that is registered with eureka. */ public InstanceInfo getInfo() { return instanceInfo; } public EurekaInstanceConfig getEurekaInstanceConfig() { return config; } /** * Register user-specific instance meta data. Application can send any other * additional meta data that need to be accessed for other reasons.The data * will be periodically sent to the eureka server. * * Please Note that metadata added via this method is not guaranteed to be submitted * to the eureka servers upon initial registration, and may be submitted as an update * at a subsequent time. If you want guaranteed metadata for initial registration, * please use the mechanism described in {@link EurekaInstanceConfig#getMetadataMap()} * * @param appMetadata application specific meta data. */ public void registerAppMetadata(Map<String, String> appMetadata) { instanceInfo.registerRuntimeMetadata(appMetadata); } /** * Set the status of this instance. Application can use this to indicate * whether it is ready to receive traffic. Setting the status here also notifies all registered listeners * of a status change event. * * @param status Status of the instance */ public synchronized void setInstanceStatus(InstanceStatus status) { InstanceStatus next = instanceStatusMapper.map(status); if (next == null) { return; } InstanceStatus prev = instanceInfo.setStatus(next); if (prev != null) { for (StatusChangeListener listener : listeners.values()) { try { listener.notify(new StatusChangeEvent(prev, next)); } catch (Exception e) { logger.warn("failed to notify listener: {}", listener.getId(), e); } } } } public void registerStatusChangeListener(StatusChangeListener listener) { listeners.put(listener.getId(), listener); } public void unregisterStatusChangeListener(String listenerId) { listeners.remove(listenerId); } /** * Refetches the hostname to check if it has changed. If it has, the entire * <code>DataCenterInfo</code> is refetched and passed on to the eureka * server on next heartbeat. * * see {@link InstanceInfo#getHostName()} for explanation on why the hostname is used as the default address */ public void refreshDataCenterInfoIfRequired() { String existingAddress = instanceInfo.getHostName(); String newAddress; if (config instanceof RefreshableInstanceConfig) { // Refresh data center info, and return up to date address newAddress = ((RefreshableInstanceConfig) config).resolveDefaultAddress(true); } else { newAddress = config.getHostName(true); } String newIp = config.getIpAddress(); if (newAddress != null && !newAddress.equals(existingAddress)) { logger.warn("The address changed from : {} => {}", existingAddress, newAddress); // :( in the legacy code here the builder is acting as a mutator. // This is hard to fix as this same instanceInfo instance is referenced elsewhere. // We will most likely re-write the client at sometime so not fixing for now. InstanceInfo.Builder builder = new InstanceInfo.Builder(instanceInfo); builder.setHostName(newAddress).setIPAddr(newIp).setDataCenterInfo(config.getDataCenterInfo()); instanceInfo.setIsDirty(); } } public void refreshLeaseInfoIfRequired() { LeaseInfo leaseInfo = instanceInfo.getLeaseInfo(); if (leaseInfo == null) { return; } int currentLeaseDuration = config.getLeaseExpirationDurationInSeconds(); int currentLeaseRenewal = config.getLeaseRenewalIntervalInSeconds(); if (leaseInfo.getDurationInSecs() != currentLeaseDuration || leaseInfo.getRenewalIntervalInSecs() != currentLeaseRenewal) { LeaseInfo newLeaseInfo = LeaseInfo.Builder.newBuilder() .setRenewalIntervalInSecs(currentLeaseRenewal) .setDurationInSecs(currentLeaseDuration) .build(); instanceInfo.setLeaseInfo(newLeaseInfo); instanceInfo.setIsDirty(); } } public static interface StatusChangeListener { String getId(); void notify(StatusChangeEvent statusChangeEvent); } public static interface InstanceStatusMapper { /** * given a starting {@link com.netflix.appinfo.InstanceInfo.InstanceStatus}, apply a mapping to return * the follow up status, if applicable. * * @return the mapped instance status, or null if the mapping is not applicable. */ InstanceStatus map(InstanceStatus prev); } }