package alien4cloud.topology.validation; import static alien4cloud.utils.AlienUtils.safe; 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.model.definitions.AbstractPropertyValue; import org.alien4cloud.tosca.model.definitions.ComplexPropertyValue; import org.alien4cloud.tosca.model.definitions.FunctionPropertyValue; import org.alien4cloud.tosca.model.definitions.ListPropertyValue; import org.alien4cloud.tosca.model.definitions.PropertyDefinition; import org.alien4cloud.tosca.model.definitions.ScalarPropertyValue; import org.alien4cloud.tosca.model.templates.Capability; import org.alien4cloud.tosca.model.templates.NodeTemplate; import org.alien4cloud.tosca.model.templates.RelationshipTemplate; import org.alien4cloud.tosca.model.templates.Topology; import org.alien4cloud.tosca.model.types.CapabilityType; 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.elasticsearch.common.collect.Lists; import org.springframework.stereotype.Component; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import alien4cloud.paas.exception.NotSupportedException; import alien4cloud.paas.function.FunctionEvaluator; import alien4cloud.topology.task.PropertiesTask; import alien4cloud.topology.task.ScalableTask; import alien4cloud.topology.task.TaskCode; import alien4cloud.topology.task.TaskLevel; import alien4cloud.tosca.normative.NormativeComputeConstants; import lombok.extern.slf4j.Slf4j; /** * Performs validation of the properties */ @Component @Slf4j public class TopologyPropertiesValidationService { @Resource private IToscaTypeSearchService toscaTypeSearchService; /** * Validate that the properties values in the topology are matching the property definitions (required & constraints). * Skips properties defined as get_input * * @param topology The actual topology to validate. * @return A list tasks to be done to make this topology valid. */ public List<PropertiesTask> validateStaticProperties(Topology topology) { return validateProperties(topology, true); } /** * Validate that the All properties (including dynamic properties (get_input )) values in the topology are matching the property definitions (required & * constraints). * * @param topology The actual topology to validate. * @return A list tasks to be done to make this topology valid. */ public List<PropertiesTask> validateAllProperties(Topology topology) { return validateProperties(topology, false); } /** * Validate properties. * * @param topology * @param skipInputProperties whether to skip input properties validation or not. This is in case inputs are not yet processed * @return */ private List<PropertiesTask> validateProperties(Topology topology, boolean skipInputProperties) { List<PropertiesTask> toReturnTaskList = Lists.newArrayList(); Map<String, NodeTemplate> nodeTemplates = topology.getNodeTemplates(); // create task by nodetemplate for (Map.Entry<String, NodeTemplate> nodeTempEntry : nodeTemplates.entrySet()) { NodeTemplate nodeTemplate = nodeTempEntry.getValue(); NodeType relatedIndexedNodeType = toscaTypeSearchService.getRequiredElementInDependencies(NodeType.class, nodeTemplate.getType(), topology.getDependencies()); // do pass if abstract node if (relatedIndexedNodeType.isAbstract()) { continue; } // Define a task regarding properties PropertiesTask task = new PropertiesTask(); task.setNodeTemplateName(nodeTempEntry.getKey()); task.setComponent(relatedIndexedNodeType); task.setCode(TaskCode.PROPERTIES); task.setProperties(Maps.<TaskLevel, List<String>> newHashMap()); // Check the properties of node template if (MapUtils.isNotEmpty(nodeTemplate.getProperties())) { addRequiredPropertyIdToTaskProperties(null, nodeTemplate.getProperties(), relatedIndexedNodeType.getProperties(), task, skipInputProperties); } // Check relationships PD for (Map.Entry<String, RelationshipTemplate> relationshipEntry : safe(nodeTemplate.getRelationships()).entrySet()) { RelationshipTemplate relationship = relationshipEntry.getValue(); if (relationship.getProperties() == null || relationship.getProperties().isEmpty()) { continue; } addRequiredPropertyIdToTaskProperties("relationships[" + relationshipEntry.getKey() + "]", relationship.getProperties(), getRelationshipPropertyDefinition(topology, nodeTemplate), task, skipInputProperties); } for (Map.Entry<String, Capability> capabilityEntry : safe(nodeTemplate.getCapabilities()).entrySet()) { Capability capability = capabilityEntry.getValue(); if (capability.getProperties() == null || capability.getProperties().isEmpty()) { continue; } addRequiredPropertyIdToTaskProperties("capabilities[" + capabilityEntry.getKey() + "]", capability.getProperties(), getCapabilitiesPropertyDefinition(topology, nodeTemplate), task, skipInputProperties); if (capability.getType().equals(NormativeComputeConstants.SCALABLE_CAPABILITY_TYPE)) { Map<String, AbstractPropertyValue> scalableProperties = capability.getProperties(); verifyScalableProperties(scalableProperties, toReturnTaskList, nodeTempEntry.getKey(), skipInputProperties); } } if (MapUtils.isNotEmpty(task.getProperties())) { toReturnTaskList.add(task); } } return toReturnTaskList.isEmpty() ? null : toReturnTaskList; } private Map<String, PropertyDefinition> getCapabilitiesPropertyDefinition(Topology topology, NodeTemplate nodeTemplate) { Map<String, PropertyDefinition> relatedProperties = Maps.newTreeMap(); for (Map.Entry<String, Capability> capabilityEntry : nodeTemplate.getCapabilities().entrySet()) { CapabilityType indexedCapabilityType = toscaTypeSearchService.getRequiredElementInDependencies(CapabilityType.class, capabilityEntry.getValue().getType(), topology.getDependencies()); if (indexedCapabilityType.getProperties() != null && !indexedCapabilityType.getProperties().isEmpty()) { relatedProperties.putAll(indexedCapabilityType.getProperties()); } } return relatedProperties; } private Map<String, PropertyDefinition> getRelationshipPropertyDefinition(Topology topology, NodeTemplate nodeTemplate) { Map<String, PropertyDefinition> relatedProperties = Maps.newTreeMap(); for (Map.Entry<String, RelationshipTemplate> relationshipTemplateEntry : nodeTemplate.getRelationships().entrySet()) { RelationshipType indexedRelationshipType = toscaTypeSearchService.getRequiredElementInDependencies(RelationshipType.class, relationshipTemplateEntry.getValue().getType(), topology.getDependencies()); if (indexedRelationshipType.getProperties() != null && !indexedRelationshipType.getProperties().isEmpty()) { relatedProperties.putAll(indexedRelationshipType.getProperties()); } } return relatedProperties; } private void verifyScalableProperties(Map<String, AbstractPropertyValue> scalableProperties, List<PropertiesTask> toReturnTaskList, String nodeTemplateId, boolean skipInputProperties) { Set<String> missingProperties = Sets.newHashSet(); Set<String> errorProperties = Sets.newHashSet(); if (skipInputProperties) { for (Entry<String, AbstractPropertyValue> entry : scalableProperties.entrySet()) { if (entry.getValue() instanceof FunctionPropertyValue) { return; } } } if (MapUtils.isEmpty(scalableProperties)) { missingProperties.addAll(Lists.newArrayList(NormativeComputeConstants.SCALABLE_MIN_INSTANCES, NormativeComputeConstants.SCALABLE_MAX_INSTANCES, NormativeComputeConstants.SCALABLE_DEFAULT_INSTANCES)); } else { int min = verifyScalableProperty(scalableProperties, NormativeComputeConstants.SCALABLE_MIN_INSTANCES, missingProperties, errorProperties); int max = verifyScalableProperty(scalableProperties, NormativeComputeConstants.SCALABLE_MAX_INSTANCES, missingProperties, errorProperties); int init = verifyScalableProperty(scalableProperties, NormativeComputeConstants.SCALABLE_DEFAULT_INSTANCES, missingProperties, errorProperties); if (min > 0 && max > 0 && init > 0) { if (min > init || min > max) { errorProperties.add(NormativeComputeConstants.SCALABLE_MIN_INSTANCES); } if (init > max || init < min) { errorProperties.add(NormativeComputeConstants.SCALABLE_DEFAULT_INSTANCES); } if (max < min || max < init) { errorProperties.add(NormativeComputeConstants.SCALABLE_MAX_INSTANCES); } } } if (!missingProperties.isEmpty()) { ScalableTask scalableTask = new ScalableTask(nodeTemplateId); scalableTask.getProperties().put(TaskLevel.REQUIRED, Lists.newArrayList(missingProperties)); toReturnTaskList.add(scalableTask); } if (!errorProperties.isEmpty()) { ScalableTask scalableTask = new ScalableTask(nodeTemplateId); scalableTask.getProperties().put(TaskLevel.ERROR, Lists.newArrayList(errorProperties)); toReturnTaskList.add(scalableTask); } } private int verifyScalableProperty(Map<String, AbstractPropertyValue> scalableProperties, String propertyToVerify, Set<String> missingProperties, Set<String> errorProperties) { String rawValue = null; try { rawValue = FunctionEvaluator.getScalarValue(scalableProperties.get(propertyToVerify)); } catch (NotSupportedException e1) { // the value is a function (get_input normally), this means the input is not yet filled. } if (StringUtils.isEmpty(rawValue)) { missingProperties.add(propertyToVerify); return -1; } int value; try { value = Integer.parseInt(rawValue); } catch (Exception e) { errorProperties.add(propertyToVerify); return -1; } if (value <= 0) { errorProperties.add(propertyToVerify); return -1; } return value; } private void addRequiredPropertyError(PropertiesTask task, String propertyName) { if (!task.getProperties().containsKey(TaskLevel.REQUIRED)) { task.getProperties().put(TaskLevel.REQUIRED, Lists.<String> newArrayList()); } task.getProperties().get(TaskLevel.REQUIRED).add(propertyName); } private void addRequiredPropertyIdToTaskProperties(String prefix, Map<String, AbstractPropertyValue> properties, Map<String, PropertyDefinition> relatedProperties, PropertiesTask task, boolean skipInputProperties) { for (Map.Entry<String, AbstractPropertyValue> propertyEntry : properties.entrySet()) { PropertyDefinition propertyDef = relatedProperties.get(propertyEntry.getKey()); String propertyErrorKey = prefix == null ? propertyEntry.getKey() : prefix + "." + propertyEntry.getKey(); AbstractPropertyValue value = propertyEntry.getValue(); if (propertyDef != null && propertyDef.isRequired()) { if (value == null) { addRequiredPropertyError(task, propertyErrorKey); } else if (value instanceof ScalarPropertyValue) { String propertyValue = ((ScalarPropertyValue) value).getValue(); if (StringUtils.isBlank(propertyValue)) { addRequiredPropertyError(task, propertyErrorKey); } } else if (value instanceof ComplexPropertyValue) { Map<String, Object> mapValue = ((ComplexPropertyValue) value).getValue(); if (MapUtils.isEmpty(mapValue)) { addRequiredPropertyError(task, propertyErrorKey); } } else if (value instanceof ListPropertyValue) { List<Object> listValue = ((ListPropertyValue) value).getValue(); if (listValue.isEmpty()) { addRequiredPropertyError(task, propertyErrorKey); } } else if (skipInputProperties) { // this is a get_input funtion. // get_input Will be validated later on continue; } else { addRequiredPropertyError(task, propertyErrorKey); } } } } }