All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.aws.AwsDiscoveryStrategy Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
 *
 * Licensed 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 com.hazelcast.aws;

import com.hazelcast.cluster.Address;
import com.hazelcast.config.InvalidConfigurationException;
import com.hazelcast.config.properties.PropertyDefinition;
import com.hazelcast.logging.ILogger;
import com.hazelcast.logging.Logger;
import com.hazelcast.spi.discovery.AbstractDiscoveryStrategy;
import com.hazelcast.spi.discovery.DiscoveryNode;
import com.hazelcast.spi.discovery.DiscoveryStrategy;
import com.hazelcast.spi.discovery.SimpleDiscoveryNode;
import com.hazelcast.spi.discovery.integration.DiscoveryMode;
import com.hazelcast.spi.exception.NoCredentialsException;
import com.hazelcast.spi.exception.RestClientException;
import com.hazelcast.spi.partitiongroup.PartitionGroupMetaData;
import com.hazelcast.spi.utils.PortRange;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static com.hazelcast.aws.AwsProperties.ACCESS_KEY;
import static com.hazelcast.aws.AwsProperties.CLUSTER;
import static com.hazelcast.aws.AwsProperties.CONNECTION_RETRIES;
import static com.hazelcast.aws.AwsProperties.CONNECTION_TIMEOUT_SECONDS;
import static com.hazelcast.aws.AwsProperties.FAMILY;
import static com.hazelcast.aws.AwsProperties.HOST_HEADER;
import static com.hazelcast.aws.AwsProperties.IAM_ROLE;
import static com.hazelcast.aws.AwsProperties.PORT;
import static com.hazelcast.aws.AwsProperties.READ_TIMEOUT_SECONDS;
import static com.hazelcast.aws.AwsProperties.REGION;
import static com.hazelcast.aws.AwsProperties.SECRET_KEY;
import static com.hazelcast.aws.AwsProperties.SECURITY_GROUP_NAME;
import static com.hazelcast.aws.AwsProperties.SERVICE_NAME;
import static com.hazelcast.aws.AwsProperties.TAG_KEY;
import static com.hazelcast.aws.AwsProperties.TAG_VALUE;

/**
 * AWS implementation of {@link DiscoveryStrategy}.
 *
 * @see AwsClient
 */
