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

com.vmware.photon.controller.model.adapters.awsadapter.enumeration.AWSEnumerationAndDeletionAdapterService 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.AWSConstants.AWS_INSTANCE_ID_PREFIX;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.AWS_INVALID_INSTANCE_ID_ERROR_CODE;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants.getQueryResultLimit;
import static com.vmware.photon.controller.model.adapters.awsadapter.AWSUtils.getAWSNonTerminatedInstancesFilter;
import static com.vmware.photon.controller.model.adapters.util.AdapterUtils.getDeletionState;
import static com.vmware.photon.controller.model.util.PhotonModelUriUtils.createInventoryUri;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListMap;

import com.amazonaws.handlers.AsyncHandler;
import com.amazonaws.services.ec2.AmazonEC2AsyncClient;
import com.amazonaws.services.ec2.model.AmazonEC2Exception;
import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.amazonaws.services.ec2.model.Filter;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.Reservation;

import com.vmware.photon.controller.model.adapterapi.EnumerationAction;
import com.vmware.photon.controller.model.adapters.awsadapter.AWSConstants;
import com.vmware.photon.controller.model.adapters.awsadapter.AWSUriPaths;
import com.vmware.photon.controller.model.adapters.awsadapter.AWSUtils;
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.ComputeEnumerateAdapterRequest;
import com.vmware.photon.controller.model.query.QueryUtils;
import com.vmware.photon.controller.model.resources.ComputeService.ComputeState;
import com.vmware.photon.controller.model.resources.ComputeService.ComputeStateWithDescription;
import com.vmware.photon.controller.model.resources.ComputeService.LifecycleState;
import com.vmware.photon.controller.model.resources.ComputeService.PowerState;
import com.vmware.photon.controller.model.resources.DiskService.DiskState;
import com.vmware.photon.controller.model.resources.NetworkInterfaceService.NetworkInterfaceState;
import com.vmware.photon.controller.model.resources.ResourceState;
import com.vmware.photon.controller.model.resources.util.PhotonModelUtils;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.OperationContext;
import com.vmware.xenon.common.OperationJoin;
import com.vmware.xenon.common.StatelessService;
import com.vmware.xenon.common.Utils;
import com.vmware.xenon.services.common.AuthCredentialsService;
import com.vmware.xenon.services.common.QueryTask;
import com.vmware.xenon.services.common.QueryTask.Query;
import com.vmware.xenon.services.common.QueryTask.Query.Occurance;
import com.vmware.xenon.services.common.QueryTask.QuerySpecification.QueryOption;

/**
 * Enumeration Adapter for the Amazon Web Services. Performs a list call to the AWS API and
 * reconciles the local state with the state on the remote system. It starts by looking at the local
 * state in the system. Queries the remote endpoint to check if the same instances exist there. In
 * case some items are found to be deleted on the remote endpoint then it goes ahead and deletes
 * them from the local system.
 *
 */
public class AWSEnumerationAndDeletionAdapterService extends StatelessService {
    public static final String SELF_LINK = AWSUriPaths.AWS_ENUMERATION_DELETION_ADAPTER;
    private AWSClientManager clientManager;

    public static enum AWSEnumerationDeletionStages {
        CLIENT,
        VALIDATE_CLIENT,
        ENUMERATE,
        ERROR
    }

    public static enum AWSEnumerationDeletionSubStage {
        GET_LOCAL_RESOURCES,
        GET_REMOTE_RESOURCES,
        COMPARE,
        PROCESS_COMPUTE_STATES,
        GET_NEXT_PAGE,
        ENUMERATION_STOP
    }

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

    /**
     * The enumeration service context that holds all the information needed to determine the list
     * of instances that need to be deleted from the system as they have been terminated from the
     * remote instance.
     */
    public static class EnumerationDeletionContext {
        public AmazonEC2AsyncClient amazonEC2Client;
        public ComputeEnumerateAdapterRequest request;
        public AuthCredentialsService.AuthCredentialsServiceState endpointAuth;
        public ComputeStateWithDescription parentCompute;
        public AWSEnumerationDeletionStages stage;
        public AWSEnumerationDeletionSubStage subStage;
        public Throwable error;
        // Mapping of instance Id and the compute state that represents it in the local system.
        public Map localInstanceIds;
        // Set of all the instance Ids of the non terminated instances on AWS
        public Set remoteInstanceIds;
        // Map of Instance Ids and compute states that have to be deleted from the local system.
        public List instancesToBeDeleted;
        public Operation operation;
        // The next page link for the next set of results to fetch from the local system.
        public String nextPageLink;
        public int pageNo = 0;
        public ResourceState resourceDeletionState;

        public EnumerationDeletionContext(ComputeEnumerateAdapterRequest request,
                Operation op) {
            this.request = request;
            this.operation = op;
            this.endpointAuth = request.endpointAuth;
            this.parentCompute = request.parentCompute;
            this.localInstanceIds = new ConcurrentSkipListMap<>();
            this.remoteInstanceIds = new HashSet<>();
            this.instancesToBeDeleted = new ArrayList<>();
            this.stage = AWSEnumerationDeletionStages.CLIENT;
            this.subStage = AWSEnumerationDeletionSubStage.GET_LOCAL_RESOURCES;
            this.resourceDeletionState = getDeletionState(request.original
                    .deletedResourceExpirationMicros);
        }
    }

    /**
     * Extend default 'start' logic with loading AWS client.
     */
    @Override
    public void handleStart(Operation op) {

        this.clientManager = AWSClientManagerFactory
                .getClientManager(AWSConstants.AwsClientType.EC2);

        super.handleStart(op);
    }

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

    @Override
    public void handlePatch(Operation op) {
        setOperationHandlerInvokeTimeStat(op);
        if (!op.hasBody()) {
            op.fail(new IllegalArgumentException("body is required"));
            return;
        }
        EnumerationDeletionContext awsEnumerationContext = new EnumerationDeletionContext(
                op.getBody(ComputeEnumerateAdapterRequest.class), op);
        handleEnumerationRequestForDeletion(awsEnumerationContext);
    }

    /**
     * Handles the different steps required to process the local resources , get the corresponding
     * resources from the remote endpoint and delete the instances from the local system that do not
     * exist on the remote system any longer.
     */
    private void handleEnumerationRequestForDeletion(EnumerationDeletionContext aws) {
        switch (aws.stage) {
        case CLIENT:
            getAWSAsyncClient(aws, AWSEnumerationDeletionStages.VALIDATE_CLIENT);
            break;
        case VALIDATE_CLIENT:
            validateClient(aws, AWSEnumerationDeletionStages.ENUMERATE);
            break;
        case ENUMERATE:
            switch (aws.request.original.enumerationAction) {
            case START:
                logInfo(() -> String.format("Started deletion enumeration for %s",
                        aws.request.original.resourceReference));
                aws.request.original.enumerationAction = EnumerationAction.REFRESH;
                handleEnumerationRequestForDeletion(aws);
                break;
            case REFRESH:
                logInfo(() -> String.format("Running deletion enumeration in refresh mode for %s",
                        aws.parentCompute.description.environmentName));
                deleteResourcesInLocalSystem(aws);
                break;
            case STOP:
                logInfo(() -> String.format("Stopping deletion enumeration for %s",
                        aws.request.original.resourceReference));
                setOperationDurationStat(aws.operation);
                aws.operation.complete();
                break;
            default:
                logSevere(() -> String.format("Unknown AWS enumeration action %s",
                        aws.request.original.enumerationAction.toString()));
                Throwable t = new Exception("Unknown AWS enumeration action");
                signalErrorToEnumerationAdapter(aws, t);
                break;
            }
            break;
        case ERROR:
            aws.operation.fail(aws.error);
            break;
        default:
            logSevere(() -> String.format("Unknown AWS enumeration stage %s", aws.stage.toString()));
            Throwable t = new Exception("Unknown AWS enumeration stage");
            signalErrorToEnumerationAdapter(aws, t);
            break;
        }
    }

    /**
     * Method to instantiate the AWS Async client for future use
     */
    private void getAWSAsyncClient(EnumerationDeletionContext aws,
            AWSEnumerationDeletionStages next) {
        this.clientManager.getOrCreateEC2ClientAsync(aws.endpointAuth, aws.request.regionId, this)
                .whenComplete((ec2Client, t) -> {
                    if (t != null) {
                        aws.error = t;
                        aws.stage = AWSEnumerationDeletionStages.ERROR;
                        handleEnumerationRequestForDeletion(aws);
                        return;
                    }

                    aws.amazonEC2Client = ec2Client;
                    aws.stage = next;
                    handleEnumerationRequestForDeletion(aws);
                });
    }

    /**
     * Method to validate an EC2 Client
     */
    private void validateClient(EnumerationDeletionContext aws,
            AWSEnumerationDeletionStages next) {
        OperationContext opContext = OperationContext.getOperationContext();

        AWSUtils.validateCredentials(aws.amazonEC2Client, this.clientManager, aws.endpointAuth,
                aws.request, this,
                (describeAvailabilityZonesResult) -> {
                    OperationContext.restoreOperationContext(opContext);
                    aws.stage = next;
                    handleEnumerationRequestForDeletion(aws);
                },
                t -> {
                    OperationContext.restoreOperationContext(opContext);
                    aws.error = t;
                    aws.stage = AWSEnumerationDeletionStages.ERROR;
                    handleEnumerationRequestForDeletion(aws);
                },
                // onUnaccessible: the region is not accessible so complete the enum Op
                () -> {
                    OperationContext.restoreOperationContext(opContext);
                    aws.operation.complete();
                });
    }

    /**
     * Uses the received enumeration information and compares it against it the state of the local
     * system and then tries to find and fix the gaps. At a high level this is the sequence of steps
     * that is followed: 1) Create a query to get the list of local compute states 2) Compare the
     * list of local resources against the list received from the AWS endpoint. 3) In case some
     * instances have been terminated on the AWS endpoint, mark those instances for deletion in the
     * local system. 4) Delete the compute state and network associated with that AWS instance from
     * the local system that have been terminated on AWS.
     *
     * @param aws
     */
    private void deleteResourcesInLocalSystem(EnumerationDeletionContext aws) {
        switch (aws.subStage) {
        case GET_LOCAL_RESOURCES:
            getLocalResources(aws,
                    AWSEnumerationDeletionSubStage.GET_REMOTE_RESOURCES);
            break;
        case GET_REMOTE_RESOURCES:
            getRemoteInstances(aws, AWSEnumerationDeletionSubStage.COMPARE);
            break;
        case COMPARE:
            compareResources(aws, AWSEnumerationDeletionSubStage.PROCESS_COMPUTE_STATES);
            break;
        case PROCESS_COMPUTE_STATES:
            if (aws.nextPageLink == null) {
                aws.subStage = AWSEnumerationDeletionSubStage.ENUMERATION_STOP;
            } else {
                aws.subStage = AWSEnumerationDeletionSubStage.GET_NEXT_PAGE;
            }

            if (aws.instancesToBeDeleted == null || aws.instancesToBeDeleted.size() == 0) {
                logFine(() -> "No local compute states found.");
                deleteResourcesInLocalSystem(aws);
                return;
            } else {
                if (aws.request.original.preserveMissing) {
                    retireComputeStates(aws);
                } else {
                    disassociateComputeStates(aws);
                }
            }
            break;
        case GET_NEXT_PAGE:
            getNextPageFromLocalSystem(aws, AWSEnumerationDeletionSubStage.GET_REMOTE_RESOURCES);
            break;
        case ENUMERATION_STOP:
            logInfo(() -> "Stopping enumeration");
            stopEnumeration(aws);
            break;
        default:
            Throwable t = new Exception("Unknown AWS enumeration deletion sub stage");
            signalErrorToEnumerationAdapter(aws, t);
        }
    }

    /**
     * Get the list of compute states already known to the local system. Filter them by parent
     * compute link : AWS.
     */
    public void getLocalResources(EnumerationDeletionContext context,
            AWSEnumerationDeletionSubStage next) {
        // query all ComputeState resources known to the local system.
        logFine(() -> "Getting local resources that need to be reconciled with the AWS endpoint.");
        Query.Builder qBuilder = Query.Builder.create()
                .addKindFieldClause(ComputeState.class)
                .addFieldClause(ComputeState.FIELD_NAME_PARENT_LINK,
                        context.request.original.resourceLink())
                .addInClause(ComputeState.FIELD_NAME_LIFECYCLE_STATE,
                        Arrays.asList(LifecycleState.PROVISIONING.toString(),
                                LifecycleState.RETIRED.toString()),
                        Occurance.MUST_NOT_OCCUR);

        addScopeCriteria(qBuilder, context);

        QueryTask queryTask = QueryTask.Builder.createDirectTask()
                .setQuery(qBuilder.build())
                .addOption(QueryOption.EXPAND_CONTENT)
                .setResultLimit(getQueryResultLimit())
                .build();
        queryTask.tenantLinks = context.parentCompute.tenantLinks;

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

                    populateLocalInstanceInformationFromQueryResults(context, qrt);
                    logFine(() -> String.format("Got page No. %d of local resources. %d instances"
                                    + " found.", context.pageNo, qrt.results.documentCount));
                    context.subStage = next;
                    deleteResourcesInLocalSystem(context);
                });
    }

    /**
     * Populates the local instance information from the query results.
     */
    public QueryTask populateLocalInstanceInformationFromQueryResults(
            EnumerationDeletionContext context, QueryTask queryTask) {
        for (Object s : queryTask.results.documents.values()) {
            ComputeState localInstance = Utils.fromJson(s, ComputeState.class);
            if (!localInstance.id.startsWith(AWS_INSTANCE_ID_PREFIX)) {
                continue;
            }
            context.localInstanceIds.put(localInstance.id, localInstance);
        }
        context.pageNo++;
        context.nextPageLink = queryTask.results.nextPageLink;
        logFine(() -> String.format("Next page link %s", context.nextPageLink));
        return queryTask;
    }

    /**
     * Get the instances from AWS filtered by the instances Ids known to the local system.
     */
    public void getRemoteInstances(EnumerationDeletionContext aws,
            AWSEnumerationDeletionSubStage next) {
        if (aws.localInstanceIds == null || aws.localInstanceIds.size() == 0) {
            logFine(() -> "No local records found. No states need to be fetched from the AWS"
                    + " endpoint.");
            aws.subStage = next;
            deleteResourcesInLocalSystem(aws);
            return;
        }
        DescribeInstancesRequest request = new DescribeInstancesRequest();
        Filter runningInstanceFilter = getAWSNonTerminatedInstancesFilter();
        request.getFilters().add(runningInstanceFilter);
        // Get only the instances from the remote system for which a compute state exists in the
        // local system.
        logFine(() -> String.format("Fetching instance details for %d instances on the AWS"
                        + " endpoint.", aws.localInstanceIds.keySet().size()));
        request.getInstanceIds().addAll(new ArrayList<>(aws.localInstanceIds.keySet()));
        AsyncHandler resultHandler =
                new AWSEnumerationAsyncHandler(this, aws, next);
        aws.amazonEC2Client.describeInstancesAsync(request,
                resultHandler);
    }

    /**
     * The async handler to handle the success and errors received after invoking the describe
     * instances API on AWS
     */
    public static class AWSEnumerationAsyncHandler implements
            AsyncHandler {

        private StatelessService service;
        private EnumerationDeletionContext aws;
        public AWSEnumerationDeletionSubStage next;
        private OperationContext opContext;

        private AWSEnumerationAsyncHandler(StatelessService service,
                EnumerationDeletionContext aws, AWSEnumerationDeletionSubStage next) {
            this.service = service;
            this.aws = aws;
            this.next = next;
            this.opContext = OperationContext.getOperationContext();
        }

        @Override
        public void onError(Exception exception) {
            OperationContext.restoreOperationContext(this.opContext);
            if (exception instanceof AmazonEC2Exception) {
                // This is to handle enumeration after a credential update. AWS will return 400
                // InvalidInstanceID.NotFound if credentials for an endpoint get updated to a
                // different IAM user or account. According to AWS some other account's instance IDs
                // are not valid for another account, which is why they throw this error.
                if (((AmazonEC2Exception) exception).getErrorCode()
                        .equals(AWS_INVALID_INSTANCE_ID_ERROR_CODE)) {
                    this.service.logInfo("IAM user or account changed for endpoint: %s. "
                                    + "Removing all invalid computes for this endpoint.",
                            this.aws.request.original.endpointLink);
                    this.aws.subStage = this.next;
                    ((AWSEnumerationAndDeletionAdapterService) this.service)
                            .deleteResourcesInLocalSystem(this.aws);
                    return;
                }
            }
            this.service.logSevere(exception);
            this.aws.operation.fail(exception);
        }

        @Override
        public void onSuccess(DescribeInstancesRequest request,
                DescribeInstancesResult result) {
            OperationContext.restoreOperationContext(this.opContext);
            int totalNumberOfInstances = 0;
            // Print the details of the instances discovered on the AWS endpoint
            for (Reservation r : result.getReservations()) {
                for (Instance i : r.getInstances()) {
                    ++totalNumberOfInstances;
                    final int finalTotal1 = totalNumberOfInstances;
                    this.service.logFine(() -> String.format("%d=====Instance details %s =====",
                            finalTotal1, i.getInstanceId()));
                    this.aws.remoteInstanceIds.add(i.getInstanceId());
                }
            }
            final int finalTotal2 = totalNumberOfInstances;
            this.service.logFine(() -> String.format("Successfully enumerated %d instances on the"
                            + " AWS host.", finalTotal2));
            this.aws.subStage = this.next;
            ((AWSEnumerationAndDeletionAdapterService) this.service)
                    .deleteResourcesInLocalSystem(this.aws);
            return;
        }
    }

    /**
     * Compares the state between what is known to the local system and what is retrieved from AWS.
     * If some instances are terminated on the AWS endpoint then they are marked for deletion on the
     * local system.
     */
    public void compareResources(EnumerationDeletionContext aws,
            AWSEnumerationDeletionSubStage next) {
        // No local resources
        if (aws.localInstanceIds == null || aws.localInstanceIds.size() == 0) {
            logFine(() -> "No local resources found. Nothing to delete.");
            // No remote instances
        } else if (aws.remoteInstanceIds == null || aws.remoteInstanceIds.size() == 0) {
            logFine(() -> "No resources discovered on the cloud. Delete stale local resources.");
            aws.instancesToBeDeleted.addAll(aws.localInstanceIds.values());
            logFine(() -> String.format("====Deleting compute state for instance Ids %s ====",
                    aws.localInstanceIds.keySet().toString()));
        } else { // compare and mark the instances for deletion that have been terminated from the
                 // AWS endpoint.
            for (String key : aws.localInstanceIds.keySet()) {
                if (!aws.remoteInstanceIds.contains(key)) {
                    aws.instancesToBeDeleted.add(aws.localInstanceIds.get(key));
                    logFine(() -> String.format("====Deleting compute state for instance Id %s ====",
                            key));
                }
            }
            logFine(() -> String.format("%d local instances to be deleted as they were terminated on the AWS endpoint.",
                    aws.instancesToBeDeleted.size()));
        }
        aws.subStage = next;
        deleteResourcesInLocalSystem(aws);
        return;
    }

    /**
     * Creates operations for the deletion of all the compute states and networks from the local
     * system for which the AWS instance has been terminated from the remote instance. Kicks off the
     * disassociation of all the identified compute states, also the networks and disks for which the
     * actual AWS instance has been terminated.
     */
    private void disassociateComputeStates(EnumerationDeletionContext context) {

        // List updateOperations = new ArrayList<>();
        // Create delete operations for the compute states that have to be deleted from the system.
        for (ComputeState computeStateToDelete : context.instancesToBeDeleted) {
            Operation csUpdateOp = PhotonModelUtils.createRemoveEndpointLinksOperation(
                    this, context.request.original.endpointLink, computeStateToDelete);
            if (csUpdateOp != null) {
                csUpdateOp.sendWith(getHost());
            }
            // Create update operations for all the network links associated with each of the
            // compute states.
            if (computeStateToDelete.networkInterfaceLinks != null) {
                for (String networkLinkToDelete : computeStateToDelete.networkInterfaceLinks) {

                    sendRequest(Operation.createGet(this.getHost(), networkLinkToDelete)
                            .setCompletion((o, e) -> {
                                if (e != null) {
                                    logWarning(() -> String.format("Failure retrieving networkInterfaceState: %s, " +
                                            "reason: %s", networkLinkToDelete, e.toString()));
                                    return;
                                }
                                NetworkInterfaceState networkInterfaceState =
                                        o.getBody(NetworkInterfaceState.class);
                                Operation nsUpdateOp = PhotonModelUtils
                                        .createRemoveEndpointLinksOperation(
                                                this,
                                                context.request.original.endpointLink,
                                                networkInterfaceState);
                                if (nsUpdateOp != null) {
                                    nsUpdateOp.sendWith(getHost());
                                }
                            }));
                }
            }

            // Create update operations for all the disk links associated with each of the
            // compute states.
            if (computeStateToDelete.diskLinks != null) {
                for (String diskLinkToDelete : computeStateToDelete.diskLinks) {
                    sendRequest(Operation.createGet(this.getHost(), diskLinkToDelete)
                            .setCompletion((o, e) -> {
                                if (e != null) {
                                    logWarning(() -> String.format("Failure retrieving diskState: %s, " +
                                            "reason: %s", diskLinkToDelete, e.toString()));
                                    return;
                                }
                                DiskState diskState = o.getBody(DiskState.class);
                                Operation dsUpdateOp = PhotonModelUtils
                                        .createRemoveEndpointLinksOperation(
                                                this,
                                                context.request.original.endpointLink,
                                                diskState);
                                if (dsUpdateOp != null) {
                                    dsUpdateOp.sendWith(getHost());
                                }
                            }));
                }
            }
        }

        deleteResourcesInLocalSystem(context);
    }

    /**
     * Creates operations to retire all the compute states in the local system for which the AWS
     * instance has been terminated/missing from the remote instance.
     */
    private void retireComputeStates(EnumerationDeletionContext context) {

        List operations = new ArrayList<>();
        // Create patch operations for the compute states that have to be retired in the system.
        // We don't modify the lifecycle state for contained objects like NetworkInterfaces and
        // Disks.
        for (ComputeState cs : context.instancesToBeDeleted) {
            ComputeState cps = new ComputeState();
            cps.powerState = PowerState.OFF;
            cps.lifecycleState = LifecycleState.RETIRED;
            Operation operation = Operation
                    .createPatch(this.getHost(), cs.documentSelfLink)
                    .setBody(cps)
                    .setReferer(getHost().getUri());
            operations.add(operation);
        }
        // Kick off patch operations with a join handler.
        if (operations == null || operations.size() == 0) {
            logFine(() -> "No local compute states to be deleted.");
            deleteResourcesInLocalSystem(context);
            return;
        }
        OperationJoin.JoinedCompletionHandler joinCompletion = (ox,
                exc) -> {
            if (exc != null) {
                logSevere(() -> String.format("Failure retiring local compute states: %s ",
                        Utils.toString(exc)));
                deleteResourcesInLocalSystem(context);
                return;

            }
            logFine(() -> "Successfully retired local compute states.");
            deleteResourcesInLocalSystem(context);
            return;
        };
        OperationJoin joinOp = OperationJoin.create(operations);
        joinOp.setCompletion(joinCompletion);
        joinOp.sendWith(getHost());
    }

    /**
     * Signals Enumeration Stop to the AWS enumeration adapter. The AWS enumeration adapter will in
     * turn patch the parent task to indicate completion.
     */
    public void stopEnumeration(EnumerationDeletionContext aws) {
        aws.request.original.enumerationAction = EnumerationAction.STOP;
        handleEnumerationRequestForDeletion(aws);
    }

    /**
     * Signals error to the AWS enumeration adapter. The adapter will in turn clean up resources and
     * signal error to the parent task.
     */
    public void signalErrorToEnumerationAdapter(EnumerationDeletionContext aws, Throwable t) {
        aws.error = t;
        aws.stage = AWSEnumerationDeletionStages.ERROR;
        handleEnumerationRequestForDeletion(aws);
    }

    /**
     * Gets the next page from the local system for which the state has to be reconciled after
     * comparison with the remote AWS endpoint.
     */
    private void getNextPageFromLocalSystem(EnumerationDeletionContext context,
            AWSEnumerationDeletionSubStage next) {
        context.localInstanceIds.clear();
        context.remoteInstanceIds.clear();
        context.instancesToBeDeleted.clear();
        logFine(() -> "Getting next page of local records.");
        sendRequest(Operation
                .createGet(createInventoryUri(getHost(), context.nextPageLink))
                .setCompletion((o, e) -> {
                    if (e != null) {
                        logSevere(() -> String.format("Failure retrieving next page from the local"
                                        + " system: %s", e.toString()));
                        signalErrorToEnumerationAdapter(context, e);
                        return;
                    }
                    QueryTask responseTask = populateLocalInstanceInformationFromQueryResults(context,
                            o.getBody(QueryTask.class));
                    logFine(() -> String.format("Got page No. %d of local resources. %d instances"
                                    + " in this page.", context.pageNo,
                            responseTask.results.documentCount));
                    context.subStage = next;
                    deleteResourcesInLocalSystem(context);
                }));
    }

    /**
     * Constrain every query with region and tenantLinks, if presented.
     */
    private static void addScopeCriteria(
            Query.Builder qBuilder,
            EnumerationDeletionContext ctx) {

        // Add REGION criteria
        qBuilder.addFieldClause(ResourceState.FIELD_NAME_REGION_ID, ctx.request.regionId);
        // Add parentComputeHost criteria
        qBuilder.addFieldClause(ResourceState.FIELD_NAME_COMPUTE_HOST_LINK, ctx.request.parentCompute.documentSelfLink);
        // Add TENANT_LINKS criteria
        QueryUtils.addTenantLinks(qBuilder, ctx.parentCompute.tenantLinks);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy