From de10b25a007097c36ddaabf32a403bcc40469eaa Mon Sep 17 00:00:00 2001 From: Muhammet Yazici Date: Mon, 10 Aug 2020 11:53:44 +0300 Subject: [PATCH 1/4] Implement auto-config --- pom.xml | 2 +- .../azure/AzureDiscoveryStrategyFactory.java | 49 ++++++++++++++- .../AzureDiscoveryStrategyFactoryTest.java | 59 +++++++++++++++++++ 3 files changed, 108 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 80e8d61..03af452 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ 1.8 - 4.0.2 + 4.1-SNAPSHOT 4.13 1.10.19 diff --git a/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java b/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java index 98e72ad..0de9b1f 100644 --- a/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java +++ b/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java @@ -16,11 +16,18 @@ package com.hazelcast.azure; import com.hazelcast.config.properties.PropertyDefinition; +import com.hazelcast.internal.nio.IOUtil; import com.hazelcast.logging.ILogger; +import com.hazelcast.logging.Logger; import com.hazelcast.spi.discovery.DiscoveryNode; import com.hazelcast.spi.discovery.DiscoveryStrategy; import com.hazelcast.spi.discovery.DiscoveryStrategyFactory; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -30,7 +37,7 @@ * Factory class which returns {@link AzureDiscoveryStrategy} to Discovery SPI */ public class AzureDiscoveryStrategyFactory implements DiscoveryStrategyFactory { - + private static final ILogger LOGGER = Logger.getLogger(AzureDiscoveryStrategyFactory.class); @Override public Class getDiscoveryStrategyType() { return AzureDiscoveryStrategy.class; @@ -50,4 +57,44 @@ public Collection getConfigurationProperties() { } return result; } + @Override + public boolean isAutoDetectionApplicable() { + return azureDnsServerConfigured() && azureInstanceMetadataAvailable(); + } + + private static boolean azureDnsServerConfigured() { + return readFileContents("/etc/resolv.conf").contains("168.63.129.16"); + } + + static String readFileContents(String fileName) { + InputStream is = null; + try { + File file = new File(fileName); + byte[] data = new byte[(int) file.length()]; + is = new FileInputStream(file); + is.read(data); + return new String(data, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Could not get " + fileName, e); + } finally { + IOUtil.closeResource(is); + } + } + + private static boolean azureInstanceMetadataAvailable() { + return isEndpointAvailable("http://169.254.169.254/metadata/instance?api-version=2020-06-01"); + } + + + static boolean isEndpointAvailable(String url) { + return !RestClient.create(url) + .withHeader("Metadata", "True") + .get() + .isEmpty(); + } + + @Override + public DiscoveryStrategyLevel discoveryStrategyLevel() { + return DiscoveryStrategyLevel.CLOUD_VM; + } } diff --git a/src/test/java/com/hazelcast/azure/AzureDiscoveryStrategyFactoryTest.java b/src/test/java/com/hazelcast/azure/AzureDiscoveryStrategyFactoryTest.java index 92d3b31..b7bbe8f 100644 --- a/src/test/java/com/hazelcast/azure/AzureDiscoveryStrategyFactoryTest.java +++ b/src/test/java/com/hazelcast/azure/AzureDiscoveryStrategyFactoryTest.java @@ -15,20 +15,37 @@ package com.hazelcast.azure; +import com.github.tomakehurst.wiremock.junit.WireMockRule; import com.hazelcast.config.Config; import com.hazelcast.config.DiscoveryConfig; import com.hazelcast.config.XmlConfigBuilder; +import com.hazelcast.internal.nio.IOUtil; import com.hazelcast.spi.discovery.DiscoveryStrategy; import com.hazelcast.spi.discovery.impl.DefaultDiscoveryService; import com.hazelcast.spi.discovery.integration.DiscoveryServiceSettings; +import org.junit.Rule; import org.junit.Test; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; import java.util.Iterator; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class AzureDiscoveryStrategyFactoryTest { + @Rule + public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); @Test public void validConfiguration() { @@ -51,4 +68,46 @@ private static DiscoveryStrategy createStrategy(String xmlFileName) { Iterator strategies = service.getDiscoveryStrategies().iterator(); return strategies.next(); } + + @Test + public void isEndpointAvailable() { + // given + String endpoint = "/some-endpoint"; + String url = String.format("http://localhost:%d%s", wireMockRule.port(), endpoint); + stubFor(get(urlEqualTo(endpoint)).willReturn(aResponse().withStatus(200).withBody("some-body"))); + + // when + boolean isAvailable = AzureDiscoveryStrategyFactory.isEndpointAvailable(url); + + // then + assertTrue(isAvailable); + } + + @Test + public void readFileContents() + throws IOException { + // given + String expectedContents = "Hello, world!\nThis is a test with Unicode ✓."; + String testFile = createTestFile(expectedContents); + + // when + String actualContents = AzureDiscoveryStrategyFactory.readFileContents(testFile); + + // then + assertEquals(expectedContents, actualContents); + } + + private static String createTestFile(String expectedContents) + throws IOException { + File temp = File.createTempFile("test", ".tmp"); + temp.deleteOnExit(); + BufferedWriter bufferedWriter = null; + try { + bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(temp), StandardCharsets.UTF_8)); + bufferedWriter.write(expectedContents); + } finally { + IOUtil.closeResource(bufferedWriter); + } + return temp.getAbsolutePath(); + } } \ No newline at end of file From 145b3651f11115e85f5c91246e82aed7f219c196 Mon Sep 17 00:00:00 2001 From: Muhammet Yazici Date: Mon, 10 Aug 2020 12:28:32 +0300 Subject: [PATCH 2/4] Add documentation --- .../azure/AzureDiscoveryStrategyFactory.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java b/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java index 0de9b1f..7a15873 100644 --- a/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java +++ b/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java @@ -57,6 +57,18 @@ public Collection getConfigurationProperties() { } return result; } + + /** + * Checks if Hazelcast is running on Azure. + *

+ * To check if Hazelcast is running on Azure, we first check whether Azure DNS name server is configured as "168.63.129.16" + * in "/etc/resolv.conf". Such an approach is not officially documented but seems like a good enough heuristic to detect an + * Azure Compute VM Instance. Since it's not the official method, we still need to make an API call to a local, non-routable + * address http://169.254.169.254/metadata/instance. + * + * @return true if running on Azure Instance + * @see https://docs.microsoft.com/en-us/azure/virtual-machines/linux/instance-metadata-service#metadata-apis + */ @Override public boolean isAutoDetectionApplicable() { return azureDnsServerConfigured() && azureInstanceMetadataAvailable(); From b9645491fb931a53ee2824698217e3573621882c Mon Sep 17 00:00:00 2001 From: Muhammet Yazici <44066377+mtyazici@users.noreply.github.com> Date: Wed, 12 Aug 2020 09:11:16 +0300 Subject: [PATCH 3/4] Update src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafał Leszko --- .../java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java b/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java index 7a15873..ac4a03c 100644 --- a/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java +++ b/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java @@ -37,7 +37,9 @@ * Factory class which returns {@link AzureDiscoveryStrategy} to Discovery SPI */ public class AzureDiscoveryStrategyFactory implements DiscoveryStrategyFactory { + private static final ILogger LOGGER = Logger.getLogger(AzureDiscoveryStrategyFactory.class); + @Override public Class getDiscoveryStrategyType() { return AzureDiscoveryStrategy.class; From 65681c14d120f8e8b41213fb7837d960774a6dbb Mon Sep 17 00:00:00 2001 From: Muhammet Yazici Date: Wed, 12 Aug 2020 09:14:41 +0300 Subject: [PATCH 4/4] Update what we check for in "/etc/resolv.conf" We used to check a name server ip "168.63.129.16", but it seems this is not present in every Azure VM. --- .../com/hazelcast/azure/AzureDiscoveryStrategyFactory.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java b/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java index ac4a03c..f9ea145 100644 --- a/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java +++ b/src/main/java/com/hazelcast/azure/AzureDiscoveryStrategyFactory.java @@ -63,8 +63,8 @@ public Collection getConfigurationProperties() { /** * Checks if Hazelcast is running on Azure. *

- * To check if Hazelcast is running on Azure, we first check whether Azure DNS name server is configured as "168.63.129.16" - * in "/etc/resolv.conf". Such an approach is not officially documented but seems like a good enough heuristic to detect an + * To check if Hazelcast is running on Azure, we first check whether "internal.cloudapp.net" is present in the file + * "/etc/resolv.conf". Such an approach is not officially documented but seems like a good enough heuristic to detect an * Azure Compute VM Instance. Since it's not the official method, we still need to make an API call to a local, non-routable * address http://169.254.169.254/metadata/instance. * @@ -77,7 +77,7 @@ public boolean isAutoDetectionApplicable() { } private static boolean azureDnsServerConfigured() { - return readFileContents("/etc/resolv.conf").contains("168.63.129.16"); + return readFileContents("/etc/resolv.conf").contains("internal.cloudapp.net"); } static String readFileContents(String fileName) {