/** * Copyright 2015 StreamSets Inc. * * Licensed under 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.streamsets.datacollector.restapi; import com.streamsets.datacollector.execution.AclManager; import com.streamsets.datacollector.execution.Manager; import com.streamsets.datacollector.execution.PreviewOutput; import com.streamsets.datacollector.execution.PreviewStatus; import com.streamsets.datacollector.execution.Previewer; import com.streamsets.datacollector.execution.RawPreview; import com.streamsets.datacollector.main.RuntimeInfo; import com.streamsets.datacollector.main.UserGroupManager; import com.streamsets.datacollector.restapi.bean.BeanHelper; import com.streamsets.datacollector.restapi.bean.PreviewInfoJson; import com.streamsets.datacollector.restapi.bean.PreviewOutputJson; import com.streamsets.datacollector.restapi.bean.StageOutputJson; import com.streamsets.datacollector.restapi.bean.UserJson; import com.streamsets.datacollector.runner.PipelineRuntimeException; import com.streamsets.datacollector.store.AclStoreTask; import com.streamsets.datacollector.store.PipelineInfo; import com.streamsets.datacollector.store.PipelineStoreTask; import com.streamsets.datacollector.util.AuthzRole; import com.streamsets.datacollector.util.Configuration; import com.streamsets.datacollector.util.ContainerError; import com.streamsets.datacollector.util.PipelineException; import com.streamsets.lib.security.http.SSOPrincipal; import com.streamsets.pipeline.api.StageException; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import io.swagger.annotations.Authorization; import javax.annotation.security.DenyAll; import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import java.io.IOException; import java.security.Principal; import java.util.Collections; import java.util.List; @Path("/v1") @Api(value = "preview") @DenyAll public class PreviewResource { private static final String MAX_BATCH_SIZE_KEY = "preview.maxBatchSize"; private static final int MAX_BATCH_SIZE_DEFAULT = 10; private static final String MAX_BATCHES_KEY = "preview.maxBatches"; private static final int MAX_BATCHES_DEFAULT = 10; //preview.maxBatchSize private final Manager manager; private final PipelineStoreTask store; private final Configuration configuration; private final String user; @Inject public PreviewResource( Manager manager, Configuration configuration, Principal principal, PipelineStoreTask store, AclStoreTask aclStore, RuntimeInfo runtimeInfo, UserGroupManager userGroupManager ) { this.configuration = configuration; this.user = principal.getName(); this.store = store; UserJson currentUser; if (runtimeInfo.isDPMEnabled()) { currentUser = new UserJson((SSOPrincipal)principal); } else { currentUser = userGroupManager.getUser(principal); } if (runtimeInfo.isAclEnabled()) { this.manager = new AclManager(manager, aclStore, currentUser); } else { this.manager = manager; } } @Path("/pipeline/{pipelineId}/preview") @POST @ApiOperation(value = "Run Pipeline preview", response = PreviewInfoJson.class, authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.CREATOR_REMOTE, AuthzRole.ADMIN_REMOTE }) public Response previewWithOverride( @PathParam("pipelineId") String pipelineId, @QueryParam("rev") String rev, @QueryParam("batchSize") @DefaultValue("" + Integer.MAX_VALUE) int batchSize, @QueryParam("batches") @DefaultValue("1") int batches, @QueryParam("skipTargets") @DefaultValue("true") boolean skipTargets, @QueryParam("endStage") String endStageInstanceName, @QueryParam("timeout") @DefaultValue("2000") long timeout, @ApiParam(name="stageOutputsToOverrideJson", required = true) List<StageOutputJson> stageOutputsToOverrideJson ) throws PipelineException, StageException { if (stageOutputsToOverrideJson == null) { stageOutputsToOverrideJson = Collections.emptyList(); } PipelineInfo pipelineInfo = store.getInfo(pipelineId); RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId()); int maxBatchSize = configuration.get(MAX_BATCH_SIZE_KEY, MAX_BATCH_SIZE_DEFAULT); batchSize = Math.min(maxBatchSize, batchSize); int maxBatches = configuration.get(MAX_BATCHES_KEY, MAX_BATCHES_DEFAULT); batches = Math.min(maxBatches, batches); Previewer previewer = manager.createPreviewer(this.user, pipelineId, rev); try { previewer.start(batches, batchSize, skipTargets, endStageInstanceName, BeanHelper.unwrapStageOutput(stageOutputsToOverrideJson), timeout); PreviewInfoJson previewInfoJson = new PreviewInfoJson(previewer.getId(), previewer.getStatus()); return Response.ok().type(MediaType.APPLICATION_JSON).entity(previewInfoJson).build(); } catch (PipelineRuntimeException ex) { if (ex.getErrorCode() == ContainerError.CONTAINER_0165) { return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON).entity( BeanHelper.wrapIssues(ex.getIssues())).build(); } else { throw ex; } } } @Path("/pipeline/{pipelineId}/preview/{previewerId}/status") @GET @ApiOperation(value = "Return Preview status by previewer ID", response = PreviewInfoJson.class, authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.CREATOR_REMOTE, AuthzRole.ADMIN_REMOTE }) public Response getPreviewStatus( @PathParam("pipelineId") String pipelineId, @PathParam("previewerId") String previewerId ) throws PipelineException, StageException { Previewer previewer = manager.getPreviewer(previewerId); if(previewer == null) { return Response.status(Response.Status.NOT_FOUND).entity("Cannot find previewer with id " + previewerId).build(); } PipelineInfo pipelineInfo = store.getInfo(previewer.getName()); RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId()); PreviewInfoJson previewInfoJson = new PreviewInfoJson(previewer.getId(), previewer.getStatus()); return Response.ok().type(MediaType.APPLICATION_JSON).entity(previewInfoJson).build(); } @Path("/pipeline/{pipelineId}/preview/{previewerId}") @GET @ApiOperation(value = "Return Preview Data by previewer ID", response = PreviewOutputJson.class, authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.CREATOR_REMOTE, AuthzRole.ADMIN_REMOTE }) public Response getPreviewData( @PathParam("pipelineId") String pipelineId, @PathParam("previewerId") String previewerId ) throws PipelineException, StageException { Previewer previewer = manager.getPreviewer(previewerId); if(previewer == null) { return Response.status(Response.Status.NOT_FOUND).entity("Cannot find previewer with id " + previewerId).build(); } PipelineInfo pipelineInfo = store.getInfo(previewer.getName()); RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId()); PreviewOutput previewOutput = previewer.getOutput(); return Response.ok().type(MediaType.APPLICATION_JSON).entity(BeanHelper.wrapPreviewOutput(previewOutput)).build(); } @Path("/pipeline/{pipelineId}/preview/{previewerId}") @DELETE @ApiOperation(value = "Stop Preview by previewer ID", response = PreviewInfoJson.class, authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.CREATOR_REMOTE, AuthzRole.ADMIN_REMOTE }) public Response stopPreview( @PathParam("pipelineId") String pipelineId, @PathParam("previewerId") String previewerId ) throws PipelineException, StageException { Previewer previewer = manager.getPreviewer(previewerId); if(previewer == null) { return Response.status(Response.Status.NOT_FOUND).entity("Cannot find previewer with id " + previewerId).build(); } PipelineInfo pipelineInfo = store.getInfo(previewer.getName()); RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId()); previewer.stop(); PreviewInfoJson previewInfoJson = new PreviewInfoJson(previewer.getId(), previewer.getStatus()); return Response.ok().type(MediaType.APPLICATION_JSON).entity(previewInfoJson).build(); } @Path("/pipeline/{pipelineId}/rawSourcePreview") @GET @ApiOperation(value = "Get raw source preview data for pipeline name and revision", response = RawPreview.class, authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.CREATOR_REMOTE, AuthzRole.ADMIN_REMOTE }) public Response rawSourcePreview( @PathParam("pipelineId") String pipelineId, @QueryParam("rev") String rev, @Context UriInfo uriInfo ) throws PipelineException, IOException { PipelineInfo pipelineInfo = store.getInfo(pipelineId); RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId()); MultivaluedMap<String, String> previewParams = uriInfo.getQueryParameters(); Previewer previewer = manager.createPreviewer(this.user, pipelineId, rev); RawPreview rawPreview = previewer.getRawSource(4 * 1024, previewParams); return Response.ok().type(MediaType.APPLICATION_JSON).entity(rawPreview).build(); } @Path("/pipeline/{pipelineId}/validate") @GET @ApiOperation(value = "Validate pipeline configuration and return validation status and issues", response = PreviewInfoJson.class, authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ AuthzRole.CREATOR, AuthzRole.MANAGER, AuthzRole.ADMIN, AuthzRole.CREATOR_REMOTE, AuthzRole.MANAGER_REMOTE, AuthzRole.ADMIN_REMOTE }) public Response validateConfigs( @PathParam("pipelineId") String pipelineId, @QueryParam("rev") String rev, @QueryParam("timeout") @DefaultValue("2000") long timeout ) throws PipelineException, StageException { PipelineInfo pipelineInfo = store.getInfo(pipelineId); RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId()); try { Previewer previewer = manager.createPreviewer(this.user, pipelineId, rev); previewer.validateConfigs(timeout); PreviewStatus previewStatus = previewer.getStatus(); if(previewStatus == null) { previewStatus = PreviewStatus.VALIDATING; } PreviewInfoJson previewInfoJson = new PreviewInfoJson(previewer.getId(), previewStatus); return Response.ok().type(MediaType.APPLICATION_JSON).entity(previewInfoJson).build(); } catch (PipelineRuntimeException ex) { if (ex.getErrorCode() == ContainerError.CONTAINER_0165) { return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON).entity( BeanHelper.wrapIssues(ex.getIssues())).build(); } else { throw ex; } } } }