com.vmware.photon.controller.model.tasks.ProvisionComputeTaskService.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.photon.controller.model.tasks.ProvisionComputeTaskService.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.model.tasks;

import com.vmware.photon.controller.model.adapterapi.ComputeBootRequest;
import com.vmware.photon.controller.model.adapterapi.ComputeInstanceRequest;
import com.vmware.photon.controller.model.adapterapi.ComputeInstanceRequest.InstanceRequestType;
import com.vmware.photon.controller.model.resources.ComputeDescriptionService.ComputeDescription;
import com.vmware.photon.controller.model.resources.ComputeService;
import com.vmware.photon.controller.model.resources.ComputeService.BootDevice;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.Operation.CompletionHandler;
import com.vmware.xenon.common.ServiceDocument;
import com.vmware.xenon.common.ServiceDocumentDescription;
import com.vmware.xenon.common.StatefulService;
import com.vmware.xenon.common.TaskState;
import com.vmware.xenon.common.TaskState.TaskStage;
import com.vmware.xenon.common.UriUtils;
import com.vmware.xenon.common.Utils;

import org.apache.commons.validator.routines.InetAddressValidator;

import java.net.URI;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Task implementing the provision compute resource work flow. Utilizes sub tasks and services
 * provided in the compute host description to perform various sub stages.
 * <p>
 * This is service is not replicated or partitioned since its created by a higher level task, which
 * is partitioned. So this task executes in isolation, per node. It does however talk to replicated
 * DCP services, so it sets the operation.setTargetReplicated(true) to guard against services not
 * yet available on the current node.
 */
public class ProvisionComputeTaskService extends StatefulService {

    /**
     * Represent state of a provision task.
     */
    public static class ProvisionComputeTaskState extends ServiceDocument {

        public static final long DEFAULT_EXPIRATION_MICROS = TimeUnit.HOURS.toMicros(1);
        public static final String FIELD_NAME_PARENT_TASK_LINK = "parentTaskLink";

        public TaskState taskInfo = new TaskState();

        /**
         * SubStage.
         */
        public enum SubStage {
            CREATING_HOST, BOOTING_FROM_NETWORK, BOOTING_FROM_CDROM, BOOTING_FROM_ANY, VALIDATE_COMPUTE_HOST, DONE, FAILED
        }

        /**
         * Task SubStage.
         */
        public ProvisionComputeTaskState.SubStage taskSubStage;

        /**
         * URI reference to compute instance.
         */
        public String computeLink;

        /**
         * Optional, set by the task if not specified by the client, by querying the compute host.
         */
        public URI instanceAdapterReference;

        /**
         * Optional, set by the task if not specified by the client, by querying the compute host.
         */
        public URI powerAdapterReference;

        /**
         * Optional, set by the task if not specified by the client, by querying the compute host.
         */
        public URI bootAdapterReference;

        /**
         * Link that initiated this task.
         */
        public String parentTaskLink;

        /**
         * Value indicating whether the service should treat this as a mock request and complete the
         * work flow without involving the underlying compute host infrastructure.
         */
        public boolean isMockRequest;

        /**
         * A list of tenant links which can access this task.
         */
        public List<String> tenantLinks;
    }

    public ProvisionComputeTaskService() {
        super(ProvisionComputeTaskState.class);
        super.toggleOption(ServiceOption.INSTRUMENTATION, true);
    }

    @Override
    public void handleStart(Operation startPost) {
        try {
            ProvisionComputeTaskState state = startPost.getBody(ProvisionComputeTaskState.class);
            validateState(state);

            // we need to retrieve and cache the power and boot services we need to actuate
            URI computeHost = buildComputeHostUri(state);
            sendRequest(Operation.createGet(computeHost).setTargetReplicated(true).setCompletion((o, e) -> {
                validateComputeHostAndStart(startPost, o, e, state);
            }));
        } catch (Throwable e) {
            logSevere(e);
            startPost.fail(e);
        }
    }

