From da18301cd77d38058375a3adae339638e8885068 Mon Sep 17 00:00:00 2001 From: Brian Tucker Date: Wed, 13 Sep 2023 17:09:31 -0700 Subject: [PATCH 1/8] Add support for EIPs associated with secondary ENIs --- .../java/com/netflix/appinfo/AmazonInfo.java | 25 +++++++++++++++++++ .../netflix/appinfo/CloudInstanceConfig.java | 6 +++++ 2 files changed, 31 insertions(+) diff --git a/eureka-client/src/main/java/com/netflix/appinfo/AmazonInfo.java b/eureka-client/src/main/java/com/netflix/appinfo/AmazonInfo.java index 67eb6e672c..60a2989717 100644 --- a/eureka-client/src/main/java/com/netflix/appinfo/AmazonInfo.java +++ b/eureka-client/src/main/java/com/netflix/appinfo/AmazonInfo.java @@ -22,6 +22,7 @@ import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; @@ -63,6 +64,13 @@ public enum MetaDataKey { availabilityZone("availability-zone", "placement/"), publicHostname("public-hostname"), publicIpv4("public-ipv4"), + macs("macs", "network/interfaces/"), // macs declared above public-ipv4s so will be found before publicIpv4s (where it is needed) + publicIpv4s("public-ipv4s", "network/interfaces/macs/") { + @Override + public URL getURL(String prepend, String mac) throws MalformedURLException { + return new URL(AWS_METADATA_URL + this.path + mac + "/" + this.name); + } + }, ipv6("ipv6"), spotTerminationTime("termination-time", "spot/"), spotInstanceAction("instance-action", "spot/"), @@ -200,10 +208,27 @@ public AmazonInfo autoBuild(String namespace) { int numOfRetries = config.getNumRetries(); while (numOfRetries-- > 0) { try { + if (key == MetaDataKey.publicIpv4s) { + // macs should be read before publicIpv4s due to declaration order + String[] macs = result.metadata.get(MetaDataKey.macs.getName()).split("\n"); + for (String mac : macs) { + URL url = key.getURL(null, mac); + String publicIpv4s = AmazonInfoUtils.readEc2MetadataUrl(key, url, config.getConnectTimeout(), config.getReadTimeout()); + + if (publicIpv4s != null) { + // only support registering the first found public IPv4 address + result.metadata.put(key.getName(), publicIpv4s.split("\n")[0]); + break; + } + } + break; + } + String mac = null; if (key == MetaDataKey.vpcId) { mac = result.metadata.get(MetaDataKey.mac.getName()); // mac should be read before vpcId due to declaration order } + URL url = key.getURL(null, mac); String value = AmazonInfoUtils.readEc2MetadataUrl(key, url, config.getConnectTimeout(), config.getReadTimeout()); if (value != null) { diff --git a/eureka-client/src/main/java/com/netflix/appinfo/CloudInstanceConfig.java b/eureka-client/src/main/java/com/netflix/appinfo/CloudInstanceConfig.java index 356a7052ce..84aef61680 100644 --- a/eureka-client/src/main/java/com/netflix/appinfo/CloudInstanceConfig.java +++ b/eureka-client/src/main/java/com/netflix/appinfo/CloudInstanceConfig.java @@ -137,6 +137,12 @@ private String getPrivateIpv4Addr() { } private String getPublicIpv4Addr() { String publicIpv4Addr = amazonInfoHolder.get().get(MetaDataKey.publicIpv4); + if (publicIpv4Addr == null) { + // publicIpv4s is named as a plural to not conflict with the existing publicIpv4 key. In AmazonInfo.java, + // we filter out for the first found IPv4 address in any of the network interface IMDS keys. If it exists, + // the value for MetaDataKey.publicIpv4s will always be a string with at most 1 public IPv4 address. + publicIpv4Addr = amazonInfoHolder.get().get(MetaDataKey.publicIpv4s); + } return publicIpv4Addr == null ? super.getIpAddress() : publicIpv4Addr; } From c0e280d710eab038d2bfa5c5c5b12f47bd7a95ca Mon Sep 17 00:00:00 2001 From: Brian Tucker Date: Wed, 8 Nov 2023 07:36:01 -0800 Subject: [PATCH 2/8] Added tests --- build.gradle | 2 +- eureka-client/build.gradle | 2 +- .../java/com/netflix/appinfo/AmazonInfo.java | 26 +++++- .../com/netflix/appinfo/AmazonInfoTest.java | 89 +++++++++++++++++++ .../appinfo/CloudInstanceConfigTest.java | 11 +++ 5 files changed, 124 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 5ee56d9360..0215430ab2 100644 --- a/build.gradle +++ b/build.gradle @@ -52,7 +52,7 @@ allprojects { // test deps jetty_version = '7.2.0.v20101020' junit_version = '4.11' - mockitoVersion = '1.10.19' + mockitoVersion = '3.4.0' mockserverVersion = '3.9.2' } } diff --git a/eureka-client/build.gradle b/eureka-client/build.gradle index a6e556b4a3..a27b477cb0 100644 --- a/eureka-client/build.gradle +++ b/eureka-client/build.gradle @@ -35,7 +35,7 @@ dependencies { testCompile project(':eureka-test-utils') testCompile "junit:junit:${junit_version}" testCompile 'org.mortbay.jetty:jetty:6.1H.22' - testCompile "org.mockito:mockito-core:${mockitoVersion}" + testCompile "org.mockito:mockito-inline:${mockitoVersion}" testCompile "org.mock-server:mockserver-netty:${mockserverVersion}" testCompile "com.netflix.governator:governator:${governatorVersion}" testCompile "com.github.tomakehurst:wiremock-jre8:2.25.1" diff --git a/eureka-client/src/main/java/com/netflix/appinfo/AmazonInfo.java b/eureka-client/src/main/java/com/netflix/appinfo/AmazonInfo.java index 60a2989717..916a04576f 100644 --- a/eureka-client/src/main/java/com/netflix/appinfo/AmazonInfo.java +++ b/eureka-client/src/main/java/com/netflix/appinfo/AmazonInfo.java @@ -22,11 +22,11 @@ import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -64,7 +64,26 @@ public enum MetaDataKey { availabilityZone("availability-zone", "placement/"), publicHostname("public-hostname"), publicIpv4("public-ipv4"), - macs("macs", "network/interfaces/"), // macs declared above public-ipv4s so will be found before publicIpv4s (where it is needed) + // macs declared above public-ipv4s so will be found before publicIpv4s (where it is needed) + macs("macs", "network/interfaces/") { + @Override + public String read(InputStream inputStream) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); + String toReturn; + try { + toReturn = br.lines().collect(Collectors.joining("\n")); + } finally { + br.close(); + } + + return toReturn; + } + }, + // Because we stop reading the body returned by IMDS after the first newline, + // publicIPv4s will only contain the first IPv4 address returned in the body of the + // URL for a given MAC, despite IMDS being able to return multiple addresses under + // that key (separated by \n). The first IPv4 address is always the one attached to + // the interface and is the only address that should be registered for this instance. publicIpv4s("public-ipv4s", "network/interfaces/macs/") { @Override public URL getURL(String prepend, String mac) throws MalformedURLException { @@ -216,8 +235,7 @@ public AmazonInfo autoBuild(String namespace) { String publicIpv4s = AmazonInfoUtils.readEc2MetadataUrl(key, url, config.getConnectTimeout(), config.getReadTimeout()); if (publicIpv4s != null) { - // only support registering the first found public IPv4 address - result.metadata.put(key.getName(), publicIpv4s.split("\n")[0]); + result.metadata.put(key.getName(), publicIpv4s); break; } } diff --git a/eureka-client/src/test/java/com/netflix/appinfo/AmazonInfoTest.java b/eureka-client/src/test/java/com/netflix/appinfo/AmazonInfoTest.java index 2be327ab96..ab096ff048 100644 --- a/eureka-client/src/test/java/com/netflix/appinfo/AmazonInfoTest.java +++ b/eureka-client/src/test/java/com/netflix/appinfo/AmazonInfoTest.java @@ -2,10 +2,18 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; +import java.net.URL; +import com.netflix.discovery.internal.util.AmazonInfoUtils; import org.junit.Test; +import org.mockito.MockedStatic; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; + import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; /** * @author David Liu @@ -34,4 +42,85 @@ public void testExtractAccountId() throws Exception { assertEquals("1111111111", accountId); } + + @Test + public void testExtractMacs_SingleMac() throws Exception { + String body = "0d:c2:9a:3c:18:2b"; + + InputStream inputStream = new ByteArrayInputStream(body.getBytes()); + String macs = AmazonInfo.MetaDataKey.macs.read(inputStream); + + assertEquals("0d:c2:9a:3c:18:2b", macs); + } + + @Test + public void testExtractMacs_MultipleMacs() throws Exception { + String body = "0d:c2:9a:3c:18:2b\n4c:31:99:7e:26:d6"; + + InputStream inputStream = new ByteArrayInputStream(body.getBytes()); + String macs = AmazonInfo.MetaDataKey.macs.read(inputStream); + + assertEquals("0d:c2:9a:3c:18:2b\n4c:31:99:7e:26:d6", macs); + } + + @Test + public void testExtractPublicIPv4s_SingleAddress() throws Exception { + String body = "10.0.0.1"; + + InputStream inputStream = new ByteArrayInputStream(body.getBytes()); + String publicIPv4s = AmazonInfo.MetaDataKey.publicIpv4s.read(inputStream); + + assertEquals("10.0.0.1", publicIPv4s); + } + + @Test + public void testExtractPublicIPv4s_MultipleAddresses() throws Exception { + String body = "10.0.0.1\n10.0.0.2"; + + InputStream inputStream = new ByteArrayInputStream(body.getBytes()); + String publicIPv4s = AmazonInfo.MetaDataKey.publicIpv4s.read(inputStream); + + assertEquals("10.0.0.1", publicIPv4s); + } + + @Test + public void testAutoBuild() throws Exception { + try (MockedStatic mockUtils = mockStatic(AmazonInfoUtils.class)) { + mockUtils.when( + () -> AmazonInfoUtils.readEc2MetadataUrl(any(AmazonInfo.MetaDataKey.class), any(URL.class), anyInt(), anyInt()) + ).thenReturn(null); + + mockUtils.when( + () -> AmazonInfoUtils.readEc2MetadataUrl(any(AmazonInfo.MetaDataKey.class), any(URL.class), anyInt(), anyInt()) + ).thenReturn(null); + + URL macsUrl = AmazonInfo.MetaDataKey.macs.getURL(null, null); + mockUtils.when( + () -> AmazonInfoUtils.readEc2MetadataUrl(eq(AmazonInfo.MetaDataKey.macs), eq(macsUrl), anyInt(), anyInt()) + ).thenReturn("0d:c2:9a:3c:18:2b\n4c:31:99:7e:26:d6"); + + URL firstMacPublicIPv4sUrl = AmazonInfo.MetaDataKey.publicIpv4s.getURL(null, "0d:c2:9a:3c:18:2b"); + mockUtils.when( + () -> AmazonInfoUtils.readEc2MetadataUrl(eq(AmazonInfo.MetaDataKey.publicIpv4s), eq(firstMacPublicIPv4sUrl), anyInt(), anyInt()) + ).thenReturn(null); + + URL secondMacPublicIPv4sUrl = AmazonInfo.MetaDataKey.publicIpv4s.getURL(null, "4c:31:99:7e:26:d6"); + mockUtils.when( + () -> AmazonInfoUtils.readEc2MetadataUrl(eq(AmazonInfo.MetaDataKey.publicIpv4s), eq(secondMacPublicIPv4sUrl), anyInt(), anyInt()) + ).thenReturn("10.0.0.1"); + + AmazonInfoConfig config = mock(AmazonInfoConfig.class); + when(config.getNamespace()).thenReturn("test_namespace"); + when(config.getConnectTimeout()).thenReturn(10); + when(config.getNumRetries()).thenReturn(1); + when(config.getReadTimeout()).thenReturn(10); + when(config.shouldLogAmazonMetadataErrors()).thenReturn(false); + when(config.shouldValidateInstanceId()).thenReturn(false); + when(config.shouldFailFastOnFirstLoad()).thenReturn(false); + + AmazonInfo info = AmazonInfo.Builder.newBuilder().withAmazonInfoConfig(config).autoBuild("test_namespace"); + + assertEquals("10.0.0.1", info.get(AmazonInfo.MetaDataKey.publicIpv4s)); + } + } } diff --git a/eureka-client/src/test/java/com/netflix/appinfo/CloudInstanceConfigTest.java b/eureka-client/src/test/java/com/netflix/appinfo/CloudInstanceConfigTest.java index 299d96e838..16bdf45772 100644 --- a/eureka-client/src/test/java/com/netflix/appinfo/CloudInstanceConfigTest.java +++ b/eureka-client/src/test/java/com/netflix/appinfo/CloudInstanceConfigTest.java @@ -55,6 +55,17 @@ public void testBroadcastPublicIpv4Address() { assertEquals(instanceInfo.getIPAddr(), config.getIpAddress()); } + @Test + public void testBroadcastPublicIpv4Address_usingPublicIpv4s() { + AmazonInfo info = (AmazonInfo) instanceInfo.getDataCenterInfo(); + info.getMetadata().remove(AmazonInfo.MetaDataKey.publicIpv4.getName()); + info.getMetadata().put(AmazonInfo.MetaDataKey.publicIpv4s.getName(), "10.0.0.1"); + + config = createConfig(info); + + assertEquals("10.0.0.1", config.getIpAddress()); + } + private CloudInstanceConfig createConfig(AmazonInfo info) { return new CloudInstanceConfig(info) { From ac96965653ff41a090534fc26aacae803fb66338 Mon Sep 17 00:00:00 2001 From: Brian Tucker Date: Wed, 8 Nov 2023 08:43:16 -0800 Subject: [PATCH 3/8] fix flaky tests --- .../com/netflix/eureka/GzipEncodingEnforcingFilterTest.java | 1 - .../netflix/discovery/shared/transport/ClusterSampleData.java | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/eureka-core/src/test/java/com/netflix/eureka/GzipEncodingEnforcingFilterTest.java b/eureka-core/src/test/java/com/netflix/eureka/GzipEncodingEnforcingFilterTest.java index 58869251cd..597181d0ed 100644 --- a/eureka-core/src/test/java/com/netflix/eureka/GzipEncodingEnforcingFilterTest.java +++ b/eureka-core/src/test/java/com/netflix/eureka/GzipEncodingEnforcingFilterTest.java @@ -89,7 +89,6 @@ public void testForceGzip() throws Exception { @Test public void testForceGzipOtherHeader() throws Exception { noneGzipRequest(); - when(request.getHeader("Test")).thenReturn("ok"); when(request.getHeaders("Test")).thenReturn(new Enumeration() { private int c = 0; diff --git a/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java b/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java index 0b2bd2b2b6..07bacc4e23 100644 --- a/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java +++ b/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java @@ -99,8 +99,8 @@ public static ReplicationInstance newReplicationInstanceOf(Action action, Instan instance.getAppName(), instance.getId(), System.currentTimeMillis(), - InstanceStatus.OUT_OF_SERVICE.name(), null, + InstanceStatus.OUT_OF_SERVICE.name(), null, action ); @@ -110,7 +110,7 @@ public static ReplicationInstance newReplicationInstanceOf(Action action, Instan instance.getId(), System.currentTimeMillis(), InstanceStatus.OUT_OF_SERVICE.name(), - null, + InstanceStatus.UP.name(), null, action ); From 4e35eb01207c51c0be3b9efb3b93b6d085c99934 Mon Sep 17 00:00:00 2001 From: Brian Tucker Date: Wed, 8 Nov 2023 09:08:58 -0800 Subject: [PATCH 4/8] fix more flaky test --- .../java/com/netflix/discovery/InstanceInfoReplicatorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eureka-client/src/test/java/com/netflix/discovery/InstanceInfoReplicatorTest.java b/eureka-client/src/test/java/com/netflix/discovery/InstanceInfoReplicatorTest.java index 4174dc694e..76e5417469 100644 --- a/eureka-client/src/test/java/com/netflix/discovery/InstanceInfoReplicatorTest.java +++ b/eureka-client/src/test/java/com/netflix/discovery/InstanceInfoReplicatorTest.java @@ -76,7 +76,7 @@ public void testOnDemandUpdateRateLimiting() throws Throwable { assertTrue(replicator.onDemandUpdate()); Thread.sleep(10); // give some time for execution assertFalse(replicator.onDemandUpdate()); - Thread.sleep(10); + Thread.sleep(1000); verify(discoveryClient, times(2)).refreshInstanceInfo(); verify(discoveryClient, times(1)).register(); From c316bcd547e3a851423578888242eaf1fafbac81 Mon Sep 17 00:00:00 2001 From: Brian Tucker Date: Wed, 8 Nov 2023 09:15:35 -0800 Subject: [PATCH 5/8] explicit imports --- .../src/test/java/com/netflix/appinfo/AmazonInfoTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/eureka-client/src/test/java/com/netflix/appinfo/AmazonInfoTest.java b/eureka-client/src/test/java/com/netflix/appinfo/AmazonInfoTest.java index ab096ff048..d8431730ce 100644 --- a/eureka-client/src/test/java/com/netflix/appinfo/AmazonInfoTest.java +++ b/eureka-client/src/test/java/com/netflix/appinfo/AmazonInfoTest.java @@ -11,9 +11,13 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.mockStatic; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.*; + /** * @author David Liu From 470ff15c90b71f2b4fcc07f53b0f9d1d947d5976 Mon Sep 17 00:00:00 2001 From: Brian Tucker Date: Wed, 8 Nov 2023 09:19:18 -0800 Subject: [PATCH 6/8] test something --- .../netflix/discovery/shared/transport/ClusterSampleData.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java b/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java index 07bacc4e23..0b2bd2b2b6 100644 --- a/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java +++ b/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java @@ -99,9 +99,9 @@ public static ReplicationInstance newReplicationInstanceOf(Action action, Instan instance.getAppName(), instance.getId(), System.currentTimeMillis(), - null, InstanceStatus.OUT_OF_SERVICE.name(), null, + null, action ); case DeleteStatusOverride: @@ -110,7 +110,7 @@ public static ReplicationInstance newReplicationInstanceOf(Action action, Instan instance.getId(), System.currentTimeMillis(), InstanceStatus.OUT_OF_SERVICE.name(), - InstanceStatus.UP.name(), + null, null, action ); From 0c7f8ef4c2e5c6f168b4e8dfa923f9c86f5a72dc Mon Sep 17 00:00:00 2001 From: Brian Tucker Date: Wed, 8 Nov 2023 09:26:26 -0800 Subject: [PATCH 7/8] no idea how these worked before --- .../netflix/discovery/shared/transport/ClusterSampleData.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java b/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java index 0b2bd2b2b6..07bacc4e23 100644 --- a/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java +++ b/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java @@ -99,8 +99,8 @@ public static ReplicationInstance newReplicationInstanceOf(Action action, Instan instance.getAppName(), instance.getId(), System.currentTimeMillis(), - InstanceStatus.OUT_OF_SERVICE.name(), null, + InstanceStatus.OUT_OF_SERVICE.name(), null, action ); @@ -110,7 +110,7 @@ public static ReplicationInstance newReplicationInstanceOf(Action action, Instan instance.getId(), System.currentTimeMillis(), InstanceStatus.OUT_OF_SERVICE.name(), - null, + InstanceStatus.UP.name(), null, action ); From f4b1fa2405f610d696b0b27867f15fc333fba4c7 Mon Sep 17 00:00:00 2001 From: Brian Tucker Date: Thu, 9 Nov 2023 15:51:04 -0800 Subject: [PATCH 8/8] adjust clustersampledata --- .../netflix/discovery/shared/transport/ClusterSampleData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java b/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java index 07bacc4e23..a03fcb9d3f 100644 --- a/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java +++ b/eureka-test-utils/src/main/java/com/netflix/discovery/shared/transport/ClusterSampleData.java @@ -109,7 +109,7 @@ public static ReplicationInstance newReplicationInstanceOf(Action action, Instan instance.getAppName(), instance.getId(), System.currentTimeMillis(), - InstanceStatus.OUT_OF_SERVICE.name(), + null, InstanceStatus.UP.name(), null, action