This is an automated email from the ASF dual-hosted git repository.

akshayrai09 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new c4e1ac9  [TE] Auto-Resolving Create-Update API for alerts (#4295)
c4e1ac9 is described below

commit c4e1ac91a68971105ee9f61e4bfc14381eb53a4d
Author: Akshay Rai <[email protected]>
AuthorDate: Tue Jun 11 10:18:41 2019 -0700

    [TE] Auto-Resolving Create-Update API for alerts (#4295)
    
    * Autoresolve api
    
    * Addressed Xiaohui's comments
    
    * Fixed tests
---
 .../validators/DetectionConfigValidator.java       | 20 +++---
 .../thirdeye/detection/yaml/YamlResource.java      | 76 +++++++++++++++++++++-
 .../thirdeye/detection/yaml/YamlResourceTest.java  | 57 +++++++++++++++-
 .../yaml/detection/detection-config-1.yaml         | 22 +++++++
 .../yaml/detection/detection-config-2.yaml         | 22 +++++++
 5 files changed, 186 insertions(+), 11 deletions(-)

diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/DetectionConfigValidator.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/DetectionConfigValidator.java
index 77c1cdf..42dc0d9 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/DetectionConfigValidator.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/DetectionConfigValidator.java
@@ -146,9 +146,11 @@ public class DetectionConfigValidator implements 
ConfigValidator<DetectionConfig
     String alertName = MapUtils.getString(detectionYaml, PROP_NAME);
 
     // Validate all compulsory fields
-    Preconditions.checkArgument(detectionYaml.containsKey(PROP_METRIC),
+    String metric = MapUtils.getString(detectionYaml, PROP_METRIC);
+    Preconditions.checkArgument(StringUtils.isNotEmpty(metric),
         "Missing property (" + PROP_METRIC + ") in sub-alert " + alertName);
-    Preconditions.checkArgument(detectionYaml.containsKey(PROP_DATASET),
+    String dataset = MapUtils.getString(detectionYaml, PROP_DATASET);
+    Preconditions.checkArgument(StringUtils.isNotEmpty(dataset),
         "Missing property (" + PROP_DATASET + ") in sub-alert " + alertName);
     Preconditions.checkArgument(detectionYaml.containsKey(PROP_RULES),
         "Missing property (" + PROP_RULES + ") in sub-alert " + alertName);
@@ -160,10 +162,15 @@ public class DetectionConfigValidator implements 
ConfigValidator<DetectionConfig
             + " indentation level of detection yaml it applies to.");
 
     // Check if the metric defined in the config exists
-    MetricConfigDTO metricConfig = provider
-        .fetchMetric(MapUtils.getString(detectionYaml, PROP_METRIC), 
MapUtils.getString(detectionYaml, PROP_DATASET));
+    MetricConfigDTO metricConfig = provider.fetchMetric(metric, dataset);
     Preconditions.checkArgument(metricConfig != null,
-        "Invalid metric (not found) in sub-alert " + alertName);
+        "Metric doesn't exist in our records. Metric " + metric + " in 
sub-alert " + alertName);
+
+    // Check if the dataset defined in the config exists
+    DatasetConfigDTO datasetConfig = provider
+        
.fetchDatasets(Collections.singletonList(metricConfig.getDataset())).get(metricConfig.getDataset());
+    Preconditions.checkArgument(datasetConfig != null,
+        "Dataset doesn't exist in our records. Dataset " + dataset + " in 
sub-alert " + alertName);
 
     // We support only one grouper per metric
     
Preconditions.checkArgument(ConfigUtils.getList(detectionYaml.get(PROP_GROUPER)).size()
 <= 1,
@@ -197,9 +204,6 @@ public class DetectionConfigValidator implements 
ConfigValidator<DetectionConfig
     // Safety condition: Validate if maxDuration is greater than 15 minutes
     Map<String, Object> mergerProperties = MapUtils.getMap(detectionYaml, 
PROP_MERGER, new HashMap());
     if (mergerProperties.get(PROP_MAX_DURATION) != null) {
-      DatasetConfigDTO datasetConfig = provider
-          .fetchDatasets(Collections.singletonList(metricConfig.getDataset()))
-          .get(metricConfig.getDataset());
       Preconditions.checkArgument(
           MapUtils.getLong(mergerProperties, PROP_MAX_DURATION) >= 
datasetConfig.bucketTimeGranularity().toMillis(),
           "The maxDuration field set is not acceptable. Please check the the 
document and set it correctly.");
diff --git 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/yaml/YamlResource.java
 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/yaml/YamlResource.java
index 6b5dde0..e3806ea 100644
--- 
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/yaml/YamlResource.java
+++ 
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/yaml/YamlResource.java
@@ -27,6 +27,7 @@ import com.wordnik.swagger.annotations.ApiOperation;
 import com.wordnik.swagger.annotations.ApiParam;
 import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -98,6 +99,7 @@ public class YamlResource {
   private static ObjectMapper OBJECT_MAPPER = new ObjectMapper();
 
   private static final String PROP_SUBS_GROUP_NAME = "subscriptionGroupName";
+  private static final String PROP_DETECTION_NAME = "detectionName";
 
   // default onboarding replay period
   private static final long ONBOARDING_REPLAY_LOOKBACK = 
TimeUnit.DAYS.toMillis(30);
@@ -287,6 +289,10 @@ public class YamlResource {
     ).build();
   }
 
+  long createDetectionPipeline(String yamlDetectionConfig) {
+    return createDetectionPipeline(yamlDetectionConfig, 0, 0);
+  }
+
   long createDetectionPipeline(String yamlDetectionConfig, long startTime, 
long endTime)
       throws IllegalArgumentException {
     Preconditions.checkArgument(StringUtils.isNotBlank(yamlDetectionConfig), 
"The Yaml Payload in the request is empty.");
@@ -342,11 +348,15 @@ public class YamlResource {
     }
 
     LOG.info("Detection Pipeline created with id " + detectionConfigId + " 
using payload " + payload);
-    responseMessage.put("message", "The subscription group was created 
successfully.");
+    responseMessage.put("message", "Alert was created successfully.");
     responseMessage.put("more-info", "Record saved with id " + 
detectionConfigId);
     return Response.ok().entity(responseMessage).build();
   }
 
+  void updateDetectionPipeline(long detectionID, String yamlDetectionConfig) {
+    updateDetectionPipeline(detectionID, yamlDetectionConfig, 0, 0);
+  }
+
   void updateDetectionPipeline(long detectionID, String yamlDetectionConfig, 
long startTime, long endTime)
       throws IllegalArgumentException {
     DetectionConfigDTO existingDetectionConfig = 
this.detectionConfigDAO.findById(detectionID);
@@ -414,6 +424,70 @@ public class YamlResource {
     return Response.ok().entity(responseMessage).build();
   }
 
+  private DetectionConfigDTO fetchExistingDetection(String payload) {
+    DetectionConfigDTO existingDetectionConfig = null;
+
+    // Extract the detectionName from payload
+    Map<String, Object> detectionConfigMap = new HashMap<>();
+    detectionConfigMap.putAll(ConfigUtils.getMap(this.yaml.load(payload)));
+    String detectionName = MapUtils.getString(detectionConfigMap, 
PROP_DETECTION_NAME);
+    Preconditions.checkNotNull(detectionName, "Missing property detectionName 
in the detection config.");
+
+    // Check if detection already existing
+    Collection<DetectionConfigDTO> detectionConfigs = this.detectionConfigDAO
+        .findByPredicate(Predicate.EQ("name", detectionName));
+    if (detectionConfigs != null && !detectionConfigs.isEmpty()) {
+      existingDetectionConfig = detectionConfigs.iterator().next();
+    }
+
+    return existingDetectionConfig;
+  }
+
+  long createOrUpdateDetectionPipeline(String payload) {
+    Preconditions.checkArgument(StringUtils.isNotBlank(payload), "The Yaml 
Payload in the request is empty.");
+    long detectionId;
+    DetectionConfigDTO existingDetection = fetchExistingDetection(payload);
+    if (existingDetection != null) {
+      detectionId = existingDetection.getId();
+      updateDetectionPipeline(detectionId, payload);
+    } else {
+      detectionId = createDetectionPipeline(payload);
+    }
+
+    return detectionId;
+  }
+
+  /**
+   Set up a detection pipeline using a YAML config - create new or update 
existing
+   @param payload YAML config string
+   @return a message contains the saved detection config id
+   */
+  @POST
+  @Produces(MediaType.APPLICATION_JSON)
+  @Consumes(MediaType.TEXT_PLAIN)
+  @ApiOperation("Create a new detection pipeline or update existing if one 
already exists")
+  public Response createOrUpdateDetectionPipelineApi(@ApiParam("yaml config") 
String payload) {
+    Map<String, String> responseMessage = new HashMap<>();
+    long detectionConfigId;
+    try {
+      detectionConfigId = createOrUpdateDetectionPipeline(payload);
+    } catch (IllegalArgumentException e) {
+      LOG.warn("Validation error while creating/updating detection pipeline 
with payload " + payload, e);
+      responseMessage.put("message", "Validation Error! " + e.getMessage());
+      return 
Response.status(Response.Status.BAD_REQUEST).entity(responseMessage).build();
+    } catch (Exception e) {
+      LOG.error("Error creating/updating detection pipeline with payload " + 
payload, e);
+      responseMessage.put("message", "Failed to create the detection pipeline. 
Reach out to the ThirdEye team.");
+      responseMessage.put("more-info", "Error = " + e.getMessage());
+      return Response.serverError().entity(responseMessage).build();
+    }
+
+    LOG.info("Detection Pipeline created/updated id " + detectionConfigId + " 
using payload " + payload);
+    responseMessage.put("message", "The alert was created/updated 
successfully.");
+    responseMessage.put("more-info", "Record saved/updated with id " + 
detectionConfigId);
+    return Response.ok().entity(responseMessage).build();
+  }
+
   long createSubscriptionGroup(String yamlAlertConfig) throws 
IllegalArgumentException {
     Preconditions.checkArgument(StringUtils.isNotBlank(yamlAlertConfig),
         "The Yaml Payload in the request is empty.");
diff --git 
a/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/yaml/YamlResourceTest.java
 
b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/yaml/YamlResourceTest.java
index da07db9..f72110c 100644
--- 
a/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/yaml/YamlResourceTest.java
+++ 
b/thirdeye/thirdeye-pinot/src/test/java/org/apache/pinot/thirdeye/detection/yaml/YamlResourceTest.java
@@ -1,14 +1,20 @@
 package org.apache.pinot.thirdeye.detection.yaml;
 
+import java.util.concurrent.TimeUnit;
 import org.apache.pinot.thirdeye.datalayer.bao.DAOTestBase;
 import org.apache.pinot.thirdeye.datalayer.bao.DetectionConfigManager;
 import org.apache.pinot.thirdeye.datalayer.dto.ApplicationDTO;
+import org.apache.pinot.thirdeye.datalayer.dto.DatasetConfigDTO;
 import org.apache.pinot.thirdeye.datalayer.dto.DetectionAlertConfigDTO;
 import org.apache.pinot.thirdeye.datalayer.dto.DetectionConfigDTO;
+import org.apache.pinot.thirdeye.datalayer.dto.MetricConfigDTO;
 import org.apache.pinot.thirdeye.datasource.DAORegistry;
 import 
org.apache.pinot.thirdeye.detection.annotation.registry.DetectionAlertRegistry;
 import java.io.IOException;
 import org.apache.commons.io.IOUtils;
+import 
org.apache.pinot.thirdeye.detection.annotation.registry.DetectionRegistry;
+import org.apache.pinot.thirdeye.detection.components.ThresholdRuleDetector;
+import 
org.apache.pinot.thirdeye.detection.yaml.translator.CompositePipelineConfigTranslator;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
@@ -16,7 +22,6 @@ import org.testng.annotations.Test;
 
 
 public class YamlResourceTest {
-
   private DAOTestBase testDAOProvider;
   private YamlResource yamlResource;
   private DAORegistry daoRegistry;
@@ -36,6 +41,8 @@ public class YamlResourceTest {
     config2.setName("test_detection_2");
     alertId2 = detectionDAO.save(config2);
 
+    
DetectionRegistry.getInstance().registerYamlConvertor(CompositePipelineConfigTranslator.class.getName(),
 "COMPOSITE");
+    DetectionRegistry.registerComponent(ThresholdRuleDetector.class.getName(), 
"THRESHOLD");
     DetectionAlertRegistry.getInstance().registerAlertScheme("EMAIL", 
"EmailClass");
     DetectionAlertRegistry.getInstance().registerAlertScheme("IRIS", 
"IrisClass");
     
DetectionAlertRegistry.getInstance().registerAlertSuppressor("TIME_WINDOW", 
"TimeWindowClass");
@@ -48,7 +55,53 @@ public class YamlResourceTest {
   }
 
   @Test
-  public void testCreateDetectionAlertConfig() throws IOException {
+  public void testCreateOrUpdateDetectionConfig() throws IOException {
+    String blankYaml = "";
+    try {
+      this.yamlResource.createOrUpdateDetectionPipeline(blankYaml);
+      Assert.fail("Exception not thrown on empty yaml");
+    } catch (Exception e) {
+      Assert.assertEquals(e.getMessage(), "The Yaml Payload in the request is 
empty.");
+    }
+
+    MetricConfigDTO metricConfig = new MetricConfigDTO();
+    metricConfig.setAlias("test_alias");
+    metricConfig.setName("test_metric");
+    metricConfig.setDataset("test_dataset");
+    daoRegistry.getMetricConfigDAO().save(metricConfig);
+
+    DatasetConfigDTO datasetConfigDTO = new DatasetConfigDTO();
+    datasetConfigDTO.setDataset("test_dataset");
+    datasetConfigDTO.setTimeUnit(TimeUnit.DAYS);
+    datasetConfigDTO.setTimeDuration(1);
+    daoRegistry.getDatasetConfigDAO().save(datasetConfigDTO);
+
+    // Create a new detection
+    String validYaml = 
IOUtils.toString(this.getClass().getResourceAsStream("detection/detection-config-1.yaml"));
+    try {
+      long id = this.yamlResource.createOrUpdateDetectionPipeline(validYaml);
+      DetectionConfigDTO detection = 
daoRegistry.getDetectionConfigManager().findById(id);
+      Assert.assertNotNull(detection);
+      Assert.assertEquals(detection.getName(), "testPipeline");
+    } catch (Exception e) {
+      Assert.fail("Exception should not be thrown for valid yaml. Message: " + 
e + " Cause: " + e.getCause());
+    }
+
+    // Update above created detection
+    String updatedYaml = 
IOUtils.toString(this.getClass().getResourceAsStream("detection/detection-config-2.yaml"));
+    try {
+      long id = this.yamlResource.createOrUpdateDetectionPipeline(updatedYaml);
+      DetectionConfigDTO detection = 
daoRegistry.getDetectionConfigManager().findById(id);
+      Assert.assertNotNull(detection);
+      Assert.assertEquals(detection.getName(), "testPipeline");
+      Assert.assertEquals(detection.getDescription(), "My modified pipeline");
+    } catch (Exception e) {
+      Assert.fail("Exception should not be thrown if detection already exists. 
Message: " + e + " Cause: " + e.getCause());
+    }
+  }
+
+  @Test
+  public void testCreateOrDetectionAlertConfig() throws IOException {
     String blankYaml = "";
     try {
       this.yamlResource.createSubscriptionGroup(blankYaml);
diff --git 
a/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/detection/detection-config-1.yaml
 
b/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/detection/detection-config-1.yaml
new file mode 100644
index 0000000..9c19a76
--- /dev/null
+++ 
b/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/detection/detection-config-1.yaml
@@ -0,0 +1,22 @@
+detectionName: testPipeline
+description: My test pipeline
+type: METRIC_ALERT
+metric: test_metric
+dataset: test_dataset
+filters:
+  D1:
+    - v1
+    - v2
+  D2:
+    - v3
+dimensionExploration:
+  dimensions:
+    - D1
+    - D2
+  minContribution: 0.05
+rules:
+  - detection:
+      - type: THRESHOLD
+        name: maxThreshold_1
+        params:
+          max: 100
diff --git 
a/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/detection/detection-config-2.yaml
 
b/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/detection/detection-config-2.yaml
new file mode 100644
index 0000000..1330155
--- /dev/null
+++ 
b/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/detection/detection-config-2.yaml
@@ -0,0 +1,22 @@
+detectionName: testPipeline
+description: My modified pipeline
+type: METRIC_ALERT
+metric: test_metric
+dataset: test_dataset
+filters:
+  D1:
+    - v1
+    - v2
+  D2:
+    - v3
+dimensionExploration:
+  dimensions:
+    - D1
+    - D2
+  minContribution: 0.05
+rules:
+  - detection:
+      - type: THRESHOLD
+        name: maxThreshold_1
+        params:
+          max: 100


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to