| /* |
| * 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. |
| */ |
| |
| package org.apache.ignite.spi.discovery.tcp.ipfinder.cloud; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.InetSocketAddress; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import com.google.common.base.Charsets; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Supplier; |
| import com.google.common.io.Files; |
| import org.apache.ignite.internal.IgniteInterruptedCheckedException; |
| import org.apache.ignite.internal.util.tostring.GridToStringExclude; |
| import org.apache.ignite.internal.util.typedef.F; |
| import org.apache.ignite.internal.util.typedef.internal.S; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| import org.apache.ignite.spi.IgniteSpiConfiguration; |
| import org.apache.ignite.spi.IgniteSpiException; |
| import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; |
| import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinderAdapter; |
| import org.jclouds.Constants; |
| import org.jclouds.ContextBuilder; |
| import org.jclouds.compute.ComputeService; |
| import org.jclouds.compute.ComputeServiceContext; |
| import org.jclouds.compute.domain.ComputeMetadata; |
| import org.jclouds.compute.domain.NodeMetadata; |
| import org.jclouds.domain.Credentials; |
| import org.jclouds.domain.Location; |
| import org.jclouds.googlecloud.GoogleCredentialsFromJson; |
| import org.jclouds.location.reference.LocationConstants; |
| |
| /** |
| * IP finder for automatic lookup of nodes running in a cloud. |
| * <p> |
| * Implementation is based on Apache jclouds multi-cloud toolkit. |
| * For information about jclouds visit <a href="https://jclouds.apache.org/">jclouds.apache.org</a>. |
| * <h1 class="header">Configuration</h1> |
| * <h2 class="header">Mandatory</h2> |
| * <ul> |
| * <li>Cloud provider (see {@link #setProvider(String)})</li> |
| * <li>Identity (see {@link #setIdentity(String)})</li> |
| * </ul> |
| * <h2 class="header">Optional</h2> |
| * <ul> |
| * <li>Credential (see {@link #setCredential(String)})</li> |
| * <li>Credential path (see {@link #setCredentialPath(String)}</li> |
| * <li>Regions (see {@link #setRegions(Collection)})</li> |
| * <li>Zones (see {@link #setZones(Collection)}</li> |
| * </ul> |
| * </p> |
| * <p> |
| * The finder forms nodes addresses, that possibly running Ignite, by getting private and public IPs of all |
| * VMs in a cloud and adding a port number to them. |
| * The port is either the one that is set with {@link TcpDiscoverySpi#setLocalPort(int)} or |
| * {@link TcpDiscoverySpi#DFLT_PORT}. |
| * Make sure that all VMs start Ignite instances on the same port, otherwise they will not be able to discover each |
| * other using this IP finder. |
| * </p> |
| * <p> |
| * Both {@link #registerAddresses(Collection)} and {@link #unregisterAddresses(Collection)} has no effect. |
| * </p> |
| * <p> |
| * Note, this finder is only workable when it used directly by cloud VM. |
| * Choose another implementation of {@link org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder} for local |
| * or home network tests. |
| * </p> |
| * <h2 class="header">Java Example</h2> |
| * <pre name="code" class="java"> |
| * String accountId = "your_account_id"; |
| * String accountKey = "your_account_key"; |
| * |
| * TcpDiscoveryCloudIpFinder ipFinder = new TcpDiscoveryCloudIpFinder(); |
| * |
| * ipFinder.setProvider("aws-ec2"); |
| * ipFinder.setIdentity(accountId); |
| * ipFinder.setCredential(accountKey); |
| * ipFinder.setRegions(Collections.<String>emptyList().add("us-east-1")); |
| * ipFinder.setZones(Arrays.asList("us-east-1b", "us-east-1e")); |
| * </pre> |
| * <h2 class="header">Spring Example</h2> |
| * TcpDiscoveryCloudIpFinder can be configured from Spring XML configuration file: |
| * <pre name="code" class="xml"> |
| * <bean id="grid.custom.cfg" class="org.apache.ignite.configuration.IgniteConfiguration" singleton="true"> |
| * ... |
| * <property name="discoverySpi"> |
| * <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi"> |
| * <property name="ipFinder"> |
| * <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.cloud.TcpDiscoveryCloudIpFinder"/> |
| * <property name="provider" value="google-compute-engine"/> |
| * <property name="identity" value="your_service_account_email"/> |
| * <property name="credentialPath" value="path_to_json_key"/> |
| * <property name="zones"> |
| * <list> |
| * <value>us-central1-a</value> |
| * <value>asia-east1-a</value> |
| * </list> |
| * </property> |
| * </bean> |
| * </property> |
| * |
| * <property name="socketTimeout" value="400"/> |
| * </bean> |
| * </property> |
| * ... |
| * </bean> |
| * </pre> |
| * <p> |
| * <img src="http://ignite.apache.org/images/spring-small.png"> |
| * <br> |
| * For information about Spring framework visit <a href="http://www.springframework.org/">www.springframework.org</a> |
| */ |
| public class TcpDiscoveryCloudIpFinder extends TcpDiscoveryIpFinderAdapter { |
| /** JCloud default connection timeout. */ |
| private static final String JCLOUD_CONNECTION_TIMEOUT = "10000"; //10 secs |
| |
| /** Cloud provider. */ |
| private String provider; |
| |
| /** Cloud specific identity (user name, email address, etc.). */ |
| private String identity; |
| |
| /** Cloud specific credential (password, access key, etc.). */ |
| @GridToStringExclude |
| private String credential; |
| |
| /** Path to a cloud specific credential. */ |
| @GridToStringExclude |
| private String credentialPath; |
| |
| /** Regions where VMs are located. */ |
| private TreeSet<String> regions; |
| |
| /** Zones where VMs are located. */ |
| private TreeSet<String> zones; |
| |
| /** Nodes filter by regions and zones. */ |
| private Predicate<ComputeMetadata> nodesFilter; |
| |
| /** Init guard. */ |
| @GridToStringExclude |
| private final AtomicBoolean initGuard = new AtomicBoolean(); |
| |
| /** Init latch. */ |
| @GridToStringExclude |
| private final CountDownLatch initLatch = new CountDownLatch(1); |
| |
| /** JCloud compute service. */ |
| private ComputeService computeService; |
| |
| /** |
| * Constructor. |
| */ |
| public TcpDiscoveryCloudIpFinder() { |
| setShared(true); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public Collection<InetSocketAddress> getRegisteredAddresses() throws IgniteSpiException { |
| initComputeService(); |
| |
| Collection<InetSocketAddress> addresses = new LinkedList<>(); |
| |
| try { |
| Set<NodeMetadata> nodes; |
| |
| if (nodesFilter != null) |
| nodes = (Set<NodeMetadata>)computeService.listNodesDetailsMatching(nodesFilter); |
| else { |
| nodes = new HashSet<>(); |
| |
| for (ComputeMetadata metadata : computeService.listNodes()) |
| nodes.add(computeService.getNodeMetadata(metadata.getId())); |
| } |
| |
| for (NodeMetadata metadata : nodes) { |
| if (metadata.getStatus() != NodeMetadata.Status.RUNNING) |
| continue; |
| |
| for (String addr : metadata.getPrivateAddresses()) |
| addresses.add(new InetSocketAddress(addr, 0)); |
| |
| for (String addr : metadata.getPublicAddresses()) |
| addresses.add(new InetSocketAddress(addr, 0)); |
| } |
| } |
| catch (Exception e) { |
| throw new IgniteSpiException("Failed to get registered addresses for the provider: " + provider, e); |
| } |
| |
| return addresses; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void registerAddresses(Collection<InetSocketAddress> addrs) throws IgniteSpiException { |
| // No-op |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void unregisterAddresses(Collection<InetSocketAddress> addrs) throws IgniteSpiException { |
| // No-op |
| } |
| |
| /** |
| * Sets the cloud provider to use. |
| * |
| * <a href="https://jclouds.apache.org/reference/providers/#compute">Apache jclouds providers list</a> from |
| * ComputeService section contains names of all supported providers. |
| * |
| * @param provider Provider name. |
| * @return {@code this} for chaining. |
| */ |
| @IgniteSpiConfiguration(optional = false) |
| public TcpDiscoveryCloudIpFinder setProvider(String provider) { |
| this.provider = provider; |
| |
| return this; |
| } |
| |
| /** |
| * Sets the identity that is used as a user name during a connection to the cloud. |
| * Depending on a cloud platform it can be an email address, user name, etc. |
| * |
| * Refer to <a href="http://jclouds.apache.org/guides/">Apache jclouds guide</a> to get concrete information on |
| * what is used as an identity for a particular cloud platform. |
| * |
| * @param identity Identity to use during authentication on the cloud. |
| * @return {@code this} for chaining. |
| */ |
| @IgniteSpiConfiguration(optional = false) |
| public TcpDiscoveryCloudIpFinder setIdentity(String identity) { |
| this.identity = identity; |
| |
| return this; |
| } |
| |
| /** |
| * Sets credential that is used during authentication on the cloud. |
| * Depending on a cloud platform it can be a password or access key. |
| * |
| * Refer to <a href="http://jclouds.apache.org/guides/">Apache jclouds guide</a> to get concrete information on |
| * what is used as an credential for a particular cloud platform. |
| * |
| * @param credential Credential to use during authentication on the cloud. |
| * @return {@code this} for chaining. |
| */ |
| @IgniteSpiConfiguration(optional = true) |
| public TcpDiscoveryCloudIpFinder setCredential(String credential) { |
| this.credential = credential; |
| |
| return this; |
| } |
| |
| /** |
| * Sets the path to a credential that is used during authentication on the cloud. |
| * |
| * This method should be used when an access key or private key is stored in a file. |
| * Content of the file, referred by @{code credentialPath}, is fully read and used as a access key or private key |
| * during authentication. |
| * |
| * Refer to <a href="http://jclouds.apache.org/guides/">Apache jclouds guide</a> to get concrete information on |
| * what is used as an credential for a particular cloud platform. |
| * |
| * @param credentialPath Path to the credential to use during authentication on the cloud. |
| * @return {@code this} for chaining. |
| */ |
| @IgniteSpiConfiguration(optional = true) |
| public TcpDiscoveryCloudIpFinder setCredentialPath(String credentialPath) { |
| this.credentialPath = credentialPath; |
| |
| return this; |
| } |
| |
| /** |
| * Sets list of zones where VMs are located. |
| * |
| * If the zones are not set then every zone from regions, set by {@link #setRegions(Collection)}}, will be |
| * taken into account. |
| * |
| * Note, that some cloud providers, like Rackspace, doesn't have a notion of a zone. For such |
| * providers a call to this method is redundant. |
| * |
| * @param zones Zones where VMs are located or null if to take every zone into account. |
| * @return {@code this} for chaining. |
| */ |
| @IgniteSpiConfiguration(optional = true) |
| public TcpDiscoveryCloudIpFinder setZones(Collection<String> zones) { |
| if (!F.isEmpty(zones)) |
| this.zones = new TreeSet<>(zones); |
| |
| return this; |
| } |
| |
| /** |
| * Sets list of regions where VMs are located. |
| * |
| * If the regions are not set then every region, that a cloud provider has, will be investigated. This could lead |
| * to significant performance degradation. |
| * |
| * Note, that some cloud providers, like Google Compute Engine, doesn't have a notion of a region. For such |
| * providers a call to this method is redundant. |
| * |
| * @param regions Regions where VMs are located or null if to check every region a provider has. |
| * @return {@code this} for chaining. |
| */ |
| @IgniteSpiConfiguration(optional = true) |
| public TcpDiscoveryCloudIpFinder setRegions(Collection<String> regions) { |
| if (!F.isEmpty(regions)) |
| this.regions = new TreeSet<>(regions); |
| |
| return this; |
| } |
| |
| /** |
| * Initializes Apache jclouds compute service. |
| */ |
| private void initComputeService() { |
| if (initGuard.compareAndSet(false, true)) |
| try { |
| if (provider == null) |
| throw new IgniteSpiException("Cloud provider is not set."); |
| |
| if (identity == null) |
| throw new IgniteSpiException("Cloud identity is not set."); |
| |
| if (credential != null && credentialPath != null) |
| throw new IgniteSpiException("Both credential and credentialPath are set. Use only one method."); |
| |
| if (credentialPath != null) |
| credential = getCredentialFromFile(); |
| |
| try { |
| ContextBuilder ctxBuilder = ContextBuilder.newBuilder(provider); |
| |
| ctxBuilder.credentials(identity, credential); |
| |
| Properties properties = new Properties(); |
| properties.setProperty(Constants.PROPERTY_SO_TIMEOUT, JCLOUD_CONNECTION_TIMEOUT); |
| properties.setProperty(Constants.PROPERTY_CONNECTION_TIMEOUT, JCLOUD_CONNECTION_TIMEOUT); |
| |
| if (!F.isEmpty(regions)) |
| properties.setProperty(LocationConstants.PROPERTY_REGIONS, keysSetToStr(regions)); |
| |
| if (!F.isEmpty(zones)) |
| properties.setProperty(LocationConstants.PROPERTY_ZONES, keysSetToStr(zones)); |
| |
| ctxBuilder.overrides(properties); |
| |
| computeService = ctxBuilder.buildView(ComputeServiceContext.class).getComputeService(); |
| |
| if (!F.isEmpty(zones) || !F.isEmpty(regions)) { |
| nodesFilter = new Predicate<ComputeMetadata>() { |
| @Override public boolean apply(ComputeMetadata computeMetadata) { |
| String region = null; |
| String zone = null; |
| |
| Location location = computeMetadata.getLocation(); |
| |
| while (location != null) { |
| switch (location.getScope()) { |
| case ZONE: |
| zone = location.getId(); |
| break; |
| |
| case REGION: |
| region = location.getId(); |
| break; |
| } |
| |
| location = location.getParent(); |
| } |
| |
| if (regions != null && region != null && !regions.contains(region)) |
| return false; |
| |
| if (zones != null && zone != null && !zones.contains(zone)) |
| return false; |
| |
| return true; |
| } |
| }; |
| } |
| } |
| catch (Exception e) { |
| throw new IgniteSpiException("Failed to connect to the provider: " + provider, e); |
| } |
| } |
| finally { |
| initLatch.countDown(); |
| } |
| else |
| { |
| try { |
| U.await(initLatch); |
| } |
| catch (IgniteInterruptedCheckedException e) { |
| throw new IgniteSpiException("Thread has been interrupted.", e); |
| } |
| |
| if (computeService == null) |
| throw new IgniteSpiException("Ip finder has not been initialized properly."); |
| } |
| } |
| |
| /** |
| * Reads credential info from {@link #credentialPath} and returns in a string format. |
| * |
| * @return Credential in {@code String} representation. |
| * @throws IgniteSpiException In case of error. |
| */ |
| private String getCredentialFromFile() throws IgniteSpiException { |
| try { |
| String fileContents = Files.toString(new File(credentialPath), Charsets.UTF_8); |
| |
| if (provider.equals("google-compute-engine")) { |
| Supplier<Credentials> credentialSupplier = new GoogleCredentialsFromJson(fileContents); |
| |
| return credentialSupplier.get().credential; |
| } |
| |
| return fileContents; |
| } |
| catch (IOException e) { |
| throw new IgniteSpiException("Failed to retrieve the private key from the file: " + credentialPath, e); |
| } |
| } |
| |
| /** |
| * Converts set keys to string. |
| * |
| * @param set Set. |
| * @return String where keys delimited by ','. |
| */ |
| private String keysSetToStr(Set<String> set) { |
| Iterator<String> iter = set.iterator(); |
| StringBuilder builder = new StringBuilder(); |
| |
| while (iter.hasNext()) { |
| builder.append(iter.next()); |
| |
| if (iter.hasNext()) |
| builder.append(','); |
| } |
| |
| return builder.toString(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public TcpDiscoveryCloudIpFinder setShared(boolean shared) { |
| super.setShared(shared); |
| |
| return this; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String toString() { |
| return S.toString(TcpDiscoveryCloudIpFinder.class, this); |
| } |
| } |