public class AwsDiscoveryStrategy
        extends AbstractDiscoveryStrategy {
    private static final ILogger LOGGER = Logger.getLogger(AwsDiscoveryStrategy.class);

    private static final int HTTP_FORBIDDEN = 403;

    private static final String DEFAULT_PORT_RANGE = "5701-5708";
    private static final Integer DEFAULT_CONNECTION_RETRIES = 3;
    private static final int DEFAULT_CONNECTION_TIMEOUT_SECONDS = 10;
    private static final int DEFAULT_READ_TIMEOUT_SECONDS = 10;

    private final AwsClient awsClient;
    private final PortRange portRange;

    private final Map memberMetadata = new HashMap<>();

    private boolean isKnownExceptionAlreadyLogged;
    private boolean isEmptyAddressListAlreadyLogged;

    AwsDiscoveryStrategy(Map properties) {
        this(null, properties);
    }

    AwsDiscoveryStrategy(DiscoveryNode discoveryNode, Map properties) {
        super(LOGGER, properties);
        AwsConfig awsConfig = createAwsConfig()
                .setDiscoveryMode(discoveryNode == null ? DiscoveryMode.Client : DiscoveryMode.Member)
                .build();
        LOGGER.info("Using AWS discovery plugin with configuration: " + awsConfig);
        this.awsClient = AwsClientConfigurator.createAwsClient(awsConfig);
        this.portRange = awsConfig.getHzPort();
    }

    /**
     * For test purposes only.
     */
    AwsDiscoveryStrategy(Map properties, AwsClient client) {
        super(LOGGER, properties);
        this.awsClient = client;
        this.portRange = new PortRange(getPortRange());
    }

    private AwsConfig.Builder createAwsConfig() {
        try {
            return AwsConfig.builder()
                    .setAccessKey(getOrNull(ACCESS_KEY)).setSecretKey(getOrNull(SECRET_KEY))
                    .setRegion(getOrDefault(REGION.getDefinition(), null))
                    .setIamRole(getOrNull(IAM_ROLE))
                    .setHostHeader(getOrNull(HOST_HEADER.getDefinition()))
                    .setSecurityGroupName(getOrNull(SECURITY_GROUP_NAME)).setTagKey(getOrNull(TAG_KEY))
                    .setTagValue(getOrNull(TAG_VALUE))
                    .setConnectionTimeoutSeconds(getOrDefault(CONNECTION_TIMEOUT_SECONDS.getDefinition(),
                            DEFAULT_CONNECTION_TIMEOUT_SECONDS))
                    .setConnectionRetries(getOrDefault(CONNECTION_RETRIES.getDefinition(), DEFAULT_CONNECTION_RETRIES))
                    .setReadTimeoutSeconds(getOrDefault(READ_TIMEOUT_SECONDS.getDefinition(), DEFAULT_READ_TIMEOUT_SECONDS))
                    .setHzPort(new PortRange(getPortRange()))
                    .setCluster(getOrNull(CLUSTER))
                    .setFamily(getOrNull(FAMILY))
                    .setServiceName(getOrNull(SERVICE_NAME));
        } catch (IllegalArgumentException e) {
            throw new InvalidConfigurationException("AWS configuration is not valid", e);
        }
    }

    /**
     * Returns port range from properties or default value if the property does not exist.
     * 

* Note that {@link AbstractDiscoveryStrategy#getOrDefault(PropertyDefinition, Comparable)} cannot be reused, since * the "hz-port" property can be either {@code String} or {@code Integer}. */ private String getPortRange() { Object portRange = getOrNull(PORT.getDefinition()); if (portRange == null) { return DEFAULT_PORT_RANGE; } return portRange.toString(); } @Override public Map discoverLocalMetadata() { if (memberMetadata.isEmpty()) { String availabilityZone = awsClient.getAvailabilityZone(); LOGGER.info(String.format("Availability zone found: '%s'", availabilityZone)); memberMetadata.put(PartitionGroupMetaData.PARTITION_GROUP_ZONE, availabilityZone); getPlacementGroup().ifPresent(pg -> memberMetadata.put(PartitionGroupMetaData.PARTITION_GROUP_PLACEMENT, availabilityZone + '-' + pg)); } return memberMetadata; } /** * Resolves the placement group of the resource if it belongs to any. *

* If the placement group is Cluster Placement Group or Spread Placement Group, then returns * the group name. If it is Partition Placement Group, then returns the group name with the * partition number prefixed by '-' appended. *

* When forming partition groups, this name should be combined with zone name. Otherwise * two resources in different zones but in the same placement group will be assumed as * a single group. * * @return Placement group name if exists, empty otherwise. * @see AwsClient#getPlacementGroup() * @see AwsClient#getPlacementPartitionNumber() */ private Optional getPlacementGroup() { Optional placementGroup = awsClient.getPlacementGroup(); if (placementGroup.isEmpty()) { LOGGER.fine("No placement group is found."); return Optional.empty(); } StringBuilder result = new StringBuilder(placementGroup.get()); awsClient.getPlacementPartitionNumber().ifPresent(ppn -> result.append('-').append(ppn)); LOGGER.info(String.format("Placement group found: '%s'", result)); return Optional.of(result.toString()); } @Override public Iterable discoverNodes() { try { Map addresses = awsClient.getAddresses(); logResult(addresses); List result = new ArrayList<>(); for (Map.Entry entry : addresses.entrySet()) { for (int port = portRange.getFromPort(); port <= portRange.getToPort(); port++) { Address privateAddress = new Address(entry.getKey(), port); Address publicAddress = new Address(entry.getValue(), port); result.add(new SimpleDiscoveryNode(privateAddress, publicAddress)); } } return result; } catch (NoCredentialsException e) { if (!isKnownExceptionAlreadyLogged) { LOGGER.warning("No AWS credentials found! Starting standalone. To use Hazelcast AWS discovery, configure" + " properties (access-key, secret-key) or assign the required IAM Role to your EC2 instance"); LOGGER.finest(e); isKnownExceptionAlreadyLogged = true; } } catch (RestClientException e) { if (e.getHttpErrorCode() == HTTP_FORBIDDEN) { if (!isKnownExceptionAlreadyLogged) { LOGGER.warning("AWS IAM Role Policy missing 'ec2:DescribeInstances' Action! Starting standalone."); isKnownExceptionAlreadyLogged = true; } LOGGER.finest(e); } else { LOGGER.warning("Cannot discover nodes. Starting standalone.", e); } } catch (Exception e) { LOGGER.warning("Cannot discover nodes. Starting standalone.", e); } return Collections.emptyList(); } private void logResult(Map addresses) { if (addresses.isEmpty() && !isEmptyAddressListAlreadyLogged) { LOGGER.warning("No IP addresses found! Starting standalone."); isEmptyAddressListAlreadyLogged = true; } LOGGER.fine(String.format("Found the following (private => public) addresses: %s", addresses)); } private String getOrNull(AwsProperties awsProperties) { return getOrNull(awsProperties.getDefinition()); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy