package alien4cloud.rest.orchestrator; import java.util.List; import javax.inject.Inject; import javax.validation.Valid; import org.apache.commons.collections4.CollectionUtils; import org.elasticsearch.index.query.FilterBuilder; import org.hibernate.validator.constraints.NotEmpty; import org.springframework.http.HttpStatus; 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.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import alien4cloud.audit.annotation.Audit; import alien4cloud.dao.model.GetMultipleDataResult; import alien4cloud.model.common.Usage; import alien4cloud.model.orchestrators.Orchestrator; import alien4cloud.model.orchestrators.OrchestratorState; import alien4cloud.model.orchestrators.locations.LocationSupport; import alien4cloud.orchestrators.services.OrchestratorService; import alien4cloud.orchestrators.services.OrchestratorStateService; import alien4cloud.paas.exception.PluginConfigurationException; import alien4cloud.rest.model.RestError; import alien4cloud.rest.model.RestErrorBuilder; import alien4cloud.rest.model.RestErrorCode; import alien4cloud.rest.model.RestResponse; import alien4cloud.rest.model.RestResponseBuilder; import alien4cloud.rest.orchestrator.model.CreateOrchestratorRequest; import alien4cloud.security.AuthorizationUtil; import alien4cloud.utils.ReflectionUtil; 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; /** * Controller to manage orchestrators. */ @Slf4j @RestController @RequestMapping(value = {"/rest/orchestrators", "/rest/v1/orchestrators", "/rest/latest/orchestrators", "/rest/latest/orchestrators", "/rest/latest/orchestrators"}, produces = MediaType.APPLICATION_JSON_VALUE) @Api(value = "Orchestrators", description = "Manages orchestrators.", authorizations = { @Authorization("ADMIN") }, position = 4300) public class OrchestratorController { @Inject private OrchestratorService orchestratorService; @Inject private OrchestratorStateService orchestratorStateService; @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) @ApiOperation(value = "Create a new orchestrators.", authorizations = { @Authorization("ADMIN") }) @ResponseStatus(value = HttpStatus.CREATED) @PreAuthorize("hasAuthority('ADMIN')") @Audit public RestResponse<String> create( @ApiParam(value = "Request for orchestrators creation", required = true) @Valid @RequestBody CreateOrchestratorRequest orchestratorRequest) { String id = orchestratorService.create(orchestratorRequest.getName(), orchestratorRequest.getPluginId(), orchestratorRequest.getPluginBean()); return RestResponseBuilder.<String> builder().data(id).build(); } @ApiOperation(value = "Update the name of an existing orchestrators.", authorizations = { @Authorization("ADMIN") }) @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAuthority('ADMIN')") @Audit public RestResponse<Void> update(@ApiParam(value = "Id of the orchestrators to update.", required = true) @PathVariable @Valid @NotEmpty String id, @ApiParam(value = "Orchestrator update request, representing the fields to updates and their new values.", required = true) @Valid @NotEmpty @RequestBody UpdateOrchestratorRequest request) { Orchestrator orchestrator = orchestratorService.getOrFail(id); String currentName = orchestrator.getName(); ReflectionUtil.mergeObject(request, orchestrator); orchestratorService.ensureNameUnicityAndSave(orchestrator, currentName); return RestResponseBuilder.<Void> builder().build(); } @ApiOperation(value = "Delete an existing orchestrators.", authorizations = { @Authorization("ADMIN") }) @RequestMapping(value = "/{id}", method = RequestMethod.DELETE) @PreAuthorize("hasAuthority('ADMIN')") @Audit public RestResponse<Void> delete(@ApiParam(value = "Id of the orchestrators to delete.", required = true) @PathVariable @Valid @NotEmpty String id) { Orchestrator orchestrator = orchestratorService.getOrFail(id); if (!orchestrator.getState().equals(OrchestratorState.DISABLED)) { return RestResponseBuilder.<Void> builder() .error(RestErrorBuilder.builder(RestErrorCode.ILLEGAL_STATE_OPERATION).message("An activated orchestrator can not be deleted").build()) .build(); } orchestratorService.delete(id); return RestResponseBuilder.<Void> builder().build(); } @ApiOperation(value = "Search for orchestrators.") @RequestMapping(method = RequestMethod.GET) @PreAuthorize("isAuthenticated()") public RestResponse<GetMultipleDataResult<Orchestrator>> search(@ApiParam(value = "Query text.") @RequestParam(required = false) String query, @ApiParam(value = "If true only connected orchestrators will be retrieved.") @RequestParam(required = false, defaultValue = "false") boolean connectedOnly, @ApiParam(value = "Query from the given index.") @RequestParam(required = false, defaultValue = "0") int from, @ApiParam(value = "Maximum number of results to retrieve.") @RequestParam(required = false, defaultValue = "20") int size) { FilterBuilder authorizationFilter = AuthorizationUtil.getResourceAuthorizationFilters(); OrchestratorState filterStatus = connectedOnly ? OrchestratorState.CONNECTED : null; GetMultipleDataResult<Orchestrator> result = orchestratorService.search(query, filterStatus, from, size, authorizationFilter); return RestResponseBuilder.<GetMultipleDataResult<Orchestrator>> builder().data(result).build(); } @ApiOperation(value = "Get an orchestrators from it's id.") @RequestMapping(value = "/{id}", method = RequestMethod.GET) @PreAuthorize("isAuthenticated()") public RestResponse<Orchestrator> get(@ApiParam(value = "Id of the orchestrator to get", required = true) @PathVariable String id) { // check roles on the requested cloud Orchestrator orchestrator = orchestratorService.getOrFail(id); return RestResponseBuilder.<Orchestrator> builder().data(orchestrator).build(); } @ApiOperation(value = "Enable an orchestrator. Creates the instance of orchestrator if not already created.", authorizations = { @Authorization("ADMIN") }) @RequestMapping(value = "/{id}/instance", method = RequestMethod.POST) @PreAuthorize("hasAuthority('ADMIN')") public RestResponse<Void> enable(@ApiParam(value = "Id of the orchestrator to enable", required = true) @PathVariable String id) { Orchestrator orchestrator = orchestratorService.getOrFail(id); try { orchestratorStateService.enable(orchestrator); } catch (PluginConfigurationException e) { log.error("Failed to instanciate orchestrator because of invalid configuration.", e); return RestResponseBuilder.<Void> builder().error(RestErrorBuilder.builder(RestErrorCode.INVALID_PLUGIN_CONFIGURATION) .message("Fail to update cloud configuration because Plugin used is not valid.").build()).build(); } return RestResponseBuilder.<Void> builder().build(); } @ApiOperation(value = "Disable an orchestrator. Destroys the instance of the orchestrator connector.", authorizations = { @Authorization("ADMIN") }) @RequestMapping(value = "/{id}/instance", method = RequestMethod.DELETE) @PreAuthorize("hasAuthority('ADMIN')") public RestResponse<List<Usage>> disable(@ApiParam(value = "Id of the orchestrator to enable", required = true) @PathVariable String id, @ApiParam(value = "This parameter is useful only when trying to disable the orchestrator, if deployments are performed using this orchestrator disable " + "operation will fail unnless the force flag is true", required = false) @RequestParam(required = false, defaultValue = "false") boolean force, @ApiParam(value = "In case an orchestrator with deployment is forced to be disabled, the user may decide to mark all deployments managed " + "by this orchestrator as ended.", required = false) @RequestParam(required = false, defaultValue = "false") boolean clearDeployments) { RestResponseBuilder<List<Usage>> responseBuilder = RestResponseBuilder.<List<Usage>> builder(); Orchestrator orchestrator = orchestratorService.getOrFail(id); List<Usage> usages = orchestratorStateService.disable(orchestrator, force); if (CollectionUtils.isEmpty(usages)) { return responseBuilder.build(); } return responseBuilder.data(usages) .error(new RestError(RestErrorCode.RESOURCE_USED_ERROR.getCode(), "There are actives deployment with the orchestrator. It cannot be disabled.")) .build(); } @ApiOperation(value = "Get information on the locations that an orchestrator can support.", authorizations = { @Authorization("ADMIN") }) @RequestMapping(value = "/{id}/locationsupport", method = RequestMethod.GET) @PreAuthorize("hasAuthority('ADMIN')") public RestResponse<LocationSupport> getLocationSupport( @ApiParam(value = "Id of the orchestrator for which to get location support informations", required = true) @PathVariable String id) { LocationSupport support = orchestratorService.getLocationSupport(id); return RestResponseBuilder.<LocationSupport> builder().data(support).build(); } @ApiOperation(value = "Get information on the artifacts that an orchestrator can support.", authorizations = { @Authorization("ADMIN") }) @RequestMapping(value = "/{id}/artifacts-support", method = RequestMethod.GET) @PreAuthorize("hasAuthority('ADMIN')") public RestResponse<String[]> getArtifactsSupport( @ApiParam(value = "Id of the orchestrator for which to get artifact support informations", required = true) @PathVariable String id) { if (orchestratorService.getArtifactSupport(id) == null || orchestratorService.getArtifactSupport(id).getTypes() == null) { log.error("An orchestrator should have an artifact support information."); return RestResponseBuilder.<String[]> builder().data(null).build(); } String[] supportedArtifacts = orchestratorService.getArtifactSupport(id).getTypes(); return RestResponseBuilder.<String[]> builder().data(supportedArtifacts).build(); } }