com.netflix.spinnaker.clouddriver.aws.security.AmazonClientInvocationHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.netflix.spinnaker.clouddriver.aws.security.AmazonClientInvocationHandler.java

Source

/*
 * Copyright 2015 Netflix, Inc.
 *
 * 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.netflix.spinnaker.clouddriver.aws.security;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.AmazonWebServiceRequest;
import com.amazonaws.services.autoscaling.model.*;
import com.amazonaws.services.cloudwatch.model.DescribeAlarmsRequest;
import com.amazonaws.services.cloudwatch.model.DescribeAlarmsResult;
import com.amazonaws.services.cloudwatch.model.MetricAlarm;
import com.amazonaws.services.ec2.model.*;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.elasticloadbalancing.model.DescribeLoadBalancersRequest;
import com.amazonaws.services.elasticloadbalancing.model.DescribeLoadBalancersResult;
import com.amazonaws.services.elasticloadbalancing.model.LoadBalancerDescription;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.*;

class AmazonClientInvocationHandler implements InvocationHandler {

    private static final Logger log = LoggerFactory.getLogger(AmazonClientInvocationHandler.class);

    static final ThreadLocal<Long> lastModified = new ThreadLocal<>();

    private final String edda;
    private final HttpClient httpClient;
    private final Object delegate;
    private final String serviceName;
    private final ObjectMapper objectMapper;
    private final EddaTimeoutConfig eddaTimeoutConfig;

    public AmazonClientInvocationHandler(Object delegate, String serviceName, String edda, HttpClient httpClient,
            ObjectMapper objectMapper, EddaTimeoutConfig eddaTimeoutConfig) {
        this.edda = edda;
        this.httpClient = httpClient;
        this.objectMapper = objectMapper;
        this.delegate = delegate;
        this.serviceName = serviceName;
        this.eddaTimeoutConfig = eddaTimeoutConfig == null ? EddaTimeoutConfig.DEFAULT : eddaTimeoutConfig;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Method thisMethod = this.getClass().getMethod(method.getName(),
                    args != null && args.length > 0 ? getClassArgs(args) : new Class[0]);
            return thisMethod.invoke(this, args);
        } catch (NoSuchMethodException e) {
            return method.invoke(delegate, args);
        }
    }

    static Class[] getClassArgs(Object[] args) {
        List<Class> classes = new ArrayList<>();
        for (Object object : args) {
            classes.add(object.getClass());
        }
        return classes.toArray(new Class[classes.size()]);
    }

    ////////////////////////////////////
    //
    // AmazonAutoScaling
    //
    ////////////////////////////////////
    public DescribeAutoScalingGroupsResult describeAutoScalingGroups() {
        return describeAutoScalingGroups(null);
    }

    public DescribeAutoScalingGroupsResult describeAutoScalingGroups(DescribeAutoScalingGroupsRequest request) {
        return new DescribeAutoScalingGroupsResult().withAutoScalingGroups(
                describe(request, "autoScalingGroupNames", "autoScalingGroups", AutoScalingGroup.class));
    }

    ////////////////////////////////////
    //
    // AmazonCloudWatch
    //
    ////////////////////////////////////
    public DescribeAlarmsResult describeAlarms() {
        return describeAlarms(null);
    }

    public DescribeAlarmsResult describeAlarms(DescribeAlarmsRequest request) {
        return new DescribeAlarmsResult()
                .withMetricAlarms(describe(request, "alarmNames", "alarms", MetricAlarm.class));
    }

    public DescribeScheduledActionsResult describeScheduledActions() {
        return describeScheduledActions(null);
    }

    public DescribeScheduledActionsResult describeScheduledActions(DescribeScheduledActionsRequest request) {
        return new DescribeScheduledActionsResult().withScheduledUpdateGroupActions(
                describe(request, "scheduledActionNames", "scheduledActions", ScheduledUpdateGroupAction.class));
    }

    public DescribePoliciesResult describePolicies() {
        return describePolicies(null);
    }

    public DescribePoliciesResult describePolicies(DescribePoliciesRequest request) {
        return new DescribePoliciesResult()
                .withScalingPolicies(describe(request, "policyNames", "scalingPolicies", ScalingPolicy.class));
    }

    ////////////////////////////////////
    //
    // AmazonEC2
    //
    ////////////////////////////////////
    public DescribeImagesResult describeImages() {
        return describeImages(null);
    }

    public DescribeImagesResult describeImages(DescribeImagesRequest request) {
        return new DescribeImagesResult().withImages(describe(request, "imageIds", "images", Image.class));
    }

    public DescribeInstancesResult describeInstances() {
        return describeInstances(null);
    }

    public DescribeInstancesResult describeInstances(DescribeInstancesRequest request) {
        return new DescribeInstancesResult().withReservations(new Reservation().withReservationId("1234")
                .withInstances(describe(request, "instanceIds", "../view/instances", Instance.class)));
    }

    public DescribeLaunchConfigurationsResult describeLaunchConfigurations() {
        return describeLaunchConfigurations(null);
    }

    public DescribeLaunchConfigurationsResult describeLaunchConfigurations(
            DescribeLaunchConfigurationsRequest request) {
        return new DescribeLaunchConfigurationsResult().withLaunchConfigurations(
                describe(request, "launchConfigurationNames", "launchConfigurations", LaunchConfiguration.class));
    }

    public DescribeReservedInstancesOfferingsResult describeReservedInstancesOfferings() {
        return describeReservedInstancesOfferings(null);
    }

    public DescribeReservedInstancesOfferingsResult describeReservedInstancesOfferings(
            DescribeReservedInstancesOfferingsRequest request) {
        return new DescribeReservedInstancesOfferingsResult().withReservedInstancesOfferings(describe(request,
                "reservedInstancesOfferingIds", "reservedInstancesOfferings", ReservedInstancesOffering.class));
    }

    public DescribeSecurityGroupsResult describeSecurityGroups() {
        return describeSecurityGroups(null);
    }

    public DescribeSecurityGroupsResult describeSecurityGroups(DescribeSecurityGroupsRequest request) {
        return new DescribeSecurityGroupsResult()
                .withSecurityGroups(describe(request, "groupIds", "securityGroups", SecurityGroup.class));
    }

    public DescribeSubnetsResult describeSubnets() {
        return describeSubnets(null);
    }

    public DescribeSubnetsResult describeSubnets(DescribeSubnetsRequest request) {
        return new DescribeSubnetsResult().withSubnets(describe(request, "subnetIds", "subnets", Subnet.class));
    }

    public DescribeVpcsResult describeVpcs() {
        return describeVpcs(null);
    }

    public DescribeVpcsResult describeVpcs(DescribeVpcsRequest request) {
        return new DescribeVpcsResult().withVpcs(describe(request, "vpcIds", "vpcs", Vpc.class));
    }

    public DescribeVpcClassicLinkResult describeVpcClassicLink() {
        return describeVpcClassicLink(null);
    }

    public DescribeVpcClassicLinkResult describeVpcClassicLink(DescribeVpcClassicLinkRequest request) {
        return new DescribeVpcClassicLinkResult()
                .withVpcs(describe(request, "vpcIds", "vpcClassicLinks", VpcClassicLink.class));
    }

    public DescribeClassicLinkInstancesResult describeClassicLinkInstances() {
        return describeClassicLinkInstances(null);
    }

    public DescribeClassicLinkInstancesResult describeClassicLinkInstances(
            DescribeClassicLinkInstancesRequest request) {
        return new DescribeClassicLinkInstancesResult()
                .withInstances(describe(request, "instanceIds", "classicLinkInstances", ClassicLinkInstance.class));
    }

    ////////////////////////////////////
    //
    // AmazonElasticLoadBalancing
    //
    ////////////////////////////////////
    public DescribeLoadBalancersResult describeLoadBalancers() {
        return describeLoadBalancers(null);
    }

    public DescribeLoadBalancersResult describeLoadBalancers(DescribeLoadBalancersRequest request) {
        return new DescribeLoadBalancersResult().withLoadBalancerDescriptions(
                describe(request, "loadBalancerNames", "loadBalancers", LoadBalancerDescription.class));
    }

    ////////////////////////////////////
    private <T> List<T> describe(AmazonWebServiceRequest request, String idKey, final String object,
            final Class<T> singleType) {
        lastModified.set(null);
        try {
            JavaType singleMeta = objectMapper.getTypeFactory().constructParametrizedType(Metadata.class,
                    Metadata.class, singleType);
            Collection<String> ids = getRequestIds(request, idKey);
            Long mtime = null;
            List<T> results = new ArrayList<>();
            if (ids.isEmpty()) {
                final byte[] json = getJson(object, null);
                JavaType listMeta = objectMapper.getTypeFactory().constructParametrizedType(List.class, List.class,
                        singleMeta);
                List<Metadata<T>> metadataResults = objectMapper.readValue(json, listMeta);
                for (Metadata<T> meta : metadataResults) {
                    mtime = mtime == null ? meta.mtime : Math.min(mtime, meta.mtime);
                    results.add(meta.data);
                }
            } else {
                for (String id : ids) {
                    final byte[] json = getJson(object, id);
                    Metadata<T> result = objectMapper.readValue(json, singleMeta);
                    mtime = mtime == null ? result.mtime : Math.min(mtime, result.mtime);
                    results.add(result.data);
                }
            }
            lastModified.set(mtime);
            return results;
        } catch (Exception e) {
            AmazonServiceException ex = new AmazonServiceException(
                    "400 Bad Request -- Edda could not find one of the managed objects requested.", e);
            ex.setStatusCode(400);
            ex.setServiceName(serviceName);
            ex.setErrorType(AmazonServiceException.ErrorType.Unknown);
            throw ex;
        }
    }

    private static Collection<String> getRequestIds(AmazonWebServiceRequest request, String idFieldName) {
        if (request == null) {
            return Collections.emptySet();
        }
        try {
            Field field = request.getClass().getDeclaredField(idFieldName);
            field.setAccessible(true);
            Collection<String> collection = (Collection<String>) field.get(request);
            return collection == null ? Collections.emptySet() : collection;
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private byte[] getJson(String objectName, String key) throws IOException {
        final String url = edda + "/REST/v2/aws/" + objectName + (key == null ? ";_expand" : "/" + key) + ";_meta";
        HttpGet get = new HttpGet(url);
        get.setConfig(RequestConfig.custom().setConnectTimeout(eddaTimeoutConfig.getConnectTimeout())
                .setSocketTimeout(eddaTimeoutConfig.getSocketTimeout()).build());

        long retryDelay = eddaTimeoutConfig.getRetryBase();
        int retryAttempts = 0;
        String lastException = "";
        Random r = new Random();
        Exception ex;
        while (retryAttempts < eddaTimeoutConfig.getMaxAttempts()) {
            ex = null;
            HttpEntity entity = null;
            try {
                HttpResponse response = httpClient.execute(get);
                entity = response.getEntity();
                if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                    lastException = response.getProtocolVersion().toString() + " "
                            + response.getStatusLine().getStatusCode() + " "
                            + response.getStatusLine().getReasonPhrase();
                } else {
                    return getBytesFromInputStream(entity.getContent(), entity.getContentLength());
                }
            } catch (IOException ioe) {
                lastException = ioe.getClass().getSimpleName() + ": " + ioe.getMessage();
                ex = ioe;
            } finally {
                EntityUtils.consume(entity);
            }
            final String exceptionFormat = "Edda request {} failed with {}";
            if (ex == null) {
                log.warn(exceptionFormat, url, lastException);
            } else {
                log.warn(exceptionFormat, url, lastException, ex);
            }
            try {
                Thread.sleep(retryDelay);
            } catch (InterruptedException inter) {
                break;
            }
            retryAttempts++;
            retryDelay += r.nextInt(eddaTimeoutConfig.getBackoffMillis());
        }
        throw new IOException(lastException);
    }

    private static byte[] getBytesFromInputStream(InputStream is, long contentLength) throws IOException {
        final int bufLen = contentLength < 0 || contentLength > Integer.MAX_VALUE ? 128 * 1024
                : (int) contentLength;
        final ByteArrayOutputStream baos = new ByteArrayOutputStream(bufLen);
        final byte[] buf = new byte[16 * 1024];
        int bytesRead;
        while ((bytesRead = is.read(buf)) != -1) {
            baos.write(buf, 0, bytesRead);
        }
        is.close();
        return baos.toByteArray();
    }

    private static class Metadata<T> {
        final Long mtime;
        final T data;

        @JsonCreator
        public Metadata(@JsonProperty("mtime") Long mtime, @JsonProperty("data") T data) {
            this.mtime = mtime;
            this.data = data;
        }
    }

}