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

Java tutorial

Introduction

Here is the source code for com.vmware.photon.controller.deployer.xenon.task.UploadVibTaskService.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.PatchUtils;
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.scheduler.RateLimitedWorkQueueService;
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.Immutable;
import com.vmware.photon.controller.common.xenon.validation.NotNull;
import com.vmware.photon.controller.common.xenon.validation.WriteOnce;
import com.vmware.photon.controller.deployer.deployengine.ScriptRunner;
import com.vmware.photon.controller.deployer.xenon.DeployerContext;
import com.vmware.photon.controller.deployer.xenon.entity.VibService;
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.UriUtils;
import com.vmware.xenon.common.Utils;

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 static com.google.common.base.Preconditions.checkState;

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * This class implements a Xenon service which performs the task of uploading a VIB image to a
 * host.
 */
public class UploadVibTaskService extends StatefulService {

    /**
     * This string specifies the script which is used to install a VIB.
     */
    public static final String UPLOAD_VIB_SCRIPT_NAME = "esx-upload-vib";

    /**
     * This class defines the state of a {@link UploadVibTaskService} task.
     */
    public static class TaskState extends com.vmware.xenon.common.TaskState {

        /**
         * This type defines the possible sub-stages of a {@link UploadVibTaskService} in the
         * {@link TaskState.TaskStage#STARTED} stage.
         */
        public enum SubStage {
            BEGIN_EXECUTION, UPLOAD_VIB,
        }

        /**
         * This value represents the sub-stage of the current task.
         */
        public SubStage subStage;
    }

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

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

        /**
         * This value represents the {@link ControlFlags} for the current task.
         */
        @DefaultInteger(value = 0)
        @Immutable
        public Integer taskControlFlags;

        /**
         * This value represents the optional document self-link of the parent task service to be
         * notified on completion.
         */
        @Immutable
        public String parentTaskServiceLink;

        /**
         * This value represents the optional patch body, serialized to JSON, to be sent to the parent
         * task service on successful completion.
         */
        @Immutable
        public String parentTaskPatchBody;

        /**
         * This value represents the start time, in microseconds since the Unix epoch, of the current
         * task (e.g. when the task entered the {@link TaskState.TaskStage#STARTED} state).
         */
        @WriteOnce
        public Long taskStartTimeMicros;

        /**
         * This value represents the timeout interval, in microseconds, of the current task (e.g. the
         * interval after which the task should enter the {@link TaskState.TaskStage#FAILED} state).
         * <p>
         * N.B. An initial value of 0 indicates that no timeout should be enforced.
         */
        @WriteOnce
        public Long taskTimeoutMicros;

        /**
         * This value represents the document self-link of the {@link RateLimitedWorkQueueService}
         * which will schedule instances of the current task.
         */
        @NotNull
        @Immutable
        public String workQueueServiceLink;

