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

com.vmware.photon.controller.model.adapters.awsadapter.enumeration.AWSComputeDescriptionEnumerationAdapterService Maven / Gradle / Ivy

The newest version!
/*
 * 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.enumeration;

import static com.vmware.photon.controller.model.adapters.awsadapter.util.AWSEnumerationUtils.getCDsRepresentingVMsInLocalSystemCreatedByEnumerationQuery;
import static com.vmware.photon.controller.model.adapters.awsadapter.util.AWSEnumerationUtils.getKeyForComputeDescriptionFromCD;
import static com.vmware.photon.controller.model.adapters.awsadapter.util.AWSEnumerationUtils.getRepresentativeListOfCDsFromInstanceList;
import static com.vmware.photon.controller.model.constants.PhotonModelConstants.SOURCE_TASK_LINK;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import com.amazonaws.services.ec2.model.Instance;

import org.apache.commons.lang3.StringUtils;

import com.vmware.photon.controller.model.adapters.awsadapter.AWSCostStatsService;
import com.vmware.photon.controller.model.adapters.awsadapter.AWSUriPaths;
import com.vmware.photon.controller.model.adapters.awsadapter.util.AWSEnumerationUtils.InstanceDescKey;
import com.vmware.photon.controller.model.adapters.awsadapter.util.AWSEnumerationUtils.ZoneData;
import com.vmware.photon.controller.model.adapters.util.AdapterUriUtil;
import com.vmware.photon.controller.model.query.QueryUtils;
import com.vmware.photon.controller.model.resources.ComputeDescriptionService;
import com.vmware.photon.controller.model.resources.ComputeDescriptionService.ComputeDescription;
import com.vmware.photon.controller.model.tasks.ResourceEnumerationTaskService;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.OperationJoin;
import com.vmware.xenon.common.ServiceStateCollectionUpdateRequest;
import com.vmware.xenon.common.StatelessService;
import com.vmware.xenon.common.Utils;
import com.vmware.xenon.services.common.QueryTask;

/**
 * Stateless service for the creation of compute descriptions that are discovered during the enumeration phase.
 * It first represents all the instances in a representative set of compute descriptions. Further checks if these
 * compute descriptions exist in the system. If they don't exist in the system then creates them in the local document store.
 */
public class AWSComputeDescriptionEnumerationAdapterService extends StatelessService {

    public static final String SELF_LINK = AWSUriPaths.AWS_COMPUTE_DESCRIPTION_CREATION_ADAPTER;

    public enum AWSComputeDescCreationStage {
        GET_REPRESENTATIVE_LIST,
        QUERY_LOCAL_COMPUTE_DESCRIPTIONS,
        COMPARE,
        POPULATE_CREATE_COMPUTEDESC,
        POPULATE_UPDATE_COMPUTEDESC,
        CREATE_OR_UPDATE_COMPUTEDESC,
        SIGNAL_COMPLETION
    }

    public AWSComputeDescriptionEnumerationAdapterService() {
        super.toggleOption(ServiceOption.INSTRUMENTATION, true);
    }

    /**
     * Holds the list of instances for which compute descriptions have to be created.
     */
    public static class AWSComputeDescriptionCreationState {
        public List instancesToBeCreated;
        public List instancesToBeUpdated;
        public String regionId;
        public URI parentTaskLink;
        public String authCredentiaslLink;
        public boolean isMock;
        public List tenantLinks;
        public Map zones;
        public ComputeDescription parentDescription;
        public String endpointLink;
        public String parentComputeLink;
    }

    /**
     * The local service context that is created to identify and create a representative set of compute descriptions
     * that are required to be created in the system based on the enumeration data received from AWS.
     */
    public static class AWSComputeDescriptionCreationServiceContext {
        public List enumerationOperations;
        public Map localComputeDescriptionMap;
        public Set representativeComputeDescriptionSet;
        public List computeDescriptionsToBeCreatedList;
        public List computeDescriptionsToBeUpdatedList;
        public AWSComputeDescCreationStage creationStage;
        public AWSComputeDescriptionCreationState cdState;
        // Cached operation to signal completion to the AWS instance adapter once all the compute
        // descriptions are successfully created.
        public Operation operation;

        public AWSComputeDescriptionCreationServiceContext(AWSComputeDescriptionCreationState cdState,
                Operation op) {
            this.cdState = cdState;
            this.localComputeDescriptionMap = new HashMap<>();
            this.representativeComputeDescriptionSet = new HashSet<>();
            this.computeDescriptionsToBeCreatedList = new ArrayList<>();
            this.computeDescriptionsToBeUpdatedList = new ArrayList<>();
            this.enumerationOperations = new ArrayList<>();
            this.creationStage = AWSComputeDescCreationStage.GET_REPRESENTATIVE_LIST;
            this.operation = op;
        }
    }

