package com.sequenceiq.cloudbreak.cloud.openstack.heat; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.openstack4j.api.Builders; import org.openstack4j.api.OSClient; import org.openstack4j.model.heat.Stack; import org.openstack4j.model.heat.StackUpdate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import com.sequenceiq.cloudbreak.api.model.AdjustmentType; import com.sequenceiq.cloudbreak.cloud.ResourceConnector; import com.sequenceiq.cloudbreak.cloud.context.AuthenticatedContext; import com.sequenceiq.cloudbreak.cloud.exception.CloudConnectorException; import com.sequenceiq.cloudbreak.cloud.exception.TemplatingDoesNotSupportedException; 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.CloudStack; import com.sequenceiq.cloudbreak.cloud.model.Group; import com.sequenceiq.cloudbreak.cloud.model.InstanceStatus; import com.sequenceiq.cloudbreak.cloud.model.ResourceStatus; import com.sequenceiq.cloudbreak.cloud.model.TlsInfo; import com.sequenceiq.cloudbreak.cloud.notification.PersistenceNotifier; import com.sequenceiq.cloudbreak.cloud.openstack.auth.OpenStackClient; import com.sequenceiq.cloudbreak.cloud.openstack.common.OpenStackUtils; import com.sequenceiq.cloudbreak.cloud.openstack.view.NeutronNetworkView; import com.sequenceiq.cloudbreak.common.type.ResourceType; @Service public class OpenStackResourceConnector implements ResourceConnector<Object> { private static final Logger LOGGER = LoggerFactory.getLogger(OpenStackResourceConnector.class); private static final long OPERATION_TIMEOUT = 60L; @Inject private OpenStackClient openStackClient; @Inject private HeatTemplateBuilder heatTemplateBuilder; @Inject private OpenStackUtils utils; @SuppressWarnings("unchecked") @Override public List<CloudResourceStatus> launch(AuthenticatedContext authenticatedContext, CloudStack stack, PersistenceNotifier notifier, AdjustmentType adjustmentType, Long threshold) { String stackName = utils.getStackName(authenticatedContext); NeutronNetworkView neutronNetworkView = new NeutronNetworkView(stack.getNetwork()); boolean existingNetwork = neutronNetworkView.isExistingNetwork(); String existingSubnetCidr = getExistingSubnetCidr(authenticatedContext, stack); HeatTemplateBuilder.ModelContext modelContext = new HeatTemplateBuilder.ModelContext(); modelContext.withExistingNetwork(existingNetwork); modelContext.withExistingSubnet(existingSubnetCidr != null); modelContext.withGroups(stack.getGroups()); modelContext.withInstanceUserData(stack.getImage()); modelContext.withLocation(authenticatedContext.getCloudContext().getLocation()); modelContext.withStackName(stackName); modelContext.withNeutronNetworkView(neutronNetworkView); modelContext.withTemplateString(stack.getTemplate()); modelContext.withTags(stack.getTags()); String heatTemplate = heatTemplateBuilder.build(modelContext); Map<String, String> parameters = heatTemplateBuilder.buildParameters( authenticatedContext, stack.getNetwork(), stack.getImage(), existingNetwork, existingSubnetCidr); OSClient client = openStackClient.createOSClient(authenticatedContext); List<CloudResourceStatus> resources; Stack existingStack = client.heat().stacks().getStackByName(stackName); if (existingStack == null) { Stack heatStack = client .heat() .stacks() .create(Builders.stack().name(stackName).template(heatTemplate).disableRollback(false) .parameters(parameters).timeoutMins(OPERATION_TIMEOUT).build()); CloudResource cloudResource = new CloudResource.Builder().type(ResourceType.HEAT_STACK).name(heatStack.getId()).build(); try { notifier.notifyAllocation(cloudResource, authenticatedContext.getCloudContext()); } catch (Exception e) { //Rollback terminate(authenticatedContext, stack, Collections.singletonList(cloudResource)); } resources = check(authenticatedContext, Collections.singletonList(cloudResource)); } else { LOGGER.info("Heat stack already exists: {}", existingStack.getName()); CloudResource cloudResource = new CloudResource.Builder().type(ResourceType.HEAT_STACK).name(existingStack.getId()).build(); resources = Collections.singletonList(new CloudResourceStatus(cloudResource, ResourceStatus.CREATED)); } LOGGER.debug("Launched resources: {}", resources); return resources; } @Override public List<CloudResourceStatus> check(AuthenticatedContext authenticatedContext, List<CloudResource> resources) { List<CloudResourceStatus> result = new ArrayList<>(); OSClient client = openStackClient.createOSClient(authenticatedContext); for (CloudResource resource : resources) { switch (resource.getType()) { case HEAT_STACK: String heatStackId = resource.getName(); String stackName = utils.getStackName(authenticatedContext); LOGGER.info("Checking OpenStack Heat stack status of: {}", stackName); Stack heatStack = client.heat().stacks().getDetails(stackName, heatStackId); CloudResourceStatus heatResourceStatus = utils.heatStatus(resource, heatStack); result.add(heatResourceStatus); break; default: throw new CloudConnectorException(String.format("Invalid resource type: %s", resource.getType())); } } return result; } @Override public List<CloudResourceStatus> terminate(AuthenticatedContext authenticatedContext, CloudStack cloudStack, List<CloudResource> resources) { for (CloudResource resource : resources) { switch (resource.getType()) { case HEAT_STACK: String heatStackId = resource.getName(); String stackName = utils.getStackName(authenticatedContext); LOGGER.info("Terminate stack: {}", stackName); OSClient client = openStackClient.createOSClient(authenticatedContext); client.heat().stacks().delete(stackName, heatStackId); break; default: throw new CloudConnectorException(String.format("Invalid resource type: %s", resource.getType())); } } return check(authenticatedContext, resources); } @Override public List<CloudResourceStatus> upscale(AuthenticatedContext authenticatedContext, CloudStack stack, List<CloudResource> resources) { String stackName = utils.getStackName(authenticatedContext); NeutronNetworkView neutronNetworkView = new NeutronNetworkView(stack.getNetwork()); boolean existingNetwork = neutronNetworkView.isExistingNetwork(); String existingSubnetCidr = getExistingSubnetCidr(authenticatedContext, stack); HeatTemplateBuilder.ModelContext modelContext = new HeatTemplateBuilder.ModelContext(); modelContext.withExistingNetwork(existingNetwork); modelContext.withExistingSubnet(existingSubnetCidr != null); modelContext.withGroups(stack.getGroups()); modelContext.withInstanceUserData(stack.getImage()); modelContext.withLocation(authenticatedContext.getCloudContext().getLocation()); modelContext.withStackName(stackName); modelContext.withNeutronNetworkView(neutronNetworkView); modelContext.withTemplateString(stack.getTemplate()); modelContext.withTags(stack.getTags()); String heatTemplate = heatTemplateBuilder.build(modelContext); Map<String, String> parameters = heatTemplateBuilder.buildParameters( authenticatedContext, stack.getNetwork(), stack.getImage(), existingNetwork, existingSubnetCidr); return updateHeatStack(authenticatedContext, resources, heatTemplate, parameters); } @Override public Object collectResourcesToRemove(AuthenticatedContext authenticatedContext, CloudStack stack, List<CloudResource> resources, List<CloudInstance> vms) { return null; } @Override public List<CloudResourceStatus> downscale(AuthenticatedContext authenticatedContext, CloudStack cloudStack, List<CloudResource> resources, List<CloudInstance> vms, Object resourcesToRemove) { CloudStack stack = removeDeleteRequestedInstances(cloudStack); String stackName = utils.getStackName(authenticatedContext); NeutronNetworkView neutronNetworkView = new NeutronNetworkView(stack.getNetwork()); boolean existingNetwork = neutronNetworkView.isExistingNetwork(); String existingSubnetCidr = getExistingSubnetCidr(authenticatedContext, stack); HeatTemplateBuilder.ModelContext modelContext = new HeatTemplateBuilder.ModelContext(); modelContext.withExistingNetwork(existingNetwork); modelContext.withExistingSubnet(existingSubnetCidr != null); modelContext.withGroups(stack.getGroups()); modelContext.withInstanceUserData(stack.getImage()); modelContext.withLocation(authenticatedContext.getCloudContext().getLocation()); modelContext.withStackName(stackName); modelContext.withNeutronNetworkView(neutronNetworkView); modelContext.withTemplateString(stack.getTemplate()); modelContext.withTags(stack.getTags()); String heatTemplate = heatTemplateBuilder.build(modelContext); Map<String, String> parameters = heatTemplateBuilder.buildParameters( authenticatedContext, stack.getNetwork(), stack.getImage(), existingNetwork, existingSubnetCidr); return updateHeatStack(authenticatedContext, resources, heatTemplate, parameters); } @Override public TlsInfo getTlsInfo(AuthenticatedContext authenticatedContext, CloudStack cloudStack) { return new TlsInfo(false); } @Override public String getStackTemplate() throws TemplatingDoesNotSupportedException { return heatTemplateBuilder.getTemplate(); } @Override public List<CloudResourceStatus> update(AuthenticatedContext authenticatedContext, CloudStack stack, List<CloudResource> resources) { String stackName = utils.getStackName(authenticatedContext); NeutronNetworkView neutronNetworkView = new NeutronNetworkView(stack.getNetwork()); boolean existingNetwork = neutronNetworkView.isExistingNetwork(); String existingSubnetCidr = getExistingSubnetCidr(authenticatedContext, stack); HeatTemplateBuilder.ModelContext modelContext = new HeatTemplateBuilder.ModelContext(); modelContext.withExistingNetwork(existingNetwork); modelContext.withExistingSubnet(existingSubnetCidr != null); modelContext.withGroups(stack.getGroups()); modelContext.withInstanceUserData(stack.getImage()); modelContext.withLocation(authenticatedContext.getCloudContext().getLocation()); modelContext.withStackName(stackName); modelContext.withNeutronNetworkView(neutronNetworkView); modelContext.withTemplateString(stack.getTemplate()); modelContext.withTags(stack.getTags()); String heatTemplate = heatTemplateBuilder.build(modelContext); Map<String, String> parameters = heatTemplateBuilder.buildParameters( authenticatedContext, stack.getNetwork(), stack.getImage(), existingNetwork, existingSubnetCidr); return updateHeatStack(authenticatedContext, resources, heatTemplate, parameters); } private List<CloudResourceStatus> updateHeatStack(AuthenticatedContext authenticatedContext, List<CloudResource> resources, String heatTemplate, Map<String, String> parameters) { CloudResource resource = utils.getHeatResource(resources); String stackName = utils.getStackName(authenticatedContext); String heatStackId = resource.getName(); OSClient client = openStackClient.createOSClient(authenticatedContext); StackUpdate updateRequest = Builders.stackUpdate().template(heatTemplate) .parameters(parameters).timeoutMins(OPERATION_TIMEOUT).build(); client.heat().stacks().update(stackName, heatStackId, updateRequest); LOGGER.info("Heat stack update request sent with stack name: '{}' for Heat stack: '{}'", stackName, heatStackId); return check(authenticatedContext, resources); } private CloudStack removeDeleteRequestedInstances(CloudStack stack) { List<Group> groups = new ArrayList<>(); for (Group group : stack.getGroups()) { List<CloudInstance> instances = new ArrayList<>(group.getInstances()); for (CloudInstance instance : group.getInstances()) { if (InstanceStatus.DELETE_REQUESTED == instance.getTemplate().getStatus()) { instances.remove(instance); } } groups.add(new Group(group.getName(), group.getType(), instances, group.getSecurity(), null)); } return new CloudStack(groups, stack.getNetwork(), stack.getImage(), stack.getParameters(), stack.getTags(), stack.getTemplate()); } private String getExistingSubnetCidr(AuthenticatedContext authenticatedContext, CloudStack stack) { NeutronNetworkView neutronView = new NeutronNetworkView(stack.getNetwork()); return neutronView.isExistingSubnet() ? utils.getExistingSubnetCidr(authenticatedContext, neutronView) : null; } }