Java tutorial
/* * 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.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.validation.DefaultBoolean; 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.deployer.dcp.entity.ContainerService; import com.vmware.photon.controller.deployer.dcp.entity.VmService; import com.vmware.photon.controller.deployer.dcp.util.HostUtils; import com.vmware.photon.controller.deployer.deployengine.DockerProvisioner; 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 com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFutureTask; import javax.annotation.Nullable; import java.util.concurrent.Callable; /** * This class implements a DCP micro-service which performs the task of * deleting a container on the specified VM. */ public class DeleteContainerTaskService extends StatefulService { /** * This class defines the document state associated with a single * {@link DeleteContainerTaskService} instance. */ 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 URL of the ContainerService object which * represents the container to be deleted. */ @NotNull @Immutable public String containerServiceLink; /** * This value represents whether the volumes associated with the container * should also be removed on deleting the container. It defaults to false. */ @Immutable @DefaultBoolean(value = false) public Boolean removeVolumes; /** * This value represents whether the container to be deleted should be * deleted even if it is running. It defaults to true. */ @Immutable @DefaultBoolean(value = true) public Boolean force; /** * Control flags. */ @Immutable @DefaultInteger(value = 0) public Integer controlFlags; } public DeleteContainerTaskService() { 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 on the current * service instance. * * @param start Supplies a patch operation to be handled. */ @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) { TaskUtils.sendSelfPatch(this, buildPatch(startState.taskState.stage, null)); } } catch (Throwable t) { failTask(t); } } /** * This method is called when a patch operation is performed on the current * service instance. * * @param patch Supplies a patch operation to be handled. */ @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 handling (disabled)"); } else if (TaskState.TaskStage.STARTED == currentState.taskState.stage) { processGetContainerService(currentState); } } catch (Throwable t) { failTask(t); } } /** * This method validates a state object for internal consistency. * * @param currentState Supplies the current state of the service instance. */ protected void validateState(State currentState) { ValidationUtils.validateState(currentState); ValidationUtils.validateTaskStage(currentState.taskState); } /** * This method validates a patch object against a valid document state * object. * * @param startState Supplies the state of the current service instance. * @param patchState Supplies the state object specified in the patch * operation. */ protected void validatePatchState(State startState, State patchState) { ValidationUtils.validatePatch(startState, patchState); ValidationUtils.validateTaskStage(patchState.taskState); ValidationUtils.validateTaskStageProgression(startState.taskState, patchState.taskState); } /** * This method performs document state updates in response to an operation * which deletes a container. It has a callback handler which retrieves the * container service link and calls another method to get the VM ip address. * * @param currentState */ private void processGetContainerService(final State currentState) { final Operation.CompletionHandler completionHandler = new Operation.CompletionHandler() { @Override public void handle(Operation operation, Throwable throwable) { if (null != throwable) { failTask(throwable); return; } try { ContainerService.State containerState = operation.getBody(ContainerService.State.class); processGetVmIp(currentState, containerState); } catch (Throwable t) { failTask(t); } } }; Operation getOperation = Operation .createGet(UriUtils.buildUri(getHost(), currentState.containerServiceLink)) .setCompletion(completionHandler); sendRequest(getOperation); } /** * This method retrieves the VM Service entity document pointed by the * container service to get the ip address of the vm. * * @param currentState */ private void processGetVmIp(final State currentState, final ContainerService.State containerState) { final Operation.CompletionHandler completionHandler = 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); processDeleteContainer(currentState, vmState.ipAddress, containerState.containerId); } catch (Throwable t) { failTask(t); } } }; Operation getOperation = Operation.createGet(UriUtils.buildUri(getHost(), containerState.vmServiceLink)) .setCompletion(completionHandler); sendRequest(getOperation); } /** * This method deletes a docker container by submitting a future task to * the executor service for the DCP host. On successful completion, the * service is transitioned to the FINISHED state. * * @param currentState Supplies the updated state of the current service * instance. */ private void processDeleteContainer(final State currentState, final String vmIpAddress, final String containerId) { final Service service = this; ListenableFutureTask<String> futureTask = ListenableFutureTask.create(new Callable<String>() { @Override public String call() { DockerProvisioner dockerProvisioner = HostUtils.getDockerProvisionerFactory(service) .create(vmIpAddress); return dockerProvisioner.deleteContainer(containerId, currentState.removeVolumes, currentState.force); } }); HostUtils.getListeningExecutorService(this).submit(futureTask); FutureCallback<String> futureCallback = new FutureCallback<String>() { @Override public void onSuccess(@Nullable String result) { try { if (null == result) { failTask(new IllegalStateException("Delete container returned null")); return; } State patchState = buildPatch(TaskState.TaskStage.FINISHED, null); TaskUtils.sendSelfPatch(service, patchState); } catch (Throwable e) { failTask(e); } } @Override public void onFailure(Throwable t) { failTask(t); } }; Futures.addCallback(futureTask, futureCallback); } /** * This method applies a patch to a state object. * * @param startState Supplies the initial state of the current service * instance. * @param patchState Supplies the patch state associated with a patch * operation. * @return The updated state of the current service instance. */ private State applyPatch(State startState, State patchState) { if (patchState.taskState != null) { 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 builds a state object which can be used to submit a stage * progress self-patch. * * @param stage Supplies the state to which the service instance should be * transitioned. * @param e Supplies an optional Throwable object representing the failure * encountered by the service instance. * @return A State object which can be used to submit a stage progress self- * patch. */ @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; } /** * This method sends a patch operation to the current service instance to * transition to the FAILED state in response to the specified exception. * * @param e Supplies the failure encountered by the service instance. */ private void failTask(Throwable e) { ServiceUtils.logSevere(this, e); TaskUtils.sendSelfPatch(this, buildPatch(TaskState.TaskStage.FAILED, e)); } }