package alien4cloud.deployment;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Resource;
import org.alien4cloud.tosca.model.definitions.AbstractPropertyValue;
import org.alien4cloud.tosca.model.definitions.FunctionPropertyValue;
import org.alien4cloud.tosca.model.definitions.PropertyValue;
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.Requirement;
import org.alien4cloud.tosca.model.templates.Topology;
import org.apache.commons.collections4.MapUtils;
import org.springframework.stereotype.Service;
import com.google.common.collect.Maps;
import alien4cloud.application.ApplicationService;
import alien4cloud.application.TopologyCompositionService;
import alien4cloud.common.MetaPropertiesService;
import alien4cloud.deployment.matching.services.location.TopologyLocationUtils;
import alien4cloud.model.application.Application;
import alien4cloud.model.application.ApplicationEnvironment;
import alien4cloud.model.common.MetaPropConfiguration;
import alien4cloud.model.deployment.DeploymentTopology;
import alien4cloud.model.orchestrators.locations.Location;
import alien4cloud.orchestrators.locations.services.LocationService;
import alien4cloud.tosca.normative.ToscaFunctionConstants;
import alien4cloud.utils.TagUtil;
import lombok.extern.slf4j.Slf4j;
/**
* Inputs pre processor service manages pre-processing of inputs parameters in a Topology.
*
* Inputs in alien 4 cloud may be stored in the DeploymentSetup or may be injected from the cloud, application or environment (not implemented yet).
*/
@Slf4j
@Service
public class InputsPreProcessorService {
private static final String LOC_META = "loc_meta_";
private static final String APP_META = "app_meta_";
private static final String APP_TAGS = "app_tags_";
@Resource
private LocationService locationService;
@Resource
private ApplicationService applicationService;
@Resource
private MetaPropertiesService metaPropertiesService;
@Resource
private TopologyCompositionService topologyCompositionService;
/**
* Process the get inputs functions of a topology to inject actual input provided by the deployer user (from deployment setup) or from the location
* or application meta-properties.
*
* @param deploymentTopology The deployment setup that contains the input values.
* @param environment The environment instance linked to the deployment setup.
* @param topology The original topology.
* @return The full inputs map used to actually inject inputs. This includes both the user inputs as well as location meta and applications meta and tags.
*/
public Map<String, PropertyValue> injectInputValues(DeploymentTopology deploymentTopology, ApplicationEnvironment environment, Topology topology) {
topologyCompositionService.processTopologyComposition(topology);
Map<String, PropertyValue> inputs = getInputs(deploymentTopology, environment);
if (deploymentTopology.getNodeTemplates() != null) {
for (Entry<String, NodeTemplate> entry : deploymentTopology.getNodeTemplates().entrySet()) {
NodeTemplate nodeTemplate = entry.getValue();
NodeTemplate initialNodeTemplate = topology.getNodeTemplates().get(entry.getKey());
mergeGetInputProperties(initialNodeTemplate.getProperties(), nodeTemplate.getProperties());
processGetInput(inputs, nodeTemplate.getProperties());
// process relationships
if (nodeTemplate.getRelationships() != null) {
for (Entry<String, RelationshipTemplate> relEntry : nodeTemplate.getRelationships().entrySet()) {
RelationshipTemplate relationshipTemplate = relEntry.getValue();
Map<String, AbstractPropertyValue> initialProperties = getInitialRelationshipProperties(relEntry.getKey(), initialNodeTemplate);
mergeGetInputProperties(initialProperties, relationshipTemplate.getProperties());
processGetInput(inputs, relationshipTemplate.getProperties());
}
}
if (nodeTemplate.getCapabilities() != null) {
for (Entry<String, Capability> capaEntry : nodeTemplate.getCapabilities().entrySet()) {
Capability capability = capaEntry.getValue();
Map<String, AbstractPropertyValue> capaInitialProps = getInitialCapabilityProperties(capaEntry.getKey(), initialNodeTemplate);
mergeGetInputProperties(capaInitialProps, capability.getProperties());
processGetInput(inputs, capability.getProperties());
}
}
}
}
return inputs;
}
private void mergeGetInputProperties(Map<String, AbstractPropertyValue> from, Map<String, AbstractPropertyValue> to) {
if (from != null && to != null) {
for (Entry<String, AbstractPropertyValue> entry : to.entrySet()) {
AbstractPropertyValue fromValue = from.get(entry.getKey());
if (fromValue instanceof FunctionPropertyValue) {
entry.setValue(fromValue);
}
}
}
}
private Map<String, AbstractPropertyValue> getInitialRelationshipProperties(String relationShipName, NodeTemplate nodeTemplate) {
Map<String, AbstractPropertyValue> properties = Maps.newHashMap();
if (nodeTemplate.getRelationships() != null && nodeTemplate.getRelationships().get(relationShipName) != null) {
if (nodeTemplate.getRelationships().get(relationShipName).getProperties() != null) {
properties = nodeTemplate.getRelationships().get(relationShipName).getProperties();
}
}
return properties;
}
private Map<String, AbstractPropertyValue> getInitialCapabilityProperties(String capabilityName, NodeTemplate nodeTemplate) {
Map<String, AbstractPropertyValue> properties = Maps.newHashMap();
if (nodeTemplate.getCapabilities() != null && nodeTemplate.getCapabilities().get(capabilityName) != null) {
if (nodeTemplate.getCapabilities().get(capabilityName).getProperties() != null) {
properties = nodeTemplate.getCapabilities().get(capabilityName).getProperties();
}
}
return properties;
}
private Map<String, AbstractPropertyValue> getInitialRequirementProperties(String requirementName, NodeTemplate nodeTemplate) {
Map<String, AbstractPropertyValue> properties = Maps.newHashMap();
if (nodeTemplate.getRequirements() != null && nodeTemplate.getRequirements().get(requirementName) != null) {
if (nodeTemplate.getRequirements().get(requirementName).getProperties() != null) {
properties = nodeTemplate.getRequirements().get(requirementName).getProperties();
}
}
return properties;
}
/**
*
* Set the value of the not found getInput functions to null
*
* @param deploymentTopology
*/
public void setUnprocessedGetInputToNullValue(DeploymentTopology deploymentTopology) {
if (deploymentTopology.getNodeTemplates() != null) {
for (NodeTemplate nodeTemplate : deploymentTopology.getNodeTemplates().values()) {
setUnprocessedGetInputToNullValue(nodeTemplate.getProperties());
if (nodeTemplate.getRelationships() != null) {
for (RelationshipTemplate relationshipTemplate : nodeTemplate.getRelationships().values()) {
setUnprocessedGetInputToNullValue(relationshipTemplate.getProperties());
}
}
if (nodeTemplate.getCapabilities() != null) {
for (Capability capability : nodeTemplate.getCapabilities().values()) {
setUnprocessedGetInputToNullValue(capability.getProperties());
}
}
}
}
}
/**
* Inputs can come from the deployer user (in such situations they are saved in the deployment setup) but also from the location or application
* meta-properties.
* This method creates a unified map of inputs to be injected in deployed applications.
*
* @param deploymentTopology The deployment topology.
* @param environment The environment instance linked to the deployment setup.
* @return A unified map of input for the topology containing the inputs from the deployment setup as well as the ones coming from location or application
* meta-properties.
*/
private Map<String, PropertyValue> getInputs(DeploymentTopology deploymentTopology, ApplicationEnvironment environment) {
// initialize a map with input from the deployment setup
Map<String, PropertyValue> inputs = Maps.newHashMap();
if (!MapUtils.isEmpty(deploymentTopology.getInputProperties())) {
inputs.putAll(deploymentTopology.getInputProperties());
}
// Map id -> value of meta properties from cloud or application.
Map<String, String> metaPropertiesValuesMap = Maps.newHashMap();
Map<String, String> locationIds = TopologyLocationUtils.getLocationIds(deploymentTopology);
if (MapUtils.isNotEmpty(locationIds)) {
Map<String, Location> locations = locationService.getMultiple(locationIds.values());
for (Location location : locations.values()) {
if (MapUtils.isNotEmpty(location.getMetaProperties())) {
metaPropertiesValuesMap.putAll(location.getMetaProperties());
}
}
}
// inputs from the location starts with location meta
prefixAndAddContextInput(deploymentTopology, inputs, InputsPreProcessorService.LOC_META, metaPropertiesValuesMap, true);
// and the ones from the application meta-properties
// meta or tags from application
if (environment.getApplicationId() != null) {
metaPropertiesValuesMap = Maps.newHashMap();
Application application = applicationService.getOrFail(environment.getApplicationId());
if (application.getMetaProperties() != null) {
metaPropertiesValuesMap.putAll(application.getMetaProperties());
}
prefixAndAddContextInput(deploymentTopology, inputs, InputsPreProcessorService.APP_META, metaPropertiesValuesMap, true);
Map<String, String> tags = TagUtil.tagListToMap(application.getTags());
prefixAndAddContextInput(deploymentTopology, inputs, InputsPreProcessorService.APP_TAGS, tags, false);
}
return inputs;
}
/**
* Add the context inputs to the actual deployment inputs by adding the given prefix to the key.
*
* @param deploymentTopology The deployment topology under processing (to know if the input exists and should be added).
* @param inputs The inputs to be used for the topology deployment.
* @param prefix The prefix to be added to the context inputs.
* @param contextInputs The map of inputs from context elements (cloud, application, environment).
*/
private void prefixAndAddContextInput(DeploymentTopology deploymentTopology, Map<String, PropertyValue> inputs, String prefix,
Map<String, String> contextInputs, boolean isMeta) {
if (contextInputs == null || contextInputs.isEmpty()) {
return; // no inputs to add.
}
if (isMeta) {
String[] ids = new String[contextInputs.size()];
int i = 0;
for (String id : contextInputs.keySet()) {
ids[i++] = id;
}
Map<String, MetaPropConfiguration> configurationMap = metaPropertiesService.getByIds(ids);
for (Map.Entry<String, String> contextInputEntry : contextInputs.entrySet()) {
addToInputs(deploymentTopology, inputs, prefix + configurationMap.get(contextInputEntry.getKey()).getName(), contextInputEntry.getValue());
}
} else {
for (Map.Entry<String, String> contextInputEntry : contextInputs.entrySet()) {
addToInputs(deploymentTopology, inputs, prefix + contextInputEntry.getKey(), contextInputEntry.getValue());
}
}
}
private void addToInputs(DeploymentTopology deploymentTopology, Map<String, PropertyValue> inputs, String inputKey, String value) {
if (value != null && deploymentTopology.getInputProperties() != null && deploymentTopology.getInputs().containsKey(inputKey)) {
inputs.put(inputKey, new ScalarPropertyValue(value));
}
}
private void processGetInput(Map<String, PropertyValue> inputs, Map<String, AbstractPropertyValue> properties) {
if (properties != null) {
for (Map.Entry<String, AbstractPropertyValue> propEntry : properties.entrySet()) {
if (propEntry.getValue() instanceof FunctionPropertyValue) {
FunctionPropertyValue function = (FunctionPropertyValue) propEntry.getValue();
if (ToscaFunctionConstants.GET_INPUT.equals(function.getFunction())) {
String inputName = function.getParameters().get(0);
PropertyValue value = inputs.get(inputName);
// if not null, replace the input value. Otherwise, let it as a function for validation purpose later
if (value != null) {
propEntry.setValue(value);
}
} else {
log.warn("Function <{}> detected for property <{}> while only <get_input> should be authorized.", function.getFunction(),
propEntry.getKey());
}
}
}
}
}
private void setUnprocessedGetInputToNullValue(Map<String, AbstractPropertyValue> properties) {
if (properties != null) {
for (Map.Entry<String, AbstractPropertyValue> propEntry : properties.entrySet()) {
if (propEntry.getValue() instanceof FunctionPropertyValue) {
FunctionPropertyValue function = (FunctionPropertyValue) propEntry.getValue();
if (!ToscaFunctionConstants.GET_INPUT.equals(function.getFunction())) {
log.warn("Function <{}> detected for property <{}> while only <get_input> should be authorized. Value will be set to null",
function.getFunction(), propEntry.getKey());
}
propEntry.setValue(null);
}
}
}
}
/**
* Clean input values from the deployment topologies node templates and put back the getinput functions.
*
* @param deploymentTopology The deployment topology to clean.
* @param topology The initial topology.
*/
public void cleanInputValues(DeploymentTopology deploymentTopology, Topology topology) {
topologyCompositionService.processTopologyComposition(topology);
if (deploymentTopology.getNodeTemplates() != null) {
for (Entry<String, NodeTemplate> entry : deploymentTopology.getNodeTemplates().entrySet()) {
NodeTemplate nodeTemplate = entry.getValue();
NodeTemplate initialNodeTemplate = topology.getNodeTemplates().get(entry.getKey());
mergeGetInputProperties(initialNodeTemplate.getProperties(), nodeTemplate.getProperties());
// process relationships
if (nodeTemplate.getRelationships() != null) {
for (Entry<String, RelationshipTemplate> relEntry : nodeTemplate.getRelationships().entrySet()) {
RelationshipTemplate relationshipTemplate = relEntry.getValue();
Map<String, AbstractPropertyValue> initialProperties = getInitialRelationshipProperties(relEntry.getKey(), initialNodeTemplate);
mergeGetInputProperties(initialProperties, relationshipTemplate.getProperties());
}
}
if (nodeTemplate.getCapabilities() != null) {
for (Entry<String, Capability> capaEntry : nodeTemplate.getCapabilities().entrySet()) {
Capability capability = capaEntry.getValue();
Map<String, AbstractPropertyValue> capaInitialProps = getInitialCapabilityProperties(capaEntry.getKey(), initialNodeTemplate);
mergeGetInputProperties(capaInitialProps, capability.getProperties());
}
}
if (nodeTemplate.getRequirements() != null) {
for (Entry<String, Requirement> requirementEntry : nodeTemplate.getRequirements().entrySet()) {
Requirement capability = requirementEntry.getValue();
Map<String, AbstractPropertyValue> reqInitialProps = getInitialRequirementProperties(requirementEntry.getKey(), initialNodeTemplate);
mergeGetInputProperties(reqInitialProps, capability.getProperties());
}
}
}
}
}
}