package alien4cloud.paas.plan;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Resource;
import org.alien4cloud.tosca.catalog.index.IToscaTypeSearchService;
import org.alien4cloud.tosca.catalog.repository.CsarFileRepository;
import org.alien4cloud.tosca.model.CSARDependency;
import org.alien4cloud.tosca.model.definitions.ConcatPropertyValue;
import org.alien4cloud.tosca.model.definitions.FunctionPropertyValue;
import org.alien4cloud.tosca.model.definitions.IValue;
import org.alien4cloud.tosca.model.definitions.Interface;
import org.alien4cloud.tosca.model.definitions.Operation;
import org.alien4cloud.tosca.model.definitions.OperationOutput;
import org.alien4cloud.tosca.model.templates.AbstractTemplate;
import org.alien4cloud.tosca.model.templates.Capability;
import org.alien4cloud.tosca.model.templates.NodeGroup;
import org.alien4cloud.tosca.model.templates.NodeTemplate;
import org.alien4cloud.tosca.model.templates.RelationshipTemplate;
import org.alien4cloud.tosca.model.templates.ScalingPolicy;
import org.alien4cloud.tosca.model.templates.Topology;
import org.alien4cloud.tosca.model.types.AbstractInheritableToscaType;
import org.alien4cloud.tosca.model.types.AbstractInstantiableToscaType;
import org.alien4cloud.tosca.model.types.AbstractToscaType;
import org.alien4cloud.tosca.model.types.NodeType;
import org.alien4cloud.tosca.model.types.RelationshipType;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import alien4cloud.exception.AlreadyExistException;
import alien4cloud.exception.NotFoundException;
import alien4cloud.model.components.IndexedModelUtils;
import alien4cloud.paas.IPaaSTemplate;
import alien4cloud.paas.exception.InvalidTopologyException;
import alien4cloud.paas.function.FunctionEvaluator;
import alien4cloud.paas.model.AbstractPaaSTemplate;
import alien4cloud.paas.model.PaaSNodeTemplate;
import alien4cloud.paas.model.PaaSRelationshipTemplate;
import alien4cloud.paas.model.PaaSTopology;
import alien4cloud.rest.utils.JsonUtil;
import alien4cloud.topology.TopologyUtils;
import alien4cloud.tosca.ToscaUtils;
import alien4cloud.tosca.context.ToscaContextual;
import alien4cloud.tosca.normative.NormativeBlockStorageConstants;
import alien4cloud.tosca.normative.NormativeComputeConstants;
import alien4cloud.tosca.normative.NormativeNetworkConstants;
import alien4cloud.tosca.normative.NormativeRelationshipConstants;
import alien4cloud.tosca.normative.ToscaFunctionConstants;
import alien4cloud.utils.AlienUtils;
import alien4cloud.utils.TypeMap;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
/**
* Utility to build an hosted on tree from a topology.
*/
@Component
@Slf4j
public class TopologyTreeBuilderService {
@Resource
private CsarFileRepository repository;
@Resource
private IToscaTypeSearchService toscaTypeSearchService;
public Map<String, PaaSNodeTemplate> buildPaaSNodeTemplates(Topology topology) {
// cache IndexedToscaElements, CloudServiceArchive and ToscaElements to limit queries.
TypeMap cache = new TypeMap();
return buildPaaSNodeTemplates(topology, cache);
}
/**
* Fetch informations from the repository to complete the topology node template informations with additional data such as artifacts paths etc.
*
* @param topology The topology for which to build PaaSNodeTemplate map.
* @return A map of PaaSNodeTemplate that match the one of the NodeTempaltes in the given topology (and filled with artifact paths etc.).
*/
public Map<String, PaaSNodeTemplate> buildPaaSNodeTemplates(Topology topology, TypeMap cache) {
Map<String, PaaSNodeTemplate> nodeTemplates = Maps.newHashMap();
// cache IndexedToscaElements, CloudServiceArchive and ToscaElements to limit queries.
// TypeMap cache = new TypeMap();
// Fill in PaaSNodeTemplate by fetching node types and CSAR path from the repositories.
if (topology.getNodeTemplates() != null) {
for (Entry<String, NodeTemplate> templateEntry : topology.getNodeTemplates().entrySet()) {
NodeTemplate template = templateEntry.getValue();
PaaSNodeTemplate paaSNodeTemplate = new PaaSNodeTemplate(templateEntry.getKey(), template);
fillType(cache, topology, template, paaSNodeTemplate, NodeType.class);
mergeInterfaces(paaSNodeTemplate, template);
if (template.getRelationships() != null) {
for (Map.Entry<String, RelationshipTemplate> relationshipEntry : template.getRelationships().entrySet()) {
RelationshipTemplate relationshipTemplate = relationshipEntry.getValue();
PaaSRelationshipTemplate paaSRelationshipTemplate = new PaaSRelationshipTemplate(relationshipEntry.getKey(), relationshipTemplate,
paaSNodeTemplate.getId());
fillType(cache, topology, relationshipTemplate, paaSRelationshipTemplate, RelationshipType.class);
mergeInterfaces(paaSRelationshipTemplate, relationshipTemplate);
paaSNodeTemplate.getRelationshipTemplates().add(paaSRelationshipTemplate);
}
}
Capability scalableCapability = TopologyUtils.getScalableCapability(topology, templateEntry.getKey(), false);
if (scalableCapability != null) {
ScalingPolicy scalingPolicy = TopologyUtils.getScalingPolicy(scalableCapability);
// A node with a scaling policy 1, 1, 1 is a simple node and so do not set scaling policy
if (!ScalingPolicy.NOT_SCALABLE_POLICY.equals(scalingPolicy)) {
paaSNodeTemplate.setScalingPolicy(scalingPolicy);
}
}
if (topology.getGroups() != null) {
Set<String> nodeGroups = Sets.newHashSet();
for (Map.Entry<String, NodeGroup> groupEntry : topology.getGroups().entrySet()) {
if (groupEntry.getValue().getMembers() != null && groupEntry.getValue().getMembers().contains(templateEntry.getKey())) {
nodeGroups.add(groupEntry.getKey());
}
}
paaSNodeTemplate.setGroups(nodeGroups);
}
nodeTemplates.put(templateEntry.getKey(), paaSNodeTemplate);
}
}
return nodeTemplates;
}
/**
* Get the non-natives node of a topology.
*
* @param topology
* @return a Map of non-natives nodes.
*/
public Map<String, NodeTemplate> getNonNativesNodes(Topology topology) {
TypeMap cache = new TypeMap();
Map<String, NodeTemplate> nonNativesNode = new HashMap<>();
if (topology.getNodeTemplates() != null) {
for (Entry<String, NodeTemplate> templateEntry : topology.getNodeTemplates().entrySet()) {
NodeTemplate template = templateEntry.getValue();
NodeType indexedToscaElement = getToscaType(template.getType(), cache, topology.getDependencies(), NodeType.class);
boolean isCompute = ToscaUtils.isFromType(NormativeComputeConstants.COMPUTE_TYPE, indexedToscaElement);
boolean isNetwork = ToscaUtils.isFromType(NormativeNetworkConstants.NETWORK_TYPE, indexedToscaElement);
boolean isVolume = ToscaUtils.isFromType(NormativeBlockStorageConstants.BLOCKSTORAGE_TYPE, indexedToscaElement);
if (!isCompute && !isNetwork && !isVolume) {
nonNativesNode.put(templateEntry.getKey(), template);
}
}
}
return nonNativesNode;
}
@SneakyThrows
private void mergeInterfaces(AbstractPaaSTemplate pasSTemplate, AbstractTemplate abstractTemplate) {
AbstractToscaType type = pasSTemplate.getIndexedToscaElement();
Map<String, Interface> typeInterfaces = null;
if (type instanceof AbstractInstantiableToscaType) {
typeInterfaces = ((AbstractInstantiableToscaType) type).getInterfaces();
}
Map<String, Interface> templateInterfaces = abstractTemplate.getInterfaces();
// Here merge interfaces: the interface defined in the template should override those from type.
pasSTemplate.setInterfaces(
IndexedModelUtils.mergeInterfaces(JsonUtil.toMap(JsonUtil.toString(typeInterfaces), String.class, Interface.class), templateInterfaces));
}
/**
* Build the topology for deployment on the PaaS.
*
* @param topology The topology.
* @return The parsed topology for the PaaS with.
*/
public PaaSTopology buildPaaSTopology(Topology topology, TypeMap cache) {
return buildPaaSTopology(buildPaaSNodeTemplates(topology, cache));
}
/**
* Build the topology for deployment on the PaaS.
*
* @param topology The topology.
* @return The parsed topology for the PaaS with.
*/
@ToscaContextual
public PaaSTopology buildPaaSTopology(Topology topology) {
return buildPaaSTopology(buildPaaSNodeTemplates(topology, new TypeMap()));
}
/**
* Build the topology for deployment on the PaaS.
*
* @param nodeTemplates The node templates that are part of the topology.
* @return The parsed topology for the PaaS with.
*/
public PaaSTopology buildPaaSTopology(Map<String, PaaSNodeTemplate> nodeTemplates) {
// Build hosted_on tree
List<PaaSNodeTemplate> computes = new ArrayList<PaaSNodeTemplate>();
List<PaaSNodeTemplate> networks = new ArrayList<PaaSNodeTemplate>();
List<PaaSNodeTemplate> volumes = new ArrayList<PaaSNodeTemplate>();
List<PaaSNodeTemplate> nonNatives = new ArrayList<PaaSNodeTemplate>();
Map<String, List<PaaSNodeTemplate>> groups = Maps.newHashMap();
for (Entry<String, PaaSNodeTemplate> entry : nodeTemplates.entrySet()) {
PaaSNodeTemplate paaSNodeTemplate = entry.getValue();
boolean isCompute = ToscaUtils.isFromType(NormativeComputeConstants.COMPUTE_TYPE, paaSNodeTemplate.getIndexedToscaElement());
boolean isNetwork = ToscaUtils.isFromType(NormativeNetworkConstants.NETWORK_TYPE, paaSNodeTemplate.getIndexedToscaElement());
boolean isVolume = ToscaUtils.isFromType(NormativeBlockStorageConstants.BLOCKSTORAGE_TYPE, paaSNodeTemplate.getIndexedToscaElement());
if (isVolume) {
// manage block storage
processBlockStorage(paaSNodeTemplate, nodeTemplates);
volumes.add(paaSNodeTemplate);
} else if (isCompute) {
// manage compute
processNetwork(paaSNodeTemplate, nodeTemplates);
processRelationship(paaSNodeTemplate, nodeTemplates);
computes.add(paaSNodeTemplate);
} else if (isNetwork) {
// manage network
networks.add(paaSNodeTemplate);
} else {
// manage non native
nonNatives.add(paaSNodeTemplate);
processRelationship(paaSNodeTemplate, nodeTemplates);
}
if (entry.getValue().getGroups() != null) {
for (String group : entry.getValue().getGroups()) {
List<PaaSNodeTemplate> currentGroupMembers = groups.get(group);
if (currentGroupMembers == null) {
currentGroupMembers = Lists.newArrayList();
groups.put(group, currentGroupMembers);
}
currentGroupMembers.add(entry.getValue());
}
}
}
// check and register possible operation outputs
processOperationsOutputs(nodeTemplates);
return new PaaSTopology(computes, networks, volumes, nonNatives, nodeTemplates, groups);
}
private void processRelationship(PaaSNodeTemplate paaSNodeTemplate, Map<String, PaaSNodeTemplate> nodeTemplates) {
PaaSRelationshipTemplate hostedOnRelationship = getPaaSRelationshipTemplateFromType(paaSNodeTemplate, NormativeRelationshipConstants.HOSTED_ON);
if (hostedOnRelationship != null) {
String target = hostedOnRelationship.getRelationshipTemplate().getTarget();
PaaSNodeTemplate parent = nodeTemplates.get(target);
parent.getChildren().add(paaSNodeTemplate);
paaSNodeTemplate.setParent(parent);
}
// Relationships are defined from sources to target. We have to make sure that target node also has the relationship injected.
List<PaaSRelationshipTemplate> allRelationships = getPaaSRelationshipsTemplateFromType(paaSNodeTemplate, NormativeRelationshipConstants.ROOT);
for (PaaSRelationshipTemplate relationship : allRelationships) {
// inject the relationship in it's target.
String target = relationship.getRelationshipTemplate().getTarget();
nodeTemplates.get(target).getRelationshipTemplates().add(relationship);
}
}
private void processNetwork(PaaSNodeTemplate paaSNodeTemplate, Map<String, PaaSNodeTemplate> nodeTemplates) {
List<PaaSRelationshipTemplate> networkRelationships = getPaaSRelationshipsTemplateFromType(paaSNodeTemplate, NormativeRelationshipConstants.NETWORK);
List<PaaSNodeTemplate> networks = Lists.newArrayList();
if (networkRelationships != null && !networkRelationships.isEmpty()) {
for (PaaSRelationshipTemplate networkRelationship : networkRelationships) {
String target = networkRelationship.getRelationshipTemplate().getTarget();
PaaSNodeTemplate network = nodeTemplates.get(target);
networks.add(network);
network.setParent(paaSNodeTemplate);
}
}
paaSNodeTemplate.setNetworkNodes(networks);
}
private void processBlockStorage(PaaSNodeTemplate paaSNodeTemplate, Map<String, PaaSNodeTemplate> nodeTemplates) {
PaaSRelationshipTemplate attachTo = getPaaSRelationshipTemplateFromType(paaSNodeTemplate, NormativeRelationshipConstants.ATTACH_TO);
if (attachTo != null) {
String target = attachTo.getRelationshipTemplate().getTarget();
PaaSNodeTemplate parent = nodeTemplates.get(target);
parent.getStorageNodes().add(paaSNodeTemplate);
paaSNodeTemplate.setParent(parent);
}
}
/**
* Get a single relationship from a given type. Note that the relationship MUST be unique, if not we throw an exception as the workflow cannot be generated.
*
* @param paaSNodeTemplate The node for which to get relationship.
* @param type The type of relationship.
* @return The unique relationship that matches the given type.
*/
private PaaSRelationshipTemplate getPaaSRelationshipTemplateFromType(PaaSNodeTemplate paaSNodeTemplate, String type) {
List<PaaSRelationshipTemplate> relationships = getPaaSRelationshipsTemplateFromType(paaSNodeTemplate, type);
if (relationships.size() == 1) {
return relationships.get(0);
}
if (relationships.size() > 1) {
throw new InvalidTopologyException("Relationship that extends <" + type + "> must be unique on a given node.");
}
return null;
}
/**
* Get all relationships from a given type (only if the node is the source of the relationship).
*
* @param paaSNodeTemplate The node.
* @param type The type of relationships to get.
* @return The relationship template
*/
private List<PaaSRelationshipTemplate> getPaaSRelationshipsTemplateFromType(PaaSNodeTemplate paaSNodeTemplate, String type) {
List<PaaSRelationshipTemplate> relationships = Lists.newArrayList();
for (PaaSRelationshipTemplate relationship : paaSNodeTemplate.getRelationshipTemplates()) {
if (relationship.instanceOf(type) && relationship.getSource().equals(paaSNodeTemplate.getId())) {
relationships.add(relationship);
}
}
return relationships;
}
public <V extends AbstractInheritableToscaType> V getToscaType(String type, TypeMap typeMap, Set<CSARDependency> dependencies, Class<V> clazz) {
V indexedToscaElement = typeMap.get(clazz, type);
if (indexedToscaElement == null) {
indexedToscaElement = toscaTypeSearchService.getElementInDependencies(clazz, type, dependencies);
if (indexedToscaElement == null) {
throw new NotFoundException("Type <" + type + "> required in the topology cannot be found in the repository.");
}
typeMap.put(type, indexedToscaElement);
}
return indexedToscaElement;
}
@SuppressWarnings("unchecked")
private <V extends AbstractInheritableToscaType> void fillType(TypeMap typeMap, Topology topology, AbstractTemplate template, IPaaSTemplate<V> paaSTemplate,
Class<V> clazz) {
V indexedToscaElement = getToscaType(template.getType(), typeMap, topology.getDependencies(), clazz);
paaSTemplate.setIndexedToscaElement(indexedToscaElement);
List<String> derivedFroms = indexedToscaElement.getDerivedFrom();
List<V> derivedFromTypes = Lists.newArrayList();
if (derivedFroms != null) {
for (String derivedFrom : derivedFroms) {
derivedFromTypes.add(getToscaType(derivedFrom, typeMap, topology.getDependencies(), clazz));
}
}
paaSTemplate.setDerivedFroms(derivedFromTypes);
try {
Path csarPath = repository.getCSAR(indexedToscaElement.getArchiveName(), indexedToscaElement.getArchiveVersion());
paaSTemplate.setCsarPath(csarPath);
} catch (AlreadyExistException e) {
log.debug("No csarPath for " + indexedToscaElement + "; not setting in " + paaSTemplate);
}
}
/**
* For every templates (nodes and relationship), check the attributes and interfaces operations input parameters for usage of the get_operation_output
* function.
* if found, register the referenced output on the related operation
*
* @param paaSNodeTemplates
*/
private void processOperationsOutputs(final Map<String, PaaSNodeTemplate> paaSNodeTemplates) {
// TODO: try to cache already processed nodes
for (PaaSNodeTemplate paaSNodeTemplate : paaSNodeTemplates.values()) {
processAttributesForOperationOutputs(paaSNodeTemplate, paaSNodeTemplates);
processOperationsInputsForOperationOutputs(paaSNodeTemplate, paaSNodeTemplates);
// do the same for the relationships
for (PaaSRelationshipTemplate paaSRelationshipTemplate : paaSNodeTemplate.getRelationshipTemplates()) {
processAttributesForOperationOutputs(paaSRelationshipTemplate, paaSNodeTemplates);
processOperationsInputsForOperationOutputs(paaSRelationshipTemplate, paaSNodeTemplates);
}
}
}
/**
* Check attributes of a paaSNodeTemplate for get_operation_output usage, and register in the related operation the output name
*
* @param paaSNodeTemplate
* @param paaSNodeTemplates
*/
private void processAttributesForOperationOutputs(final IPaaSTemplate<? extends AbstractInstantiableToscaType> paaSNodeTemplate,
final Map<String, PaaSNodeTemplate> paaSNodeTemplates) {
Map<String, IValue> attributes = paaSNodeTemplate.getIndexedToscaElement().getAttributes();
if (MapUtils.isEmpty(attributes)) {
return;
}
for (Entry<String, IValue> attribute : attributes.entrySet()) {
String name = attribute.getKey();
IValue value = attribute.getValue();
processIValueForOperationOutput(name, value, paaSNodeTemplate, paaSNodeTemplates, true);
}
}
/**
* Check operations input of every operations of all interfaces of a IPaaSTemplate for get_operation_output usage, and register in the related operation
* the output name
*
* @param paaSTemplate
* @param paaSNodeTemplates
*/
private <V extends AbstractInstantiableToscaType> void processOperationsInputsForOperationOutputs(final IPaaSTemplate<V> paaSTemplate,
final Map<String, PaaSNodeTemplate> paaSNodeTemplates) {
Map<String, Interface> interfaces = ((AbstractInstantiableToscaType) paaSTemplate.getIndexedToscaElement()).getInterfaces();
if (interfaces != null) {
for (Interface interfass : interfaces.values()) {
for (Operation operation : interfass.getOperations().values()) {
Map<String, IValue> inputsParams = operation.getInputParameters();
if (inputsParams != null) {
for (Entry<String, IValue> input : inputsParams.entrySet()) {
String name = input.getKey();
IValue value = input.getValue();
processIValueForOperationOutput(name, value, paaSTemplate, paaSNodeTemplates, false);
}
}
}
}
}
}
private <V extends AbstractInstantiableToscaType> void processIValueForOperationOutput(String name, IValue iValue, final IPaaSTemplate<V> paaSTemplate,
final Map<String, PaaSNodeTemplate> paaSNodeTemplates, final boolean fromAttributes) {
if (iValue instanceof FunctionPropertyValue) {
FunctionPropertyValue function = (FunctionPropertyValue) iValue;
if (ToscaFunctionConstants.GET_OPERATION_OUTPUT.equals(function.getFunction())) {
String formatedAttributeName = null;
List<? extends IPaaSTemplate> paaSTemplates = FunctionEvaluator.getPaaSTemplatesFromKeyword(paaSTemplate, function.getTemplateName(),
paaSNodeTemplates);
if (fromAttributes) {
// nodeId:attributeName
formatedAttributeName = AlienUtils.prefixWith(AlienUtils.COLON_SEPARATOR, name, paaSTemplate.getId());
}
registerOperationOutput(paaSTemplates, function.getInterfaceName(), function.getOperationName(), function.getElementNameToFetch(),
formatedAttributeName);
}
} else if (iValue instanceof ConcatPropertyValue) {
ConcatPropertyValue concatFunction = (ConcatPropertyValue) iValue;
for (IValue param : concatFunction.getParameters()) {
processIValueForOperationOutput(name, param, paaSTemplate, paaSNodeTemplates, false);
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private <V extends AbstractInstantiableToscaType> void registerOperationOutput(final List<? extends IPaaSTemplate> paaSTemplates,
final String interfaceName, final String operationName, final String output, final String formatedAttributeName) {
for (IPaaSTemplate<V> paaSTemplate : paaSTemplates) {
if (paaSTemplate.getInterfaces() != null) {
Interface interfass = MapUtils.getObject(paaSTemplate.getInterfaces(), (interfaceName));
if (interfass != null && interfass.getOperations().containsKey(operationName)) {
OperationOutput toAdd = new OperationOutput(output);
if (StringUtils.isNotBlank(formatedAttributeName)) {
toAdd.getRelatedAttributes().add(formatedAttributeName);
}
interfass.getOperations().get(operationName).addOutput(toAdd);
}
}
}
}
}