Merge pull request #328 from apache/DLAB-1075
[DLAB-1075]: Added possibility to stop project with related instances…
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/domain/ProjectManagingDTO.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/domain/ProjectManagingDTO.java
new file mode 100644
index 0000000..167128e
--- /dev/null
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/domain/ProjectManagingDTO.java
@@ -0,0 +1,16 @@
+package com.epam.dlab.backendapi.domain;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+@AllArgsConstructor
+public class ProjectManagingDTO {
+ private String name;
+ private final Integer budget;
+ private boolean canBeStopped;
+ private boolean canBeTerminated;
+}
\ No newline at end of file
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/ProjectResource.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/ProjectResource.java
index 3d618e3..c361b1b 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/ProjectResource.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/ProjectResource.java
@@ -110,6 +110,26 @@
.build();
}
+ @Operation(summary = "Stop project on Manage environment popup", tags = "project")
+ @ApiResponses({
+ @ApiResponse(responseCode = "202", description = "Project is stopping"),
+ @ApiResponse(responseCode = "400", description = "Validation error", content = @Content(mediaType =
+ MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = ErrorDTO.class)))
+ })
+ @Path("managing/stop/{name}")
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @RolesAllowed("/api/project")
+ public Response stopProjectWithResources(@Parameter(hidden = true) @Auth UserInfo userInfo,
+ @Parameter(description = "Project name")
+ @PathParam("name") String name) {
+ projectService.stopWithResources(userInfo, name);
+ return Response
+ .accepted()
+ .build();
+ }
+
@Operation(summary = "Get project info", tags = "project")
@ApiResponses({
@@ -149,6 +169,22 @@
.build();
}
+ @Operation(summary = "Get available projects for managing", tags = "project")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "Return information about projects",
+ content = @Content(mediaType = MediaType.APPLICATION_JSON, schema =
+ @Schema(implementation = ProjectManagingDTO.class))),
+ })
+ @GET
+ @Path("managing")
+ @Produces(MediaType.APPLICATION_JSON)
+ @RolesAllowed("/api/project")
+ public Response getProjectsForManaging(@Parameter(hidden = true) @Auth UserInfo userInfo) {
+ return Response
+ .ok(projectService.getProjectsForManaging())
+ .build();
+ }
+
@Operation(summary = "Get projects assigned to user", tags = "project")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Return information about projects",
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/ProjectService.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/ProjectService.java
index 2823eb7..1a94259 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/ProjectService.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/ProjectService.java
@@ -2,6 +2,7 @@
import com.epam.dlab.auth.UserInfo;
import com.epam.dlab.backendapi.domain.ProjectDTO;
+import com.epam.dlab.backendapi.domain.ProjectManagingDTO;
import com.epam.dlab.backendapi.domain.UpdateProjectDTO;
import java.util.List;
@@ -9,6 +10,8 @@
public interface ProjectService {
List<ProjectDTO> getProjects();
+ List<ProjectManagingDTO> getProjectsForManaging();
+
List<ProjectDTO> getUserActiveProjects(UserInfo userInfo);
List<ProjectDTO> getProjectsWithStatus(ProjectDTO.Status status);
@@ -25,6 +28,8 @@
void stop(UserInfo userInfo, String endpoint, String name);
+ void stopWithResources(UserInfo userInfo, String projectName);
+
void update(UserInfo userInfo, UpdateProjectDTO projectDTO);
void updateBudget(String project, Integer budget);
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ProjectServiceImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ProjectServiceImpl.java
index de5ecfa..7b44d0f 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ProjectServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ProjectServiceImpl.java
@@ -3,17 +3,21 @@
import com.epam.dlab.auth.UserInfo;
import com.epam.dlab.backendapi.annotation.BudgetLimited;
import com.epam.dlab.backendapi.annotation.Project;
+import com.epam.dlab.backendapi.dao.ExploratoryDAO;
import com.epam.dlab.backendapi.dao.ProjectDAO;
import com.epam.dlab.backendapi.dao.UserGroupDao;
import com.epam.dlab.backendapi.domain.ProjectDTO;
import com.epam.dlab.backendapi.domain.ProjectEndpointDTO;
+import com.epam.dlab.backendapi.domain.ProjectManagingDTO;
import com.epam.dlab.backendapi.domain.RequestId;
import com.epam.dlab.backendapi.domain.UpdateProjectDTO;
import com.epam.dlab.backendapi.service.EndpointService;
import com.epam.dlab.backendapi.service.ExploratoryService;
import com.epam.dlab.backendapi.service.ProjectService;
+import com.epam.dlab.backendapi.service.SecurityService;
import com.epam.dlab.backendapi.util.RequestBuilder;
import com.epam.dlab.constants.ServiceConsts;
+import com.epam.dlab.dto.UserInstanceDTO;
import com.epam.dlab.dto.UserInstanceStatus;
import com.epam.dlab.exceptions.ResourceConflictException;
import com.epam.dlab.exceptions.ResourceNotFoundException;
@@ -22,6 +26,8 @@
import com.google.inject.name.Named;
import lombok.extern.slf4j.Slf4j;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -39,6 +45,9 @@
private static final String START_PRJ_API = "infrastructure/project/start";
private static final String STOP_PRJ_API = "infrastructure/project/stop";
private static final String ANY_USER_ROLE = "$anyuser";
+ private static final String STOP_ACTION = "stop";
+ private static final String TERMINATE_ACTION = "terminate";
+
private final ProjectDAO projectDAO;
private final ExploratoryService exploratoryService;
private final UserGroupDao userGroupDao;
@@ -46,12 +55,15 @@
private final RequestId requestId;
private final RequestBuilder requestBuilder;
private final EndpointService endpointService;
+ private final ExploratoryDAO exploratoryDAO;
+ private final SecurityService securityService;
@Inject
public ProjectServiceImpl(ProjectDAO projectDAO, ExploratoryService exploratoryService,
UserGroupDao userGroupDao,
@Named(ServiceConsts.PROVISIONING_SERVICE_NAME) RESTService provisioningService,
- RequestId requestId, RequestBuilder requestBuilder, EndpointService endpointService) {
+ RequestId requestId, RequestBuilder requestBuilder, EndpointService endpointService,
+ ExploratoryDAO exploratoryDAO, SecurityService securityService) {
this.projectDAO = projectDAO;
this.exploratoryService = exploratoryService;
this.userGroupDao = userGroupDao;
@@ -59,6 +71,8 @@
this.requestId = requestId;
this.requestBuilder = requestBuilder;
this.endpointService = endpointService;
+ this.exploratoryDAO = exploratoryDAO;
+ this.securityService = securityService;
}
@Override
@@ -67,6 +81,16 @@
}
@Override
+ public List<ProjectManagingDTO> getProjectsForManaging() {
+ return projectDAO.getProjects().stream().map(p -> new ProjectManagingDTO(
+ p.getName(), p.getBudget(), !exploratoryDAO.fetchProjectExploratoriesWhereStatusIn(p.getName(),
+ Collections.singletonList(UserInstanceStatus.RUNNING), UserInstanceStatus.RUNNING).isEmpty(),
+ !p.getEndpoints().stream().allMatch(e -> Arrays.asList(UserInstanceStatus.STARTING,
+ UserInstanceStatus.TERMINATED, UserInstanceStatus.TERMINATING).contains(e.getStatus()))))
+ .collect(Collectors.toList());
+ }
+
+ @Override
public List<ProjectDTO> getUserActiveProjects(UserInfo userInfo) {
userInfo.getRoles().add(ANY_USER_ROLE);
return projectDAO.getUserProjects(userInfo);
@@ -103,6 +127,7 @@
@Override
public void terminateProject(UserInfo userInfo, String name) {
+ checkProjectRelatedResourcesInProgress(name, TERMINATE_ACTION);
get(name).getEndpoints()
.stream()
.map(ProjectEndpointDTO::getName)
@@ -123,6 +148,16 @@
}
@Override
+ public void stopWithResources(UserInfo userInfo, String projectName) {
+ ProjectDTO project = get(projectName);
+ checkProjectRelatedResourcesInProgress(projectName, STOP_ACTION);
+ exploratoryDAO.fetchRunningExploratoryFieldsForProject(projectName).forEach(this::stopNotebook);
+ project.getEndpoints().stream().filter(e -> !Arrays.asList(UserInstanceStatus.TERMINATED,
+ UserInstanceStatus.TERMINATING).contains(e.getStatus())).
+ forEach(e -> stop(userInfo, e.getName(), projectName));
+ }
+
+ @Override
public void update(UserInfo userInfo, UpdateProjectDTO projectDTO) {
final ProjectDTO project = projectDAO.get(projectDTO.getName()).orElseThrow(projectNotFound());
final Set<String> endpoints = project.getEndpoints()
@@ -189,6 +224,23 @@
}
}
+ private void checkProjectRelatedResourcesInProgress(String projectName, String action) {
+ List<UserInstanceDTO> userInstanceDTOs = exploratoryDAO.fetchProjectExploratoriesWhereStatusIn(projectName,
+ Arrays.asList(UserInstanceStatus.CREATING, UserInstanceStatus.STARTING,
+ UserInstanceStatus.CREATING_IMAGE), UserInstanceStatus.CREATING,
+ UserInstanceStatus.CONFIGURING, UserInstanceStatus.STARTING, UserInstanceStatus.RECONFIGURING,
+ UserInstanceStatus.CREATING_IMAGE);
+ if (!userInstanceDTOs.isEmpty()) {
+ throw new ResourceConflictException((String.format("Can not %s environment because on of user resource " +
+ "is in status CREATING or STARTING", action)));
+ }
+ }
+
+ private void stopNotebook(UserInstanceDTO instance) {
+ final UserInfo userInfo = securityService.getUserInfoOffline(instance.getUser());
+ exploratoryService.stop(userInfo, instance.getExploratoryName());
+ }
+
private Supplier<ResourceNotFoundException> projectNotFound() {
return () -> new ResourceNotFoundException("Project with passed name not found");
}
diff --git a/services/self-service/src/test/java/com/epam/dlab/backendapi/resources/ProjectResourceTest.java b/services/self-service/src/test/java/com/epam/dlab/backendapi/resources/ProjectResourceTest.java
index 4a893a1..3f20967 100644
--- a/services/self-service/src/test/java/com/epam/dlab/backendapi/resources/ProjectResourceTest.java
+++ b/services/self-service/src/test/java/com/epam/dlab/backendapi/resources/ProjectResourceTest.java
@@ -37,6 +37,33 @@
}
@Test
+ public void getProjectsForManaging() {
+ final Response response = resources.getJerseyTest()
+ .target("project/managing")
+ .request()
+ .header("Authorization", "Bearer " + TOKEN)
+ .get();
+
+ assertEquals(HttpStatus.SC_OK, response.getStatus());
+ assertEquals(MediaType.APPLICATION_JSON, response.getHeaderString(HttpHeaders.CONTENT_TYPE));
+ verify(projectService, times(1)).getProjectsForManaging();
+ verifyNoMoreInteractions(projectService);
+ }
+
+ @Test
+ public void stopProjectWithResources() {
+ final Response response = resources.getJerseyTest()
+ .target("project/managing/stop/" + "projectName")
+ .request()
+ .header("Authorization", "Bearer " + TOKEN)
+ .post(Entity.json(""));
+
+ assertEquals(HttpStatus.SC_ACCEPTED, response.getStatus());
+ verify(projectService, times(1)).stopWithResources(any(UserInfo.class), anyString());
+ verifyNoMoreInteractions(projectService);
+ }
+
+ @Test
public void generate() {
when(keyService.generateKeys(any(UserInfo.class))).thenReturn(new KeysDTO("somePublicKey", "somePrivateKey",
"user"));