        /**
         * This value represents the document self-link of the {@link VibService} representing the VIB
         * file to be uploaded.
         */
        @NotNull
        @Immutable
        public String vibServiceLink;
    }

    public UploadVibTaskService() {
        super(State.class);
        super.toggleOption(ServiceOption.PERSISTENCE, true);
    }

    @Override
    public void handleStart(Operation startOp) {
        ServiceUtils.logTrace(this, "Handling start operation");
        if (!startOp.hasBody()) {
            startOp.fail(new IllegalArgumentException("Body is required"));
            return;
        }

        State initialState = startOp.getBody(State.class);
        InitializationUtils.initialize(initialState);

        if (initialState.taskTimeoutMicros == null) {
            initialState.taskTimeoutMicros = TimeUnit.MINUTES.toMicros(10);
        }

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

        try {
            validateInitialState(initialState);
        } catch (Throwable t) {
            ServiceUtils.failOperationAsBadRequest(this, startOp, t);
            return;
        }

        if (initialState.taskTimeoutMicros != 0) {
            super.toggleOption(ServiceOption.PERIODIC_MAINTENANCE, true);
            super.setMaintenanceIntervalMicros(initialState.taskTimeoutMicros);
        }

        startOp.setBody(initialState).complete();

        try {
            if (ControlFlags.isOperationProcessingDisabled(initialState.taskControlFlags)) {
                ServiceUtils.logInfo(this, "Skipping start operation processing (disabled)");
            } else {
                notifyWorkQueueService(initialState);
            }
        } catch (Throwable t) {
            failTask(t);
        }
    }

    private void validateInitialState(State initialState) {
        ValidationUtils.validateState(initialState);
        ValidationUtils.validateTaskStage(initialState.taskState, initialState.taskState.subStage);
    }

    private void notifyWorkQueueService(State initialState) {
        RateLimitedWorkQueueService.PatchState patchState = new RateLimitedWorkQueueService.PatchState();
        patchState.pendingTaskServiceDelta = 1;
        patchState.taskServiceLink = getSelfLink();
        sendRequest(Operation.createPatch(this, initialState.workQueueServiceLink).setBody(patchState));
    }

    @Override
    public void handlePatch(Operation patchOp) {
        ServiceUtils.logTrace(this, "Handling patch operation");
        if (!patchOp.hasBody()) {
            patchOp.fail(new IllegalArgumentException("Body is required"));
            return;
        }

        State currentState = getState(patchOp);
        State patchState = patchOp.getBody(State.class);

        try {
            applyPatch(currentState, patchState);
        } catch (Throwable t) {
            ServiceUtils.failOperationAsBadRequest(this, patchOp, t);
            return;
        }

        patchOp.complete();

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

    private void applyPatch(State currentState, State patchState) {

        ValidationUtils.validatePatch(currentState, patchState);
        ValidationUtils.validateTaskStage(patchState.taskState, patchState.taskState.subStage);
        ValidationUtils.validateTaskStageProgression(currentState.taskState, currentState.taskState.subStage,
                patchState.taskState, patchState.taskState.subStage);

        PatchUtils.patchState(currentState, patchState);

        if (currentState.taskState.stage == TaskState.TaskStage.STARTED) {

            //
            // Set the task start time if it has not already been set.
            //

            if (currentState.taskStartTimeMicros == null) {
                currentState.taskStartTimeMicros = Utils.getNowMicrosUtc();
            }

            //
            // Transition to the UPLOAD_VIB sub-stage if appropriate.
            //

            if (currentState.taskState.subStage == TaskState.SubStage.BEGIN_EXECUTION) {
                currentState.taskState.subStage = TaskState.SubStage.UPLOAD_VIB;
            }
        }

        ValidationUtils.validateState(currentState);
    }

    private void processStartedStage(State currentState) {
        switch (currentState.taskState.subStage) {
        case BEGIN_EXECUTION:
            throw new IllegalStateException("Unexpected task sub-stage " + currentState.taskState.subStage);
        case UPLOAD_VIB:
            processUploadVibSubStage(currentState);
            break;
        }
    }

    private void processTerminalStage(State currentState) {

        //
        // Since the task has reached a terminal state, its state no longer needs to be monitored for
        // timeout enforcement. Disable periodic maintenance.
        //

        super.toggleOption(ServiceOption.PERIODIC_MAINTENANCE, false);

        //
        // Notify the work queue service that the current task has ceased execution.
        //

        RateLimitedWorkQueueService.PatchState workQueuePatchState = new RateLimitedWorkQueueService.PatchState();
        workQueuePatchState.runningTaskServiceDelta = -1;

        sendRequest(Operation.createPatch(this, currentState.workQueueServiceLink).setBody(workQueuePatchState)
                .setCompletion((o, e) -> {
                    if (e != null) {
                        ServiceUtils.logSevere(this, "Failed to notify work queue service: " + e);
                    } else {
                        TaskUtils.notifyParentTask(this, currentState.taskState, currentState.parentTaskServiceLink,
                                currentState.parentTaskPatchBody);
                    }
                }));
    }

    @Override
    public void handlePeriodicMaintenance(Operation maintenanceOp) {
        ServiceUtils.logInfo(this, "Handling maintenance operation");
        maintenanceOp.complete();

        sendRequest(Operation.createGet(this, getSelfLink()).setCompletion((o, e) -> {
            if (e != null) {
                ServiceUtils.logWarning(this, "Failed to get state during maintenance: " + e);
            } else {
                handlePeriodicMaintenance(o.getBody(State.class));
            }
        }));
    }

    private void handlePeriodicMaintenance(State currentState) {

        checkState(currentState.taskTimeoutMicros != 0);

        boolean failTask = (currentState.taskState.stage == TaskState.TaskStage.STARTED
                && currentState.taskStartTimeMicros != null
                && currentState.taskStartTimeMicros < Utils.getNowMicrosUtc() + currentState.taskTimeoutMicros);

        if (failTask) {
            State patchState = buildPatch(TaskState.TaskStage.FAILED, null, new TimeoutException());
            patchState.taskState.failure.statusCode = Operation.STATUS_CODE_TIMEOUT;
            TaskUtils.sendSelfPatch(this, patchState);
        }
    }

    //
    // UPLOAD_VIB sub-stage routines
    //

    private void processUploadVibSubStage(State currentState) {

        sendRequest(Operation.createGet(this, currentState.vibServiceLink).setCompletion((o, e) -> {
            try {
                if (e != null) {
                    failTask(e);
                } else {
                    processUploadVibSubStage(o.getBody(VibService.State.class));
                }
            } catch (Throwable t) {
                failTask(t);
            }
        }));
    }

    private void processUploadVibSubStage(VibService.State vibState) {

        sendRequest(
                HostUtils.getCloudStoreHelper(this).createGet(vibState.hostServiceLink).setCompletion((o, e) -> {
                    try {
                        if (e != null) {
                            failTask(e);
                        } else {
                            processUploadVibSubStage(vibState, o.getBody(HostService.State.class));
                        }
                    } catch (Throwable t) {
                        failTask(t);
                    }
                }));
    }

    private void processUploadVibSubStage(VibService.State vibState, HostService.State hostState) {

        File sourceDirectory = new File(HostUtils.getDeployerContext(this).getVibDirectory());
        if (!sourceDirectory.exists() || !sourceDirectory.isDirectory()) {
            throw new IllegalStateException("Invalid VIB source directory " + sourceDirectory);
        }

        File sourceFile = new File(sourceDirectory, vibState.vibName);
        if (!sourceFile.exists() || !sourceFile.isFile()) {
            throw new IllegalStateException("Invalid VIB source file " + sourceFile);
        }

        String uploadPath = UriUtils.buildUriPath("tmp", "photon-controller-vibs",
                ServiceUtils.getIDFromDocumentSelfLink(vibState.documentSelfLink), sourceFile.getName());

        ServiceUtils.logInfo(this, "Uploading %s vib with following arguments : %s %s %s %s", vibState.vibName,
                hostState.hostAddress, hostState.userName, sourceFile.getAbsolutePath(), uploadPath);

        List<String> command = Arrays.asList("./" + UPLOAD_VIB_SCRIPT_NAME, hostState.hostAddress,
                hostState.userName, hostState.password, sourceFile.getAbsolutePath(), uploadPath);

        DeployerContext deployerContext = HostUtils.getDeployerContext(this);

        File scriptLogFile = new File(deployerContext.getScriptLogDirectory(),
                UPLOAD_VIB_SCRIPT_NAME + "-" + hostState.hostAddress + "-"
                        + ServiceUtils.getIDFromDocumentSelfLink(vibState.documentSelfLink) + ".log");

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

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

        Futures.addCallback(futureTask, new FutureCallback<Integer>() {
            @Override
            public void onSuccess(@javax.validation.constraints.NotNull Integer result) {
                try {
                    if (result != 0) {
                        logFailureAndFail(vibState, hostState, result, scriptLogFile);
                    } else {
                        setUploadPath(vibState.documentSelfLink, uploadPath);
                    }
                } catch (Throwable t) {
                    failTask(t);
                }
            }

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

    private void logFailureAndFail(VibService.State vibState, HostService.State hostState, int result,
            File scriptLogFile) throws Throwable {

        ServiceUtils.logSevere(this, UPLOAD_VIB_SCRIPT_NAME + " returned " + result);
        ServiceUtils.logSevere(this, "Script output: " + FileUtils.readFileToString(scriptLogFile));
        throw new IllegalStateException("Uploading VIB file " + vibState.vibName + " to host "
                + hostState.hostAddress + " failed with exit code " + result);
    }

    private void setUploadPath(String vibServiceLink, String uploadPath) {

        VibService.State patchState = new VibService.State();
        patchState.uploadPath = uploadPath;

        sendRequest(Operation.createPatch(this, vibServiceLink).setBody(patchState).setCompletion((o, e) -> {
            try {
                if (e != null) {
                    failTask(e);
                } else {
                    sendStageProgressPatch(TaskState.TaskStage.FINISHED);
                }
            } catch (Throwable t) {
                failTask(t);
            }
        }));
    }

    //
    // Utility routines
    //

    private void sendStageProgressPatch(TaskState.TaskStage taskStage) {
        ServiceUtils.logTrace(this, "Sending self-patch to stage " + taskStage);
        TaskUtils.sendSelfPatch(this, buildPatch(taskStage, null, null));
    }

    private void failTask(Throwable failure) {
        ServiceUtils.logSevere(this, failure);
        TaskUtils.sendSelfPatch(this, buildPatch(TaskState.TaskStage.FAILED, null, failure));
    }

    public static State buildPatch(TaskState.TaskStage taskStage, TaskState.SubStage subStage) {
        return buildPatch(taskStage, subStage, null);
    }

    private static State buildPatch(TaskState.TaskStage taskStage, TaskState.SubStage subStage, Throwable failure) {
        State patchState = new State();
        patchState.taskState = new TaskState();
        patchState.taskState.stage = taskStage;
        patchState.taskState.subStage = subStage;

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

        return patchState;
    }
}