package alien4cloud.deployment; import alien4cloud.common.AlienConstants; import alien4cloud.deployment.matching.services.nodes.NodeMatcherService; import org.alien4cloud.tosca.model.definitions.AbstractPropertyValue; import org.alien4cloud.tosca.model.CSARDependency; import org.alien4cloud.tosca.model.types.NodeType; import alien4cloud.model.deployment.DeploymentTopology; import alien4cloud.model.orchestrators.locations.LocationResourceTemplate; import org.alien4cloud.tosca.model.templates.Capability; import org.alien4cloud.tosca.model.templates.LocationPlacementPolicy; import org.alien4cloud.tosca.model.templates.NodeGroup; import org.alien4cloud.tosca.model.templates.NodeTemplate; import alien4cloud.orchestrators.locations.services.ILocationResourceService; import alien4cloud.topology.TopologyServiceCore; import alien4cloud.utils.PropertyUtil; import com.google.common.base.Predicate; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.apache.commons.collections4.MapUtils; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import javax.inject.Inject; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @Service public class DeploymentNodeSubstitutionService implements IDeploymentNodeSubstitutionService { @Inject private TopologyServiceCore topologyServiceCore; @Inject private NodeMatcherService nodeMatcherService; @Inject @Lazy(true) private ILocationResourceService locationResourceService; /** * Get all available substitutions (LocationResourceTemplate) for the given node templates with given dependencies and location groups * * @param nodeTemplates the node template to check * @param dependencies dependencies of those node templates * @param locationGroups group of location policy * @return a map which contains mapping from node template id to its available substitutions */ private Map<String, List<LocationResourceTemplate>> getAvailableSubstitutions(Map<String, NodeTemplate> nodeTemplates, Set<CSARDependency> dependencies, Map<String, NodeGroup> locationGroups) { Map<String, NodeType> nodeTypes = topologyServiceCore.getIndexedNodeTypesFromDependencies(nodeTemplates, dependencies, false, false, true); Map<String, List<LocationResourceTemplate>> availableSubstitutions = Maps.newHashMap(); for (final Map.Entry<String, NodeGroup> locationGroupEntry : locationGroups.entrySet()) { String groupName = locationGroupEntry.getKey(); final NodeGroup locationNodeGroup = locationGroupEntry.getValue(); Map<String, NodeTemplate> nodesToMatch = Maps.newHashMap(); if (MapUtils.isNotEmpty(nodeTemplates)) { if (AlienConstants.GROUP_ALL.equals(groupName)) { locationNodeGroup.setMembers(nodeTemplates.keySet()); nodesToMatch = nodeTemplates; } else { nodesToMatch = Maps.filterEntries(nodeTemplates, new Predicate<Map.Entry<String, NodeTemplate>>() { @Override public boolean apply(Map.Entry<String, NodeTemplate> input) { return locationNodeGroup.getMembers().contains(input.getKey()); } }); } } LocationPlacementPolicy locationPlacementPolicy = (LocationPlacementPolicy) locationGroupEntry.getValue().getPolicies().get(0); availableSubstitutions.putAll(nodeMatcherService.match(nodeTypes, nodesToMatch, locationPlacementPolicy.getLocationId())); } return availableSubstitutions; } /* (non-Javadoc) * @see alien4cloud.deployment.IDeploymentNodeSubstitutionService#getAvailableSubstitutions(alien4cloud.model.deployment.DeploymentTopology) */ @Override public Map<String, List<LocationResourceTemplate>> getAvailableSubstitutions(DeploymentTopology deploymentTopology) { return getAvailableSubstitutions(deploymentTopology.getOriginalNodes(), deploymentTopology.getDependencies(), deploymentTopology.getLocationGroups()); } /** * Process node substitution for the deployment topology * * @param deploymentTopology the deployment topology to process substitution */ /* (non-Javadoc) * @see alien4cloud.deployment.IDeploymentNodeSubstitutionService#processNodesSubstitution(alien4cloud.model.deployment.DeploymentTopology, java.util.Map) */ @Override public void processNodesSubstitution(DeploymentTopology deploymentTopology, Map<String, NodeTemplate> nodesToMergeProperties) { if (MapUtils.isEmpty(deploymentTopology.getLocationGroups())) { // No location group is defined do nothing return; } deploymentTopology.getDependencies().addAll(deploymentTopology.getLocationDependencies()); Map<String, List<LocationResourceTemplate>> availableSubstitutions = getAvailableSubstitutions(deploymentTopology.getNodeTemplates(), deploymentTopology.getDependencies(), deploymentTopology.getLocationGroups()); Map<String, Set<String>> availableSubstitutionsIds = Maps.newHashMap(); for (Map.Entry<String, List<LocationResourceTemplate>> availableSubstitutionEntry : availableSubstitutions.entrySet()) { Set<String> ids = Sets.newHashSet(); for (LocationResourceTemplate availableSubstitution : availableSubstitutionEntry.getValue()) { ids.add(availableSubstitution.getId()); } availableSubstitutionsIds.put(availableSubstitutionEntry.getKey(), ids); } Map<String, String> substitutedNodes = deploymentTopology.getSubstitutedNodes(); removeUnsynchronizedSubstitutions(deploymentTopology, substitutedNodes, availableSubstitutions); // clean the originalNodes map since some nodes might have been deleted from the initial topology, and thus not appearing in the availableSubstitutions // also update the original node, since it might have changed since it was added Iterator<Entry<String, NodeTemplate>> originalNodesIter = deploymentTopology.getOriginalNodes().entrySet().iterator(); while (originalNodesIter.hasNext()) { Entry<String, NodeTemplate> next = originalNodesIter.next(); if (!availableSubstitutions.containsKey(next.getKey())) { originalNodesIter.remove(); } else { // override with the latest value. next.setValue(deploymentTopology.getNodeTemplates().get(next.getKey())); } } for (Map.Entry<String, List<LocationResourceTemplate>> entry : availableSubstitutions.entrySet()) { // select default values if (!substitutedNodes.containsKey(entry.getKey())) { deploymentTopology.getOriginalNodes().put(entry.getKey(), deploymentTopology.getNodeTemplates().get(entry.getKey())); if (!entry.getValue().isEmpty()) { // Only take the first element as selected if no configuration has been set before substitutedNodes.put(entry.getKey(), entry.getValue().iterator().next().getId()); } } } deploymentTopology.setSubstitutedNodes(substitutedNodes); for (Map.Entry<String, String> substitutedNodeEntry : substitutedNodes.entrySet()) { // Substitute the node template of the topology by those matched NodeTemplate locationNode = locationResourceService.getOrFail(substitutedNodeEntry.getValue()).getTemplate(); NodeTemplate abstractTopologyNode = deploymentTopology.getNodeTemplates().put(substitutedNodeEntry.getKey(), locationNode); NodeTemplate previousNode = null; if (nodesToMergeProperties != null) { previousNode = nodesToMergeProperties.get(substitutedNodeEntry.getKey()); } // TODO define what need to be merged and what not to be merged // Merge name, properties and capability properties locationNode.setName(abstractTopologyNode.getName()); // Also merge relationships locationNode.setRelationships(abstractTopologyNode.getRelationships()); if (MapUtils.isNotEmpty(locationNode.getProperties())) { // Merge properties from previous values Set<String> keysToConsider = locationNode.getProperties().keySet(); Map<String, AbstractPropertyValue> mergedProperties = Maps.newLinkedHashMap(); if (previousNode != null && MapUtils.isNotEmpty(previousNode.getProperties())) { PropertyUtil.mergeProperties(previousNode.getProperties(), mergedProperties, keysToConsider); } // Merge properties from abstract topology if (MapUtils.isNotEmpty(abstractTopologyNode.getProperties())) { PropertyUtil.mergeProperties(abstractTopologyNode.getProperties(), mergedProperties, keysToConsider); } // Merge properties from location resources if (MapUtils.isNotEmpty(locationNode.getProperties())) { PropertyUtil.mergeProperties(locationNode.getProperties(), mergedProperties, keysToConsider); } locationNode.setProperties(mergedProperties); } if (MapUtils.isNotEmpty(locationNode.getCapabilities())) { // The location node is the node from orchestrator which must be a child type of the abstract topology node so should loop on this node to do // not miss any capability for (Map.Entry<String, Capability> locationCapabilityEntry : locationNode.getCapabilities().entrySet()) { Capability locationCapability = locationCapabilityEntry.getValue(); if (MapUtils.isEmpty(locationCapability.getProperties())) { continue; } Set<String> keysToConsider = locationCapability.getProperties().keySet(); Map<String, AbstractPropertyValue> mergedCapabilityProperties = Maps.newLinkedHashMap(); // Merge from previous existing nodes Capability previousCapability = null; if (previousNode != null && MapUtils.isNotEmpty(previousNode.getCapabilities())) { previousCapability = previousNode.getCapabilities().get(locationCapabilityEntry.getKey()); } if (previousCapability != null && MapUtils.isNotEmpty(previousCapability.getProperties())) { PropertyUtil.mergeProperties(previousCapability.getProperties(), mergedCapabilityProperties, keysToConsider); } // Merge from abstract topology Capability abstractCapability = null; if (MapUtils.isNotEmpty(abstractTopologyNode.getCapabilities())) { abstractCapability = abstractTopologyNode.getCapabilities().get(locationCapabilityEntry.getKey()); } if (abstractCapability != null && MapUtils.isNotEmpty(abstractCapability.getProperties())) { PropertyUtil.mergeProperties(abstractCapability.getProperties(), mergedCapabilityProperties, keysToConsider); } // Merge from location resources PropertyUtil.mergeProperties(locationCapability.getProperties(), mergedCapabilityProperties, keysToConsider); locationCapability.setProperties(mergedCapabilityProperties); } } } } /** * This methods checks all the previously configured substitutions and ensures that they are still related to existing node and matching in the topology. * * @param deploymentTopology The deployment topology. * @param substitutedNodes The previous configuration for substitution nodes. * @param availableSubstitutions The substitutions provided by the location's node matching. */ private void removeUnsynchronizedSubstitutions(DeploymentTopology deploymentTopology, Map<String, String> substitutedNodes, Map<String, List<LocationResourceTemplate>> availableSubstitutions) { if (deploymentTopology.getNodeTemplates() == null) { substitutedNodes.clear(); return; } // When the user has removed some mapped nodes from the topology the previous substitution configuration still exits. Iterator<Map.Entry<String, String>> mappingEntryIterator = substitutedNodes.entrySet().iterator(); while (mappingEntryIterator.hasNext()) { Map.Entry<String, String> entry = mappingEntryIterator.next(); if (deploymentTopology.getNodeTemplates().containsKey(entry.getKey())) { // The node is still in the topology but we have to check that the existing substitution value is still a valid option. List<LocationResourceTemplate> availableSubstitutionsForNode = availableSubstitutions.get(entry.getKey()); if (availableSubstitutionsForNode == null) { // no options => remove existing mapping mappingEntryIterator.remove(); } else { boolean substitutedTemplateExist = false; for (LocationResourceTemplate availableSubstitutionForNode : availableSubstitutionsForNode) { if (availableSubstitutionForNode.getId().equals(entry.getValue())) { substitutedTemplateExist = true; break; } } if (!substitutedTemplateExist) { // The mapping do not exist anymore in the match result mappingEntryIterator.remove(); } } } else { // if node is not anymore in the topology just remove the entry mappingEntryIterator.remove(); } } } }