You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@helix.apache.org by jx...@apache.org on 2020/04/14 02:20:55 UTC
[helix] 04/10: Implement Azure cloud instance information processor
(#698)
This is an automated email from the ASF dual-hosted git repository.
jxue pushed a commit to branch helix-cloud
in repository https://gitbox.apache.org/repos/asf/helix.git
commit 83afe3a1544b9d32f5813ba67729d9776b93ed2f
Author: zhangmeng916 <56...@users.noreply.github.com>
AuthorDate: Fri Feb 7 15:24:29 2020 -0800
Implement Azure cloud instance information processor (#698)
Implement Azure cloud instance information processor. The processor performs the functions of fetching and parsing instance information from Azure cloud environment. The endpoint that the processor queries is a globally standard url, called Azure Instance Metadata Service.
---
helix-core/pom.xml | 5 +
.../cloud/CloudInstanceInformationProcessor.java | 1 -
.../cloud/azure/AzureCloudInstanceInformation.java | 11 +-
.../AzureCloudInstanceInformationProcessor.java | 113 +++++++++++++++++++--
.../org/apache/helix/cloud/MockHttpClient.java | 53 ++++++++++
...TestAzureCloudInstanceInformationProcessor.java | 69 +++++++++++++
helix-core/src/test/resources/AzureResponse.json | 104 +++++++++++++++++++
helix-rest/pom.xml | 5 -
8 files changed, 343 insertions(+), 18 deletions(-)
diff --git a/helix-core/pom.xml b/helix-core/pom.xml
index 45b6552..9aecf46 100644
--- a/helix-core/pom.xml
+++ b/helix-core/pom.xml
@@ -159,6 +159,11 @@ under the License.
<artifactId>metrics-core</artifactId>
<version>3.2.3</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.5.8</version>
+ </dependency>
</dependencies>
<build>
<resources>
diff --git a/helix-core/src/main/java/org/apache/helix/api/cloud/CloudInstanceInformationProcessor.java b/helix-core/src/main/java/org/apache/helix/api/cloud/CloudInstanceInformationProcessor.java
index a365777..9ff6b00 100644
--- a/helix-core/src/main/java/org/apache/helix/api/cloud/CloudInstanceInformationProcessor.java
+++ b/helix-core/src/main/java/org/apache/helix/api/cloud/CloudInstanceInformationProcessor.java
@@ -21,7 +21,6 @@ package org.apache.helix.api.cloud;
import java.util.List;
-
/**
* Generic interface to fetch and parse cloud instance information
*/
diff --git a/helix-core/src/main/java/org/apache/helix/cloud/azure/AzureCloudInstanceInformation.java b/helix-core/src/main/java/org/apache/helix/cloud/azure/AzureCloudInstanceInformation.java
index f7fd657..1fef205 100644
--- a/helix-core/src/main/java/org/apache/helix/cloud/azure/AzureCloudInstanceInformation.java
+++ b/helix-core/src/main/java/org/apache/helix/cloud/azure/AzureCloudInstanceInformation.java
@@ -19,17 +19,17 @@ package org.apache.helix.cloud.azure;
* under the License.
*/
+import java.util.HashMap;
import java.util.Map;
import org.apache.helix.api.cloud.CloudInstanceInformation;
-
public class AzureCloudInstanceInformation implements CloudInstanceInformation {
private Map<String, String> _cloudInstanceInfoMap;
/**
* Instantiate the AzureCloudInstanceInformation using each field individually.
- * Users should use AzureCloudInstanceInformation.Builder to create information.
+ * Users should use AzureCloudInstanceInformation.Builder to set field information.
* @param cloudInstanceInfoMap
*/
protected AzureCloudInstanceInformation(Map<String, String> cloudInstanceInfoMap) {
@@ -42,10 +42,11 @@ public class AzureCloudInstanceInformation implements CloudInstanceInformation {
}
public static class Builder {
- private Map<String, String> _cloudInstanceInfoMap = null;
+
+ private final Map<String, String> _cloudInstanceInfoMap = new HashMap<>();
public AzureCloudInstanceInformation build() {
- return new AzureCloudInstanceInformation(_cloudInstanceInfoMap);
+ return new AzureCloudInstanceInformation(new HashMap<>(_cloudInstanceInfoMap));
}
public Builder setInstanceName(String name) {
@@ -68,4 +69,4 @@ public class AzureCloudInstanceInformation implements CloudInstanceInformation {
return this;
}
}
-}
\ No newline at end of file
+}
diff --git a/helix-core/src/main/java/org/apache/helix/cloud/azure/AzureCloudInstanceInformationProcessor.java b/helix-core/src/main/java/org/apache/helix/cloud/azure/AzureCloudInstanceInformationProcessor.java
index 84a102c..85e2bb5 100644
--- a/helix-core/src/main/java/org/apache/helix/cloud/azure/AzureCloudInstanceInformationProcessor.java
+++ b/helix-core/src/main/java/org/apache/helix/cloud/azure/AzureCloudInstanceInformationProcessor.java
@@ -19,38 +19,137 @@ package org.apache.helix.cloud.azure;
* under the License.
*/
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
+import javax.net.ssl.SSLException;
+import org.apache.helix.HelixCloudProperty;
+import org.apache.helix.HelixException;
import org.apache.helix.api.cloud.CloudInstanceInformationProcessor;
+import org.apache.http.client.HttpRequestRetryHandler;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+public class AzureCloudInstanceInformationProcessor
+ implements CloudInstanceInformationProcessor<String> {
+ private static final Logger LOG =
+ LoggerFactory.getLogger(AzureCloudInstanceInformationProcessor.class);
+ private final CloseableHttpClient _closeableHttpClient;
+ private final HelixCloudProperty _helixCloudProperty;
+ private final String COMPUTE = "compute";
+ private final String INSTANCE_NAME = "vmId";
+ private final String DOMAIN = "platformFaultDomain";
+ private final String INSTANCE_SET_NAME = "vmScaleSetName";
-public class AzureCloudInstanceInformationProcessor implements CloudInstanceInformationProcessor<String> {
+ public AzureCloudInstanceInformationProcessor(HelixCloudProperty helixCloudProperty) {
+ _helixCloudProperty = helixCloudProperty;
- public AzureCloudInstanceInformationProcessor() {
+ RequestConfig requestConifg = RequestConfig.custom()
+ .setConnectionRequestTimeout((int) helixCloudProperty.getCloudRequestTimeout())
+ .setConnectTimeout((int) helixCloudProperty.getCloudConnectionTimeout()).build();
+
+ HttpRequestRetryHandler httpRequestRetryHandler =
+ (IOException exception, int executionCount, HttpContext context) -> {
+ LOG.warn("Execution count: " + executionCount + ".", exception);
+ return !(executionCount >= helixCloudProperty.getCloudMaxRetry()
+ || exception instanceof InterruptedIOException
+ || exception instanceof UnknownHostException || exception instanceof SSLException);
+ };
+
+ //TODO: we should regularize the way how httpClient should be used throughout Helix. e.g. Helix-rest could also use in the same way
+ _closeableHttpClient = HttpClients.custom().setDefaultRequestConfig(requestConifg)
+ .setRetryHandler(httpRequestRetryHandler).build();
}
/**
- * fetch the raw Azure cloud instance information
+ * This constructor is for unit test purpose only.
+ * User could provide helixCloudProperty and a mocked http client to test the functionality of
+ * this class.
+ */
+ public AzureCloudInstanceInformationProcessor(HelixCloudProperty helixCloudProperty,
+ CloseableHttpClient closeableHttpClient) {
+ _helixCloudProperty = helixCloudProperty;
+ _closeableHttpClient = closeableHttpClient;
+ }
+
+ /**
+ * Fetch raw Azure cloud instance information based on the urls provided
* @return raw Azure cloud instance information
*/
@Override
public List<String> fetchCloudInstanceInformation() {
List<String> response = new ArrayList<>();
- //TODO: implement the fetching logic
+ for (String url : _helixCloudProperty.getCloudInfoSources()) {
+ response.add(getAzureCloudInformationFromUrl(url));
+ }
return response;
}
/**
+ * Query Azure Instance Metadata Service to get the instance(VM) information
+ * @return raw Azure cloud instance information
+ */
+ private String getAzureCloudInformationFromUrl(String url) {
+ HttpGet httpGet = new HttpGet(url);
+ httpGet.setHeader("Metadata", "true");
+
+ try {
+ CloseableHttpResponse response = _closeableHttpClient.execute(httpGet);
+ if (response == null || response.getStatusLine().getStatusCode() != 200) {
+ String errorMsg = String.format(
+ "Failed to get an HTTP Response for the request. Response: {}. Status code: {}",
+ (response == null ? "NULL" : response.getStatusLine().getReasonPhrase()),
+ response.getStatusLine().getStatusCode());
+ throw new HelixException(errorMsg);
+ }
+ String responseString = EntityUtils.toString(response.getEntity());
+ LOG.info("VM instance information query result: {}", responseString);
+ return responseString;
+ } catch (IOException e) {
+ throw new HelixException(
+ String.format("Failed to get Azure cloud instance information from url {}", url), e);
+ }
+ }
+
+ /**
* Parse raw Azure cloud instance information.
* @return required azure cloud instance information
*/
@Override
public AzureCloudInstanceInformation parseCloudInstanceInformation(List<String> responses) {
AzureCloudInstanceInformation azureCloudInstanceInformation = null;
- //TODO: implement the parsing logic
+ if (responses.size() > 1) {
+ throw new HelixException("Multiple responses are not supported for Azure now");
+ }
+ String response = responses.get(0);
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ JsonNode jsonNode = mapper.readTree(response);
+ JsonNode computeNode = jsonNode.path(COMPUTE);
+ if (!computeNode.isMissingNode()) {
+ String vmName = computeNode.path(INSTANCE_NAME).getTextValue();
+ String platformFaultDomain = computeNode.path(DOMAIN).getTextValue();
+ String vmssName = computeNode.path(INSTANCE_SET_NAME).getValueAsText();
+ AzureCloudInstanceInformation.Builder builder = new AzureCloudInstanceInformation.Builder();
+ builder.setInstanceName(vmName).setFaultDomain(platformFaultDomain)
+ .setInstanceSetName(vmssName);
+ azureCloudInstanceInformation = builder.build();
+ }
+ } catch (IOException e) {
+ throw new HelixException(String.format("Error in parsing cloud instance information: {}", response, e));
+ }
return azureCloudInstanceInformation;
}
}
-
-
diff --git a/helix-core/src/test/java/org/apache/helix/cloud/MockHttpClient.java b/helix-core/src/test/java/org/apache/helix/cloud/MockHttpClient.java
new file mode 100644
index 0000000..03e687e
--- /dev/null
+++ b/helix-core/src/test/java/org/apache/helix/cloud/MockHttpClient.java
@@ -0,0 +1,53 @@
+package org.apache.helix.cloud;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import java.io.InputStream;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.StatusLine;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+
+
+/**
+ * Mock a http client and provide response using resource file. This is for unit test purpose only.
+ */
+public class MockHttpClient {
+ protected CloseableHttpClient createMockHttpClient(String file) throws Exception {
+ InputStream responseInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(file);
+ HttpEntity httpEntity = Mockito.mock(HttpEntity.class);
+ StatusLine statusLine = Mockito.mock(StatusLine.class);
+
+ CloseableHttpResponse mockCloseableHttpResponse = Mockito.mock(CloseableHttpResponse.class);
+ CloseableHttpClient mockCloseableHttpClient = Mockito.mock(CloseableHttpClient.class);
+
+ Mockito.when(httpEntity.getContent()).thenReturn(responseInputStream);
+ Mockito.when(mockCloseableHttpClient.execute(Matchers.any(HttpGet.class))).thenReturn(mockCloseableHttpResponse);
+ Mockito.when(mockCloseableHttpResponse.getEntity()).thenReturn(httpEntity);
+ Mockito.when(mockCloseableHttpResponse.getStatusLine()).thenReturn(statusLine);
+ Mockito.when(statusLine.getStatusCode()).thenReturn(200);
+
+ return mockCloseableHttpClient;
+ }
+}
\ No newline at end of file
diff --git a/helix-core/src/test/java/org/apache/helix/cloud/TestAzureCloudInstanceInformationProcessor.java b/helix-core/src/test/java/org/apache/helix/cloud/TestAzureCloudInstanceInformationProcessor.java
new file mode 100644
index 0000000..10ba42d
--- /dev/null
+++ b/helix-core/src/test/java/org/apache/helix/cloud/TestAzureCloudInstanceInformationProcessor.java
@@ -0,0 +1,69 @@
+package org.apache.helix.cloud;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import java.util.List;
+
+import org.apache.helix.HelixCloudProperty;
+import org.apache.helix.api.cloud.CloudInstanceInformation;
+import org.apache.helix.cloud.azure.AzureCloudInstanceInformation;
+import org.apache.helix.cloud.azure.AzureCloudInstanceInformationProcessor;
+import org.apache.helix.cloud.constants.CloudProvider;
+import org.apache.helix.model.CloudConfig;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+
+/**
+ * Unit test for {@link AzureCloudInstanceInformationProcessor}
+ */
+public class TestAzureCloudInstanceInformationProcessor extends MockHttpClient {
+
+ @Test()
+ public void testAzureCloudInstanceInformationProcessing() throws Exception {
+ String responseFile = "AzureResponse.json";
+
+ CloudConfig.Builder cloudConfigBuilder = new CloudConfig.Builder();
+ cloudConfigBuilder.setCloudEnabled(true);
+ cloudConfigBuilder.setCloudProvider(CloudProvider.AZURE);
+ cloudConfigBuilder.setCloudID("TestID");
+ HelixCloudProperty helixCloudProperty = new HelixCloudProperty(cloudConfigBuilder.build());
+ AzureCloudInstanceInformationProcessor processor = new AzureCloudInstanceInformationProcessor(
+ helixCloudProperty, createMockHttpClient(responseFile));
+ List<String> response = processor.fetchCloudInstanceInformation();
+
+ Assert.assertEquals(response.size(), 1);
+ Assert.assertNotNull(response.get(0));
+
+ // Verify the response from mock http client
+ AzureCloudInstanceInformation azureCloudInstanceInformation =
+ processor.parseCloudInstanceInformation(response);
+ Assert.assertEquals(azureCloudInstanceInformation
+ .get(CloudInstanceInformation.CloudInstanceField.FAULT_DOMAIN.name()), "2");
+ Assert.assertEquals(
+ azureCloudInstanceInformation
+ .get(CloudInstanceInformation.CloudInstanceField.INSTANCE_SET_NAME.name()),
+ "test-helix");
+ Assert.assertEquals(
+ azureCloudInstanceInformation
+ .get(CloudInstanceInformation.CloudInstanceField.INSTANCE_NAME.name()),
+ "d2b921cc-c16c-41f7-a86d-a445eac6ec26");
+ }
+}
diff --git a/helix-core/src/test/resources/AzureResponse.json b/helix-core/src/test/resources/AzureResponse.json
new file mode 100644
index 0000000..dfe13ad
--- /dev/null
+++ b/helix-core/src/test/resources/AzureResponse.json
@@ -0,0 +1,104 @@
+{
+ "compute": {
+ "azEnvironment": "AzurePublicCloud",
+ "customData": "",
+ "location": "southcentralus",
+ "name": "test-helix_1",
+ "offer": "",
+ "osType": "Linux",
+ "placementGroupId": "81e605b2-a807-48ee-a84a-63c76a9c9543",
+ "plan": {
+ "name": "",
+ "product": "",
+ "publisher": ""
+ },
+ "platformFaultDomain": "2",
+ "platformUpdateDomain": "2",
+ "provider": "Microsoft.Compute",
+ "publicKeys": [],
+ "publisher": "",
+ "resourceGroupName": "scus-lpsazureei1-app-rg",
+ "resourceId": "/subscriptions/c9a251d8-1272-4c0f-8055-8271bbc1d677/resourceGroups/scus-lpsazureei1-app-rg/providers/Microsoft.Compute/virtualMachines/test-helix_2",
+ "sku": "",
+ "storageProfile": {
+ "dataDisks": [],
+ "imageReference": {
+ "id": "/subscriptions/7dd5a659-67c4-441c-ac0b-d48b7a029668/resourceGroups/scus-infra-app-rg/providers/Microsoft.Compute/galleries/pieimagerepo/images/FastCOP4/versions/190924.1.1",
+ "offer": "",
+ "publisher": "",
+ "sku": "",
+ "version": ""
+ },
+ "osDisk": {
+ "caching": "ReadWrite",
+ "createOption": "FromImage",
+ "diskSizeGB": "32",
+ "encryptionSettings": {
+ "enabled": "false"
+ },
+ "image": {
+ "uri": ""
+ },
+ "managedDisk": {
+ "id": "/subscriptions/c9a251d8-1272-4c0f-8055-8271bbc1d677/resourceGroups/scus-lpsazureei1-app-rg/providers/Microsoft.Compute/disks/test-helix_test-helix_2_OsDisk_1_124c3534b8e848e296ec22b24d44c027",
+ "storageAccountType": "Standard_LRS"
+ },
+ "name": "test-helix_test-helix_2_OsDisk_1_124c3534b8e848e296ec22b24d44c027",
+ "osType": "Linux",
+ "vhd": {
+ "uri": ""
+ },
+ "writeAcceleratorEnabled": "false"
+ }
+ },
+ "subscriptionId": "c9a251d8-1272-4c0f-8055-8271bbc1d677",
+ "tags": "automation:terraform;environment:dev;module:lid-vmss;moduleVersion:0.0.1",
+ "tagsList": [
+ {
+ "name": "automation",
+ "value": "terraform"
+ },
+ {
+ "name": "environment",
+ "value": "dev"
+ },
+ {
+ "name": "module",
+ "value": "lid-vmss"
+ },
+ {
+ "name": "moduleVersion",
+ "value": "0.0.1"
+ }
+ ],
+ "version": "",
+ "vmId": "d2b921cc-c16c-41f7-a86d-a445eac6ec26",
+ "vmScaleSetName": "test-helix",
+ "vmSize": "Standard_D16s_v3",
+ "zone": ""
+ },
+ "network": {
+ "interface": [
+ {
+ "ipv4": {
+ "ipAddress": [
+ {
+ "privateIpAddress": "10.3.64.12",
+ "publicIpAddress": ""
+ }
+ ],
+ "subnet": [
+ {
+ "address": "10.3.64.0",
+ "prefix": "19"
+ }
+ ]
+ },
+ "ipv6": {
+ "ipAddress": []
+ },
+ "macAddress": "000D3A769010"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/helix-rest/pom.xml b/helix-rest/pom.xml
index 236669d..34293e6 100644
--- a/helix-rest/pom.xml
+++ b/helix-rest/pom.xml
@@ -67,11 +67,6 @@ under the License.
<version>3.8.1</version>
</dependency>
<dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpclient</artifactId>
- <version>4.5.8</version>
- </dependency>
- <dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.1.0.RC0</version>