blob: f213e28825f5f906410ca205fd1a5d4d13ac9856 [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.dlab.backendapi.resources.gcp;
import com.epam.dlab.auth.UserInfo;
import com.epam.dlab.backendapi.SelfServiceApplicationConfiguration;
import com.epam.dlab.backendapi.resources.dto.SparkStandaloneClusterCreateForm;
import com.epam.dlab.backendapi.resources.dto.gcp.GcpComputationalCreateForm;
import com.epam.dlab.backendapi.resources.swagger.SwaggerSecurityInfo;
import com.epam.dlab.backendapi.roles.RoleType;
import com.epam.dlab.backendapi.roles.UserRoles;
import com.epam.dlab.backendapi.service.ComputationalService;
import com.epam.dlab.dto.aws.computational.ClusterConfig;
import com.epam.dlab.dto.base.DataEngineType;
import com.epam.dlab.dto.gcp.computational.GcpComputationalResource;
import com.epam.dlab.exceptions.DlabException;
import com.epam.dlab.rest.contracts.ComputationalAPI;
import com.google.inject.Inject;
import io.dropwizard.auth.Auth;
import io.swagger.annotations.*;
import lombok.extern.slf4j.Slf4j;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import static com.epam.dlab.dto.UserInstanceStatus.CREATING;
/**
* Provides the REST API for the computational resource on GCP.
*/
@Path("/infrastructure_provision/computational_resources")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Api(value = "Service for computational resources on GCP. (NOTE: available only on GCP platform)",
authorizations = @Authorization(SwaggerSecurityInfo.TOKEN_AUTH), hidden = true)
@Slf4j
public class ComputationalResourceGcp implements ComputationalAPI {
@Inject
private SelfServiceApplicationConfiguration configuration;
@Inject
private ComputationalService computationalService;
/**
* Asynchronously creates Dataproc cluster
*
* @param userInfo user info.
* @param formDTO DTO info about creation of the computational resource.
* @return 200 OK - if request success, 302 Found - for duplicates.
* @throws IllegalArgumentException if docker image name is malformed
*/
@PUT
@Path("dataengine-service")
@ApiOperation("Creates Dataproc cluster on GCP")
@ApiResponses({
@ApiResponse(code = 302, message = "Dataproc cluster on GCP with current parameters already exists"),
@ApiResponse(code = 200, message = "Dataproc cluster on GCP created successfully")
})
public Response createDataEngineService(@ApiParam(hidden = true) @Auth UserInfo userInfo,
@ApiParam(value = "GCP form DTO for Dataproc creation", required = true)
@Valid @NotNull GcpComputationalCreateForm formDTO) {
log.debug("Create computational resources for {} | form is {}", userInfo.getName(), formDTO);
if (DataEngineType.CLOUD_SERVICE == DataEngineType.fromDockerImageName(formDTO.getImage())) {
validate(userInfo, formDTO);
GcpComputationalResource gcpComputationalResource = GcpComputationalResource.builder().computationalName
(formDTO.getName())
.imageName(formDTO.getImage())
.templateName(formDTO.getTemplateName())
.status(CREATING.toString())
.masterShape(formDTO.getMasterInstanceType())
.slaveShape(formDTO.getSlaveInstanceType())
.slaveNumber(formDTO.getSlaveInstanceCount())
.masterNumber(formDTO.getMasterInstanceCount())
.preemptibleNumber(formDTO.getPreemptibleCount())
.version(formDTO.getVersion())
.build();
boolean resourceAdded = computationalService.createDataEngineService(userInfo, formDTO,
gcpComputationalResource, formDTO.getProject());
return resourceAdded ? Response.ok().build() : Response.status(Response.Status.FOUND).build();
}
throw new IllegalArgumentException("Malformed image " + formDTO.getImage());
}
/**
* Asynchronously triggers creation of Spark cluster
*
* @param userInfo user info.
* @param form DTO info about creation of the computational resource.
* @return 200 OK - if request success, 302 Found - for duplicates.
*/
@PUT
@Path("dataengine")
@ApiOperation("Creates Spark cluster on GCP")
@ApiResponses({
@ApiResponse(code = 302, message = "Spark cluster on GCP with current parameters already exists"),
@ApiResponse(code = 200, message = "Spark cluster on GCP successfully created")
})
public Response createDataEngine(@ApiParam(hidden = true) @Auth UserInfo userInfo,
@ApiParam(value = "Spark cluster create form DTO", required = true)
@Valid @NotNull SparkStandaloneClusterCreateForm form) {
log.debug("Create computational resources for {} | form is {}", userInfo.getName(), form);
if (!UserRoles.checkAccess(userInfo, RoleType.COMPUTATIONAL, form.getImage(), userInfo.getRoles())) {
log.warn("Unauthorized attempt to create a {} by user {}", form.getImage(), userInfo.getName());
throw new DlabException("You do not have the privileges to create a " + form.getTemplateName());
}
return computationalService.createSparkCluster(userInfo, form, form.getProject())
? Response.ok().build()
: Response.status(Response.Status.FOUND).build();
}
/**
* Sends request to provisioning service for termination the computational resource for user.
*
* @param userInfo user info.
* @param exploratoryName name of exploratory.
* @param computationalName name of computational resource.
* @return 200 OK if operation is successfully triggered
*/
@DELETE
@Path("/{exploratoryName}/{computationalName}/terminate")
@ApiOperation("Terminates computational resource (Dataproc/Spark cluster) on GCP")
@ApiResponses(@ApiResponse(code = 200, message = "Dataproc/Spark cluster on GCP successfully terminated"))
public Response terminate(@ApiParam(hidden = true) @Auth UserInfo userInfo,
@ApiParam(value = "Notebook's name corresponding to computational resource",
required = true)
@PathParam("exploratoryName") String exploratoryName,
@ApiParam(value = "Computational resource's name for terminating", required = true)
@PathParam("computationalName") String computationalName) {
log.debug("Terminating computational resource {} for user {}", computationalName, userInfo.getName());
computationalService.terminateComputational(userInfo, exploratoryName, computationalName);
return Response.ok().build();
}
/**
* Sends request to provisioning service for stopping the computational resource for user.
*
* @param userInfo user info.
* @param exploratoryName name of exploratory.
* @param computationalName name of computational resource.
* @return 200 OK if operation is successfully triggered
*/
@DELETE
@Path("/{exploratoryName}/{computationalName}/stop")
@ApiOperation("Stops Spark cluster on GCP")
@ApiResponses(@ApiResponse(code = 200, message = "Spark cluster on GCP successfully stopped"))
public Response stop(@ApiParam(hidden = true) @Auth UserInfo userInfo,
@ApiParam(value = "Notebook's name corresponding to Spark cluster", required = true)
@PathParam("exploratoryName") String exploratoryName,
@ApiParam(value = "Spark cluster's name for stopping", required = true)
@PathParam("computationalName") String computationalName) {
log.debug("Stopping computational resource {} for user {}", computationalName, userInfo.getName());
computationalService.stopSparkCluster(userInfo, exploratoryName, computationalName);
return Response.ok().build();
}
/**
* Sends request to provisioning service for starting the computational resource for user.
*
* @param userInfo user info.
* @param exploratoryName name of exploratory.
* @param computationalName name of computational resource.
* @return 200 OK if operation is successfully triggered
*/
@PUT
@Path("/{project}/{exploratoryName}/{computationalName}/start")
@ApiOperation("Starts Spark cluster on GCP")
@ApiResponses(@ApiResponse(code = 200, message = "Spark cluster on GCP successfully started"))
public Response start(@ApiParam(hidden = true) @Auth UserInfo userInfo,
@ApiParam(value = "Notebook's name corresponding to Spark cluster", required = true)
@PathParam("exploratoryName") String exploratoryName,
@ApiParam(value = "Spark cluster's name for starting", required = true)
@PathParam("computationalName") String computationalName,
@ApiParam(value = "Project name", required = true)
@PathParam("project") String project) {
log.debug("Starting computational resource {} for user {}", computationalName, userInfo.getName());
computationalService.startSparkCluster(userInfo, exploratoryName, computationalName, project);
return Response.ok().build();
}
@PUT
@Path("dataengine/{exploratoryName}/{computationalName}/config")
@ApiOperation("Updates Spark cluster configuration on AWS")
@ApiResponses(
@ApiResponse(code = 200, message = "Spark cluster configuration on AWS successfully updated")
)
public Response updateDataEngineConfig(@ApiParam(hidden = true) @Auth UserInfo userInfo,
@ApiParam(value = "Notebook's name corresponding to Spark cluster",
required = true)
@PathParam("exploratoryName") String exploratoryName,
@ApiParam(value = "Spark cluster's name for reconfiguring", required = true)
@PathParam("computationalName") String computationalName,
@ApiParam(value = "Spark cluster config", required = true)
@Valid @NotNull List<ClusterConfig> config) {
computationalService.updateSparkClusterConfig(userInfo, exploratoryName, computationalName, config);
return Response.ok().build();
}
@GET
@Path("{exploratoryName}/{computationalName}/config")
@ApiOperation("Returns Spark cluster configuration on AWS")
@ApiResponses(
@ApiResponse(code = 200, message = "Spark cluster configuration on AWS successfully returned")
)
public Response getClusterConfig(@ApiParam(hidden = true) @Auth UserInfo userInfo,
@ApiParam(value = "Notebook's name corresponding to Spark cluster",
required = true)
@PathParam("exploratoryName") String exploratoryName,
@ApiParam(value = "Spark cluster's name for reconfiguring", required = true)
@PathParam("computationalName") String computationalName) {
return Response.ok(computationalService.getClusterConfig(userInfo, exploratoryName, computationalName)).build();
}
private void validate(@Auth UserInfo userInfo, GcpComputationalCreateForm formDTO) {
if (!UserRoles.checkAccess(userInfo, RoleType.COMPUTATIONAL, formDTO.getImage(), userInfo.getRoles())) {
log.warn("Unauthorized attempt to create a {} by user {}", formDTO.getImage(), userInfo.getName());
throw new DlabException("You do not have the privileges to create a " + formDTO.getTemplateName());
}
int slaveInstanceCount = Integer.parseInt(formDTO.getSlaveInstanceCount());
int masterInstanceCount = Integer.parseInt(formDTO.getMasterInstanceCount());
final int total = slaveInstanceCount + masterInstanceCount;
if (total < configuration.getMinInstanceCount()
|| total > configuration.getMaxInstanceCount()) {
log.debug("Creating computational resource {} for user {} fail: Limit exceeded to creation total " +
"instances. Minimum is {}, maximum is {}", formDTO.getName(), userInfo.getName(),
configuration.getMinInstanceCount(), configuration.getMaxInstanceCount());
throw new DlabException("Limit exceeded to creation slave instances. Minimum is " + configuration
.getMinInstanceCount() + ", maximum is " + configuration.getMaxInstanceCount());
}
final int preemptibleInstanceCount = Integer.parseInt(formDTO.getPreemptibleCount());
if (preemptibleInstanceCount < configuration.getMinDataprocPreemptibleCount()) {
log.debug("Creating computational resource {} for user {} fail: Limit exceeded to creation preemptible " +
"instances. Minimum is {}",
formDTO.getName(), userInfo.getName(), configuration.getMinDataprocPreemptibleCount());
throw new DlabException("Limit exceeded to creation preemptible instances. " +
"Minimum is " + configuration.getMinDataprocPreemptibleCount());
}
}
}