    private void validateComputeHostAndStart(Operation startPost, Operation get, Throwable e,
            ProvisionComputeTaskState state) {
        if (e != null) {
            logWarning("Failure retrieving host state (%s): %s", get.getUri(), e.toString());
            startPost.complete();
            failTask(e);
            return;
        }

        ComputeService.ComputeStateWithDescription hostState = get
                .getBody(ComputeService.ComputeStateWithDescription.class);
        if (hostState.description == null) {
            hostState.description = new ComputeDescription();
        }

        if (hostState.description.cpuCount <= 0) {
            hostState.description.cpuCount = 1;
        }

        if (hostState.description.totalMemoryBytes <= 0) {
            hostState.description.totalMemoryBytes = Integer.MAX_VALUE / 4L;
        }

        state.bootAdapterReference = hostState.description.bootAdapterReference;
        state.powerAdapterReference = hostState.description.powerAdapterReference;
        state.instanceAdapterReference = hostState.description.instanceAdapterReference;

        // we can complete start operation now, it will index and cache the
        // update state
        startPost.complete();

        if (state.bootAdapterReference == null ^ state.powerAdapterReference == null) {
            failTask(new IllegalArgumentException("computeHost power and boot need to both be set or empty"));
            return;
        }

        if (state.taskSubStage == ProvisionComputeTaskState.SubStage.CREATING_HOST
                && state.instanceAdapterReference == null) {
            failTask(new IllegalArgumentException("computeHost does not have create service specified"));
            return;
        }

        // now we are ready to start our self-running state machine
        sendSelfPatch(TaskStage.STARTED, state.taskSubStage, null);
    }

    private void sendSelfPatch(TaskStage newStage, ProvisionComputeTaskState.SubStage newSubStage, Throwable ex) {
        ProvisionComputeTaskState patchBody = new ProvisionComputeTaskState();
        patchBody.taskInfo = new TaskState();
        patchBody.taskInfo.stage = newStage;
        patchBody.taskSubStage = newSubStage;
        if (ex != null) {
            patchBody.taskInfo.failure = Utils.toServiceErrorResponse(ex);
        }
        Operation patch = Operation.createPatch(getUri()).setBody(patchBody).setCompletion((o, e) -> {
            if (e != null) {
                logWarning("Self patch failed: %s", com.vmware.xenon.common.Utils.toString(e));
            }
        });
        sendRequest(patch);
    }

    @Override
    public void handlePatch(Operation patch) {
        ProvisionComputeTaskState patchBody = patch.getBody(ProvisionComputeTaskState.class);
        ProvisionComputeTaskState currentState = getState(patch);

        // this validates AND transitions the stage to the next state by using the patchBody.
        if (validateStageTransition(patch, patchBody, currentState)) {
            return;
        }

        handleStagePatch(patch, currentState);
    }

    private void handleStagePatch(Operation patch, ProvisionComputeTaskState currentState) {
        // Complete the self patch eagerly, after we clone state (since state is not "owned"
        // by the service handler once the operation is complete).
        patch.complete();

        switch (currentState.taskInfo.stage) {
        case CREATED:
            break;
        case STARTED:
            processNextSubStage(currentState);
            break;
        case CANCELLED:
            break;
        case FAILED:
            logWarning("Task failed with %s", Utils.toJsonHtml(currentState.taskInfo.failure));
            notifyParentTask(currentState);
            break;
        case FINISHED:
            notifyParentTask(currentState);
            break;
        default:
            break;
        }
    }

    private void notifyParentTask(ProvisionComputeTaskState clonedState) {
        if (clonedState.parentTaskLink == null) {
            return;
        }

        logFine("Patching parent task %s", clonedState.parentTaskLink);
        ProvisionComputeTaskState parentPatchBody = new ProvisionComputeTaskState();
        parentPatchBody.taskInfo = clonedState.taskInfo;
        sendRequest(Operation.createPatch(this, clonedState.parentTaskLink).setBody(parentPatchBody));
    }

