From a1f85f6d900ac6e296830fcbc26fcf4837877f43 Mon Sep 17 00:00:00 2001 From: David Pilato Date: Wed, 6 Nov 2013 09:54:30 +0100 Subject: [PATCH] Preparing release for 1.0.0-Alpha --- README.md | 129 +++++++--- pom.xml | 16 +- src/main/assemblies/plugin.xml | 9 - .../cloud/azure/AzureComputeService.java | 219 +---------------- .../cloud/azure/AzureComputeServiceImpl.java | 221 ++++++++++++++++++ .../cloud/azure/AzureModule.java | 12 +- .../elasticsearch/cloud/azure/Instance.java | 113 +++++++++ .../discovery/azure/AzureDiscovery.java | 5 +- .../azure/AzureUnicastHostsProvider.java | 120 +++++++++- .../plugin/cloud/azure/CloudAzurePlugin.java | 12 - src/test/java/AzureSimpleTest.java | 58 ----- .../azure/itest/AzureSimpleITest.java | 37 +++ .../azure/test/AzureAbstractTest.java | 144 ++++++++++++ .../test/AzureComputeServiceAbstractMock.java | 50 ++++ .../test/AzureComputeServiceSimpleMock.java | 49 ++++ .../test/AzureComputeServiceTwoNodesMock.java | 51 ++++ .../test/AzureInstanceXmlParserTest.java | 61 +++++ .../azure/test/AzureSimpleTest.java | 37 +++ .../azure/test/AzureTwoStartedNodesTest.java | 38 +++ src/test/resources/azure.crt | 3 - src/test/resources/azure.pk | 3 - src/test/resources/elasticsearch.yml | 5 +- src/test/resources/log4j.xml | 6 +- .../org/elasticsearch/azure/test/services.xml | 170 ++++++++++++++ 24 files changed, 1211 insertions(+), 357 deletions(-) create mode 100644 src/main/java/org/elasticsearch/cloud/azure/AzureComputeServiceImpl.java create mode 100644 src/main/java/org/elasticsearch/cloud/azure/Instance.java delete mode 100644 src/test/java/AzureSimpleTest.java create mode 100644 src/test/java/org/elasticsearch/azure/itest/AzureSimpleITest.java create mode 100644 src/test/java/org/elasticsearch/azure/test/AzureAbstractTest.java create mode 100644 src/test/java/org/elasticsearch/azure/test/AzureComputeServiceAbstractMock.java create mode 100644 src/test/java/org/elasticsearch/azure/test/AzureComputeServiceSimpleMock.java create mode 100644 src/test/java/org/elasticsearch/azure/test/AzureComputeServiceTwoNodesMock.java create mode 100644 src/test/java/org/elasticsearch/azure/test/AzureInstanceXmlParserTest.java create mode 100644 src/test/java/org/elasticsearch/azure/test/AzureSimpleTest.java create mode 100644 src/test/java/org/elasticsearch/azure/test/AzureTwoStartedNodesTest.java delete mode 100644 src/test/resources/azure.crt delete mode 100644 src/test/resources/azure.pk create mode 100644 src/test/resources/org/elasticsearch/azure/test/services.xml diff --git a/README.md b/README.md index e7b55c88fde..f790ba5791a 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,27 @@ The Azure Cloud plugin allows to use Azure API for the unicast discovery mechani In order to install the plugin, simply run: `bin/plugin -install elasticsearch/elasticsearch-cloud-azure/1.0.0`. - ----------------------------------------- - | Azure Cloud Plugin | ElasticSearch | - ----------------------------------------- - | master | 0.90 -> master | - ----------------------------------------- - | 1.0.0 | 0.20.6 | - ----------------------------------------- + + + + + + + + + + + + + + + + + + + + +
Azure Cloud PluginElasticSearchRelease date
1.1.0-SNAPSHOT (master)0.90.6
1.0.00.90.62013-11-12
Azure Virtual Machine Discovery @@ -23,10 +37,10 @@ multicast environments). Here is a simple sample configuration: ``` cloud: azure: - private_key: /path/to/private.key - certificate: /path/to/azure.certficate - password: your_password_for_pk + keystore: /path/to/keystore + password: your_password_for_keystore subscription_id: your_azure_subscription_id + service_name: your_azure_cloud_service_name discovery: type: azure ``` @@ -124,6 +138,7 @@ Let's say we are going to deploy an Ubuntu image on an extra small instance in W * VM Size: `extrasmall` * Location: `West Europe` * Login: `elasticsearch` +* Password: `password1234!!` Using command line: @@ -135,7 +150,7 @@ azure vm create azure-elasticsearch-cluster \ --vm-size extrasmall \ --ssh 22 \ --ssh-cert /tmp/azure-certificate.pem \ - elasticsearch password + elasticsearch password1234!! ``` You should see something like: @@ -183,15 +198,17 @@ ssh azure-elasticsearch-cluster.cloudapp.net Once connected, install Elasticsearch: ```sh -# Install Latest JDK +# Install Latest OpenJDK +# If you would like to use Oracle JDK instead, read the following: +# http://www.webupd8.org/2012/01/install-oracle-java-jdk-7-in-ubuntu-via.html sudo apt-get update sudo apt-get install openjdk-7-jre-headless # Download Elasticsearch -curl -s https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-0.90.3.deb -o elasticsearch-0.90.3.deb +curl -s https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-0.90.6.deb -o elasticsearch-0.90.6.deb # Prepare Elasticsearch installation -sudo dpkg -i elasticsearch-0.90.3.deb +sudo dpkg -i elasticsearch-0.90.6.deb ``` Check that elasticsearch is running: @@ -206,13 +223,13 @@ This command should give you a JSON result: { "ok" : true, "status" : 200, - "name" : "Mandarin", + "name" : "Grey, Dr. John", "version" : { - "number" : "0.90.3", - "build_hash" : "5c38d6076448b899d758f29443329571e2522410", - "build_timestamp" : "2013-08-06T13:18:31Z", + "number" : "0.90.6", + "build_hash" : "e2a24efdde0cb7cc1b2071ffbbd1fd874a6d8d6b", + "build_timestamp" : "2013-11-04T13:44:16Z", "build_snapshot" : false, - "lucene_version" : "4.4" + "lucene_version" : "4.5.1" }, "tagline" : "You Know, for Search" } @@ -220,8 +237,6 @@ This command should give you a JSON result: ### Install nodejs and Azure tools -*TODO: check if there is a downloadable version of NodeJS* - ```sh # Install node (aka download and compile source) sudo apt-get update @@ -240,7 +255,7 @@ azure account import /tmp/azure.publishsettings rm /tmp/azure.publishsettings ``` -### Generate private keys for this instance +### Generate a keystore ```sh openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout azure-private.key -out azure-certificate.pem @@ -250,9 +265,13 @@ openssl x509 -outform der -in azure-certificate.pem -out azure-certificate.cer openssl pkcs8 -topk8 -nocrypt -in azure-private.key -inform PEM -out azure-pk.pem -outform PEM # Transform certificate to PEM format openssl x509 -inform der -in azure-certificate.cer -out azure-cert.pem +cat azure-cert.pem azure-pk.pem > azure.pem.txt +# You MUST enter a password! +openssl pkcs12 -export -in azure.pem.txt -out azurekeystore.pkcs12 -name azure -noiter -nomaciter ``` -Upload the generated key to Azure platform +Upload the generated key to Azure platform. **Important**: when prompted for a password, +you need to enter a non empty one. ```sh azure service cert create azure-elasticsearch-cluster azure-certificate.cer @@ -264,8 +283,8 @@ azure service cert create azure-elasticsearch-cluster azure-certificate.cer # Stop elasticsearch sudo service elasticsearch stop -# Install the plugin (TODO : USE THE RIGHT VERSION NUMBER) -sudo /usr/share/elasticsearch/bin/plugin -install elasticsearch/elasticsearch-cloud-azure/0.1.0-SNAPSHOT +# Install the plugin +sudo /usr/share/elasticsearch/bin/plugin -install elasticsearch/elasticsearch-cloud-azure/1.0.0 # Configure it sudo vi /etc/elasticsearch/elasticsearch.yml @@ -277,9 +296,10 @@ And add the following lines: # If you don't remember your account id, you may get it with `azure account list` cloud: azure: - private_key: /home/elasticsearch/azure-pk.pem - certificate: /home/elasticsearch/azure-cert.pem + keystore: /home/elasticsearch/azurekeystore.pkcs12 + password: your_password_for_keystore subscription_id: your_azure_subscription_id + service_name: your_azure_cloud_service_name discovery: type: azure ``` @@ -293,28 +313,63 @@ sudo service elasticsearch start If anything goes wrong, check your logs in `/var/log/elasticsearch`. -TODO: Ask pierre for Azure commands +Scaling Out! +------------ -Cloning your existing machine: +You need first to create an image of your previous machine. +Disconnect from your machine and run locally the following commands: ```sh -azure .... +# Shutdown the instance +azure vm shutdown myesnode1 + +# Create an image from this instance (it could take some minutes) +azure vm capture myesnode1 esnode-image --delete + +# Note that the previous instance has been deleted (mandatory) +# So you need to create it again and BTW create other instances. + +azure vm create azure-elasticsearch-cluster \ + esnode-image \ + --vm-name myesnode1 \ + --location "West Europe" \ + --vm-size extrasmall \ + --ssh 22 \ + --ssh-cert /tmp/azure-certificate.pem \ + elasticsearch password1234!! ``` +> **Note:** It could happen that azure changes the endpoint public IP address. +> DNS propagation could take some minutes before you can connect again using +> name. You can get from azure the IP address if needed, using: +> +> ```sh +> # Look at Network `Endpoints 0 Vip` +> azure vm show myesnode1 +> ``` -Add a new machine: +Let's start more instances! ```sh -azure vm create -c myescluster --vm-name myesnode2 b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-13_04-amd64-server-20130501-en-us-30GB -l "West Europe" --vm-size extrasmall --ssh 22 elasticsearch fantastic0! +for x in $(seq 2 10) + do + echo "Launching azure instance #$x..." + azure vm create azure-elasticsearch-cluster \ + esnode-image \ + --vm-name myesnode$x \ + --vm-size extrasmall \ + --ssh $((21 + $x)) \ + --ssh-cert /tmp/azure-certificate.pem \ + --connect \ + elasticsearch password1234!! + done ``` -Add you certificate for this new instance. +If you want to remove your running instances: -```sh -# Add certificate for this instance -azure service cert create myescluster1 azure-certificate.cer ``` - +azure vm delete myesnode1 +``` License ------- diff --git a/pom.xml b/pom.xml index 3baf27948e9..b20ecad75a5 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ governing permissions and limitations under the License. --> 4.0.0 org.elasticsearch elasticsearch-cloud-azure - 0.1.0-SNAPSHOT + 1.0.0-SNAPSHOT jar Azure Cloud plugin for ElasticSearch 2013 @@ -42,8 +42,7 @@ governing permissions and limitations under the License. --> - 0.90.3 - 1.5.7 + 0.90.6 @@ -53,17 +52,6 @@ governing permissions and limitations under the License. --> ${elasticsearch.version} - - org.jclouds - jclouds-compute - ${jclouds.version} - - - org.jclouds.labs - azure-management - ${jclouds.version} - - log4j log4j diff --git a/src/main/assemblies/plugin.xml b/src/main/assemblies/plugin.xml index a434a8a1ed0..634831c235c 100644 --- a/src/main/assemblies/plugin.xml +++ b/src/main/assemblies/plugin.xml @@ -25,14 +25,5 @@ governing permissions and limitations under the License. --> org.elasticsearch:elasticsearch - - / - true - true - - org.jclouds:jclouds-compute - org.jclouds.labs:azure-management - - diff --git a/src/main/java/org/elasticsearch/cloud/azure/AzureComputeService.java b/src/main/java/org/elasticsearch/cloud/azure/AzureComputeService.java index cdfad72e854..5f19ca22632 100644 --- a/src/main/java/org/elasticsearch/cloud/azure/AzureComputeService.java +++ b/src/main/java/org/elasticsearch/cloud/azure/AzureComputeService.java @@ -19,221 +19,22 @@ package org.elasticsearch.cloud.azure; -import com.google.common.collect.ImmutableSet; -import com.google.inject.Module; -import org.elasticsearch.ElasticSearchException; -import org.elasticsearch.Version; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.collect.Lists; -import org.elasticsearch.common.component.AbstractLifecycleComponent; -import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.network.NetworkService; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.settings.SettingsFilter; -import org.elasticsearch.common.transport.TransportAddress; -import org.elasticsearch.discovery.zen.ping.unicast.UnicastZenPing; -import org.elasticsearch.transport.TransportService; -import org.jclouds.Constants; -import org.jclouds.ContextBuilder; -import org.jclouds.azure.management.AzureManagementApi; -import org.jclouds.azure.management.AzureManagementApiMetadata; -import org.jclouds.azure.management.AzureManagementAsyncApi; -import org.jclouds.azure.management.config.AzureManagementProperties; -import org.jclouds.azure.management.domain.Deployment; -import org.jclouds.azure.management.domain.HostedServiceWithDetailedProperties; -import org.jclouds.logging.LoggingModules; -import org.jclouds.rest.RestContext; - -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.InetAddress; -import java.util.List; -import java.util.Properties; -import java.util.Scanner; import java.util.Set; -import static org.elasticsearch.common.Strings.cleanPath; - /** * */ -public class AzureComputeService extends AbstractLifecycleComponent { +public interface AzureComputeService { - static final class Fields { - private static final String ENDPOINT = "https://management.core.windows.net/"; - private static final String VERSION = "2012-08-01"; - private static final String SUBSCRIPTION_ID = "subscription_id"; - private static final String PASSWORD = "password"; - private static final String CERTIFICATE = "certificate"; - private static final String PRIVATE_KEY = "private_key"; + static public final class Fields { + public static final String SUBSCRIPTION_ID = "subscription_id"; + public static final String SERVICE_NAME = "service_name"; + public static final String KEYSTORE = "keystore"; + public static final String PASSWORD = "password"; + public static final String REFRESH = "refresh_interval"; + public static final String PORT_NAME = "port_name"; + public static final String HOST_TYPE = "host_type"; } - private List discoNodes; - private TransportService transportService; - private NetworkService networkService; - - @Inject - public AzureComputeService(Settings settings, SettingsFilter settingsFilter, TransportService transportService, - NetworkService networkService) { - super(settings); - settingsFilter.addFilter(new AzureSettingsFilter()); - this.transportService = transportService; - this.networkService = networkService; - } - - /** - * We build the list of Nodes from Azure Management API - * @param client Azure Client - */ - private List buildNodes(RestContext client) { - List discoNodes = Lists.newArrayList(); - String ipAddress = null; - try { - InetAddress inetAddress = networkService.resolvePublishHostAddress(null); - if (inetAddress != null) { - ipAddress = inetAddress.getHostAddress(); - } - } catch (IOException e) { - // We can't find the publish host address... Hmmm. Too bad :-( - } - - Set response = client.getApi().getHostedServiceApi().list(); - - for (HostedServiceWithDetailedProperties hostedService : response) { - // Ask Azure for each IP address - Deployment deployment = client.getApi().getHostedServiceApi().getDeployment(hostedService.getName(), hostedService.getName()); - if (deployment != null) { - try { - if (deployment.getPrivateIpAddress().equals(ipAddress) || - deployment.getPublicIpAddress().equals(ipAddress)) { - // We found the current node. - // We can ignore it in the list of DiscoveryNode - // We can now set the public Address as the publish address (if not already set) - String publishHost = settings.get("transport.publish_host", settings.get("transport.host")); - if (!Strings.hasText(publishHost) || !deployment.getPublicIpAddress().equals(publishHost)) { - logger.info("you should define publish_host with {}", deployment.getPublicIpAddress()); - } - } else { - TransportAddress[] addresses = transportService.addressesFromString(deployment.getPublicIpAddress()); - // we only limit to 1 addresses, makes no sense to ping 100 ports - for (int i = 0; (i < addresses.length && i < UnicastZenPing.LIMIT_PORTS_COUNT); i++) { - logger.trace("adding {}, address {}, transport_address {}", hostedService.getName(), deployment.getPublicIpAddress(), addresses[i]); - discoNodes.add(new DiscoveryNode("#cloud-" + hostedService.getName() + "-" + i, addresses[i], Version.CURRENT)); - } - } - } catch (Exception e) { - logger.warn("failed to add {}, address {}", e, hostedService.getName(), deployment.getPublicIpAddress()); - } - } else { - logger.trace("ignoring {}", hostedService.getName()); - } - } - - return discoNodes; - } - - public synchronized List nodes() { - if (this.discoNodes != null) { - return this.discoNodes; - } - - String PK8_PATH = componentSettings.get(Fields.PRIVATE_KEY, settings.get("cloud." + Fields.PRIVATE_KEY)); - String CERTIFICATE_PATH = componentSettings.get(Fields.CERTIFICATE, settings.get("cloud." + Fields.CERTIFICATE)); - String PASSWORD = componentSettings.get(Fields.PASSWORD, settings.get("cloud" + Fields.PASSWORD, "")); - String SUBSCRIPTION_ID = componentSettings.get(Fields.SUBSCRIPTION_ID, settings.get("cloud." + Fields.SUBSCRIPTION_ID)); - - // Check that we have all needed properties - if (!checkProperty(Fields.SUBSCRIPTION_ID, SUBSCRIPTION_ID)) return null; - if (!checkProperty(Fields.CERTIFICATE, CERTIFICATE_PATH)) return null; - if (!checkProperty(Fields.PRIVATE_KEY, PK8_PATH)) return null; - - // Reading files from local disk - String pk8 = readFromFile(cleanPath(PK8_PATH)); - String cert = readFromFile(cleanPath(CERTIFICATE_PATH)); - - // Check file content - if (!checkProperty(Fields.CERTIFICATE, cert)) return null; - if (!checkProperty(Fields.PRIVATE_KEY, pk8)) return null; - - String IDENTITY = pk8 + cert; - - // We set properties used to create an Azure client - Properties overrides = new Properties(); - overrides.setProperty(Constants.PROPERTY_TRUST_ALL_CERTS, "true"); - overrides.setProperty(Constants.PROPERTY_RELAX_HOSTNAME, "true"); - overrides.setProperty("azure-management.identity", IDENTITY); - overrides.setProperty("azure-management.credential", PASSWORD); - overrides.setProperty("azure-management.endpoint", Fields.ENDPOINT + SUBSCRIPTION_ID); - overrides.setProperty("azure-management.api-version", Fields.VERSION); - overrides.setProperty("azure-management.build-version", ""); - overrides.setProperty(AzureManagementProperties.SUBSCRIPTION_ID, SUBSCRIPTION_ID); - - RestContext client = null; - - try { - client = ContextBuilder.newBuilder("azure-management") - .modules(ImmutableSet.of(LoggingModules.firstOrJDKLoggingModule())) - .overrides(overrides) - .build(AzureManagementApiMetadata.CONTEXT_TOKEN); - logger.debug("starting Azure discovery service for [{}]", SUBSCRIPTION_ID); - - this.discoNodes = buildNodes(client); - } catch (Throwable t) { - logger.warn("error while trying to find nodes for azure service [{}]: {}", SUBSCRIPTION_ID, t.getMessage()); - logger.debug("error found is: ", t); - // We create an empty list in that specific case. - // So discovery process won't fail with NPE but this node won't join any cluster - this.discoNodes = Lists.newArrayList(); - } finally { - if (client != null) client.close(); - } - - logger.debug("using dynamic discovery nodes {}", discoNodes); - return this.discoNodes; - } - - @Override - protected void doStart() throws ElasticSearchException { - } - - @Override - protected void doStop() throws ElasticSearchException { - } - - @Override - protected void doClose() throws ElasticSearchException { - } - - private String readFromFile(String path) { - try { - logger.trace("reading file content from [{}]", path); - - StringBuilder text = new StringBuilder(); - String NL = System.getProperty("line.separator"); - Scanner scanner = new Scanner(new FileInputStream(path), "UTF-8"); - try { - while (scanner.hasNextLine()){ - text.append(scanner.nextLine() + NL); - } - return text.toString(); - } - finally{ - scanner.close(); - } - } catch (FileNotFoundException e) { - logger.trace("file does not exist [{}]", path); - } - - return null; - } - - private boolean checkProperty(String name, String value) { - if (!Strings.hasText(value)) { - logger.warn("cloud.azure.{} is not set. Disabling azure discovery.", name); - return false; - } - return true; - } + public Set instances(); } diff --git a/src/main/java/org/elasticsearch/cloud/azure/AzureComputeServiceImpl.java b/src/main/java/org/elasticsearch/cloud/azure/AzureComputeServiceImpl.java new file mode 100644 index 00000000000..f44270df150 --- /dev/null +++ b/src/main/java/org/elasticsearch/cloud/azure/AzureComputeServiceImpl.java @@ -0,0 +1,221 @@ +/* + * Licensed to ElasticSearch under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. ElasticSearch 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. + */ + +package org.elasticsearch.cloud.azure; + +import org.elasticsearch.ElasticSearchException; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.component.AbstractLifecycleComponent; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.settings.SettingsException; +import org.elasticsearch.common.settings.SettingsFilter; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.security.*; +import java.security.cert.CertificateException; +import java.util.HashSet; +import java.util.Set; + +/** + * + */ +public class AzureComputeServiceImpl extends AbstractLifecycleComponent + implements AzureComputeService { + + static final class Azure { + private static final String ENDPOINT = "https://management.core.windows.net/"; + private static final String VERSION = "2013-03-01"; + } + + private SSLSocketFactory socketFactory; + private final String keystore; + private final String password; + private final String subscription_id; + private final String service_name; + private final String port_name; + + @Inject + public AzureComputeServiceImpl(Settings settings, SettingsFilter settingsFilter) { + super(settings); + settingsFilter.addFilter(new AzureSettingsFilter()); + + // Creating socketFactory + subscription_id = componentSettings.get(Fields.SUBSCRIPTION_ID, settings.get("cloud.azure." + Fields.SUBSCRIPTION_ID)); + service_name = componentSettings.get(Fields.SERVICE_NAME, settings.get("cloud.azure." + Fields.SERVICE_NAME)); + keystore = componentSettings.get(Fields.KEYSTORE, settings.get("cloud.azure." + Fields.KEYSTORE)); + password = componentSettings.get(Fields.PASSWORD, settings.get("cloud.azure." + Fields.PASSWORD)); + port_name = componentSettings.get(Fields.PORT_NAME, settings.get("cloud.azure." + Fields.PORT_NAME, "elasticsearch")); + + // Check that we have all needed properties + try { + checkProperty(Fields.SUBSCRIPTION_ID, subscription_id); + checkProperty(Fields.SERVICE_NAME, service_name); + checkProperty(Fields.KEYSTORE, keystore); + checkProperty(Fields.PASSWORD, password); + socketFactory = getSocketFactory(keystore, password); + + if (logger.isTraceEnabled()) logger.trace("creating new Azure client for [{}], [{}], [{}], [{}]", + subscription_id, service_name, port_name); + } catch (Exception e) { + // Can not start Azure Client + logger.error("can not start azure client: {}", e.getMessage()); + socketFactory = null; + } + } + + private InputStream getXML(String api) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, KeyManagementException { + String https_url = Azure.ENDPOINT + subscription_id + api; + + URL url = new URL( https_url ); + HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); + con.setSSLSocketFactory( socketFactory ); + con.setRequestProperty("x-ms-version", Azure.VERSION); + + if (logger.isDebugEnabled()) logger.debug("calling azure REST API: {}", api); + if (logger.isTraceEnabled()) logger.trace("get {} from azure", https_url); + + return con.getInputStream(); + } + + @Override + public Set instances() { + if (socketFactory == null) { + // Azure plugin is disabled + if (logger.isTraceEnabled()) logger.trace("azure plugin is disabled. Returning an empty list of nodes."); + return new HashSet(); + } else { + try { + String SERVICE_NAME = componentSettings.get(Fields.SERVICE_NAME, settings.get("cloud." + Fields.SERVICE_NAME)); + InputStream stream = getXML("/services/hostedservices/" + SERVICE_NAME + "?embed-detail=true"); + Set instances = buildInstancesFromXml(stream, port_name); + if (logger.isTraceEnabled()) logger.trace("get instances from azure: {}", instances); + + return instances; + + } catch (ParserConfigurationException e) { + logger.warn("can not parse XML response: {}", e.getMessage()); + return new HashSet(); + } catch (XPathExpressionException e) { + logger.warn("can not parse XML response: {}", e.getMessage()); + return new HashSet(); + } catch (SAXException e) { + logger.warn("can not parse XML response: {}", e.getMessage()); + return new HashSet(); + } catch (Exception e) { + logger.warn("can not get list of azure nodes: {}", e.getMessage()); + return new HashSet(); + } + } + } + + private static String extractValueFromPath(Node node, String path) throws XPathExpressionException { + XPath xPath = XPathFactory.newInstance().newXPath(); + Node subnode = (Node) xPath.compile(path).evaluate(node, XPathConstants.NODE); + return subnode.getFirstChild().getNodeValue(); + } + + public static Set buildInstancesFromXml(InputStream inputStream, String port_name) throws ParserConfigurationException, IOException, SAXException, XPathExpressionException { + Set instances = new HashSet(); + + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(inputStream); + + doc.getDocumentElement().normalize(); + + XPath xPath = XPathFactory.newInstance().newXPath(); + + // We only fetch Started nodes (TODO: should we start with all nodes whatever the status is?) + String expression = "/HostedService/Deployments/Deployment/RoleInstanceList/RoleInstance[PowerState='Started']"; + NodeList nodeList = (NodeList) xPath.compile(expression).evaluate(doc, XPathConstants.NODESET); + for (int i = 0; i < nodeList.getLength(); i++) { + Instance instance = new Instance(); + Node node = nodeList.item(i); + instance.setPrivateIp(extractValueFromPath(node, "IpAddress")); + instance.setName(extractValueFromPath(node, "InstanceName")); + instance.setStatus(Instance.Status.STARTED); + + // Let's digg into + expression = "InstanceEndpoints/InstanceEndpoint[Name='"+ port_name +"']"; + NodeList endpoints = (NodeList) xPath.compile(expression).evaluate(node, XPathConstants.NODESET); + for (int j = 0; j < endpoints.getLength(); j++) { + Node endpoint = endpoints.item(j); + instance.setPublicIp(extractValueFromPath(endpoint, "Vip")); + instance.setPublicPort(extractValueFromPath(endpoint, "PublicPort")); + } + + instances.add(instance); + } + + return instances; + } + + private SSLSocketFactory getSocketFactory(String keystore, String password) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException, KeyManagementException { + File pKeyFile = new File(keystore); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509"); + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + InputStream keyInput = new FileInputStream(pKeyFile); + keyStore.load(keyInput, password.toCharArray()); + keyInput.close(); + keyManagerFactory.init(keyStore, password.toCharArray()); + + SSLContext context = SSLContext.getInstance("TLS"); + context.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom()); + return context.getSocketFactory(); + } + + @Override + protected void doStart() throws ElasticSearchException { + } + + @Override + protected void doStop() throws ElasticSearchException { + } + + @Override + protected void doClose() throws ElasticSearchException { + } + + private void checkProperty(String name, String value) throws ElasticSearchException { + if (!Strings.hasText(value)) { + throw new SettingsException("cloud.azure." + name +" is not set or is incorrect."); + } + } + +} diff --git a/src/main/java/org/elasticsearch/cloud/azure/AzureModule.java b/src/main/java/org/elasticsearch/cloud/azure/AzureModule.java index b9bf9bf402e..7582a0c436a 100644 --- a/src/main/java/org/elasticsearch/cloud/azure/AzureModule.java +++ b/src/main/java/org/elasticsearch/cloud/azure/AzureModule.java @@ -20,14 +20,24 @@ package org.elasticsearch.cloud.azure; import org.elasticsearch.common.inject.AbstractModule; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; /** * */ public class AzureModule extends AbstractModule { + private Settings settings; + + @Inject + public AzureModule(Settings settings) { + this.settings = settings; + } @Override protected void configure() { - bind(AzureComputeService.class).asEagerSingleton(); + bind(AzureComputeService.class) + .to(settings.getAsClass("cloud.azure.api.impl", AzureComputeServiceImpl.class)) + .asEagerSingleton(); } } diff --git a/src/main/java/org/elasticsearch/cloud/azure/Instance.java b/src/main/java/org/elasticsearch/cloud/azure/Instance.java new file mode 100644 index 00000000000..03acacd3a94 --- /dev/null +++ b/src/main/java/org/elasticsearch/cloud/azure/Instance.java @@ -0,0 +1,113 @@ +/* + * Licensed to Elasticsearch (the "Author") under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Author 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. + */ + +package org.elasticsearch.cloud.azure; + +/** + * Define an Azure Instance + */ +public class Instance { + public static enum Status { + STARTED; + } + + private String privateIp; + private String publicIp; + private String publicPort; + private Status status; + private String name; + + public String getPrivateIp() { + return privateIp; + } + + public void setPrivateIp(String privateIp) { + this.privateIp = privateIp; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPublicIp() { + return publicIp; + } + + public void setPublicIp(String publicIp) { + this.publicIp = publicIp; + } + + public String getPublicPort() { + return publicPort; + } + + public void setPublicPort(String publicPort) { + this.publicPort = publicPort; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Instance instance = (Instance) o; + + if (name != null ? !name.equals(instance.name) : instance.name != null) return false; + if (privateIp != null ? !privateIp.equals(instance.privateIp) : instance.privateIp != null) return false; + if (publicIp != null ? !publicIp.equals(instance.publicIp) : instance.publicIp != null) return false; + if (publicPort != null ? !publicPort.equals(instance.publicPort) : instance.publicPort != null) return false; + if (status != instance.status) return false; + + return true; + } + + @Override + public int hashCode() { + int result = privateIp != null ? privateIp.hashCode() : 0; + result = 31 * result + (publicIp != null ? publicIp.hashCode() : 0); + result = 31 * result + (publicPort != null ? publicPort.hashCode() : 0); + result = 31 * result + (status != null ? status.hashCode() : 0); + result = 31 * result + (name != null ? name.hashCode() : 0); + return result; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("Instance{"); + sb.append("privateIp='").append(privateIp).append('\''); + sb.append(", publicIp='").append(publicIp).append('\''); + sb.append(", publicPort='").append(publicPort).append('\''); + sb.append(", status=").append(status); + sb.append(", name='").append(name).append('\''); + sb.append('}'); + return sb.toString(); + } +} diff --git a/src/main/java/org/elasticsearch/discovery/azure/AzureDiscovery.java b/src/main/java/org/elasticsearch/discovery/azure/AzureDiscovery.java index 2af216eb4c8..6313e152383 100755 --- a/src/main/java/org/elasticsearch/discovery/azure/AzureDiscovery.java +++ b/src/main/java/org/elasticsearch/discovery/azure/AzureDiscovery.java @@ -26,6 +26,7 @@ import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.node.DiscoveryNodeService; import org.elasticsearch.common.collect.ImmutableList; import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.discovery.zen.ZenDiscovery; import org.elasticsearch.discovery.zen.ping.ZenPing; @@ -43,7 +44,7 @@ public class AzureDiscovery extends ZenDiscovery { @Inject public AzureDiscovery(Settings settings, ClusterName clusterName, ThreadPool threadPool, TransportService transportService, ClusterService clusterService, NodeSettingsService nodeSettingsService, ZenPingService pingService, - DiscoveryNodeService discoveryNodeService, AzureComputeService azureService) { + DiscoveryNodeService discoveryNodeService, AzureComputeService azureService, NetworkService networkService) { super(settings, clusterName, threadPool, transportService, clusterService, nodeSettingsService, discoveryNodeService, pingService, Version.CURRENT); if (settings.getAsBoolean("cloud.enabled", true)) { ImmutableList zenPings = pingService.zenPings(); @@ -58,7 +59,7 @@ public class AzureDiscovery extends ZenDiscovery { if (unicastZenPing != null) { // update the unicast zen ping to add cloud hosts provider // and, while we are at it, use only it and not the multicast for example - unicastZenPing.addHostsProvider(new AzureUnicastHostsProvider(settings, azureService.nodes())); + unicastZenPing.addHostsProvider(new AzureUnicastHostsProvider(settings, azureService, transportService, networkService)); pingService.zenPings(ImmutableList.of(unicastZenPing)); } else { logger.warn("failed to apply azure unicast discovery, no unicast ping found"); diff --git a/src/main/java/org/elasticsearch/discovery/azure/AzureUnicastHostsProvider.java b/src/main/java/org/elasticsearch/discovery/azure/AzureUnicastHostsProvider.java index 5bc482b93d0..f4125a9a5f4 100644 --- a/src/main/java/org/elasticsearch/discovery/azure/AzureUnicastHostsProvider.java +++ b/src/main/java/org/elasticsearch/discovery/azure/AzureUnicastHostsProvider.java @@ -19,29 +19,141 @@ package org.elasticsearch.discovery.azure; +import org.elasticsearch.ElasticSearchIllegalArgumentException; +import org.elasticsearch.Version; +import org.elasticsearch.cloud.azure.AzureComputeService; +import org.elasticsearch.cloud.azure.Instance; import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.common.collect.Lists; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.discovery.zen.ping.unicast.UnicastHostsProvider; +import org.elasticsearch.discovery.zen.ping.unicast.UnicastZenPing; +import org.elasticsearch.transport.TransportService; +import java.io.IOException; +import java.net.InetAddress; import java.util.List; +import java.util.Set; /** * */ public class AzureUnicastHostsProvider extends AbstractComponent implements UnicastHostsProvider { - private final List discoNodes; + public static enum HostType { + PRIVATE_IP, + PUBLIC_IP + } + + + private final AzureComputeService azureComputeService; + private TransportService transportService; + private NetworkService networkService; + + private final TimeValue refreshInterval; + private long lastRefresh; + private List cachedDiscoNodes; + private final HostType host_type; @Inject - public AzureUnicastHostsProvider(Settings settings, List discoNodes) { + public AzureUnicastHostsProvider(Settings settings, AzureComputeService azureComputeService, + TransportService transportService, + NetworkService networkService) { super(settings); - this.discoNodes = discoNodes; + this.azureComputeService = azureComputeService; + this.transportService = transportService; + this.networkService = networkService; + + this.refreshInterval = componentSettings.getAsTime(AzureComputeService.Fields.REFRESH, + settings.getAsTime("cloud.azure." + AzureComputeService.Fields.REFRESH, TimeValue.timeValueSeconds(0))); + this.host_type = HostType.valueOf(componentSettings.get(AzureComputeService.Fields.HOST_TYPE, + settings.get("cloud.azure." + AzureComputeService.Fields.HOST_TYPE, HostType.PRIVATE_IP.name())).toUpperCase()); + } + /** + * We build the list of Nodes from Azure Management API + * Information can be cached using `cloud.azure.refresh_interval` property if needed. + * Setting `cloud.azure.refresh_interval` to `-1` will cause infinite caching. + * Setting `cloud.azure.refresh_interval` to `0` will disable caching (default). + */ @Override public List buildDynamicNodes() { - return discoNodes; + if (refreshInterval.millis() != 0) { + if (cachedDiscoNodes != null && + (refreshInterval.millis() < 0 || (System.currentTimeMillis() - lastRefresh) < refreshInterval.millis())) { + if (logger.isTraceEnabled()) logger.trace("using cache to retrieve node list"); + return cachedDiscoNodes; + } + lastRefresh = System.currentTimeMillis(); + } + logger.debug("start building nodes list using Azure API"); + + cachedDiscoNodes = Lists.newArrayList(); + + Set response = azureComputeService.instances(); + + String ipAddress = null; + try { + InetAddress inetAddress = networkService.resolvePublishHostAddress(null); + if (inetAddress != null) { + ipAddress = inetAddress.getHostAddress(); + } + if (logger.isTraceEnabled()) logger.trace("ipAddress found: [{}]", ipAddress); + } catch (IOException e) { + // We can't find the publish host address... Hmmm. Too bad :-( + if (logger.isTraceEnabled()) logger.trace("exception while finding ipAddress", e); + } + + try { + for (Instance instance : response) { + String networkAddress = null; + // Let's detect if we want to use public or private IP + if (host_type == HostType.PRIVATE_IP) { + if (instance.getPrivateIp() != null) { + if (logger.isTraceEnabled() && instance.getPrivateIp().equals(ipAddress)) { + logger.trace("adding ourselves {}", ipAddress); + } + + networkAddress = instance.getPrivateIp(); + } else { + logger.trace("no private ip provided ignoring {}", instance.getName()); + } + } + if (host_type == HostType.PUBLIC_IP) { + if (instance.getPublicIp() != null && instance.getPublicPort() != null) { + networkAddress = instance.getPublicIp() + ":" +instance.getPublicPort(); + } else { + logger.trace("no public ip provided ignoring {}", instance.getName()); + } + } + + if (networkAddress == null) { + // We have a bad parameter here or not enough information from azure + throw new ElasticSearchIllegalArgumentException("can't find any " + host_type.name() + " address"); + } else { + TransportAddress[] addresses = transportService.addressesFromString(networkAddress); + // we only limit to 1 addresses, makes no sense to ping 100 ports + for (int i = 0; (i < addresses.length && i < UnicastZenPing.LIMIT_PORTS_COUNT); i++) { + logger.trace("adding {}, transport_address {}", networkAddress, addresses[i]); + cachedDiscoNodes.add(new DiscoveryNode("#cloud-" + instance.getName() + "-" + i, addresses[i], Version.CURRENT)); + } + } + + } + } catch (Throwable e) { + logger.warn("Exception caught during discovery {} : {}", e.getClass().getName(), e.getMessage()); + logger.trace("Exception caught during discovery", e); + } + + logger.debug("{} node(s) added", cachedDiscoNodes.size()); + logger.debug("using dynamic discovery nodes {}", cachedDiscoNodes); + + return cachedDiscoNodes; } } diff --git a/src/main/java/org/elasticsearch/plugin/cloud/azure/CloudAzurePlugin.java b/src/main/java/org/elasticsearch/plugin/cloud/azure/CloudAzurePlugin.java index 42d2d086cdc..fa98e848fde 100644 --- a/src/main/java/org/elasticsearch/plugin/cloud/azure/CloudAzurePlugin.java +++ b/src/main/java/org/elasticsearch/plugin/cloud/azure/CloudAzurePlugin.java @@ -19,10 +19,8 @@ package org.elasticsearch.plugin.cloud.azure; -import org.elasticsearch.cloud.azure.AzureComputeService; import org.elasticsearch.cloud.azure.AzureModule; import org.elasticsearch.common.collect.Lists; -import org.elasticsearch.common.component.LifecycleComponent; import org.elasticsearch.common.inject.Module; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.plugins.AbstractPlugin; @@ -58,14 +56,4 @@ public class CloudAzurePlugin extends AbstractPlugin { } return modules; } - - @Override - public Collection> services() { - Collection> services = Lists.newArrayList(); - if (settings.getAsBoolean("cloud.enabled", true)) { - services.add(AzureComputeService.class); - } - return services; - } - } diff --git a/src/test/java/AzureSimpleTest.java b/src/test/java/AzureSimpleTest.java deleted file mode 100644 index 885f931afe8..00000000000 --- a/src/test/java/AzureSimpleTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to ElasticSearch under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. ElasticSearch 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 org.elasticsearch.common.io.FileSystemUtils; -import org.elasticsearch.common.settings.ImmutableSettings; -import org.elasticsearch.node.Node; -import org.elasticsearch.node.NodeBuilder; -import org.junit.Test; - -import java.io.File; - -/** - * To run this *test*, you need first to modify test/resources dir: - * - elasticsearch.yml: set your azure password and azure subscription_id - * - azure.crt: fill this file with your azure certificate (PEM format) - * - azure.pk: fill this file with your azure private key (PEM format) - */ -public class AzureSimpleTest { - - @Test - public void launchNode() { - File dataDir = new File("./target/es/data"); - if(dataDir.exists()) { - FileSystemUtils.deleteRecursively(dataDir, true); - } - - // Then we start our node for tests - Node node = NodeBuilder - .nodeBuilder() - .settings( - ImmutableSettings.settingsBuilder() - .put("gateway.type", "local") - .put("path.data", "./target/es/data") - .put("path.logs", "./target/es/logs") - .put("path.work", "./target/es/work") - ).node(); - - // We wait now for the yellow (or green) status -// node.client().admin().cluster().prepareHealth().setWaitForYellowStatus().execute().actionGet(); - - } -} diff --git a/src/test/java/org/elasticsearch/azure/itest/AzureSimpleITest.java b/src/test/java/org/elasticsearch/azure/itest/AzureSimpleITest.java new file mode 100644 index 00000000000..59fafbb003f --- /dev/null +++ b/src/test/java/org/elasticsearch/azure/itest/AzureSimpleITest.java @@ -0,0 +1,37 @@ +/* + * Licensed to ElasticSearch under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. ElasticSearch 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. + */ +package org.elasticsearch.azure.itest; + +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.node.NodeBuilder; +import org.junit.Ignore; +import org.junit.Test; + +public class AzureSimpleITest { + + @Test @Ignore + public void one_node_should_run() { + ImmutableSettings.Builder builder = ImmutableSettings.settingsBuilder() + //.put("gateway.type", "local") + .put("path.data", "./target/es/data") + .put("path.logs", "./target/es/logs") + .put("path.work", "./target/es/work"); + NodeBuilder.nodeBuilder().settings(builder).node(); + } +} diff --git a/src/test/java/org/elasticsearch/azure/test/AzureAbstractTest.java b/src/test/java/org/elasticsearch/azure/test/AzureAbstractTest.java new file mode 100644 index 00000000000..c02f267d95f --- /dev/null +++ b/src/test/java/org/elasticsearch/azure/test/AzureAbstractTest.java @@ -0,0 +1,144 @@ +/* + * Licensed to ElasticSearch under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. ElasticSearch 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. + */ +package org.elasticsearch.azure.test; + +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.client.transport.NoNodeAvailableException; +import org.elasticsearch.client.transport.TransportClient; +import org.elasticsearch.cloud.azure.AzureComputeService; +import org.elasticsearch.common.io.FileSystemUtils; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.ESLoggerFactory; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.node.Node; +import org.elasticsearch.node.NodeBuilder; +import org.elasticsearch.node.internal.InternalNode; +import org.elasticsearch.transport.netty.NettyTransport; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public abstract class AzureAbstractTest { + + protected static ESLogger logger = ESLoggerFactory.getLogger(AzureAbstractTest.class.getName()); + private static List nodes; + private Class mock; + + public AzureAbstractTest(Class mock) { + // We want to inject the Azure API Mock + this.mock = mock; + } + + @Before + public void setUp() { + nodes = new ArrayList(); + + File dataDir = new File("./target/es/data"); + if(dataDir.exists()) { + FileSystemUtils.deleteRecursively(dataDir, true); + } + } + + @After + public void tearDown() { + // Cleaning nodes after test + for (Node node : nodes) { + node.close(); + } + } + + protected Client getTransportClient() { + // Create a TransportClient on node 1 and 2 + Settings settings = ImmutableSettings.settingsBuilder() + .put("cluster.name", "azure") + .put("transport.tcp.connect_timeout", "1s") + .build(); + + TransportClient client = new TransportClient(settings); + + for (Node node : nodes) { + NettyTransport nettyTransport = ((InternalNode) node).injector().getInstance(NettyTransport.class); + TransportAddress transportAddress = nettyTransport.boundAddress().publishAddress(); + client.addTransportAddress(transportAddress); + } + + return client; + } + + protected Client getNodeClient() { + for (Node node : nodes) { + return node.client(); + } + + return null; + } + + protected void checkNumberOfNodes(int expected, boolean fail) { + NodesInfoResponse nodeInfos = null; + + try { + nodeInfos = getTransportClient().admin().cluster().prepareNodesInfo().execute().actionGet(); + } catch (NoNodeAvailableException e) { + // If we can't build a Transport Client, we are may be not connected to any network + // Let's try a Node Client + nodeInfos = getNodeClient().admin().cluster().prepareNodesInfo().execute().actionGet(); + } + + Assert.assertNotNull(nodeInfos); + Assert.assertNotNull(nodeInfos.getNodes()); + + if (fail) { + Assert.assertEquals(expected, nodeInfos.getNodes().length); + } else { + if (nodeInfos.getNodes().length != expected) { + logger.warn("expected {} node(s) but found {}. Could be due to no local IP address available.", + expected, nodeInfos.getNodes().length); + } + } + } + + protected void checkNumberOfNodes(int expected) { + checkNumberOfNodes(expected, true); + } + + protected void nodeBuilder() { + ImmutableSettings.Builder builder = ImmutableSettings.settingsBuilder() + //.put("gateway.type", "local") + .put("path.data", "./target/es/data") + .put("path.logs", "./target/es/logs") + .put("path.work", "./target/es/work") +// .put("discovery.zen.ping.timeout", "500ms") +// .put("discovery.zen.fd.ping_retries",1) +// .put("discovery.zen.fd.ping_timeout", "500ms") +// .put("discovery.initial_state_timeout", "5s") +// .put("transport.tcp.connect_timeout", "1s") + .put("cloud.azure.api.impl", mock) + .put("cloud.azure.refresh_interval", "5s") + .put("node.name", (nodes.size()+1) + "#" + mock.getSimpleName()); + Node node = NodeBuilder.nodeBuilder().settings(builder).node(); + nodes.add(node); + } +} diff --git a/src/test/java/org/elasticsearch/azure/test/AzureComputeServiceAbstractMock.java b/src/test/java/org/elasticsearch/azure/test/AzureComputeServiceAbstractMock.java new file mode 100644 index 00000000000..b04524fec98 --- /dev/null +++ b/src/test/java/org/elasticsearch/azure/test/AzureComputeServiceAbstractMock.java @@ -0,0 +1,50 @@ +/* + * Licensed to ElasticSearch under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. ElasticSearch 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. + */ + +package org.elasticsearch.azure.test; + +import org.elasticsearch.ElasticSearchException; +import org.elasticsearch.cloud.azure.AzureComputeService; +import org.elasticsearch.common.component.AbstractLifecycleComponent; +import org.elasticsearch.common.settings.Settings; + +/** + * + */ +public abstract class AzureComputeServiceAbstractMock extends AbstractLifecycleComponent + implements AzureComputeService { + + protected AzureComputeServiceAbstractMock(Settings settings) { + super(settings); + } + + @Override + protected void doStart() throws ElasticSearchException { + logger.debug("starting Azure Api Mock"); + } + + @Override + protected void doStop() throws ElasticSearchException { + logger.debug("stopping Azure Api Mock"); + } + + @Override + protected void doClose() throws ElasticSearchException { + } +} diff --git a/src/test/java/org/elasticsearch/azure/test/AzureComputeServiceSimpleMock.java b/src/test/java/org/elasticsearch/azure/test/AzureComputeServiceSimpleMock.java new file mode 100644 index 00000000000..f3cac7a940f --- /dev/null +++ b/src/test/java/org/elasticsearch/azure/test/AzureComputeServiceSimpleMock.java @@ -0,0 +1,49 @@ +/* + * Licensed to ElasticSearch under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. ElasticSearch 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. + */ + +package org.elasticsearch.azure.test; + +import org.elasticsearch.cloud.azure.Instance; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; + +import java.util.HashSet; +import java.util.Set; + +/** + * Mock Azure API with a single started node + */ +public class AzureComputeServiceSimpleMock extends AzureComputeServiceAbstractMock { + + @Inject + protected AzureComputeServiceSimpleMock(Settings settings) { + super(settings); + logger.debug("starting Azure Mock"); + } + + @Override + public Set instances() { + Set instances = new HashSet(); + Instance azureHost = new Instance(); + azureHost.setPrivateIp("127.0.0.1"); + instances.add(azureHost); + + return instances; + } +} diff --git a/src/test/java/org/elasticsearch/azure/test/AzureComputeServiceTwoNodesMock.java b/src/test/java/org/elasticsearch/azure/test/AzureComputeServiceTwoNodesMock.java new file mode 100644 index 00000000000..d94efc13f54 --- /dev/null +++ b/src/test/java/org/elasticsearch/azure/test/AzureComputeServiceTwoNodesMock.java @@ -0,0 +1,51 @@ +/* + * Licensed to ElasticSearch under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. ElasticSearch 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. + */ + +package org.elasticsearch.azure.test; + +import org.elasticsearch.cloud.azure.Instance; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; + +import java.util.HashSet; +import java.util.Set; + +/** + * Mock Azure API with two started nodes + */ +public class AzureComputeServiceTwoNodesMock extends AzureComputeServiceAbstractMock { + + @Inject + protected AzureComputeServiceTwoNodesMock(Settings settings) { + super(settings); + logger.debug("starting Azure Mock"); + } + + @Override + public Set instances() { + Set instances = new HashSet(); + Instance azureHost = new Instance(); + azureHost.setPrivateIp("127.0.0.1"); + instances.add(azureHost); + azureHost = new Instance(); + azureHost.setPrivateIp("127.0.0.1"); + instances.add(azureHost); + return instances; + } +} diff --git a/src/test/java/org/elasticsearch/azure/test/AzureInstanceXmlParserTest.java b/src/test/java/org/elasticsearch/azure/test/AzureInstanceXmlParserTest.java new file mode 100644 index 00000000000..d99e3edc74b --- /dev/null +++ b/src/test/java/org/elasticsearch/azure/test/AzureInstanceXmlParserTest.java @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch (the "Author") under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Author 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. + */ + +package org.elasticsearch.azure.test; + +import org.elasticsearch.cloud.azure.AzureComputeServiceImpl; +import org.elasticsearch.cloud.azure.Instance; +import org.junit.Assert; +import org.junit.Test; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPathExpressionException; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashSet; +import java.util.Set; + +public class AzureInstanceXmlParserTest { + + private Instance build(String name, String privateIpAddress, + String publicIpAddress, + String publicPort, + Instance.Status status) { + Instance instance = new Instance(); + instance.setName(name); + instance.setPrivateIp(privateIpAddress); + instance.setPublicIp(publicIpAddress); + instance.setPublicPort(publicPort); + instance.setStatus(status); + return instance; + } + + @Test + public void testReadXml() throws ParserConfigurationException, SAXException, XPathExpressionException, IOException { + InputStream inputStream = AzureInstanceXmlParserTest.class.getResourceAsStream("/org/elasticsearch/azure/test/services.xml"); + Set instances = AzureComputeServiceImpl.buildInstancesFromXml(inputStream, "elasticsearch"); + + Set expected = new HashSet(); + expected.add(build("es-windows2008", "10.53.250.55", null, null, Instance.Status.STARTED)); + expected.add(build("myesnode1", "10.53.218.75", "137.116.213.150", "9300", Instance.Status.STARTED)); + + Assert.assertArrayEquals(expected.toArray(), instances.toArray()); + } +} diff --git a/src/test/java/org/elasticsearch/azure/test/AzureSimpleTest.java b/src/test/java/org/elasticsearch/azure/test/AzureSimpleTest.java new file mode 100644 index 00000000000..2fee19dac90 --- /dev/null +++ b/src/test/java/org/elasticsearch/azure/test/AzureSimpleTest.java @@ -0,0 +1,37 @@ +/* + * Licensed to ElasticSearch under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. ElasticSearch 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. + */ +package org.elasticsearch.azure.test; + +import org.junit.Test; + +public class AzureSimpleTest extends AzureAbstractTest { + + public AzureSimpleTest() { + super(AzureComputeServiceSimpleMock.class); + } + + @Test + public void one_node_should_run() { + // Then we start our node for tests + nodeBuilder(); + + // We expect having 2 nodes as part of the cluster, let's test that + checkNumberOfNodes(1); + } +} diff --git a/src/test/java/org/elasticsearch/azure/test/AzureTwoStartedNodesTest.java b/src/test/java/org/elasticsearch/azure/test/AzureTwoStartedNodesTest.java new file mode 100644 index 00000000000..ae1e3ac8feb --- /dev/null +++ b/src/test/java/org/elasticsearch/azure/test/AzureTwoStartedNodesTest.java @@ -0,0 +1,38 @@ +/* + * Licensed to ElasticSearch under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. ElasticSearch 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. + */ +package org.elasticsearch.azure.test; + +import org.junit.Test; + +public class AzureTwoStartedNodesTest extends AzureAbstractTest { + + public AzureTwoStartedNodesTest() { + super(AzureComputeServiceTwoNodesMock.class); + } + + @Test + public void two_nodes_should_run() { + // Then we start our node for tests + nodeBuilder(); + nodeBuilder(); + + // We expect having 2 nodes as part of the cluster, let's test that + checkNumberOfNodes(2, false); + } +} diff --git a/src/test/resources/azure.crt b/src/test/resources/azure.crt deleted file mode 100644 index 1be5a80046a..00000000000 --- a/src/test/resources/azure.crt +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN CERTIFICATE----- -YOUR-CERTIFICATE-HERE ------END CERTIFICATE----- diff --git a/src/test/resources/azure.pk b/src/test/resources/azure.pk deleted file mode 100644 index 6406c000190..00000000000 --- a/src/test/resources/azure.pk +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PRIVATE KEY----- -YOUR-PK-HERE ------END PRIVATE KEY----- diff --git a/src/test/resources/elasticsearch.yml b/src/test/resources/elasticsearch.yml index 9ad55935713..82583a481cb 100644 --- a/src/test/resources/elasticsearch.yml +++ b/src/test/resources/elasticsearch.yml @@ -10,6 +10,7 @@ # governing permissions and limitations under the License. cluster.name: azure + # Azure discovery allows to use Azure API in order to perform discovery. # # You have to install the cloud-azure plugin for enabling the Azure discovery. @@ -21,9 +22,9 @@ # for a step-by-step tutorial. cloud: azure: - private_key: ${project.build.testOutputDirectory}/azure.pk - certificate: ${project.build.testOutputDirectory}/azure.crt + keystore: FULLPATH-TO-YOUR-KEYSTORE password: YOUR-PASSWORD subscription_id: YOUR-AZURE-SUBSCRIPTION-ID + service_name: YOUR-AZURE-SERVICE-NAME discovery: type: azure diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml index 46249c145c5..95b3bf51c77 100644 --- a/src/test/resources/log4j.xml +++ b/src/test/resources/log4j.xml @@ -16,7 +16,7 @@ governing permissions and limitations under the License. --> - + @@ -24,10 +24,10 @@ governing permissions and limitations under the License. --> - + - + diff --git a/src/test/resources/org/elasticsearch/azure/test/services.xml b/src/test/resources/org/elasticsearch/azure/test/services.xml new file mode 100644 index 00000000000..0b8bd9ab1f8 --- /dev/null +++ b/src/test/resources/org/elasticsearch/azure/test/services.xml @@ -0,0 +1,170 @@ + + + https://management.core.windows.net/a836d210-c681-4916-ae7d-9ca5f1b4906f/services/hostedservices/azure-elasticsearch-cluster + azure-elasticsearch-cluster + + Implicitly created hosted service + West Europe + + Created + 2013-09-23T12:45:45Z + 2013-09-23T13:42:46Z + + + + + azure-elasticsearch-cluster + Production + c6a690796e6b497a918487546193b94a + Running + + http://azure-elasticsearch-cluster.cloudapp.net/ + PFNlcnZpY2VDb25maWd1cmF0aW9uIHhtbG5zOnhzZD0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEiIHhtbG5zOnhzaT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS9YTUxTY2hlbWEtaW5zdGFuY2UiIHhtbG5zPSJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL1NlcnZpY2VIb3N0aW5nLzIwMDgvMTAvU2VydmljZUNvbmZpZ3VyYXRpb24iPg0KICA8Um9sZSBuYW1lPSJlcy13aW5kb3dzMjAwOCI+DQogICAgPEluc3RhbmNlcyBjb3VudD0iMSIgLz4NCiAgPC9Sb2xlPg0KICA8Um9sZSBuYW1lPSJteWVzbm9kZTEiPg0KICAgIDxJbnN0YW5jZXMgY291bnQ9IjEiIC8+DQogIDwvUm9sZT4NCjwvU2VydmljZUNvbmZpZ3VyYXRpb24+ + + + es-windows2008 + es-windows2008 + ReadyRole + 0 + 0 + ExtraSmall + + 10.53.250.55 + + + PowerShell + 137.116.213.150 + 5986 + 5986 + tcp + + + Remote Desktop + 137.116.213.150 + 53867 + 3389 + tcp + + + Started + es-windows2008 + E6798DEACEAD4752825A2ABFC0A9A367389ED802 + + + myesnode1 + myesnode1 + ReadyRole + 0 + 0 + ExtraSmall + + 10.53.218.75 + + + ssh + 137.116.213.150 + 22 + 22 + tcp + + + elasticsearch + 137.116.213.150 + 9300 + 9300 + tcp + + + Started + myesnode1 + + + 1 + + + es-windows2008 + + PersistentVMRole + + + NetworkConfiguration + + + 5986 + PowerShell + 5986 + tcp + 137.116.213.150 + + + 3389 + Remote Desktop + 53867 + tcp + 137.116.213.150 + + + + + + + + ReadWrite + azure-elasticsearch-cluster-es-windows2008-0-201309231342520150 + http://portalvhdsv5skghwlmf765.blob.core.windows.net/vhds/azure-elasticsearch-cluster-es-windows2008-2013-09-23.vhd + a699494373c04fc0bc8f2bb1389d6106__Win2K8R2SP1-Datacenter-201308.02-en.us-127GB.vhd + Windows + + ExtraSmall + + + myesnode1 + + PersistentVMRole + + + NetworkConfiguration + + + 22 + ssh + 22 + tcp + 137.116.213.150 + + + + + + + + ReadWrite + azure-elasticsearch-cluster-myesnode1-0-201309231246380270 + http://azureelasticsearchcluste.blob.core.windows.net/vhd-store/myesnode1-0cc86cc84c0ed2e0.vhd + b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-13_10-amd64-server-20130808-alpha3-en-us-30GB + Linux + + ExtraSmall + + + + false + false + 2013-09-23T12:46:27Z + 2013-09-23T20:38:58Z + + + 2013-08-27T08:00:00Z + 2013-09-27T20:00:00Z + PersistentVMUpdateScheduled + + + +
137.116.213.150
+ true + azure-elasticsearch-clusterContractContract +
+
+
+
+