com.vmware.photon.controller.model.adapters.awsadapter.AWSEndpointAdapterService.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.photon.controller.model.adapters.awsadapter.AWSEndpointAdapterService.java

Source

/*
 * Copyright (c) 2015-2016 VMware, 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.vmware.photon.controller.model.adapters.awsadapter;

import static com.vmware.photon.controller.model.adapterapi.EndpointConfigRequest.PRIVATE_KEYID_KEY;
import static com.vmware.photon.controller.model.adapterapi.EndpointConfigRequest.PRIVATE_KEY_KEY;
import static com.vmware.photon.controller.model.adapterapi.EndpointConfigRequest.REGION_KEY;
import static com.vmware.photon.controller.model.adapterapi.EndpointConfigRequest.ZONE_KEY;
import static com.vmware.xenon.common.Operation.STATUS_CODE_UNAUTHORIZED;

import java.net.URI;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.function.BiConsumer;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.handlers.AsyncHandler;
import com.amazonaws.services.ec2.AmazonEC2AsyncClient;
import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesRequest;
import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesResult;
import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient;

import com.vmware.photon.controller.model.adapterapi.EndpointConfigRequest;
import com.vmware.photon.controller.model.adapters.awsadapter.util.AWSClientManager;
import com.vmware.photon.controller.model.adapters.awsadapter.util.AWSClientManagerFactory;
import com.vmware.photon.controller.model.adapters.util.AdapterUriUtil;
import com.vmware.photon.controller.model.adapters.util.EndpointAdapterUtils;
import com.vmware.photon.controller.model.adapters.util.EndpointAdapterUtils.Retriever;
import com.vmware.photon.controller.model.resources.ComputeDescriptionService.ComputeDescription;
import com.vmware.photon.controller.model.resources.ComputeService.ComputeState;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.ServiceErrorResponse;
import com.vmware.xenon.common.StatelessService;
import com.vmware.xenon.common.UriUtils;
import com.vmware.xenon.common.Utils;
import com.vmware.xenon.services.common.AuthCredentialsService.AuthCredentialsServiceState;

/**
 * Adapter to validate and enhance AWS based endpoints.
 *
 */
public class AWSEndpointAdapterService extends StatelessService {
    public static final String SELF_LINK = AWSUriPaths.AWS_ENDPOINT_CONFIG_ADAPTER;

    private AWSClientManager clientManager;

    public AWSEndpointAdapterService() {
        this.clientManager = AWSClientManagerFactory.getClientManager(AWSConstants.AwsClientType.EC2);
    }

    @Override
    public void handleStop(Operation op) {
        AWSClientManagerFactory.returnClientManager(this.clientManager, AWSConstants.AwsClientType.EC2);
        super.handleStop(op);
    }

    @Override
    public void handlePatch(Operation op) {
        if (!op.hasBody()) {
            op.fail(new IllegalArgumentException("body is required"));
            return;
        }
        EndpointConfigRequest body = op.getBody(EndpointConfigRequest.class);

        EndpointAdapterUtils.handleEndpointRequest(this, op, body, credentials(), computeDesc(), compute(),
                validate(body));
    }

    private BiConsumer<AuthCredentialsServiceState, BiConsumer<ServiceErrorResponse, Throwable>> validate(
            EndpointConfigRequest body) {

        return (credentials, callback) -> {
            String regionId = body.endpointProperties.get(REGION_KEY);
            AmazonEC2AsyncClient client = AWSUtils.getAsyncClient(credentials, regionId,
                    this.clientManager.getExecutor(getHost()));

            // make a call to validate credentials
            client.describeAvailabilityZonesAsync(new DescribeAvailabilityZonesRequest(),
                    new AsyncHandler<DescribeAvailabilityZonesRequest, DescribeAvailabilityZonesResult>() {
                        @Override
                        public void onError(Exception e) {
                            if (e instanceof AmazonServiceException) {
                                AmazonServiceException ase = (AmazonServiceException) e;
                                if (ase.getStatusCode() == STATUS_CODE_UNAUTHORIZED) {
                                    ServiceErrorResponse r = Utils.toServiceErrorResponse(e);
                                    r.statusCode = STATUS_CODE_UNAUTHORIZED;
                                    callback.accept(r, e);
                                }
                                return;
                            }

                            callback.accept(null, e);
                        }

                        @Override
                        public void onSuccess(DescribeAvailabilityZonesRequest request,
                                DescribeAvailabilityZonesResult describeAvailabilityZonesResult) {
                            callback.accept(null, null);
                        }
                    });
        };
    }

