You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by xh...@apache.org on 2020/03/05 16:34:42 UTC

[incubator-pinot] branch master updated: [TE][api] create or update subscription config api (#5111)

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

xhsun 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 f2969b8  [TE][api] create or update subscription config api (#5111)
f2969b8 is described below

commit f2969b84e6406c947519c0acaeb657f84a3c733c
Author: Xiaohui Sun <xh...@linkedin.com>
AuthorDate: Thu Mar 5 08:34:34 2020 -0800

    [TE][api] create or update subscription config api (#5111)
    
    * [TE][api] create or update subscription config api
    
    * correction in comment
---
 .../thirdeye/detection/yaml/YamlResource.java      | 175 ++++++++++++++-------
 .../thirdeye/detection/yaml/YamlResourceTest.java  |  77 ++++++---
 .../subscription-config-1.yaml}                    |   0
 .../subscription-config-2.yaml}                    |   0
 .../subscription-config-3.yaml}                    |   0
 .../subscription-config-4.yaml}                    |   0
 .../subscription-config-5.yaml}                    |   0
 .../yaml/subscription/subscription-config-6.yaml   |  22 +++
 8 files changed, 197 insertions(+), 77 deletions(-)

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 3ee8c12..cd870a7 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
@@ -133,7 +133,7 @@ public class YamlResource {
   private static final long ONBOARDING_REPLAY_LOOKBACK = TimeUnit.DAYS.toMillis(30);
 
   private final DetectionConfigManager detectionConfigDAO;
-  private final DetectionAlertConfigManager detectionAlertConfigDAO;
+  private final DetectionAlertConfigManager subscriptionConfigDAO;
   private final DetectionConfigValidator detectionValidator;
   private final SubscriptionConfigValidator subscriptionValidator;
   private final DataProvider provider;
@@ -152,7 +152,7 @@ public class YamlResource {
 
   public YamlResource(DetectionPreviewConfiguration previewConfig) {
     this.detectionConfigDAO = DAORegistry.getInstance().getDetectionConfigManager();
-    this.detectionAlertConfigDAO = DAORegistry.getInstance().getDetectionAlertConfigManager();
+    this.subscriptionConfigDAO = DAORegistry.getInstance().getDetectionAlertConfigManager();
     this.metricDAO = DAORegistry.getInstance().getMetricConfigDAO();
     this.datasetDAO = DAORegistry.getInstance().getDatasetConfigDAO();
     this.eventDAO = DAORegistry.getInstance().getEventDAO();
@@ -185,9 +185,8 @@ public class YamlResource {
   /*
    * Helper method to build the detection config from a yaml.
    */
-  private DetectionConfigDTO buildDetectionConfigFromYaml(long tuningStartTime, long tuningEndTime, String yamlConfig,
-      DetectionConfigDTO existingConfig) {
-
+  private DetectionConfigDTO buildDetectionConfigFromYaml(long tuningStartTime, long tuningEndTime,
+      @NotNull String yamlConfig, DetectionConfigDTO existingConfig) {
     // Configure the tuning window
     if (tuningStartTime == 0L && tuningEndTime == 0L) {
       // default tuning window 28 days
@@ -278,12 +277,12 @@ public class YamlResource {
     // Detection
     long detectionConfigId;
     try {
+      validatePayload(payload);
       yamls = OBJECT_MAPPER.readValue(payload, Map.class);
+      String detectionYaml = yamls.get(PROP_DETECTION);
+      Preconditions.checkArgument(StringUtils.isNotBlank(detectionYaml), "detection yaml is missing");
 
-      Preconditions.checkArgument(StringUtils.isNotBlank(payload), "The Yaml Payload in the request is empty.");
-      Preconditions.checkArgument(yamls.containsKey(PROP_DETECTION), "Detection pipeline yaml is missing");
-
-      detectionConfigId = createDetectionPipeline(yamls.get(PROP_DETECTION), startTime, endTime);
+      detectionConfigId = createDetectionConfig(detectionYaml, startTime, endTime);
     } catch (IllegalArgumentException e) {
       return processBadRequestResponse(PROP_DETECTION, YamlOperations.CREATING.name(), payload, e);
     } catch (Exception e) {
@@ -293,9 +292,9 @@ public class YamlResource {
     // Notification
     long detectionAlertConfigId;
     try {
-      Preconditions.checkArgument(yamls.containsKey(PROP_SUBSCRIPTION), "Subscription group yaml is missing.");
-
       String subscriptionYaml = yamls.get(PROP_SUBSCRIPTION);
+      Preconditions.checkArgument(StringUtils.isNotBlank(subscriptionYaml), "subscription yaml is missing");
+
       Map<String, Object> subscriptionYamlConfig;
       try {
         subscriptionYamlConfig = ConfigUtils.getMap(this.yaml.load(subscriptionYaml));
@@ -305,12 +304,12 @@ public class YamlResource {
 
       // Check if existing or new subscription group
       String groupName = MapUtils.getString(subscriptionYamlConfig, PROP_SUBS_GROUP_NAME);
-      List<DetectionAlertConfigDTO> alertConfigDTOS = detectionAlertConfigDAO.findByPredicate(Predicate.EQ("name", groupName));
+      List<DetectionAlertConfigDTO> alertConfigDTOS = subscriptionConfigDAO.findByPredicate(Predicate.EQ("name", groupName));
       if (!alertConfigDTOS.isEmpty()) {
         detectionAlertConfigId = alertConfigDTOS.get(0).getId();
         updateSubscriptionGroup(user, detectionAlertConfigId, subscriptionYaml);
       } else {
-        detectionAlertConfigId = createSubscriptionGroup(subscriptionYaml);
+        detectionAlertConfigId = createSubscriptionConfig(subscriptionYaml);
       }
     } catch (IllegalArgumentException e) {
       this.detectionConfigDAO.deleteById(detectionConfigId);
@@ -333,14 +332,12 @@ public class YamlResource {
     ).build();
   }
 
-  long createDetectionPipeline(String yamlDetectionConfig) {
-    return createDetectionPipeline(yamlDetectionConfig, 0, 0);
+  long createDetectionConfig(@NotNull String yamlDetectionConfig) {
+    return createDetectionConfig(yamlDetectionConfig, 0, 0);
   }
 
-  long createDetectionPipeline(String payload, long startTime, long endTime)
+  long createDetectionConfig(@NotNull String payload, long startTime, long endTime)
       throws IllegalArgumentException {
-    Preconditions.checkArgument(StringUtils.isNotBlank(payload), "The Yaml Payload in the request is empty.");
-
     DetectionConfigDTO detectionConfig = buildDetectionConfigFromYaml(startTime, endTime, payload, null);
 
     // Check for duplicates
@@ -376,7 +373,8 @@ public class YamlResource {
     Map<String, String> responseMessage = new HashMap<>();
     long detectionConfigId;
     try {
-      detectionConfigId = createDetectionPipeline(payload, startTime, endTime);
+      validatePayload(payload);
+      detectionConfigId = createDetectionConfig(payload, startTime, endTime);
     } catch (IllegalArgumentException e) {
       return processBadRequestResponse(PROP_DETECTION, YamlOperations.CREATING.name(), payload, e);
     } catch (Exception e) {
@@ -389,18 +387,18 @@ public class YamlResource {
     return Response.ok().entity(responseMessage).build();
   }
 
-  private void updateDetectionPipeline(ThirdEyePrincipal user, long detectionID, String yamlDetectionConfig) {
-    updateDetectionPipeline(user, detectionID, yamlDetectionConfig, 0, 0);
+  private void updateDetectionConfig(ThirdEyePrincipal user, long detectionID, @NotNull String payload) {
+    updateDetectionConfig(user, detectionID, payload, 0, 0);
   }
 
-  private void updateDetectionPipeline(ThirdEyePrincipal user, long detectionID, String payload, long startTime, long endTime)
+  private void updateDetectionConfig(ThirdEyePrincipal user, long detectionID, @NotNull String payload, long startTime,
+      long endTime)
       throws IllegalArgumentException {
     DetectionConfigDTO existingDetectionConfig = this.detectionConfigDAO.findById(detectionID);
-    DetectionConfigDTO detectionConfig;
     Preconditions.checkNotNull(existingDetectionConfig, "Cannot find detection pipeline " + detectionID);
-    Preconditions.checkArgument(StringUtils.isNotBlank(payload), "The Yaml Payload in the request is empty.");
 
     authorizeUser(user, detectionID, PROP_DETECTION);
+    DetectionConfigDTO detectionConfig;
     try {
       detectionConfig = buildDetectionConfigFromYaml(startTime, endTime, payload, existingDetectionConfig);
 
@@ -451,7 +449,7 @@ public class YamlResource {
         DetectionConfigDTO detectionConfig = this.detectionConfigDAO.findById(id);
         validateConfigOwner(user, detectionConfig.getOwners());
       } else if (authEntity.equals(PROP_SUBSCRIPTION)) {
-        DetectionAlertConfigDTO subscriptionConfig = this.detectionAlertConfigDAO.findById(id);
+        DetectionAlertConfigDTO subscriptionConfig = this.subscriptionConfigDAO.findById(id);
         validateConfigOwner(user, subscriptionConfig.getOwners());
       }
 
@@ -481,7 +479,8 @@ public class YamlResource {
       @ApiParam("tuning window end time for tunable components") @QueryParam("endTime") long endTime) {
     Map<String, String> responseMessage = new HashMap<>();
     try {
-      updateDetectionPipeline(user, id, payload, startTime, endTime);
+      validatePayload(payload);
+      updateDetectionConfig(user, id, payload, startTime, endTime);
     } catch (IllegalArgumentException e) {
       return processBadRequestResponse(PROP_DETECTION, YamlOperations.UPDATING.name(), payload, e);
     } catch (NotAuthorizedException e) {
@@ -496,14 +495,14 @@ public class YamlResource {
     return Response.ok().entity(responseMessage).build();
   }
 
-  private DetectionConfigDTO fetchExistingDetection(String payload) {
+  private DetectionConfigDTO fetchExistingDetection(@NotNull 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.");
+    Preconditions.checkNotNull(detectionName, PROP_DETECTION_NAME + " cannot be left empty");
 
     // Check if detection already existing
     Collection<DetectionConfigDTO> detectionConfigs = this.detectionConfigDAO
@@ -515,15 +514,14 @@ public class YamlResource {
     return existingDetectionConfig;
   }
 
-  long createOrUpdateDetectionPipeline(ThirdEyePrincipal user, String payload) {
-    Preconditions.checkArgument(StringUtils.isNotBlank(payload), "The Yaml Payload in the request is empty.");
+  long createOrUpdateDetectionConfig(ThirdEyePrincipal user, @NotNull String payload) {
     long detectionId;
     DetectionConfigDTO existingDetection = fetchExistingDetection(payload);
     if (existingDetection != null) {
       detectionId = existingDetection.getId();
-      updateDetectionPipeline(user, detectionId, payload);
+      updateDetectionConfig(user, detectionId, payload);
     } else {
-      detectionId = createDetectionPipeline(payload);
+      detectionId = createDetectionConfig(payload);
     }
 
     return detectionId;
@@ -539,14 +537,15 @@ public class YamlResource {
   @Produces(MediaType.APPLICATION_JSON)
   @Consumes(MediaType.TEXT_PLAIN)
   @PermitAll
-  @ApiOperation("Create a new detection pipeline or update existing if one already exists")
-  public Response createOrUpdateDetectionPipelineApi(
+  @ApiOperation("Create a new detection config or update existing if one already exists")
+  public Response createOrUpdateDetectionConfigApi(
       @Auth ThirdEyePrincipal user,
       @ApiParam("yaml config") String payload) {
     Map<String, String> responseMessage = new HashMap<>();
     long detectionConfigId;
     try {
-      detectionConfigId = createOrUpdateDetectionPipeline(user, payload);
+      validatePayload(payload);
+      detectionConfigId = createOrUpdateDetectionConfig(user, payload);
     } catch (IllegalArgumentException e) {
       return processBadRequestResponse(PROP_DETECTION, YamlOperations.CREATING.name() + "/" + YamlOperations.UPDATING.name(), payload, e);
     } catch (NotAuthorizedException e) {
@@ -555,20 +554,85 @@ public class YamlResource {
       return processServerErrorResponse(PROP_DETECTION, YamlOperations.CREATING.name() + "/" + YamlOperations.UPDATING.name(), payload, e);
     }
 
-    LOG.info("Detection Pipeline created/updated with id " + detectionConfigId + " using payload " + payload);
+    LOG.info("Detection Config created/updated with 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();
   }
 
-  public long createSubscriptionGroup(String yamlConfig) throws IllegalArgumentException {
-    Preconditions.checkArgument(StringUtils.isNotBlank(yamlConfig),
-        "The Yaml Payload in the request is empty.");
+  private DetectionAlertConfigDTO fetchExistingSubscriptionGroup(@NotNull String payload) {
+    DetectionAlertConfigDTO existingSubscriptionConfig = null;
 
-    DetectionAlertConfigDTO alertConfig = new SubscriptionConfigTranslator(detectionConfigDAO, yamlConfig).translate();
+    // Extract the subscription group name from payload
+    Map<String, Object> subscriptionConfigMap = new HashMap<>(ConfigUtils.getMap(this.yaml.load(payload)));
+    String subscriptionGroupName = MapUtils.getString(subscriptionConfigMap, PROP_SUBS_GROUP_NAME);
+    Preconditions.checkNotNull(subscriptionGroupName, "Missing property " + PROP_SUBS_GROUP_NAME
+        + " in the subscription config.");
+
+    // Check if subscription already exists
+    Collection<DetectionAlertConfigDTO> subscriptionConfigs = this.subscriptionConfigDAO
+        .findByPredicate(Predicate.EQ("name", subscriptionGroupName));
+    if (subscriptionConfigs != null && !subscriptionConfigs.isEmpty()) {
+      existingSubscriptionConfig = subscriptionConfigs.iterator().next();
+    }
+
+    return existingSubscriptionConfig;
+  }
+
+  /**
+   * Update an existing subscription config or create a new one otherwise.
+   */
+  long createOrUpdateSubscriptionConfig(ThirdEyePrincipal user, @NotNull String payload) {
+    long subscriptionId;
+    DetectionAlertConfigDTO existingSubscriptionGroup = fetchExistingSubscriptionGroup(payload);
+    if (existingSubscriptionGroup != null) {
+      subscriptionId = existingSubscriptionGroup.getId();
+      updateSubscriptionGroup(user, subscriptionId, payload);
+    } else {
+      subscriptionId = createSubscriptionConfig(payload);
+    }
+
+    return subscriptionId;
+  }
+
+  /**
+   Set up a subscription pipeline using a YAML config - create new or update existing
+   @param payload YAML config string
+   @return a message contains the saved subscription config id
+   */
+  @POST
+  @Path("/subscription/create-or-update")
+  @Produces(MediaType.APPLICATION_JSON)
+  @Consumes(MediaType.TEXT_PLAIN)
+  @PermitAll
+  @ApiOperation("Create a new subscription config or update existing if one already exists")
+  public Response createOrUpdateSubscriptionConfigApi(
+      @Auth ThirdEyePrincipal user,
+      @ApiParam("yaml config") String payload) {
+    Map<String, String> responseMessage = new HashMap<>();
+    long subscriptionConfigId;
+    try {
+      validatePayload(payload);
+      subscriptionConfigId = createOrUpdateSubscriptionConfig(user, payload);
+    } catch (IllegalArgumentException e) {
+      return processBadRequestResponse(PROP_DETECTION, YamlOperations.CREATING.name() + "/" + YamlOperations.UPDATING.name(), payload, e);
+    } catch (NotAuthorizedException e) {
+      return processBadAuthorizationResponse(PROP_DETECTION, YamlOperations.CREATING.name() + "/" + YamlOperations.UPDATING.name(), payload, e);
+    } catch (Exception e) {
+      return processServerErrorResponse(PROP_DETECTION, YamlOperations.CREATING.name() + "/" + YamlOperations.UPDATING.name(), payload, e);
+    }
+
+    LOG.info("Subscription config created/updated with id " + subscriptionConfigId + " using payload " + payload);
+    responseMessage.put("message", "The subscription group was created/updated successfully.");
+    responseMessage.put("more-info", "Record saved/updated with id " + subscriptionConfigId);
+    return Response.ok().entity(responseMessage).build();
+  }
+
+  public long createSubscriptionConfig(@NotNull String payload) throws IllegalArgumentException {
+    DetectionAlertConfigDTO alertConfig = new SubscriptionConfigTranslator(detectionConfigDAO, payload).translate();
 
     // Check for duplicates
-    List<DetectionAlertConfigDTO> alertConfigDTOS = detectionAlertConfigDAO
+    List<DetectionAlertConfigDTO> alertConfigDTOS = subscriptionConfigDAO
         .findByPredicate(Predicate.EQ("name", alertConfig.getName()));
     Preconditions.checkArgument(alertConfigDTOS.isEmpty(),
         "Subscription group name is already taken. Please use a different name.");
@@ -577,7 +641,7 @@ public class YamlResource {
     subscriptionValidator.validateConfig(alertConfig);
 
     // Save the detection alert config
-    Long id = this.detectionAlertConfigDAO.save(alertConfig);
+    Long id = this.subscriptionConfigDAO.save(alertConfig);
     Preconditions.checkNotNull(id, "Error while saving the subscription group");
 
     return alertConfig.getId();
@@ -589,12 +653,13 @@ public class YamlResource {
   @Consumes(MediaType.TEXT_PLAIN)
   @ApiOperation("Create a subscription group using a YAML config")
   @SuppressWarnings("unchecked")
-  public Response createSubscriptionGroupApi(
+  public Response createSubscriptionConfigApi(
       @ApiParam("payload") String payload) {
     Map<String, String> responseMessage = new HashMap<>();
     long detectionAlertConfigId;
     try {
-      detectionAlertConfigId = createSubscriptionGroup(payload);
+      validatePayload(payload);
+      detectionAlertConfigId = createSubscriptionConfig(payload);
     } catch (IllegalArgumentException e) {
       return processBadRequestResponse(PROP_SUBSCRIPTION, YamlOperations.CREATING.name(), payload, e);
     } catch (Exception e) {
@@ -631,15 +696,14 @@ public class YamlResource {
     return oldAlertConfig;
   }
 
-  void updateSubscriptionGroup(ThirdEyePrincipal user, long oldAlertConfigID, String yamlConfig) {
-    DetectionAlertConfigDTO oldAlertConfig = this.detectionAlertConfigDAO.findById(oldAlertConfigID);
+  void updateSubscriptionGroup(ThirdEyePrincipal user, long oldAlertConfigID, @NotNull String payload) {
+    DetectionAlertConfigDTO oldAlertConfig = this.subscriptionConfigDAO.findById(oldAlertConfigID);
     if (oldAlertConfig == null) {
       throw new RuntimeException("Cannot find subscription group " + oldAlertConfigID);
     }
-    Preconditions.checkArgument(StringUtils.isNotBlank(yamlConfig), "The Yaml Payload in the request is empty.");
 
     authorizeUser(user, oldAlertConfigID, PROP_SUBSCRIPTION);
-    DetectionAlertConfigDTO newAlertConfig = new SubscriptionConfigTranslator(detectionConfigDAO, yamlConfig).translate();
+    DetectionAlertConfigDTO newAlertConfig = new SubscriptionConfigTranslator(detectionConfigDAO, payload).translate();
     DetectionAlertConfigDTO updatedAlertConfig = updateDetectionAlertConfig(oldAlertConfig, newAlertConfig);
 
     // Update watermarks to reflect changes to detectionName list in subscription config
@@ -663,7 +727,7 @@ public class YamlResource {
     subscriptionValidator.validateUpdatedConfig(updatedAlertConfig, oldAlertConfig);
 
     // Save the updated subscription config
-    int detectionAlertConfigId = this.detectionAlertConfigDAO.update(updatedAlertConfig);
+    int detectionAlertConfigId = this.subscriptionConfigDAO.update(updatedAlertConfig);
     if (detectionAlertConfigId <= 0) {
       throw new RuntimeException("Failed to update the detection alert config.");
     }
@@ -681,6 +745,7 @@ public class YamlResource {
       @ApiParam("the detection alert config id to edit") @PathParam("id") long id) {
     Map<String, String> responseMessage = new HashMap<>();
     try {
+      validatePayload(payload);
       updateSubscriptionGroup(user, id, payload);
     } catch (IllegalArgumentException e) {
       return processBadRequestResponse(PROP_SUBSCRIPTION, YamlOperations.UPDATING.name(), payload, e);
@@ -707,7 +772,7 @@ public class YamlResource {
       @QueryParam("tuningStart") long tuningStart,
       @QueryParam("tuningEnd") long tuningEnd,
       @ApiParam("jsonPayload") String payload) {
-    Preconditions.checkArgument(StringUtils.isNotBlank(payload), "The Yaml Payload in the request is empty.");
+    validatePayload(payload);
     return runPreview(start, end, tuningStart, tuningEnd, payload, null);
   }
 
@@ -723,14 +788,14 @@ public class YamlResource {
       @QueryParam("tuningStart") long tuningStart,
       @QueryParam("tuningEnd") long tuningEnd,
       @ApiParam("jsonPayload") String payload) {
-    Preconditions.checkArgument(StringUtils.isNotBlank(payload), "The Yaml Payload in the request is empty.");
+    validatePayload(payload);
     DetectionConfigDTO existingConfig = this.detectionConfigDAO.findById(id);
     Preconditions.checkNotNull(existingConfig, "can not find existing detection config " + id);
     return runPreview(start, end, tuningStart, tuningEnd, payload, existingConfig);
   }
 
   private Response runPreview(long start, long end,
-      long tuningStart, long tuningEnd, String payload, DetectionConfigDTO existingConfig) {
+      long tuningStart, long tuningEnd, @NotNull String payload, DetectionConfigDTO existingConfig) {
     long ts = System.currentTimeMillis();
     Map<String, String> responseMessage = new HashMap<>();
     DetectionPipelineResult result;
@@ -795,8 +860,7 @@ public class YamlResource {
       @ApiParam("jsonPayload") @NotNull String payload,
       @QueryParam("ruleName") String ruleName) {
     try {
-      Preconditions.checkArgument(StringUtils.isNotBlank(payload), "The Yaml Payload in the request is empty.");
-
+      validatePayload(payload);
       DetectionConfigDTO detectionConfig = buildDetectionConfigFromYaml(tuningStart, tuningEnd, payload, null);
       Preconditions.checkNotNull(detectionConfig);
       detectionConfig.setId(Long.MAX_VALUE);
@@ -970,6 +1034,9 @@ public class YamlResource {
     return yamls;
   }
 
+  private static void validatePayload(String payload) {
+    Preconditions.checkArgument(StringUtils.isNotBlank(payload), "The Yaml Payload in the request is empty.");
+  }
 
   /**
    * List all yaml configurations as JSON enhanced with detection config id, isActive and createBy information.
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 dc1f6f5..0feb1fc 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
@@ -47,6 +47,11 @@ public class YamlResourceTest {
     config2.setName("test_detection_2");
     alertId2 = detectionDAO.save(config2);
 
+    ApplicationDTO app = new ApplicationDTO();
+    app.setApplication("test_application");
+    app.setRecipients("test");
+    this.daoRegistry.getApplicationDAO().save(app);
+
     DetectionRegistry.getInstance().registerYamlConvertor(DetectionConfigTranslator.class.getName(), "COMPOSITE");
     DetectionRegistry.registerComponent(ThresholdRuleDetector.class.getName(), "THRESHOLD");
     DetectionAlertRegistry.getInstance().registerAlertScheme("EMAIL", "EmailClass");
@@ -65,10 +70,10 @@ public class YamlResourceTest {
   public void testCreateOrUpdateDetectionConfig() throws IOException {
     String blankYaml = "";
     try {
-      this.yamlResource.createOrUpdateDetectionPipeline(user, blankYaml);
+      this.yamlResource.createOrUpdateDetectionConfig(user, blankYaml);
       Assert.fail("Exception not thrown on empty yaml");
     } catch (Exception e) {
-      Assert.assertEquals(e.getMessage(), "The Yaml Payload in the request is empty.");
+      Assert.assertEquals(e.getMessage(), "detectionName cannot be left empty");
     }
 
     MetricConfigDTO metricConfig = new MetricConfigDTO();
@@ -87,7 +92,7 @@ public class YamlResourceTest {
     // Create a new detection
     String validYaml = IOUtils.toString(this.getClass().getResourceAsStream("detection/detection-config-1.yaml"));
     try {
-      long id = this.yamlResource.createOrUpdateDetectionPipeline(user, validYaml);
+      long id = this.yamlResource.createOrUpdateDetectionConfig(user, validYaml);
       DetectionConfigDTO detection = daoRegistry.getDetectionConfigManager().findById(id);
       Assert.assertNotNull(detection);
       Assert.assertEquals(detection.getName(), "testPipeline");
@@ -98,7 +103,7 @@ public class YamlResourceTest {
     // Update above created detection
     String updatedYaml = IOUtils.toString(this.getClass().getResourceAsStream("detection/detection-config-2.yaml"));
     try {
-      long id = this.yamlResource.createOrUpdateDetectionPipeline(user, updatedYaml);
+      long id = this.yamlResource.createOrUpdateDetectionConfig(user, updatedYaml);
       DetectionConfigDTO detection = daoRegistry.getDetectionConfigManager().findById(id);
       Assert.assertNotNull(detection);
       Assert.assertEquals(detection.getName(), "testPipeline");
@@ -109,18 +114,44 @@ public class YamlResourceTest {
   }
 
   @Test
-  public void testCreateOrDetectionAlertConfig() throws IOException {
+  public void testCreateOrUpdateSubscriptionConfig() throws IOException {
+    // Create a new subscription
+    String validYaml = IOUtils.toString(this.getClass().getResourceAsStream("subscription/subscription-config-2.yaml"));
+    try {
+      long id = this.yamlResource.createOrUpdateSubscriptionConfig(user, validYaml);
+      DetectionAlertConfigDTO subscription = daoRegistry.getDetectionAlertConfigManager().findById(id);
+      Assert.assertNotNull(subscription);
+      Assert.assertEquals(subscription.getName(), "Subscription Group Name");
+    } catch (Exception e) {
+      Assert.fail("Exception should not be thrown for valid yaml. Message: " + e + " Cause: " + e.getCause(), e);
+    }
+
+    // Update above created subscription based on subscriptionGroupName
+    String updatedYaml = IOUtils.toString(this.getClass().getResourceAsStream("subscription/subscription-config-4.yaml"));
+    try {
+      long id = this.yamlResource.createOrUpdateSubscriptionConfig(user, updatedYaml);
+      DetectionAlertConfigDTO subscription = daoRegistry.getDetectionAlertConfigManager().findById(id);
+      Assert.assertNotNull(subscription);
+      Assert.assertEquals(subscription.getName(), "Subscription Group Name");
+      Assert.assertEquals(subscription.getApplication(), "test_application");
+    } catch (Exception e) {
+      Assert.fail("Exception should not be thrown if subscription already exists. Message: " + e + " Cause: " + e.getCause());
+    }
+  }
+
+  @Test
+  public void testCreateSubscriptionConfig() throws IOException {
     String blankYaml = "";
     try {
-      this.yamlResource.createSubscriptionGroup(blankYaml);
+      this.yamlResource.createSubscriptionConfig(blankYaml);
       Assert.fail("Exception not thrown on empty yaml");
     } catch (Exception e) {
-      Assert.assertEquals(e.getMessage(), "The Yaml Payload in the request is empty.");
+      Assert.assertEquals(e.getMessage(), "Subscription group name field cannot be left empty.");
     }
 
     String inValidYaml = "application:test:application";
     try {
-      this.yamlResource.createSubscriptionGroup(inValidYaml);
+      this.yamlResource.createSubscriptionConfig(inValidYaml);
       Assert.fail("Exception not thrown on empty yaml");
     } catch (Exception e) {
       Assert.assertEquals(e.getMessage(), "Could not parse as map: application:test:application");
@@ -128,23 +159,23 @@ public class YamlResourceTest {
 
     String noSubscriptGroupYaml = "application: test_application";
     try {
-      this.yamlResource.createSubscriptionGroup(noSubscriptGroupYaml);
+      this.yamlResource.createSubscriptionConfig(noSubscriptGroupYaml);
       Assert.fail("Exception not thrown on empty yaml");
     } catch (Exception e) {
       Assert.assertEquals(e.getMessage(), "Subscription group name field cannot be left empty.");
     }
 
-    String appFieldMissingYaml = IOUtils.toString(this.getClass().getResourceAsStream("alertconfig/alert-config-1.yaml"));
+    String appFieldMissingYaml = IOUtils.toString(this.getClass().getResourceAsStream("subscription/subscription-config-1.yaml"));
     try {
-      this.yamlResource.createSubscriptionGroup(appFieldMissingYaml);
+      this.yamlResource.createSubscriptionConfig(appFieldMissingYaml);
       Assert.fail("Exception not thrown on empty yaml");
     } catch (Exception e) {
       Assert.assertEquals(e.getMessage(), "Application field cannot be left empty");
     }
 
-    String appMissingYaml = IOUtils.toString(this.getClass().getResourceAsStream("alertconfig/alert-config-2.yaml"));
+    String appMissingYaml = IOUtils.toString(this.getClass().getResourceAsStream("subscription/subscription-config-6.yaml"));
     try {
-      this.yamlResource.createSubscriptionGroup(appMissingYaml);
+      this.yamlResource.createSubscriptionConfig(appMissingYaml);
       Assert.fail("Exception not thrown on empty yaml");
     } catch (Exception e) {
       Assert.assertEquals(e.getMessage(), "Application name doesn't exist in our registry."
@@ -161,17 +192,17 @@ public class YamlResourceTest {
     request.setRecipients("abc@abc.in");
     daoRegistry.getApplicationDAO().save(request);
 
-    String groupExists = IOUtils.toString(this.getClass().getResourceAsStream("alertconfig/alert-config-3.yaml"));
+    String groupExists = IOUtils.toString(this.getClass().getResourceAsStream("subscription/subscription-config-3.yaml"));
     try {
-      this.yamlResource.createSubscriptionGroup(groupExists);
+      this.yamlResource.createSubscriptionConfig(groupExists);
       Assert.fail("Exception not thrown on empty yaml");
     } catch (Exception e) {
       Assert.assertEquals(e.getMessage(), "Subscription group name is already taken. Please use a different name.");
     }
 
-    String validYaml = IOUtils.toString(this.getClass().getResourceAsStream("alertconfig/alert-config-4.yaml"));
+    String validYaml = IOUtils.toString(this.getClass().getResourceAsStream("subscription/subscription-config-4.yaml"));
     try {
-      long id = this.yamlResource.createSubscriptionGroup(validYaml);
+      long id = this.yamlResource.createSubscriptionConfig(validYaml);
       DetectionConfigDTO detection = daoRegistry.getDetectionConfigManager().findById(id);
       Assert.assertNotNull(detection);
       Assert.assertEquals(detection.getName(), "Subscription Group Name");
@@ -181,16 +212,16 @@ public class YamlResourceTest {
   }
 
   @Test
-  public void testUpdateDetectionAlertConfig() throws IOException {
+  public void testUpdateSubscriptionConfig() throws IOException {
     ApplicationDTO request = new ApplicationDTO();
     request.setApplication("test_application");
     request.setRecipients("abc@abc.in");
     daoRegistry.getApplicationDAO().save(request);
 
-    String validYaml = IOUtils.toString(this.getClass().getResourceAsStream("alertconfig/alert-config-4.yaml"));
+    String validYaml = IOUtils.toString(this.getClass().getResourceAsStream("subscription/subscription-config-4.yaml"));
     long oldId = -1;
     try {
-      oldId = this.yamlResource.createSubscriptionGroup(validYaml);
+      oldId = this.yamlResource.createSubscriptionConfig(validYaml);
     } catch (Exception e) {
       Assert.fail("Exception should not be thrown for valid yaml. Message = " + e);
     }
@@ -209,7 +240,7 @@ public class YamlResourceTest {
       this.yamlResource.updateSubscriptionGroup(user, oldId, blankYaml);
       Assert.fail("Exception not thrown on empty yaml");
     } catch (Exception e) {
-      Assert.assertEquals(e.getMessage(), "The Yaml Payload in the request is empty.");
+      Assert.assertEquals(e.getMessage(), "Subscription group name field cannot be left empty.");
     }
 
     String inValidYaml = "application:test:application";
@@ -228,7 +259,7 @@ public class YamlResourceTest {
       Assert.assertEquals(e.getMessage(), "Subscription group name field cannot be left empty.");
     }
 
-    String appFieldMissingYaml = IOUtils.toString(this.getClass().getResourceAsStream("alertconfig/alert-config-1.yaml"));
+    String appFieldMissingYaml = IOUtils.toString(this.getClass().getResourceAsStream("subscription/subscription-config-1.yaml"));
     try {
       this.yamlResource.updateSubscriptionGroup(user, oldId, appFieldMissingYaml);
       Assert.fail("Exception not thrown on empty yaml");
@@ -236,7 +267,7 @@ public class YamlResourceTest {
       Assert.assertEquals(e.getMessage(), "Application field cannot be left empty");
     }
 
-    String validYaml2 = IOUtils.toString(this.getClass().getResourceAsStream("alertconfig/alert-config-5.yaml"));
+    String validYaml2 = IOUtils.toString(this.getClass().getResourceAsStream("subscription/subscription-config-5.yaml"));
     try {
       this.yamlResource.updateSubscriptionGroup(user, oldId, validYaml2);
       alertDTO = daoRegistry.getDetectionAlertConfigManager().findById(oldId);
diff --git a/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/alertconfig/alert-config-1.yaml b/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/subscription/subscription-config-1.yaml
similarity index 100%
rename from thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/alertconfig/alert-config-1.yaml
rename to thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/subscription/subscription-config-1.yaml
diff --git a/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/alertconfig/alert-config-2.yaml b/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/subscription/subscription-config-2.yaml
similarity index 100%
rename from thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/alertconfig/alert-config-2.yaml
rename to thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/subscription/subscription-config-2.yaml
diff --git a/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/alertconfig/alert-config-3.yaml b/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/subscription/subscription-config-3.yaml
similarity index 100%
rename from thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/alertconfig/alert-config-3.yaml
rename to thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/subscription/subscription-config-3.yaml
diff --git a/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/alertconfig/alert-config-4.yaml b/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/subscription/subscription-config-4.yaml
similarity index 100%
rename from thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/alertconfig/alert-config-4.yaml
rename to thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/subscription/subscription-config-4.yaml
diff --git a/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/alertconfig/alert-config-5.yaml b/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/subscription/subscription-config-5.yaml
similarity index 100%
rename from thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/alertconfig/alert-config-5.yaml
rename to thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/subscription/subscription-config-5.yaml
diff --git a/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/subscription/subscription-config-6.yaml b/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/subscription/subscription-config-6.yaml
new file mode 100644
index 0000000..746ed78
--- /dev/null
+++ b/thirdeye/thirdeye-pinot/src/test/resources/org/apache/pinot/thirdeye/detection/yaml/subscription/subscription-config-6.yaml
@@ -0,0 +1,22 @@
+subscriptionGroupName: "Subscription Group Name"
+cron: "0 0/5 * 1/1 * ? *"
+application: "test_missing_application"
+active: true
+subscribedDetections:
+  - test_detection_2
+
+type: DEFAULT_ALERTER_PIPELINE
+
+alertSchemes:
+- type: EMAIL
+  params:
+    template: ENTITY_GROUPBY_REPORT
+    subject: METRICS
+    recipients:
+      to:
+        - "thirdeye@thirdeye.com"
+      cc:
+        - "thirdeye-developers@thirdeye.com"
+
+referenceLinks:
+ "Oncall Runbook": "test_url"
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org