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 ac058c5 [TE] Refactor detection & preview API for better debugging
(#3812)
ac058c5 is described below
commit ac058c5c9b5caf489d751ce04920e53072d7e386
Author: Akshay Rai <[email protected]>
AuthorDate: Fri Feb 8 16:59:59 2019 -0800
[TE] Refactor detection & preview API for better debugging (#3812)
---
.../detection/DetectionMigrationResource.java | 13 +-
.../detection/validators/ConfigValidator.java | 2 +-
.../validators/DetectionConfigValidator.java | 111 ++++++
...dator.java => SubscriptionConfigValidator.java} | 15 +-
.../yaml/YamlDetectionAlertConfigTranslator.java | 1 -
.../thirdeye/detection/yaml/YamlResource.java | 371 ++++++++++-----------
.../thirdeye/detection/yaml/YamlResourceTest.java | 4 +-
7 files changed, 307 insertions(+), 210 deletions(-)
diff --git
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionMigrationResource.java
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionMigrationResource.java
index 795e917..1e041cc 100644
---
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionMigrationResource.java
+++
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/DetectionMigrationResource.java
@@ -19,6 +19,7 @@
package org.apache.pinot.thirdeye.detection;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.ArrayList;
@@ -319,12 +320,12 @@ public class DetectionMigrationResource {
}
// Migrate anomaly function config to the detection config by converting
to YAML and then to Detection Config
- Map<String, String> responseMessage = new HashMap<>();
- Map<String, Object> detectionYAMLMap =
translateAnomalyFunctionToYaml(anomalyFunctionDTO);
- detectionConfig = new
YamlResource().translateToDetectionConfig(detectionYAMLMap, responseMessage);
-
- if (detectionConfig == null) {
- throw new RuntimeException("Couldn't translate yaml to detection config
due to " + responseMessage.get("message"));
+ try {
+ Map<String, Object> detectionYAMLMap =
translateAnomalyFunctionToYaml(anomalyFunctionDTO);
+ detectionConfig = new
YamlResource().translateToDetectionConfig(detectionYAMLMap);
+ Preconditions.checkNotNull(detectionConfig);
+ } catch (Exception e) {
+ throw new RuntimeException("Error translating anomaly function config to
the detection config" + e.getMessage());
}
// Save the migrated anomaly function
diff --git
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/ConfigValidator.java
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/ConfigValidator.java
index 4879674..aa5be6f 100644
---
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/ConfigValidator.java
+++
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/ConfigValidator.java
@@ -55,7 +55,7 @@ public abstract class ConfigValidator {
try {
Map<String, Object> yamlConfigMap = (Map<String, Object>)
YAML.load(yamlConfig);
} catch (Exception e) {
- throw new ValidationException("Error parsing the Yaml input. Check for
syntax issues.");
+ throw new ValidationException("Error parsing the Yaml payload. Check for
syntax issues.");
}
return true;
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
new file mode 100644
index 0000000..bcd7586
--- /dev/null
+++
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/DetectionConfigValidator.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to 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 org.apache.pinot.thirdeye.detection.validators;
+
+import javax.xml.bind.ValidationException;
+import org.apache.pinot.thirdeye.datalayer.bao.DatasetConfigManager;
+import org.apache.pinot.thirdeye.datalayer.bao.MetricConfigManager;
+import org.apache.pinot.thirdeye.datalayer.dto.DetectionConfigDTO;
+import org.apache.pinot.thirdeye.datasource.DAORegistry;
+import org.apache.pinot.thirdeye.datasource.ThirdEyeCacheRegistry;
+import org.apache.pinot.thirdeye.datasource.loader.AggregationLoader;
+import org.apache.pinot.thirdeye.datasource.loader.DefaultAggregationLoader;
+import org.apache.pinot.thirdeye.datasource.loader.DefaultTimeSeriesLoader;
+import org.apache.pinot.thirdeye.datasource.loader.TimeSeriesLoader;
+import org.apache.pinot.thirdeye.detection.DataProvider;
+import org.apache.pinot.thirdeye.detection.DefaultDataProvider;
+import org.apache.pinot.thirdeye.detection.DetectionPipelineLoader;
+import
org.apache.pinot.thirdeye.detection.yaml.YamlDetectionAlertConfigTranslator;
+import org.apache.pinot.thirdeye.detection.yaml.YamlDetectionTranslatorLoader;
+import org.yaml.snakeyaml.Yaml;
+
+
+public class DetectionConfigValidator extends ConfigValidator {
+
+ private static DetectionConfigValidator INSTANCE;
+
+ private final DataProvider provider;
+ private final DetectionPipelineLoader loader;
+
+ public static DetectionConfigValidator getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new DetectionConfigValidator();
+ }
+ return INSTANCE;
+ }
+
+ DetectionConfigValidator() {
+ MetricConfigManager metricDAO =
DAORegistry.getInstance().getMetricConfigDAO();
+ DatasetConfigManager datasetDAO =
DAORegistry.getInstance().getDatasetConfigDAO();
+
+ TimeSeriesLoader timeseriesLoader =
+ new DefaultTimeSeriesLoader(metricDAO, datasetDAO,
ThirdEyeCacheRegistry.getInstance().getQueryCache());
+
+ AggregationLoader aggregationLoader =
+ new DefaultAggregationLoader(metricDAO, datasetDAO,
ThirdEyeCacheRegistry.getInstance().getQueryCache(),
+ ThirdEyeCacheRegistry.getInstance().getDatasetMaxDataTimeCache());
+
+ this.loader = new DetectionPipelineLoader();
+
+ this.provider = new DefaultDataProvider(metricDAO, datasetDAO,
+ DAORegistry.getInstance().getEventDAO(),
+ DAORegistry.getInstance().getMergedAnomalyResultDAO(),
+ timeseriesLoader, aggregationLoader, loader);
+ }
+
+ /**
+ * Validate the pipeline by loading and initializing components
+ */
+ private void semanticValidation(DetectionConfigDTO detectionConfig) throws
ValidationException {
+ try {
+ // backup and swap out id
+ Long id = detectionConfig.getId();
+ detectionConfig.setId(-1L);
+
+ // try to load the detection pipeline and init all the components
+ this.loader.from(provider, detectionConfig, 0, 0);
+
+ // set id back
+ detectionConfig.setId(id);
+ } catch (Exception e) {
+ throw new ValidationException("Semantic error while initializing the
detection pipeline.");
+ }
+ }
+
+ /**
+ * Perform validation on the detection config like verifying if all the
required fields are set
+ */
+ public void validateConfig(DetectionConfigDTO detectionConfig) throws
ValidationException {
+ semanticValidation(detectionConfig);
+
+ // TODO: Add more static validations here
+ }
+
+ /**
+ * Perform validation on the updated detection config. Check for fields
which shouldn't be
+ * updated by the user.
+ */
+ public void validateUpdatedConfig(DetectionConfigDTO updatedConfig,
DetectionConfigDTO oldConfig)
+ throws ValidationException {
+ validateConfig(updatedConfig);
+
+ // TODO: Add more checks here
+ }
+}
diff --git
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/DetectionAlertConfigValidator.java
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/SubscriptionConfigValidator.java
similarity index 89%
rename from
thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/DetectionAlertConfigValidator.java
rename to
thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/SubscriptionConfigValidator.java
index 07e83d2..34bdba9 100644
---
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/DetectionAlertConfigValidator.java
+++
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/validators/SubscriptionConfigValidator.java
@@ -26,23 +26,23 @@ import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.pinot.thirdeye.datalayer.util.Predicate;
import org.apache.pinot.thirdeye.datasource.DAORegistry;
+import org.apache.pinot.thirdeye.detection.ConfigUtils;
import static
org.apache.pinot.thirdeye.detection.yaml.YamlDetectionAlertConfigTranslator.*;
-public class DetectionAlertConfigValidator extends ConfigValidator {
+public class SubscriptionConfigValidator extends ConfigValidator {
- private static final DetectionAlertConfigValidator INSTANCE = new
DetectionAlertConfigValidator();
+ private static final SubscriptionConfigValidator INSTANCE = new
SubscriptionConfigValidator();
private static final String PROP_CLASS_NAME = "className";
- public static DetectionAlertConfigValidator getInstance() {
+ public static SubscriptionConfigValidator getInstance() {
return INSTANCE;
}
/**
* Perform validation on the alert config like verifying if all the required
fields are set
*/
- @SuppressWarnings("unchecked")
public void validateConfig(DetectionAlertConfigDTO alertConfig) throws
ValidationException {
// Check for all the required fields in the alert
if (StringUtils.isEmpty(alertConfig.getName())) {
@@ -68,14 +68,14 @@ public class DetectionAlertConfigValidator extends
ConfigValidator {
+ " subscribed detections, and type.");
}
// detectionConfigIds cannot be empty
- List<Long> detectionIds = (List<Long>)
alertConfig.getProperties().get(PROP_DETECTION_CONFIG_IDS);
+ List<Long> detectionIds =
ConfigUtils.getLongs(alertConfig.getProperties().get(PROP_DETECTION_CONFIG_IDS));
if (detectionIds == null || detectionIds.isEmpty()) {
throw new ValidationException("A notification group should subscribe to
at least one alert. If you wish to"
+ " unsubscribe, set active to false.");
}
// At least one recipient must be specified
- Map<String, Object> recipients = (Map<String, Object>)
alertConfig.getProperties().get(PROP_RECIPIENTS);
- if (recipients == null || recipients.isEmpty() || ((List<String>)
recipients.get("to")).isEmpty()) {
+ Map<String, Object> recipients =
ConfigUtils.getMap(alertConfig.getProperties().get(PROP_RECIPIENTS));
+ if (recipients.isEmpty() ||
ConfigUtils.getList(recipients.get("to")).isEmpty()) {
throw new ValidationException("Please specify at least one recipient in
the notification group. If you wish to"
+ " unsubscribe, set active to false.");
}
@@ -93,7 +93,6 @@ public class DetectionAlertConfigValidator extends
ConfigValidator {
* Perform validation on the updated alert config. Check for fields which
shouldn't be
* updated by the user.
*/
- @SuppressWarnings("unchecked")
public void validateUpdatedConfig(DetectionAlertConfigDTO
updatedAlertConfig, DetectionAlertConfigDTO oldAlertConfig)
throws ValidationException {
validateConfig(updatedAlertConfig);
diff --git
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/yaml/YamlDetectionAlertConfigTranslator.java
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/yaml/YamlDetectionAlertConfigTranslator.java
index 2ed1a9a..ba3eede 100644
---
a/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/yaml/YamlDetectionAlertConfigTranslator.java
+++
b/thirdeye/thirdeye-pinot/src/main/java/org/apache/pinot/thirdeye/detection/yaml/YamlDetectionAlertConfigTranslator.java
@@ -161,7 +161,6 @@ public class YamlDetectionAlertConfigTranslator {
/**
* Generates the {@link DetectionAlertConfigDTO} from the YAML Alert Map
*/
- @SuppressWarnings("unchecked")
public DetectionAlertConfigDTO translate(Map<String,Object> yamlAlertConfig)
{
DetectionAlertConfigDTO alertConfigDTO = new DetectionAlertConfigDTO();
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 e5dd378..41c84d6 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
@@ -25,7 +25,6 @@ import com.google.common.collect.ImmutableMap;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
-import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -68,7 +67,8 @@ import
org.apache.pinot.thirdeye.detection.DefaultDataProvider;
import org.apache.pinot.thirdeye.detection.DetectionPipeline;
import org.apache.pinot.thirdeye.detection.DetectionPipelineLoader;
import org.apache.pinot.thirdeye.detection.DetectionPipelineResult;
-import
org.apache.pinot.thirdeye.detection.validators.DetectionAlertConfigValidator;
+import org.apache.pinot.thirdeye.detection.validators.DetectionConfigValidator;
+import
org.apache.pinot.thirdeye.detection.validators.SubscriptionConfigValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;
@@ -80,14 +80,14 @@ public class YamlResource {
protected static final Logger LOG =
LoggerFactory.getLogger(YamlResource.class);
private static ObjectMapper OBJECT_MAPPER = new ObjectMapper();
- private static final String PROP_DETECTION_NAME = "detectionName";
private static final String PROP_SUBS_GROUP_NAME = "subscriptionGroupName";
private final DetectionConfigManager detectionConfigDAO;
private final DetectionAlertConfigManager detectionAlertConfigDAO;
private final YamlDetectionTranslatorLoader translatorLoader;
private final YamlDetectionAlertConfigTranslator alertConfigTranslator;
- private final DetectionAlertConfigValidator notificationValidator;
+ private final DetectionConfigValidator detectionValidator;
+ private final SubscriptionConfigValidator subscriptionValidator;
private final DataProvider provider;
private final MetricConfigManager metricDAO;
private final DatasetConfigManager datasetDAO;
@@ -100,7 +100,8 @@ public class YamlResource {
this.detectionConfigDAO =
DAORegistry.getInstance().getDetectionConfigManager();
this.detectionAlertConfigDAO =
DAORegistry.getInstance().getDetectionAlertConfigManager();
this.translatorLoader = new YamlDetectionTranslatorLoader();
- this.notificationValidator = DetectionAlertConfigValidator.getInstance();
+ this.detectionValidator = DetectionConfigValidator.getInstance();
+ this.subscriptionValidator = SubscriptionConfigValidator.getInstance();
this.alertConfigTranslator = new
YamlDetectionAlertConfigTranslator(this.detectionConfigDAO);
this.metricDAO = DAORegistry.getInstance().getMetricConfigDAO();
this.datasetDAO = DAORegistry.getInstance().getDatasetConfigDAO();
@@ -120,59 +121,35 @@ public class YamlResource {
this.provider = new DefaultDataProvider(metricDAO, datasetDAO, eventDAO,
anomalyDAO, timeseriesLoader, aggregationLoader, loader);
}
- public DetectionConfigDTO translateToDetectionConfig(Map<String, Object>
yamlConfig, Map<String, String> responseMessage) {
- return buildDetectionConfigFromYaml(0, 0, yamlConfig, null,
responseMessage);
+ public DetectionConfigDTO translateToDetectionConfig(Map<String, Object>
yamlConfig) throws Exception {
+ return buildDetectionConfigFromYaml(0, 0, yamlConfig, null);
}
/*
* Build the detection config from a yaml.
- * Returns null if building or validation failed. Error messages stored in
responseMessage.
*/
private DetectionConfigDTO buildDetectionConfigFromYaml(long startTime, long
endTime, Map<String, Object> yamlConfig,
- DetectionConfigDTO existingDetectionConfig, Map<String, String>
responseMessage) {
+ DetectionConfigDTO existingDetectionConfig) throws Exception {
+
+ // Configure the tuning window
if (startTime == 0L && endTime == 0L) {
// default tuning window 28 days
endTime = System.currentTimeMillis();
startTime = endTime - TimeUnit.DAYS.toMillis(28);
}
- try{
- YamlDetectionConfigTranslator translator =
this.translatorLoader.from(yamlConfig, this.provider);
- DetectionConfigDTO detectionConfig =
translator.withTrainingWindow(startTime, endTime)
- .withExistingDetectionConfig(existingDetectionConfig)
- .generateDetectionConfig();
- validatePipeline(detectionConfig);
- return detectionConfig;
- } catch (InvocationTargetException e){
- // exception thrown in validate pipeline via reflection
- LOG.error("Validate pipeline error", e);
- responseMessage.put("message", e.getCause().getMessage());
- } catch (Exception e) {
- LOG.error("yaml translation error", e);
- responseMessage.put("message", e.getMessage());
- }
- return null;
- }
-
- /*
- * Init the pipeline to check if detection pipeline property is valid
semantically.
- */
- private void validatePipeline(DetectionConfigDTO detectionConfig) throws
Exception {
- Long id = detectionConfig.getId();
- // swap out id
- detectionConfig.setId(-1L);
- // try to load the detection pipeline and init all the components
- this.loader.from(provider, detectionConfig, 0, 0);
- // set id back
- detectionConfig.setId(id);
+ YamlDetectionConfigTranslator translator =
this.translatorLoader.from(yamlConfig, this.provider);
+ return translator.withTrainingWindow(startTime, endTime)
+ .withExistingDetectionConfig(existingDetectionConfig)
+ .generateDetectionConfig();
}
@POST
@Path("/create-alert")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
- @ApiOperation("Use yaml to create both notification and detection yaml. ")
- public Response createYamlAlert(@ApiParam(value = "a json contains both
notification and detection yaml as string") String payload,
+ @ApiOperation("Use yaml to create both subscription and detection yaml. ")
+ public Response createYamlAlert(@ApiParam(value = "a json contains both
subscription and detection yaml as string") String payload,
@ApiParam("tuning window start time for tunable components")
@QueryParam("startTime") long startTime,
@ApiParam("tuning window end time for tunable components")
@QueryParam("endTime") long endTime) throws Exception{
Map<String, String> yamls = OBJECT_MAPPER.readValue(payload, Map.class);
@@ -180,42 +157,15 @@ public class YamlResource {
return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"Empty payload")).build();
}
if (!yamls.containsKey("detection")) {
- return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"detection yaml is missing")).build();
+ return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"detection pipeline yaml is missing")).build();
}
if (!yamls.containsKey("notification")){
- return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"notification yaml is missing")).build();
+ return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"subscription group yaml is missing")).build();
}
// Detection
String detectionYaml = yamls.get("detection");
-
- Map<String, Object> detectionYamlConfig;
- try {
- detectionYamlConfig = ConfigUtils.getMap(this.yaml.load(detectionYaml));
- } catch (Exception e){
- return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"detection yaml parsing error, " + e.getMessage())).build();
- }
-
- // check if detection config already exists
- String name = MapUtils.getString(detectionYamlConfig, PROP_DETECTION_NAME);
- List<DetectionConfigDTO> detectionConfigDTOs =
this.detectionConfigDAO.findByPredicate(
- Predicate.EQ("name", name));
- if (!detectionConfigDTOs.isEmpty()){
- return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"detection name already exist: " + name )).build();
- }
-
- HashMap<String, String> responseMessage = new HashMap<>();
- DetectionConfigDTO detectionConfig =
- buildDetectionConfigFromYaml(startTime, endTime, detectionYamlConfig,
null, responseMessage);
- if (detectionConfig == null) {
- return
Response.status(Response.Status.BAD_REQUEST).entity(responseMessage).build();
- }
- detectionConfig.setYaml(detectionYaml);
- Long detectionConfigId = this.detectionConfigDAO.save(detectionConfig);
- if (detectionConfigId == null){
- return Response.serverError().entity(ImmutableMap.of("message", "Save
detection config failed")).build();
- }
- Preconditions.checkNotNull(detectionConfigId, "Save detection config
failed");
+ long detectionConfigId = createDetectionPipeline(detectionYaml, startTime,
endTime);
// Notification
String notificationYaml = yamls.get("notification");
@@ -246,7 +196,34 @@ public class YamlResource {
}
long alertId =
Long.parseLong(ConfigUtils.getMap(response.getEntity()).get("detectionAlertConfigId").toString());
- return Response.ok().entity(ImmutableMap.of("detectionConfigId",
detectionConfig.getId(), "detectionAlertConfigId", alertId)).build();
+ return Response.ok().entity(ImmutableMap.of("detectionConfigId",
detectionConfigId, "detectionAlertConfigId", alertId)).build();
+ }
+
+ long createDetectionPipeline(String yamlDetectionConfig, long startTime,
long endTime) throws Exception {
+ detectionValidator.validateYAMLConfig(yamlDetectionConfig);
+
+ // Translate config from YAML to detection config (JSON)
+ TreeMap<String, Object> newDetectionConfigMap = new
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
newDetectionConfigMap.putAll(ConfigUtils.getMap(this.yaml.load(yamlDetectionConfig)));
+ DetectionConfigDTO detectionConfig =
buildDetectionConfigFromYaml(startTime, endTime, newDetectionConfigMap, null);
+ detectionConfig.setYaml(yamlDetectionConfig);
+
+ // Check for duplicates
+ List<DetectionConfigDTO> detectionConfigDTOS = detectionConfigDAO
+ .findByPredicate(Predicate.EQ("name", detectionConfig.getName()));
+ if (!detectionConfigDTOS.isEmpty()) {
+ throw new ValidationException("Detection name is already taken. Please
use a different detectionName.");
+ }
+
+ // Validate the config before saving it
+ detectionValidator.validateConfig(detectionConfig);
+
+ // Save the detection config
+ Preconditions.checkNotNull(detectionConfig);
+ Long id = this.detectionConfigDAO.save(detectionConfig);
+ Preconditions.checkNotNull(id, "Error while saving the detection
pipeline");
+
+ return detectionConfig.getId();
}
/**
@@ -260,37 +237,51 @@ public class YamlResource {
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.TEXT_PLAIN)
@ApiOperation("Set up a detection pipeline using a YAML config")
- public Response setUpDetectionPipeline(
+ public Response createDetectionPipelineApi(
@ApiParam("yaml config") String payload,
@ApiParam("tuning window start time for tunable components")
@QueryParam("startTime") long startTime,
@ApiParam("tuning window end time for tunable components")
@QueryParam("endTime") long endTime) {
- if (StringUtils.isBlank(payload)){
- return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"empty payload")).build();
- }
- Map<String, Object> yamlConfig;
+ Map<String, String> responseMessage = new HashMap<>();
+ long detectionConfigId;
try {
- yamlConfig = (Map<String, Object>) this.yaml.load(payload);
- } catch (Exception e){
- return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"detection yaml parsing error, " + e.getMessage())).build();
+ detectionConfigId = createDetectionPipeline(payload, startTime, endTime);
+ } catch (ValidationException e) {
+ LOG.warn("Validation error while creating detection pipeline with
payload " + payload, e);
+ responseMessage.put("message", "Validation Error! " + e.getMessage());
+ return Response.serverError().entity(responseMessage).build();
+ } catch (Exception e) {
+ LOG.error("Error creating subscription group with payload " + payload,
e);
+ responseMessage.put("message", "Failed to create the subscription group.
Reach out to the ThirdEye team.");
+ responseMessage.put("more-info", "Error = " + e.getMessage());
+ return Response.serverError().entity(responseMessage).build();
}
- // check if detection config already exists
- String name = MapUtils.getString(yamlConfig, PROP_DETECTION_NAME);
- List<DetectionConfigDTO> detectionConfigDTOs =
this.detectionConfigDAO.findByPredicate(
- Predicate.EQ("name", name));
- if (!detectionConfigDTOs.isEmpty()){
- return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"detection name already exist: " + name )).build();
- }
- Map<String, String> responseMessage = new HashMap<>();
- DetectionConfigDTO detectionConfig =
- buildDetectionConfigFromYaml(startTime, endTime, yamlConfig, null,
responseMessage);
- if (detectionConfig == null) {
- return
Response.status(Response.Status.BAD_REQUEST).entity(responseMessage).build();
+ LOG.info("Detection Pipeline created with id " + detectionConfigId + "
using payload " + payload);
+ responseMessage.put("message", "The subscription group was created
successfully.");
+ responseMessage.put("more-info", "Record saved with id " +
detectionConfigId);
+ return Response.ok().entity(responseMessage).build();
+ }
+
+ void updateDetectionPipeline(long detectionID, String yamlDetectionConfig,
long startTime, long endTime) throws Exception {
+ DetectionConfigDTO existingDetectionConfig =
this.detectionConfigDAO.findById(detectionID);
+ if (existingDetectionConfig == null) {
+ throw new RuntimeException("Cannot find detection pipeline " +
detectionID);
}
- detectionConfig.setYaml(payload);
- Long detectionConfigId = this.detectionConfigDAO.save(detectionConfig);
- Preconditions.checkNotNull(detectionConfigId, "Save detection config
failed");
- return Response.ok(detectionConfig).build();
+ detectionValidator.validateYAMLConfig(yamlDetectionConfig);
+
+ // Translate config from YAML to detection config (JSON)
+ TreeMap<String, Object> newDetectionConfigMap = new
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
newDetectionConfigMap.putAll(ConfigUtils.getMap(this.yaml.load(yamlDetectionConfig)));
+ DetectionConfigDTO detectionConfig =
buildDetectionConfigFromYaml(startTime, endTime, newDetectionConfigMap,
existingDetectionConfig);
+ detectionConfig.setYaml(yamlDetectionConfig);
+
+ // Validate updated config before saving it
+ detectionValidator.validateUpdatedConfig(detectionConfig,
existingDetectionConfig);
+
+ // Save the detection config
+ Preconditions.checkNotNull(detectionConfig);
+ Long id = this.detectionConfigDAO.save(detectionConfig);
+ Preconditions.checkNotNull(id, "Error while saving the detection
pipeline");
}
/**
@@ -306,45 +297,37 @@ public class YamlResource {
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.TEXT_PLAIN)
@ApiOperation("Edit a detection pipeline using a YAML config")
- public Response editDetectionPipeline(
+ public Response updateDetectionPipelineApi(
@ApiParam("yaml config") String payload,
@ApiParam("the detection config id to edit") @PathParam("id") long id,
@ApiParam("tuning window start time for tunable components")
@QueryParam("startTime") long startTime,
@ApiParam("tuning window end time for tunable components")
@QueryParam("endTime") long endTime) {
- if (StringUtils.isBlank(payload)){
- return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"empty payload")).build();
- }
- Map<String, Object> yamlConfig;
+ Map<String, String> responseMessage = new HashMap<>();
try {
- yamlConfig = (Map<String, Object>) this.yaml.load(payload);
- } catch (Exception e){
- return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"detection yaml parsing error, " + e.getMessage())).build();
+ updateDetectionPipeline(id, payload, startTime, endTime);
+ } catch (ValidationException e) {
+ LOG.warn("Validation error while creating detection pipeline with
payload " + payload, e);
+ responseMessage.put("message", "Validation Error! " + e.getMessage());
+ return Response.serverError().entity(responseMessage).build();
+ } catch (Exception e) {
+ LOG.error("Error creating subscription group with payload " + payload,
e);
+ responseMessage.put("message", "Failed to create the subscription group.
Reach out to the ThirdEye team.");
+ responseMessage.put("more-info", "Error = " + e.getMessage());
+ return Response.serverError().entity(responseMessage).build();
}
- Map<String, String> responseMessage = new HashMap<>();
- // retrieve id if detection config already exists
- DetectionConfigDTO existingDetectionConfig =
this.detectionConfigDAO.findById(id);
- if (existingDetectionConfig == null) {
- return
Response.status(Response.Status.BAD_REQUEST).entity(responseMessage).build();
- }
- DetectionConfigDTO detectionConfig =
- buildDetectionConfigFromYaml(startTime, endTime, yamlConfig,
existingDetectionConfig, responseMessage);
- if (detectionConfig == null) {
- return
Response.status(Response.Status.BAD_REQUEST).entity(responseMessage).build();
- }
- detectionConfig.setYaml(payload);
- Long detectionConfigId = this.detectionConfigDAO.save(detectionConfig);
- Preconditions.checkNotNull(detectionConfigId, "Save detection config
failed");
- return Response.ok(detectionConfig).build();
+ LOG.info("Detection Pipeline " + id + " updated successfully with payload
" + payload);
+ responseMessage.put("message", "The detection Pipeline was created
successfully.");
+ responseMessage.put("detectionConfigId", String.valueOf(id));
+ return Response.ok().entity(responseMessage).build();
}
- @SuppressWarnings("unchecked")
long createSubscriptionGroup(String yamlAlertConfig) throws
ValidationException {
- notificationValidator.validateYAMLConfig(yamlAlertConfig);
+ subscriptionValidator.validateYAMLConfig(yamlAlertConfig);
// Translate config from YAML to detection alert config (JSON)
TreeMap<String, Object> newAlertConfigMap = new
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- newAlertConfigMap.putAll((Map<String, Object>)
this.yaml.load(yamlAlertConfig));
+
newAlertConfigMap.putAll(ConfigUtils.getMap(this.yaml.load(yamlAlertConfig)));
DetectionAlertConfigDTO alertConfig =
this.alertConfigTranslator.translate(newAlertConfigMap);
alertConfig.setYaml(yamlAlertConfig);
@@ -355,42 +338,44 @@ public class YamlResource {
throw new ValidationException("Subscription group name is already taken.
Please use a different name.");
}
- // Validate the config
- notificationValidator.validateConfig(alertConfig);
+ // Validate the config before saving it
+ subscriptionValidator.validateConfig(alertConfig);
// Save the detection alert config
Preconditions.checkNotNull(alertConfig);
Long id = this.detectionAlertConfigDAO.save(alertConfig);
- Preconditions.checkNotNull(id);
+ Preconditions.checkNotNull(id, "Error while saving the subscription
group");
return alertConfig.getId();
}
+ @POST
+ @Path("/notification")
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.TEXT_PLAIN)
+ @ApiOperation("Create a notification group using a YAML config")
@SuppressWarnings("unchecked")
- void updateSubscriptionGroup(long oldAlertConfigID, String yamlAlertConfig)
throws ValidationException {
- DetectionAlertConfigDTO oldAlertConfig =
this.detectionAlertConfigDAO.findById(oldAlertConfigID);
- if (oldAlertConfig == null) {
- throw new RuntimeException("Cannot find subscription group " +
oldAlertConfigID);
+ public Response createSubscriptionGroupApi(
+ @ApiParam("payload") String yamlAlertConfig) {
+ Map<String, String> responseMessage = new HashMap<>();
+ long detectionAlertConfigId;
+ try {
+ detectionAlertConfigId = createSubscriptionGroup(yamlAlertConfig);
+ } catch (ValidationException e) {
+ LOG.warn("Validation error while creating subscription group with
payload " + yamlAlertConfig, e);
+ responseMessage.put("message", "Validation Error! " + e.getMessage());
+ return Response.serverError().entity(responseMessage).build();
+ } catch (Exception e) {
+ LOG.error("Error creating subscription group with payload " +
yamlAlertConfig, e);
+ responseMessage.put("message", "Failed to create the subscription group.
Reach out to the ThirdEye team.");
+ responseMessage.put("more-info", "Error = " + e.getMessage());
+ return Response.serverError().entity(responseMessage).build();
}
- notificationValidator.validateYAMLConfig(yamlAlertConfig);
-
- // Translate payload to detection alert config
- TreeMap<String, Object> newAlertConfigMap = new
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- newAlertConfigMap.putAll((Map<String, Object>)
this.yaml.load(yamlAlertConfig));
- DetectionAlertConfigDTO newAlertConfig =
this.alertConfigTranslator.translate(newAlertConfigMap);
- // Update existing alert config with the newly supplied config.
- DetectionAlertConfigDTO updatedAlertConfig =
updateDetectionAlertConfig(oldAlertConfig, newAlertConfig);
- updatedAlertConfig.setYaml(yamlAlertConfig);
-
- // Validate before updating the config
- notificationValidator.validateUpdatedConfig(updatedAlertConfig,
oldAlertConfig);
-
- // Save the updated notification config
- int detectionAlertConfigId =
this.detectionAlertConfigDAO.update(updatedAlertConfig);
- if (detectionAlertConfigId <= 0) {
- throw new RuntimeException("Failed to update the detection alert
config.");
- }
+ LOG.info("Notification group created with id " + detectionAlertConfigId +
" using payload " + yamlAlertConfig);
+ responseMessage.put("message", "The subscription group was created
successfully.");
+ responseMessage.put("detectionAlertConfigId",
String.valueOf(detectionAlertConfigId));
+ return Response.ok().entity(responseMessage).build();
}
/**
@@ -399,7 +384,7 @@ public class YamlResource {
* Update all the fields except the vector clocks and high watermark. The
clocks and watermarks
* are managed by the platform. They shouldn't be reset by the user.
*/
- DetectionAlertConfigDTO updateDetectionAlertConfig(DetectionAlertConfigDTO
oldAlertConfig,
+ private DetectionAlertConfigDTO
updateDetectionAlertConfig(DetectionAlertConfigDTO oldAlertConfig,
DetectionAlertConfigDTO newAlertConfig) {
oldAlertConfig.setName(newAlertConfig.getName());
oldAlertConfig.setCronExpression(newAlertConfig.getCronExpression());
@@ -416,33 +401,30 @@ public class YamlResource {
return oldAlertConfig;
}
- @POST
- @Path("/notification")
- @Produces(MediaType.APPLICATION_JSON)
- @Consumes(MediaType.TEXT_PLAIN)
- @ApiOperation("Create a notification group using a YAML config")
- @SuppressWarnings("unchecked")
- public Response createSubscriptionGroupApi(
- @ApiParam("payload") String yamlAlertConfig) {
- Map<String, String> responseMessage = new HashMap<>();
- long detectionAlertConfigId;
- try {
- detectionAlertConfigId = createSubscriptionGroup(yamlAlertConfig);
- } catch (ValidationException e) {
- LOG.warn("Validation error while creating notification group with
payload " + yamlAlertConfig, e);
- responseMessage.put("message", "Validation Error! " + e.getMessage());
- return Response.serverError().entity(responseMessage).build();
- } catch (Exception e) {
- LOG.error("Error creating notification group with payload " +
yamlAlertConfig, e);
- responseMessage.put("message", "Failed to create the notification group.
Reach out to the ThirdEye team.");
- responseMessage.put("more-info", "Error = " + e.getMessage());
- return Response.serverError().entity(responseMessage).build();
+ void updateSubscriptionGroup(long oldAlertConfigID, String yamlAlertConfig)
throws ValidationException {
+ DetectionAlertConfigDTO oldAlertConfig =
this.detectionAlertConfigDAO.findById(oldAlertConfigID);
+ if (oldAlertConfig == null) {
+ throw new RuntimeException("Cannot find subscription group " +
oldAlertConfigID);
}
+ subscriptionValidator.validateYAMLConfig(yamlAlertConfig);
- LOG.info("Notification group created with id " + detectionAlertConfigId +
" using payload " + yamlAlertConfig);
- responseMessage.put("message", "The notification group was created
successfully.");
- responseMessage.put("detectionAlertConfigId",
String.valueOf(detectionAlertConfigId));
- return Response.ok().entity(responseMessage).build();
+ // Translate payload to detection alert config
+ TreeMap<String, Object> newAlertConfigMap = new
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
newAlertConfigMap.putAll(ConfigUtils.getMap(this.yaml.load(yamlAlertConfig)));
+ DetectionAlertConfigDTO newAlertConfig =
this.alertConfigTranslator.translate(newAlertConfigMap);
+
+ // Update existing alert config with the newly supplied config.
+ DetectionAlertConfigDTO updatedAlertConfig =
updateDetectionAlertConfig(oldAlertConfig, newAlertConfig);
+ updatedAlertConfig.setYaml(yamlAlertConfig);
+
+ // Validate before updating the config
+ subscriptionValidator.validateUpdatedConfig(updatedAlertConfig,
oldAlertConfig);
+
+ // Save the updated subscription config
+ int detectionAlertConfigId =
this.detectionAlertConfigDAO.update(updatedAlertConfig);
+ if (detectionAlertConfigId <= 0) {
+ throw new RuntimeException("Failed to update the detection alert
config.");
+ }
}
@PUT
@@ -457,12 +439,12 @@ public class YamlResource {
try {
updateSubscriptionGroup(id, yamlAlertConfig);
} catch (ValidationException e) {
- LOG.warn("Validation error while updating notification group " + id + "
with payload " + yamlAlertConfig, e);
+ LOG.warn("Validation error while updating subscription group " + id + "
with payload " + yamlAlertConfig, e);
responseMessage.put("message", "Validation Error! " + e.getMessage());
return Response.serverError().entity(responseMessage).build();
} catch (Exception e) {
- LOG.error("Error updating notification group " + id + " with payload " +
yamlAlertConfig, e);
- responseMessage.put("message", "Failed to update the notification group.
Reach out to the ThirdEye team.");
+ LOG.error("Error updating subscription group " + id + " with payload " +
yamlAlertConfig, e);
+ responseMessage.put("message", "Failed to update the subscription group.
Reach out to the ThirdEye team.");
responseMessage.put("more-info", "Error = " + e.getMessage());
return Response.serverError().entity(responseMessage).build();
}
@@ -478,32 +460,37 @@ public class YamlResource {
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.TEXT_PLAIN)
@ApiOperation("Preview the anomaly detection result of a YAML configuration")
- public Response yamlPreview(
+ public Response yamlPreviewApi(
@QueryParam("start") long start,
@QueryParam("end") long end,
@QueryParam("tuningStart") long tuningStart,
@QueryParam("tuningEnd") long tuningEnd,
@ApiParam("jsonPayload") String payload) throws Exception {
- if (StringUtils.isBlank(payload)){
- return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"empty payload")).build();
- }
- Map<String, Object> yamlConfig;
+ Map<String, String> responseMessage = new HashMap<>();
+ DetectionPipelineResult result;
try {
- yamlConfig = (Map<String, Object>) this.yaml.load(payload);
- } catch (Exception e){
- return
Response.status(Response.Status.BAD_REQUEST).entity(ImmutableMap.of("message",
"detection yaml parsing error, " + e.getMessage())).build();
- }
+ detectionValidator.validateYAMLConfig(payload);
- Map<String, String> responseMessage = new HashMap<>();
- DetectionConfigDTO detectionConfig =
- buildDetectionConfigFromYaml(tuningStart, tuningEnd, yamlConfig, null,
responseMessage);
- if (detectionConfig == null) {
- return
Response.status(Response.Status.BAD_REQUEST).entity(responseMessage).build();
+ // Translate config from YAML to detection config (JSON)
+ Map<String, Object> newDetectionConfigMap = new
HashMap<>(ConfigUtils.getMap(this.yaml.load(payload)));
+ DetectionConfigDTO detectionConfig =
buildDetectionConfigFromYaml(tuningStart, tuningEnd, newDetectionConfigMap,
null);
+ Preconditions.checkNotNull(detectionConfig);
+ detectionConfig.setId(Long.MAX_VALUE);
+
+ DetectionPipeline pipeline = this.loader.from(this.provider,
detectionConfig, start, end);
+ result = pipeline.run();
+
+ } catch (ValidationException e) {
+ LOG.warn("Validation error while running preview with payload " +
payload, e);
+ responseMessage.put("message", "Validation Error! " + e.getMessage());
+ return Response.serverError().entity(responseMessage).build();
+ } catch (Exception e) {
+ LOG.error("Error running preview with payload " + payload, e);
+ responseMessage.put("message", "Failed to run the preview due to " +
e.getMessage());
+ return Response.serverError().entity(responseMessage).build();
}
- detectionConfig.setId(Long.MAX_VALUE);
- DetectionPipeline pipeline = this.loader.from(this.provider,
detectionConfig, start, end);
- DetectionPipelineResult result = pipeline.run();
+ LOG.info("Preview successful using payload " + payload);
return Response.ok(result).build();
}
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 279340b..a181b5e 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
@@ -61,7 +61,7 @@ public class YamlResourceTest {
this.yamlResource.createSubscriptionGroup(inValidYaml);
Assert.fail("Exception not thrown on empty yaml");
} catch (Exception e) {
- Assert.assertEquals(e.getMessage(), "Error parsing the Yaml input. Check
for syntax issues.");
+ Assert.assertEquals(e.getMessage(), "Error parsing the Yaml payload.
Check for syntax issues.");
}
String noSubscriptGroupYaml = "application: test_application";
@@ -147,7 +147,7 @@ public class YamlResourceTest {
this.yamlResource.updateSubscriptionGroup(oldId, inValidYaml);
Assert.fail("Exception not thrown on empty yaml");
} catch (Exception e) {
- Assert.assertEquals(e.getMessage(), "Error parsing the Yaml input. Check
for syntax issues.");
+ Assert.assertEquals(e.getMessage(), "Error parsing the Yaml payload.
Check for syntax issues.");
}
String noSubscriptGroupYaml = "application: test_application";
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]