com.vmware.admiral.closures.services.adapter.AdmiralAdapterService.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.admiral.closures.services.adapter.AdmiralAdapterService.java

Source

/*
 * Copyright (c) 2016 VMware, Inc. All Rights Reserved.
 *
 * This product is licensed to you under the Apache License, Version 2.0 (the "License").
 * You may not use this product except in compliance with the License.
 *
 * This product may include a number of subcomponents with separate copyright notices
 * and license terms. Your use of these subcomponents is subject to the terms and
 * conditions of the subcomponent's license, as noted in the LICENSE file.
 */

package com.vmware.admiral.closures.services.adapter;

import static com.vmware.admiral.adapter.docker.service.DockerAdapterCommandExecutor.DOCKER_BUILD_IMAGE_BUILDARGS_PROP_NAME;
import static com.vmware.admiral.adapter.docker.service.DockerAdapterCommandExecutor.DOCKER_BUILD_IMAGE_DOCKERFILE_PROP_NAME;
import static com.vmware.admiral.adapter.docker.service.DockerAdapterCommandExecutor.DOCKER_BUILD_IMAGE_FORCERM_PROP_NAME;
import static com.vmware.admiral.adapter.docker.service.DockerAdapterCommandExecutor.DOCKER_BUILD_IMAGE_NOCACHE_PROP_NAME;
import static com.vmware.admiral.adapter.docker.service.DockerAdapterCommandExecutor.DOCKER_BUILD_IMAGE_TAG_PROP_NAME;
import static com.vmware.admiral.adapter.docker.service.DockerAdapterCommandExecutor.DOCKER_CONTAINER_CREATE_USE_LOCAL_IMAGE_WITH_PRIORITY;
import static com.vmware.admiral.adapter.docker.service.DockerAdapterCommandExecutor.DOCKER_IMAGE_NAME_PROP_NAME;
import static com.vmware.admiral.closures.util.ClosureUtils.loadDockerImageData;
import static com.vmware.admiral.common.ManagementUriParts.CLOSURES_CONTAINER_DESC;
import static com.vmware.admiral.common.util.AssertUtil.assertNotEmpty;
import static com.vmware.admiral.common.util.AssertUtil.assertNotNull;
import static com.vmware.admiral.common.util.ServiceDocumentQuery.error;
import static com.vmware.admiral.common.util.ServiceDocumentQuery.noResult;
import static com.vmware.admiral.common.util.ServiceDocumentQuery.result;
import static com.vmware.admiral.common.util.ServiceDocumentQuery.resultLink;
import static com.vmware.xenon.common.ServiceDocumentDescription.PropertyIndexingOption.STORE_ONLY;
import static com.vmware.xenon.common.ServiceDocumentDescription.PropertyUsageOption.AUTO_MERGE_IF_NOT_NULL;
import static com.vmware.xenon.common.ServiceDocumentDescription.PropertyUsageOption.LINK;
import static com.vmware.xenon.common.ServiceDocumentDescription.PropertyUsageOption.SERVICE_USE;
import static com.vmware.xenon.common.ServiceDocumentDescription.PropertyUsageOption.SINGLE_ASSIGNMENT;

import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import com.vmware.admiral.adapter.common.ImageOperationType;
import com.vmware.admiral.adapter.docker.service.DockerImageHostRequest;
import com.vmware.admiral.closures.drivers.ContainerConfiguration;
import com.vmware.admiral.closures.drivers.DriverConstants;
import com.vmware.admiral.closures.services.closure.ClosureFactoryService;
import com.vmware.admiral.closures.services.images.DockerImage;
import com.vmware.admiral.closures.services.images.DockerImageFactoryService;
import com.vmware.admiral.closures.util.ClosureProps;
import com.vmware.admiral.closures.util.ClosureUtils;
import com.vmware.admiral.common.ManagementUriParts;
import com.vmware.admiral.common.util.OperationUtil;
import com.vmware.admiral.common.util.QueryUtil;
import com.vmware.admiral.common.util.ServiceDocumentQuery;
import com.vmware.admiral.common.util.ServiceUtils;
import com.vmware.admiral.compute.ResourceType;
import com.vmware.admiral.compute.container.ContainerDescriptionService;
import com.vmware.admiral.compute.container.ContainerDescriptionService.ContainerDescription;
import com.vmware.admiral.compute.container.GroupResourcePlacementService;
import com.vmware.admiral.compute.container.GroupResourcePlacementService.GroupResourcePlacementState;
import com.vmware.admiral.compute.container.LogConfig;
import com.vmware.admiral.request.ContainerAllocationTaskFactoryService;
import com.vmware.admiral.request.ContainerAllocationTaskService.ContainerAllocationTaskState;
import com.vmware.admiral.request.allocation.filter.HostSelectionFilter;
import com.vmware.admiral.service.common.AbstractTaskStatefulService;
import com.vmware.admiral.service.common.ConfigurationService;
import com.vmware.admiral.service.common.ServiceTaskCallback;
import com.vmware.photon.controller.model.resources.ComputeService;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.ServiceDocument;
import com.vmware.xenon.common.ServiceHost;
import com.vmware.xenon.common.TaskState;
import com.vmware.xenon.common.UriUtils;
import com.vmware.xenon.common.Utils;
import com.vmware.xenon.services.common.QueryTask;
import com.vmware.xenon.services.common.ServiceUriPaths;

