package alien4cloud.deployment.matching.services.nodes;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.alien4cloud.tosca.model.definitions.AbstractPropertyValue;
import org.alien4cloud.tosca.model.definitions.FilterDefinition;
import org.alien4cloud.tosca.model.definitions.PropertyDefinition;
import org.alien4cloud.tosca.model.definitions.ScalarPropertyValue;
import org.alien4cloud.tosca.model.types.CapabilityType;
import org.alien4cloud.tosca.model.types.NodeType;
import org.springframework.stereotype.Component;
import alien4cloud.deployment.matching.plugins.INodeMatcherPlugin;
import org.alien4cloud.tosca.model.definitions.constraints.IMatchPropertyConstraint;
import alien4cloud.model.deployment.matching.MatchingConfiguration;
import alien4cloud.model.deployment.matching.MatchingFilterDefinition;
import alien4cloud.model.orchestrators.locations.LocationResourceTemplate;
import alien4cloud.model.orchestrators.locations.LocationResources;
import org.alien4cloud.tosca.model.templates.Capability;
import org.alien4cloud.tosca.model.templates.NodeTemplate;
import alien4cloud.tosca.normative.IPropertyType;
import alien4cloud.tosca.normative.ToscaType;
import alien4cloud.tosca.properties.constraints.exception.ConstraintValueDoNotMatchPropertyTypeException;
import alien4cloud.tosca.properties.constraints.exception.ConstraintViolationException;
import com.google.common.collect.Lists;
/**
* Default implementation of INodeMatcherPlugin to be used when no matching plugin has been defined.
*/
@Slf4j
@Component
public class DefaultNodeMatcher implements INodeMatcherPlugin {
// TODO initialize default matching configuration based on parsing a yaml file within a4c for nodes like Compute etc.
/**
* Match a node against a location.
*
* @param nodeTemplate The node template to match.
* @param nodeType The node type that defines the type of the node template to match.
* @param locationResources The resources configured for the location against which we are matching the nodes.
*/
public List<LocationResourceTemplate> matchNode(NodeTemplate nodeTemplate, NodeType nodeType, LocationResources locationResources,
Map<String, MatchingConfiguration> matchingConfigurations) {
List<LocationResourceTemplate> matchingResults = Lists.newArrayList();
List<LocationResourceTemplate> matchedServices = matchServices(nodeTemplate, nodeType, locationResources);
matchingResults.addAll(matchedServices);
List<LocationResourceTemplate> matchedOnDemands = matchedOnDemands(nodeTemplate, nodeType, locationResources, matchingConfigurations);
matchingResults.addAll(matchedOnDemands);
return matchingResults;
}
/**
* Match a node against the services provided by a location.
*
* @param nodeTemplate The node template to match.
* @param nodeType The node type that defines the type of the node template to match.
* @param locationResources The resources configured for the location against which we are matching the nodes.
*/
private List<LocationResourceTemplate> matchServices(NodeTemplate nodeTemplate, NodeType nodeType, LocationResources locationResources) {
// TODO perform service matching
// check if the node template candidate has any specified operation or relation operations if so reject service matching for this node as it is not
// possible to execute operations on services
return Lists.newArrayList();
}
/**
* Match a node against the on demand resources provided by a location.
*
* @param nodeTemplate The node template to match.
* @param nodeType The node type that defines the type of the node template to match.
* @param locationResources The resources configured for the location against which we are matching the nodes.
*/
private List<LocationResourceTemplate> matchedOnDemands(NodeTemplate nodeTemplate, NodeType nodeType, LocationResources locationResources,
Map<String, MatchingConfiguration> matchingConfigurations) {
/*
* TODO Refine node matching by considering specific matching rules for the node. If no constraint is specified in a matching configuration then equals
* constraint is applied.
*/
List<LocationResourceTemplate> matchingResults = Lists.newArrayList();
List<LocationResourceTemplate> candidates = locationResources.getNodeTemplates();
for (LocationResourceTemplate candidate : candidates) {
String candidateTypeName = candidate.getTemplate().getType();
NodeType candidateType = locationResources.getNodeTypes().get(candidateTypeName);
// For the moment only match by node type
if (isValidCandidate(nodeTemplate, nodeType, candidate, candidateType, locationResources.getCapabilityTypes(), matchingConfigurations)) {
matchingResults.add(candidate);
}
}
// TODO Sort the matching results to get the best match for the driver.
return matchingResults;
}
/**
* Checks if the type of a LocationResourceTemplate is matching the expected type.
*
* @param nodeTemplate The node template to match.
* @param nodeType The type of the node template to match.
* @param candidateType The type of the candidate node.
* @param candidate The candidate location resource.
* @param capabilityTypes Map of capability types that may be used by the candidateType.
* @return True if the candidate is a valid match for the node template.
*/
private boolean isValidCandidate(NodeTemplate nodeTemplate, NodeType nodeType, LocationResourceTemplate candidate, NodeType candidateType,
Map<String, CapabilityType> capabilityTypes, Map<String, MatchingConfiguration> matchingConfigurations) {
// Check that the candidate node type is valid
if (!isCandidateTypeValid(nodeTemplate, candidate, candidateType)) {
return false;
}
// The matchingConfigurations can be null when the associate orchestrator is disabled
if (matchingConfigurations == null) {
return false;
}
// Check that the note template properties are matching the constraints specified for matching.
MatchingConfiguration matchingConfiguration = matchingConfigurations.get(candidateType.getElementId());
if (matchingConfiguration == null) {
return true;
}
// create a node filter based on all properties configured on the candidate node
return isTemplatePropertiesMatchCandidateFilters(nodeTemplate, matchingConfiguration, candidate, candidateType, capabilityTypes);
}
private boolean isTemplatePropertiesMatchCandidateFilters(NodeTemplate nodeTemplate, MatchingConfiguration matchingConfiguration,
LocationResourceTemplate candidate, NodeType candidateType, Map<String, CapabilityType> capabilityTypes) {
// check that the node root properties matches the filters defined on the MatchingConfigurations.
if (!isTemplatePropertiesMatchCandidateFilter(nodeTemplate.getProperties(), matchingConfiguration.getProperties(),
candidate.getTemplate().getProperties(), candidateType.getProperties())) {
return false;
}
if(matchingConfiguration.getCapabilities() == null) {
// no constraints on capabilities.
return true;
}
// check that the properties defined on the capabilities matches the filters configured for the capabilities
for (Map.Entry<String, MatchingFilterDefinition> capabilityMatchingFilterEntry : matchingConfiguration.getCapabilities().entrySet()) {
FilterDefinition filterDefinition = new FilterDefinition();
Capability candidateCapability = candidate.getTemplate().getCapabilities().get(capabilityMatchingFilterEntry.getKey());
CapabilityType capabilityType = capabilityTypes.get(candidateCapability.getType());
Capability templateCapability = nodeTemplate.getCapabilities().get(capabilityMatchingFilterEntry.getKey());
if (templateCapability != null
&& !isTemplatePropertiesMatchCandidateFilter(templateCapability.getProperties(), capabilityMatchingFilterEntry.getValue().getProperties(),
candidateCapability.getProperties(), capabilityType.getProperties())) {
return false;
}
}
return true;
}
/**
* Add filters from the matching configuration to the node filter that will be applied for matching only if a value is specified on the configuration
* template.
*
* @param nodeTemplateValues The properties values from the node template to match.
* @param sourceFilters The filtering map (based on constraints) from matching configuration.
* @param propertyValues The values defined on the Location Template.
* @param propertyDefinitions The properties definitions associated with the node.
*/
private boolean isTemplatePropertiesMatchCandidateFilter(Map<String, AbstractPropertyValue> nodeTemplateValues,
Map<String, List<IMatchPropertyConstraint>> sourceFilters, Map<String, AbstractPropertyValue> propertyValues,
Map<String, PropertyDefinition> propertyDefinitions) {
for (Map.Entry<String, List<IMatchPropertyConstraint>> filterEntry : sourceFilters.entrySet()) {
AbstractPropertyValue candidatePropertyValue = propertyValues.get(filterEntry.getKey());
AbstractPropertyValue templatePropertyValue = nodeTemplateValues.get(filterEntry.getKey());
if (candidatePropertyValue != null && candidatePropertyValue instanceof ScalarPropertyValue && templatePropertyValue != null
&& templatePropertyValue instanceof ScalarPropertyValue) {
try {
IPropertyType<?> toscaType = ToscaType.fromYamlTypeName(propertyDefinitions.get(filterEntry.getKey()).getType());
// set the constraint value and add it to the node filter
for (IMatchPropertyConstraint constraint : filterEntry.getValue()) {
constraint.setConstraintValue(toscaType, ((ScalarPropertyValue) candidatePropertyValue).getValue());
try {
constraint.validate(toscaType, ((ScalarPropertyValue) templatePropertyValue).getValue());
} catch (ConstraintViolationException e) {
return false;
}
}
} catch (ConstraintValueDoNotMatchPropertyTypeException e) {
log.debug("The value of property for a constraint is not valid.", e);
}
}
}
return true;
}
/**
* Checks if the type of a LocationResourceTemplate is matching the expected type.
*
* @param nodeTemplate The node template to match.
* @param candidateType The type of the candidate node.
* @param candidate The candidate location resource.
* @return True if the candidate type matches the node template type, false if not.
*/
private boolean isCandidateTypeValid(NodeTemplate nodeTemplate, LocationResourceTemplate candidate, NodeType candidateType) {
return candidateType.getElementId().equals(nodeTemplate.getType())
|| (candidateType.getDerivedFrom() != null && candidateType.getDerivedFrom().contains(nodeTemplate.getType()));
}
}