Skip to content

Commit

Permalink
Adding ability to define resolution order of the local InstanceInfo's
Browse files Browse the repository at this point in the history
"default" network address based on data available in AmazonInfo.
  • Loading branch information
qiangdavidliu committed Dec 8, 2015
1 parent 5817015 commit e0c9072
Show file tree
Hide file tree
Showing 13 changed files with 292 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,13 @@
@Singleton
public class Ec2EurekaArchaius2InstanceConfig extends EurekaArchaius2InstanceConfig {
private static final Logger LOG = LoggerFactory.getLogger(Ec2EurekaArchaius2InstanceConfig.class);

private volatile DataCenterInfo info;

private static final String[] DEFAULT_AWS_ADDRESS_RESOLUTION_ORDER = new String[] {
MetaDataKey.publicHostname.name(),
MetaDataKey.localIpv4.name()
};

private volatile AmazonInfo amazonInfo;

@Inject
public Ec2EurekaArchaius2InstanceConfig(Config config) {
Expand All @@ -39,7 +44,7 @@ public Ec2EurekaArchaius2InstanceConfig(Config config, String namespace) {
super(config, namespace);

try {
this.info = AmazonInfo.Builder.newBuilder().autoBuild(namespace);
this.amazonInfo = AmazonInfo.Builder.newBuilder().autoBuild(namespace);
LOG.info("Datacenter is: " + Name.Amazon);
}
catch (Exception e) {
Expand All @@ -48,7 +53,6 @@ public Ec2EurekaArchaius2InstanceConfig(Config config, String namespace) {
}

// Instance id being null means we could not get the amazon metadata
AmazonInfo amazonInfo = (AmazonInfo) info;
if (amazonInfo.get(MetaDataKey.instanceId) == null) {
if (config.getBoolean(namespace + ".validateInstanceId", true)) {
throw new RuntimeException(
Expand Down Expand Up @@ -79,12 +83,18 @@ public String getHostName(boolean refresh) {
if (refresh) {
refreshAmazonInfo();
}
return ((AmazonInfo) info).get(MetaDataKey.publicHostname);
return amazonInfo.get(MetaDataKey.publicHostname);
}

@Override
public DataCenterInfo getDataCenterInfo() {
return info;
return amazonInfo;
}

@Override
public String[] getDefaultAddressResolutionOrder() {
String[] order = super.getDefaultAddressResolutionOrder();
return (order.length == 0) ? DEFAULT_AWS_ADDRESS_RESOLUTION_ORDER : order;
}

/**
Expand All @@ -94,14 +104,12 @@ public DataCenterInfo getDataCenterInfo() {
public synchronized void refreshAmazonInfo() {
try {
AmazonInfo newInfo = AmazonInfo.Builder.newBuilder().autoBuild(DEFAULT_NAMESPACE);
String newHostname = newInfo.get(MetaDataKey.publicHostname);
String existingHostname = ((AmazonInfo) info).get(MetaDataKey.publicHostname);
if (newHostname != null && !newHostname.equals(existingHostname)) {
// public dns has changed on us, re-sync it
LOG.warn("The public hostname changed from : {} => {}", existingHostname, newHostname);
this.info = newInfo;
if (!newInfo.equals(amazonInfo)) {
// the datacenter info has changed, re-sync it
LOG.warn("The AmazonInfo changed from : {} => {}", amazonInfo, newInfo);
this.amazonInfo = newInfo;
}
}
}
catch (Exception e) {
LOG.error("Cannot refresh the Amazon Info ", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ public String getSecureHealthCheckUrl() {
return config.getString("secureHealthCheckUrl", null);
}

@Override
public String[] getDefaultAddressResolutionOrder() {
String result = config.getString(namespace + "defaultAddressResolutionOrder", null);
return result == null ? new String[0] : result.split(",");
}

@Override
public String getNamespace() {
return namespace;
Expand Down
25 changes: 25 additions & 0 deletions eureka-client/src/main/java/com/netflix/appinfo/AmazonInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -316,4 +316,29 @@ public String get(MetaDataKey key) {
public String getId() {
return get(MetaDataKey.instanceId);
}


@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof AmazonInfo)) return false;

AmazonInfo that = (AmazonInfo) o;

if (metadata != null ? !metadata.equals(that.metadata) : that.metadata != null) return false;

return true;
}

@Override
public int hashCode() {
return metadata != null ? metadata.hashCode() : 0;
}

@Override
public String toString() {
return "AmazonInfo{" +
"metadata=" + metadata +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,29 @@ public void unregisterStatusChangeListener(String 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#getDefaultAddress()} for explanation on why the hostname is used as the default address
*/
public void refreshDataCenterInfoIfRequired() {
String existingHostname = instanceInfo.getHostName();
String newHostname = config.getHostName(true);
if (newHostname != null && !newHostname.equals(existingHostname)) {
logger.warn("The public hostname changed from : "
+ existingHostname + " => " + newHostname);
InstanceInfo.Builder builder = new InstanceInfo.Builder(
instanceInfo);
builder.setHostName(newHostname).setDataCenterInfo(
config.getDataCenterInfo());
String existingAddress = instanceInfo.getDefaultAddress();
DataCenterInfo dataCenterInfo = instanceInfo.getDataCenterInfo();

String newAddress;
if (config instanceof CloudInstanceConfig) {
newAddress = ((CloudInstanceConfig) config).resolveDefaultAddress(dataCenterInfo);
} 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();
}
}
Expand All @@ -169,8 +181,7 @@ public void refreshLeaseInfoIfRequired() {
int currentLeaseDuration = config.getLeaseExpirationDurationInSeconds();
int currentLeaseRenewal = config.getLeaseRenewalIntervalInSeconds();
if (leaseInfo.getDurationInSecs() != currentLeaseDuration || leaseInfo.getRenewalIntervalInSecs() != currentLeaseRenewal) {
LeaseInfo newLeaseInfo = LeaseInfo.Builder
.newBuilder()
LeaseInfo newLeaseInfo = LeaseInfo.Builder.newBuilder()
.setRenewalIntervalInSecs(currentLeaseRenewal)
.setDurationInSecs(currentLeaseDuration)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,13 @@ public class CloudInstanceConfig extends PropertiesInstanceConfig {
private static final Logger logger = LoggerFactory.getLogger(CloudInstanceConfig.class);
private static final DynamicPropertyFactory INSTANCE = DynamicPropertyFactory.getInstance();

private static final String[] DEFAULT_AWS_ADDRESS_RESOLUTION_ORDER = new String[] {
MetaDataKey.publicHostname.name(),
MetaDataKey.localIpv4.name()
};

private DynamicBooleanProperty propValidateInstanceId;
private DataCenterInfo info;
private volatile AmazonInfo info;

public CloudInstanceConfig() {
initCloudInstanceConfig(namespace);
Expand All @@ -63,13 +68,12 @@ public CloudInstanceConfig(String namespace) {
}

private void initCloudInstanceConfig(String namespace) {
propValidateInstanceId = INSTANCE.getBooleanProperty(namespace
+ "validateInstanceId", true);
propValidateInstanceId = INSTANCE.getBooleanProperty(namespace + "validateInstanceId", true);
info = initDataCenterInfo();
}

private DataCenterInfo initDataCenterInfo() {
DataCenterInfo info;
private AmazonInfo initDataCenterInfo() {
AmazonInfo info;
try {
info = AmazonInfo.Builder.newBuilder().autoBuild(namespace);
logger.info("Datacenter is: " + Name.Amazon);
Expand All @@ -78,62 +82,94 @@ private DataCenterInfo initDataCenterInfo() {
throw new RuntimeException(e);
}
// Instance id being null means we could not get the amazon metadata
AmazonInfo amazonInfo = (AmazonInfo) info;
if (amazonInfo.get(MetaDataKey.instanceId) == null) {
if (info.get(MetaDataKey.instanceId) == null) {
if (propValidateInstanceId.get()) {
throw new RuntimeException(
"Your datacenter is defined as cloud but we are not able to get the amazon metadata to "
+ "register. \nSet the property " + namespace + "validateInstanceId to false to ignore the"
+ "metadata call");
+ "register. \nSet the property " + namespace + "validateInstanceId to false to "
+ "ignore the metadata call");
} else {
// The property to not validate instance ids may be set for
// development and in that scenario, populate instance id
// and public hostname with the hostname of the machine
Map<String, String> metadataMap = new HashMap<String, String>();
metadataMap.put(MetaDataKey.instanceId.getName(), super.getIpAddress());
metadataMap.put(MetaDataKey.publicHostname.getName(), super.getHostName(false));
amazonInfo.setMetadata(metadataMap);
info.setMetadata(metadataMap);
}
} else if ((amazonInfo.get(MetaDataKey.publicHostname) == null)
&& (amazonInfo.get(MetaDataKey.localIpv4) != null)) {
} else if ((info.get(MetaDataKey.publicHostname) == null)
&& (info.get(MetaDataKey.localIpv4) != null)) {
// :( legacy code and logic
// This might be a case of VPC where the instance id is not null, but
// public hostname might be null
amazonInfo.getMetadata().put(MetaDataKey.publicHostname.getName(),
(amazonInfo.get(MetaDataKey.localIpv4)));
info.getMetadata().put(MetaDataKey.publicHostname.getName(), (info.get(MetaDataKey.localIpv4)));
}
return info;
}

public String resolveDefaultAddress(DataCenterInfo dataCenterInfo) {
String result = getHostName(true);

if (dataCenterInfo instanceof AmazonInfo) {
AmazonInfo amazonInfo = (AmazonInfo) dataCenterInfo;
for (String name : getDefaultAddressResolutionOrder()) {
try {
AmazonInfo.MetaDataKey key = AmazonInfo.MetaDataKey.valueOf(name);
String address = amazonInfo.get(key);
if (address != null && !address.isEmpty()) {
result = address;
break;
}
} catch (Exception e) {
logger.error("failed to resolve default address for key {}, skipping", name, e);
}
}
} else {
logger.warn("DataCenterInfo is not of type AmazonInfo. Defaulting to default resolution");
}

return result;
}

@Override
public String getHostName(boolean refresh) {
if (refresh) {
refreshAmazonInfo();
}
return ((AmazonInfo) info).get(MetaDataKey.publicHostname);
return info.get(MetaDataKey.publicHostname);
}

@Override
public String getIpAddress() {
String ipAddr = info.get(MetaDataKey.localIpv4);
return ipAddr == null ? super.getIpAddress() : ipAddr;
}

@Override
public DataCenterInfo getDataCenterInfo() {
return info;
}

@Override
public String[] getDefaultAddressResolutionOrder() {
String[] order = super.getDefaultAddressResolutionOrder();
return (order.length == 0) ? DEFAULT_AWS_ADDRESS_RESOLUTION_ORDER : order;
}

/**
* Refresh instance info - currently only used when in AWS cloud
* as a public ip can change whenever an EIP is associated or dissociated.
*/
public synchronized void refreshAmazonInfo() {
try {
AmazonInfo newInfo = AmazonInfo.Builder.newBuilder().autoBuild(namespace);
String newHostname = newInfo.get(MetaDataKey.publicHostname);
String existingHostname = ((AmazonInfo) info).get(MetaDataKey.publicHostname);
if (newHostname != null && !newHostname.equals(existingHostname)) {
// public dns has changed on us, re-sync it
logger.warn("The public hostname changed from : {} => {}", existingHostname, newHostname);
if (!newInfo.equals(info)) {
// the datacenter info has changed, re-sync it
logger.warn("The AmazonInfo changed from : {} => {}", info, newInfo);
this.info = newInfo;
}
} catch (Throwable t) {
logger.error("Cannot refresh the Amazon Info ", t);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,21 @@ public interface EurekaInstanceConfig {
*/
String getSecureHealthCheckUrl();

/**
* An instance's network addresses should be fully expressed in it's {@link DataCenterInfo}.
* For example for instances in AWS, this will include the publicHostname, publicIp,
* privateHostname and privateIp, when available. The {@link com.netflix.appinfo.InstanceInfo}
* will further express a "default address", which is a field that can be configured by the
* registering instance to advertise it's default address. This configuration allowed
* for the expression of an ordered list of fields that can be used to resolve the default
* address. The exact field values will depend on the implementation details of the corresponding
* implementing DataCenterInfo types.
*
* @return an ordered list of fields that should be used to preferentially
* resolve this instance's default address, empty String[] for default.
*/
String[] getDefaultAddressResolutionOrder();

/**
* Get the namespace used to find properties.
* @return the namespace used to find properties.
Expand Down
16 changes: 16 additions & 0 deletions eureka-client/src/main/java/com/netflix/appinfo/InstanceInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,22 @@ public String getHostName() {
return hostName;
}

/**
* Return the default network address to connect to this instance.
*
* The address can either be a hostname or an ip and there is no guarantee which will be returned.
* Assume the address can change dynamically over time.
* If a usecase need more specific hostnames or ips, please use data from {@link #getDataCenterInfo()}.
*
* For legacy reasons, the data backing this field is extracted from the hostname field
*
* @return either a hostname or an ipAddress
*/
@JsonIgnore
public String getDefaultAddress() {
return hostName;
}

@Deprecated
public void setSID(String sid) {
this.sid = sid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,12 @@ public String getSecureHealthCheckUrl() {
null).get();
}

@Override
public String[] getDefaultAddressResolutionOrder() {
String result = INSTANCE.getStringProperty(namespace + "defaultAddressResolutionOrder", null).get();
return result == null ? new String[0] : result.split(",");
}

@Override
public String getNamespace() {
return this.namespace;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.Map;

import com.google.inject.Provider;
import com.netflix.appinfo.CloudInstanceConfig;
import com.netflix.appinfo.DataCenterInfo;
import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.appinfo.InstanceInfo;
Expand Down Expand Up @@ -60,13 +61,20 @@ public synchronized InstanceInfo get() {
}
}

String defaultAddress;
if (config instanceof CloudInstanceConfig) {
defaultAddress = ((CloudInstanceConfig) config).resolveDefaultAddress(dataCenterInfo);
} else {
defaultAddress = config.getHostName(false);
}

builder.setNamespace(config.getNamespace())
.setInstanceId(instanceId)
.setAppName(config.getAppname())
.setAppGroupName(config.getAppGroupName())
.setDataCenterInfo(config.getDataCenterInfo())
.setIPAddr(config.getIpAddress())
.setHostName(config.getHostName(false))
.setHostName(defaultAddress)
.setPort(config.getNonSecurePort())
.enablePort(PortType.UNSECURE, config.isNonSecurePortEnabled())
.setSecurePort(config.getSecurePort())
Expand Down
Loading

0 comments on commit e0c9072

Please sign in to comment.