package com.sequenceiq.cloudbreak.core.flow2.stack.provision.action; import static com.sequenceiq.cloudbreak.api.model.Status.AVAILABLE; import static com.sequenceiq.cloudbreak.api.model.Status.CREATE_FAILED; import static com.sequenceiq.cloudbreak.api.model.Status.CREATE_IN_PROGRESS; import static com.sequenceiq.cloudbreak.api.model.Status.UPDATE_IN_PROGRESS; import static com.sequenceiq.cloudbreak.common.type.BillingStatus.BILLING_STOPPED; import static com.sequenceiq.cloudbreak.core.flow2.stack.provision.StackProvisionConstants.START_DATE; import static java.lang.String.format; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.inject.Inject; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.time.DateUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import com.sequenceiq.cloudbreak.api.model.DetailedStackStatus; import com.sequenceiq.cloudbreak.api.model.InstanceStatus; import com.sequenceiq.cloudbreak.api.model.OnFailureAction; import com.sequenceiq.cloudbreak.api.model.Status; import com.sequenceiq.cloudbreak.cloud.context.CloudContext; import com.sequenceiq.cloudbreak.cloud.event.instance.CollectMetadataResult; import com.sequenceiq.cloudbreak.cloud.event.instance.GetSSHFingerprintsResult; import com.sequenceiq.cloudbreak.cloud.event.resource.LaunchStackResult; import com.sequenceiq.cloudbreak.cloud.event.setup.CheckImageRequest; import com.sequenceiq.cloudbreak.cloud.event.setup.CheckImageResult; import com.sequenceiq.cloudbreak.cloud.model.CloudInstance; import com.sequenceiq.cloudbreak.cloud.model.CloudResourceStatus; import com.sequenceiq.cloudbreak.cloud.model.Group; import com.sequenceiq.cloudbreak.cloud.model.Image; import com.sequenceiq.cloudbreak.cloud.model.TlsInfo; import com.sequenceiq.cloudbreak.cloud.scheduler.CancellationException; import com.sequenceiq.cloudbreak.common.type.BillingStatus; import com.sequenceiq.cloudbreak.converter.spi.StackToCloudStackConverter; import com.sequenceiq.cloudbreak.core.CloudbreakException; import com.sequenceiq.cloudbreak.core.CloudbreakImageNotFoundException; import com.sequenceiq.cloudbreak.core.flow2.stack.FlowMessageService; import com.sequenceiq.cloudbreak.core.flow2.stack.Msg; import com.sequenceiq.cloudbreak.core.flow2.stack.StackContext; import com.sequenceiq.cloudbreak.domain.InstanceGroup; import com.sequenceiq.cloudbreak.domain.InstanceMetaData; import com.sequenceiq.cloudbreak.domain.SecurityConfig; import com.sequenceiq.cloudbreak.domain.Stack; import com.sequenceiq.cloudbreak.logger.MDCBuilder; import com.sequenceiq.cloudbreak.repository.InstanceGroupRepository; import com.sequenceiq.cloudbreak.repository.StackUpdater; import com.sequenceiq.cloudbreak.service.CloudbreakServiceException; import com.sequenceiq.cloudbreak.service.GatewayConfigService; import com.sequenceiq.cloudbreak.service.events.CloudbreakEventService; import com.sequenceiq.cloudbreak.service.image.ImageService; import com.sequenceiq.cloudbreak.service.messages.CloudbreakMessagesService; import com.sequenceiq.cloudbreak.service.notification.Notification; import com.sequenceiq.cloudbreak.service.notification.NotificationSender; import com.sequenceiq.cloudbreak.service.stack.InstanceMetadataService; import com.sequenceiq.cloudbreak.service.stack.StackService; import com.sequenceiq.cloudbreak.service.stack.connector.OperationException; import com.sequenceiq.cloudbreak.service.stack.connector.adapter.ServiceProviderConnectorAdapter; import com.sequenceiq.cloudbreak.service.stack.flow.MetadataSetupService; import com.sequenceiq.cloudbreak.service.stack.flow.TlsSetupService; import com.sequenceiq.cloudbreak.service.usages.UsageService; import reactor.bus.Event; import reactor.bus.EventBus; @Component public class StackCreationService { private static final Logger LOGGER = LoggerFactory.getLogger(StackCreationService.class); @Inject private StackService stackService; @Inject private StackUpdater stackUpdater; @Inject private ImageService imageService; @Inject private NotificationSender notificationSender; @Inject private EventBus eventBus; @Inject private CloudbreakMessagesService messagesService; @Inject private CloudbreakEventService cloudbreakEventService; @Inject private ServiceProviderConnectorAdapter connector; @Inject private InstanceGroupRepository instanceGroupRepository; @Inject private InstanceMetadataService instanceMetadataService; @Inject private MetadataSetupService metadatSetupService; @Inject private TlsSetupService tlsSetupService; @Inject private StackToCloudStackConverter cloudStackConverter; @Inject private FlowMessageService flowMessageService; @Inject private GatewayConfigService gatewayConfigService; @Inject private UsageService usageService; public void setupProvision(Stack stack) { stackUpdater.updateStackStatus(stack.getId(), DetailedStackStatus.PROVISION_SETUP, "Provisioning setup"); } public void prepareImage(Stack stack) { stackUpdater.updateStackStatus(stack.getId(), DetailedStackStatus.IMAGE_SETUP, "Image setup"); flowMessageService.fireEventAndLog(stack.getId(), Msg.STACK_IMAGE_SETUP, CREATE_IN_PROGRESS.name()); } public void startProvisioning(StackContext context) { Stack stack = context.getStack(); MDCBuilder.buildMdcContext(stack); stackUpdater.updateStackStatus(stack.getId(), DetailedStackStatus.CREATING_INFRASTRUCTURE, "Creating infrastructure"); flowMessageService.fireEventAndLog(stack.getId(), Msg.STACK_PROVISIONING, CREATE_IN_PROGRESS.name()); instanceMetadataService.saveInstanceRequests(stack, context.getCloudStack().getGroups()); } public Stack provisioningFinished(StackContext context, LaunchStackResult result, Map<Object, Object> variables) { Date startDate = getStartDateIfExist(variables); Stack stack = context.getStack(); validateResourceResults(context.getCloudContext(), result); List<CloudResourceStatus> results = result.getResults(); stackUpdater.updateStackStatus(stack.getId(), DetailedStackStatus.METADATA_COLLECTION, "Metadata collection"); updateNodeCount(stack.getId(), context.getCloudStack().getGroups(), results, true); flowMessageService.fireEventAndLog(stack.getId(), Msg.STACK_INFRASTRUCTURE_TIME, UPDATE_IN_PROGRESS.name(), calculateStackCreationTime(startDate)); return stackService.getById(stack.getId()); } private Date getStartDateIfExist(Map<Object, Object> variables) { Date result = null; Object startDateObj = variables.get(START_DATE); if (startDateObj != null && startDateObj instanceof Date) { result = (Date) startDateObj; } return result; } public CheckImageResult checkImage(StackContext context) { try { Stack stack = context.getStack(); Image image = imageService.getImage(stack.getId()); CheckImageRequest<CheckImageResult> checkImageRequest = new CheckImageRequest<>(context.getCloudContext(), context.getCloudCredential(), cloudStackConverter.convert(stack), image); LOGGER.info("Triggering event: {}", checkImageRequest); eventBus.notify(checkImageRequest.selector(), Event.wrap(checkImageRequest)); CheckImageResult result = checkImageRequest.await(); sendNotification(result, stack); LOGGER.info("Result: {}", result); return result; } catch (InterruptedException e) { LOGGER.error("Error while executing check image", e); throw new OperationException(e); } catch (CloudbreakImageNotFoundException e) { throw new CloudbreakServiceException(e); } } public Stack setupMetadata(StackContext context, CollectMetadataResult collectMetadataResult) { Stack stack = context.getStack(); metadatSetupService.saveInstanceMetaData(stack, collectMetadataResult.getResults(), InstanceStatus.CREATED); stackUpdater.updateStackStatus(stack.getId(), DetailedStackStatus.TLS_SETUP, "TLS setup"); flowMessageService.fireEventAndLog(stack.getId(), Msg.FLOW_STACK_METADATA_COLLECTED, UPDATE_IN_PROGRESS.name()); LOGGER.debug("Metadata setup DONE."); return stackService.getById(stack.getId()); } public Stack saveTlsInfo(StackContext context, TlsInfo tlsInfo) { boolean usePrivateIpToTls = tlsInfo.usePrivateIpToTls(); Stack stack = context.getStack(); if (usePrivateIpToTls) { SecurityConfig securityConfig = stack.getSecurityConfig(); securityConfig.setUsePrivateIpToTls(usePrivateIpToTls); stackUpdater.updateStackSecurityConfig(stack, securityConfig); stack = stackService.getById(stack.getId()); LOGGER.info("Update Stack and it's SecurityConfig to use private ip when TLS is built."); } return stack; } public void setupTls(StackContext context, GetSSHFingerprintsResult sshFingerprints) throws CloudbreakException { LOGGER.info("Fingerprint has been determined: {}", sshFingerprints.getSshFingerprints()); Stack stack = context.getStack(); for (InstanceMetaData gwInstance : stack.getGatewayInstanceMetadata()) { tlsSetupService.setupTls(stack, gwInstance, stack.getCredential().getLoginUserName(), sshFingerprints.getSshFingerprints()); } } public void removeTemporarySShKey(StackContext context, Set<String> sshFingerprints) throws CloudbreakException { Stack stack = context.getStack(); for (InstanceMetaData gateway : stack.getGatewayInstanceMetadata()) { String ipToTls = gatewayConfigService.getGatewayIp(stack, gateway); tlsSetupService.removeTemporarySShKey(stack, ipToTls, gateway.getSshPort(), stack.getCredential().getLoginUserName(), sshFingerprints); } } public void stackCreationFinished(Stack stack) { flowMessageService.fireEventAndLog(stack.getId(), Msg.FLOW_STACK_PROVISIONED_BILLING, BillingStatus.BILLING_STARTED.name()); flowMessageService.fireEventAndLog(stack.getId(), Msg.FLOW_STACK_PROVISIONED, AVAILABLE.name()); stackUpdater.updateStackStatus(stack.getId(), DetailedStackStatus.PROVISIONED, "Stack provisioned."); usageService.openUsagesForStack(stack); } public void handleStackCreationFailure(Stack stack, Exception errorDetails) { MDCBuilder.buildMdcContext(stack); LOGGER.error("Error during stack creation flow:", errorDetails); String errorReason = errorDetails == null ? "Unknown error" : errorDetails.getMessage(); if (errorDetails instanceof CancellationException || ExceptionUtils.getRootCause(errorDetails) instanceof CancellationException) { LOGGER.warn("The flow has been cancelled."); } else { flowMessageService.fireEventAndLog(stack.getId(), Msg.STACK_INFRASTRUCTURE_CREATE_FAILED, UPDATE_IN_PROGRESS.name(), errorReason); if (!stack.isStackInDeletionPhase()) { handleFailure(stack, errorReason); stackUpdater.updateStackStatus(stack.getId(), DetailedStackStatus.PROVISION_FAILED, errorReason); flowMessageService.fireEventAndLog(stack.getId(), Msg.STACK_INFRASTRUCTURE_CREATE_FAILED, CREATE_FAILED.name(), errorReason); } } } private void sendNotification(CheckImageResult result, Stack stack) { notificationSender.send(getImageCopyNotification(result, stack)); } private Notification getImageCopyNotification(CheckImageResult result, Stack stack) { Notification notification = new Notification(); notification.setEventType("IMAGE_COPY_STATE"); notification.setEventTimestamp(new Date()); notification.setEventMessage(String.valueOf(result.getStatusProgressValue())); notification.setOwner(stack.getOwner()); notification.setAccount(stack.getAccount()); notification.setCloud(stack.cloudPlatform()); notification.setRegion(stack.getRegion()); notification.setStackId(stack.getId()); notification.setStackName(stack.getName()); notification.setStackStatus(stack.getStatus()); return notification; } private void handleFailure(Stack stack, String errorReason) { try { if (!stack.getOnFailureActionAction().equals(OnFailureAction.ROLLBACK)) { LOGGER.debug("Nothing to do. OnFailureAction {}", stack.getOnFailureActionAction()); } else { stackUpdater.updateStackStatus(stack.getId(), DetailedStackStatus.ROLLING_BACK); connector.rollback(stack, stack.getResources()); flowMessageService.fireEventAndLog(stack.getId(), Msg.STACK_INFRASTRUCTURE_CREATE_FAILED, BILLING_STOPPED.name(), errorReason); usageService.closeUsagesForStack(stack); } } catch (Exception ex) { LOGGER.error("Stack rollback failed on stack id : {}. Exception:", stack.getId(), ex); stackUpdater.updateStackStatus(stack.getId(), DetailedStackStatus.PROVISION_FAILED, String.format("Rollback failed: %s", ex.getMessage())); flowMessageService.fireEventAndLog(stack.getId(), Msg.STACK_INFRASTRUCTURE_ROLLBACK_FAILED, CREATE_FAILED.name(), ex.getMessage()); } } private long calculateStackCreationTime(Date startDate) { long result = 0; if (startDate != null) { return (new Date().getTime() - startDate.getTime()) / DateUtils.MILLIS_PER_SECOND; } return result; } private void validateResourceResults(CloudContext cloudContext, LaunchStackResult res) { validateResourceResults(cloudContext, res.getErrorDetails(), res.getResults(), true); } private void validateResourceResults(CloudContext cloudContext, Exception exception, List<CloudResourceStatus> results, boolean create) { String action = create ? "create" : "upscale"; if (exception != null) { LOGGER.error(format("Failed to %s stack: %s", action, cloudContext), exception); throw new OperationException(exception); } if (results.size() == 1 && (results.get(0).isFailed() || results.get(0).isDeleted())) { throw new OperationException(format("Failed to %s the stack for %s due to: %s", action, cloudContext, results.get(0).getStatusReason())); } } private void updateNodeCount(Long stackId, List<Group> originalGroups, List<CloudResourceStatus> statuses, boolean create) { for (Group group : originalGroups) { int nodeCount = group.getInstancesSize(); List<CloudResourceStatus> failedResources = removeFailedMetadata(stackId, statuses, group); if (!failedResources.isEmpty() && create) { int failedCount = failedResources.size(); InstanceGroup instanceGroup = instanceGroupRepository.findOneByGroupNameInStack(stackId, group.getName()); instanceGroup.setNodeCount(nodeCount - failedCount); instanceGroupRepository.save(instanceGroup); flowMessageService.fireEventAndLog(stackId, Msg.STACK_INFRASTRUCTURE_ROLLBACK_MESSAGE, Status.UPDATE_IN_PROGRESS.name(), failedCount, group.getName(), failedResources.get(0).getStatusReason()); } } } private List<CloudResourceStatus> removeFailedMetadata(Long stackId, List<CloudResourceStatus> statuses, Group group) { Map<Long, CloudResourceStatus> failedResources = new HashMap<>(); Set<Long> groupPrivateIds = getPrivateIds(group); for (CloudResourceStatus status : statuses) { Long privateId = status.getPrivateId(); if (privateId != null && status.isFailed() && !failedResources.containsKey(privateId) && groupPrivateIds.contains(privateId)) { failedResources.put(privateId, status); instanceMetadataService.deleteInstanceRequest(stackId, privateId); } } return new ArrayList<>(failedResources.values()); } private Set<Long> getPrivateIds(Group group) { Set<Long> ids = new HashSet<>(); for (CloudInstance cloudInstance : group.getInstances()) { ids.add(cloudInstance.getTemplate().getPrivateId()); } return ids; } }