package com.sequenceiq.cloudbreak.cloud.openstack.heat; import static com.sequenceiq.cloudbreak.util.FreeMarkerTemplateUtils.processTemplateIntoString; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNoneEmpty; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.inject.Inject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.sequenceiq.cloudbreak.api.model.InstanceGroupType; import com.sequenceiq.cloudbreak.cloud.context.AuthenticatedContext; import com.sequenceiq.cloudbreak.cloud.exception.CloudConnectorException; import com.sequenceiq.cloudbreak.cloud.model.AvailabilityZone; import com.sequenceiq.cloudbreak.cloud.model.Group; import com.sequenceiq.cloudbreak.cloud.model.Image; import com.sequenceiq.cloudbreak.cloud.model.Location; import com.sequenceiq.cloudbreak.cloud.model.Network; import com.sequenceiq.cloudbreak.cloud.openstack.common.OpenStackUtils; import com.sequenceiq.cloudbreak.cloud.openstack.view.KeystoneCredentialView; import com.sequenceiq.cloudbreak.cloud.openstack.view.NeutronNetworkView; import com.sequenceiq.cloudbreak.cloud.openstack.view.NovaInstanceView; import com.sequenceiq.cloudbreak.cloud.openstack.view.OpenStackGroupView; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; @Service public class HeatTemplateBuilder { private static final Logger LOGGER = LoggerFactory.getLogger(HeatTemplateBuilder.class); @Value("${cb.openstack.heat.template.path:}") private String openStackHeatTemplatePath; @Inject private OpenStackUtils openStackUtil; @Inject private Configuration freemarkerConfiguration; public String build(ModelContext modelContext) { try { List<NovaInstanceView> novaInstances = new OpenStackGroupView(modelContext.stackName, modelContext.groups, modelContext.tags).getFlatNovaView(); Map<String, Object> model = new HashMap<>(); model.put("cb_stack_name", openStackUtil.adjustStackNameLength(modelContext.stackName)); model.put("agents", novaInstances); model.put("core_user_data", formatUserData(modelContext.instanceUserData.getUserData(InstanceGroupType.CORE))); model.put("gateway_user_data", formatUserData(modelContext.instanceUserData.getUserData(InstanceGroupType.GATEWAY))); model.put("groups", modelContext.groups); model.put("existingNetwork", modelContext.existingNetwork); model.put("existingSubnet", modelContext.existingSubnet); model.put("network", modelContext.neutronNetworkView); AvailabilityZone az = modelContext.location.getAvailabilityZone(); if (az != null && az.value() != null) { model.put("availability_zone", az.value()); } Template template = new Template(openStackHeatTemplatePath, modelContext.templateString, freemarkerConfiguration); String generatedTemplate = processTemplateIntoString(template, model); LOGGER.debug("Generated Heat template: {}", generatedTemplate); return generatedTemplate; } catch (IOException | TemplateException e) { throw new CloudConnectorException("Failed to process the OpenStack HeatTemplateBuilder", e); } } public Map<String, String> buildParameters(AuthenticatedContext auth, Network network, Image image, boolean existingNetwork, String existingSubnetCidr) { KeystoneCredentialView osCredential = new KeystoneCredentialView(auth); NeutronNetworkView neutronView = new NeutronNetworkView(network); Map<String, String> parameters = new HashMap<>(); if (neutronView.isAssignFloatingIp()) { parameters.put("public_net_id", neutronView.getPublicNetId()); } parameters.put("image_id", image.getImageName()); parameters.put("key_name", osCredential.getKeyPairName()); if (existingNetwork) { parameters.put("app_net_id", neutronView.getCustomNetworkId()); if (isNoneEmpty(existingSubnetCidr)) { parameters.put("subnet_id", neutronView.getCustomSubnetId()); } else { parameters.put("router_id", neutronView.getCustomRouterId()); } } parameters.put("app_net_cidr", isBlank(existingSubnetCidr) ? neutronView.getSubnetCIDR() : existingSubnetCidr); return parameters; } private String formatUserData(String userData) { String[] lines = userData.split("\n"); StringBuilder sb = new StringBuilder(); for (String line : lines) { // be aware of the OpenStack Heat template formatting sb.append(" ").append(line).append("\n"); } return sb.toString(); } public String getTemplate() { try { return freemarkerConfiguration.getTemplate(openStackHeatTemplatePath, "UTF-8").toString(); } catch (IOException e) { throw new CloudConnectorException("can't get openstack heat template", e); } } public static class ModelContext { private Location location; private String stackName; private List<Group> groups; private Image instanceUserData; private boolean existingNetwork; private boolean existingSubnet; private NeutronNetworkView neutronNetworkView; private String templateString; private Map<String, String> tags = new HashMap<>(); public ModelContext withLocation(Location location) { this.location = location; return this; } public ModelContext withStackName(String stackName) { this.stackName = stackName; return this; } public ModelContext withGroups(List<Group> groups) { this.groups = groups; return this; } public ModelContext withInstanceUserData(Image instanceUserData) { this.instanceUserData = instanceUserData; return this; } public ModelContext withExistingNetwork(boolean existingNetwork) { this.existingNetwork = existingNetwork; return this; } public ModelContext withExistingSubnet(boolean existingSubnet) { this.existingSubnet = existingSubnet; return this; } public ModelContext withNeutronNetworkView(NeutronNetworkView neutronNetworkView) { this.neutronNetworkView = neutronNetworkView; return this; } public ModelContext withTemplateString(String templateString) { this.templateString = templateString; return this; } public ModelContext withTags(Map<String, String> tags) { this.tags = tags; return this; } } }