package alien4cloud.deployment;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Resource;
import javax.inject.Inject;
import org.alien4cloud.tosca.model.definitions.PropertyValue;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Service;
import com.google.common.collect.Maps;
import alien4cloud.application.ApplicationEnvironmentService;
import alien4cloud.application.ApplicationService;
import alien4cloud.dao.IGenericSearchDAO;
import alien4cloud.dao.model.FacetedSearchResult;
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.Deployment;
import alien4cloud.model.deployment.DeploymentSourceType;
import alien4cloud.model.deployment.DeploymentTopology;
import alien4cloud.model.deployment.IDeploymentSource;
import alien4cloud.model.orchestrators.Orchestrator;
import alien4cloud.model.orchestrators.locations.Location;
import alien4cloud.orchestrators.plugin.IOrchestratorPlugin;
import alien4cloud.orchestrators.services.OrchestratorService;
import alien4cloud.paas.IPaaSCallback;
import alien4cloud.paas.OrchestratorPluginService;
import alien4cloud.paas.exception.EmptyMetaPropertyException;
import alien4cloud.paas.exception.OrchestratorDeploymentIdConflictException;
import alien4cloud.paas.model.PaaSDeploymentLog;
import alien4cloud.paas.model.PaaSDeploymentLogLevel;
import alien4cloud.paas.model.PaaSMessageMonitorEvent;
import alien4cloud.paas.model.PaaSTopologyDeploymentContext;
import alien4cloud.topology.TopologyValidationResult;
import alien4cloud.utils.PropertyUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
/**
* Service responsible for deployment of an application.
*/
@Slf4j
@Service
public class DeployService {
@Resource(name = "alien-monitor-es-dao")
private IGenericSearchDAO alienMonitorDao;
@Resource(name = "alien-es-dao")
private IGenericSearchDAO alienDao;
@Inject
private ApplicationService applicationService;
@Inject
private ApplicationEnvironmentService applicationEnvironmentService;
@Inject
private OrchestratorService orchestratorService;
@Inject
private DeploymentService deploymentService;
@Inject
private OrchestratorPluginService orchestratorPluginService;
@Inject
private DeploymentContextService deploymentContextService;
@Inject
private DeploymentTopologyService deploymentTopologyService;
@Inject
private DeploymentTopologyValidationService deploymentTopologyValidationService;
@Inject
private ArtifactProcessorService artifactProcessorService;
@Inject
private DeploymentInputService deploymentInputService;
/**
* Deploy a topology and return the deployment ID.
*
* @param deploymentTopology Location aware and matched topology.
* @param deploymentSource Application to be deployed or the Csar that contains test toplogy to be deployed
* @return The id of the generated deployment.
*/
public String deploy(final DeploymentTopology deploymentTopology, IDeploymentSource deploymentSource) {
Map<String, String> locationIds = TopologyLocationUtils.getLocationIds(deploymentTopology);
Map<String, Location> locations = deploymentTopologyService.getLocations(locationIds);
final Location firstLocation = locations.values().iterator().next();
// FIXME check that all nodes to match are matched
// FIXME check that all required properties are defined
// TODO DeploymentSetupValidator.validate doesn't check that inputs linked to required properties are indeed configured.
// Get the orchestrator that will perform the deployment
IOrchestratorPlugin orchestratorPlugin = orchestratorPluginService.getOrFail(firstLocation.getOrchestratorId());
String deploymentTopologyId = deploymentTopology.getId();
// Create a deployment object to be kept in ES.
final Deployment deployment = new Deployment();
deployment.setId(UUID.randomUUID().toString());
deployment.setOrchestratorId(firstLocation.getOrchestratorId());
deployment.setLocationIds(locationIds.values().toArray(new String[locationIds.size()]));
deployment.setOrchestratorDeploymentId(generateOrchestratorDeploymentId(deploymentTopology.getEnvironmentId(), firstLocation.getOrchestratorId()));
deployment.setSourceId(deploymentSource.getId());
String sourceName;
if (deploymentSource.getName() == null) {
sourceName = UUID.randomUUID().toString();
} else {
sourceName = deploymentSource.getName();
}
deployment.setSourceName(sourceName);
deployment.setSourceType(DeploymentSourceType.fromSourceType(deploymentSource.getClass()));
deployment.setStartDate(new Date());
// mandatory for the moment since we could have deployment with no environment (csar test)
deployment.setEnvironmentId(deploymentTopology.getEnvironmentId());
deployment.setVersionId(deploymentTopology.getVersionId());
alienDao.save(deployment);
// save the topology as a deployed topology.
// change the Id before saving
deploymentTopology.setId(deployment.getId());
deploymentTopology.setDeployed(true);
alienMonitorDao.save(deploymentTopology);
// put back the old Id for deployment
deploymentTopology.setId(deploymentTopologyId);
// Process all input artifact, replace all artifact inside the topology with input artifact
deploymentInputService.processInputArtifacts(deploymentTopology);
PaaSTopologyDeploymentContext deploymentContext = deploymentContextService.buildTopologyDeploymentContext(deployment, locations, deploymentTopology);
// Download and process all remote artifacts before deployment
artifactProcessorService.processArtifacts(deploymentContext);
// Build the context for deployment and deploy
orchestratorPlugin.deploy(deploymentContext, new IPaaSCallback<Object>() {
@Override
public void onSuccess(Object data) {
log.info("Deployed topology [{}] on location [{}], generated deployment with id [{}]", deploymentTopology.getInitialTopologyId(),
firstLocation.getId(), deployment.getId());
}
@Override
public void onFailure(Throwable t) {
log.error("Deployment failed with cause", t);
PaaSDeploymentLog deploymentLog = new PaaSDeploymentLog();
deploymentLog.setDeploymentId(deployment.getId());
deploymentLog.setDeploymentPaaSId(deployment.getOrchestratorDeploymentId());
deploymentLog.setContent(t.getMessage() + "\n" + ExceptionUtils.getStackTrace(t));
deploymentLog.setLevel(PaaSDeploymentLogLevel.ERROR);
deploymentLog.setTimestamp(new Date());
alienMonitorDao.save(deploymentLog);
PaaSMessageMonitorEvent messageMonitorEvent = new PaaSMessageMonitorEvent();
messageMonitorEvent.setDeploymentId(deploymentLog.getDeploymentId());
messageMonitorEvent.setOrchestratorId(deploymentLog.getDeploymentPaaSId());
messageMonitorEvent.setMessage(t.getMessage());
messageMonitorEvent.setDate(deploymentLog.getTimestamp().getTime());
alienMonitorDao.save(messageMonitorEvent);
}
});
log.debug("Triggered deployment of topology [{}] on location [{}], generated deployment with id [{}]", deploymentTopology.getInitialTopologyId(),
firstLocation.getId(), deployment.getId());
return deployment.getId();
}
/**
* Generate the human readable deployment id for the orchestrator.
*
* @param envId Id of the deployed environment.
* @param orchestratorId Id of the orchestrator on which the deployment is performed.
* @return The orchestrator deployment id.
* @throws alien4cloud.paas.exception.OrchestratorDeploymentIdConflictException
*/
private String generateOrchestratorDeploymentId(String envId, String orchestratorId) throws OrchestratorDeploymentIdConflictException {
log.debug("Generating deployment paaS Id...");
log.debug("All spaces will be replaced by an \"_\" charactr. You might consider it while naming your applications.");
ApplicationEnvironment env = applicationEnvironmentService.getOrFail(envId);
Orchestrator orchestrator = orchestratorService.getOrFail(orchestratorId);
String namePattern = orchestrator.getDeploymentNamePattern();
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(namePattern);
String orchestratorDeploymentId = (String) exp
.getValue(new OrchestratorIdContext(env, applicationService.getOrFail(env.getApplicationId()), namePattern.contains("metaProperties[")));
// ensure that the id is not used by another deployment.
if (deploymentService.isActiveDeployment(orchestratorId, orchestratorDeploymentId)) {
throw new OrchestratorDeploymentIdConflictException("Conflict detected with the generated paasId <" + orchestratorDeploymentId + ">.");
}
return orchestratorDeploymentId;
}
/**
* Performs some actions such as final validation Prepare a deployment topology for deployment.
* Note that this operation is actually changing the content of the deployment topology.
*
* @param deploymentTopology The deployment topology to update for deployment.
* @return The result of the topology validation.
*/
public TopologyValidationResult prepareForDeployment(DeploymentTopology deploymentTopology, ApplicationEnvironment environment) {
// finalize the deploymentTopology for deployment
Map<String, PropertyValue> inputs = deploymentTopologyService.processForDeployment(deploymentTopology, environment);
// perform validation of the processed deployment topology.
return deploymentTopologyValidationService.validateProcessedDeploymentTopology(deploymentTopology, inputs);
}
// Inner class used to build context for generation of the orchestrator id.
@Getter
class OrchestratorIdContext {
private ApplicationEnvironment environment;
private Application application;
private Map<String, String> metaProperties;
private String time;
private Map<String, String> constructMapOfMetaProperties(final Application app) {
Map<String, String[]> filters = new HashMap<String, String[]>();
filters.put("target", new String[] { "application" }); // get all meta properties configuration for applications.
FacetedSearchResult result = alienDao.facetedSearch(MetaPropConfiguration.class, null, filters, null, 0, 20);
MetaPropConfiguration metaProp;
Map<String, String> metaProperties = Maps.newHashMap();
for (int i = 0; i < result.getData().length; i++) {
metaProp = (MetaPropConfiguration) result.getData()[i];
if (app.getMetaProperties().get(metaProp.getId()) != null) {
metaProperties.put(metaProp.getName(), app.getMetaProperties().get(metaProp.getId()));
} else if (!PropertyUtil.setScalarDefaultValueIfNotNull(metaProperties, metaProp.getName(), metaProp.getDefault())) {
throw new EmptyMetaPropertyException("The meta property " + metaProp.getName() + " is null and don't have a default value.");
}
}
return metaProperties;
}
public OrchestratorIdContext(ApplicationEnvironment env, Application app, boolean hasMetaProperties) {
this.environment = env;
this.application = app;
SimpleDateFormat ft = new SimpleDateFormat("yyyyMMddHHmm");
this.time = ft.format(new Date());
if (hasMetaProperties) {
this.metaProperties = constructMapOfMetaProperties(app);
}
}
}
}