/*
* Licensed to Crate under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership. Crate 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.
*
* However, if you have executed another commercial license agreement
* with Crate these terms will supersede the license and you may use the
* software solely pursuant to the terms of the relevant commercial
* agreement.
*/
package io.crate.azure.discovery;
import com.microsoft.azure.management.network.NetworkResourceProviderClient;
import com.microsoft.azure.management.network.models.*;
import io.crate.azure.management.AzureComputeService;
import io.crate.azure.management.AzureComputeService.Discovery;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
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.common.util.SingleObjectCache;
import org.elasticsearch.discovery.zen.ping.unicast.UnicastHostsProvider;
import org.elasticsearch.transport.TransportService;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
public class AzureUnicastHostsProvider extends AbstractComponent implements UnicastHostsProvider {
public static enum HostType {
PRIVATE_IP("private_ip"),
PUBLIC_IP("public_ip");
private String type;
private HostType(String type) {
this.type = type;
}
public static HostType fromString(String type) {
for (HostType hostType : values()) {
if (hostType.type.equalsIgnoreCase(type)) {
return hostType;
}
}
return null;
}
}
private final AzureComputeService azureComputeService;
private final TransportService transportService;
private final NetworkService networkService;
private final TimeValue refreshInterval;
private DiscoNodeCache cache;
private final String resourceGroup;
private final HostType hostType;
private final String discoveryMethod;
@Inject
public AzureUnicastHostsProvider(Settings settings,
AzureComputeService azureComputeService,
TransportService transportService,
NetworkService networkService) {
super(settings);
this.azureComputeService = azureComputeService;
this.transportService = transportService;
this.networkService = networkService;
refreshInterval = Discovery.REFRESH.get(settings);
resourceGroup = AzureComputeService.Management.RESOURCE_GROUP_NAME.get(settings);
hostType = HostType.fromString(Discovery.HOST_TYPE.get(settings));
discoveryMethod = Discovery.DISCOVERY_METHOD.get(settings);
}
/**
* We build the list of Nodes from Azure Management API
* List of discovery nodes is cached.
* The cache time can be controlled using `cloud.azure.refresh_interval` setting.
*/
@Override
public List<DiscoveryNode> buildDynamicNodes() {
if (cache == null) {
cache = new DiscoNodeCache(refreshInterval, Collections.<DiscoveryNode>emptyList());
}
return cache.getOrRefresh();
}
private class DiscoNodeCache extends SingleObjectCache<List<DiscoveryNode>> {
protected DiscoNodeCache(TimeValue refreshInterval, List<DiscoveryNode> initialValue) {
super(refreshInterval, initialValue);
}
@Override
protected List<DiscoveryNode> refresh() {
ArrayList<DiscoveryNode> nodes = new ArrayList<>();
InetAddress ipAddress;
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.warn("Cannot find publish host address/ip", e);
return Collections.emptyList();
}
// In other case, it should be the right deployment so we can add it to the list of instances
NetworkResourceProviderClient client = azureComputeService.networkResourceClient();
if (client == null) {
return Collections.emptyList();
}
try {
final HashMap<String, String> networkNameOfCurrentHost = retrieveNetInfo(client, resourceGroup, NetworkAddress.format(ipAddress));
if (networkNameOfCurrentHost.size() == 0) {
logger.error("Could not find vnet or subnet of current host");
return Collections.emptyList();
}
List<String> ipAddresses = listIPAddresses(client, resourceGroup, networkNameOfCurrentHost.get(AzureDiscovery.VNET),
networkNameOfCurrentHost.get(AzureDiscovery.SUBNET), discoveryMethod, hostType, logger);
for (String networkAddress : ipAddresses) {
// limit to 1 port per address
TransportAddress[] addresses = transportService.addressesFromString(networkAddress, 1);
for (TransportAddress address : addresses) {
logger.trace("adding {}, transport_address {}", networkAddress, address);
nodes.add(new DiscoveryNode(
"#cloud-" + networkAddress, address, Version.CURRENT.minimumCompatibilityVersion()));
}
}
} catch (UnknownHostException e) {
logger.error("Error occurred in getting hostname");
} catch (Exception e) {
logger.error(e.getMessage());
}
logger.debug("{} node(s) added", nodes.size());
return nodes;
}
}
public static HashMap<String, String> retrieveNetInfo(NetworkResourceProviderClient networkResourceProviderClient,
String rgName,
String ipAddress) throws Exception {
HashMap<String, String> networkNames = new HashMap<>();
ArrayList<VirtualNetwork> virtualNetworks = networkResourceProviderClient
.getVirtualNetworksOperations()
.list(rgName)
.getVirtualNetworks();
for (VirtualNetwork vn : virtualNetworks) {
ArrayList<Subnet> subnets = vn.getSubnets();
for (Subnet subnet : subnets) {
for (ResourceId resourceId : subnet.getIpConfigurations()) {
String[] nicURI = resourceId.getId().split("/");
NetworkInterface nic = networkResourceProviderClient.getNetworkInterfacesOperations().get(rgName, nicURI[
nicURI.length - 3]).getNetworkInterface();
ArrayList<NetworkInterfaceIpConfiguration> ips = nic.getIpConfigurations();
// find public ip address
for (NetworkInterfaceIpConfiguration ipConfiguration : ips) {
if (ipAddress.equals(ipConfiguration.getPrivateIpAddress())) {
networkNames.put(AzureDiscovery.VNET, vn.getName());
networkNames.put(AzureDiscovery.SUBNET, subnet.getName());
break;
}
}
}
}
}
return networkNames;
}
public static List<String> listIPAddresses(NetworkResourceProviderClient networkResourceProviderClient,
String rgName,
String vnetName,
String subnetName,
String discoveryMethod,
HostType hostType,
Logger logger) {
List<String> ipList = new ArrayList<>();
List<ResourceId> ipConfigurations = new ArrayList<>();
try {
List<Subnet> subnets = networkResourceProviderClient.getVirtualNetworksOperations().get(rgName, vnetName).getVirtualNetwork().getSubnets();
if (discoveryMethod.equalsIgnoreCase(AzureDiscovery.VNET)) {
for (Subnet subnet : subnets) {
ipConfigurations.addAll(subnet.getIpConfigurations());
}
} else {
for (Subnet subnet : subnets) {
if (subnet.getName().equalsIgnoreCase(subnetName)) {
ipConfigurations.addAll(subnet.getIpConfigurations());
}
}
}
for (ResourceId resourceId : ipConfigurations) {
String[] nicURI = resourceId.getId().split("/");
NetworkInterface nic = networkResourceProviderClient.getNetworkInterfacesOperations().get(rgName, nicURI[
nicURI.length - 3]).getNetworkInterface();
ArrayList<NetworkInterfaceIpConfiguration> ips = nic.getIpConfigurations();
// find public ip address
for (NetworkInterfaceIpConfiguration ipConfiguration : ips) {
String networkAddress = null;
// Let's detect if we want to use public or private IP
switch (hostType) {
case PRIVATE_IP:
InetAddress privateIp = InetAddress.getByName(ipConfiguration.getPrivateIpAddress());
if (privateIp != null) {
networkAddress = NetworkAddress.format(privateIp);
} else {
logger.trace("no private ip provided. ignoring [{}]...", nic.getName());
}
break;
case PUBLIC_IP:
if (ipConfiguration.getPublicIpAddress() != null) {
String[] pipID = ipConfiguration.getPublicIpAddress().getId().split("/");
PublicIpAddress pip = networkResourceProviderClient.getPublicIpAddressesOperations()
.get(rgName, pipID[pipID.length - 1]).getPublicIpAddress();
networkAddress = NetworkAddress.format(InetAddress.getByName(pip.getIpAddress()));
}
if (networkAddress == null) {
logger.trace("no public ip provided. ignoring [{}]...", nic.getName());
}
break;
}
if (networkAddress == null) {
// We have a bad parameter here or not enough information from azure
logger.warn("no network address found. ignoring [{}]...", nic.getName());
continue;
} else {
ipList.add(networkAddress);
}
}
}
} catch (Exception e) {
logger.error("Could not retrieve IP addresses for unicast host list", e);
}
return ipList;
}
}