/* * 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.discovery.azure.classic; import com.microsoft.windowsazure.management.compute.models.DeploymentSlot; import com.microsoft.windowsazure.management.compute.models.DeploymentStatus; import com.microsoft.windowsazure.management.compute.models.HostedServiceGetDetailedResponse; import com.microsoft.windowsazure.management.compute.models.InstanceEndpoint; import com.microsoft.windowsazure.management.compute.models.RoleInstance; import org.elasticsearch.Version; import org.elasticsearch.cloud.azure.classic.AzureServiceDisableException; import org.elasticsearch.cloud.azure.classic.AzureServiceRemoteException; import org.elasticsearch.cloud.azure.classic.management.AzureComputeService; import org.elasticsearch.cloud.azure.classic.management.AzureComputeService.Discovery; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.common.Strings; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.network.InetAddresses; import org.elasticsearch.common.network.NetworkAddress; 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.UnicastHostsProvider; import org.elasticsearch.transport.TransportService; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; public class AzureUnicastHostsProvider extends AbstractComponent implements UnicastHostsProvider { public enum HostType { PRIVATE_IP("private_ip"), PUBLIC_IP("public_ip"); private String type ; HostType(String type) { this.type = type ; } public static HostType fromString(String type) { for (HostType hostType : values()) { if (hostType.type.equalsIgnoreCase(type)) { return hostType; } } throw new IllegalArgumentException("invalid value for host type [" + type + "]"); } } public enum Deployment { PRODUCTION("production", DeploymentSlot.Production), STAGING("staging", DeploymentSlot.Staging); private String deployment; private DeploymentSlot slot; Deployment(String deployment, DeploymentSlot slot) { this.deployment = deployment; this.slot = slot; } public static Deployment fromString(String string) { for (Deployment deployment : values()) { if (deployment.deployment.equalsIgnoreCase(string)) { return deployment; } } throw new IllegalArgumentException("invalid value for deployment type [" + string + "]"); } } private final AzureComputeService azureComputeService; private TransportService transportService; private NetworkService networkService; private final TimeValue refreshInterval; private long lastRefresh; private List<DiscoveryNode> cachedDiscoNodes; private final HostType hostType; private final String publicEndpointName; private final String deploymentName; private final DeploymentSlot deploymentSlot; public AzureUnicastHostsProvider(Settings settings, AzureComputeService azureComputeService, TransportService transportService, NetworkService networkService) { super(settings); this.azureComputeService = azureComputeService; this.transportService = transportService; this.networkService = networkService; this.refreshInterval = Discovery.REFRESH_SETTING.get(settings); this.hostType = Discovery.HOST_TYPE_SETTING.get(settings); this.publicEndpointName = Discovery.ENDPOINT_NAME_SETTING.get(settings); // Deployment name could be set with discovery.azure.deployment.name // Default to cloud.azure.management.cloud.service.name this.deploymentName = Discovery.DEPLOYMENT_NAME_SETTING.get(settings); // Reading deployment_slot this.deploymentSlot = Discovery.DEPLOYMENT_SLOT_SETTING.get(settings).slot; } /** * 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<DiscoveryNode> buildDynamicNodes() { if (refreshInterval.millis() != 0) { if (cachedDiscoNodes != null && (refreshInterval.millis() < 0 || (System.currentTimeMillis() - lastRefresh) < refreshInterval.millis())) { logger.trace("using cache to retrieve node list"); return cachedDiscoNodes; } lastRefresh = System.currentTimeMillis(); } logger.debug("start building nodes list using Azure API"); cachedDiscoNodes = new ArrayList<>(); HostedServiceGetDetailedResponse detailed; try { detailed = azureComputeService.getServiceDetails(); } catch (AzureServiceDisableException e) { logger.debug("Azure discovery service has been disabled. Returning empty list of nodes."); return cachedDiscoNodes; } catch (AzureServiceRemoteException e) { // We got a remote exception logger.warn("can not get list of azure nodes: [{}]. Returning empty list of nodes.", e.getMessage()); logger.trace("AzureServiceRemoteException caught", e); return cachedDiscoNodes; } InetAddress ipAddress = null; try { ipAddress = networkService.resolvePublishHostAddresses(null); logger.trace("ip of current node: [{}]", ipAddress); } catch (IOException e) { // We can't find the publish host address... Hmmm. Too bad :-( logger.trace("exception while finding ip", e); } for (HostedServiceGetDetailedResponse.Deployment deployment : detailed.getDeployments()) { // We check the deployment slot if (deployment.getDeploymentSlot() != deploymentSlot) { logger.debug("current deployment slot [{}] for [{}] is different from [{}]. skipping...", deployment.getDeploymentSlot(), deployment.getName(), deploymentSlot); continue; } // If provided, we check the deployment name if (Strings.hasLength(deploymentName) && !deploymentName.equals(deployment.getName())) { logger.debug("current deployment name [{}] different from [{}]. skipping...", deployment.getName(), deploymentName); continue; } // We check current deployment status if (deployment.getStatus() != DeploymentStatus.Starting && deployment.getStatus() != DeploymentStatus.Deploying && deployment.getStatus() != DeploymentStatus.Running) { logger.debug("[{}] status is [{}]. skipping...", deployment.getName(), deployment.getStatus()); continue; } // In other case, it should be the right deployment so we can add it to the list of instances for (RoleInstance instance : deployment.getRoleInstances()) { String networkAddress = null; // Let's detect if we want to use public or private IP switch (hostType) { case PRIVATE_IP: InetAddress privateIp = instance.getIPAddress(); if (privateIp != null) { if (privateIp.equals(ipAddress)) { logger.trace("adding ourselves {}", NetworkAddress.format(ipAddress)); } networkAddress = InetAddresses.toUriString(privateIp); } else { logger.trace("no private ip provided. ignoring [{}]...", instance.getInstanceName()); } break; case PUBLIC_IP: for (InstanceEndpoint endpoint : instance.getInstanceEndpoints()) { if (!publicEndpointName.equals(endpoint.getName())) { logger.trace("ignoring endpoint [{}] as different than [{}]", endpoint.getName(), publicEndpointName); continue; } networkAddress = NetworkAddress.format(new InetSocketAddress(endpoint.getVirtualIPAddress(), endpoint.getPort())); } if (networkAddress == null) { logger.trace("no public ip provided. ignoring [{}]...", instance.getInstanceName()); } break; default: // This could never happen! logger.warn("undefined host_type [{}]. Please check your settings.", hostType); return cachedDiscoNodes; } if (networkAddress == null) { // We have a bad parameter here or not enough information from azure logger.warn("no network address found. ignoring [{}]...", instance.getInstanceName()); continue; } try { // we only limit to 1 port per address, makes no sense to ping 100 ports TransportAddress[] addresses = transportService.addressesFromString(networkAddress, 1); for (TransportAddress address : addresses) { logger.trace("adding {}, transport_address {}", networkAddress, address); cachedDiscoNodes.add(new DiscoveryNode("#cloud-" + instance.getInstanceName(), address, emptyMap(), emptySet(), Version.CURRENT.minimumCompatibilityVersion())); } } catch (Exception e) { logger.warn("can not convert [{}] to transport address. skipping. [{}]", networkAddress, e.getMessage()); } } } logger.debug("{} node(s) added", cachedDiscoNodes.size()); return cachedDiscoNodes; } }