    private BiConsumer<AuthCredentialsServiceState, Retriever> credentials() {
        return (c, r) -> {
            c.privateKey = r.getRequired(PRIVATE_KEY_KEY);
            c.privateKeyId = r.getRequired(PRIVATE_KEYID_KEY);
            c.type = "accessKey";
        };
    }

    private BiConsumer<ComputeDescription, Retriever> computeDesc() {
        return (cd, r) -> {
            cd.regionId = r.getRequired(REGION_KEY);
            cd.zoneId = r.get(ZONE_KEY).orElse(null);
            cd.environmentName = ComputeDescription.ENVIRONMENT_NAME_AWS;

            cd.instanceAdapterReference = AdapterUriUtil.buildAdapterUri(this.getHost(),
                    AWSUriPaths.AWS_INSTANCE_ADAPTER);
            cd.enumerationAdapterReference = AdapterUriUtil.buildAdapterUri(this.getHost(),
                    AWSUriPaths.AWS_ENUMERATION_ADAPTER);
            cd.powerAdapterReference = AdapterUriUtil.buildAdapterUri(this.getHost(),
                    AWSUriPaths.AWS_POWER_ADAPTER);

            URI statsAdapterUri = AdapterUriUtil.buildAdapterUri(this.getHost(), AWSUriPaths.AWS_STATS_ADAPTER);
            URI costStatsAdapterUri = AdapterUriUtil.buildAdapterUri(this.getHost(),
                    AWSUriPaths.AWS_COST_STATS_ADAPTER);

            cd.statsAdapterReferences = new LinkedHashSet<>();
            cd.statsAdapterReferences.add(costStatsAdapterUri);
            cd.statsAdapterReferences.add(statsAdapterUri);
            cd.statsAdapterReference = statsAdapterUri;
        };
    }

    private BiConsumer<ComputeState, Retriever> compute() {
        return (c, r) -> {
            StringBuffer b = new StringBuffer("https://ec2.");
            b.append(r.getRequired(REGION_KEY));
            b.append(".amazonaws.com");

            c.adapterManagementReference = UriUtils.buildUri(b.toString());
            String billsBucketName = r.get(AWSConstants.AWS_BILLS_S3_BUCKET_NAME_KEY).orElse(null);
            if (billsBucketName != null) {
                addEntryToCustomProperties(c, AWSConstants.AWS_BILLS_S3_BUCKET_NAME_KEY, billsBucketName);
            }

            Boolean mock = Boolean.valueOf(r.getRequired(EndpointAdapterUtils.MOCK_REQUEST));
            if (!mock) {
                String accountId = getAccountId(r.getRequired(PRIVATE_KEYID_KEY), r.getRequired(PRIVATE_KEY_KEY));
                if (accountId != null && !accountId.isEmpty()) {
                    addEntryToCustomProperties(c, AWSConstants.AWS_ACCOUNT_ID_KEY, accountId);
                }
            }
        };
    }

    private void addEntryToCustomProperties(ComputeState c, String key, String value) {
        if (c.customProperties == null) {
            c.customProperties = new HashMap<>();
        }
        c.customProperties.put(key, value);
    }

    /**
     * Method gets the aws accountId from the specified credentials.
     *
     * @param privateKeyId
     * @param privateKey
     * @return account ID
     */
    private String getAccountId(String privateKeyId, String privateKey) {
        AWSCredentials awsCredentials = new BasicAWSCredentials(privateKeyId, privateKey);
        AmazonIdentityManagementClient iamClient = new AmazonIdentityManagementClient(awsCredentials);
        String userId = null;
        try {
            if ((iamClient.getUser() != null) && (iamClient.getUser().getUser() != null)
                    && (iamClient.getUser().getUser().getArn() != null)) {

                String arn = iamClient.getUser().getUser().getArn();
                /*
                 * arn:aws:service:region:account:resource -> so limiting the split to 6 words and
                 * extracting the accountId which is 5th one in list. If the user is not authorized
                 * to perform iam:GetUser on that resource,still error mesage will have accountId
                 */
                userId = arn.split(":", 6)[4];
            }
        } catch (AmazonServiceException ex) {
            if (ex.getErrorCode().compareTo("AccessDenied") == 0) {
                String msg = ex.getMessage();
                userId = msg.split(":", 7)[5];
            }
        }
        return userId;
    }
}