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

com.hazelcast.aws.AwsEc2Api 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.internal.util.StringUtil;
import com.hazelcast.logging.ILogger;
import com.hazelcast.logging.Logger;
import org.w3c.dom.Node;

import java.time.Clock;
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.AwsRequestUtils.canonicalQueryString;
import static com.hazelcast.aws.AwsRequestUtils.createRestClient;
import static com.hazelcast.aws.AwsRequestUtils.currentTimestamp;

/**
 * Responsible for connecting to AWS EC2 API.
 *
 * @see AWS EC2 API
 */
class AwsEc2Api {
    private static final ILogger LOGGER = Logger.getLogger(AwsEc2Api.class);

    private final String endpoint;
    private final AwsConfig awsConfig;
    private final AwsRequestSigner requestSigner;
    private final Clock clock;
    private boolean isNoPublicIpAlreadyLogged;

    AwsEc2Api(String endpoint, AwsConfig awsConfig, AwsRequestSigner requestSigner, Clock clock) {
        this.endpoint = endpoint;
        this.awsConfig = awsConfig;
        this.requestSigner = requestSigner;
        this.clock = clock;
    }

    /**
     * Calls AWS EC2 Describe Instances API, parses the response, and returns mapping from private to public IPs.
     * 

* Note that if EC2 Instance does not have a public IP, then an entry (private-ip, null) is returned. * * @return map from private to public IP * @see EC2 Describe Instances */ Map describeInstances(AwsCredentials credentials) { Map attributes = createAttributesDescribeInstances(); Map headers = createHeaders(attributes, credentials); String response = callAwsService(attributes, headers); return parseDescribeInstances(response); } private Map createAttributesDescribeInstances() { Map attributes = createSharedAttributes(); attributes.put("Action", "DescribeInstances"); attributes.putAll(filterAttributesDescribeInstances()); return attributes; } private Map filterAttributesDescribeInstances() { Filter filter = new Filter(); for (Tag tag : awsConfig.getTags()) { addTagFilter(filter, tag); } if (!StringUtil.isNullOrEmptyAfterTrim(awsConfig.getSecurityGroupName())) { filter.add("instance.group-name", awsConfig.getSecurityGroupName()); } filter.add("instance-state-name", "running"); return filter.getFilterAttributes(); } /** * Adds filter entry to {@link Filter} base on provided {@link Tag}. Follows AWS API recommendations for * filtering EC2 instances using tags. * * @see * EC2 Describe Instances - Request Parameters */ private void addTagFilter(Filter filter, Tag tag) { if (!StringUtil.isNullOrEmptyAfterTrim(tag.getKey()) && !StringUtil.isNullOrEmptyAfterTrim(tag.getValue())) { filter.add("tag:" + tag.getKey(), tag.getValue()); } else if (!StringUtil.isNullOrEmptyAfterTrim(tag.getKey())) { filter.add("tag-key", tag.getKey()); } else if (!StringUtil.isNullOrEmptyAfterTrim(tag.getValue())) { filter.add("tag-value", tag.getValue()); } } private static Map parseDescribeInstances(String xmlResponse) { Map result = new HashMap<>(); XmlNode.create(xmlResponse) .getSubNodes("reservationset").stream() .flatMap(e -> e.getSubNodes("item").stream()) .flatMap(e -> e.getSubNodes("instancesset").stream()) .flatMap(e -> e.getSubNodes("item").stream()) .filter(e -> e.getValue("privateipaddress") != null) .peek(AwsEc2Api::logInstanceName) .forEach(e -> result.put(e.getValue("privateipaddress"), e.getValue("ipaddress"))); return result; } private static void logInstanceName(XmlNode item) { LOGGER.fine(String.format("Accepting EC2 instance [%s][%s]", parseInstanceName(item).orElse(""), item.getValue("privateipaddress"))); } private static Optional parseInstanceName(XmlNode nodeHolder) { return nodeHolder.getSubNodes("tagset").stream() .flatMap(e -> e.getSubNodes("item").stream()) .filter(AwsEc2Api::isNameField) .flatMap(e -> e.getSubNodes("value").stream()) .map(XmlNode::getNode) .map(Node::getFirstChild) .map(Node::getNodeValue) .findFirst(); } private static boolean isNameField(XmlNode item) { return item.getSubNodes("key").stream() .map(XmlNode::getNode) .map(Node::getFirstChild) .map(Node::getNodeValue) .map("Name"::equals) .findFirst() .orElse(false); } /** * Calls AWS EC2 Describe Network Interfaces API, parses the response, and returns mapping from private to public * IPs. *

* Note that if the given private IP does not have a public IP association, then an entry (private-ip, null) * is returned. * * @return map from private to public IP * @see * EC2 Describe Network Interfaces * *

* @implNote This is done as best-effort and does not fail if no public addresses are found, because: *

    *
  • Task may not have public IP addresses
  • *
  • Task may not have access rights to query for public addresses
  • *
*

* This is performed regardless of the configured use-public-ip value * to make external multi socket clients able to work properly when possible. */ Map describeNetworkInterfaces(List privateAddresses, AwsCredentials credentials) { if (privateAddresses.isEmpty()) { return Collections.emptyMap(); } try { Map attributes = createAttributesDescribeNetworkInterfaces(privateAddresses); Map headers = createHeaders(attributes, credentials); String response = callAwsService(attributes, headers); return parseDescribeNetworkInterfaces(response); } catch (Exception e) { LOGGER.finest(e); // Log warning only once. if (!isNoPublicIpAlreadyLogged) { LOGGER.warning("Cannot fetch the public IPs of ECS Tasks. You won't be able to use " + "Hazelcast Smart Client from outside of this VPC."); isNoPublicIpAlreadyLogged = true; } Map map = new HashMap<>(); privateAddresses.forEach(k -> map.put(k, null)); return map; } } private Map createAttributesDescribeNetworkInterfaces(List privateAddresses) { Map attributes = createSharedAttributes(); attributes.put("Action", "DescribeNetworkInterfaces"); attributes.putAll(filterAttributesDescribeNetworkInterfaces(privateAddresses)); return attributes; } private Map filterAttributesDescribeNetworkInterfaces(List privateAddresses) { Filter filter = new Filter(); filter.addMulti("addresses.private-ip-address", privateAddresses); return filter.getFilterAttributes(); } private static Map parseDescribeNetworkInterfaces(String xmlResponse) { Map result = new HashMap<>(); XmlNode.create(xmlResponse) .getSubNodes("networkinterfaceset").stream() .flatMap(e -> e.getSubNodes("item").stream()) .filter(e -> e.getValue("privateipaddress") != null) .forEach(e -> result.put( e.getValue("privateipaddress"), e.getSubNodes("association").stream() .map(a -> a.getValue("publicip")) .findFirst() .orElse(null) )); return result; } private static Map createSharedAttributes() { Map attributes = new HashMap<>(); attributes.put("Version", "2016-11-15"); return attributes; } private Map createHeaders(Map attributes, AwsCredentials credentials) { Map headers = new HashMap<>(); if (!StringUtil.isNullOrEmptyAfterTrim(credentials.getToken())) { headers.put("X-Amz-Security-Token", credentials.getToken()); } headers.put("Host", endpoint); String timestamp = currentTimestamp(clock); headers.put("X-Amz-Date", timestamp); headers.put("Authorization", requestSigner.authHeader(attributes, headers, "", credentials, timestamp, "GET")); return headers; } private String callAwsService(Map attributes, Map headers) { String query = canonicalQueryString(attributes); return createRestClient(urlFor(endpoint, query), awsConfig) .withHeaders(headers) .get() .getBody(); } private static String urlFor(String endpoint, String query) { return AwsRequestUtils.urlFor(endpoint) + "/?" + query; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy