com.vmware.photon.controller.deployer.xenon.task.DeleteAgentTaskService.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.photon.controller.deployer.xenon.task.DeleteAgentTaskService.java

Source

/*
 * Copyright 2015 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.deployer.xenon.task;

import com.vmware.photon.controller.cloudstore.xenon.entity.HostService;
import com.vmware.photon.controller.common.xenon.ControlFlags;
import com.vmware.photon.controller.common.xenon.InitializationUtils;
import com.vmware.photon.controller.common.xenon.ServiceUtils;
import com.vmware.photon.controller.common.xenon.TaskUtils;
import com.vmware.photon.controller.common.xenon.ValidationUtils;
import com.vmware.photon.controller.common.xenon.deployment.NoMigrationDuringDeployment;
import com.vmware.photon.controller.common.xenon.migration.NoMigrationDuringUpgrade;
import com.vmware.photon.controller.common.xenon.validation.DefaultInteger;
import com.vmware.photon.controller.common.xenon.validation.DefaultTaskState;
import com.vmware.photon.controller.common.xenon.validation.DefaultUuid;
import com.vmware.photon.controller.common.xenon.validation.Immutable;
import com.vmware.photon.controller.common.xenon.validation.NotNull;
import com.vmware.photon.controller.deployer.deployengine.ScriptRunner;
import com.vmware.photon.controller.deployer.xenon.DeployerContext;
import com.vmware.photon.controller.deployer.xenon.util.HostUtils;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.ServiceDocument;
import com.vmware.xenon.common.StatefulService;
import com.vmware.xenon.common.TaskState;
import com.vmware.xenon.common.Utils;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFutureTask;
import org.apache.commons.io.FileUtils;

import javax.annotation.Nullable;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * This class implements a Xenon micro-service which performs the task of
 * deleting a Photon Controller agent instance from an ESX hypervisor.
 */
public class DeleteAgentTaskService extends StatefulService {

    @VisibleForTesting
    public static final String SCRIPT_NAME = "esx-delete-agent2";

    /**
     * This class defines the document state associated with a single
     * {@link DeleteAgentTaskService} instance.
     */
    @NoMigrationDuringUpgrade
    @NoMigrationDuringDeployment
    public static class State extends ServiceDocument {

        /**
         * This value represents the URL of the {@link HostService} object which represents the host to be provisioned.
         */
        @NotNull
        @Immutable
        public String hostServiceLink;

        /**
         * This value represents the state of the current task.
         */
        @DefaultTaskState(value = TaskState.TaskStage.CREATED)
        public TaskState taskState;

        /**
         * This value represents the control flags for the current task.
         */
        @DefaultInteger(value = 0)
        @Immutable
        public Integer controlFlags;

        /**
         * This value represents the unique ID of the current task.
         */
        @DefaultUuid
        @Immutable
        public String uniqueID;
    }

    public DeleteAgentTaskService() {
        super(State.class);
        super.toggleOption(ServiceOption.OWNER_SELECTION, true);
        super.toggleOption(ServiceOption.PERSISTENCE, true);
        super.toggleOption(ServiceOption.REPLICATION, true);
    }

    /**
     * This method is called when a start operation is performed for the current
     * service instance.
     *
     * @param start
     */
    @Override
    public void handleStart(Operation start) {
        ServiceUtils.logInfo(this, "Starting service %s", getSelfLink());
        State startState = start.getBody(State.class);
        InitializationUtils.initialize(startState);
        validateState(startState);

        if (TaskState.TaskStage.CREATED == startState.taskState.stage) {
            startState.taskState.stage = TaskState.TaskStage.STARTED;
        }

        if (startState.documentExpirationTimeMicros <= 0) {
            startState.documentExpirationTimeMicros = ServiceUtils
                    .computeExpirationTime(ServiceUtils.DEFAULT_DOC_EXPIRATION_TIME_MICROS);
        }

        start.setBody(startState).complete();

        try {
            if (ControlFlags.isOperationProcessingDisabled(startState.controlFlags)) {
                ServiceUtils.logInfo(this, "Skipping start operation processing (disabled)");
            } else if (TaskState.TaskStage.STARTED == startState.taskState.stage) {
                sendStageProgressPatch(startState.taskState.stage);
            }
        } catch (Throwable t) {
            failTask(t);
        }
    }

    /**
     * This method is called when a patch operation is performed on the current
     * service instance.
     *
     * @param patch
     */
    @Override
    public void handlePatch(Operation patch) {
        ServiceUtils.logInfo(this, "Handling patch for service %s", getSelfLink());
        State startState = getState(patch);
        State patchState = patch.getBody(State.class);
        validatePatchState(startState, patchState);
        State currentState = applyPatch(startState, patchState);
        validateState(currentState);
        patch.complete();

        try {
            if (ControlFlags.isOperationProcessingDisabled(currentState.controlFlags)) {
                ServiceUtils.logInfo(this, "Skipping patch operation processing (disabled)");
            } else if (TaskState.TaskStage.STARTED == currentState.taskState.stage) {
                processStartedState(currentState);
            }
        } catch (Throwable t) {
            failTask(t);
        }
    }

    /**
     * This method validates the state of a service document for internal
     * consistency.
     *
     * @param currentState
     */
    private void validateState(State currentState) {
        ValidationUtils.validateState(currentState);
        ValidationUtils.validateTaskStage(currentState.taskState);
    }

    /**
     * This method validates a patch against a valid service document.
     *
     * @param startState
     * @param patchState
     */
    private void validatePatchState(State startState, State patchState) {
        ValidationUtils.validatePatch(startState, patchState);
        ValidationUtils.validateTaskStage(patchState.taskState);
        ValidationUtils.validateTaskStageProgression(startState.taskState, patchState.taskState);
    }

    /**
     * This method applies a patch to a service document and returns the updated
     * document state.
     * <p>
     * N.B. It is not safe to access the startState object after this call, since
     * the object is modified and returned as the new document state.
     *
     * @param startState
     * @param patchState
     * @return
     */
    private State applyPatch(State startState, State patchState) {
        if (patchState.taskState.stage != startState.taskState.stage) {
            ServiceUtils.logInfo(this, "Moving to stage %s", patchState.taskState.stage);
            startState.taskState = patchState.taskState;
        }

        return startState;
    }

    /**
     * This method performs the processing required of the service document owner
     * in response to a patch in the STARTED state.
     *
     * @param currentState
     */
    private void processStartedState(final State currentState) {

        sendRequest(HostUtils.getCloudStoreHelper(this).createGet(currentState.hostServiceLink)
                .setCompletion((completedOp, failure) -> {
                    if (null != failure) {
                        failTask(failure);
                        return;
                    }

                    try {
                        HostService.State hostState = completedOp.getBody(HostService.State.class);
                        processStartedState(currentState, hostState);
                    } catch (Throwable t) {
                        failTask(t);
                    }
                }));
    }

    /**
     * This method creates a script runner object and submits it to the executor
     * service for the Xenon host. On successful completion, the service is
     * transitioned to the FINISHED state.
     *
     * @param currentState
     * @param hostState
     */
    private void processStartedState(final State currentState, final HostService.State hostState) {
        DeployerContext deployerContext = HostUtils.getDeployerContext(this);
        List<String> command = new ArrayList<>();
        command.add("./" + SCRIPT_NAME);
        command.add(hostState.hostAddress);
        command.add(hostState.userName);
        command.add(hostState.password);
        command.add("photon-controller-agent");

        File scriptLogFile = new File(deployerContext.getScriptLogDirectory(),
                SCRIPT_NAME + "-" + currentState.uniqueID + ".log");

        ScriptRunner scriptRunner = new ScriptRunner.Builder(command, deployerContext.getScriptTimeoutSec())
                .directory(deployerContext.getScriptDirectory())
                .redirectOutput(ProcessBuilder.Redirect.to(scriptLogFile)).build();

        ListenableFutureTask<Integer> futureTask = ListenableFutureTask.create(scriptRunner);
        HostUtils.getListeningExecutorService(this).submit(futureTask);

        FutureCallback<Integer> futureCallback = new FutureCallback<Integer>() {
            @Override
            public void onSuccess(@Nullable Integer result) {
                if (null == result) {
                    failTask(new NullPointerException(SCRIPT_NAME + " returned null"));
                } else if (0 != result) {
                    logScriptErrorAndFail(hostState, result, scriptLogFile);
                } else {
                    sendStageProgressPatch(TaskState.TaskStage.FINISHED);
                }
            }

            @Override
            public void onFailure(Throwable t) {
                failTask(t);
            }
        };

        Futures.addCallback(futureTask, futureCallback);
    }

    private void logScriptErrorAndFail(HostService.State hostState, Integer result, File scriptLogFile) {

        try {
            ServiceUtils.logSevere(this, SCRIPT_NAME + " returned " + result.toString());
            ServiceUtils.logSevere(this, "Script output: " + FileUtils.readFileToString(scriptLogFile));
        } catch (Throwable t) {
            ServiceUtils.logSevere(this, t);
        }

        failTask(new IllegalStateException("Deleting the agent on host " + hostState.hostAddress
                + " failed with exit code " + result.toString()));
    }

    /**
     * This method sends a patch operation to the current service instance to
     * move to a new state.
     *
     * @param stage
     */
    private void sendStageProgressPatch(TaskState.TaskStage stage) {
        ServiceUtils.logInfo(this, "Sending self-patch to stage %s", stage);
        TaskUtils.sendSelfPatch(this, buildPatch(stage, null));
    }

    /**
     * This method sends a patch operation to the current service instance to
     * move to the FAILED state in response to the specified exception.
     *
     * @param e
     */
    private void failTask(Throwable e) {
        ServiceUtils.logSevere(this, e);
        TaskUtils.sendSelfPatch(this, buildPatch(TaskState.TaskStage.FAILED, e));
    }

    /**
     * This method builds a patch state object which can be used to submit a
     * self-patch.
     *
     * @param stage
     * @param t
     * @return
     */
    @VisibleForTesting
    protected State buildPatch(TaskState.TaskStage stage, @Nullable Throwable t) {
        State state = new State();
        state.taskState = new TaskState();
        state.taskState.stage = stage;

        if (null != t) {
            state.taskState.failure = Utils.toServiceErrorResponse(t);
        }

        return state;
    }
}