package alien4cloud.topology;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import alien4cloud.tosca.model.ArchiveRoot;
import alien4cloud.tosca.parser.ParsingError;
import alien4cloud.tosca.parser.ParsingErrorLevel;
import alien4cloud.tosca.parser.ParsingResult;
import alien4cloud.tosca.parser.impl.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.alien4cloud.tosca.model.templates.*;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import alien4cloud.exception.NotFoundException;
import org.alien4cloud.tosca.model.types.NodeType;
import org.alien4cloud.tosca.model.definitions.Interface;
import org.alien4cloud.tosca.model.definitions.Operation;
import org.alien4cloud.tosca.model.definitions.ScalarPropertyValue;
import alien4cloud.paas.function.FunctionEvaluator;
import alien4cloud.tosca.ToscaUtils;
import alien4cloud.tosca.normative.NormativeComputeConstants;
import com.google.common.collect.Maps;
@Slf4j
public class TopologyUtils {
private TopologyUtils() {
}
/**
* Extract all interfaces that have given operations, and filter out all operations that are not in the includedOperations
*
* @param interfaces interfaces to filter
* @param includedOperations operations that will be included in the result
* @return filter interfaces
*/
public static Map<String, Interface> filterInterfaces(Map<String, Interface> interfaces, Set<String> includedOperations) {
Map<String, Interface> result = Maps.newHashMap();
for (Map.Entry<String, Interface> interfaceEntry : interfaces.entrySet()) {
Map<String, Operation> operations = Maps.newHashMap();
for (Map.Entry<String, Operation> operationEntry : interfaceEntry.getValue().getOperations().entrySet()) {
if (includedOperations.contains(operationEntry.getKey())) {
operations.put(operationEntry.getKey(), operationEntry.getValue());
}
}
if (!operations.isEmpty()) {
Interface inter = new Interface();
inter.setDescription(interfaceEntry.getValue().getDescription());
inter.setOperations(operations);
result.put(interfaceEntry.getKey(), inter);
}
}
return result;
}
/**
* Extract interfaces that have implemented operations only.
*
* @param allInterfaces all interfaces
* @return interfaces that have implemented operations
*/
public static Map<String, Interface> filterAbstractInterfaces(Map<String, Interface> allInterfaces) {
Map<String, Interface> interfaces = Maps.newHashMap();
for (Map.Entry<String, Interface> interfaceEntry : allInterfaces.entrySet()) {
Map<String, Operation> operations = Maps.newHashMap();
for (Map.Entry<String, Operation> operationEntry : interfaceEntry.getValue().getOperations().entrySet()) {
if (operationEntry.getValue().getImplementationArtifact() == null) {
// Don't consider operation which do not have any implementation artifact
continue;
}
operations.put(operationEntry.getKey(), operationEntry.getValue());
}
if (!operations.isEmpty()) {
// At least one operation fulfill the criteria
Interface inter = new Interface();
inter.setDescription(interfaceEntry.getValue().getDescription());
inter.setOperations(operations);
interfaces.put(interfaceEntry.getKey(), inter);
}
}
return interfaces;
}
public static void setNullScalingPolicy(NodeTemplate nodeTemplate, NodeType resourceType) {
// FIXME Workaround to remove default scalable properties from compute
if (ToscaUtils.isFromType(NormativeComputeConstants.COMPUTE_TYPE, resourceType)) {
if (nodeTemplate.getCapabilities() != null) {
Capability scalableCapability = nodeTemplate.getCapabilities().get(NormativeComputeConstants.SCALABLE);
if (scalableCapability != null && scalableCapability.getProperties() != null) {
scalableCapability.getProperties().put(NormativeComputeConstants.SCALABLE_MIN_INSTANCES, null);
scalableCapability.getProperties().put(NormativeComputeConstants.SCALABLE_MAX_INSTANCES, null);
scalableCapability.getProperties().put(NormativeComputeConstants.SCALABLE_DEFAULT_INSTANCES, null);
}
}
}
}
public static ScalingPolicy getScalingPolicy(Capability capability) {
int initialInstances = getScalingProperty(NormativeComputeConstants.SCALABLE_DEFAULT_INSTANCES, capability);
int minInstances = getScalingProperty(NormativeComputeConstants.SCALABLE_MIN_INSTANCES, capability);
int maxInstances = getScalingProperty(NormativeComputeConstants.SCALABLE_MAX_INSTANCES, capability);
return new ScalingPolicy(minInstances, maxInstances, initialInstances);
}
public static int getScalingProperty(String propertyName, Capability capability) {
if (MapUtils.isEmpty(capability.getProperties())) {
throw new NotFoundException("The capability scalable has no defined properties, verify your tosca-normative-type archive");
}
if (!capability.getProperties().containsKey(propertyName)) {
throw new NotFoundException(propertyName + " property is not found in the the capability");
}
// default value is 1
int propertyValue = 1;
String rawPropertyValue = FunctionEvaluator.getScalarValue(capability.getProperties().get(propertyName));
if (StringUtils.isNotBlank(rawPropertyValue)) {
propertyValue = Integer.parseInt(rawPropertyValue);
}
return propertyValue;
}
public static void setScalingProperty(String propertyName, int propertyValue, Capability capability) {
if (MapUtils.isEmpty(capability.getProperties())) {
throw new NotFoundException("The capability scalable has no defined properties, verify your tosca-normative-type archive");
}
capability.getProperties().put(propertyName, new ScalarPropertyValue(String.valueOf(propertyValue)));
}
private static Capability getCapability(Topology topology, String nodeTemplateId, String capabilityName, boolean throwNotFoundException) {
return getCapability(topology.getNodeTemplates(), nodeTemplateId, capabilityName, throwNotFoundException);
}
private static Capability getCapability(Map<String, NodeTemplate> nodes, String nodeTemplateId, String capabilityName, boolean throwNotFoundException) {
NodeTemplate node = nodes.get(nodeTemplateId);
if (node == null) {
if (throwNotFoundException) {
throw new NotFoundException("Node " + nodeTemplateId + " is not found in the topology");
} else {
return null;
}
}
Map<String, Capability> capabilities = node.getCapabilities();
if (MapUtils.isEmpty(capabilities)) {
if (throwNotFoundException) {
throw new NotFoundException("Node " + nodeTemplateId + " does not have any capability");
} else {
return null;
}
}
Capability capability = node.getCapabilities().get(capabilityName);
if (capability == null) {
if (throwNotFoundException) {
throw new NotFoundException("Node " + nodeTemplateId + " does not have the capability scalable");
} else {
return null;
}
}
return capability;
}
public static Capability getScalableCapability(Topology topology, String nodeTemplateId, boolean throwNotFoundException) {
return getCapability(topology, nodeTemplateId, NormativeComputeConstants.SCALABLE, throwNotFoundException);
}
public static Capability getScalableCapability(Map<String, NodeTemplate> nodes, String nodeTemplateId, boolean throwNotFoundException) {
return getCapability(nodes, nodeTemplateId, NormativeComputeConstants.SCALABLE, throwNotFoundException);
}
public static int getAvailableGroupIndex(Topology topology) {
if (topology == null || topology.getGroups() == null) {
return 0;
}
Collection<NodeGroup> nodeGroups = topology.getGroups().values();
LinkedHashSet<Integer> indexSet = new LinkedHashSet<>(nodeGroups.size());
for (int i = 0; i < nodeGroups.size(); i++) {
indexSet.add(i);
}
for (NodeGroup nodeGroup : nodeGroups) {
indexSet.remove(nodeGroup.getIndex());
}
if (indexSet.isEmpty()) {
return nodeGroups.size();
}
return indexSet.iterator().next();
}
private static String toLowerCase(String text) {
return text.substring(0, 1).toLowerCase() + text.substring(1);
}
private static String toUpperCase(String text) {
return text.substring(0, 1).toUpperCase() + text.substring(1);
}
/**
* Construct a relationship name from target and relationship type.
*
* @param type type of the relationship
* @param targetName name of the target
* @return the default constructed name
*/
private static String getRelationShipName(String type, String targetName) {
String[] tokens = type.split("\\.");
if (tokens.length > 1) {
return toLowerCase(tokens[tokens.length - 1]) + toUpperCase(targetName);
} else {
return toLowerCase(type) + toUpperCase(targetName);
}
}
/**
* Update properties in a topology
*/
private static void updateOnNodeTemplateNameChange(String oldNodeTemplateName, String newNodeTemplateName, Topology topology) {
// Output properties
if (topology.getOutputProperties() != null) {
Set<String> oldPropertiesOutputs = topology.getOutputProperties().remove(oldNodeTemplateName);
if (oldPropertiesOutputs != null) {
topology.getOutputProperties().put(newNodeTemplateName, oldPropertiesOutputs);
}
}
// substitution mapping
if (topology.getSubstitutionMapping() != null) {
if (topology.getSubstitutionMapping().getCapabilities() != null) {
for (SubstitutionTarget st : topology.getSubstitutionMapping().getCapabilities().values()) {
if (st.getNodeTemplateName().equals(oldNodeTemplateName)) {
st.setNodeTemplateName(newNodeTemplateName);
}
}
}
if (topology.getSubstitutionMapping().getRequirements() != null) {
for (SubstitutionTarget st : topology.getSubstitutionMapping().getRequirements().values()) {
if (st.getNodeTemplateName().equals(oldNodeTemplateName)) {
st.setNodeTemplateName(newNodeTemplateName);
}
}
}
}
}
/**
* <p>
* Update the name of a node template in the relationships of a topology.
* This requires two operations:
* <ul>
* <li>Rename the target node of a relationship</li>
* <li>If a relationship has an auto-generated id, update it's id to take in account the new target name.</li>
* </ul>
* </p>
*
* @param oldNodeTemplateName Name of the node template that changes.
* @param newNodeTemplateName New name for the node template.
* @param nodeTemplates Map of all node templates in the topology.
*/
public static void refreshNodeTempNameInRelationships(String oldNodeTemplateName, String newNodeTemplateName, Map<String, NodeTemplate> nodeTemplates) {
// node templates copy
for (NodeTemplate nodeTemplate : nodeTemplates.values()) {
if (nodeTemplate.getRelationships() != null) {
refreshNodeTemplateNameInRelationships(oldNodeTemplateName, newNodeTemplateName, nodeTemplate.getRelationships());
}
}
}
private static void refreshNodeTemplateNameInRelationships(String oldNodeTemplateName, String newNodeTemplateName,
Map<String, RelationshipTemplate> relationshipTemplates) {
Map<String, String> updatedKeys = Maps.newHashMap();
for (Map.Entry<String, RelationshipTemplate> relationshipTemplateEntry : relationshipTemplates.entrySet()) {
String relationshipTemplateId = relationshipTemplateEntry.getKey();
RelationshipTemplate relationshipTemplate = relationshipTemplateEntry.getValue();
if (relationshipTemplate.getTarget().equals(oldNodeTemplateName)) {
relationshipTemplate.setTarget(newNodeTemplateName);
String formatedOldNodeName = getRelationShipName(relationshipTemplate.getType(), oldNodeTemplateName);
// if the id/name of the relationship is auto-generated we should update it also as auto-generation is <typeName+targetId>
if (relationshipTemplateId.equals(formatedOldNodeName)) {
String newRelationshipTemplateId = getRelationShipName(relationshipTemplate.getType(), newNodeTemplateName);
// check that the new name is not already used (so we won't override another relationship)...
String validNewRelationshipTemplateId = newRelationshipTemplateId;
int counter = 0;
while (relationshipTemplates.containsKey(validNewRelationshipTemplateId)) {
validNewRelationshipTemplateId = newRelationshipTemplateId + counter;
counter++;
}
updatedKeys.put(relationshipTemplateId, validNewRelationshipTemplateId);
}
}
}
// update the relationship keys if any has been impacted
for (Map.Entry<String, String> updateKeyEntry : updatedKeys.entrySet()) {
RelationshipTemplate relationshipTemplate = relationshipTemplates.remove(updateKeyEntry.getKey());
relationshipTemplates.put(updateKeyEntry.getValue(), relationshipTemplate);
}
}
/**
* Manage node group members when a node name is removed or its name has changed.
*
* @param newName : the new name of the node or <code>null</code> if the node has been removed.
*/
public static void updateGroupMembers(Topology topology, NodeTemplate template, String nodeName, String newName) {
Map<String, NodeGroup> topologyGroups = topology.getGroups();
if (template.getGroups() != null && !template.getGroups().isEmpty() && topologyGroups != null) {
for (String groupId : template.getGroups()) {
NodeGroup nodeGroup = topologyGroups.get(groupId);
if (nodeGroup != null && nodeGroup.getMembers() != null) {
boolean removed = nodeGroup.getMembers().remove(nodeName);
if (removed && newName != null) {
nodeGroup.getMembers().add(newName);
}
}
}
}
}
/**
* Rename formattedOldNodeName node template of a topology.
*
* @param topology
* @param nodeTemplateName
* @param newNodeTemplateName
*/
public static void renameNodeTemplate(Topology topology, String nodeTemplateName, String newNodeTemplateName) {
Map<String, NodeTemplate> nodeTemplates = TopologyServiceCore.getNodeTemplates(topology);
NodeTemplate nodeTemplate = TopologyServiceCore.getNodeTemplate(topology.getId(), nodeTemplateName, nodeTemplates);
nodeTemplate.setName(newNodeTemplateName);
nodeTemplates.put(newNodeTemplateName, nodeTemplate);
nodeTemplates.remove(nodeTemplateName);
refreshNodeTempNameInRelationships(nodeTemplateName, newNodeTemplateName, nodeTemplates);
updateOnNodeTemplateNameChange(nodeTemplateName, newNodeTemplateName, topology);
updateGroupMembers(topology, nodeTemplate, nodeTemplateName, newNodeTemplateName);
}
public static boolean isValidNodeName(String name) {
return TopologyService.NODE_NAME_PATTERN.matcher(name).matches();
}
/**
* In alien 4 Cloud we try
* Rename the node template with an invalid name on the topology.
*
* @param topology
* @param parsedArchive
*/
public static void normalizeAllNodeTemplateName(Topology topology, ParsingResult<ArchiveRoot> parsedArchive) {
if (topology.getNodeTemplates() != null && !topology.getNodeTemplates().isEmpty()) {
Map<String, NodeTemplate> nodeTemplates = Maps.newHashMap(topology.getNodeTemplates());
for (Map.Entry<String, NodeTemplate> nodeEntry : nodeTemplates.entrySet()) {
String nodeName = nodeEntry.getKey();
if (!isValidNodeName(nodeName)) {
String newName = StringUtils.stripAccents(nodeName);
newName = TopologyService.NODE_NAME_REPLACE_PATTERN.matcher(newName).replaceAll("_");
if (topology.getNodeTemplates().containsKey(newName)) {
int i = 1;
while (topology.getNodeTemplates().containsKey(newName + i)) {
i++;
}
newName = newName + i;
}
renameNodeTemplate(topology, nodeName, newName);
if (parsedArchive != null) {
parsedArchive.getContext().getParsingErrors().add(
new ParsingError(ParsingErrorLevel.WARNING, ErrorCode.INVALID_NODE_TEMPLATE_NAME, nodeName, null, nodeName, null, newName));
}
}
}
}
}
}