    @Override
    public void handlePatch(Operation op) {
        setOperationHandlerInvokeTimeStat(op);
        if (!op.hasBody()) {
            op.fail(new IllegalArgumentException("body is required"));
            return;
        }
        AWSComputeDescriptionCreationState cdState = op.getBody(AWSComputeDescriptionCreationState.class);
        AWSComputeDescriptionCreationServiceContext context = new AWSComputeDescriptionCreationServiceContext(
                cdState, op);
        if (cdState.isMock) {
            op.complete();
        }
        handleComputeDescriptionCreation(context);
    }

    /**
     *
     */
    private void handleComputeDescriptionCreation(AWSComputeDescriptionCreationServiceContext context) {
        switch (context.creationStage) {
        case GET_REPRESENTATIVE_LIST:
            getRepresentativeListOfComputeDescriptions(context,
                    AWSComputeDescCreationStage.QUERY_LOCAL_COMPUTE_DESCRIPTIONS);
            break;
        case QUERY_LOCAL_COMPUTE_DESCRIPTIONS:
            if (context.representativeComputeDescriptionSet.size() > 0) {
                getLocalComputeDescriptions(context, AWSComputeDescCreationStage.COMPARE);
            } else {
                context.creationStage = AWSComputeDescCreationStage.SIGNAL_COMPLETION;
                handleComputeDescriptionCreation(context);
            }
            break;
        case COMPARE:
            compareLocalStateWithEnumerationData(context,
                    AWSComputeDescCreationStage.POPULATE_CREATE_COMPUTEDESC);
            break;
        case POPULATE_CREATE_COMPUTEDESC:
            populateCreateOperations(context,
                        AWSComputeDescCreationStage.POPULATE_UPDATE_COMPUTEDESC);
            break;
        case POPULATE_UPDATE_COMPUTEDESC:
            populateUpdateOperations(context,
                        AWSComputeDescCreationStage.CREATE_OR_UPDATE_COMPUTEDESC);
            break;
        case CREATE_OR_UPDATE_COMPUTEDESC:
            createOrUpdateComputeDescriptions(context, AWSComputeDescCreationStage.SIGNAL_COMPLETION);
            break;
        case SIGNAL_COMPLETION:
            setOperationDurationStat(context.operation);
            context.operation.complete();
            break;
        default:
            Throwable t = new IllegalArgumentException(
                    "Unknown AWS enumeration:compute description creation stage");
            finishWithFailure(context, t);
        }
    }

    /**
     * From the list of instances that are received from AWS arrive at the minimal set of compute descriptions that need
     * to be created locally to represent them.
     *
     * This logic basically tries to map n number of discovered instances to m compute descriptions.
     * The attributes of the instance considered to map to compute descriptions are the statically known template type
     * attributes that are expected to be fixed for the life of a virtual machine.
     *
     * These include
     * 1) Region Id
     * 2) Instance Type
     * 3) Network Id.
     *
     * Once the instances are mapped to these limited set of compute descriptions. Checks are performed to see if such compute descriptions
     * exist in the system. Else they are created.
     *
     * @param context
     * @param next
     */
    private void getRepresentativeListOfComputeDescriptions(
            AWSComputeDescriptionCreationServiceContext context, AWSComputeDescCreationStage next) {
        Collection instanceList = new ArrayList<>();
        instanceList.addAll(context.cdState.instancesToBeCreated);
        instanceList.addAll(context.cdState.instancesToBeUpdated);
        context.representativeComputeDescriptionSet = getRepresentativeListOfCDsFromInstanceList(
                instanceList, context.cdState.zones);
        context.creationStage = next;
        handleComputeDescriptionCreation(context);
    }

    /**
     * Get all the compute descriptions already in the system and filtered by
     * - Supported Children (Docker Container)
     * - Environment name(AWS),
     * - Name (instance type),
     * - ZoneId(placement).
     * - Endpoint link
     */
    private void getLocalComputeDescriptions(AWSComputeDescriptionCreationServiceContext context,
            AWSComputeDescCreationStage next) {
        QueryTask queryTask = getCDsRepresentingVMsInLocalSystemCreatedByEnumerationQuery(
                context.representativeComputeDescriptionSet, context.cdState.tenantLinks,
                context.cdState.regionId, context.cdState.parentComputeLink, context.cdState.endpointLink);

        // create the query to find an existing compute description
        QueryUtils.startInventoryQueryTask(this, queryTask)
                .whenComplete((qrt, e) -> {
                    if (e != null) {
                        logWarning(() -> String.format("Failure retrieving query results: %s",
                                e.toString()));
                        finishWithFailure(context, e);
                        return;
                    }

                    if (qrt != null && qrt.results.documentCount > 0) {
                        for (Object s : qrt.results.documents.values()) {
                            ComputeDescription localComputeDescription = Utils.fromJson(s,
                                    ComputeDescription.class);
                            context.localComputeDescriptionMap.put(
                                    getKeyForComputeDescriptionFromCD(localComputeDescription),
                                    localComputeDescription);
                        }
                        logFine(() -> String.format("%d compute descriptions found",
                                context.localComputeDescriptionMap.size()));

                    } else {
                        logFine(() -> "No compute descriptions found");
                    }
                    context.creationStage = next;
                    handleComputeDescriptionCreation(context);
                });
    }

