blob: ffdf3bde9420ba560c51762edf59d604a5545be8 [file] [log] [blame]
/*
* 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.Audit;
import com.epam.datalab.backendapi.annotation.Info;
import com.epam.datalab.backendapi.annotation.Project;
import com.epam.datalab.backendapi.annotation.ResourceName;
import com.epam.datalab.backendapi.annotation.User;
import com.epam.datalab.backendapi.dao.*;
import com.epam.datalab.backendapi.domain.EndpointDTO;
import com.epam.datalab.backendapi.domain.ProjectDTO;
import com.epam.datalab.backendapi.resources.dto.*;
import com.epam.datalab.backendapi.roles.RoleType;
import com.epam.datalab.backendapi.roles.UserRoles;
import com.epam.datalab.backendapi.service.EndpointService;
import com.epam.datalab.backendapi.service.ImageExploratoryService;
import com.epam.datalab.backendapi.service.ProjectService;
import com.epam.datalab.backendapi.util.RequestBuilder;
import com.epam.datalab.constants.ServiceConsts;
import com.epam.datalab.dto.SharedWith;
import com.epam.datalab.dto.UserInstanceDTO;
import com.epam.datalab.dto.UserInstanceStatus;
import com.epam.datalab.dto.exploratory.ExploratoryStatusDTO;
import com.epam.datalab.dto.exploratory.ImageSharingStatus;
import com.epam.datalab.dto.exploratory.ImageStatus;
import com.epam.datalab.exceptions.ResourceAlreadyExistException;
import com.epam.datalab.exceptions.ResourceNotFoundException;
import com.epam.datalab.model.ResourceType;
import com.epam.datalab.model.exploratory.Image;
import com.epam.datalab.model.library.Library;
import com.epam.datalab.rest.client.RESTService;
import com.epam.datalab.rest.contracts.ExploratoryAPI;
import com.google.common.collect.Lists;
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.collections4.CollectionUtils;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.epam.datalab.backendapi.domain.AuditActionEnum.CREATE;
import static com.epam.datalab.backendapi.domain.AuditActionEnum.TERMINATE;
import static com.epam.datalab.backendapi.domain.AuditResourceTypeEnum.IMAGE;
@Singleton
@Slf4j
public class ImageExploratoryServiceImpl implements ImageExploratoryService {
private static final String IMAGE_EXISTS_MSG = "Image with name %s is already exist in project %s";
private static final String IMAGE_NOT_FOUND_MSG = "Image with name %s was not found for user %s";
private static final String SHARE_OWN_IMAGES_PAGE = "/api/image/share";
private static final String TERMINATE_OWN_IMAGES_PAGE = "/api/image/terminate";
private static final String SHARE_RECEIVED_IMAGES_PAGE = "/api/image/shareReceived";
@Inject
private ExploratoryDAO exploratoryDAO;
@Inject
private ImageExploratoryDAO imageExploratoryDao;
@Inject
private ExploratoryLibDAO libDAO;
@Inject
private UserGroupDAO userGroupDAO;
@Inject
@Named(ServiceConsts.PROVISIONING_SERVICE_NAME)
private RESTService provisioningService;
@Inject
private RequestBuilder requestBuilder;
@Inject
private EndpointService endpointService;
@Inject
private ProjectService projectService;
@Inject
private UserSettingsDAO userSettingsDAO;
@Audit(action = CREATE, type = IMAGE)
@Override
public String createImage(@User UserInfo user, @Project String project, @ResourceName String exploratoryName, String imageName, String imageDescription, @Info String info) {
ProjectDTO projectDTO = projectService.get(project);
UserInstanceDTO userInstance = exploratoryDAO.fetchRunningExploratoryFields(user.getName(), project, exploratoryName);
if (imageExploratoryDao.exist(imageName, userInstance.getProject())) {
log.error(String.format(IMAGE_EXISTS_MSG, imageName, userInstance.getProject()));
throw new ResourceAlreadyExistException(String.format(IMAGE_EXISTS_MSG, imageName, userInstance.getProject()));
}
final List<Library> libraries = libDAO.getLibraries(user.getName(), project, exploratoryName);
imageExploratoryDao.save(Image.builder()
.name(imageName)
.description(imageDescription)
.status(ImageStatus.CREATING)
.user(user.getName())
.sharedWith(new SharedWith())
.libraries(fetchExploratoryLibs(libraries))
.computationalLibraries(fetchComputationalLibs(libraries))
.clusterConfig(userInstance.getClusterConfig())
.dockerImage(userInstance.getImageName())
.exploratoryId(userInstance.getId())
.templateName(userInstance.getTemplateName())
.instanceName(userInstance.getExploratoryName())
.cloudProvider(userInstance.getCloudProvider())
.project(userInstance.getProject())
.endpoint(userInstance.getEndpoint())
.build());
exploratoryDAO.updateExploratoryStatus(new ExploratoryStatusDTO()
.withUser(user.getName())
.withProject(project)
.withExploratoryName(exploratoryName)
.withStatus(UserInstanceStatus.CREATING_IMAGE));
EndpointDTO endpointDTO = endpointService.get(userInstance.getEndpoint());
return provisioningService.post(endpointDTO.getUrl() + ExploratoryAPI.EXPLORATORY_IMAGE,
user.getAccessToken(),
requestBuilder.newExploratoryImageCreate(user, userInstance, imageName, endpointDTO, projectDTO), String.class);
}
@Audit(action = TERMINATE, type = IMAGE)
@Override
public void terminateImage(@User UserInfo user, @Project String project, String endpoint, String imageName) {
Optional<ImageInfoRecord> image = imageExploratoryDao.getImage(user.getName(), imageName, project, endpoint);
if(image.isPresent()){
ImageInfoRecord imageInfoRecord = image.get();
imageExploratoryDao.updateImageStatus(user.getName(),imageName,project,endpoint,ImageStatus.TERMINATING);
EndpointDTO endpointDTO = endpointService.get(endpoint);
ProjectDTO projectDTO = projectService.get(project);
UserInstanceDTO userInstance = exploratoryDAO.fetchExploratoryFields(user.getName(), project, imageInfoRecord.getInstanceName());
log.info("ExploratoryImageCreate {}", requestBuilder.newExploratoryImageCreate(user, userInstance, imageName, endpointDTO, projectDTO));
provisioningService.post(endpointDTO.getUrl() + ExploratoryAPI.EXPLORATORY_IMAGE_TERMINATE,
user.getAccessToken(), requestBuilder.newExploratoryImageCreate(user, userInstance, imageName, endpointDTO, projectDTO)
, String.class);
}
}
@Override
public void finishTerminateImage(String imageName, String projectName, String endpoint) {
imageExploratoryDao.updateImageStatus(imageName, projectName, endpoint, ImageStatus.TERMINATED);
}
@Override
public void finishImageCreate(Image image, String exploratoryName, String newNotebookIp) {
log.debug("Returning exploratory status with name {} to RUNNING for user {}",
exploratoryName, image.getUser());
exploratoryDAO.updateExploratoryStatus(new ExploratoryStatusDTO()
.withUser(image.getUser())
.withProject(image.getProject())
.withExploratoryName(exploratoryName)
.withStatus(UserInstanceStatus.RUNNING));
imageExploratoryDao.updateImageFields(image);
log.debug("Image {}", image);
if (newNotebookIp != null) {
log.debug("Changing exploratory ip with name {} for user {} to {}", exploratoryName, image.getUser(),
newNotebookIp);
exploratoryDAO.updateExploratoryIp(image.getUser(), image.getProject(), newNotebookIp, exploratoryName);
}
}
@Override
public List<ImageInfoRecord> getNotFailedImages(UserInfo user, String dockerImage, String project, String endpoint) {
List<ImageInfoRecord> images = imageExploratoryDao.getImages(user.getName(), dockerImage, project, endpoint, ImageStatus.ACTIVE, ImageStatus.CREATING);
images.addAll(getSharedImages(user,dockerImage,project,endpoint));
return images;
}
@Override
public List<ImageInfoRecord> getNotFailedImages(String dockerImage, String project, String endpoint) {
return imageExploratoryDao.getImages(project, endpoint, dockerImage);
}
@Override
public ImageInfoRecord getImage(String user, String name, String project, String endpoint) {
return imageExploratoryDao.getImage(user, name, project, endpoint).orElseThrow(() ->
new ResourceNotFoundException(String.format(IMAGE_NOT_FOUND_MSG, name, user)));
}
@Override
public List<ImageInfoRecord> getImagesForProject(String project) {
return imageExploratoryDao.getImagesForProject(project);
}
@Override
public ImagesPageInfo getImagesOfUser(UserInfo user, ImageFilter imageFilter) {
log.debug("Loading list of images for user {}", user.getName());
List<ImageInfoRecord> images = imageExploratoryDao.getImagesOfUser(user.getName());
images.forEach(img -> img.setSharingStatus(getImageSharingStatus(user.getName(),img)));
images.addAll(getSharedImages(user));
ImageFilterFormData filterData = getDataForFilter(images);
if(imageFilter == null){
if(userSettingsDAO.getImageFilter(user.getName()).isPresent()){
imageFilter = userSettingsDAO.getImageFilter(user.getName()).get();
images = filterImages(images, imageFilter);
}
} else{
images = filterImages(images, imageFilter);
userSettingsDAO.setUserImageFilter(user.getName(),imageFilter);
}
images.forEach(img -> img.setImageUserPermissions(getUserImagePermissions(user,img)));
final List<ImageInfoRecord> finalImages = images;
List<ProjectImagesInfo> projectImagesInfoList = projectService.getUserProjects(user, Boolean.FALSE)
.stream()
.map(p -> {
List<ImageInfoRecord> im = finalImages.stream().filter(img -> img.getProject().equals(p.getName())).collect(Collectors.toList());
return ProjectImagesInfo.builder()
.project(p.getName())
.images(im)
.build();
})
.collect(Collectors.toList());
return ImagesPageInfo.builder()
.projectImagesInfos(projectImagesInfoList)
.filterData(filterData)
.imageFilter(imageFilter)
.build();
}
@Override
public void shareImage(UserInfo user, String imageName, String projectName, String endpoint, Set<SharedWithDTO> sharedWithDTOS) {
Optional<ImageInfoRecord> image = imageExploratoryDao.getImage(user.getName(),imageName,projectName,endpoint);
image.ifPresent(img -> {
log.info("image {}", img);
imageExploratoryDao.updateSharing(toSharedWith(sharedWithDTOS), img.getName() ,img.getProject(), img.getEndpoint());
});
}
@Override
public Set<SharedWithDTO> getImageSharingInfo(String userName, String imageName, String project, String endpoint){
Optional<ImageInfoRecord> image = imageExploratoryDao.getImage(userName, imageName, project, endpoint);
if(image.isPresent()){
return toSharedWithDTOs(image.get().getSharedWith());
} else {
throw new ResourceNotFoundException(IMAGE_NOT_FOUND_MSG);
}
}
private Set<SharedWithDTO> toSharedWithDTOs(SharedWith sharedWith){
Set<SharedWithDTO> sharedWithDTO = sharedWith.getGroups().stream()
.map(s -> new SharedWithDTO(SharedWithDTO.Type.GROUP, s)).collect(Collectors.toSet());
Set<SharedWithDTO> users = sharedWith.getUsers().stream()
.map(s -> new SharedWithDTO(SharedWithDTO.Type.USER, s)).collect(Collectors.toSet());
sharedWithDTO.addAll(users);
return sharedWithDTO;
}
private SharedWith toSharedWith(Set<SharedWithDTO> dtos){
SharedWith sharedWith = new SharedWith();
dtos.forEach(dto -> {
if(dto.getType().equals(SharedWithDTO.Type.GROUP)){
sharedWith.getGroups().add(dto.getValue());
}
else sharedWith.getUsers().add(dto.getValue());
});
return sharedWith;
}
public boolean hasAccess(String userName, SharedWith sharedWith){
boolean accessByUserName = sharedWith.getUsers().contains(userName);
boolean accessByGroup = sharedWith.getGroups().stream().anyMatch(groupName -> userGroupDAO.getUsers(groupName).contains(userName));
return accessByUserName || accessByGroup;
}
private Map<String, List<Library>> fetchComputationalLibs(List<Library> libraries) {
return libraries.stream()
.filter(resourceTypePredicate(ResourceType.COMPUTATIONAL))
.collect(Collectors.toMap(Library::getResourceName, Lists::newArrayList, this::merge));
}
private List<Library> merge(List<Library> oldValue, List<Library> newValue) {
oldValue.addAll(newValue);
return oldValue;
}
private List<Library> fetchExploratoryLibs(List<Library> libraries) {
return libraries.stream()
.filter(resourceTypePredicate(ResourceType.EXPLORATORY))
.collect(Collectors.toList());
}
private Predicate<Library> resourceTypePredicate(ResourceType resourceType) {
return l -> resourceType == l.getType();
}
public List<ImageInfoRecord> getSharedImages(UserInfo userInfo) {
List<ImageInfoRecord> sharedImages = imageExploratoryDao.getAllImages().stream()
.filter(img -> !img.getUser().equals(userInfo.getName()))
.filter(img -> hasAccess(userInfo.getName(),img.getSharedWith()))
.collect(Collectors.toList());
sharedImages.forEach(img -> img.setSharingStatus(getImageSharingStatus(userInfo.getName(),img)));
log.info("Shared with user {} images : {}", userInfo.getName(), sharedImages);
return sharedImages;
}
public List<ImageInfoRecord> getSharedImages(UserInfo userInfo, String dockerImage, String project, String endpoint) {
List<ImageInfoRecord> sharedImages = imageExploratoryDao.getAllImages().stream()
.filter(img -> img.getStatus().equals(ImageStatus.ACTIVE))
.filter(img -> !img.getUser().equals(userInfo.getName()))
.filter(img -> img.getDockerImage().equals(dockerImage) && img.getProject().equals(project) && img.getEndpoint().equals(endpoint))
.filter(img -> hasAccess(userInfo.getName(),img.getSharedWith()))
.collect(Collectors.toList());
sharedImages.forEach(img -> img.setSharingStatus(getImageSharingStatus(userInfo.getName(),img)));
log.info("Found shared with user {} images {}", userInfo.getName(), sharedImages);
return sharedImages;
}
@Override
public ImageUserPermissions getUserImagePermissions(UserInfo userInfo, ImageInfoRecord image) {
boolean canShare = false;
boolean canTerminate = (image.getStatus().equals(ImageStatus.ACTIVE) || image.getStatus().equals(ImageStatus.FAILED)) &&
(image.getUser().equals(userInfo.getName())
&& UserRoles.checkAccess(userInfo, RoleType.PAGE, TERMINATE_OWN_IMAGES_PAGE, userInfo.getRoles()));
if(image.getStatus().equals(ImageStatus.ACTIVE) ){
if(image.getUser().equals(userInfo.getName())){
canShare = UserRoles.checkAccess(userInfo, RoleType.PAGE, SHARE_OWN_IMAGES_PAGE,userInfo.getRoles());
} else {
canShare = UserRoles.checkAccess(userInfo, RoleType.PAGE, SHARE_RECEIVED_IMAGES_PAGE,userInfo.getRoles());
}
}
return new ImageUserPermissions(canShare,canTerminate);
}
private ImageSharingStatus getImageSharingStatus(String username, ImageInfoRecord image){
boolean notShared = image.getSharedWith().getUsers().isEmpty() && image.getSharedWith().getGroups().isEmpty();
if(notShared && image.getUser().equals(username)){
return ImageSharingStatus.PRIVATE;
} else if (!notShared && image.getUser().equals(username)){
return ImageSharingStatus.SHARED;
}
return ImageSharingStatus.RECEIVED ;
}
private List<ImageInfoRecord> filterImages(List<ImageInfoRecord> images, ImageFilter filter){
return images.stream()
.filter(img -> img.getName().toLowerCase().contains(filter.getImageName().toLowerCase()))
.filter(img -> CollectionUtils.isEmpty(filter.getStatuses()) || filter.getStatuses().contains(img.getStatus()))
.filter(img -> CollectionUtils.isEmpty(filter.getEndpoints()) || filter.getEndpoints().contains(img.getEndpoint()))
.filter(img -> CollectionUtils.isEmpty(filter.getTemplateNames()) || filter.getTemplateNames().contains(img.getTemplateName()))
.filter(img -> CollectionUtils.isEmpty(filter.getSharingStatuses()) || filter.getSharingStatuses().contains(img.getSharingStatus()))
.collect(Collectors.toList());
}
private ImageFilterFormData getDataForFilter(List<ImageInfoRecord> images){
ImageFilterFormData filterData = new ImageFilterFormData();
filterData.setImageNames(images.stream().map(ImageInfoRecord::getName).collect(Collectors.toSet()));
filterData.setStatuses(images.stream().map(ImageInfoRecord::getStatus).collect(Collectors.toSet()));
filterData.setEndpoints(images.stream().map(ImageInfoRecord::getEndpoint).collect(Collectors.toSet()));
filterData.setTemplateNames(images.stream().map(ImageInfoRecord::getTemplateName).collect(Collectors.toSet()));
filterData.setSharingStatuses(images.stream().map(ImageInfoRecord::getSharingStatus).collect(Collectors.toSet()));
return filterData;
}
}