/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
package org.opensearch.ad.rest.handler;
import static org.opensearch.ad.settings.AnomalyDetectorSettings.CONFIG_BUCKET_MINIMUM_SUCCESS_RATE;
import static org.opensearch.ad.settings.AnomalyDetectorSettings.INTERVAL_BUCKET_MINIMUM_SUCCESS_RATE;
import static org.opensearch.ad.settings.AnomalyDetectorSettings.INTERVAL_RECOMMENDATION_DECREASING_MULTIPLIER;
import static org.opensearch.ad.settings.AnomalyDetectorSettings.INTERVAL_RECOMMENDATION_INCREASING_MULTIPLIER;
import static org.opensearch.ad.settings.AnomalyDetectorSettings.MAX_INTERVAL_REC_LENGTH_IN_MINUTES;
import static org.opensearch.ad.settings.AnomalyDetectorSettings.MAX_TIMES_DECREASING_INTERVAL;
import static org.opensearch.ad.settings.AnomalyDetectorSettings.TOP_VALIDATE_TIMEOUT_IN_MILLIS;
import java.io.IOException;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensearch.OpenSearchStatusException;
import org.opensearch.action.ActionListener;
import org.opensearch.action.search.SearchRequest;
import org.opensearch.action.search.SearchResponse;
import org.opensearch.ad.constant.ADCommonMessages;
import org.opensearch.ad.model.AnomalyDetector;
import org.opensearch.ad.settings.AnomalyDetectorSettings;
import org.opensearch.ad.transport.ValidateAnomalyDetectorResponse;
import org.opensearch.client.Client;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.settings.Settings;
import org.opensearch.common.unit.TimeValue;
import org.opensearch.commons.authuser.User;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.core.xcontent.NamedXContentRegistry;
import org.opensearch.index.query.BoolQueryBuilder;
import org.opensearch.index.query.QueryBuilder;
import org.opensearch.index.query.QueryBuilders;
import org.opensearch.index.query.RangeQueryBuilder;
import org.opensearch.search.aggregations.AggregationBuilder;
import org.opensearch.search.aggregations.AggregationBuilders;
import org.opensearch.search.aggregations.Aggregations;
import org.opensearch.search.aggregations.BucketOrder;
import org.opensearch.search.aggregations.PipelineAggregatorBuilders;
import org.opensearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.opensearch.search.aggregations.bucket.composite.CompositeAggregation;
import org.opensearch.search.aggregations.bucket.composite.TermsValuesSourceBuilder;
import org.opensearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.opensearch.search.aggregations.bucket.histogram.Histogram;
import org.opensearch.search.aggregations.bucket.histogram.LongBounds;
import org.opensearch.search.aggregations.bucket.terms.Terms;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.opensearch.search.sort.FieldSortBuilder;
import org.opensearch.search.sort.SortOrder;
import org.opensearch.timeseries.AnalysisType;
import org.opensearch.timeseries.common.exception.EndRunException;
import org.opensearch.timeseries.common.exception.ValidationException;
import org.opensearch.timeseries.constant.CommonMessages;
import org.opensearch.timeseries.feature.SearchFeatureDao;
import org.opensearch.timeseries.model.Feature;
import org.opensearch.timeseries.model.IntervalTimeConfiguration;
import org.opensearch.timeseries.model.MergeableList;
import org.opensearch.timeseries.model.TimeConfiguration;
import org.opensearch.timeseries.model.ValidationAspect;
import org.opensearch.timeseries.model.ValidationIssueType;
import org.opensearch.timeseries.util.MultiResponsesDelegateActionListener;
import org.opensearch.timeseries.util.ParseUtils;
import org.opensearch.timeseries.util.SecurityClientUtil;
/**
*
This class executes all validation checks that are not blocking on the 'model' level.
* This mostly involves checking if the data is generally dense enough to complete model training
* which is based on if enough buckets in the last x intervals have at least 1 document present.
* Initially different bucket aggregations are executed with with every configuration applied and with
* different varying intervals in order to find the best interval for the data. If no interval is found with all
* configuration applied then each configuration is tested sequentially for sparsity
*/
// TODO: Add more UT and IT
public class ModelValidationActionHandler {
protected static final String AGG_NAME_TOP = "top_agg";
protected static final String AGGREGATION = "agg";
protected final AnomalyDetector anomalyDetector;
protected final ClusterService clusterService;
protected final Logger logger = LogManager.getLogger(AbstractAnomalyDetectorActionHandler.class);
protected final TimeValue requestTimeout;
protected final AnomalyDetectorActionHandler handler = new AnomalyDetectorActionHandler();
protected final Client client;
protected final SecurityClientUtil clientUtil;
protected final NamedXContentRegistry xContentRegistry;
protected final ActionListener listener;
protected final SearchFeatureDao searchFeatureDao;
protected final Clock clock;
protected final String validationType;
protected final Settings settings;
protected final User user;
/**
* Constructor function.
*
* @param clusterService ClusterService
* @param client ES node client that executes actions on the local node
* @param clientUtil AD client util
* @param listener ES channel used to construct bytes / builder based outputs, and send responses
* @param anomalyDetector anomaly detector instance
* @param requestTimeout request time out configuration
* @param xContentRegistry Registry which is used for XContentParser
* @param searchFeatureDao Search feature DAO
* @param validationType Specified type for validation
* @param clock clock object to know when to timeout
* @param settings Node settings
* @param user User info
*/
public ModelValidationActionHandler(
ClusterService clusterService,
Client client,
SecurityClientUtil clientUtil,
ActionListener listener,
AnomalyDetector anomalyDetector,
TimeValue requestTimeout,
NamedXContentRegistry xContentRegistry,
SearchFeatureDao searchFeatureDao,
String validationType,
Clock clock,
Settings settings,
User user
) {
this.clusterService = clusterService;
this.client = client;
this.clientUtil = clientUtil;
this.listener = listener;
this.anomalyDetector = anomalyDetector;
this.requestTimeout = requestTimeout;
this.xContentRegistry = xContentRegistry;
this.searchFeatureDao = searchFeatureDao;
this.validationType = validationType;
this.clock = clock;
this.settings = settings;
this.user = user;
}
// Need to first check if multi entity detector or not before doing any sort of validation.
// If detector is HCAD then we will find the top entity and treat as single entity for
// validation purposes
public void checkIfMultiEntityDetector() {
ActionListener