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

com.hazelcast.gcp.GcpClient Maven / Gradle / Ivy

There is a newer version: 5.0-BETA-1
Show newest version
/*
 * Copyright 2020 Hazelcast Inc.
 *
 * Licensed under the Hazelcast Community License (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the
 * License at
 *
 * http://hazelcast.com/hazelcast-community-license
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.hazelcast.gcp;

import com.hazelcast.logging.ILogger;
import com.hazelcast.logging.Logger;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;

/**
 * Responsible for fetching the discovery information from GCP APIs.
 */
class GcpClient {
    private static final ILogger LOGGER = Logger.getLogger(GcpDiscoveryStrategy.class);

    private static final int HTTP_UNAUTHORIZED = 401;
    private static final int HTTP_FORBIDDEN = 403;
    private static final int HTTP_NOT_FOUND = 404;

    private static final int RETRIES = 3;
    private static final List NON_RETRYABLE_KEYWORDS = asList("Private key json file not found",
            "Request had insufficient authentication scopes", "Required 'compute.instances.list' permission",
            "Service account not enabled on this instance");

    private boolean isKnownExceptionAlreadyLogged;

    private final GcpMetadataApi gcpMetadataApi;
    private final GcpComputeApi gcpComputeApi;
    private final GcpAuthenticator gcpAuthenticator;

    private final String privateKeyPath;
    private final List projects;
    private final List zones;
    private final Label label;

    GcpClient(GcpMetadataApi gcpMetadataApi, GcpComputeApi gcpComputeApi, GcpAuthenticator gcpAuthenticator,
              GcpConfig gcpConfig) {
        this.gcpMetadataApi = gcpMetadataApi;
        this.gcpComputeApi = gcpComputeApi;
        this.gcpAuthenticator = gcpAuthenticator;

        this.privateKeyPath = gcpConfig.getPrivateKeyPath();
        this.projects = projectFromConfigOrMetadataApi(gcpConfig);
        this.zones = zonesFromConfigOrComputeApi(gcpConfig);
        this.label = gcpConfig.getLabel();
    }

    private List projectFromConfigOrMetadataApi(final GcpConfig gcpConfig) {
        if (!gcpConfig.getProjects().isEmpty()) {
            return gcpConfig.getProjects();
        }
        LOGGER.finest("Property 'projects' not configured, fetching the current GCP project");
        return singletonList(RetryUtils.retry(new Callable() {
            @Override
            public String call() {
                return gcpMetadataApi.currentProject();
            }
        }, RETRIES, NON_RETRYABLE_KEYWORDS));
    }

    private List zonesFromConfigOrComputeApi(final GcpConfig gcpConfig) {
        try {
            if (gcpConfig.getRegion() != null) {
                LOGGER.finest("Property 'region' configured, fetching GCP zones of the specified GCP region");
                return RetryUtils.retry(new Callable>() {
                    @Override
                    public List call() {
                        return fetchZones(gcpConfig.getRegion());
                    }
                }, RETRIES, NON_RETRYABLE_KEYWORDS);
            }

            if (!gcpConfig.getZones().isEmpty()) {
                return gcpConfig.getZones();
            }

            LOGGER.finest("Property 'zones' not configured, fetching GCP zones of the current GCP region");
            return RetryUtils.retry(new Callable>() {
                @Override
                public List call() {
                    String region = gcpMetadataApi.currentRegion();
                    return fetchZones(region);
                }
            }, RETRIES, NON_RETRYABLE_KEYWORDS);
        } catch (RestClientException e) {
            handleKnownException(e);
            return emptyList();
        }
    }

    List getAddresses() {
        try {
            return RetryUtils.retry(new Callable>() {
                @Override
                public List call() {
                    return fetchGcpAddresses();
                }
            }, RETRIES, NON_RETRYABLE_KEYWORDS);
        } catch (RestClientException e) {
            handleKnownException(e);
            return emptyList();
        }
    }

    private List fetchZones(String region) {
        List zones = new ArrayList<>();
        String accessToken = fetchAccessToken();
        for (String project : projects) {
            zones.addAll(gcpComputeApi.zones(project, region, accessToken));
        }
        return zones;
    }

    private List fetchGcpAddresses() {
        LOGGER.finest("Fetching OAuth Access Token");
        final String accessToken = fetchAccessToken();

        List result = new ArrayList();
        for (final String project : projects) {
            for (final String zone : zones) {
                LOGGER.finest(String.format("Fetching instances for project '%s' and zone '%s'", project, zone));
                List addresses = gcpComputeApi.instances(project, zone, label, accessToken);
                LOGGER.finest(String.format("Found the following instances for project '%s' and zone '%s': %s", project, zone,
                        addresses));
                result.addAll(addresses);
            }
        }
        return result;
    }

    private String fetchAccessToken() {
        if (privateKeyPath != null) {
            return gcpAuthenticator.refreshAccessToken(privateKeyPath);
        }
        return gcpMetadataApi.accessToken();
    }

    String getAvailabilityZone() {
        return gcpMetadataApi.currentZone();
    }

    private void handleKnownException(RestClientException e) {
        if (e.getHttpErrorCode() == HTTP_UNAUTHORIZED) {
            if (!isKnownExceptionAlreadyLogged) {
                LOGGER.warning("Google Cloud API Authorization failed! Check your credentials. Starting standalone.");
                isKnownExceptionAlreadyLogged = true;
            }
        } else if (e.getHttpErrorCode() == HTTP_FORBIDDEN) {
            if (!isKnownExceptionAlreadyLogged) {
                LOGGER.warning("Google Cloud API access is forbidden! Starting standalone. To use Hazelcast GCP discovery, "
                        + "make sure that your service account has at minimum \"Read Only\" Access Scope to Compute Engine API.");
                isKnownExceptionAlreadyLogged = true;
            }
        } else if (e.getHttpErrorCode() == HTTP_NOT_FOUND) {
            if (!isKnownExceptionAlreadyLogged) {
                LOGGER.warning("Google Cloud API Not Found! Starting standalone. Please check that you have a service account "
                        + "assigned to your VM instance or `private-key-path` property defined.");
                isKnownExceptionAlreadyLogged = true;
            }
        } else {
            throw e;
        }
        LOGGER.finest(e);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy