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"));