public class AdmiralAdapterService extends
        AbstractTaskStatefulService<AdmiralAdapterService.AdmiralAdapterTaskState, AdmiralAdapterService.AdmiralAdapterTaskState.SubStage> {

    public static final String DISPLAY_NAME = "Closure Container Provisioning";

    private static final String MAX_LOG_FILE_SIZE = "90k";

    private static final String BUILD_IMAGE_RETRIES_COUNT_PARAM_NAME = "build.closure.image.retries.count";

    private final Random randomIntegers = new Random();

    private volatile Integer retriesCount;

    private ContainerDescription cachedContainerDescription;

    public AdmiralAdapterService() {
        super(AdmiralAdapterTaskState.class, AdmiralAdapterTaskState.SubStage.class, DISPLAY_NAME);
        super.toggleOption(ServiceOption.PERSISTENCE, true);
        super.toggleOption(ServiceOption.REPLICATION, true);
        super.toggleOption(ServiceOption.OWNER_SELECTION, true);
        super.toggleOption(ServiceOption.INSTRUMENTATION, true);
    }

    public static class AdmiralAdapterTaskState
            extends com.vmware.admiral.service.common.TaskServiceDocument<AdmiralAdapterTaskState.SubStage> {

        private static final String FIELD_NAME_CONTAINER_IMAGE = "containerImage";
        private static final String FIELD_NAME_CONFIGURATION = "configuration";

        public static enum SubStage {
            CREATED, RESOURCE_POOL_RESERVED, COMPUTE_STATE_SELECTED, COMPLETED, ERROR;
        }

        /** Image name to use */
        @PropertyOptions(usage = { SINGLE_ASSIGNMENT, LINK }, indexing = STORE_ONLY)
        public String containerImage;

        /** Container configuration to be applied. */
        @PropertyOptions(usage = { SINGLE_ASSIGNMENT }, indexing = STORE_ONLY)
        public ContainerConfiguration configuration;

        /** Resource pool to use on provisioning */
        @PropertyOptions(usage = { SERVICE_USE, SINGLE_ASSIGNMENT, LINK,
                AUTO_MERGE_IF_NOT_NULL }, indexing = STORE_ONLY)
        public String placementZoneLink;

        /** Resource policy to use on provisioning */
        @PropertyOptions(usage = { SERVICE_USE, SINGLE_ASSIGNMENT, LINK,
                AUTO_MERGE_IF_NOT_NULL }, indexing = STORE_ONLY)
        public String groupResourcePlacementLink;

        /** Compute state to use on provisioning */
        @PropertyOptions(usage = { SERVICE_USE, SINGLE_ASSIGNMENT, LINK,
                AUTO_MERGE_IF_NOT_NULL }, indexing = STORE_ONLY)
        public String selectedComputeLink;
    }

    @Override
    protected void handleStartedStagePatch(AdmiralAdapterTaskState state) {
        switch (state.taskSubStage) {
        case CREATED:
            proceedWithContainerDescription(state, null);
            break;
        case RESOURCE_POOL_RESERVED:
            handleResourcePoolReserved(state);
            break;
        case COMPUTE_STATE_SELECTED:
            handleComputeSelected(state);
            break;
        case COMPLETED:
            complete();
            break;
        case ERROR:
            completeWithError();
            break;
        default:
            break;
        }
    }

    private void handleResourcePoolReserved(AdmiralAdapterTaskState state) {
        if (cachedContainerDescription == null) {
            fetchContainerDescription(state, (contDesc) -> {
                selectComputeStates(state, contDesc);
            });
            return;
        }

        selectComputeStates(state, cachedContainerDescription);
    }

    private void selectComputeStates(AdmiralAdapterTaskState state, ContainerDescription contDesc) {
        proceedWithComputeStates(contDesc, state, ComputeService.ComputeState.class,
                ComputeService.ComputeState.FIELD_NAME_RESOURCE_POOL_LINK, state.placementZoneLink,
                (computeResult) -> {
                    if (computeResult.hasException()) {
                        failTask("Unable to fetch compute states: ", computeResult.getException());
                        return;
                    }
                    proceedTo(AdmiralAdapterTaskState.SubStage.COMPUTE_STATE_SELECTED, (s) -> {
                        s.selectedComputeLink = computeResult.getDocumentSelfLink();
                    });

                });
    }

    private void handleComputeSelected(AdmiralAdapterTaskState state) {
        if (cachedContainerDescription == null) {
            fetchContainerDescription(state, (contDesc) -> {
                proceedWithBaseDockerImage(contDesc, state, 0);
            });
            return;
        }

        proceedWithBaseDockerImage(cachedContainerDescription, state, 0);
    }

    @Override
    protected void validateStateOnStart(AdmiralAdapterTaskState state) throws IllegalArgumentException {
        assertNotEmpty(state.containerImage, AdmiralAdapterTaskState.FIELD_NAME_CONTAINER_IMAGE);
        assertNotNull(state.configuration, AdmiralAdapterTaskState.FIELD_NAME_CONFIGURATION);
    }

    @SuppressWarnings("SuspiciousMethodCalls")
    private void seedPeers(Collection<ComputeService.ComputeState> computeStates, List<String> computeStateLinks,
            String selectedComputeLink, Consumer<String> consumer) {
        Collection<ComputeService.ComputeState> availableStates = new ArrayList<>(computeStates);
        List<ComputeService.ComputeState> selectedForSeed = availableStates.parallelStream().filter(
                (s) -> !s.documentSelfLink.equalsIgnoreCase(selectedComputeLink) && !computeStateLinks.contains(s))
                .collect(Collectors.toList());

        selectedForSeed.parallelStream().forEach((s) -> consumer.accept(s.documentSelfLink));
    }

    private void proceedWithBaseDockerImage(ContainerDescription containerDesc, AdmiralAdapterTaskState state,
            int retriesCount) {
        logInfo("Checking docker build image request for image: %s host: %s", containerDesc.image,
                state.selectedComputeLink);
        String baseImageName = createBaseImageName(containerDesc.image);
        String dockerBuildImageLink = createImageBuildRequestUri(baseImageName, state.selectedComputeLink);
        getHost().sendRequest(Operation.createGet(getHost(), dockerBuildImageLink).setReferer(getHost().getUri())
                .setCompletion((op, ex) -> {
                    if (ex != null) {
                        if (op.getStatusCode() == 404) {
                            logWarning("No build image found! image: %s compute state: %s", baseImageName,
                                    state.selectedComputeLink);
                            // proceed with images creation
                            proceedWithBaseImageCreation(containerDesc, state, 0);
                        } else {
                            logWarning("Unable to fetch docker image requests:", ex);
                            failTask("Unable to fetch docker image requests", ex);
                        }
                    } else {
                        logInfo("Docker build image request already created.");
                        DockerImage imageRequest = op.getBody(DockerImage.class);
                        handleBaseDockerBuildImageRequest(containerDesc, state, imageRequest, retriesCount);
                    }
                }));
    }

    private void proceedWithDockerImage(ContainerDescription containerDesc, AdmiralAdapterTaskState state,
            int retriesCount) {
        logInfo("Checking docker build image request for image: %s host: %s, retries: %s", containerDesc.image,
                state.selectedComputeLink, retriesCount);
        String dockerBuildImageLink = createImageBuildRequestUri(containerDesc.image, state.selectedComputeLink);
        getHost().sendRequest(Operation.createGet(getHost(), dockerBuildImageLink).setReferer(getHost().getUri())
                .setCompletion((op, ex) -> {
                    if (ex != null) {
                        if (op.getStatusCode() == 404) {
                            logWarning("No build image found! image: %s compute state: %s", containerDesc.image,
                                    state.selectedComputeLink);
                            // proceed with images creation
                            proceedWithDockerImageCreation(containerDesc, state, 0);
                        } else {
                            logWarning("Unable to fetch docker image requests:", ex);
                            failTask("Unable to fetch docker image requests", ex);
                        }
                    } else {
                        logInfo("Docker build image request already created.");
                        DockerImage imageRequest = op.getBody(DockerImage.class);
                        handleDockerBuildImageRequest(containerDesc, state, imageRequest, retriesCount);
                    }
                }));
    }

    private void ensurePropertyExists(Consumer<Integer> callback) {
        if (retriesCount != null) {
            callback.accept(retriesCount);
        } else {
            String maxRetriesCountConfigPropPath = UriUtils.buildUriPath(
                    ConfigurationService.ConfigurationFactoryService.SELF_LINK,
                    BUILD_IMAGE_RETRIES_COUNT_PARAM_NAME);
            sendRequest(Operation.createGet(this, maxRetriesCountConfigPropPath).setCompletion((o, ex) -> {
                /** in case of exception the default retry count will be 3 */
                retriesCount = Integer.valueOf(3);
                if (ex == null) {
                    retriesCount = Integer.valueOf(o.getBody(ConfigurationService.ConfigurationState.class).value);
                }
                callback.accept(retriesCount);
            }));
        }
    }

    private void proceedWithBaseImageCreation(ContainerDescription containerDesc, AdmiralAdapterTaskState state,
            int retriesCount) {
        URI uri = UriUtils.buildUri(getHost(), DockerImageFactoryService.FACTORY_LINK);

        DockerImage buildImage = new DockerImage();
        String baseImageName = createBaseImageName(containerDesc.image);
        buildImage.name = baseImageName;
        buildImage.computeStateLink = state.selectedComputeLink;
        buildImage.taskInfo = TaskState.create();
        buildImage.documentSelfLink = createImageBuildRequestUri(baseImageName, state.selectedComputeLink);

        logInfo("Creating docker build image request: %s ", uri);
        getHost().sendRequest(OperationUtil.createForcedPost(uri).setBody(buildImage).setReferer(getHost().getUri())
                .setCompletion((o, e) -> {
                    if (e != null) {
                        logSevere("Exception while submitting docker build image request: ", e);
                        failTask("Unable to submit docker build image request", e);
                        return;
                    }

                    logInfo("Docker build image request has been created successfully.");
                    // 2. Send image load request
                    loadBaseImage(baseImageName, containerDesc, state.selectedComputeLink);
                    // 3. Poll for completion
                    getHost().schedule(() -> proceedWithBaseDockerImage(containerDesc, state, retriesCount), 5,
                            TimeUnit.SECONDS);
                }));
    }

    private void loadBaseImage(String baseImageName, ContainerDescription containerDesc, String computeStateLink) {
        DockerImageHostRequest request = new DockerImageHostRequest();
        request.operationTypeId = ImageOperationType.LOAD.id;
        String completionServiceCallBack = createImageBuildRequestUri(baseImageName, computeStateLink);
        request.serviceTaskCallback = ServiceTaskCallback.create(completionServiceCallBack);
        request.resourceReference = UriUtils.buildUri(getHost(), computeStateLink);

        logInfo("Loading image to REMOTE DOCKER HOST: %s ", request.resourceReference);

        request.customProperties = new HashMap<>();
        request.customProperties.putIfAbsent(DOCKER_IMAGE_NAME_PROP_NAME, baseImageName);

        getHost().sendRequest(Operation.createPatch(getHost(), ManagementUriParts.ADAPTER_DOCKER_IMAGE_HOST)
                .setBody(request).setReferer(getHost().getUri()).setCompletion((o, ex) -> {
                    if (ex != null) {
                        logSevere("Unable to load image on docker host: ", ex);
                        failTask("Unable to load image on docker host: " + computeStateLink, ex);
                        return;
                    }

                    logInfo("Docker load image request sent. Image: %s, host: %s", baseImageName, computeStateLink);
                }));
    }

    private String createBaseImageName(String image) {
        String baseName = image.substring(image.indexOf("/") + 1);
        int tagIndex = baseName.lastIndexOf(":");
        if (tagIndex > 0) {
            baseName = baseName.substring(0, tagIndex);
        }
        return baseName + "_base.tar.xz";
    }

    private void seedWithBaseDockerImage(ContainerDescription containerDesc, String computeStateLink,
            AdmiralAdapterTaskState state) {
        String baseImageName = createBaseImageName(containerDesc.image);
        logInfo("Checking docker build image request for base image: %s host: %s", baseImageName, computeStateLink);
        String dockerBuildImageLink = createImageBuildRequestUri(baseImageName, computeStateLink);
        getHost().sendRequest(Operation.createGet(getHost(), dockerBuildImageLink).setReferer(getHost().getUri())
                .setCompletion((op, ex) -> {
                    if (ex != null) {
                        if (op.getStatusCode() == 404) {
                            logWarning("No base image found! image: %s compute state: %s", containerDesc.image,
                                    computeStateLink);
                            // proceed with images creation
                            seedWithBaseDockerImageCreation(baseImageName, containerDesc, computeStateLink, state);
                        } else {
                            logWarning("Unable to fetch docker base image request:", ex);
                        }
                    } else {
                        logInfo("Docker build base image request already created.");
                        DockerImage imageRequest = op.getBody(DockerImage.class);
                        touchDockerImage(dockerBuildImageLink, imageRequest);
                        seedWithDockerImage(containerDesc, computeStateLink, state);
                    }
                }));
    }

    private void seedWithDockerImage(ContainerDescription containerDesc, String computeStateLink,
            AdmiralAdapterTaskState state) {
        logInfo("Checking docker build image request for image: %s host: %s", containerDesc.image,
                computeStateLink);
        String dockerBuildImageLink = createImageBuildRequestUri(containerDesc.image, computeStateLink);
        getHost().sendRequest(Operation.createGet(getHost(), dockerBuildImageLink).setReferer(getHost().getUri())
                .setCompletion((op, ex) -> {
                    if (ex != null) {
                        if (op.getStatusCode() == 404) {
                            logWarning("No build image found! image: %s compute state: %s", containerDesc.image,
                                    computeStateLink);
                            // proceed with images creation
                            seedWithDockerImageCreation(containerDesc, computeStateLink, state);
                        } else {
                            logWarning("Unable to fetch docker image requests:", ex);
                        }
                    } else {
                        logInfo("Docker build image request already created.");
                        DockerImage imageRequest = op.getBody(DockerImage.class);

                        touchDockerImage(dockerBuildImageLink, imageRequest);
                    }
                }));
    }

    private void seedWithBaseDockerImageCreation(String baseImageName, ContainerDescription containerDesc,
            String computeStateLink, AdmiralAdapterTaskState state) {
        URI uri = UriUtils.buildUri(getHost(), DockerImageFactoryService.FACTORY_LINK);

        DockerImage buildImage = new DockerImage();
        buildImage.name = baseImageName;
        buildImage.computeStateLink = computeStateLink;
        buildImage.taskInfo = TaskState.create();
        buildImage.documentSelfLink = createImageBuildRequestUri(baseImageName, computeStateLink);

        logInfo("Creating docker build image request: %s", uri);
        getHost().sendRequest(OperationUtil.createForcedPost(uri).setBody(buildImage).setReferer(getHost().getUri())
                .setCompletion((o, e) -> {
                    if (e != null) {
                        logSevere("Exception while submitting docker build image request: ", e);
                        failTask("Unable to submit docker build image request", e);
                        return;
                    }

                    logInfo("Docker build image request has been created successfully.");
                    // 2. Send image build request
                    loadBaseImage(baseImageName, containerDesc, computeStateLink);
                    // 3. Poll for completion
                    getHost().schedule(() -> seedWithBaseDockerImage(containerDesc, computeStateLink, state), 5,
                            TimeUnit.SECONDS);
                }));
    }

    private void handleBaseDockerBuildImageRequest(ContainerDescription containerDesc,
            AdmiralAdapterTaskState state, DockerImage imageRequest, int retriesCount) {
        if (TaskState.isFinished(imageRequest.taskInfo)) {
            // Image has been already built, proceed with container allocation
            logInfo("Base Image has already been built. Creating child image...");
            proceedWithDockerImage(containerDesc, state, 0);
            touchDockerImage(imageRequest.documentSelfLink, imageRequest);
        } else if (TaskState.isFailed(imageRequest.taskInfo)) {
            AtomicInteger retryCount = new AtomicInteger(retriesCount);
            ensurePropertyExists((retryCountProperty) -> {
                if (retryCount.getAndIncrement() < retryCountProperty) {
                    // try to load base image again
                    proceedWithBaseImageCreation(containerDesc, state, retryCount.get());
                } else {
                    // Failed to build docker image
                    logWarning("Failed to build base image %s on host: %s Reason: %s", containerDesc.image,
                            state.selectedComputeLink, imageRequest.taskInfo.failure);
                    String errorMessage = getErrorMsg(imageRequest);
                    failTask(
                            "Failed to build base image " + containerDesc.image + " on host: "
                                    + state.selectedComputeLink + " Reason: " + imageRequest.taskInfo.failure,
                            new Exception(errorMessage));
                }
            });
        } else {
            // Base image is still not ready. Wait until build process completes.
            logInfo("Base image is still building: %s", containerDesc.image);
            getHost().schedule(() -> proceedWithBaseDockerImage(containerDesc, state, 0), 5, TimeUnit.SECONDS);
        }
    }

    private String getErrorMsg(DockerImage imageRequest) {
        return imageRequest.taskInfo.failure != null ? imageRequest.taskInfo.failure.message : "General error";
    }

    private void handleDockerBuildImageRequest(ContainerDescription containerDesc, AdmiralAdapterTaskState state,
            DockerImage imageRequest, int retriesCount) {
        if (TaskState.isFinished(imageRequest.taskInfo)) {
            // Image has been already built, proceed with container allocation
            logInfo("Image: %s is ready. Allocating docker container...", containerDesc.image);
            proceedWithProvidedPolicy(containerDesc, state);
            String dockerBuildImageLink = createImageBuildRequestUri(containerDesc.image,
                    state.selectedComputeLink);
            touchDockerImage(dockerBuildImageLink, imageRequest);
        } else if (TaskState.isFailed(imageRequest.taskInfo)) {
            AtomicInteger retryCount = new AtomicInteger(retriesCount);
            ensurePropertyExists((retryCountProperty) -> {
                if (retryCount.getAndIncrement() < retryCountProperty) {
                    // try to load base image again
                    proceedWithDockerImageCreation(containerDesc, state, retryCount.get());
                } else {
                    // Failed to build docker image
                    logWarning("Failed to build image {} on host: %s Reason: %s", containerDesc.image,
                            state.selectedComputeLink, imageRequest.taskInfo.failure);
                    String errorMessage = getErrorMsg(imageRequest);
                    failTask(
                            "Failed to build image " + containerDesc.image + " on host: "
                                    + state.selectedComputeLink + " Reason: " + imageRequest.taskInfo.failure,
                            new Exception(errorMessage));
                }
            });
        } else {
            // Image is still not ready. Wait until build process completes.
            logInfo("Image is still building: %s", containerDesc.image);
            getHost().schedule(() -> proceedWithDockerImage(containerDesc, state, retriesCount), 3,
                    TimeUnit.SECONDS);
        }
    }

    private void proceedWithProvidedPolicy(ContainerDescription containerDesc, AdmiralAdapterTaskState state) {
        // Create allocation closure
        ContainerAllocationTaskState allocationTask = prepareContainerAllocationTask(containerDesc, 1,
                state.groupResourcePlacementLink);

        HostSelectionFilter.HostSelection hostSelection = new HostSelectionFilter.HostSelection();
        hostSelection.resourceCount = 1;
        hostSelection.hostLink = state.selectedComputeLink;
        hostSelection.resourcePoolLinks = new ArrayList<>(Arrays.asList(state.placementZoneLink));
        allocationTask.hostSelections = new ArrayList<>(Collections.singletonList(hostSelection));

        // Allocate container
        try {
            logInfo("Initiating provisioning closure for: {}", containerDesc.env[0]);
            startAllocationTask(allocationTask);

            proceedTo(AdmiralAdapterService.AdmiralAdapterTaskState.SubStage.COMPLETED);
        } catch (Throwable ex) {
            logWarning("Unable to initiate provisioning closure: " + containerDesc.env[0], ex);
            throw new RuntimeException(ex);
        }
    }

    private void startAllocationTask(ContainerAllocationTaskState allocationTask) {
        URI uri = UriUtils.buildUri(getHost(), ContainerAllocationTaskFactoryService.SELF_LINK);
        getHost().sendRequest(OperationUtil.createForcedPost(uri).setBody(allocationTask)
                .setReferer(getHost().getUri()).setCompletion((o, e) -> {
                    if (e != null) {
                        logSevere("Exception while submitting allocation closure: ", e);
                        return;
                    }

                    logInfo("Allocation closure submitted successfully");
                }));

    }

    private ContainerAllocationTaskState prepareContainerAllocationTask(ContainerDescription containerDesc,
            long resourceCount, String placemenStateLink) {
        String taskLink = buildTaskLink(containerDesc);

        ContainerAllocationTaskState allocationTask = new ContainerAllocationTaskState();
        allocationTask.resourceDescriptionLink = containerDesc.documentSelfLink;
        allocationTask.groupResourcePlacementLink = placemenStateLink;
        allocationTask.resourceType = ResourceType.CONTAINER_TYPE.getName();
        allocationTask.resourceCount = resourceCount;
        allocationTask.tenantLinks = containerDesc.tenantLinks;
        allocationTask.serviceTaskCallback = createServiceCallBack(taskLink);
        allocationTask.customProperties = new HashMap<>();
        return allocationTask;
    }

    private ServiceTaskCallback createServiceCallBack(String taskLink) {
        return ServiceTaskCallback.create(taskLink);
    }

    private String buildTaskLink(ContainerDescription containerDesc) {
        String taskId = containerDesc.name.substring(0, containerDesc.name.indexOf("_"));
        return ClosureFactoryService.FACTORY_LINK + "/" + taskId;
    }

    private void seedWithDockerImageCreation(ContainerDescription containerDesc, String computeStateLink,
            AdmiralAdapterTaskState state) {
        URI uri = UriUtils.buildUri(getHost(), DockerImageFactoryService.FACTORY_LINK);

        DockerImage buildImage = new DockerImage();
        buildImage.name = containerDesc.image;
        buildImage.computeStateLink = computeStateLink;
        buildImage.taskInfo = TaskState.create();
        buildImage.documentSelfLink = createImageBuildRequestUri(containerDesc.image, computeStateLink);

        logInfo("Creating docker build image request: %", uri);
        getHost().sendRequest(OperationUtil.createForcedPost(uri).setBody(buildImage).setReferer(getHost().getUri())
                .setCompletion((o, e) -> {
                    if (e != null) {
                        logSevere("Exception while submitting docker build image request: ", e);
                        failTask("Unable to submit docker build image request", e);
                        return;
                    }

                    logInfo("Docker build image request has been created successfully.");
                    // 2. Send image build request
                    buildDockerImage(containerDesc, computeStateLink, state);
                    // 3. Poll for completion
                    getHost().schedule(() -> seedWithDockerImage(containerDesc, computeStateLink, state), 3,
                            TimeUnit.SECONDS);
                }));

    }

    private void proceedWithDockerImageCreation(ContainerDescription containerDesc, AdmiralAdapterTaskState state,
            int retriesCount) {

        URI uri = UriUtils.buildUri(getHost(), DockerImageFactoryService.FACTORY_LINK);

        DockerImage buildImage = new DockerImage();
        buildImage.name = containerDesc.image;
        buildImage.computeStateLink = state.selectedComputeLink;
        buildImage.taskInfo = TaskState.create();
        buildImage.documentSelfLink = createImageBuildRequestUri(containerDesc.image, state.selectedComputeLink);

        logInfo("Creating docker build image request: %s, retries: %s", uri, retriesCount);
        getHost().sendRequest(OperationUtil.createForcedPost(uri).setBody(buildImage).setReferer(getHost().getUri())
                .setCompletion((o, e) -> {
                    if (e != null) {
                        logSevere("Exception while submitting docker build image request: ", e);
                        failTask("Unable to submit docker build image request", e);
                        return;
                    }

                    logInfo("Docker build image request has been created successfully.");
                    // 2. Send image build request
                    buildDockerImage(containerDesc, state.selectedComputeLink, state);
                    // 3. Poll for completion
                    getHost().schedule(() -> proceedWithDockerImage(containerDesc, state, retriesCount), 3,
                            TimeUnit.SECONDS);
                }));

    }

    private void buildDockerImage(ContainerDescription containerDesc, String computeStateLink,
            AdmiralAdapterTaskState state) {
        logInfo("Sending docker image build request of execution container...");

        DockerImageHostRequest request = new DockerImageHostRequest();
        request.operationTypeId = ImageOperationType.BUILD.id;
        String completionServiceCallBack = createImageBuildRequestUri(containerDesc.image, computeStateLink);
        request.serviceTaskCallback = ServiceTaskCallback.create(completionServiceCallBack);
        request.resourceReference = UriUtils.buildUri(getHost(), computeStateLink);

        logInfo("Build image on REMOTE DOCKER HOST: %s ", request.resourceReference);

        request.customProperties = new HashMap<>();
        request.customProperties.putIfAbsent(DOCKER_BUILD_IMAGE_TAG_PROP_NAME, containerDesc.image);
        request.customProperties.putIfAbsent(DOCKER_BUILD_IMAGE_DOCKERFILE_PROP_NAME, "Dockerfile");
        request.customProperties.putIfAbsent(DOCKER_BUILD_IMAGE_FORCERM_PROP_NAME, "true");
        request.customProperties.putIfAbsent(DOCKER_BUILD_IMAGE_NOCACHE_PROP_NAME, "true");

        boolean setTaskUri = mustSetTaskUri(state);
        JsonElement buildArgsObj = prepareBuildArgs(containerDesc, setTaskUri);

        request.customProperties.putIfAbsent(DOCKER_BUILD_IMAGE_BUILDARGS_PROP_NAME, buildArgsObj.toString());

        request.setDockerImageData(loadDockerImageData(containerDesc.image,
                DriverConstants.DOCKER_IMAGE_DATA_FOLDER_NAME, getClass()));

        Operation op = Operation.createPatch(getHost(), ManagementUriParts.ADAPTER_DOCKER_IMAGE_HOST)
                .setBody(request).setReferer(getHost().getUri()).setCompletion((o, ex) -> {
                    if (ex != null) {
                        logSevere("Unable to build image on docker host: ", ex);
                        failTask("Unable to build image on docker host: " + computeStateLink, ex);
                        return;
                    }

                    logInfo("Docker build image request sent. Image: %s, host: %s", containerDesc.image,
                            computeStateLink);
                });

        prepareRequest(op, true);
        getHost().sendRequest(op);
    }

    private boolean mustSetTaskUri(AdmiralAdapterTaskState state) {
        if (!ClosureUtils.isEmpty(state.configuration.dependencies)) {
            return true;
        }

        if (!ClosureUtils.isEmpty(state.configuration.sourceURL)) {
            return true;
        }

        return false;
    }

    protected void prepareRequest(Operation op, boolean longRunningRequest) {
        op.forceRemote();
        if (op.getExpirationMicrosUtc() == 0L) {
            long timeout;
            if (longRunningRequest) {
                timeout = TimeUnit.SECONDS.toMicros(ClosureProps.DOCKER_IMAGE_REQUEST_TIMEOUT_SECONDS);
            } else {
                timeout = ServiceHost.ServiceHostState.DEFAULT_OPERATION_TIMEOUT_MICROS;
            }

            op.setExpiration(ServiceUtils.getExpirationTimeFromNowInMicros(timeout));
        }
    }

    private JsonElement prepareBuildArgs(ContainerDescription containerDesc, boolean setTaskUri) {
        JsonObject buildArgsObj = new JsonObject();
        for (String env : containerDesc.env) {
            int sepIndex = env.indexOf("=");
            if (sepIndex > 0) {
                String key = env.split("=")[0].trim();
                if (setTaskUri && "TASK_URI".equalsIgnoreCase(key)) {
                    String value = env.substring(sepIndex + 1).trim();
                    buildArgsObj.addProperty(key, value);
                }
            }
        }

        return buildArgsObj;
    }

    private String createImageBuildRequestUri(String imageName, String computeStateLink) {
        String imageBuildRequestId = ClosureUtils.calculateHash(new String[] { imageName, "/", computeStateLink });

        return UriUtils.buildUriPath(DockerImageFactoryService.FACTORY_LINK, imageBuildRequestId);
    }

    private void touchDockerImage(String dockerBuildImageLink, DockerImage imageRequest) {
        logInfo("Updating docker build image request: {}", dockerBuildImageLink);
        getHost().sendRequest(Operation.createPatch(getHost(), dockerBuildImageLink).setBody(imageRequest)
                .setReferer(getHost().getUri()).setCompletion((o, e) -> {
                    if (e != null) {
                        logWarning("Exception while updating docker build image request: ", e);
                        return;
                    }

                    logInfo("Docker build image request has been updated successfully: %s", dockerBuildImageLink);
                }));
    }

    private void proceedWithComputeStates(ContainerDescription containerDesc, AdmiralAdapterTaskState state,
            Class<? extends ServiceDocument> type, String propId, String propValue,
            Consumer<ServiceDocumentQuery.ServiceDocumentQueryElementResult<ServiceDocument>> completionHandler) {
        QueryTask q = QueryUtil.buildPropertyQuery(type, propId, propValue);
        q.documentExpirationTimeMicros = ServiceDocumentQuery.getDefaultQueryExpiration();

        QueryUtil.addExpandOption(q);

        getHost().sendRequest(Operation.createPost(UriUtils.buildUri(getHost(), ServiceUriPaths.CORE_QUERY_TASKS))
                .setBody(q).setReferer(getHost().getUri()).setCompletion((o, e) -> {
                    if (e != null) {
                        completionHandler.accept(error(e));
                        return;
                    }

                    handleFetchedComputeStates(containerDesc, state, propValue, completionHandler, o);
                }));
    }

    private void handleFetchedComputeStates(ContainerDescription containerDesc, AdmiralAdapterTaskState state,
            String propValue,
            Consumer<ServiceDocumentQuery.ServiceDocumentQueryElementResult<ServiceDocument>> completionHandler,
            Operation o) {
        try {
            QueryTask qtr = o.getBody(QueryTask.class);
            if (qtr.results.documents == null || qtr.results.documents.isEmpty()) {
                logWarning("No available computes configured for: %s", propValue);
                completionHandler.accept(error(new Exception("No computes configured for: " + propValue)));
                return;
            } else {
                Collection<Object> values = qtr.results.documents.values();
                if (values.isEmpty()) {
                    completionHandler.accept(noResult());
                    return;
                }
                Collection<ComputeService.ComputeState> computeStates = convertToComputeStates(values);
                logInfo("Size of available compute states: %s", computeStates.size());
                proceedWithPlacement(containerDesc, computeStates, state, completionHandler);
            }
        } catch (Throwable ex) {
            logSevere("Error occurred: ", ex);
            completionHandler.accept(error(ex));
        }
    }

    private void proceedWithPlacement(ContainerDescription containerDesc,
            Collection<ComputeService.ComputeState> computeStates, AdmiralAdapterTaskState state,
            Consumer<ServiceDocumentQuery.ServiceDocumentQueryElementResult<ServiceDocument>> completionHandler) {

        QueryTask q = QueryUtil.buildQuery(DockerImage.class, false);
        QueryTask.Query imageClause = new QueryTask.Query().setTermPropertyName("name")
                .setTermMatchValue(containerDesc.image);
        q.querySpec.query.addBooleanClause(imageClause);

        QueryUtil.addExpandOption(q);

        final List<String> computeStateLinks = new ArrayList<>();
        ServiceDocumentQuery<DockerImage> query = new ServiceDocumentQuery<>(getHost(), DockerImage.class);
        query.query(q, (r) -> {
            if (r.hasException()) {
                completionHandler.accept(error(r.getException()));
            } else if (r.hasResult()) {
                DockerImage dockerImage = r.getResult();
                if (TaskState.isFinished(dockerImage.taskInfo)) {
                    computeStateLinks.add(dockerImage.computeStateLink);
                }
            } else {
                String selectedComputeLink = selectComputeLink(computeStates, computeStateLinks);
                if (ClosureUtils.isEmpty(selectedComputeLink)) {
                    logWarning("No available hosts configured! Aborting deployment...");
                    completionHandler.accept(error(new Exception("No available hosts configured!")));
                }

                logInfo("Selected compute to provision: %s ", selectedComputeLink);
                completionHandler.accept(resultLink(selectedComputeLink, 1));

                seedPeers(computeStates, computeStateLinks, selectedComputeLink,
                        (computeStateLink) -> seedWithBaseDockerImage(containerDesc, computeStateLink, state));
            }
        });
    }

    private String selectComputeLink(Collection<ComputeService.ComputeState> computeStates,
            List<String> cachedComputeStateLinks) {
        String selectedComputeLink = null;
        if (cachedComputeStateLinks.isEmpty()) {
            // Image not found anywhere cached
            return selectFromComputeStates(computeStates);
        }
        List<String> liveComputeStatesLinks = filterOutdatedComputes(computeStates, cachedComputeStateLinks);
        if (liveComputeStatesLinks.isEmpty()) {
            // Image not found anywhere cached on live compute states
            return selectFromComputeStates(computeStates);
        }

        // Select random from the list of live compute states with cached images
        return (String) nextValue(liveComputeStatesLinks);
    }

    private String selectFromComputeStates(Collection<ComputeService.ComputeState> computeStates) {
        ComputeService.ComputeState selectedCompute = (ComputeService.ComputeState) nextValue(computeStates);
        if (selectedCompute != null) {
            return selectedCompute.documentSelfLink;
        }
        return null;
    }

    private List<String> filterOutdatedComputes(Collection<ComputeService.ComputeState> computeStates,
            List<String> computeStateLinks) {

        return computeStates.stream().filter(c -> computeStateLinks.contains(c.documentSelfLink))
                .map(computeState -> computeState.documentSelfLink).collect(Collectors.toList());

    }

    private Object nextValue(Collection<?> values) {
        List<Object> items = new ArrayList<>(values);
        if (items.size() == 0) {
            return null;
        } else if (items.size() == 1) {
            return items.get(0);
        }

        int randomIndex = (randomIntegers.nextInt(1000000) + 1) % items.size();
        return items.get(randomIndex);
    }

    private Collection<ComputeService.ComputeState> convertToComputeStates(Collection<Object> values) {
        List<ComputeService.ComputeState> computeStates = new LinkedList<>();
        for (Object val : values) {
            computeStates.add(Utils.fromJson(val, ComputeService.ComputeState.class));
        }

        return computeStates;
    }

    private void proceedWithContainerDescription(AdmiralAdapterTaskState state,
            ContainerDescription containerDescription) {
        if (containerDescription == null) {
            fetchContainerDescription(state, (contDesc) -> this.proceedWithContainerDescription(state, contDesc));
            return;
        }

        proceedWithGroupPlacement(state, containerDescription);
    }

    private void proceedWithGroupPlacement(AdmiralAdapterTaskState state, ContainerDescription containerDesc) {
        fetchGroupPlacement(state, (result) -> {
            if (result.hasException()) {
                failTask("No available placement group!", result.getException());
                return;
            }
            GroupResourcePlacementState placement = result.getResult();
            if (placement != null) {
                String placementZoneLink = placement.resourcePoolLink;
                proceedTo(AdmiralAdapterTaskState.SubStage.RESOURCE_POOL_RESERVED, (s) -> {
                    s.placementZoneLink = placementZoneLink;
                    s.groupResourcePlacementLink = placement.documentSelfLink;
                });

            } else {
                // create closure placement
                failTask("No configured placement available!", null);
                return;
            }
        });
    }

    private void fetchGroupPlacement(AdmiralAdapterTaskState state,
            Consumer<ServiceDocumentQuery.ServiceDocumentQueryElementResult<GroupResourcePlacementService.GroupResourcePlacementState>> callbackFunction) {
        String placementId = computePlacementId(state.configuration);
        logInfo("Fetching group placement: %s", placementId);
        try {
            getHost().sendRequest(Operation.createGet(getHost(), placementId).setReferer(getHost().getUri())
                    .setCompletion((op, ex) -> {
                        if (ex != null) {
                            callbackFunction.accept(error(ex));
                        } else {
                            GroupResourcePlacementState placement = op.getBody(GroupResourcePlacementState.class);
                            callbackFunction.accept(result(placement, 1));
                        }
                    }));

        } catch (Throwable ex) {
            logSevere("Unable to fetch configured group placement!", ex);
        }
    }

    private String computePlacementId(ContainerConfiguration configuration) {
        if (ClosureUtils.isEmpty(configuration.placementLink)) {
            return GroupResourcePlacementService.DEFAULT_RESOURCE_PLACEMENT_LINK;
        }

        return configuration.placementLink;
    }

    private void fetchContainerDescription(AdmiralAdapterTaskState state,
            Consumer<ContainerDescription> callbackFunction) {
        String checksum = ClosureUtils.calculateHash(state.configuration.envVars);

        String containerDescriptionLink = CLOSURES_CONTAINER_DESC + "-" + checksum;
        URI containerDescriptionURI = UriUtils.buildUri(getHost(), containerDescriptionLink);
        logInfo("Getting container desc: %s", containerDescriptionURI);

        try {
            ServiceDocumentQuery<ContainerDescription> query = new ServiceDocumentQuery<>(getHost(),
                    ContainerDescription.class);
            query.queryDocument(containerDescriptionLink, (r) -> {
                if (r.hasException()) {
                    Throwable ex = r.getException();
                    logWarning("Failure retrieving policy container: "
                            + (ex instanceof CancellationException ? ex.getMessage() : Utils.toString(ex)));
                    failTask("Failure retrieving description state", ex);
                    return;
                } else if (r.hasResult()) {
                    ContainerDescription contDesc = r.getResult();
                    this.cachedContainerDescription = contDesc;
                    logInfo("Already created execution container description: %s", contDesc.documentSelfLink);
                    callbackFunction.accept(contDesc);
                } else {
                    logInfo("Unable to find execution container description: %s", containerDescriptionLink);

                    // container description not found... proceed with creation
                    proceedWithDescriptionCreation(state, checksum);
                }
            });

        } catch (Throwable ex) {
            String errorMsg = "Unable to allocate execution container";
            logSevere(errorMsg, ex);
            throw new RuntimeException(ex);
        }
    }

    private String prepareImageTag(String containerImage, ContainerConfiguration configuration) {
        if (ClosureUtils.isEmpty(configuration.sourceURL)) {
            if (ClosureUtils.isEmpty(configuration.dependencies)) {
                // no dependencies
                return "latest";
            }

            return ClosureUtils.calculateHash(new String[] { configuration.dependencies });
        }

        return ClosureUtils.calculateHash(new String[] { configuration.sourceURL });
    }

    private void proceedWithDescriptionCreation(AdmiralAdapterTaskState state, String configChecksum) {
        String imageTag = prepareImageTag(state.containerImage, state.configuration);
        // Create container description
        ContainerDescription containerDesc = prepareContainerDescription(state.containerImage, imageTag,
                state.configuration, configChecksum);
        URI uri = UriUtils.buildUri(getHost(), ContainerDescriptionService.FACTORY_LINK);

        logInfo("Creating execution container description: %s", uri);
        getHost().sendRequest(OperationUtil.createForcedPost(uri).setBody(containerDesc)
                .setReferer(getHost().getUri()).setCompletion((o, e) -> {
                    if (e != null) {
                        logSevere("Exception while creating container description: ", e);
                        failTask("Exception while creating container description", e);
                        return;
                    }

                    logInfo("Execution container description created successfully.");
                    proceedWithContainerDescription(state, null);
                }));
    }

    private ContainerDescription prepareContainerDescription(String imageName, String imageTag,
            ContainerConfiguration configuration, String configChecksum) {
        ContainerDescription containerDesc = new ContainerDescription();

        containerDesc.documentSelfLink = CLOSURES_CONTAINER_DESC + "-" + configChecksum;
        containerDesc.name = configuration.name;
        containerDesc.image = imageName + ":" + imageTag;
        containerDesc.memoryLimit = ClosureUtils.toBytes(configuration.memoryMB);
        containerDesc.cpuShares = configuration.cpuShares;
        containerDesc.env = configuration.envVars;
        containerDesc.logConfig = prepareLogConfig(configuration);
        containerDesc.customProperties = new HashMap<>();
        containerDesc.customProperties.put(DOCKER_CONTAINER_CREATE_USE_LOCAL_IMAGE_WITH_PRIORITY, "true");

        return containerDesc;
    }

    private LogConfig prepareLogConfig(ContainerConfiguration configuration) {
        JsonElement jsonLogConfig = configuration.logConfiguration;
        LogConfig logConfig = new LogConfig();
        if (jsonLogConfig == null || !jsonLogConfig.isJsonObject()) {
            // set default log configuration
            logConfig.type = "json-file";
            logConfig.config = new HashMap<>();
            logConfig.config.put("max-size", MAX_LOG_FILE_SIZE);
            return logConfig;
        }

        JsonObject jsonObject = (JsonObject) jsonLogConfig;
        JsonElement typeElement = jsonObject.get("type");
        if (typeElement == null || typeElement.isJsonNull()) {
            logConfig.type = "json-file";
        } else {
            logConfig.type = typeElement.getAsString();
        }

        Map<String, String> configMap = new HashMap<>();
        JsonElement configElement = jsonObject.get("config");
        if (configElement == null || configElement.isJsonNull()) {
            logConfig.config = configMap;
            return logConfig;
        }
        JsonObject jsonConfig = configElement.getAsJsonObject();
        for (Map.Entry<String, JsonElement> entry : jsonConfig.entrySet()) {
            configMap.put(entry.getKey(), entry.getValue().getAsString());
        }

        logConfig.config = configMap;
        return logConfig;
    }

}