|
12 | 12 | package org.opensearch.ad.indices;
|
13 | 13 |
|
14 | 14 | import static org.mockito.ArgumentMatchers.any;
|
| 15 | +import static org.mockito.ArgumentMatchers.anyString; |
15 | 16 | import static org.mockito.Mockito.doAnswer;
|
| 17 | +import static org.mockito.Mockito.doReturn; |
16 | 18 | import static org.mockito.Mockito.mock;
|
17 | 19 | import static org.mockito.Mockito.never;
|
18 | 20 | import static org.mockito.Mockito.times;
|
19 | 21 | import static org.mockito.Mockito.verify;
|
20 | 22 | import static org.mockito.Mockito.when;
|
| 23 | +import static org.opensearch.timeseries.TestHelpers.createSearchResponse; |
| 24 | +import static org.opensearch.timeseries.constant.CommonName.CONFIG_INDEX; |
21 | 25 |
|
| 26 | +import java.io.IOException; |
| 27 | +import java.io.InputStream; |
22 | 28 | import java.time.Instant;
|
| 29 | +import java.util.ArrayList; |
23 | 30 | import java.util.Arrays;
|
24 | 31 | import java.util.Collections;
|
25 | 32 | import java.util.HashSet;
|
| 33 | +import java.util.List; |
| 34 | +import java.util.Locale; |
26 | 35 | import java.util.Map;
|
27 | 36 |
|
| 37 | +import com.google.common.collect.ImmutableList; |
| 38 | +import org.apache.lucene.search.TotalHits; |
| 39 | +import org.opensearch.Version; |
28 | 40 | import org.opensearch.action.admin.cluster.state.ClusterStateRequest;
|
29 | 41 | import org.opensearch.action.admin.cluster.state.ClusterStateResponse;
|
30 | 42 | import org.opensearch.action.admin.indices.create.CreateIndexRequest;
|
31 | 43 | import org.opensearch.action.admin.indices.rollover.Condition;
|
| 44 | +import org.opensearch.action.admin.indices.rollover.MaxAgeCondition; |
32 | 45 | import org.opensearch.action.admin.indices.rollover.MaxDocsCondition;
|
| 46 | +import org.opensearch.action.admin.indices.rollover.MaxSizeCondition; |
33 | 47 | import org.opensearch.action.admin.indices.rollover.RolloverRequest;
|
34 | 48 | import org.opensearch.action.admin.indices.rollover.RolloverResponse;
|
| 49 | +import org.opensearch.action.search.SearchResponse; |
| 50 | +import org.opensearch.action.search.ShardSearchFailure; |
35 | 51 | import org.opensearch.action.support.master.AcknowledgedResponse;
|
36 | 52 | import org.opensearch.ad.constant.ADCommonName;
|
| 53 | +import org.opensearch.ad.mock.model.MockSimpleLog; |
| 54 | +import org.opensearch.ad.model.AnomalyDetector; |
37 | 55 | import org.opensearch.ad.settings.AnomalyDetectorSettings;
|
38 | 56 | import org.opensearch.client.AdminClient;
|
39 | 57 | import org.opensearch.client.Client;
|
40 | 58 | import org.opensearch.client.ClusterAdminClient;
|
41 | 59 | import org.opensearch.client.IndicesAdminClient;
|
42 | 60 | import org.opensearch.cluster.ClusterName;
|
43 | 61 | import org.opensearch.cluster.ClusterState;
|
| 62 | +import org.opensearch.cluster.metadata.AliasMetadata; |
| 63 | +import org.opensearch.cluster.metadata.IndexMetadata; |
44 | 64 | import org.opensearch.cluster.metadata.Metadata;
|
45 | 65 | import org.opensearch.cluster.service.ClusterService;
|
| 66 | +import org.opensearch.common.recycler.Recycler; |
46 | 67 | import org.opensearch.common.settings.ClusterSettings;
|
47 | 68 | import org.opensearch.common.settings.Settings;
|
| 69 | +import org.opensearch.common.unit.TimeValue; |
48 | 70 | import org.opensearch.core.action.ActionListener;
|
| 71 | +import org.opensearch.core.common.bytes.BytesArray; |
| 72 | +import org.opensearch.core.common.bytes.BytesReference; |
| 73 | +import org.opensearch.core.common.unit.ByteSizeUnit; |
| 74 | +import org.opensearch.core.common.unit.ByteSizeValue; |
| 75 | +import org.opensearch.core.xcontent.DeprecationHandler; |
| 76 | +import org.opensearch.core.xcontent.MediaType; |
49 | 77 | import org.opensearch.core.xcontent.NamedXContentRegistry;
|
| 78 | +import org.opensearch.core.xcontent.XContentParser; |
| 79 | +import org.opensearch.search.SearchHit; |
| 80 | +import org.opensearch.search.SearchHits; |
| 81 | +import org.opensearch.search.aggregations.AggregationBuilder; |
| 82 | +import org.opensearch.search.aggregations.InternalAggregations; |
| 83 | +import org.opensearch.search.internal.InternalSearchResponse; |
50 | 84 | import org.opensearch.threadpool.ThreadPool;
|
51 | 85 | import org.opensearch.timeseries.AbstractTimeSeriesTest;
|
| 86 | +import org.opensearch.timeseries.TestHelpers; |
| 87 | +import org.opensearch.timeseries.function.BiCheckedFunction; |
| 88 | +import org.opensearch.timeseries.model.Config; |
| 89 | +import org.opensearch.timeseries.model.Feature; |
52 | 90 | import org.opensearch.timeseries.settings.TimeSeriesSettings;
|
53 | 91 | import org.opensearch.timeseries.util.DiscoveryNodeFilterer;
|
54 | 92 |
|
55 | 93 | public class RolloverTests extends AbstractTimeSeriesTest {
|
56 | 94 | private ADIndexManagement adIndices;
|
57 | 95 | private IndicesAdminClient indicesClient;
|
58 | 96 | private ClusterAdminClient clusterAdminClient;
|
| 97 | + private Client client; |
59 | 98 | private ClusterName clusterName;
|
60 | 99 | private ClusterState clusterState;
|
61 | 100 | private ClusterService clusterService;
|
| 101 | + private NamedXContentRegistry namedXContentRegistry; |
62 | 102 | private long defaultMaxDocs;
|
63 | 103 | private int numberOfNodes;
|
64 | 104 |
|
65 | 105 | @Override
|
66 | 106 | public void setUp() throws Exception {
|
67 | 107 | super.setUp();
|
68 |
| - Client client = mock(Client.class); |
| 108 | + client = mock(Client.class); |
69 | 109 | indicesClient = mock(IndicesAdminClient.class);
|
70 | 110 | AdminClient adminClient = mock(AdminClient.class);
|
71 | 111 | clusterService = mock(ClusterService.class);
|
@@ -98,14 +138,16 @@ public void setUp() throws Exception {
|
98 | 138 | numberOfNodes = 2;
|
99 | 139 | when(nodeFilter.getNumberOfEligibleDataNodes()).thenReturn(numberOfNodes);
|
100 | 140 |
|
| 141 | + namedXContentRegistry = TestHelpers.xContentRegistry(); |
| 142 | + |
101 | 143 | adIndices = new ADIndexManagement(
|
102 | 144 | client,
|
103 | 145 | clusterService,
|
104 | 146 | threadPool,
|
105 | 147 | settings,
|
106 | 148 | nodeFilter,
|
107 | 149 | TimeSeriesSettings.MAX_UPDATE_RETRY_TIMES,
|
108 |
| - NamedXContentRegistry.EMPTY |
| 150 | + namedXContentRegistry |
109 | 151 | );
|
110 | 152 |
|
111 | 153 | clusterAdminClient = mock(ClusterAdminClient.class);
|
@@ -248,4 +290,128 @@ public void testRetryingDelete() {
|
248 | 290 | // 1 group delete, 1 separate retry for each index to delete
|
249 | 291 | verify(indicesClient, times(2)).delete(any(), any());
|
250 | 292 | }
|
| 293 | + |
| 294 | + public void testNoCustomResultIndexFound_RolloverDefaultResultIndex_shouldSucceed() { |
| 295 | + setUpGetConfigs_withNoCustomResultIndexAlias(); |
| 296 | + setUpRolloverSuccess(); |
| 297 | + |
| 298 | + adIndices.rolloverAndDeleteHistoryIndex(); |
| 299 | + verify(indicesClient, times(1)).rolloverIndex(any(), any()); |
| 300 | + verify(client, times(1)).search(any(), any()); |
| 301 | + } |
| 302 | + |
| 303 | + public void testCustomResultIndexFound_RolloverCustomResultIndex_withConditions_shouldSucceed() throws IOException { |
| 304 | + setUpGetConfigs_withCustomResultIndexAlias(); |
| 305 | + setUpRolloverSuccessForCustomIndex(); |
| 306 | + |
| 307 | + adIndices.rolloverAndDeleteHistoryIndex(); |
| 308 | + |
| 309 | + verify(indicesClient, times(1)).rolloverIndex(any(), any()); |
| 310 | + verify(client, times(1)).search(any(), any()); |
| 311 | + } |
| 312 | + |
| 313 | + private void setUpGetConfigs_withNoCustomResultIndexAlias() { |
| 314 | + Metadata.Builder metaBuilder = Metadata |
| 315 | + .builder() |
| 316 | + .put(indexMeta(".opendistro-anomaly-detectors", 1L, ADCommonName.ANOMALY_RESULT_INDEX_ALIAS), true); |
| 317 | + clusterState = ClusterState.builder(clusterName).metadata(metaBuilder.build()).build(); |
| 318 | + when(clusterService.state()).thenReturn(clusterState); |
| 319 | + |
| 320 | + String detectorString = "{\"name\":\"AhtYYGWTgqkzairTchcs\",\"description\":\"iIiAVPMyFgnFlEniLbMyfJxyoGvJAl\"," |
| 321 | + + "\"time_field\":\"HmdFH\",\"indices\":[\"ffsBF\"],\"filter_query\":{\"bool\":{\"filter\":[{\"exists\":" |
| 322 | + + "{\"field\":\"value\",\"boost\":1}}],\"adjust_pure_negative\":true,\"boost\":1}},\"window_delay\":" |
| 323 | + + "{\"period\":{\"interval\":2,\"unit\":\"Minutes\"}},\"shingle_size\":8,\"schema_version\":-512063255," |
| 324 | + + "\"feature_attributes\":[{\"feature_id\":\"OTYJs\",\"feature_name\":\"eYYCM\",\"feature_enabled\":false," |
| 325 | + + "\"aggregation_query\":{\"XzewX\":{\"value_count\":{\"field\":\"ok\"}}}}],\"recency_emphasis\":3342," |
| 326 | + + "\"history\":62,\"last_update_time\":1717192049845,\"category_field\":[\"Tcqcb\"],\"customResultIndexOrAlias\":" |
| 327 | + + "\"\",\"imputation_option\":{\"method\":\"FIXED_VALUES\",\"defaultFill\"" |
| 328 | + + ":[],\"integerSensitive\":false},\"suggested_seasonality\":64,\"detection_interval\":{\"period\":" |
| 329 | + + "{\"interval\":5,\"unit\":\"Minutes\"}},\"detector_type\":\"MULTI_ENTITY\",\"rules\":[]}"; |
| 330 | + |
| 331 | + doAnswer(invocation -> { |
| 332 | + ActionListener<SearchResponse> listener = invocation.getArgument(1); |
| 333 | + SearchHit config = SearchHit.fromXContent(TestHelpers.parser(detectorString)); |
| 334 | + SearchHits searchHits = new SearchHits(new SearchHit[] { config }, new TotalHits(1, TotalHits.Relation.EQUAL_TO), Float.NaN); |
| 335 | + InternalSearchResponse response = new InternalSearchResponse( |
| 336 | + searchHits, |
| 337 | + InternalAggregations.EMPTY, |
| 338 | + null, |
| 339 | + null, |
| 340 | + false, |
| 341 | + null, |
| 342 | + 1 |
| 343 | + ); |
| 344 | + SearchResponse searchResponse = new SearchResponse( |
| 345 | + response, |
| 346 | + null, |
| 347 | + 1, |
| 348 | + 1, |
| 349 | + 0, |
| 350 | + 100, |
| 351 | + ShardSearchFailure.EMPTY_ARRAY, |
| 352 | + SearchResponse.Clusters.EMPTY |
| 353 | + ); |
| 354 | + listener.onResponse(searchResponse); |
| 355 | + return null; |
| 356 | + }).when(client).search(any(), any()); |
| 357 | + } |
| 358 | + |
| 359 | + private void setUpRolloverSuccessForCustomIndex() { |
| 360 | + doAnswer(invocation -> { |
| 361 | + RolloverRequest request = invocation.getArgument(0); |
| 362 | + @SuppressWarnings("unchecked") |
| 363 | + ActionListener<RolloverResponse> listener = (ActionListener<RolloverResponse>) invocation.getArgument(1); |
| 364 | + |
| 365 | + assertEquals("opensearch-ad-plugin-result-", request.indices()[0]); |
| 366 | + Map<String, Condition<?>> conditions = request.getConditions(); |
| 367 | + assertEquals(2, conditions.size()); |
| 368 | + assertEquals(new MaxAgeCondition(TimeValue.timeValueDays(7)), conditions.get(MaxAgeCondition.NAME)); |
| 369 | + assertEquals(new MaxSizeCondition(new ByteSizeValue(51200, ByteSizeUnit.MB)), conditions.get(MaxSizeCondition.NAME)); |
| 370 | + |
| 371 | + CreateIndexRequest createIndexRequest = request.getCreateIndexRequest(); |
| 372 | + assertEquals("<opensearch-ad-plugin-result--history-{now/d}-1>", createIndexRequest.index()); |
| 373 | + assertTrue(createIndexRequest.mappings().contains("data_start_time")); |
| 374 | + listener.onResponse(new RolloverResponse(null, null, Collections.emptyMap(), request.isDryRun(), true, true, true)); |
| 375 | + return null; |
| 376 | + }).when(indicesClient).rolloverIndex(any(), any()); |
| 377 | + } |
| 378 | + |
| 379 | + private void setUpGetConfigs_withCustomResultIndexAlias() throws IOException { |
| 380 | + IndexMetadata defaultResultIndex = IndexMetadata.builder(".opendistro-anomaly-detectors") |
| 381 | + .settings(settings(Version.CURRENT)) |
| 382 | + .putAlias(AliasMetadata.builder(ADCommonName.ANOMALY_RESULT_INDEX_ALIAS).writeIndex(true).build()) |
| 383 | + .numberOfShards(1) |
| 384 | + .numberOfReplicas(0) |
| 385 | + .build(); |
| 386 | + IndexMetadata customResultIndex = IndexMetadata.builder("opensearch-ad-plugin-result-test") |
| 387 | + .settings(settings(Version.CURRENT)) |
| 388 | + .numberOfShards(1) |
| 389 | + .numberOfReplicas(0) |
| 390 | + .putAlias(AliasMetadata.builder(ADCommonName.CUSTOM_RESULT_INDEX_PREFIX).writeIndex(true).build()) |
| 391 | + .build(); |
| 392 | + |
| 393 | + clusterState = ClusterState.builder(ClusterState.EMPTY_STATE) |
| 394 | + .metadata(Metadata.builder().put(defaultResultIndex, false).put(customResultIndex, false).build()) |
| 395 | + .build(); |
| 396 | + |
| 397 | + when(clusterService.state()).thenReturn(clusterState); |
| 398 | + |
| 399 | + String detectorStringWithCustomResultIndex = "{\"name\":\"todagtCMkwpcaedpyYUM\",\"description\":\"ClrcaMpuLfeDSlVduRcKlqPZyqWDBf\"," + |
| 400 | + "\"time_field\":\"dJRwh\",\"indices\":[\"eIrgWMqAED\"],\"feature_attributes\":[{\"feature_id\":\"lxYRN\"," + |
| 401 | + "\"feature_name\":\"eqSeU\",\"feature_enabled\":true,\"aggregation_query\":{\"aa\":{\"value_count\":{\"field\":\"ok\"}}}}]," + |
| 402 | + "\"detection_interval\":{\"period\":{\"interval\":425,\"unit\":\"Minutes\"}}," + |
| 403 | + "\"window_delay\":{\"period\":{\"interval\":973,\"unit\":\"Minutes\"}},\"shingle_size\":4,\"schema_version\":-1203962153," + |
| 404 | + "\"ui_metadata\":{\"JbAaV\":{\"feature_id\":\"rIFjS\",\"feature_name\":\"QXCmS\",\"feature_enabled\":false," + |
| 405 | + "\"aggregation_query\":{\"aa\":{\"value_count\":{\"field\":\"ok\"}}}}},\"last_update_time\":1568396089028," + |
| 406 | + "\"result_index\":\"opensearch-ad-plugin-result-\",\"result_index_min_size\":51200,\"result_index_min_age\":7}"; |
| 407 | + |
| 408 | + AnomalyDetector parsedDetector = AnomalyDetector.parse(TestHelpers.parser(detectorStringWithCustomResultIndex), "id", 1L, null, null); |
| 409 | + |
| 410 | + doAnswer(invocation -> { |
| 411 | + Object[] args = invocation.getArguments(); |
| 412 | + ActionListener<SearchResponse> listener = (ActionListener<SearchResponse>) args[1]; |
| 413 | + listener.onResponse(createSearchResponse(parsedDetector)); |
| 414 | + return null; |
| 415 | + }).when(client).search(any(), any()); |
| 416 | + } |
251 | 417 | }
|
0 commit comments