    /**
     *
    *Compares the locally known compute descriptions with the new list of compute descriptions to be created.
    *Identifies only the ones that do not exist locally and need to be created.
     *
    * @param context The compute description service context to be used for the creation of the compute descriptions.
    * @param next The next stage in the workflow for the compute description creation.
     */
    private void compareLocalStateWithEnumerationData(AWSComputeDescriptionCreationServiceContext context,
            AWSComputeDescCreationStage next) {
        if (context.representativeComputeDescriptionSet == null
                || context.representativeComputeDescriptionSet.size() == 0) {
            logFine(() -> "No new computes discovered on the remote system");
        } else if (context.localComputeDescriptionMap == null
                || context.localComputeDescriptionMap.size() == 0) {
            logFine(() -> "No local compute descriptions found");

            context.representativeComputeDescriptionSet
                    .forEach(cd -> context.computeDescriptionsToBeCreatedList.add(cd));
        } else { // compare and add the ones that do not exist locally
            context.representativeComputeDescriptionSet.stream()
                    .filter(d -> !context.localComputeDescriptionMap.containsKey(d))
                    .forEach(d -> context.computeDescriptionsToBeCreatedList.add(d));

            logFine(() -> String.format("%d additional compute descriptions need to be created",
                    context.computeDescriptionsToBeCreatedList.size()));

            context.representativeComputeDescriptionSet.stream()
                    .filter(d -> context.localComputeDescriptionMap.containsKey(d))
                    .forEach(d -> context.computeDescriptionsToBeUpdatedList.add(d));

            logFine(() -> String.format("%d additional compute descriptions need to be updated",
                    context.computeDescriptionsToBeUpdatedList.size()));

        }
        context.creationStage = next;
        handleComputeDescriptionCreation(context);
    }

    /**
     * Method to create compute descriptions associated with the instances received from the AWS host.
     */
    private void populateCreateOperations(AWSComputeDescriptionCreationServiceContext context,
            AWSComputeDescCreationStage next) {
        if (context.computeDescriptionsToBeCreatedList == null
                || context.computeDescriptionsToBeCreatedList.isEmpty()) {
            logFine(() -> "No local compute descriptions need to be created");
            context.creationStage = next;
            handleComputeDescriptionCreation(context);
            return;
        }
        logFine(() -> String.format("Need to create %d local compute descriptions",
                context.computeDescriptionsToBeCreatedList.size()));
        context.computeDescriptionsToBeCreatedList.stream()
                .map(dk -> createComputeDescriptionOperation(dk, context.cdState))
                .forEach(o -> context.enumerationOperations.add(o));

        context.creationStage = next;
        handleComputeDescriptionCreation(context);
    }

    private void populateUpdateOperations(AWSComputeDescriptionCreationServiceContext context,
                                          AWSComputeDescCreationStage next) {
        if (context.computeDescriptionsToBeUpdatedList == null
                || context.computeDescriptionsToBeUpdatedList.isEmpty()) {
            logFine(() -> "No local compute descriptions need to be updated");
            context.creationStage = next;
            handleComputeDescriptionCreation(context);
            return;
        }
        logFine(() -> String.format("Need to update %d local compute descriptions",
                context.computeDescriptionsToBeUpdatedList.size()));

        context.computeDescriptionsToBeUpdatedList.stream().forEach(entry -> {
            ComputeDescription cd = context.localComputeDescriptionMap.get(entry);
            if (StringUtils.isEmpty(cd.endpointLink)) {
                cd.endpointLink = context.cdState.endpointLink;
                sendRequest(Operation.createPatch(this.getHost(), cd.documentSelfLink)
                        .setReferer(this.getHost().getUri())
                        .setBody(cd));
            }
        });

        context.computeDescriptionsToBeUpdatedList.stream()
                .map(dk -> updateComputeDescriptionOperation(dk, context))
                .forEach(o -> context.enumerationOperations.add(o));

        context.creationStage = next;
        handleComputeDescriptionCreation(context);
    }

