com.vmware.photon.controller.deployer.dcp.task.WaitForServiceTaskService.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.photon.controller.deployer.dcp.task.WaitForServiceTaskService.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.dcp.task;

import com.vmware.photon.controller.common.dcp.InitializationUtils;
import com.vmware.photon.controller.common.dcp.PatchUtils;
import com.vmware.photon.controller.common.dcp.ServiceUtils;
import com.vmware.photon.controller.common.dcp.TaskUtils;
import com.vmware.photon.controller.common.dcp.ValidationUtils;
import com.vmware.photon.controller.common.dcp.validation.DefaultInteger;
import com.vmware.photon.controller.common.dcp.validation.DefaultTaskState;
import com.vmware.photon.controller.common.dcp.validation.Immutable;
import com.vmware.photon.controller.common.dcp.validation.NotNull;
import com.vmware.photon.controller.deployer.dcp.ContainersConfig;
import com.vmware.photon.controller.deployer.dcp.entity.VmService;
import com.vmware.photon.controller.deployer.dcp.util.ControlFlags;
import com.vmware.photon.controller.deployer.dcp.util.HostUtils;
import com.vmware.photon.controller.deployer.healthcheck.HealthCheckHelperFactory;
import com.vmware.photon.controller.deployer.healthcheck.HealthCheckHelperFactoryProvider;
import com.vmware.photon.controller.deployer.healthcheck.HealthChecker;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.Service;
import com.vmware.xenon.common.ServiceDocument;
import com.vmware.xenon.common.StatefulService;
import com.vmware.xenon.common.TaskState;
import com.vmware.xenon.common.UriUtils;
import com.vmware.xenon.common.Utils;

import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.EnumUtils;

import javax.annotation.Nullable;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Waits for the specified Zookeeper to be up and running and joined in a cluster.
 */
public class WaitForServiceTaskService extends StatefulService {

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

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

        /**
         * The vm service link for the zookeeper instance.
         */
        @NotNull
        @Immutable
        public String vmServiceLink;

        /**
         * The type of container service we are checking.
         */
        @NotNull
        @Immutable
        public String containerType;

        /**
         * Maximum retries to check zookeeper liveness.
         */
        @Immutable
        public Integer maxRetries;

        /**
         * This value represents the interval, in milliseconds, to use when polling
         * the state of a task object returned by an API call.
         */
        @Immutable
        public Integer taskPollDelay;

        /**
         * This value allows processing of post and patch operations to be
         * disabled, effectively making all service instances listeners. It is set
         * only in test scenarios.
         */
        @DefaultInteger(value = 0)
        @Immutable
        public Integer controlFlags;
    }

    public WaitForServiceTaskService() {
        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);

        if (null == startState.taskPollDelay) {
            startState.taskPollDelay = HostUtils.getDeployerContext(this).getTaskPollDelay();
        }

        if (null == startState.maxRetries) {
            startState.maxRetries = HostUtils.getDeployerContext(this).getWaitForServiceMaxRetryCount();
        }

        validateStartState(startState);

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

        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.
     *
     * @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);
        validateStartState(currentState);
        patch.complete();

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

    private void validateStartState(State currentState) {
        ValidationUtils.validateState(currentState);
        ValidationUtils.validateTaskStage(currentState.taskState);
    }

    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 state object.
     *
     * @param startState
     * @param patchState
     */
    private State applyPatch(State startState, State patchState) {
        if (patchState.taskState.stage != startState.taskState.stage) {
            ServiceUtils.logInfo(this, "Moving to stage %s", patchState.taskState.stage);
        }

        PatchUtils.patchState(startState, patchState);
        return startState;
    }

    /**
     * This method performs document owner operations in response to a patch
     * operation.
     *
     * @param currentState
     */
    private void processStartedStage(final State currentState) {
        final Service service = this;
        Operation.CompletionHandler handler = new Operation.CompletionHandler() {
            @Override
            public void handle(Operation operation, Throwable throwable) {
                if (null != throwable) {
                    failTask(throwable);
                    return;
                }

                try {
                    VmService.State vmState = operation.getBody(VmService.State.class);

                    if (EnumUtils.isValidEnum(ContainersConfig.ContainerType.class, currentState.containerType)) {
                        ContainersConfig.ContainerType containerType = ContainersConfig.ContainerType
                                .valueOf(currentState.containerType);

                        final HealthChecker healthChecker = getHealthCheckHelperFactory()
                                .create(service, containerType, vmState.ipAddress).getHealthChecker();
                        final AtomicInteger retryCounter = new AtomicInteger(currentState.maxRetries);

                        scheduleHealthCheckQuery(currentState, healthChecker, retryCounter);
                    } else {
                        // Assume success
                        sendStageProgressPatch(TaskState.TaskStage.FINISHED);
                    }
                } catch (Throwable t) {
                    failTask(t);
                }
            }
        };

        Operation get = Operation.createGet(UriUtils.buildUri(getHost(), currentState.vmServiceLink))
                .setCompletion(handler);

        sendRequest(get);
    }

    private void scheduleHealthCheckQuery(final State currentState, final HealthChecker healthChecker,
            final AtomicInteger retryCounter) {

        final Service service = this;

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                if (retryCounter.decrementAndGet() < 0) {
                    String errMessage = String.format("%s not ready after %s retries", currentState.containerType,
                            currentState.maxRetries);
                    ServiceUtils.logSevere(service, errMessage);
                    failTask(new RuntimeException(errMessage));
                    return;
                }

                ServiceUtils.logInfo(service, "HealthCheck (%s) retry: %s of maxRetries(%s)",
                        currentState.containerType, currentState.maxRetries - retryCounter.get(),
                        currentState.maxRetries);

                if (healthChecker.isReady()) {
                    sendStageProgressPatch(TaskState.TaskStage.FINISHED);
                    return;
                }

                scheduleHealthCheckQuery(currentState, healthChecker, retryCounter);
            }
        };

        getHost().schedule(runnable, currentState.taskPollDelay, TimeUnit.MILLISECONDS);
    }

    @VisibleForTesting
    protected HealthCheckHelperFactory getHealthCheckHelperFactory() {
        return ((HealthCheckHelperFactoryProvider) getHost()).getHealthCheckHelperFactory();
    }

    /**
     * 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, "sendStageProgressPatch %s", stage);
        TaskUtils.sendSelfPatch(this, buildPatch(stage, null));
    }

    /**
     * This method sends a patch operation to the current service instance
     * to moved 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 state object which can be used to submit a stage
     * progress self-patch.
     *
     * @param stage
     * @param e
     * @return
     */
    @VisibleForTesting
    protected State buildPatch(TaskState.TaskStage stage, @Nullable Throwable e) {
        State state = new State();
        state.taskState = new TaskState();
        state.taskState.stage = stage;

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

        return state;
    }
}