Commit 5cd88deb by Dave Syer

Use custom DataCenterInfo to make instance ID unique

The problem manifests itself as errors in the Eureka server log (gh-47), but originates in the client because it is sending requests to /eureka/apps/{NAME}/{ID} with the wrong ID. The InstanceInfo has an ID that is derived (for preference) from the EurekaInstanceConfig.dataCenterInfo, so that's the best way to fix it. See gh-63, Fixes gh-47
parent dc1aab94
...@@ -24,6 +24,7 @@ import com.thoughtworks.xstream.MarshallingStrategy; ...@@ -24,6 +24,7 @@ import com.thoughtworks.xstream.MarshallingStrategy;
import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.ConverterLookup; import com.thoughtworks.xstream.converters.ConverterLookup;
import com.thoughtworks.xstream.converters.DataHolder; import com.thoughtworks.xstream.converters.DataHolder;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.core.TreeMarshallingStrategy; import com.thoughtworks.xstream.core.TreeMarshallingStrategy;
import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamReader;
...@@ -57,7 +58,8 @@ public class DataCenterAwareMarshallingStrategy implements MarshallingStrategy { ...@@ -57,7 +58,8 @@ public class DataCenterAwareMarshallingStrategy implements MarshallingStrategy {
@Override @Override
public void marshal(HierarchicalStreamWriter writer, Object obj, public void marshal(HierarchicalStreamWriter writer, Object obj,
ConverterLookup converterLookup, Mapper mapper, DataHolder dataHolder) { ConverterLookup converterLookup, Mapper mapper, DataHolder dataHolder) {
delegate.marshal(writer, obj, converterLookup, mapper, dataHolder); ConverterLookup wrapped = new DataCenterAwareConverterLookup(converterLookup);
delegate.marshal(writer, obj, wrapped, mapper, dataHolder);
} }
public static class InstanceIdDataCenterInfo implements DataCenterInfo, public static class InstanceIdDataCenterInfo implements DataCenterInfo,
...@@ -101,6 +103,22 @@ public class DataCenterAwareMarshallingStrategy implements MarshallingStrategy { ...@@ -101,6 +103,22 @@ public class DataCenterAwareMarshallingStrategy implements MarshallingStrategy {
} }
private static class DataCenterAwareConverter extends InstanceInfoConverter { private static class DataCenterAwareConverter extends InstanceInfoConverter {
@Override
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
InstanceInfo info = (InstanceInfo) source;
String instanceId = info.getMetadata().get("instanceId");
DataCenterInfo dataCenter = info.getDataCenterInfo();
if (instanceId != null && Name.Amazon != dataCenter.getName()) {
String old = info.getId();
String id = old.endsWith(instanceId) ? old : old + ":" + instanceId;
info = new InstanceInfo.Builder(info).setDataCenterInfo(
new InstanceIdDataCenterInfo(id)).build();
source = info;
}
super.marshal(source, writer, context);
}
@Override @Override
public Object unmarshal(HierarchicalStreamReader reader, public Object unmarshal(HierarchicalStreamReader reader,
...@@ -111,8 +129,9 @@ public class DataCenterAwareMarshallingStrategy implements MarshallingStrategy { ...@@ -111,8 +129,9 @@ public class DataCenterAwareMarshallingStrategy implements MarshallingStrategy {
DataCenterInfo dataCenter = info.getDataCenterInfo(); DataCenterInfo dataCenter = info.getDataCenterInfo();
if (instanceId != null && Name.Amazon != dataCenter.getName()) { if (instanceId != null && Name.Amazon != dataCenter.getName()) {
String old = info.getId(); String old = info.getId();
String id = old.endsWith(instanceId) ? old : old + ":" + instanceId;
info = new InstanceInfo.Builder(info).setDataCenterInfo( info = new InstanceInfo.Builder(info).setDataCenterInfo(
new InstanceIdDataCenterInfo(old + ":" + instanceId)).build(); new InstanceIdDataCenterInfo(id)).build();
obj = info; obj = info;
} }
return obj; return obj;
......
...@@ -33,6 +33,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; ...@@ -33,6 +33,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import com.netflix.appinfo.DataCenterInfo; import com.netflix.appinfo.DataCenterInfo;
import com.netflix.appinfo.EurekaInstanceConfig; import com.netflix.appinfo.EurekaInstanceConfig;
import com.netflix.appinfo.InstanceInfo.InstanceStatus; import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.appinfo.UniqueIdentifier;
/** /**
* @author Dave Syer * @author Dave Syer
...@@ -76,10 +77,7 @@ public class EurekaInstanceConfigBean implements EurekaInstanceConfig { ...@@ -76,10 +77,7 @@ public class EurekaInstanceConfigBean implements EurekaInstanceConfig {
private Map<String, String> metadataMap = new HashMap<>(); private Map<String, String> metadataMap = new HashMap<>();
private DataCenterInfo dataCenterInfo = new DataCenterInfo() { private DataCenterInfo dataCenterInfo = new IdentifyingDataCenterInfo();
@Getter @Setter
private Name name = Name.MyOwn;
};
private String ipAddress = hostInfo[0]; private String ipAddress = hostInfo[0];
...@@ -130,4 +128,21 @@ public class EurekaInstanceConfigBean implements EurekaInstanceConfig { ...@@ -130,4 +128,21 @@ public class EurekaInstanceConfigBean implements EurekaInstanceConfig {
return preferIpAddress ? ipAddress : hostname; return preferIpAddress ? ipAddress : hostname;
} }
private final class IdentifyingDataCenterInfo implements DataCenterInfo, UniqueIdentifier {
@Getter @Setter
private Name name = Name.MyOwn;
@Override
public String getId() {
String instanceId = metadataMap.get("instanceId");
if (instanceId != null) {
String old = hostname;
String id = old.endsWith(instanceId) ? old : old + ":" + instanceId;
return id;
}
return hostname;
}
}
} }
...@@ -22,10 +22,12 @@ import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfigurati ...@@ -22,10 +22,12 @@ import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfigurati
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.AbstractApplicationContext;
import com.netflix.appinfo.UniqueIdentifier;
import com.netflix.appinfo.InstanceInfo.InstanceStatus; import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.springframework.boot.test.EnvironmentTestUtils.*; import static org.springframework.boot.test.EnvironmentTestUtils.*;
/** /**
...@@ -44,102 +46,101 @@ public class EurekaInstanceConfigBeanTests { ...@@ -44,102 +46,101 @@ public class EurekaInstanceConfigBeanTests {
} }
@Test @Test
public void idFromInstanceId() throws Exception {
EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean();
instance.getMetadataMap().put("instanceId", "foo");
instance.setHostname("bar");
assertEquals("bar:foo", ((UniqueIdentifier) instance.getDataCenterInfo()).getId());
}
@Test
public void basicBinding() { public void basicBinding() {
addEnvironment(context, "eureka.instance.appGroupName=mygroup"); addEnvironment(context, "eureka.instance.appGroupName=mygroup");
setupContext(); setupContext();
assertEquals("mygroup", getInstanceConfig().getAppGroupName()); assertEquals("mygroup", getInstanceConfig().getAppGroupName());
} }
@Test @Test
public void nonSecurePort() { public void nonSecurePort() {
testNonSecurePort("eureka.instance.nonSecurePort"); testNonSecurePort("eureka.instance.nonSecurePort");
} }
@Test @Test
public void nonSecurePort2() { public void nonSecurePort2() {
testNonSecurePort("server.port"); testNonSecurePort("server.port");
} }
@Test @Test
public void nonSecurePort3() { public void nonSecurePort3() {
testNonSecurePort("SERVER_PORT"); testNonSecurePort("SERVER_PORT");
} }
@Test @Test
public void nonSecurePort4() { public void nonSecurePort4() {
testNonSecurePort("PORT"); testNonSecurePort("PORT");
} }
private void testNonSecurePort(String propName) { private void testNonSecurePort(String propName) {
addEnvironment(context, propName + ":8888"); addEnvironment(context, propName + ":8888");
setupContext(); setupContext();
assertEquals(8888, getInstanceConfig().getNonSecurePort()); assertEquals(8888, getInstanceConfig().getNonSecurePort());
} }
@Test @Test
public void testDefaultInitialStatus() { public void testDefaultInitialStatus() {
setupContext(); setupContext();
assertEquals("initialStatus wrong", InstanceStatus.UP, assertEquals("initialStatus wrong", InstanceStatus.UP, getInstanceConfig()
getInstanceConfig().getInitialStatus()); .getInitialStatus());
} }
@Test(expected = BeanCreationException.class) @Test(expected = BeanCreationException.class)
public void testBadInitialStatus() { public void testBadInitialStatus() {
addEnvironment(context, "eureka.instance.initial-status:FOO"); addEnvironment(context, "eureka.instance.initial-status:FOO");
setupContext(); setupContext();
} }
@Test @Test
public void testCustomInitialStatus() { public void testCustomInitialStatus() {
addEnvironment(context, "eureka.instance.initial-status:STARTING"); addEnvironment(context, "eureka.instance.initial-status:STARTING");
setupContext(); setupContext();
assertEquals("initialStatus wrong", InstanceStatus.STARTING, assertEquals("initialStatus wrong", InstanceStatus.STARTING, getInstanceConfig()
getInstanceConfig().getInitialStatus()); .getInitialStatus());
} }
private void setupContext() { private void setupContext() {
context.register(PropertyPlaceholderAutoConfiguration.class, context.register(PropertyPlaceholderAutoConfiguration.class,
TestConfiguration.class); TestConfiguration.class);
context.refresh(); context.refresh();
} }
protected EurekaInstanceConfigBean getInstanceConfig() { protected EurekaInstanceConfigBean getInstanceConfig() {
return context.getBean(EurekaInstanceConfigBean.class); return context.getBean(EurekaInstanceConfigBean.class);
} }
/* /*
@Test * @Test public void serviceUrlWithCompositePropertySource() { CompositePropertySource
public void serviceUrlWithCompositePropertySource() { * source = new CompositePropertySource("composite");
CompositePropertySource source = new CompositePropertySource("composite"); * context.getEnvironment().getPropertySources().addFirst(source);
context.getEnvironment().getPropertySources().addFirst(source); * source.addPropertySource(new MapPropertySource("config", Collections .<String,
source.addPropertySource(new MapPropertySource("config", Collections * Object> singletonMap("eureka.client.serviceUrl.defaultZone",
.<String, Object> singletonMap("eureka.client.serviceUrl.defaultZone", * "http://example.com")));
"http://example.com"))); * context.register(PropertyPlaceholderAutoConfiguration.class,
context.register(PropertyPlaceholderAutoConfiguration.class, * TestConfiguration.class); context.refresh();
TestConfiguration.class); * assertEquals("{defaultZone=http://example.com}",
context.refresh(); * context.getBean(EurekaInstanceConfigBean.class).getServiceUrl().toString());
assertEquals("{defaultZone=http://example.com}", * assertEquals( "[http://example.com]",
context.getBean(EurekaInstanceConfigBean.class).getServiceUrl().toString()); * context.getBean(EurekaInstanceConfigBean.class)
assertEquals( * .getEurekaServerServiceUrls("defaultZone").toString()); }
"[http://example.com]", *
context.getBean(EurekaInstanceConfigBean.class) * @Test public void serviceUrlWithDefault() {
.getEurekaServerServiceUrls("defaultZone").toString()); * EnvironmentTestUtils.addEnvironment(context,
} * "eureka.client.serviceUrl.defaultZone:",
* "eureka.client.serviceUrl.default:http://example.com");
@Test * context.register(PropertyPlaceholderAutoConfiguration.class,
public void serviceUrlWithDefault() { * TestConfiguration.class); context.refresh(); assertEquals( "[http://example.com]",
EnvironmentTestUtils.addEnvironment(context, * context.getBean(EurekaInstanceConfigBean.class)
"eureka.client.serviceUrl.defaultZone:", * .getEurekaServerServiceUrls("defaultZone").toString()); }
"eureka.client.serviceUrl.default:http://example.com"); */
context.register(PropertyPlaceholderAutoConfiguration.class,
TestConfiguration.class);
context.refresh();
assertEquals(
"[http://example.com]",
context.getBean(EurekaInstanceConfigBean.class)
.getEurekaServerServiceUrls("defaultZone").toString());
}
*/
@Configuration @Configuration
@EnableConfigurationProperties(EurekaInstanceConfigBean.class) @EnableConfigurationProperties(EurekaInstanceConfigBean.class)
protected static class TestConfiguration { protected static class TestConfiguration {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment