package com.griddynamics.jagger.jaas.rest; import static com.griddynamics.jagger.jaas.storage.model.TestEnvironmentEntity.TestEnvironmentStatus.PENDING; import static org.springframework.http.HttpStatus.OK; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.web.servlet.support.ServletUriComponentsBuilder.fromCurrentRequest; import com.griddynamics.jagger.jaas.exceptions.ResourceAlreadyExistsException; import com.griddynamics.jagger.jaas.exceptions.ResourceNotFoundException; import com.griddynamics.jagger.jaas.exceptions.TestEnvironmentInvalidIdException; import com.griddynamics.jagger.jaas.exceptions.TestEnvironmentSessionNotFoundException; import com.griddynamics.jagger.jaas.exceptions.WrongTestEnvironmentStatusException; import com.griddynamics.jagger.jaas.service.TestEnvironmentService; import com.griddynamics.jagger.jaas.service.TestExecutionService; import com.griddynamics.jagger.jaas.storage.model.TestEnvUtils; import com.griddynamics.jagger.jaas.storage.model.TestEnvironmentEntity; import com.griddynamics.jagger.jaas.storage.model.TestExecutionEntity; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import io.swagger.annotations.ResponseHeader; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; @RestController @RequestMapping(value = "/envs") @Api(description = "Jagger Test Environments API. It provides endpoints for reading, creating and updating Test Environments. " + "Deleting is performed automatically by cleaning job. Expiration time of environments is set by property 'environments.ttl.minutes'. " + "This API is user by Jagger load generation components for communication with JaaS. It allows JaaS to monitor running test " + "environments and send commands to these environments. " + "Test Environments API is not intended for manual usage") public class TestEnvironmentRestController extends AbstractController { private static final String ENV_ID_PATTERN = "^[a-zA-Z0-9\\._\\-]{1,249}$"; @Value("${environments.ttl.minutes}") private int environmentsTtlMinutes; private final TestEnvironmentService testEnvService; private final TestExecutionService testExecutionService; @Autowired public TestEnvironmentRestController(TestEnvironmentService testEnvironmentService, TestExecutionService testExecutionService) { this.testEnvService = testEnvironmentService; this.testExecutionService = testExecutionService; } @GetMapping(value = "/{envId}", produces = APPLICATION_JSON_VALUE) @ApiOperation(response = TestEnvironmentEntity.class, value = "Retrieves Test Environment by envId.") @ApiResponses(value = { @ApiResponse(code = 200, message = "Test Environment is found.", responseHeaders = @ResponseHeader(name = TestEnvUtils.EXPIRES_HEADER, description = "This header stores expiration time of Test Environment.", response = ZonedDateTime.class)), @ApiResponse(code = 404, message = "Test Environment with provided envId is not found.")}) public ResponseEntity<TestEnvironmentEntity> getTestEnvironment(@PathVariable String envId, HttpServletResponse response) { ResponseEntity<TestEnvironmentEntity> responseEntity = produceGetResponse(testEnvService, function -> testEnvService.read(envId)); if (responseEntity.getStatusCode() == OK) setExpiresHeader(response, responseEntity.getBody()); return responseEntity; } @GetMapping(produces = APPLICATION_JSON_VALUE) @ApiOperation(response = TestEnvironmentEntity.class, responseContainer = "List", value = "Retrieves all Test Environments.") public ResponseEntity<List<TestEnvironmentEntity>> getTestEnvironments() { return produceGetResponse(testEnvService, function -> testEnvService.readAll()); } @PutMapping(value = "/{envId}", consumes = APPLICATION_JSON_VALUE) @ApiOperation(value = "Updates Test Environment by envId.", notes = "This operation can be performed only if '" + TestEnvUtils.SESSION_COOKIE + "' cookie is " + "present and valid. To obtain this cookie POST to /envs must be performed firstly. " + "This cookie is valid only for Test Environment with " + "envId which was specified in POST request body.") @ApiResponses(value = { @ApiResponse(code = 202, message = "Update is successful."), @ApiResponse(code = 404, message = "Test Environment with provided envId or sessionId is not found."), @ApiResponse(code = 400, message = TestEnvUtils.SESSION_COOKIE + " cookie is not present.")}) public ResponseEntity<?> updateTestEnvironment(@CookieValue(TestEnvUtils.SESSION_COOKIE) String sessionId, @PathVariable String envId, @RequestBody TestEnvironmentEntity testEnv, final HttpServletResponse response) { if (!testEnvService.exists(envId)) throw ResourceNotFoundException.getTestEnvResourceNfe(); if (!testEnvService.existsWithSessionId(envId, sessionId)) throw new TestEnvironmentSessionNotFoundException(envId, sessionId); validateTestEnv(testEnv); testEnv.setEnvironmentId(envId); testEnv.setSessionId(sessionId); TestEnvironmentEntity updated = testEnvService.update(testEnv); if (updated.getStatus() == PENDING) { getTestExecutionToExecute(updated).ifPresent( execution -> response.addHeader(TestEnvUtils.EXECUTION_ID_HEADER, execution.getId().toString()) ); } response.addCookie(getSessionCookie(updated)); return ResponseEntity.accepted().build(); } @PostMapping(consumes = APPLICATION_JSON_VALUE) @ApiOperation(value = "Creates Test Environment.", notes = "If operation successful, '" + TestEnvUtils.SESSION_COOKIE + "' cookie is returned with response, " + "which is required for PUT request. This cookie is valid only for Test Environment with envId which was specified in request body.") @ApiResponses(value = { @ApiResponse(code = 201, message = "Creation is successful."), @ApiResponse(code = 400, message = "envId doesn't match " + ENV_ID_PATTERN + " OR status is inconsistent with runningLoadScenario."), @ApiResponse(code = 409, message = "Test Environment with provided envId already exists.")}) public ResponseEntity<?> createTestEnvironment(@RequestBody TestEnvironmentEntity testEnv, HttpServletResponse response) { validateTestEnv(testEnv); if (testEnvService.exists(testEnv.getEnvironmentId())) throw new ResourceAlreadyExistsException("Test Environment", testEnv.getEnvironmentId()); TestEnvironmentEntity created = testEnvService.create(testEnv); response.addCookie(getSessionCookie(created)); return ResponseEntity.created(fromCurrentRequest().path("/{envId}").buildAndExpand(testEnv.getEnvironmentId()).toUri()).build(); } private Optional<TestExecutionEntity> getTestExecutionToExecute(TestEnvironmentEntity testEnv) { return testExecutionService.readAllPending().stream() .filter(testExec -> testExec.getEnvId().equals(testEnv.getEnvironmentId())) .findFirst(); } private void validateTestEnv(TestEnvironmentEntity testEnv) { String envId = testEnv.getEnvironmentId(); Pattern envIdPattern = Pattern.compile(ENV_ID_PATTERN); Matcher matcher = envIdPattern.matcher(envId); if (!matcher.matches()) throw new TestEnvironmentInvalidIdException(envId, envIdPattern); if (testEnv.getRunningLoadScenario() != null && testEnv.getStatus() == PENDING) throw new WrongTestEnvironmentStatusException(testEnv.getStatus(), testEnv.getRunningLoadScenario()); } private void setExpiresHeader(HttpServletResponse response, TestEnvironmentEntity testEnv) { response.addHeader(TestEnvUtils.EXPIRES_HEADER, getFormattedExpirationDate(testEnv)); } private Cookie getSessionCookie(TestEnvironmentEntity testEnv) { Cookie cookie = new Cookie(TestEnvUtils.SESSION_COOKIE, testEnv.getSessionId()); cookie.setMaxAge(environmentsTtlMinutes * 60); cookie.setPath("/"); return cookie; } private String getFormattedExpirationDate(TestEnvironmentEntity testEnv) { return TestEnvUtils.convertToExpiresValue(testEnv.getExpirationTimestamp()); } }