/** * 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.validation; import com.google.common.base.Preconditions; import com.streamsets.datacollector.config.DataRuleDefinition; import com.streamsets.datacollector.config.MetricsRuleDefinition; import com.streamsets.datacollector.config.RuleDefinitions; import com.streamsets.datacollector.config.ThresholdType; import com.streamsets.datacollector.el.ELEvaluator; import com.streamsets.datacollector.el.ELVariables; import com.streamsets.datacollector.el.RuleELRegistry; import com.streamsets.pipeline.api.Field; import com.streamsets.pipeline.api.Record; import com.streamsets.pipeline.api.el.ELEvalException; import com.streamsets.pipeline.lib.el.RecordEL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; public class RuleDefinitionValidator { private static final Logger LOG = LoggerFactory.getLogger(RuleDefinitionValidator.class); private static final String LABEL = "label"; private static final String CONDITION = "condition"; private static final String VAL = "value()"; private static final String TIME_NOW = "time:now()"; private static final String METRIC_ID = "metric id"; private static final String DEFAULT_VALUE = "10"; private static final String PROPERTY = "property"; private static final int MIN_PERCENTAGE = 0; private static final int MAX_PERCENTAGE = 100; private static final String THRESHOLD_VALUE = "Threshold Value"; private static final String EMAIL_IDS = "emailIds"; private static final String SAMPLING_PERCENTAGE = "Sampling Percentage"; private final ELEvaluator elEvaluator; private final ELVariables variables; public RuleDefinitionValidator() { variables = new ELVariables(); elEvaluator = new ELEvaluator("RuleDefinitionValidator", RuleELRegistry.getRuleELs(RuleELRegistry.GENERAL)); } public boolean validateRuleDefinition(RuleDefinitions ruleDefinitions) { Preconditions.checkNotNull(ruleDefinitions); List<RuleIssue> ruleIssues = new ArrayList<>(); for(DataRuleDefinition dataRuleDefinition : ruleDefinitions.getDataRuleDefinitions()) { //reset valid flag before validating dataRuleDefinition.setValid(true); String ruleId = dataRuleDefinition.getId(); if(dataRuleDefinition.getLabel() == null || dataRuleDefinition.getLabel().isEmpty()) { RuleIssue r = RuleIssue.createRuleIssue(ruleId, ValidationError.VALIDATION_0040, LABEL); r.setAdditionalInfo(PROPERTY, LABEL); ruleIssues.add(r); dataRuleDefinition.setValid(false); } if(dataRuleDefinition.getCondition() == null || dataRuleDefinition.getCondition().isEmpty()) { RuleIssue r = RuleIssue.createRuleIssue(ruleId, ValidationError.VALIDATION_0040, CONDITION); r.setAdditionalInfo(PROPERTY, CONDITION); ruleIssues.add(r); dataRuleDefinition.setValid(false); } else { //validate the condition el expression RuleIssue issue = validateDataRuleExpressions(dataRuleDefinition.getCondition(), ruleId); if(issue != null) { issue.setAdditionalInfo(PROPERTY, CONDITION); ruleIssues.add(issue); dataRuleDefinition.setValid(false); } } if(dataRuleDefinition.getSamplingPercentage() < MIN_PERCENTAGE || dataRuleDefinition.getSamplingPercentage() > MAX_PERCENTAGE) { RuleIssue r = RuleIssue.createRuleIssue(ruleId, ValidationError.VALIDATION_0041); r.setAdditionalInfo(PROPERTY, SAMPLING_PERCENTAGE); ruleIssues.add(r); dataRuleDefinition.setValid(false); } if(dataRuleDefinition.isSendEmail() && (ruleDefinitions.getEmailIds() == null || ruleDefinitions.getEmailIds().isEmpty())) { RuleIssue r = RuleIssue.createRuleIssue(ruleId, ValidationError.VALIDATION_0042); r.setAdditionalInfo(PROPERTY, EMAIL_IDS); ruleIssues.add(r); dataRuleDefinition.setValid(false); } double threshold; try { threshold = Double.parseDouble(dataRuleDefinition.getThresholdValue()); if(dataRuleDefinition.getThresholdType() == ThresholdType.PERCENTAGE) { if(threshold < MIN_PERCENTAGE || threshold > MAX_PERCENTAGE) { RuleIssue r = RuleIssue.createRuleIssue(ruleId, ValidationError.VALIDATION_0044); r.setAdditionalInfo(PROPERTY, THRESHOLD_VALUE); ruleIssues.add(r); dataRuleDefinition.setValid(false); } } } catch (NumberFormatException e) { RuleIssue r = RuleIssue.createRuleIssue(ruleId, ValidationError.VALIDATION_0043); r.setAdditionalInfo(PROPERTY, THRESHOLD_VALUE); ruleIssues.add(r); dataRuleDefinition.setValid(false); } } for(MetricsRuleDefinition metricsRuleDefinition : ruleDefinitions.getMetricsRuleDefinitions()) { String ruleId = metricsRuleDefinition.getId(); //reset valid flag before validating metricsRuleDefinition.setValid(true); if(metricsRuleDefinition.getAlertText() == null || metricsRuleDefinition.getAlertText().isEmpty()) { RuleIssue r = RuleIssue.createRuleIssue(ruleId, ValidationError.VALIDATION_0050, LABEL); r.setAdditionalInfo(PROPERTY, LABEL); ruleIssues.add(r); metricsRuleDefinition.setValid(false); } if(metricsRuleDefinition.getMetricId() == null || metricsRuleDefinition.getMetricId().isEmpty()) { RuleIssue r = RuleIssue.createRuleIssue(ruleId, ValidationError.VALIDATION_0050, METRIC_ID); r.setAdditionalInfo(PROPERTY, METRIC_ID); ruleIssues.add(r); metricsRuleDefinition.setValid(false); } if(metricsRuleDefinition.getCondition() == null || metricsRuleDefinition.getCondition().isEmpty()) { RuleIssue r = RuleIssue.createRuleIssue(ruleId, ValidationError.VALIDATION_0050, CONDITION); r.setAdditionalInfo(PROPERTY, CONDITION); ruleIssues.add(r); metricsRuleDefinition.setValid(false); } else { //validate the condition el expression RuleIssue issue = validateMetricAlertExpressions(metricsRuleDefinition.getCondition(), ruleId); if(issue != null) { issue.setAdditionalInfo(PROPERTY, CONDITION); ruleIssues.add(issue); metricsRuleDefinition.setValid(false); } } if(metricsRuleDefinition.isSendEmail() && (ruleDefinitions.getEmailIds() == null || ruleDefinitions.getEmailIds().isEmpty())) { RuleIssue r = RuleIssue.createRuleIssue(ruleId, ValidationError.VALIDATION_0042); r.setAdditionalInfo(PROPERTY, EMAIL_IDS); ruleIssues.add(r); metricsRuleDefinition.setValid(false); } } ruleDefinitions.setRuleIssues(ruleIssues); return ruleIssues.size() == 0 ? true : false; } private RuleIssue validateMetricAlertExpressions(String condition, String ruleId){ if(!condition.startsWith("${") || !condition.endsWith("}") || !condition.contains(VAL)) { return RuleIssue.createRuleIssue(ruleId, ValidationError.VALIDATION_0046); } String predicateWithValue = condition .replace(VAL, DEFAULT_VALUE) .replace(TIME_NOW, DEFAULT_VALUE); try { elEvaluator.eval(variables, predicateWithValue, Object.class); } catch (ELEvalException e) { return RuleIssue.createRuleIssue(ruleId, ValidationError.VALIDATION_0047, condition); } return null; } private RuleIssue validateDataRuleExpressions(String condition, String ruleId){ Record record = new Record(){ @Override public Header getHeader() { return new Header() { @Override public String getStageCreator() { return null; } @Override public String getSourceId() { return null; } @Override public String getTrackingId() { return null; } @Override public String getPreviousTrackingId() { return null; } @Override public String getStagesPath() { return null; } @Override public byte[] getRaw() { return new byte[0]; } @Override public String getRawMimeType() { return null; } @Override public Set<String> getAttributeNames() { return null; } @Override public String getAttribute(String name) { return null; } @Override public void setAttribute(String name, String value) { } @Override public void deleteAttribute(String name) { } @Override public String getErrorDataCollectorId() { return null; } @Override public String getErrorPipelineName() { return null; } @Override public String getErrorCode() { return null; } @Override public String getErrorMessage() { return null; } @Override public String getErrorStage() { return null; } @Override public long getErrorTimestamp() { return 0; } @Override public String getErrorStackTrace() { return null; } @Override public Map<String, Object> getAllAttributes() { return null; } @Override public Map<String, Object> setAllAttributes(Map<String, Object> newAttrs) { return null; } }; } @Override public Field get() { return null; } @Override public Field set(Field field) { return null; } @Override public Field get(String fieldPath) { return null; } @Override public Field delete(String fieldPath) { return null; } @Override public boolean has(String fieldPath) { return false; } @Override @Deprecated public Set<String> getFieldPaths() { return null; } @Override public Set<String> getEscapedFieldPaths() { return null; } @Override public Field set(String fieldPath, Field newField) { return null; } }; RecordEL.setRecordInContext(variables, record); try { elEvaluator.eval(variables, condition, Object.class); } catch (ELEvalException ex) { return RuleIssue.createRuleIssue(ruleId, ValidationError.VALIDATION_0045, condition, ex.toString()); } return null; } }