    private void processNextSubStage(ProvisionComputeTaskState updatedState) {
        ProvisionComputeTaskState.SubStage newStage = updatedState.taskSubStage;

        switch (newStage) {
        case CREATING_HOST:
            ProvisionComputeTaskState.SubStage nextStageOnSuccess = ProvisionComputeTaskState.SubStage.BOOTING_FROM_ANY;

            // containers and hosted instances don't have a boot operation.
            // They are simply instantiated. Go directly to validation in that case.
            if (updatedState.bootAdapterReference == null) {
                nextStageOnSuccess = ProvisionComputeTaskState.SubStage.VALIDATE_COMPUTE_HOST;
            }

            // the first reboot needs to be from the network, and the bare metal services
            // will provide the image reference (retrieved from the computeLink)
            doSubStageCreateHost(updatedState, nextStageOnSuccess);
            return;
        case BOOTING_FROM_NETWORK:
            doSubStageBootHost(updatedState, BootDevice.NETWORK,
                    ProvisionComputeTaskState.SubStage.VALIDATE_COMPUTE_HOST);
            return;
        case BOOTING_FROM_CDROM:
            doSubStageBootHost(updatedState, BootDevice.CDROM,
                    ProvisionComputeTaskState.SubStage.VALIDATE_COMPUTE_HOST);
            return;
        case BOOTING_FROM_ANY:
            BootDevice[] bootDevices = new BootDevice[] { BootDevice.DISK, BootDevice.CDROM, BootDevice.NETWORK };
            doSubStageBootHost(updatedState, bootDevices, ProvisionComputeTaskState.SubStage.VALIDATE_COMPUTE_HOST);
            return;
        case VALIDATE_COMPUTE_HOST:
            doSubStageValidateComputeHostState(updatedState);
            return;
        case DONE:
            sendSelfPatch(TaskStage.FINISHED, ProvisionComputeTaskState.SubStage.DONE, null);
            break;
        default:
            break;
        }
    }

    private void doSubStageCreateHost(ProvisionComputeTaskState updatedState,
            ProvisionComputeTaskState.SubStage nextStage) {
        CompletionHandler c = (o, e) -> {
            if (e != null) {
                failTask(e);
                return;
            }

            ComputeInstanceRequest cr = new ComputeInstanceRequest();
            cr.computeReference = UriUtils.buildUri(getHost(), updatedState.computeLink);
            cr.requestType = InstanceRequestType.CREATE;
            // the first reboot needs to be from the network, and the bare metal services
            // will provide the image reference (retrieved from the computeLink)

            cr.provisioningTaskReference = o.getUri();
            cr.isMockRequest = updatedState.isMockRequest;
            sendHostServiceRequest(cr, updatedState.instanceAdapterReference);
        };

        // after setting boot order and rebooting, we want the sub
        // task to patch us, the main task, to the "next" state
        createSubTask(c, nextStage, updatedState);
    }

    private URI buildComputeHostUri(ProvisionComputeTaskState updatedState) {
        URI computeHost = UriUtils.buildUri(getHost(), updatedState.computeLink);
        computeHost = ComputeService.ComputeStateWithDescription.buildUri(computeHost);
        return computeHost;
    }

    private void doSubStageBootHost(ProvisionComputeTaskState updatedState, BootDevice bootDevice,
            ProvisionComputeTaskState.SubStage nextStage) {
        doSubStageBootHost(updatedState, new BootDevice[] { bootDevice }, nextStage);
    }

    private void doSubStageBootHost(ProvisionComputeTaskState updatedState, BootDevice[] bootDevices,
            ProvisionComputeTaskState.SubStage nextStage) {
        CompletionHandler c = (o, e) -> {
            if (e != null) {
                failTask(e);
                return;
            }

            ComputeBootRequest br = new ComputeBootRequest();
            br.computeReference = UriUtils.buildUri(getHost(), updatedState.computeLink);
            for (BootDevice bootDevice : bootDevices) {
                br.bootDeviceOrder.add(bootDevice);
            }
            br.provisioningTaskReference = o.getUri();
            br.isMockRequest = updatedState.isMockRequest;
            sendHostServiceRequest(br, updatedState.bootAdapterReference);
        };

        // After setting boot order and rebooting, we want the sub-task
        // to patch the main task to the "next" state.
        createSubTask(c, nextStage, updatedState);
    }

    private void doSubStageValidateComputeHostState(ProvisionComputeTaskState updatedState) {
        // we need to make sure the boot/power services not only properly patched
        // our sub tasks but also patched the compute host with the IP address and other
        // runtime data

        sendRequest(Operation.createGet(this, updatedState.computeLink).setTargetReplicated(true)
                .setCompletion((o, e) -> {
                    if (e != null) {
                        // the compute host is co-located so it is unexpected that it failed the GET
                        logWarning("GET to %s failed:", o.getUri(), Utils.toString(e));
                        failTask(e);
                        return;
                    }
                    ComputeService.ComputeState chs = o.getBody(ComputeService.ComputeState.class);
                    try {
                        if (!updatedState.isMockRequest) {
                            InetAddressValidator.getInstance().isValidInet4Address(chs.address);
                        }
                        sendSelfPatch(TaskStage.FINISHED, ProvisionComputeTaskState.SubStage.DONE, null);
                    } catch (Throwable ex) {
                        failTask(ex);
                    }
                }));
    }

