package com.sequenceiq.cloudbreak.cloud.template.compute; import static com.sequenceiq.cloudbreak.cloud.template.compute.CloudFailureHandler.ScaleContext; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import javax.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.stereotype.Service; import com.google.common.collect.Ordering; import com.google.common.primitives.Ints; import com.sequenceiq.cloudbreak.api.model.AdjustmentType; import com.sequenceiq.cloudbreak.cloud.context.AuthenticatedContext; import com.sequenceiq.cloudbreak.cloud.context.CloudContext; import com.sequenceiq.cloudbreak.cloud.model.CloudInstance; import com.sequenceiq.cloudbreak.cloud.model.CloudResource; import com.sequenceiq.cloudbreak.cloud.model.CloudResourceStatus; import com.sequenceiq.cloudbreak.cloud.model.CloudVmInstanceStatus; import com.sequenceiq.cloudbreak.cloud.model.Group; import com.sequenceiq.cloudbreak.cloud.model.Image; import com.sequenceiq.cloudbreak.cloud.model.Platform; import com.sequenceiq.cloudbreak.cloud.template.ComputeResourceBuilder; import com.sequenceiq.cloudbreak.cloud.template.context.ResourceBuilderContext; import com.sequenceiq.cloudbreak.cloud.template.init.ResourceBuilders; import com.sequenceiq.cloudbreak.common.type.ResourceType; @Service public class ComputeResourceService { private static final Logger LOGGER = LoggerFactory.getLogger(ComputeResourceService.class); @Inject private AsyncTaskExecutor resourceBuilderExecutor; @Inject private ApplicationContext applicationContext; @Inject private ResourceBuilders resourceBuilders; @Inject private CloudFailureHandler cloudFailureHandler; public List<CloudResourceStatus> buildResourcesForLaunch(ResourceBuilderContext ctx, AuthenticatedContext auth, List<Group> groups, Image image, Map<String, String> tags, AdjustmentType adjustmentType, Long threshold) { return new ResourceBuilder(ctx, auth).buildResources(groups, image, false, tags, adjustmentType, threshold); } public List<CloudResourceStatus> buildResourcesForUpscale(ResourceBuilderContext ctx, AuthenticatedContext auth, List<Group> groups, Image image, Map<String, String> tags) { return new ResourceBuilder(ctx, auth).buildResources(groups, image, true, tags, AdjustmentType.BEST_EFFORT, null); } public List<CloudResourceStatus> deleteResources(ResourceBuilderContext context, AuthenticatedContext auth, List<CloudResource> resources, boolean cancellable) { List<CloudResourceStatus> results = new ArrayList<>(); List<Future<ResourceRequestResult<List<CloudResourceStatus>>>> futures = new ArrayList<>(); Platform platform = auth.getCloudContext().getPlatform(); List<ComputeResourceBuilder> builders = resourceBuilders.compute(platform); int numberOfBuilders = builders.size(); for (int i = numberOfBuilders - 1; i >= 0; i--) { ComputeResourceBuilder builder = builders.get(i); List<CloudResource> resourceList = getResources(builder.resourceType(), resources); for (CloudResource cloudResource : resourceList) { ResourceDeleteThread thread = createThread(ResourceDeleteThread.NAME, context, auth, cloudResource, builder, cancellable); Future<ResourceRequestResult<List<CloudResourceStatus>>> future = resourceBuilderExecutor.submit(thread); futures.add(future); if (isRequestFull(futures.size(), context)) { results.addAll(flatList(waitForRequests(futures).get(FutureResult.SUCCESS))); } } // wait for builder type to finish before starting the next one results.addAll(flatList(waitForRequests(futures).get(FutureResult.SUCCESS))); } return results; } public List<CloudVmInstanceStatus> stopInstances(ResourceBuilderContext context, AuthenticatedContext auth, List<CloudResource> resources, List<CloudInstance> cloudInstances) { return stopStart(context, auth, resources, cloudInstances); } public List<CloudVmInstanceStatus> startInstances(ResourceBuilderContext context, AuthenticatedContext auth, List<CloudResource> resources, List<CloudInstance> cloudInstances) { return stopStart(context, auth, resources, cloudInstances); } private List<CloudVmInstanceStatus> stopStart(ResourceBuilderContext context, AuthenticatedContext auth, List<CloudResource> resources, List<CloudInstance> instances) { List<CloudVmInstanceStatus> results = new ArrayList<>(); List<Future<ResourceRequestResult<List<CloudVmInstanceStatus>>>> futures = new ArrayList<>(); Platform platform = auth.getCloudContext().getPlatform(); List<ComputeResourceBuilder> builders = resourceBuilders.compute(platform); if (!context.isBuild()) { Collections.reverse(builders); } for (ComputeResourceBuilder builder : builders) { List<CloudResource> resourceList = getResources(builder.resourceType(), resources); for (CloudResource cloudResource : resourceList) { CloudInstance instance = getCloudInstance(cloudResource, instances); if (instance != null) { ResourceStopStartThread thread = createThread(ResourceStopStartThread.NAME, context, auth, cloudResource, instance, builder); Future<ResourceRequestResult<List<CloudVmInstanceStatus>>> future = resourceBuilderExecutor.submit(thread); futures.add(future); if (isRequestFull(futures.size(), context)) { results.addAll(flatVmList(waitForRequests(futures).get(FutureResult.SUCCESS))); } } else { break; } } } results.addAll(flatVmList(waitForRequests(futures).get(FutureResult.SUCCESS))); return results; } private <T> Map<FutureResult, List<T>> waitForRequests(List<Future<ResourceRequestResult<T>>> futures) { Map<FutureResult, List<T>> result = new HashMap<>(); result.put(FutureResult.FAILED, new ArrayList<>()); result.put(FutureResult.SUCCESS, new ArrayList<>()); int requests = futures.size(); LOGGER.info("Waiting for {} requests to finish", requests); try { for (Future<ResourceRequestResult<T>> future : futures) { ResourceRequestResult<T> resourceRequestResult = future.get(); if (FutureResult.FAILED == resourceRequestResult.getStatus()) { result.get(FutureResult.FAILED).add(resourceRequestResult.getResult()); } else { result.get(FutureResult.SUCCESS).add(resourceRequestResult.getResult()); } } } catch (InterruptedException | ExecutionException e) { LOGGER.error("Failed to execute the request", e); } LOGGER.info("{} requests have finished, continue with next group", requests); futures.clear(); return result; } private boolean isRequestFull(int runningRequests, ResourceBuilderContext context) { return isRequestFullWithCloudPlatform(1, runningRequests, context); } private boolean isRequestFullWithCloudPlatform(int numberOfBuilders, int runningRequests, ResourceBuilderContext context) { return (runningRequests * numberOfBuilders) % context.getParallelResourceRequest() == 0; } private List<CloudResource> getResources(ResourceType resourceType, List<CloudResource> resources) { List<CloudResource> selected = new ArrayList<>(); for (CloudResource resource : resources) { if (resourceType == resource.getType()) { selected.add(resource); } } return selected; } private <T> T createThread(String name, Object... args) { return (T) applicationContext.getBean(name, args); } private CloudInstance getCloudInstance(CloudResource cloudResource, List<CloudInstance> instances) { for (CloudInstance instance : instances) { if (instance.getInstanceId().equalsIgnoreCase(cloudResource.getName()) || instance.getInstanceId().equalsIgnoreCase(cloudResource.getReference())) { return instance; } } return null; } private List<CloudVmInstanceStatus> flatVmList(List<List<CloudVmInstanceStatus>> lists) { List<CloudVmInstanceStatus> result = new ArrayList<>(); for (List<CloudVmInstanceStatus> list : lists) { result.addAll(list); } return result; } private List<CloudResourceStatus> flatList(List<List<CloudResourceStatus>> lists) { List<CloudResourceStatus> result = new ArrayList<>(); for (List<CloudResourceStatus> list : lists) { result.addAll(list); } return result; } private class ResourceBuilder { private final ResourceBuilderContext ctx; private final AuthenticatedContext auth; ResourceBuilder(ResourceBuilderContext ctx, AuthenticatedContext auth) { this.ctx = ctx; this.auth = auth; } public List<CloudResourceStatus> buildResources(List<Group> groups, Image image, Boolean upscale, Map<String, String> tags, AdjustmentType adjustmentType, Long threshold) { List<CloudResourceStatus> results = new ArrayList<>(); int fullNodeCount = getFullNodeCount(groups); CloudContext cloudContext = auth.getCloudContext(); List<Future<ResourceRequestResult<List<CloudResourceStatus>>>> futures = new ArrayList<>(); List<ComputeResourceBuilder> builders = resourceBuilders.compute(cloudContext.getPlatform()); for (Group group : getOrderedCopy(groups)) { List<CloudInstance> instances = group.getInstances(); for (CloudInstance instance : instances) { ResourceCreateThread thread = createThread(ResourceCreateThread.NAME, instance.getTemplate().getPrivateId(), group, ctx, auth, image, tags); Future<ResourceRequestResult<List<CloudResourceStatus>>> future = resourceBuilderExecutor.submit(thread); futures.add(future); if (isRequestFullWithCloudPlatform(builders.size(), futures.size(), ctx)) { Map<FutureResult, List<List<CloudResourceStatus>>> futureResultListMap = waitForRequests(futures); results.addAll(flatList(futureResultListMap.get(FutureResult.SUCCESS))); results.addAll(flatList(futureResultListMap.get(FutureResult.FAILED))); cloudFailureHandler.rollback(auth, flatList(futureResultListMap.get(FutureResult.FAILED)), group, fullNodeCount, ctx, resourceBuilders, new ScaleContext(upscale, adjustmentType, threshold)); } } Map<FutureResult, List<List<CloudResourceStatus>>> futureResultListMap = waitForRequests(futures); results.addAll(flatList(futureResultListMap.get(FutureResult.SUCCESS))); results.addAll(flatList(futureResultListMap.get(FutureResult.FAILED))); cloudFailureHandler.rollback(auth, flatList(futureResultListMap.get(FutureResult.FAILED)), group, fullNodeCount, ctx, resourceBuilders, new ScaleContext(upscale, adjustmentType, threshold)); } return results; } private int getFullNodeCount(List<Group> groups) { int fullNodeCount = 0; for (Group group : groups) { fullNodeCount += group.getInstancesSize(); } return fullNodeCount; } private List<Group> getOrderedCopy(List<Group> groups) { Ordering<Group> byLengthOrdering = new Ordering<Group>() { public int compare(Group left, Group right) { return Ints.compare(left.getInstances().size(), right.getInstances().size()); } }; return byLengthOrdering.sortedCopy(groups); } } }