package alien4cloud.rest.application; import java.util.List; import java.util.Map; import javax.annotation.Resource; import javax.inject.Inject; import javax.validation.Valid; import org.hibernate.validator.constraints.NotBlank; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import com.google.common.collect.Maps; import alien4cloud.application.ApplicationEnvironmentService; import alien4cloud.application.ApplicationService; import alien4cloud.audit.annotation.Audit; import alien4cloud.deployment.DeployService; import alien4cloud.deployment.DeploymentRuntimeService; import alien4cloud.deployment.DeploymentRuntimeStateService; import alien4cloud.deployment.DeploymentService; import alien4cloud.deployment.DeploymentTopologyService; import alien4cloud.deployment.UndeployService; import alien4cloud.deployment.WorkflowExecutionService; import alien4cloud.deployment.model.DeploymentConfiguration; import alien4cloud.exception.AlreadyExistException; import alien4cloud.exception.NotFoundException; import alien4cloud.model.application.Application; import alien4cloud.model.application.ApplicationEnvironment; import alien4cloud.model.deployment.Deployment; import alien4cloud.model.deployment.DeploymentTopology; import alien4cloud.model.orchestrators.locations.Location; import alien4cloud.paas.IPaaSCallback; import alien4cloud.paas.exception.MaintenanceModeException; import alien4cloud.paas.exception.OrchestratorDisabledException; import alien4cloud.paas.exception.PaaSDeploymentException; import alien4cloud.paas.model.DeploymentStatus; import alien4cloud.paas.model.InstanceInformation; import alien4cloud.rest.application.model.DeployApplicationRequest; import alien4cloud.rest.application.model.EnvironmentStatusDTO; import alien4cloud.rest.model.RestError; import alien4cloud.rest.model.RestErrorCode; import alien4cloud.rest.model.RestResponse; import alien4cloud.rest.model.RestResponseBuilder; import alien4cloud.security.AuthorizationUtil; import alien4cloud.security.model.ApplicationEnvironmentRole; import alien4cloud.security.model.DeployerRole; import alien4cloud.topology.TopologyValidationResult; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import io.swagger.annotations.Authorization; import lombok.extern.slf4j.Slf4j; @Slf4j @RestController @RequestMapping({ "/rest/applications", "/rest/v1/applications", "/rest/latest/applications" }) @Api(value = "", description = "Manage opertions on deployed application.") public class ApplicationDeploymentController { @Resource private ApplicationService applicationService; @Resource private ApplicationEnvironmentService applicationEnvironmentService; @Resource private DeploymentService deploymentService; @Inject private DeployService deployService; @Inject private DeploymentTopologyService deploymentTopologyService; @Inject private UndeployService undeployService; @Inject private DeploymentRuntimeStateService deploymentRuntimeStateService; @Inject private DeploymentRuntimeService deploymentRuntimeService; @Inject private WorkflowExecutionService workflowExecutionService; /** * Trigger deployment of the application on the current configured PaaS. * * @param deployApplicationRequest application details for deployment (applicationId + deploymentProperties) * @return An empty rest response. */ @ApiOperation(value = "Deploys the application on the configured Cloud.", notes = "Application role required [ APPLICATION_MANAGER | APPLICATION_DEVOPS ] and Application environment role required [ DEPLOYMENT_MANAGER ]") @RequestMapping(value = "/deployment", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<?> deploy(@Valid @RequestBody DeployApplicationRequest deployApplicationRequest) { String applicationId = deployApplicationRequest.getApplicationId(); String environmentId = deployApplicationRequest.getApplicationEnvironmentId(); Application application = applicationService.checkAndGetApplication(applicationId); ApplicationEnvironment environment = applicationEnvironmentService.getEnvironmentByIdOrDefault(applicationId, environmentId); if (!environment.getApplicationId().equals(applicationId)) { throw new NotFoundException("Unable to find environment with id <" + environmentId + "> for application <" + applicationId + ">"); } // Security check user must be authorized to deploy the environment (or be application manager) AuthorizationUtil.checkAuthorizationForEnvironment(application, environment); // check that the environment is not already deployed boolean isEnvironmentDeployed = applicationEnvironmentService.isDeployed(environment.getId()); if (isEnvironmentDeployed) { throw new AlreadyExistException("Environment with id <" + environmentId + "> for application <" + applicationId + "> is already deployed"); } // Get the deployment configurations DeploymentConfiguration deploymentConfiguration = deploymentTopologyService.getDeploymentConfiguration(environment.getId()); DeploymentTopology deploymentTopology = deploymentConfiguration.getDeploymentTopology(); // Check authorization on the location // get the target locations of the deployment topology Map<String, Location> locationMap = deploymentTopologyService.getLocations(deploymentTopology); for (Location location : locationMap.values()) { AuthorizationUtil.checkAuthorizationForLocation(location, DeployerRole.DEPLOYER); } // prepare the deployment TopologyValidationResult validation = deployService.prepareForDeployment(deploymentTopology, environment); // if not valid, then return validation errors if (!validation.isValid()) { return RestResponseBuilder.<TopologyValidationResult> builder() .error(new RestError(RestErrorCode.INVALID_DEPLOYMENT_TOPOLOGY.getCode(), "The deployment topology for the application <" + application.getName() + "> on the environment <" + environment.getName() + "> is not valid.")) .data(validation).build(); } // process with the deployment deployService.deploy(deploymentTopology, application); // TODO OrchestratorDisabledException handling in the ExceptionHandler // return RestResponseBuilder.<Void> builder().error( // new RestError(RestErrorCode.CLOUD_DISABLED_ERROR.getCode(), "Cloud with id <" + environment.getCloudId() + "> is disabled or not found")) // .build(); return RestResponseBuilder.<Void> builder().build(); } /** * Trigger un-deployment of the application for a given environment on the current configured PaaS. * * @param applicationId The id of the application to undeploy. * @return An empty rest response. */ @ApiOperation(value = "Un-Deploys the application on the configured PaaS.", notes = "The logged-in user must have the [ APPLICATION_MANAGER ] role for this application. Application environment role required [ DEPLOYMENT_MANAGER ]") @RequestMapping(value = "/{applicationId:.+}/environments/{applicationEnvironmentId}/deployment", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<Void> undeploy(@PathVariable String applicationId, @PathVariable String applicationEnvironmentId) { ApplicationEnvironment environment = applicationEnvironmentService.getEnvironmentByIdOrDefault(applicationId, applicationEnvironmentId); Application application = applicationService.checkAndGetApplication(applicationId); AuthorizationUtil.checkAuthorizationForEnvironment(application, environment); try { undeployService.undeployEnvironment(applicationEnvironmentId); } catch (OrchestratorDisabledException e) { return RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.CLOUD_DISABLED_ERROR.getCode(), e.getMessage())).build(); } return RestResponseBuilder.<Void> builder().build(); } /** * Get only the active deployment for the given application on the given cloud * * @param applicationId id of the topology * @return the active deployment */ @ApiOperation(value = "Get active deployment for the given application on the given cloud.", notes = "Application role required [ APPLICATION_MANAGER | APPLICATION_DEVOPS ] and Application environment role required [ DEPLOYMENT_MANAGER ]") @RequestMapping(value = "/{applicationId:.+}/environments/{applicationEnvironmentId}/active-deployment", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") public RestResponse<Deployment> getActiveDeployment(@PathVariable String applicationId, @PathVariable String applicationEnvironmentId) { Application application = applicationService.checkAndGetApplication(applicationId); // get the topology from the version and the cloud from the environment ApplicationEnvironment environment = applicationEnvironmentService.getEnvironmentByIdOrDefault(application.getId(), applicationEnvironmentId); AuthorizationUtil.checkAuthorizationForEnvironment(application, environment, ApplicationEnvironmentRole.APPLICATION_USER); Deployment deployment = deploymentService.getActiveDeployment(environment.getId()); return RestResponseBuilder.<Deployment> builder().data(deployment).build(); } @ApiOperation(value = "Get the deployment status for the environements that the current user is allowed to see for a given application.", notes = "Returns the current status of an application list from the PaaS it is deployed on for all environments.") @RequestMapping(value = "/statuses", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") public RestResponse<Map<String, Map<String, EnvironmentStatusDTO>>> getApplicationsStatuses(@RequestBody List<String> applicationIds) { Map<String, Map<String, EnvironmentStatusDTO>> statuses = Maps.newHashMap(); for (String applicationId : applicationIds) { Map<String, EnvironmentStatusDTO> environmentStatuses = Maps.newHashMap(); Application application = applicationService.checkAndGetApplication(applicationId); // get all environments status for the current application ApplicationEnvironment[] environments = applicationEnvironmentService.getByApplicationId(application.getId()); for (ApplicationEnvironment env : environments) { if (AuthorizationUtil.hasAuthorizationForEnvironment(application, env, ApplicationEnvironmentRole.values())) { DeploymentStatus status = DeploymentStatus.UNKNOWN; try { status = applicationEnvironmentService.getStatus(env); } catch (Exception e) { log.debug("Getting status for the environment <" + env.getId() + "> failed because the associated orchestrator seems disabled. Returned status is UNKNOWN.", e); } // TODO: include environment roles in the DTO to help display on ui environmentStatuses.put(env.getId(), new EnvironmentStatusDTO(env.getName(), status)); } } statuses.put(applicationId, environmentStatuses); } return RestResponseBuilder.<Map<String, Map<String, EnvironmentStatusDTO>>> builder().data(statuses).build(); } /** * Get detailed informations for every instances of every node of the application on the PaaS. * * @param applicationId The id of the application to be deployed. * @return A {@link RestResponse} that contains detailed informations (See {@link InstanceInformation}) of the application on the PaaS it is deployed. */ @ApiOperation(value = "Get detailed informations for every instances of every node of the application on the PaaS.", notes = "Application role required [ APPLICATION_MANAGER | APPLICATION_DEVOPS ] and Application environment role required [ APPLICATION_USER | DEPLOYMENT_MANAGER ]") @RequestMapping(value = "/{applicationId:.+}/environments/{applicationEnvironmentId}/deployment/informations", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") public DeferredResult<RestResponse<Map<String, Map<String, InstanceInformation>>>> getInstanceInformation(@PathVariable String applicationId, @PathVariable String applicationEnvironmentId) { Application application = applicationService.checkAndGetApplication(applicationId); ApplicationEnvironment environment = applicationEnvironmentService.getEnvironmentByIdOrDefault(application.getId(), applicationEnvironmentId); AuthorizationUtil.checkAuthorizationForEnvironment(application, environment, ApplicationEnvironmentRole.values()); Deployment deployment = applicationEnvironmentService.getActiveDeployment(environment.getId()); final DeferredResult<RestResponse<Map<String, Map<String, InstanceInformation>>>> instancesDeferredResult = new DeferredResult<>(5L * 60L * 1000L); if (deployment == null) { // if there is no topology associated with the version it could not have been deployed. instancesDeferredResult.setResult(RestResponseBuilder.<Map<String, Map<String, InstanceInformation>>> builder().build()); } else { try { deploymentRuntimeStateService.getInstancesInformation(deployment, new IPaaSCallback<Map<String, Map<String, InstanceInformation>>>() { @Override public void onSuccess(Map<String, Map<String, InstanceInformation>> data) { instancesDeferredResult.setResult(RestResponseBuilder.<Map<String, Map<String, InstanceInformation>>> builder().data(data).build()); } @Override public void onFailure(Throwable throwable) { instancesDeferredResult.setErrorResult(throwable); } }); } catch (OrchestratorDisabledException e) { log.error("Cannot get instance informations as topology plugin cannot be found.", e); instancesDeferredResult.setResult(RestResponseBuilder.<Map<String, Map<String, InstanceInformation>>> builder().build()); } } return instancesDeferredResult; } @RequestMapping(value = "/{applicationId:.+}/environments/{applicationEnvironmentId}/deployment/maintenance", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<Void> switchMaintenanceModeOn(@PathVariable String applicationId, @PathVariable String applicationEnvironmentId) { ApplicationEnvironment environment = getAppEnvironmentAndCheckAuthorization(applicationId, applicationEnvironmentId); try { deploymentRuntimeService.switchMaintenanceMode(environment.getId(), true); } catch (OrchestratorDisabledException e) { return RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.CLOUD_DISABLED_ERROR.getCode(), e.getMessage())).build(); } catch (MaintenanceModeException e) { return RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.MAINTENANCE_MODE_ERROR.getCode(), e.getMessage())).build(); } return RestResponseBuilder.<Void> builder().build(); } @RequestMapping(value = "/{applicationId:.+}/environments/{applicationEnvironmentId}/deployment/maintenance", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<Void> switchMaintenanceModeOff(@PathVariable String applicationId, @PathVariable String applicationEnvironmentId) { ApplicationEnvironment environment = getAppEnvironmentAndCheckAuthorization(applicationId, applicationEnvironmentId); try { deploymentRuntimeService.switchMaintenanceMode(environment.getId(), false); } catch (OrchestratorDisabledException e) { return RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.CLOUD_DISABLED_ERROR.getCode(), e.getMessage())).build(); } catch (MaintenanceModeException e) { return RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.MAINTENANCE_MODE_ERROR.getCode(), e.getMessage())).build(); } return RestResponseBuilder.<Void> builder().build(); } @RequestMapping(value = "/{applicationId:.+}/environments/{applicationEnvironmentId}/deployment/{nodeTemplateId}/{instanceId}/maintenance", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<Void> switchInstanceMaintenanceModeOn(@PathVariable String applicationId, @PathVariable String applicationEnvironmentId, @PathVariable String nodeTemplateId, @PathVariable String instanceId) { ApplicationEnvironment environment = getAppEnvironmentAndCheckAuthorization(applicationId, applicationEnvironmentId); try { deploymentRuntimeService.switchInstanceMaintenanceMode(environment.getId(), nodeTemplateId, instanceId, true); } catch (OrchestratorDisabledException e) { return RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.CLOUD_DISABLED_ERROR.getCode(), e.getMessage())).build(); } catch (MaintenanceModeException e) { return RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.MAINTENANCE_MODE_ERROR.getCode(), e.getMessage())).build(); } return RestResponseBuilder.<Void> builder().build(); } @RequestMapping(value = "/{applicationId:.+}/environments/{applicationEnvironmentId}/deployment/{nodeTemplateId}/{instanceId}/maintenance", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public RestResponse<Void> switchInstanceMaintenanceModeOff(@PathVariable String applicationId, @PathVariable String applicationEnvironmentId, @PathVariable String nodeTemplateId, @PathVariable String instanceId) { ApplicationEnvironment environment = getAppEnvironmentAndCheckAuthorization(applicationId, applicationEnvironmentId); try { deploymentRuntimeService.switchInstanceMaintenanceMode(environment.getId(), nodeTemplateId, instanceId, false); } catch (OrchestratorDisabledException e) { return RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.CLOUD_DISABLED_ERROR.getCode(), e.getMessage())).build(); } catch (MaintenanceModeException e) { return RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.MAINTENANCE_MODE_ERROR.getCode(), e.getMessage())).build(); } return RestResponseBuilder.<Void> builder().build(); } private ApplicationEnvironment getAppEnvironmentAndCheckAuthorization(String applicationId, String applicationEnvironmentId) { Application application = applicationService.checkAndGetApplication(applicationId); ApplicationEnvironment environment = applicationEnvironmentService.getEnvironmentByIdOrDefault(application.getId(), applicationEnvironmentId); AuthorizationUtil.checkAuthorizationForEnvironment(application, environment); return environment; } /** * Scale an application on a particular node. * * @param applicationId The id of the application to be scaled. * @param nodeTemplateId The id of the node template to be scaled. * @param instances The instances number to be scaled up (if > 0)/ down (if < 0) * @return A {@link RestResponse} that contains the application's current {@link DeploymentStatus}. */ @ApiOperation(value = "Scale the application on a particular node.", notes = "Returns the detailed informations of the application on the PaaS it is deployed." + " Application role required [ APPLICATION_MANAGER | APPLICATION_DEVOPS ] and Application environment role required [ DEPLOYMENT_MANAGER ]") @RequestMapping(value = "/{applicationId:.+}/environments/{applicationEnvironmentId}/scale/{nodeTemplateId}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public DeferredResult<RestResponse<Void>> scale(@PathVariable String applicationId, @PathVariable String applicationEnvironmentId, @PathVariable String nodeTemplateId, @RequestParam int instances) { final DeferredResult<RestResponse<Void>> result = new DeferredResult<>(15L * 60L * 1000L); ApplicationEnvironment environment = getAppEnvironmentAndCheckAuthorization(applicationId, applicationEnvironmentId); try { deploymentRuntimeService.scale(environment.getId(), nodeTemplateId, instances, new IPaaSCallback<Object>() { @Override public void onSuccess(Object data) { result.setResult(RestResponseBuilder.<Void> builder().build()); } @Override public void onFailure(Throwable e) { result.setErrorResult( RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.SCALING_ERROR.getCode(), e.getMessage())).build()); } }); } catch (OrchestratorDisabledException e) { result.setErrorResult( RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.CLOUD_DISABLED_ERROR.getCode(), e.getMessage())).build()); } catch (PaaSDeploymentException e) { result.setErrorResult(RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.SCALING_ERROR.getCode(), e.getMessage())).build()); } return result; } @ApiOperation(value = "Launch a given workflow.", authorizations = { @Authorization("ADMIN"), @Authorization("APPLICATION_MANAGER") }) @RequestMapping(value = "/{applicationId:.+}/environments/{applicationEnvironmentId}/workflows/{workflowName}", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("isAuthenticated()") @Audit public DeferredResult<RestResponse<Void>> launchWorkflow( @ApiParam(value = "Application id.", required = true) @Valid @NotBlank @PathVariable String applicationId, @ApiParam(value = "Deployment id.", required = true) @Valid @NotBlank @PathVariable String applicationEnvironmentId, @ApiParam(value = "Workflow name.", required = true) @Valid @NotBlank @PathVariable String workflowName) { final DeferredResult<RestResponse<Void>> result = new DeferredResult<>(15L * 60L * 1000L); ApplicationEnvironment environment = getAppEnvironmentAndCheckAuthorization(applicationId, applicationEnvironmentId); // TODO merge with incoming params Map<String, Object> params = Maps.newHashMap(); try { workflowExecutionService.launchWorkflow(environment.getId(), workflowName, params, new IPaaSCallback<Object>() { @Override public void onSuccess(Object data) { result.setResult(RestResponseBuilder.<Void> builder().build()); } @Override public void onFailure(Throwable e) { result.setErrorResult( RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.SCALING_ERROR.getCode(), e.getMessage())).build()); } }); } catch (OrchestratorDisabledException e) { result.setErrorResult( RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.CLOUD_DISABLED_ERROR.getCode(), e.getMessage())).build()); } catch (PaaSDeploymentException e) { result.setErrorResult(RestResponseBuilder.<Void> builder().error(new RestError(RestErrorCode.SCALING_ERROR.getCode(), e.getMessage())).build()); } return result; } }