/** * 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.google.common.base.Predicate; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.io.BaseEncoding; import com.streamsets.datacollector.config.DataRuleDefinition; import com.streamsets.datacollector.config.DriftRuleDefinition; import com.streamsets.datacollector.config.MetricElement; import com.streamsets.datacollector.config.MetricType; import com.streamsets.datacollector.config.MetricsRuleDefinition; import com.streamsets.datacollector.config.PipelineConfiguration; import com.streamsets.datacollector.config.RuleDefinitions; import com.streamsets.datacollector.config.StageConfiguration; import com.streamsets.datacollector.config.StageDefinition; import com.streamsets.datacollector.creation.RuleDefinitionsConfigBean; import com.streamsets.datacollector.event.handler.remote.RemoteDataCollector; import com.streamsets.datacollector.execution.Manager; import com.streamsets.datacollector.execution.PipelineState; import com.streamsets.datacollector.execution.PipelineStatus; import com.streamsets.datacollector.main.RuntimeInfo; import com.streamsets.datacollector.main.UserGroupManager; import com.streamsets.datacollector.restapi.bean.AddLabelsRequestJson; import com.streamsets.datacollector.restapi.bean.BeanHelper; import com.streamsets.datacollector.restapi.bean.DefinitionsJson; import com.streamsets.datacollector.restapi.bean.MultiStatusResponseJson; import com.streamsets.datacollector.restapi.bean.PipelineConfigurationJson; import com.streamsets.datacollector.restapi.bean.PipelineDefinitionJson; import com.streamsets.datacollector.restapi.bean.PipelineEnvelopeJson; import com.streamsets.datacollector.restapi.bean.PipelineInfoJson; import com.streamsets.datacollector.restapi.bean.PipelineRulesDefinitionJson; import com.streamsets.datacollector.restapi.bean.PipelineStateJson; import com.streamsets.datacollector.restapi.bean.RuleDefinitionsJson; import com.streamsets.datacollector.restapi.bean.StageDefinitionJson; import com.streamsets.datacollector.restapi.bean.UserJson; import com.streamsets.datacollector.stagelibrary.StageLibraryTask; import com.streamsets.datacollector.store.AclStoreTask; import com.streamsets.datacollector.store.PipelineInfo; import com.streamsets.datacollector.store.PipelineStoreException; import com.streamsets.datacollector.store.PipelineStoreTask; import com.streamsets.datacollector.store.impl.AclPipelineStoreTask; import com.streamsets.datacollector.util.AuthzRole; import com.streamsets.datacollector.util.ContainerError; import com.streamsets.datacollector.util.PipelineException; import com.streamsets.datacollector.validation.PipelineConfigurationValidator; import com.streamsets.datacollector.validation.RuleDefinitionValidator; import com.streamsets.lib.security.http.SSOPrincipal; import com.streamsets.pipeline.api.impl.Utils; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import io.swagger.annotations.Authorization; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.security.DenyAll; import javax.annotation.security.PermitAll; import javax.annotation.security.RolesAllowed; import javax.inject.Inject; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; 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.Response; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriBuilder; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.security.Principal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @Path("/v1") @Api(value = "store") @DenyAll public class PipelineStoreResource { private static final String HIGH_BAD_RECORDS_ID = "badRecordsAlertID"; private static final String HIGH_BAD_RECORDS_TEXT = "High incidence of Error Records"; private static final String HIGH_BAD_RECORDS_METRIC_ID = "pipeline.batchErrorRecords.counter"; private static final String HIGH_BAD_RECORDS_CONDITION = "${value() > 100}"; private static final String HIGH_STAGE_ERRORS_ID = "stageErrorAlertID"; private static final String HIGH_STAGE_ERRORS_TEXT = "High incidence of Stage Errors"; private static final String HIGH_STAGE_ERRORS_METRIC_ID = "pipeline.batchErrorMessages.counter"; private static final String HIGH_STAGE_ERRORS_CONDITION = "${value() > 100}"; private static final String PIPELINE_IDLE_ID = "idleGaugeID"; private static final String PIPELINE_IDLE_TEXT = "Pipeline is Idle"; private static final String PIPELINE_IDLE_METRIC_ID = "RuntimeStatsGauge.gauge"; private static final String PIPELINE_IDLE_CONDITION = "${time:now() - value() > 120000}"; private static final String BATCH_TIME_ID = "batchTimeAlertID"; private static final String BATCH_TIME_TEXT = "Batch taking more time to process"; private static final String BATCH_TIME_METRIC_ID = "RuntimeStatsGauge.gauge"; private static final String BATCH_TIME_CONDITION = "${value() > 200}"; private static final String MEMORY_LIMIt_ID = "memoryLimitAlertID"; private static final String MEMORY_LIMIt_TEXT = "Memory limit for pipeline exceeded"; private static final String MEMORY_LIMIt_METRIC_ID = "pipeline.memoryConsumed.counter"; private static final String MEMORY_LIMIt_CONDITION = "${value() > (jvm:maxMemoryMB() * 0.65)}"; private static final String DPM_PIPELINE_ID = "dpm.pipeline.id"; private static final String SYSTEM_ALL_PIPELINES = "system:allPipelines"; private static final String SYSTEM_PUBLISHED_PIPELINES = "system:publishedPipelines"; private static final String SYSTEM_DPM_CONTROLLED_PIPELINES = "system:dpmControlledPipelines"; private static final String SYSTEM_LOCAL_PIPELINES = "system:localPipelines"; private static final String SYSTEM_RUNNING_PIPELINES = "system:runningPipelines"; private static final String SYSTEM_NON_RUNNING_PIPELINES = "system:nonRunningPipelines"; private static final String SYSTEM_INVALID_PIPELINES = "system:invalidPipelines"; private static final String SYSTEM_ERROR_PIPELINES = "system:errorPipelines"; private static final String SHARED_WITH_ME_PIPELINES = "system:sharedWithMePipelines"; private static final List<String> SYSTEM_PIPELINE_LABELS = ImmutableList.of( SYSTEM_ALL_PIPELINES, SYSTEM_RUNNING_PIPELINES, SYSTEM_NON_RUNNING_PIPELINES, SYSTEM_INVALID_PIPELINES, SYSTEM_ERROR_PIPELINES, SHARED_WITH_ME_PIPELINES ); private static final List<String> DPM_ENABLED_SYSTEM_PIPELINE_LABELS = ImmutableList.of( SYSTEM_ALL_PIPELINES, SYSTEM_PUBLISHED_PIPELINES, SYSTEM_DPM_CONTROLLED_PIPELINES, SYSTEM_LOCAL_PIPELINES, SYSTEM_RUNNING_PIPELINES, SYSTEM_NON_RUNNING_PIPELINES, SYSTEM_INVALID_PIPELINES, SYSTEM_ERROR_PIPELINES, SHARED_WITH_ME_PIPELINES ); private static final Logger LOG = LoggerFactory.getLogger(PipelineStoreResource.class); private final RuntimeInfo runtimeInfo; private final Manager manager; private final PipelineStoreTask store; private final StageLibraryTask stageLibrary; private final URI uri; private final String user; @Inject public PipelineStoreResource( URI uri, Principal principal, StageLibraryTask stageLibrary, PipelineStoreTask store, RuntimeInfo runtimeInfo, Manager manager, UserGroupManager userGroupManager, AclStoreTask aclStore ) { this.uri = uri; this.user = principal.getName(); this.stageLibrary = stageLibrary; this.runtimeInfo = runtimeInfo; this.manager = manager; UserJson currentUser; if (runtimeInfo.isDPMEnabled()) { currentUser = new UserJson((SSOPrincipal)principal); } else { currentUser = userGroupManager.getUser(principal); } if (runtimeInfo.isAclEnabled()) { this.store = new AclPipelineStoreTask(store, aclStore, currentUser); } else { this.store = store; } } @Path("/pipelines/count") @GET @ApiOperation(value = "Returns total Pipelines count", response = Map.class, responseContainer = "List", authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @PermitAll public Response getPipelinesCount() throws PipelineStoreException { return Response.ok() .type(MediaType.APPLICATION_JSON) .entity(ImmutableMap.of("count", store.getPipelines().size())) .build(); } @Path("/pipelines/systemLabels") @GET @ApiOperation(value = "Returns System Pipeline Labels", response = List.class, responseContainer = "List", authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @PermitAll public Response getSystemPipelineLabels() throws PipelineStoreException { return Response.ok() .type(MediaType.APPLICATION_JSON) .entity(runtimeInfo.isDPMEnabled() ? DPM_ENABLED_SYSTEM_PIPELINE_LABELS : SYSTEM_PIPELINE_LABELS) .build(); } @Path("/pipelines/labels") @GET @ApiOperation(value = "Returns all Pipeline labels", response = List.class, responseContainer = "List", authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @PermitAll public Response getPipelineLabels() throws PipelineStoreException { final List<PipelineInfo> pipelineInfoList = store.getPipelines(); Set<String> pipelineLabels = new HashSet<>(); for (PipelineInfo pipelineInfo: pipelineInfoList) { Map<String, Object> metadata = pipelineInfo.getMetadata(); if (metadata != null && metadata.containsKey("labels")) { List<String> labels = (List<String>) metadata.get("labels"); pipelineLabels.addAll(labels); } } return Response.ok() .type(MediaType.APPLICATION_JSON) .entity(pipelineLabels) .build(); } @Path("/pipelines") @GET @ApiOperation(value = "Returns all Pipeline Configuration Info", response = PipelineInfoJson.class, responseContainer = "List", authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @PermitAll public Response getPipelines( @QueryParam("filterText") @DefaultValue("") final String filterText, @QueryParam("label") final String label, @QueryParam("offset") @DefaultValue("0") int offset, @QueryParam("len") @DefaultValue("-1") int len, @QueryParam("orderBy") @DefaultValue("NAME") final PipelineOrderByFields orderBy, @QueryParam("order") @DefaultValue("ASC") final Order order, @QueryParam("includeStatus") @DefaultValue("false") boolean includeStatus ) throws PipelineException { RestAPIUtils.injectPipelineInMDC("*"); final List<PipelineInfo> pipelineInfoList = store.getPipelines(); final Map<String, PipelineState> pipelineStateCache = new HashMap<>(); Collection<PipelineInfo> filteredCollection = Collections2.filter(pipelineInfoList, new Predicate<PipelineInfo>() { @Override public boolean apply(PipelineInfo pipelineInfo) { String title = pipelineInfo.getTitle() != null ? pipelineInfo.getTitle() : pipelineInfo.getPipelineId(); if (filterText != null && !title.toLowerCase().contains(filterText.toLowerCase())) { return false; } if (label != null) { try { Map<String, Object> metadata = pipelineInfo.getMetadata(); switch (label) { case SYSTEM_ALL_PIPELINES: return true; case SYSTEM_RUNNING_PIPELINES: PipelineState state = manager.getPipelineState(pipelineInfo.getPipelineId(), pipelineInfo.getLastRev()); pipelineStateCache.put(pipelineInfo.getPipelineId(), state); return state.getStatus().isActive(); case SYSTEM_NON_RUNNING_PIPELINES: state = manager.getPipelineState(pipelineInfo.getPipelineId(), pipelineInfo.getLastRev()); pipelineStateCache.put(pipelineInfo.getPipelineId(), state); return !state.getStatus().isActive(); case SYSTEM_INVALID_PIPELINES: return !pipelineInfo.isValid(); case SYSTEM_ERROR_PIPELINES: state = manager.getPipelineState(pipelineInfo.getPipelineId(), pipelineInfo.getLastRev()); pipelineStateCache.put(pipelineInfo.getPipelineId(), state); PipelineStatus status = state.getStatus(); return status == PipelineStatus.START_ERROR || status == PipelineStatus.RUNNING_ERROR || status == PipelineStatus.RUN_ERROR || status == PipelineStatus.CONNECT_ERROR; case SYSTEM_PUBLISHED_PIPELINES: state = manager.getPipelineState(pipelineInfo.getPipelineId(), pipelineInfo.getLastRev()); pipelineStateCache.put(pipelineInfo.getPipelineId(), state); return !isRemotePipeline(state) && metadata != null && metadata.containsKey(DPM_PIPELINE_ID); case SYSTEM_DPM_CONTROLLED_PIPELINES: state = manager.getPipelineState(pipelineInfo.getPipelineId(), pipelineInfo.getLastRev()); pipelineStateCache.put(pipelineInfo.getPipelineId(), state); return isRemotePipeline(state); case SYSTEM_LOCAL_PIPELINES: return metadata == null || !metadata.containsKey(DPM_PIPELINE_ID); case SHARED_WITH_ME_PIPELINES: return !pipelineInfo.getCreator().equals(user); default: if (metadata != null && metadata.containsKey("labels")) { List<String> labels = (List<String>) metadata.get("labels"); if (!labels.contains(label)) { return false; } } else { return false; } } } catch (PipelineException e) { e.printStackTrace(); } } return true; } }); List<PipelineInfo> filteredList = new ArrayList<>(filteredCollection); Collections.sort(filteredList, new Comparator<PipelineInfo>() { @Override public int compare(PipelineInfo p1, PipelineInfo p2) { if (order.equals(Order.DESC)) { PipelineInfo tmp = p1; p1 = p2; p2 = tmp; } if (orderBy.equals(PipelineOrderByFields.NAME)) { return p1.getPipelineId().compareTo(p2.getPipelineId()); } if (orderBy.equals(PipelineOrderByFields.TITLE)) { String p1Title = p1.getTitle() != null ? p1.getTitle() : p1.getPipelineId(); String p2Title = p2.getTitle() != null ? p2.getTitle() : p2.getPipelineId(); return p1Title.compareTo(p2Title); } if (orderBy.equals(PipelineOrderByFields.LAST_MODIFIED)) { return p2.getLastModified().compareTo(p1.getLastModified()); } if (orderBy.equals(PipelineOrderByFields.CREATED)) { return p2.getCreated().compareTo(p1.getCreated()); } if (orderBy.equals(PipelineOrderByFields.CREATOR)) { return p1.getCreator().compareTo(p2.getCreator()); } if(orderBy.equals(PipelineOrderByFields.STATUS)) { try { PipelineState p1State = null; PipelineState p2State = null; if (pipelineStateCache.containsKey(p1.getPipelineId())) { p1State = pipelineStateCache.get(p1.getPipelineId()); } else { p1State = manager.getPipelineState(p1.getPipelineId(), p1.getLastRev()); pipelineStateCache.put(p1.getPipelineId(), p1State); } if (pipelineStateCache.containsKey(p2.getPipelineId())) { p2State = pipelineStateCache.get(p2.getPipelineId()); } else { p2State = manager.getPipelineState(p2.getPipelineId(), p2.getLastRev()); pipelineStateCache.put(p2.getPipelineId(), p2State); } if (p1State != null && p2State != null) { return p1State.getStatus().compareTo(p2State.getStatus()); } } catch (PipelineException e) { LOG.debug("Failed to get Pipeline State - " + e.getLocalizedMessage()); } } return 0; } }); Object responseData; if (filteredList.size() > 0) { int endIndex = offset + len; if (len == -1 || endIndex > filteredList.size()) { endIndex = filteredList.size(); } List<PipelineInfoJson> subList = BeanHelper.wrapPipelineInfo(filteredList.subList(offset, endIndex)); if (includeStatus) { List<PipelineStateJson> statusList = new ArrayList<>(subList.size()); for (PipelineInfoJson pipelineInfoJson: subList) { PipelineState state = pipelineStateCache.get(pipelineInfoJson.getPipelineId()); if (state == null) { state = manager.getPipelineState(pipelineInfoJson.getPipelineId(), pipelineInfoJson.getLastRev()); } if(state != null) { statusList.add(BeanHelper.wrapPipelineState(state, true)); } } responseData = ImmutableList.of(subList, statusList); } else { responseData = subList; } } else { if (includeStatus) { responseData = ImmutableList.of(Collections.emptyList(), Collections.emptyList()); } else { responseData = Collections.emptyList(); } } return Response.ok() .type(MediaType.APPLICATION_JSON) .entity(responseData) .header("TOTAL_COUNT", filteredList.size()) .build(); } @Path("/pipelines/delete") @POST @ApiOperation(value = "Deletes Pipelines", response = PipelineInfoJson.class, responseContainer = "List", authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.CREATOR_REMOTE, AuthzRole.ADMIN_REMOTE }) public Response deletePipelines( List<String> pipelineIds, @Context SecurityContext context ) throws PipelineException { RestAPIUtils.injectPipelineInMDC("*"); for(String pipelineId: pipelineIds) { if (store.isRemotePipeline(pipelineId, "0") && !context.isUserInRole(AuthzRole.ADMIN) && !context.isUserInRole(AuthzRole.ADMIN_REMOTE)) { throw new PipelineException(ContainerError.CONTAINER_01101, "DELETE_PIPELINE", pipelineId); } store.deleteRules(pipelineId); store.delete(pipelineId); } return Response.ok().build(); } @Path("/pipelines/deleteByFiltering") @POST @ApiOperation(value = "Deletes filtered Pipelines", response = PipelineInfoJson.class, responseContainer = "List", authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.CREATOR_REMOTE, AuthzRole.ADMIN_REMOTE }) public Response deletePipelinesByFiltering( @QueryParam("filterText") @DefaultValue("") String filterText, @QueryParam("label") String label, @Context SecurityContext context ) throws PipelineException { RestAPIUtils.injectPipelineInMDC("*"); List<PipelineInfo> pipelineInfoList = store.getPipelines(); List<String> deletepipelineIds = new ArrayList<>(); for(PipelineInfo pipelineInfo: pipelineInfoList) { if (filterText != null && !pipelineInfo.getPipelineId().toLowerCase().contains(filterText.toLowerCase())) { continue; } if (label != null) { Map<String, Object> metadata = pipelineInfo.getMetadata(); if (metadata != null && metadata.containsKey("labels")) { List<String> labels = (List<String>) metadata.get("labels"); if (!labels.contains(label)) { continue; } } else { continue; } } if (store.isRemotePipeline(pipelineInfo.getPipelineId(), "0") && !context.isUserInRole(AuthzRole.ADMIN) && !context.isUserInRole(AuthzRole.ADMIN_REMOTE)) { continue; } store.deleteRules(pipelineInfo.getPipelineId()); store.delete(pipelineInfo.getPipelineId()); deletepipelineIds.add(pipelineInfo.getPipelineId()); } return Response.ok().entity(deletepipelineIds).build(); } @Path("/pipeline/{pipelineId}") @GET @ApiOperation(value = "Find Pipeline Configuration by name and revision", response = PipelineConfigurationJson.class, authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @PermitAll public Response getPipelineInfo( @PathParam("pipelineId") String name, @QueryParam("rev") @DefaultValue("0") String rev, @QueryParam("get") @DefaultValue("pipeline") String get, @QueryParam("attachment") @DefaultValue("false") Boolean attachment ) throws PipelineException, URISyntaxException { PipelineInfo pipelineInfo = store.getInfo(name); RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId()); Object data; String title = name; if (get.equals("pipeline")) { PipelineConfiguration pipeline = store.load(name, rev); PipelineConfigurationValidator validator = new PipelineConfigurationValidator(stageLibrary, name, pipeline); pipeline = validator.validate(); data = BeanHelper.wrapPipelineConfiguration(pipeline); title = pipeline.getTitle() != null ? pipeline.getTitle() : pipeline.getInfo().getPipelineId(); } else if (get.equals("info")) { data = BeanHelper.wrapPipelineInfo(store.getInfo(name)); } else if (get.equals("history")) { data = BeanHelper.wrapPipelineRevInfo(store.getHistory(name)); } else { throw new IllegalArgumentException(Utils.format("Invalid value for parameter 'get': {}", get)); } if (attachment) { Map<String, Object> envelope = new HashMap<String, Object>(); envelope.put("pipelineConfig", data); RuleDefinitions ruleDefinitions = store.retrieveRules(name, rev); envelope.put("pipelineRules", BeanHelper.wrapRuleDefinitions(ruleDefinitions)); return Response.ok(). header("Content-Disposition", "attachment; filename=\"" + title + ".json\""). type(MediaType.APPLICATION_JSON).entity(envelope).build(); } else { return Response.ok().type(MediaType.APPLICATION_JSON).entity(data).build(); } } @Path("/pipeline/{pipelineTitle}") @PUT @ApiOperation(value = "Add a new Pipeline Configuration to the store", response = PipelineConfigurationJson.class, authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.CREATOR_REMOTE, AuthzRole.ADMIN_REMOTE }) public Response createPipeline( @PathParam("pipelineTitle") String pipelineTitle, @QueryParam("description") @DefaultValue("") String description, @QueryParam("autoGeneratePipelineId") @DefaultValue("false") boolean autoGeneratePipelineId ) throws URISyntaxException, PipelineException { String pipelineId = pipelineTitle; if (autoGeneratePipelineId) { pipelineId = pipelineTitle.replaceAll("[\\W]|_", "") + UUID.randomUUID().toString(); } RestAPIUtils.injectPipelineInMDC(pipelineTitle + "/" + pipelineId); PipelineConfiguration pipeline = store.create(user, pipelineId, pipelineTitle, description, false); //Add predefined Metric Rules to the pipeline List<MetricsRuleDefinition> metricsRuleDefinitions = new ArrayList<>(); long timestamp = System.currentTimeMillis(); metricsRuleDefinitions.add(new MetricsRuleDefinition(HIGH_BAD_RECORDS_ID, HIGH_BAD_RECORDS_TEXT, HIGH_BAD_RECORDS_METRIC_ID, MetricType.COUNTER, MetricElement.COUNTER_COUNT, HIGH_BAD_RECORDS_CONDITION, false, false, timestamp)); metricsRuleDefinitions.add(new MetricsRuleDefinition(HIGH_STAGE_ERRORS_ID, HIGH_STAGE_ERRORS_TEXT, HIGH_STAGE_ERRORS_METRIC_ID, MetricType.COUNTER, MetricElement.COUNTER_COUNT, HIGH_STAGE_ERRORS_CONDITION, false, false, timestamp)); metricsRuleDefinitions.add(new MetricsRuleDefinition(PIPELINE_IDLE_ID, PIPELINE_IDLE_TEXT, PIPELINE_IDLE_METRIC_ID, MetricType.GAUGE, MetricElement.TIME_OF_LAST_RECEIVED_RECORD, PIPELINE_IDLE_CONDITION, false, false, timestamp)); metricsRuleDefinitions.add(new MetricsRuleDefinition(BATCH_TIME_ID, BATCH_TIME_TEXT, BATCH_TIME_METRIC_ID, MetricType.GAUGE, MetricElement.CURRENT_BATCH_AGE, BATCH_TIME_CONDITION, false, false, timestamp)); metricsRuleDefinitions.add(new MetricsRuleDefinition(MEMORY_LIMIt_ID, MEMORY_LIMIt_TEXT, MEMORY_LIMIt_METRIC_ID, MetricType.COUNTER, MetricElement.COUNTER_COUNT, MEMORY_LIMIt_CONDITION, false, false, timestamp)); RuleDefinitions ruleDefinitions = new RuleDefinitions( PipelineStoreTask.RULE_DEFINITIONS_SCHEMA_VERSION, RuleDefinitionsConfigBean.VERSION, metricsRuleDefinitions, Collections.<DataRuleDefinition>emptyList(), Collections.<DriftRuleDefinition>emptyList(), Collections.<String>emptyList(), null, stageLibrary.getPipelineRules().getPipelineRulesDefaultConfigs() ); store.storeRules(pipelineId, "0", ruleDefinitions); PipelineConfigurationValidator validator = new PipelineConfigurationValidator(stageLibrary, pipelineId, pipeline); pipeline = validator.validate(); return Response.created(UriBuilder.fromUri(uri).path(pipelineId).build()).entity( BeanHelper.wrapPipelineConfiguration(pipeline)).build(); } @Path("/pipeline/{pipelineId}") @DELETE @ApiOperation(value = "Delete Pipeline Configuration by name", authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.CREATOR_REMOTE, AuthzRole.ADMIN_REMOTE }) public Response deletePipeline( @PathParam("pipelineId") String name, @Context SecurityContext context ) throws URISyntaxException, PipelineException { PipelineInfo pipelineInfo = store.getInfo(name); RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId()); if (store.isRemotePipeline(name, "0") && !context.isUserInRole(AuthzRole.ADMIN) && !context.isUserInRole(AuthzRole.ADMIN_REMOTE)) { throw new PipelineException(ContainerError.CONTAINER_01101, "DELETE_PIPELINE", name); } store.deleteRules(name); store.delete(name); return Response.ok().build(); } @Path("/pipeline/{pipelineId}") @POST @ApiOperation(value = "Update an existing Pipeline Configuration by name", response = PipelineConfigurationJson.class, authorizations = @Authorization(value = "basic")) @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.CREATOR_REMOTE, AuthzRole.ADMIN_REMOTE }) public Response savePipeline( @PathParam("pipelineId") String name, @QueryParam("rev") @DefaultValue("0") String rev, @QueryParam("description") String description, @ApiParam(name="pipeline", required = true) PipelineConfigurationJson pipeline) throws URISyntaxException, PipelineException { if (store.isRemotePipeline(name, rev)) { throw new PipelineException(ContainerError.CONTAINER_01101, "SAVE_PIPELINE", name); } PipelineInfo pipelineInfo = store.getInfo(name); RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId()); PipelineConfiguration pipelineConfig = BeanHelper.unwrapPipelineConfiguration(pipeline); PipelineConfigurationValidator validator = new PipelineConfigurationValidator(stageLibrary, name, pipelineConfig); pipelineConfig = validator.validate(); pipelineConfig = store.save(user, name, rev, description, pipelineConfig); return Response.ok().entity(BeanHelper.wrapPipelineConfiguration(pipelineConfig)).build(); } @Path("/pipeline/{pipelineId}/uiInfo") @POST @ApiOperation(value ="", hidden = true) @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.CREATOR_REMOTE, AuthzRole.ADMIN_REMOTE }) @SuppressWarnings("unchecked") public Response saveUiInfo( @PathParam("pipelineId") String name, @QueryParam("rev") @DefaultValue("0") String rev, Map uiInfo ) throws PipelineException, URISyntaxException { PipelineInfo pipelineInfo = store.getInfo(name); RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId()); store.saveUiInfo(name, rev, uiInfo); return Response.ok().build(); } @Path("/pipeline/{pipelineId}/rules") @GET @ApiOperation(value = "Find Pipeline Rules by name and revision", response = RuleDefinitionsJson.class, authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @PermitAll public Response getPipelineRules( @PathParam("pipelineId") String name, @QueryParam("rev") @DefaultValue("0") String rev ) throws PipelineException { PipelineInfo pipelineInfo = store.getInfo(name); RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId()); RuleDefinitions ruleDefinitions = store.retrieveRules(name, rev); if(ruleDefinitions != null) { RuleDefinitionValidator ruleDefinitionValidator = new RuleDefinitionValidator(); ruleDefinitionValidator.validateRuleDefinition(ruleDefinitions); } return Response.ok().type(MediaType.APPLICATION_JSON).entity( BeanHelper.wrapRuleDefinitions(ruleDefinitions)).build(); } @Path("/pipeline/{pipelineId}/rules") @POST @ApiOperation(value = "Update an existing Pipeline Rules by name", response = RuleDefinitionsJson.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 savePipelineRules( @PathParam("pipelineId") String name, @QueryParam("rev") @DefaultValue("0") String rev, @ApiParam(name="pipeline", required = true) RuleDefinitionsJson ruleDefinitionsJson ) throws PipelineException { if (store.isRemotePipeline(name, rev)) { throw new PipelineException(ContainerError.CONTAINER_01101, "SAVE_RULES_PIPELINE", name); } PipelineInfo pipelineInfo = store.getInfo(name); RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId()); RuleDefinitions ruleDefs = BeanHelper.unwrapRuleDefinitions(ruleDefinitionsJson); RuleDefinitionValidator ruleDefinitionValidator = new RuleDefinitionValidator(); ruleDefinitionValidator.validateRuleDefinition(ruleDefs); ruleDefs = store.storeRules(name, rev, ruleDefs); return Response.ok().type(MediaType.APPLICATION_JSON).entity(BeanHelper.wrapRuleDefinitions(ruleDefs)).build(); } @Path("/pipeline/{pipelineId}/export") @GET @ApiOperation(value = "Export Pipeline Configuration & Rules by name and revision", response = PipelineEnvelopeJson.class, authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @PermitAll public Response exportPipeline( @PathParam("pipelineId") String name, @QueryParam("rev") @DefaultValue("0") String rev, @QueryParam("attachment") @DefaultValue("false") Boolean attachment, @QueryParam("includeLibraryDefinitions") @DefaultValue("false") boolean includeLibraryDefinitions ) throws PipelineException, URISyntaxException { PipelineInfo pipelineInfo = store.getInfo(name); RestAPIUtils.injectPipelineInMDC(pipelineInfo.getTitle(), pipelineInfo.getPipelineId()); PipelineConfiguration pipelineConfig = store.load(name, rev); PipelineConfigurationValidator validator = new PipelineConfigurationValidator(stageLibrary, name, pipelineConfig); pipelineConfig = validator.validate(); RuleDefinitions ruleDefinitions = store.retrieveRules(name, rev); PipelineEnvelopeJson pipelineEnvelope = new PipelineEnvelopeJson(); pipelineEnvelope.setPipelineConfig(BeanHelper.wrapPipelineConfiguration(pipelineConfig)); pipelineEnvelope.setPipelineRules(BeanHelper.wrapRuleDefinitions(ruleDefinitions)); if (includeLibraryDefinitions) { DefinitionsJson definitions = new DefinitionsJson(); // Add only stage definitions for stages present in pipeline config List<StageDefinition> stageDefinitions = new ArrayList<>(); Map<String, String> stageIcons = new HashMap<>(); for (StageConfiguration conf : pipelineConfig.getStages()) { fetchStageDefinition(conf, stageDefinitions, stageIcons); } StageConfiguration errorStageConfig = pipelineConfig.getErrorStage(); if (errorStageConfig != null) { fetchStageDefinition(errorStageConfig, stageDefinitions, stageIcons); } StageConfiguration statsAggregatorStageConfig = pipelineConfig.getStatsAggregatorStage(); if (statsAggregatorStageConfig != null) { fetchStageDefinition(statsAggregatorStageConfig, stageDefinitions, stageIcons); } List<StageDefinitionJson> stages = new ArrayList<>(); stages.addAll(BeanHelper.wrapStageDefinitions(stageDefinitions)); definitions.setStages(stages); definitions.setStageIcons(stageIcons); List<PipelineDefinitionJson> pipeline = new ArrayList<>(1); pipeline.add(BeanHelper.wrapPipelineDefinition(stageLibrary.getPipeline())); definitions.setPipeline(pipeline); List<PipelineRulesDefinitionJson> pipelineRules = new ArrayList<>(1); pipelineRules.add(BeanHelper.wrapPipelineRulesDefinition(stageLibrary.getPipelineRules())); definitions.setPipelineRules(pipelineRules); pipelineEnvelope.setLibraryDefinitions(definitions); } if (attachment) { String fileName = pipelineConfig.getTitle() != null ? pipelineConfig.getTitle() : pipelineConfig.getInfo().getPipelineId(); return Response.ok(). header("Content-Disposition", "attachment; filename=\"" + fileName + ".json\""). type(MediaType.APPLICATION_JSON).entity(pipelineEnvelope).build(); } else { return Response.ok(). type(MediaType.APPLICATION_JSON).entity(pipelineEnvelope).build(); } } private void fetchStageDefinition( StageConfiguration conf, List<StageDefinition> stageDefinitions, Map<String, String> stageIcons ) { String key = conf.getLibrary() + ":" + conf.getStageName(); if (!stageIcons.containsKey(key)) { StageDefinition stageDefinition = stageLibrary.getStage(conf.getLibrary(), conf.getStageName(), false); if (stageDefinition != null) { stageDefinitions.add(stageDefinition); String iconFile = stageDefinition.getIcon(); if (iconFile != null && iconFile.trim().length() > 0) { try(InputStream icon = stageDefinition.getStageClassLoader().getResourceAsStream(iconFile)) { stageIcons.put(key, BaseEncoding.base64().encode(IOUtils.toByteArray(icon))); } catch (Exception e) { LOG.debug("Failed to convert stage icons to Base64 - " + e.getLocalizedMessage()); stageIcons.put(key, null); } } else { stageIcons.put(key, null); } } } } @Path("/pipeline/{pipelineId}/import") @POST @ApiOperation(value = "Import Pipeline Configuration & Rules", response = PipelineEnvelopeJson.class, authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @PermitAll public Response importPipeline( @PathParam("pipelineId") String name, @QueryParam("rev") @DefaultValue("0") String rev, @QueryParam("overwrite") @DefaultValue("false") boolean overwrite, @QueryParam("autoGenerateName") @DefaultValue("false") boolean autoGenerateName, @ApiParam(name="pipelineEnvelope", required = true) PipelineEnvelopeJson pipelineEnvelope ) throws PipelineException, URISyntaxException { RestAPIUtils.injectPipelineInMDC("*"); PipelineConfigurationJson pipelineConfigurationJson = pipelineEnvelope.getPipelineConfig(); PipelineConfiguration pipelineConfig = BeanHelper.unwrapPipelineConfiguration(pipelineConfigurationJson); PipelineConfigurationValidator validator = new PipelineConfigurationValidator(stageLibrary, name, pipelineConfig); pipelineConfig = validator.validate(); RuleDefinitionsJson ruleDefinitionsJson = pipelineEnvelope.getPipelineRules(); RuleDefinitions ruleDefinitions = BeanHelper.unwrapRuleDefinitions(ruleDefinitionsJson); PipelineConfiguration newPipelineConfig; RuleDefinitions newRuleDefinitions; String label = name; if (overwrite) { if (store.hasPipeline(name)) { newPipelineConfig = store.load(name, rev); } else { if (autoGenerateName) { name = UUID.randomUUID().toString(); } newPipelineConfig = store.create(user, name, label, pipelineConfig.getDescription(), false); } } else { if (autoGenerateName) { name = UUID.randomUUID().toString(); } newPipelineConfig = store.create(user, name, label, pipelineConfig.getDescription(), false); } newRuleDefinitions = store.retrieveRules(name, rev); pipelineConfig.setUuid(newPipelineConfig.getUuid()); pipelineConfig = store.save(user, name, rev, pipelineConfig.getDescription(), pipelineConfig); ruleDefinitions.setUuid(newRuleDefinitions.getUuid()); ruleDefinitions = store.storeRules(name, rev, ruleDefinitions); pipelineEnvelope.setPipelineConfig(BeanHelper.wrapPipelineConfiguration(pipelineConfig)); pipelineEnvelope.setPipelineRules(BeanHelper.wrapRuleDefinitions(ruleDefinitions)); return Response.ok(). type(MediaType.APPLICATION_JSON).entity(pipelineEnvelope).build(); } @Path("/pipelines/addLabels") @POST @ApiOperation(value = "Add labels to multiple Pipelines", response = MultiStatusResponseJson.class, authorizations = @Authorization(value = "basic")) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({ AuthzRole.CREATOR, AuthzRole.ADMIN, AuthzRole.MANAGER_REMOTE, AuthzRole.ADMIN_REMOTE }) public Response addLabelsToPipelines(AddLabelsRequestJson addLabelsRequestJson) throws PipelineException { List<String> labels = addLabelsRequestJson.getLabels(); List<String> pipelineIds = addLabelsRequestJson.getPipelineNames(); List<String> successEntities = new ArrayList<>(); List<String> errorMessages = new ArrayList<>(); for (String pipelineId: pipelineIds) { try { PipelineConfiguration pipelineConfig = store.load(pipelineId, "0"); Map<String, Object> metadata = pipelineConfig.getMetadata(); Object objLabels = metadata.get("labels"); List<String> metaLabels = objLabels == null ? new ArrayList<String>() : (List<String>) objLabels; for (String label : labels) { if (!metaLabels.contains(label)) { metaLabels.add(label); } } metadata.put("labels", metaLabels); RestAPIUtils.injectPipelineInMDC(pipelineConfig.getInfo().getTitle(), pipelineId); PipelineConfigurationValidator validator = new PipelineConfigurationValidator(stageLibrary, pipelineId, pipelineConfig); pipelineConfig = validator.validate(); store.save(user, pipelineId, "0", pipelineConfig.getDescription(), pipelineConfig); successEntities.add(pipelineId); } catch (Exception ex) { errorMessages.add("Failed adding labels " + labels + " to pipeline: " + pipelineId + ". Error: " + ex.getMessage()); } } return Response.status(207) .type(MediaType.APPLICATION_JSON) .entity(new MultiStatusResponseJson<>(successEntities, errorMessages)).build(); } private boolean isRemotePipeline(PipelineState state) { Object isRemote = state.getAttributes().get(RemoteDataCollector.IS_REMOTE_PIPELINE); return isRemote != null && (boolean) isRemote; } }