Skip to content

Commit a2c1f2d

Browse files
authored
Implement unit tests for AnomalyDetectorsIndex class (#52417)
* Implement unit tests for AnomalyDetectorsIndex class * Remove unused import * Apply review comments
1 parent 2f6ac68 commit a2c1f2d

File tree

1 file changed

+203
-0
lines changed

1 file changed

+203
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.core.ml.job.persistence;
7+
8+
import org.elasticsearch.Version;
9+
import org.elasticsearch.action.ActionListener;
10+
import org.elasticsearch.action.admin.indices.alias.Alias;
11+
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesAction;
12+
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
13+
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest.AliasActions;
14+
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequestBuilder;
15+
import org.elasticsearch.action.admin.indices.create.CreateIndexAction;
16+
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
17+
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
18+
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
19+
import org.elasticsearch.action.support.master.AcknowledgedResponse;
20+
import org.elasticsearch.client.AdminClient;
21+
import org.elasticsearch.client.Client;
22+
import org.elasticsearch.client.IndicesAdminClient;
23+
import org.elasticsearch.cluster.ClusterName;
24+
import org.elasticsearch.cluster.ClusterState;
25+
import org.elasticsearch.cluster.metadata.AliasMetaData;
26+
import org.elasticsearch.cluster.metadata.IndexMetaData;
27+
import org.elasticsearch.cluster.metadata.MetaData;
28+
import org.elasticsearch.common.collect.ImmutableOpenMap;
29+
import org.elasticsearch.common.settings.Settings;
30+
import org.elasticsearch.common.util.concurrent.ThreadContext;
31+
import org.elasticsearch.test.ESTestCase;
32+
import org.elasticsearch.threadpool.ThreadPool;
33+
import org.junit.After;
34+
import org.junit.Before;
35+
import org.mockito.ArgumentCaptor;
36+
import org.mockito.InOrder;
37+
import org.mockito.stubbing.Answer;
38+
39+
import java.util.Arrays;
40+
import java.util.Collections;
41+
import java.util.List;
42+
import java.util.Map;
43+
import java.util.function.Function;
44+
45+
import static java.util.stream.Collectors.toMap;
46+
import static org.hamcrest.Matchers.contains;
47+
import static org.hamcrest.Matchers.equalTo;
48+
import static org.mockito.Matchers.any;
49+
import static org.mockito.Mockito.doAnswer;
50+
import static org.mockito.Mockito.inOrder;
51+
import static org.mockito.Mockito.mock;
52+
import static org.mockito.Mockito.verify;
53+
import static org.mockito.Mockito.verifyNoMoreInteractions;
54+
import static org.mockito.Mockito.when;
55+
56+
public class AnomalyDetectorsIndexTests extends ESTestCase {
57+
58+
private static final String ML_STATE = ".ml-state";
59+
private static final String ML_STATE_WRITE_ALIAS = ".ml-state-write";
60+
61+
private ThreadPool threadPool;
62+
private IndicesAdminClient indicesAdminClient;
63+
private AdminClient adminClient;
64+
private Client client;
65+
private ActionListener<Boolean> finalListener;
66+
67+
private ArgumentCaptor<CreateIndexRequest> createRequestCaptor;
68+
private ArgumentCaptor<IndicesAliasesRequest> aliasesRequestCaptor;
69+
70+
@Before
71+
public void setUpMocks() {
72+
threadPool = mock(ThreadPool.class);
73+
when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY));
74+
75+
indicesAdminClient = mock(IndicesAdminClient.class);
76+
when(indicesAdminClient.prepareCreate(ML_STATE))
77+
.thenReturn(new CreateIndexRequestBuilder(client, CreateIndexAction.INSTANCE, ML_STATE));
78+
doAnswer(withResponse(new CreateIndexResponse(true, true, ML_STATE))).when(indicesAdminClient).create(any(), any());
79+
when(indicesAdminClient.prepareAliases()).thenReturn(new IndicesAliasesRequestBuilder(client, IndicesAliasesAction.INSTANCE));
80+
doAnswer(withResponse(new AcknowledgedResponse(true))).when(indicesAdminClient).aliases(any(), any());
81+
82+
adminClient = mock(AdminClient.class);
83+
when(adminClient.indices()).thenReturn(indicesAdminClient);
84+
85+
client = mock(Client.class);
86+
when(client.threadPool()).thenReturn(threadPool);
87+
when(client.admin()).thenReturn(adminClient);
88+
89+
finalListener = mock(ActionListener.class);
90+
91+
createRequestCaptor = ArgumentCaptor.forClass(CreateIndexRequest.class);
92+
aliasesRequestCaptor = ArgumentCaptor.forClass(IndicesAliasesRequest.class);
93+
}
94+
95+
@After
96+
public void verifyNoMoreInteractionsWithMocks() {
97+
verifyNoMoreInteractions(indicesAdminClient, finalListener);
98+
}
99+
100+
public void testCreateStateIndexAndAliasIfNecessary_CleanState() {
101+
ClusterState clusterState = createClusterState(Collections.emptyMap());
102+
AnomalyDetectorsIndex.createStateIndexAndAliasIfNecessary(client, clusterState, finalListener);
103+
104+
InOrder inOrder = inOrder(indicesAdminClient, finalListener);
105+
inOrder.verify(indicesAdminClient).prepareCreate(ML_STATE);
106+
inOrder.verify(indicesAdminClient).create(createRequestCaptor.capture(), any());
107+
inOrder.verify(finalListener).onResponse(true);
108+
109+
CreateIndexRequest createRequest = createRequestCaptor.getValue();
110+
assertThat(createRequest.index(), equalTo(ML_STATE));
111+
assertThat(createRequest.aliases(), equalTo(Collections.singleton(new Alias(ML_STATE_WRITE_ALIAS))));
112+
}
113+
114+
private void assertNoClientInteractionsWhenWriteAliasAlreadyExists(String indexName) {
115+
ClusterState clusterState = createClusterState(Collections.singletonMap(indexName, createIndexMetaDataWithAlias(indexName)));
116+
AnomalyDetectorsIndex.createStateIndexAndAliasIfNecessary(client, clusterState, finalListener);
117+
118+
verify(finalListener).onResponse(false);
119+
}
120+
121+
public void testCreateStateIndexAndAliasIfNecessary_WriteAliasAlreadyExistsAndPointsAtInitialStateIndex() {
122+
assertNoClientInteractionsWhenWriteAliasAlreadyExists(".ml-state-000001");
123+
}
124+
125+
public void testCreateStateIndexAndAliasIfNecessary_WriteAliasAlreadyExistsAndPointsAtSubsequentStateIndex() {
126+
assertNoClientInteractionsWhenWriteAliasAlreadyExists(".ml-state-000007");
127+
}
128+
129+
public void testCreateStateIndexAndAliasIfNecessary_WriteAliasAlreadyExistsAndPointsAtDummyIndex() {
130+
assertNoClientInteractionsWhenWriteAliasAlreadyExists("dummy-index");
131+
}
132+
133+
private void assertMlStateWriteAliasAddedToMostRecentMlStateIndex(List<String> existingIndexNames, String expectedWriteIndexName) {
134+
ClusterState clusterState =
135+
createClusterState(
136+
existingIndexNames.stream().collect(toMap(Function.identity(), AnomalyDetectorsIndexTests::createIndexMetaData)));
137+
AnomalyDetectorsIndex.createStateIndexAndAliasIfNecessary(client, clusterState, finalListener);
138+
139+
InOrder inOrder = inOrder(indicesAdminClient, finalListener);
140+
inOrder.verify(indicesAdminClient).prepareAliases();
141+
inOrder.verify(indicesAdminClient).aliases(aliasesRequestCaptor.capture(), any());
142+
inOrder.verify(finalListener).onResponse(true);
143+
144+
IndicesAliasesRequest indicesAliasesRequest = aliasesRequestCaptor.getValue();
145+
assertThat(
146+
indicesAliasesRequest.getAliasActions(),
147+
contains(AliasActions.add().alias(ML_STATE_WRITE_ALIAS).index(expectedWriteIndexName)));
148+
}
149+
150+
public void testCreateStateIndexAndAliasIfNecessary_WriteAliasDoesNotExistButInitialStateIndexExists() {
151+
assertMlStateWriteAliasAddedToMostRecentMlStateIndex(
152+
Arrays.asList(".ml-state-000001"), ".ml-state-000001");
153+
}
154+
155+
public void testCreateStateIndexAndAliasIfNecessary_WriteAliasDoesNotExistButSubsequentStateIndicesExist() {
156+
assertMlStateWriteAliasAddedToMostRecentMlStateIndex(
157+
Arrays.asList(".ml-state-000003", ".ml-state-000040", ".ml-state-000500"), ".ml-state-000500");
158+
}
159+
160+
public void testCreateStateIndexAndAliasIfNecessary_WriteAliasDoesNotExistButBothLegacyAndNewStateIndicesDoExist() {
161+
assertMlStateWriteAliasAddedToMostRecentMlStateIndex(
162+
Arrays.asList(ML_STATE, ".ml-state-000003", ".ml-state-000040", ".ml-state-000500"), ".ml-state-000500");
163+
}
164+
165+
@SuppressWarnings("unchecked")
166+
private static <Response> Answer<Response> withResponse(Response response) {
167+
return invocationOnMock -> {
168+
ActionListener<Response> listener = (ActionListener<Response>) invocationOnMock.getArguments()[1];
169+
listener.onResponse(response);
170+
return null;
171+
};
172+
}
173+
174+
private static ClusterState createClusterState(Map<String, IndexMetaData> indices) {
175+
return ClusterState.builder(ClusterName.DEFAULT)
176+
.metaData(MetaData.builder()
177+
.indices(ImmutableOpenMap.<String, IndexMetaData>builder().putAll(indices).build()).build())
178+
.build();
179+
}
180+
181+
private static IndexMetaData createIndexMetaData(String indexName) {
182+
return createIndexMetaData(indexName, false);
183+
}
184+
185+
private static IndexMetaData createIndexMetaDataWithAlias(String indexName) {
186+
return createIndexMetaData(indexName, true);
187+
}
188+
189+
private static IndexMetaData createIndexMetaData(String indexName, boolean withAlias) {
190+
Settings settings =
191+
Settings.builder()
192+
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
193+
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
194+
.put(IndexMetaData.SETTING_INDEX_VERSION_CREATED.getKey(), Version.CURRENT)
195+
.build();
196+
IndexMetaData.Builder builder = IndexMetaData.builder(indexName)
197+
.settings(settings);
198+
if (withAlias) {
199+
builder.putAlias(AliasMetaData.builder(ML_STATE_WRITE_ALIAS).build());
200+
}
201+
return builder.build();
202+
}
203+
}

0 commit comments

Comments
 (0)