    public void createSubTask(CompletionHandler c, ProvisionComputeTaskState.SubStage nextStage,
            ProvisionComputeTaskState currentState) {
        ProvisionComputeTaskState patchBody = new ProvisionComputeTaskState();
        patchBody.taskInfo.stage = TaskStage.STARTED;
        patchBody.taskSubStage = nextStage;

        ComputeSubTaskService.ComputeSubTaskState subTaskInitState = new ComputeSubTaskService.ComputeSubTaskState();
        subTaskInitState.parentPatchBody = Utils.toJson(patchBody);
        subTaskInitState.errorThreshold = 0;
        subTaskInitState.parentTaskLink = getSelfLink();
        subTaskInitState.tenantLinks = currentState.tenantLinks;
        Operation startPost = Operation.createPost(this, UUID.randomUUID().toString()).setBody(subTaskInitState)
                .setCompletion(c);
        getHost().startService(startPost, new ComputeSubTaskService());
    }

    private void sendHostServiceRequest(Object body, URI adapterReference) {
        sendRequest(Operation.createPatch(adapterReference).setBody(body).setCompletion((o, e) -> {
            if (e != null) {
                failTask(e);
                return;
            }
        }));
    }

    public boolean validateStageTransition(Operation patch, ProvisionComputeTaskState patchBody,
            ProvisionComputeTaskState currentState) {

        if (patchBody.taskInfo != null && patchBody.taskInfo.failure != null) {
            logWarning("Task failed: %s", Utils.toJson(patchBody.taskInfo.failure));
            currentState.taskInfo.failure = patchBody.taskInfo.failure;
            if (patchBody.taskSubStage == null) {
                patchBody.taskSubStage = currentState.taskSubStage;
            }
        } else {
            if (patchBody.taskInfo == null || patchBody.taskInfo.stage == null) {
                patch.fail(new IllegalArgumentException("taskInfo and taskInfo.stage are required"));
                return true;
            }

            // Current state always has a non-null taskSubStage, per validateState.
            if (currentState.taskSubStage == null) {
                patch.fail(new IllegalArgumentException("taskSubStage is required"));
                return true;
            }

            // Patched state must have a non-null taskSubStage,
            // because we're moving from one stage to the next.
            if (patchBody.taskSubStage == null) {
                patch.fail(new IllegalArgumentException("taskSubStage is required"));
                return true;
            }

            if (currentState.taskSubStage.ordinal() > patchBody.taskSubStage.ordinal()) {
                logWarning("Attempt to move progress backwards, not allowed");
                patch.setStatusCode(Operation.STATUS_CODE_NOT_MODIFIED).complete();
                return true;
            }
        }

        logFine("Current: %s(%s). New: %s(%s)", currentState.taskInfo.stage, currentState.taskSubStage,
                patchBody.taskInfo.stage, patchBody.taskSubStage);

        // update current stage to new stage
        currentState.taskInfo.stage = patchBody.taskInfo.stage;
        adjustStat(patchBody.taskInfo.stage.toString(), 1);

        // update sub stage
        currentState.taskSubStage = patchBody.taskSubStage;
        adjustStat(currentState.taskSubStage.toString(), 1);

        return false;
    }

    private void failTask(Throwable e) {
        logWarning("Self patching to FAILED, task failure: %s", e.toString());
        sendSelfPatch(TaskStage.FAILED, ProvisionComputeTaskState.SubStage.FAILED, e);
    }

    public void validateState(ProvisionComputeTaskState state) {
        if (state.computeLink == null) {
            throw new IllegalArgumentException("computeLink is required");
        }

        state.taskInfo = new TaskState();
        state.taskInfo.stage = TaskStage.CREATED;

        if (state.taskSubStage == null) {
            throw new IllegalArgumentException("taskSubStage is required");
        }

        if (state.documentExpirationTimeMicros == 0) {
            state.documentExpirationTimeMicros = Utils.getNowMicrosUtc()
                    + ProvisionComputeTaskState.DEFAULT_EXPIRATION_MICROS;
        }
    }

    @Override
    public ServiceDocument getDocumentTemplate() {
        ServiceDocument td = super.getDocumentTemplate();
        ServiceDocumentDescription.expandTenantLinks(td.documentDescription);
        return td;
    }
}