| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package com.epam.datalab.backendapi.service.impl; |
| |
| import com.epam.datalab.auth.UserInfo; |
| import com.epam.datalab.backendapi.annotation.*; |
| import com.epam.datalab.backendapi.conf.SelfServiceApplicationConfiguration; |
| import com.epam.datalab.backendapi.dao.ComputationalDAO; |
| import com.epam.datalab.backendapi.dao.ExploratoryDAO; |
| import com.epam.datalab.backendapi.dao.GitCredsDAO; |
| import com.epam.datalab.backendapi.dao.ImageExploratoryDAO; |
| import com.epam.datalab.backendapi.domain.*; |
| import com.epam.datalab.backendapi.resources.dto.ExploratoryCreatePopUp; |
| import com.epam.datalab.backendapi.service.*; |
| import com.epam.datalab.backendapi.util.RequestBuilder; |
| import com.epam.datalab.cloud.CloudProvider; |
| import com.epam.datalab.constants.ServiceConsts; |
| import com.epam.datalab.dto.StatusEnvBaseDTO; |
| import com.epam.datalab.dto.UserInstanceDTO; |
| import com.epam.datalab.dto.UserInstanceStatus; |
| import com.epam.datalab.dto.aws.computational.ClusterConfig; |
| import com.epam.datalab.dto.base.DataEngineType; |
| import com.epam.datalab.dto.exploratory.*; |
| import com.epam.datalab.exceptions.DatalabException; |
| import com.epam.datalab.model.exploratory.Exploratory; |
| import com.epam.datalab.model.library.Library; |
| import com.epam.datalab.rest.client.RESTService; |
| import com.google.inject.Inject; |
| import com.google.inject.Singleton; |
| import com.google.inject.name.Named; |
| import lombok.extern.slf4j.Slf4j; |
| import org.apache.commons.lang3.StringUtils; |
| |
| import java.util.*; |
| import java.util.stream.Collectors; |
| |
| import static com.epam.datalab.backendapi.domain.AuditActionEnum.*; |
| import static com.epam.datalab.backendapi.domain.AuditResourceTypeEnum.NOTEBOOK; |
| import static com.epam.datalab.dto.UserInstanceStatus.*; |
| import static com.epam.datalab.rest.contracts.ExploratoryAPI.*; |
| |
| @Slf4j |
| @Singleton |
| public class ExploratoryServiceImpl implements ExploratoryService { |
| private final ProjectService projectService; |
| private final ExploratoryDAO exploratoryDAO; |
| private final ComputationalDAO computationalDAO; |
| private final GitCredsDAO gitCredsDAO; |
| private final ImageExploratoryDAO imageExploratoryDao; |
| private final RESTService provisioningService; |
| private final RequestBuilder requestBuilder; |
| private final RequestId requestId; |
| private final TagService tagService; |
| private final EndpointService endpointService; |
| private final AuditService auditService; |
| private final SelfServiceApplicationConfiguration configuration; |
| |
| @Inject |
| public ExploratoryServiceImpl(ProjectService projectService, ExploratoryDAO exploratoryDAO, ComputationalDAO computationalDAO, GitCredsDAO gitCredsDAO, |
| ImageExploratoryDAO imageExploratoryDao, @Named(ServiceConsts.PROVISIONING_SERVICE_NAME) RESTService provisioningService, |
| RequestBuilder requestBuilder, RequestId requestId, TagService tagService, EndpointService endpointService, AuditService auditService, |
| SelfServiceApplicationConfiguration configuration) { |
| this.projectService = projectService; |
| this.exploratoryDAO = exploratoryDAO; |
| this.computationalDAO = computationalDAO; |
| this.gitCredsDAO = gitCredsDAO; |
| this.imageExploratoryDao = imageExploratoryDao; |
| this.provisioningService = provisioningService; |
| this.requestBuilder = requestBuilder; |
| this.requestId = requestId; |
| this.tagService = tagService; |
| this.endpointService = endpointService; |
| this.auditService = auditService; |
| this.configuration = configuration; |
| } |
| |
| @BudgetLimited |
| @Audit(action = START, type = NOTEBOOK) |
| @Override |
| public String start(@User UserInfo userInfo, @ResourceName String exploratoryName, @Project String project, @Info String auditInfo) { |
| return action(userInfo, userInfo.getName(), project, exploratoryName, EXPLORATORY_START, STARTING); |
| } |
| |
| @Audit(action = STOP, type = NOTEBOOK) |
| @Override |
| public String stop(@User UserInfo userInfo, String resourceCreator, @Project String project, @ResourceName String exploratoryName, @Info String auditInfo) { |
| return action(userInfo, resourceCreator, project, exploratoryName, EXPLORATORY_STOP, STOPPING); |
| } |
| |
| @Audit(action = TERMINATE, type = NOTEBOOK) |
| @Override |
| public String terminate(@User UserInfo userInfo, String resourceCreator, @Project String project, @ResourceName String exploratoryName, @Info String auditInfo) { |
| return action(userInfo, resourceCreator, project, exploratoryName, EXPLORATORY_TERMINATE, TERMINATING); |
| } |
| |
| @BudgetLimited |
| @Audit(action = CREATE, type = NOTEBOOK) |
| @Override |
| public String create(@User UserInfo userInfo, Exploratory exploratory, @Project String project, @ResourceName String exploratoryName) { |
| boolean isAdded = false; |
| try { |
| final ProjectDTO projectDTO = projectService.get(project); |
| final EndpointDTO endpointDTO = endpointService.get(exploratory.getEndpoint()); |
| changeImageNameForDeepLearningOnAWSandAzure(exploratory, endpointDTO); |
| final UserInstanceDTO userInstanceDTO = getUserInstanceDTO(userInfo, exploratory, project, endpointDTO.getCloudProvider()); |
| exploratoryDAO.insertExploratory(userInstanceDTO); |
| isAdded = true; |
| final ExploratoryGitCredsDTO gitCreds = gitCredsDAO.findGitCreds(userInfo.getName()); |
| log.debug("Created exploratory environment {} for user {}", exploratory.getName(), userInfo.getName()); |
| final String uuid = |
| provisioningService.post(endpointDTO.getUrl() + EXPLORATORY_CREATE, |
| userInfo.getAccessToken(), |
| requestBuilder.newExploratoryCreate(projectDTO, endpointDTO, exploratory, userInfo, |
| gitCreds, userInstanceDTO.getTags()), |
| String.class); |
| requestId.put(userInfo.getName(), uuid); |
| return uuid; |
| } catch (Exception t) { |
| log.error("Could not update the status of exploratory environment {} with name {} for user {}", |
| exploratory.getDockerImage(), exploratory.getName(), userInfo.getName(), t); |
| if (isAdded) { |
| updateExploratoryStatusSilent(userInfo.getName(), project, exploratory.getName(), FAILED); |
| } |
| throw new DatalabException("Could not create exploratory environment " + exploratory.getName() + " for user " |
| + userInfo.getName() + ": " + Optional.ofNullable(t.getCause()).map(Throwable::getMessage).orElse(t.getMessage()), t); |
| } |
| } |
| |
| private void changeImageNameForDeepLearningOnAWSandAzure(Exploratory exploratory, EndpointDTO endpointDTO) { |
| if (isDeepLearningOnAwsOrAzure(exploratory, endpointDTO)) { |
| if (exploratory.getImageName() == null || exploratory.getImageName().isEmpty()) |
| exploratory.setImageName(exploratory.getVersion()); |
| } |
| } |
| |
| private boolean isDeepLearningOnAwsOrAzure(Exploratory exploratory, EndpointDTO endpointDTO) { |
| return exploratory.getVersion().equals("Deep Learning AMI (Ubuntu 18.04) Version 42.1") || |
| exploratory.getVersion().equals("microsoft-dsvm:ubuntu-1804:1804-gen2"); |
| } |
| |
| @Override |
| public void updateProjectExploratoryStatuses(UserInfo userInfo, String project, String endpoint, UserInstanceStatus status) { |
| exploratoryDAO.fetchProjectExploratoriesWhereStatusNotIn(project, endpoint, TERMINATED, FAILED) |
| .forEach(ui -> updateExploratoryComputeStatuses(userInfo, project, ui.getExploratoryName(), status, ui.getUser())); |
| } |
| |
| @Override |
| public void updateProjectExploratoryStatuses(String project, String endpoint, UserInstanceStatus status) { |
| exploratoryDAO.fetchProjectExploratoriesWhereStatusNotIn(project, endpoint, TERMINATED, FAILED) |
| .forEach(ui -> { |
| updateExploratoryStatus(ui.getUser(), project, ui.getExploratoryName(), status); |
| updateComputationalStatuses(ui.getUser(), project, ui.getExploratoryName(), TERMINATED, TERMINATED, TERMINATED, FAILED); |
| }); |
| } |
| |
| @Audit(action = RECONFIGURE, type = NOTEBOOK) |
| @Override |
| public void updateClusterConfig(@User UserInfo userInfo, @Project String project, @ResourceName String exploratoryName, List<ClusterConfig> config) { |
| final String userName = userInfo.getName(); |
| final String token = userInfo.getAccessToken(); |
| final UserInstanceDTO userInstanceDTO = exploratoryDAO.fetchRunningExploratoryFields(userName, project, exploratoryName); |
| EndpointDTO endpointDTO = endpointService.get(userInstanceDTO.getEndpoint()); |
| final ExploratoryReconfigureSparkClusterActionDTO updateClusterConfigDTO = |
| requestBuilder.newClusterConfigUpdate(userInfo, userInstanceDTO, config, endpointDTO); |
| final String uuid = provisioningService.post(endpointDTO.getUrl() + EXPLORATORY_RECONFIGURE_SPARK, |
| token, updateClusterConfigDTO, |
| String.class); |
| requestId.put(userName, uuid); |
| exploratoryDAO.updateExploratoryFields(new ExploratoryStatusDTO() |
| .withUser(userName) |
| .withProject(project) |
| .withExploratoryName(exploratoryName) |
| .withConfig(config) |
| .withStatus(UserInstanceStatus.RECONFIGURING.toString())); |
| } |
| |
| /** |
| * Returns user instance's data by it's name. |
| * |
| * @param user user. |
| * @param project |
| * @param exploratoryName name of exploratory. |
| * @return corresponding user instance's data or empty data if resource doesn't exist. |
| */ |
| @Override |
| public Optional<UserInstanceDTO> getUserInstance(String user, String project, String exploratoryName) { |
| try { |
| return Optional.of(exploratoryDAO.fetchExploratoryFields(user, project, exploratoryName)); |
| } catch (DatalabException e) { |
| log.warn("User instance with exploratory {}, project {} for user {} not found.", exploratoryName, project, user, e); |
| } |
| return Optional.empty(); |
| } |
| |
| @Override |
| public Optional<UserInstanceDTO> getUserInstance(String user, String project, String exploratoryName, boolean includeCompResources) { |
| try { |
| return Optional.of(exploratoryDAO.fetchExploratoryFields(user, project, exploratoryName, includeCompResources)); |
| } catch (DatalabException e) { |
| log.warn("User instance with exploratory {}, project {} for user {} not found.", exploratoryName, project, user, e); |
| } |
| return Optional.empty(); |
| } |
| |
| @Override |
| public List<UserInstanceDTO> findAll() { |
| return exploratoryDAO.getInstances(); |
| } |
| |
| @Override |
| public List<UserInstanceDTO> findAll(Set<ProjectDTO> projects) { |
| List<String> projectNames = projects |
| .stream() |
| .map(ProjectDTO::getName) |
| .collect(Collectors.toList()); |
| return exploratoryDAO.fetchExploratoryFieldsForProjectWithComp(projectNames); |
| } |
| |
| @Override |
| public List<ClusterConfig> getClusterConfig(UserInfo user, String project, String exploratoryName) { |
| return exploratoryDAO.getClusterConfig(user.getName(), project, exploratoryName); |
| } |
| |
| @Override |
| public ExploratoryCreatePopUp getUserInstances(UserInfo user) { |
| List<ProjectDTO> userProjects = projectService.getUserProjects(user, false); |
| Map<String, List<String>> collect = userProjects.stream() |
| .collect(Collectors.toMap(ProjectDTO::getName, this::getProjectExploratoryNames)); |
| return new ExploratoryCreatePopUp(userProjects, collect); |
| } |
| |
| @Audit(action = UPDATE, type = NOTEBOOK) |
| @Override |
| public void updateAfterStatusCheck(@User UserInfo userInfo, @Project String project, String endpoint, @ResourceName String name, |
| String instanceID, UserInstanceStatus status, @Info String auditInfo) { |
| exploratoryDAO.updateExploratoryStatus(project, endpoint, name, instanceID, status); |
| } |
| |
| private List<String> getProjectExploratoryNames(ProjectDTO project) { |
| return exploratoryDAO.fetchExploratoryFieldsForProject(project.getName()).stream() |
| .map(UserInstanceDTO::getExploratoryName) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Sends the post request to the provisioning service and update the status of exploratory environment. |
| * |
| * @param userInfo user info. |
| * @param resourceCreator username of person who has created the resource |
| * @param project name of project |
| * @param exploratoryName name of exploratory environment. |
| * @param action action for exploratory environment. |
| * @param status status for exploratory environment. |
| * @return Invocation request as JSON string. |
| */ |
| private String action(UserInfo userInfo, String resourceCreator, String project, String exploratoryName, String action, UserInstanceStatus status) { |
| try { |
| updateExploratoryComputeStatuses(userInfo.getName(), project, exploratoryName, status, resourceCreator); |
| |
| UserInstanceDTO userInstance = exploratoryDAO.fetchExploratoryFields(resourceCreator, project, exploratoryName); |
| EndpointDTO endpointDTO = endpointService.get(userInstance.getEndpoint()); |
| final String uuid = |
| provisioningService.post(endpointDTO.getUrl() + action, userInfo.getAccessToken(), |
| getExploratoryActionDto(userInfo, resourceCreator, status, userInstance, endpointDTO), String.class); |
| requestId.put(resourceCreator, uuid); |
| return uuid; |
| } catch (Exception t) { |
| log.error("Could not {} exploratory environment {} for user {}", |
| StringUtils.substringAfter(action, "/"), exploratoryName, resourceCreator, t); |
| updateExploratoryStatusSilent(resourceCreator, project, exploratoryName, FAILED); |
| final String errorMsg = String.format("Could not %s exploratory environment %s: %s", |
| StringUtils.substringAfter(action, "/"), exploratoryName, |
| Optional.ofNullable(t.getCause()).map(Throwable::getMessage).orElse(t.getMessage())); |
| throw new DatalabException(errorMsg, t); |
| } |
| } |
| |
| @Audit(action = TERMINATE, type = NOTEBOOK) |
| public void updateExploratoryComputeStatuses(@User UserInfo userInfo, @Project String project, @ResourceName String exploratoryName, UserInstanceStatus status, String resourceCreator) { |
| updateExploratoryStatus(resourceCreator, project, exploratoryName, status); |
| updateComputationalStatuses(userInfo.getName(), resourceCreator, project, exploratoryName, status); |
| } |
| |
| private void updateExploratoryComputeStatuses(String user, String project, String exploratoryName, UserInstanceStatus status, String resourceCreator) { |
| updateExploratoryStatus(resourceCreator, project, exploratoryName, status); |
| updateComputationalStatuses(user, resourceCreator, project, exploratoryName, status); |
| } |
| |
| private void updateComputationalStatuses(String user, String resourceCreator, String project, String exploratoryName, UserInstanceStatus status) { |
| if (status == STOPPING) { |
| if (configuration.isAuditEnabled()) { |
| saveAudit(user, resourceCreator, project, exploratoryName, STOP, RUNNING); |
| } |
| updateComputationalStatuses(resourceCreator, project, exploratoryName, STOPPING, TERMINATING, FAILED, TERMINATED, STOPPED); |
| } else if (status == TERMINATING) { |
| if (configuration.isAuditEnabled()) { |
| saveAudit(user, resourceCreator, project, exploratoryName, TERMINATE, RUNNING, STOPPED); |
| } |
| updateComputationalStatuses(resourceCreator, project, exploratoryName, TERMINATING, TERMINATING, TERMINATED, FAILED); |
| } |
| } |
| |
| private void saveAudit(String user, String resourceCreator, String project, String exploratoryName, AuditActionEnum action, UserInstanceStatus... sparkStatuses) { |
| saveAuditForComputational(user, resourceCreator, project, exploratoryName, action, DataEngineType.SPARK_STANDALONE, sparkStatuses); |
| saveAuditForComputational(user, resourceCreator, project, exploratoryName, TERMINATE, DataEngineType.CLOUD_SERVICE, RUNNING, STOPPED); |
| } |
| |
| private void saveAuditForComputational(String user, String resourceCreator, String project, String exploratoryName, AuditActionEnum action, DataEngineType cloudService, |
| UserInstanceStatus... computationalStatuses) { |
| computationalDAO.getComputationalResourcesWhereStatusIn(resourceCreator, project, Collections.singletonList(cloudService), |
| exploratoryName, computationalStatuses) |
| .forEach(comp -> auditService.save( |
| AuditDTO.builder() |
| .user(user) |
| .resourceName(comp) |
| .project(project) |
| .action(action) |
| .type(AuditResourceTypeEnum.COMPUTE) |
| .build()) |
| ); |
| } |
| |
| private ExploratoryActionDTO<?> getExploratoryActionDto(UserInfo userInfo, String resourceCreator, UserInstanceStatus status, UserInstanceDTO userInstance, |
| EndpointDTO endpointDTO) { |
| ExploratoryActionDTO<?> dto; |
| if (status != UserInstanceStatus.STARTING) { |
| dto = requestBuilder.newExploratoryStop(resourceCreator, userInstance, endpointDTO); |
| } else { |
| dto = requestBuilder.newExploratoryStart(userInfo, userInstance, endpointDTO, gitCredsDAO.findGitCreds(userInfo.getName())); |
| } |
| return dto; |
| } |
| |
| |
| /** |
| * Updates the status of exploratory environment. |
| * |
| * @param user user name |
| * @param project project name |
| * @param exploratoryName name of exploratory environment. |
| * @param status status for exploratory environment. |
| */ |
| private void updateExploratoryStatus(String user, String project, String exploratoryName, UserInstanceStatus status) { |
| StatusEnvBaseDTO<?> exploratoryStatus = createStatusDTO(user, project, exploratoryName, status); |
| exploratoryDAO.updateExploratoryStatus(exploratoryStatus); |
| } |
| |
| /** |
| * Updates the status of exploratory environment without exceptions. If exception occurred then logging it. |
| * |
| * @param user user name |
| * @param project project name |
| * @param exploratoryName name of exploratory environment. |
| * @param status status for exploratory environment. |
| */ |
| private void updateExploratoryStatusSilent(String user, String project, String exploratoryName, UserInstanceStatus status) { |
| try { |
| updateExploratoryStatus(user, project, exploratoryName, status); |
| } catch (DatalabException e) { |
| log.error("Could not update the status of exploratory environment {} for user {} to {}", |
| exploratoryName, user, status, e); |
| } |
| } |
| |
| private void updateComputationalStatuses(String user, String project, String exploratoryName, UserInstanceStatus |
| dataEngineStatus, UserInstanceStatus dataEngineServiceStatus, UserInstanceStatus... excludedStatuses) { |
| log.debug("updating status for all computational resources of {} for user {}: DataEngine {}, " + |
| "dataengine-service {}", exploratoryName, user, dataEngineStatus, dataEngineServiceStatus); |
| computationalDAO.updateComputationalStatusesForExploratory(user, project, exploratoryName, |
| dataEngineStatus, dataEngineServiceStatus, excludedStatuses); |
| } |
| |
| /** |
| * Instantiates and returns the descriptor of exploratory environment status. |
| * |
| * @param user user name |
| * @param project project |
| * @param exploratoryName name of exploratory environment. |
| * @param status status for exploratory environment. |
| */ |
| private StatusEnvBaseDTO<?> createStatusDTO(String user, String project, String exploratoryName, UserInstanceStatus status) { |
| return new ExploratoryStatusDTO() |
| .withUser(user) |
| .withProject(project) |
| .withExploratoryName(exploratoryName) |
| .withStatus(status); |
| } |
| |
| private UserInstanceDTO getUserInstanceDTO(UserInfo userInfo, Exploratory exploratory, String project, CloudProvider cloudProvider) { |
| final UserInstanceDTO userInstance = new UserInstanceDTO() |
| .withUser(userInfo.getName()) |
| .withExploratoryName(exploratory.getName()) |
| .withStatus(CREATING.toString()) |
| .withImageName(exploratory.getDockerImage()) |
| .withImageVersion(exploratory.getVersion()) |
| .withTemplateName(exploratory.getTemplateName()) |
| .withClusterConfig(exploratory.getClusterConfig()) |
| .withShape(exploratory.getShape()) |
| .withProject(project) |
| .withEndpoint(exploratory.getEndpoint()) |
| .withCloudProvider(cloudProvider.toString()) |
| .withTags(tagService.getResourceTags(userInfo, exploratory.getEndpoint(), project, |
| exploratory.getExploratoryTag(), exploratory.getEnabledGPU())) |
| .withGPUCount(exploratory.getGpuCount()) |
| .withGPUEnabled(exploratory.getEnabledGPU()) |
| .withGPUType(exploratory.getGpuType()); |
| if (StringUtils.isNotBlank(exploratory.getImageName())) { |
| final List<LibInstallDTO> libInstallDtoList = getImageRelatedLibraries(userInfo, exploratory.getImageName(), |
| project, exploratory.getEndpoint()); |
| userInstance.withLibs(libInstallDtoList); |
| } |
| return userInstance; |
| } |
| |
| private List<LibInstallDTO> getImageRelatedLibraries(UserInfo userInfo, String imageFullName, String project, |
| String endpoint) { |
| final List<Library> libraries = imageExploratoryDao.getLibraries(userInfo.getName(), imageFullName, project, |
| endpoint, LibStatus.INSTALLED); |
| return toLibInstallDtoList(libraries); |
| } |
| |
| private List<LibInstallDTO> toLibInstallDtoList(List<Library> libraries) { |
| return libraries |
| .stream() |
| .map(this::toLibInstallDto) |
| .collect(Collectors.toList()); |
| } |
| |
| private LibInstallDTO toLibInstallDto(Library l) { |
| return new LibInstallDTO(l.getGroup(), l.getName(), l.getVersion()) |
| .withStatus(String.valueOf(l.getStatus())) |
| .withAddedPackages(l.getAddedPackages()) |
| .withErrorMessage(l.getErrorMessage()); |
| } |
| } |