    private Operation updateComputeDescriptionOperation(InstanceDescKey cd,
                                                        AWSComputeDescriptionCreationServiceContext context) {

        Map> collectionsToAddMap = Collections.singletonMap
                (ComputeDescription.FIELD_NAME_ENDPOINT_LINKS,
                        Collections.singletonList(context.cdState.endpointLink));

        ServiceStateCollectionUpdateRequest updateEndpointLinksRequest = ServiceStateCollectionUpdateRequest
                .create(collectionsToAddMap, null);

        return Operation.createPatch(this.getHost(), context.localComputeDescriptionMap.get(cd).documentSelfLink)
                .setReferer(this.getHost().getUri())
                .setBody(updateEndpointLinksRequest);
    }

    /**
     * Creates a compute description based on the VM instance information received from AWS. Futher creates an operation
     * that will post to the compute description service for the creation of the compute description.
     */
    private Operation createComputeDescriptionOperation(InstanceDescKey cd,
            AWSComputeDescriptionCreationState cdState) {
        // Create a compute description for the AWS instance at hand
        ComputeDescriptionService.ComputeDescription computeDescription = new ComputeDescriptionService.ComputeDescription();
        computeDescription.instanceAdapterReference = AdapterUriUtil.buildAdapterUri(getHost(),
                AWSUriPaths.AWS_INSTANCE_ADAPTER);
        computeDescription.enumerationAdapterReference = AdapterUriUtil.buildAdapterUri(getHost(),
                AWSUriPaths.AWS_ENUMERATION_CREATION_ADAPTER);
        computeDescription.statsAdapterReference = AdapterUriUtil.buildAdapterUri(getHost(),
                AWSUriPaths.AWS_STATS_ADAPTER);
        computeDescription.diskAdapterReference = AdapterUriUtil.buildAdapterUri(getHost(),
                AWSUriPaths.AWS_DISK_ADAPTER);
        // We don't want cost adapter to run for each instance. Remove it from the list of stats adapter.
        if (cdState.parentDescription.statsAdapterReferences != null) {
            computeDescription.statsAdapterReferences = cdState.parentDescription.statsAdapterReferences
                    .stream().filter(uri -> !uri.getPath().endsWith(AWSCostStatsService.SELF_LINK))
                    .collect(Collectors.toSet());
        }

        computeDescription.environmentName = ComputeDescription.ENVIRONMENT_NAME_AWS;
        computeDescription.zoneId = cd.zoneId;
        computeDescription.regionId = cd.regionId;
        computeDescription.id = cd.instanceType;
        computeDescription.instanceType = cd.instanceType;
        computeDescription.name = cd.instanceType;
        computeDescription.endpointLink = cdState.endpointLink;
        if (computeDescription.endpointLinks == null) {
            computeDescription.endpointLinks = new HashSet();
        }
        computeDescription.endpointLinks.add(cdState.endpointLink);
        computeDescription.tenantLinks = cdState.tenantLinks;
        // Book keeping information about the creation of the compute description in the system.
        computeDescription.customProperties = new HashMap<>();
        computeDescription.customProperties.put(SOURCE_TASK_LINK,
                ResourceEnumerationTaskService.FACTORY_LINK);
        computeDescription.computeHostLink = cdState.parentComputeLink;

        // security group is not being returned currently in the VM. Add additional logic VSYM-326.

        return Operation.createPost(this, ComputeDescriptionService.FACTORY_LINK)
                .setBody(computeDescription)
                .setReferer(getHost().getUri());
    }

    /**
     * Kicks off the creation of all the identified compute descriptions and creates a join handler to handle the successful
     * completion of of those operations. Once all the compute descriptions are successfully created moves the state machine
     * to the next stage.
     */
    private void createOrUpdateComputeDescriptions(AWSComputeDescriptionCreationServiceContext context,
            AWSComputeDescCreationStage next) {
        if (context.enumerationOperations == null || context.enumerationOperations.size() == 0) {
            logFine(() -> "No compute descriptions to be created");
            context.creationStage = next;
            handleComputeDescriptionCreation(context);
            return;
        }
        OperationJoin.JoinedCompletionHandler joinCompletion = (ox,
                exc) -> {
            if (exc != null) {
                logSevere(() -> String.format("Failure creating compute descriptions: %s",
                        Utils.toString(exc)));
                finishWithFailure(context, exc.values().iterator().next());
                return;
            }
            logFine(() -> "Successfully created compute descriptions");
            context.creationStage = next;
            handleComputeDescriptionCreation(context);
        };
        OperationJoin joinOp = OperationJoin.create(context.enumerationOperations);
        joinOp.setCompletion(joinCompletion);
        joinOp.sendWith(getHost());
    }

    private void finishWithFailure(AWSComputeDescriptionCreationServiceContext context, Throwable exc) {
        context.operation.fail(exc);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy