package alien4cloud.rest.deployment; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Resource; import org.alien4cloud.tosca.model.definitions.*; import org.alien4cloud.tosca.model.templates.NodeTemplate; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import alien4cloud.dao.IGenericSearchDAO; import alien4cloud.deployment.DeploymentService; import alien4cloud.deployment.DeploymentTopologyService; import alien4cloud.exception.NotFoundException; import alien4cloud.model.deployment.Deployment; import alien4cloud.model.deployment.DeploymentTopology; import alien4cloud.paas.model.AbstractMonitorEvent; import alien4cloud.paas.model.PaaSInstancePersistentResourceMonitorEvent; import alien4cloud.topology.TopologyServiceCore; import alien4cloud.tosca.normative.ToscaFunctionConstants; import lombok.extern.slf4j.Slf4j; @Slf4j @Component public class BlockStorageEventHandler extends DeploymentEventHandler { @Resource(name = "alien-monitor-es-dao") private IGenericSearchDAO alienMonitorDao; @Resource private TopologyServiceCore topoServiceCore; @Resource private DeploymentService deploymentService; @Resource private DeploymentTopologyService deploymentTopologyService; @Override public void eventHappened(AbstractMonitorEvent event) { processBlockStorageEvent((PaaSInstancePersistentResourceMonitorEvent) event); } private void processBlockStorageEvent(PaaSInstancePersistentResourceMonitorEvent persistentResourceEvent) { if (persistentResourceEvent.getPersistentProperties() == null || (persistentResourceEvent.getPersistentProperties().isEmpty())) { return; } DeploymentTopology runtimeTopo = alienMonitorDao.findById(DeploymentTopology.class, persistentResourceEvent.getDeploymentId()); Map<String, Object> persistentProperties = persistentResourceEvent.getPersistentProperties(); if (!persistentProperties.isEmpty() && valuesAreAllString(persistentProperties)) { persistentProperties = getAggregatedVolumeIds(runtimeTopo, persistentResourceEvent.getNodeTemplateId(), persistentProperties); if (persistentProperties.isEmpty()) { return; } } updateRuntimeTopology(runtimeTopo, persistentResourceEvent, persistentProperties); updateApplicationTopology(persistentResourceEvent, persistentProperties); } private boolean valuesAreAllString(Map<String, Object> persistentProperties) { for (Object value : persistentProperties.values()) { if (!(value instanceof String)) { return false; } } return true; } private void updateApplicationTopology(PaaSInstancePersistentResourceMonitorEvent persistentResourceEvent, final Map<String, Object> persistentProperties) { Deployment deployment = deploymentService.get(persistentResourceEvent.getDeploymentId()); String deploymentTopologyId = DeploymentTopology.generateId(deployment.getVersionId(), deployment.getEnvironmentId()); DeploymentTopology deploymentTopology = deploymentTopologyService.getOrFail(deploymentTopologyId); // The deployment topology may have changed and the node removed, in such situations there is nothing to update as the block won't be reused. NodeTemplate nodeTemplate; try { nodeTemplate = topoServiceCore.getNodeTemplate(deploymentTopology, persistentResourceEvent.getNodeTemplateId()); } catch (NotFoundException e) { log.warn("Fail to update persistent resource for node {}", persistentResourceEvent.getNodeTemplateId(), e); return; } for (String propertyName : persistentProperties.keySet()) { Object propertyValue = persistentProperties.get(propertyName); AbstractPropertyValue abstractPropertyValue = nodeTemplate.getProperties().get(propertyName); if (abstractPropertyValue != null && abstractPropertyValue instanceof FunctionPropertyValue) { // the value is set in the topology FunctionPropertyValue function = (FunctionPropertyValue) abstractPropertyValue; if (function.getFunction().equals(ToscaFunctionConstants.GET_INPUT) && propertyValue instanceof String) { // the value is set in the input (deployment setup) log.info("Updating deploymentsetup <{}> input properties <{}> to add a new VolumeId", deploymentTopology.getId(), function.getTemplateName()); log.debug("Property <{}> to update: <{}>. New value is <{}>", propertyName, persistentResourceEvent.getPersistentProperties().get(propertyName), propertyValue); deploymentTopology.getInputProperties().put(function.getTemplateName(), new ScalarPropertyValue((String) propertyValue)); } else { // this is not supported / print a warning log.warn("Failed to store the id of the created block storage <{}> for deployment <{}> application <{}> environment <{}>"); return; } } else { log.info("Updating deployment topology: Persistent resource property <{}> for node template <{}.{}> to add a value", propertyName, deploymentTopology.getId(), persistentResourceEvent.getNodeTemplateId()); log.debug("Value to add: <{}>. New value is <{}>", persistentResourceEvent.getPersistentProperties().get(propertyName), propertyValue); nodeTemplate.getProperties().put(propertyName, getPropertyValue(propertyValue)); } } deploymentTopologyService.updateDeploymentTopology(deploymentTopology); } private void updateRuntimeTopology(DeploymentTopology runtimeTopo, PaaSInstancePersistentResourceMonitorEvent persistentResourceEvent, Map<String, Object> persistentProperties) { NodeTemplate nodeTemplate = topoServiceCore.getNodeTemplate(runtimeTopo, persistentResourceEvent.getNodeTemplateId()); log.info("Updating Runtime topology: Storage NodeTemplate <{}.{}> to add a new volumeId", runtimeTopo.getId(), persistentResourceEvent.getNodeTemplateId()); for (String key : persistentProperties.keySet()) { nodeTemplate.getProperties().put(key, getPropertyValue(persistentProperties.get(key))); log.debug("Property <{}> to update: <{}>. New value is <{}>", key, persistentResourceEvent.getPersistentProperties().get(key), persistentProperties.get(key)); } alienMonitorDao.save(runtimeTopo); } private PropertyValue getPropertyValue(Object propertyValue) { if (propertyValue instanceof String) { return new ScalarPropertyValue((String) propertyValue); } else if (propertyValue instanceof Map) { return new ComplexPropertyValue((Map<String, Object>) propertyValue); } else if (propertyValue instanceof List) { return new ListPropertyValue((List<Object>) propertyValue); } log.error("Property value should be a string, a map or a list.", propertyValue); return null; } private Map<String, Object> getAggregatedVolumeIds(DeploymentTopology topology, String nodeTemplateId, Map<String, Object> persistentProperties) { NodeTemplate nodeTemplate; try { nodeTemplate = topoServiceCore.getNodeTemplate(topology, nodeTemplateId); } catch (NotFoundException e) { log.warn("Fail to update volumeIds for node " + nodeTemplateId, e); return null; } Map<String, Object> aggregatedProperties = Maps.newHashMap(); Map<String, Object> concernedNodeProperties = extractConcernedNodeProperties(nodeTemplate.getName(), nodeTemplate.getProperties(), persistentProperties.keySet()); List<Map<String, String>> splittedNodeProperties = splitNodePropertiesValue(concernedNodeProperties); if (!doesPersistentPropertiesAlreadyExist(persistentProperties, splittedNodeProperties)) { aggregatedProperties = buildAggregatedProperties(persistentProperties, concernedNodeProperties); } return aggregatedProperties; } private Map<String, Object> buildAggregatedProperties(Map<String, Object> persistentProperties, Map<String, Object> concernedNodeProperties) { Map<String, Object> aggregatedProperties = Maps.newHashMap(); for (String key : persistentProperties.keySet()) { String existingValues = (String) concernedNodeProperties.get(key); String newValue = (String) persistentProperties.get(key); String aggregatedValue = ""; if (StringUtils.isBlank(existingValues)) { aggregatedValue = newValue; } else { aggregatedValue = existingValues + "," + newValue; } aggregatedProperties.put(key, aggregatedValue); } return aggregatedProperties; } private boolean doesPersistentPropertiesAlreadyExist(Map<String, Object> persistentProperties, List<Map<String, String>> splittedNodeProperties) { int nbProperties = persistentProperties.size(); for (Map<String, String> existing : splittedNodeProperties) { int nbPropertiesEquals = 0; for (String key : persistentProperties.keySet()) { if (StringUtils.equals((String) persistentProperties.get(key), existing.get(key))) { nbPropertiesEquals++; } } if (nbPropertiesEquals == nbProperties) { return true; } } return false; } private List<Map<String, String>> splitNodePropertiesValue(Map<String, Object> concernedNodeProperties) { List<Map<String, String>> list = Lists.newArrayList(); int valueLength = 0; if (concernedNodeProperties.values().iterator().hasNext()) { String plainTextValue = (String) concernedNodeProperties.values().iterator().next(); valueLength = plainTextValue.split(",").length; } for (int i = 0; i < valueLength; i++) { Map<String, String> line = Maps.newHashMap(); for (String key : concernedNodeProperties.keySet()) { String plainValue = (String) concernedNodeProperties.get(key); line.put(key, getValueFromIndex(i, plainValue)); } list.add(line); } return list; } private String getValueFromIndex(int i, String plainValue) { String[] splitted = plainValue.split(","); if (splitted != null && i < splitted.length) { return splitted[i]; } throw new IllegalStateException("Properties do not have the same number of instance values."); } private Map<String, Object> extractConcernedNodeProperties(String nodeName, Map<String, AbstractPropertyValue> nodeProperties, Set<String> keys) { Map<String, Object> extractedMap = Maps.newHashMap(); for (String key : keys) { AbstractPropertyValue abstractPropertyValue = nodeProperties.get(key); if (abstractPropertyValue instanceof ScalarPropertyValue) { String existingValue = ((ScalarPropertyValue) abstractPropertyValue).getValue(); extractedMap.put(key, existingValue); } else { // Can't aggregate values log.warn("Fail to aggregate persistent value for node {} as property key {} is not a scalar type", nodeName, key); return Maps.newHashMap(); } } return extractedMap; } @Override public boolean canHandle(AbstractMonitorEvent event) { return event instanceof PaaSInstancePersistentResourceMonitorEvent; } }