|
6 | 6 | */
|
7 | 7 | package org.elasticsearch.xpack.watcher;
|
8 | 8 |
|
| 9 | +import org.elasticsearch.ElasticsearchTimeoutException; |
9 | 10 | import org.elasticsearch.Version;
|
10 | 11 | import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
|
11 | 12 | import org.elasticsearch.cluster.ClusterChangedEvent;
|
|
41 | 42 | import java.util.Collections;
|
42 | 43 | import java.util.HashSet;
|
43 | 44 | import java.util.List;
|
| 45 | +import java.util.concurrent.TimeoutException; |
| 46 | +import java.util.concurrent.atomic.AtomicBoolean; |
| 47 | +import java.util.function.Consumer; |
44 | 48 | import java.util.stream.Collectors;
|
45 | 49 | import java.util.stream.IntStream;
|
46 | 50 |
|
47 | 51 | import static java.util.Arrays.asList;
|
48 | 52 | import static org.elasticsearch.cluster.routing.ShardRoutingState.RELOCATING;
|
49 | 53 | import static org.elasticsearch.cluster.routing.ShardRoutingState.STARTED;
|
50 | 54 | import static org.elasticsearch.xpack.core.watcher.support.WatcherIndexTemplateRegistryField.HISTORY_TEMPLATE_NAME;
|
| 55 | +import static org.hamcrest.Matchers.equalTo; |
51 | 56 | import static org.hamcrest.Matchers.hasSize;
|
52 | 57 | import static org.hamcrest.Matchers.is;
|
53 | 58 | import static org.mockito.ArgumentMatchers.any;
|
@@ -174,14 +179,79 @@ public void testManualStartStop() {
|
174 | 179 | reset(watcherService);
|
175 | 180 | when(watcherService.validate(clusterState)).thenReturn(true);
|
176 | 181 | lifeCycleService.clusterChanged(new ClusterChangedEvent("any", clusterState, stoppedClusterState));
|
177 |
| - verify(watcherService, times(1)).start(eq(clusterState), any()); |
| 182 | + verify(watcherService, times(1)).start(eq(clusterState), any(), any()); |
178 | 183 |
|
179 | 184 | // no change, keep going
|
180 | 185 | reset(watcherService);
|
181 | 186 | lifeCycleService.clusterChanged(new ClusterChangedEvent("any", clusterState, clusterState));
|
182 | 187 | verifyNoMoreInteractions(watcherService);
|
183 | 188 | }
|
184 | 189 |
|
| 190 | + @SuppressWarnings("unchecked") |
| 191 | + public void testExceptionOnStart() { |
| 192 | + /* |
| 193 | + * This tests that if watcher fails to start because of some exception (for example a timeout while refreshing indices) that it |
| 194 | + * will fail gracefully, and will start the next time there is a cluster change event if there is no exception that time. |
| 195 | + */ |
| 196 | + Index index = new Index(Watch.INDEX, "uuid"); |
| 197 | + IndexRoutingTable.Builder indexRoutingTableBuilder = IndexRoutingTable.builder(index); |
| 198 | + indexRoutingTableBuilder.addShard( |
| 199 | + TestShardRouting.newShardRouting(new ShardId(index, 0), "node_1", true, ShardRoutingState.STARTED) |
| 200 | + ); |
| 201 | + IndexMetadata.Builder indexMetadataBuilder = IndexMetadata.builder(Watch.INDEX) |
| 202 | + .settings(settings(Version.CURRENT).put(IndexMetadata.INDEX_FORMAT_SETTING.getKey(), 6)) // the internal index format, required |
| 203 | + .numberOfShards(1) |
| 204 | + .numberOfReplicas(0); |
| 205 | + Metadata.Builder metadataBuilder = Metadata.builder() |
| 206 | + .put(indexMetadataBuilder) |
| 207 | + .put(IndexTemplateMetadata.builder(HISTORY_TEMPLATE_NAME).patterns(randomIndexPatterns())); |
| 208 | + if (randomBoolean()) { |
| 209 | + metadataBuilder.putCustom(WatcherMetadata.TYPE, new WatcherMetadata(false)); |
| 210 | + } |
| 211 | + Metadata metadata = metadataBuilder.build(); |
| 212 | + IndexRoutingTable indexRoutingTable = indexRoutingTableBuilder.build(); |
| 213 | + ClusterState clusterState = ClusterState.builder(new ClusterName("my-cluster")) |
| 214 | + .nodes(new DiscoveryNodes.Builder().masterNodeId("node_1").localNodeId("node_1").add(newNode("node_1"))) |
| 215 | + .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) |
| 216 | + .metadata(metadata) |
| 217 | + .build(); |
| 218 | + |
| 219 | + // mark watcher manually as stopped |
| 220 | + ClusterState stoppedClusterState = ClusterState.builder(new ClusterName("my-cluster")) |
| 221 | + .nodes(new DiscoveryNodes.Builder().masterNodeId("node_1").localNodeId("node_1").add(newNode("node_1"))) |
| 222 | + .routingTable(RoutingTable.builder().add(indexRoutingTable).build()) |
| 223 | + .metadata(Metadata.builder(metadata).putCustom(WatcherMetadata.TYPE, new WatcherMetadata(true)).build()) |
| 224 | + .build(); |
| 225 | + |
| 226 | + lifeCycleService.clusterChanged(new ClusterChangedEvent("foo", stoppedClusterState, clusterState)); |
| 227 | + assertThat(lifeCycleService.getState().get(), equalTo(WatcherState.STOPPING)); |
| 228 | + |
| 229 | + // Now attempt to start watcher with a simulated TimeoutException. Should be stopped |
| 230 | + when(watcherService.validate(clusterState)).thenReturn(true); |
| 231 | + AtomicBoolean exceptionHit = new AtomicBoolean(false); |
| 232 | + doAnswer(invocation -> { |
| 233 | + Consumer<Exception> exceptionConsumer = invocation.getArgument(2); |
| 234 | + exceptionConsumer.accept(new ElasticsearchTimeoutException(new TimeoutException("Artificial timeout"))); |
| 235 | + exceptionHit.set(true); |
| 236 | + return null; |
| 237 | + }).when(watcherService).start(any(), any(), any(Consumer.class)); |
| 238 | + lifeCycleService.clusterChanged(new ClusterChangedEvent("any", clusterState, clusterState)); |
| 239 | + assertTrue("Expected simulated timeout not hit", exceptionHit.get()); |
| 240 | + assertThat(lifeCycleService.getState().get(), equalTo(WatcherState.STOPPED)); |
| 241 | + |
| 242 | + // And now attempt to start watcher with no exception. It should start up. |
| 243 | + AtomicBoolean runnableCalled = new AtomicBoolean(false); |
| 244 | + doAnswer(invocation -> { |
| 245 | + Runnable runnable = invocation.getArgument(1); |
| 246 | + runnable.run(); |
| 247 | + runnableCalled.set(true); |
| 248 | + return null; |
| 249 | + }).when(watcherService).start(any(), any(Runnable.class), any()); |
| 250 | + lifeCycleService.clusterChanged(new ClusterChangedEvent("any", clusterState, clusterState)); |
| 251 | + assertTrue("Runnable not called", runnableCalled.get()); |
| 252 | + assertThat(lifeCycleService.getState().get(), equalTo(WatcherState.STARTED)); |
| 253 | + } |
| 254 | + |
185 | 255 | public void testNoLocalShards() {
|
186 | 256 | Index watchIndex = new Index(Watch.INDEX, "foo");
|
187 | 257 | ShardId shardId = new ShardId(watchIndex, 0);
|
@@ -443,7 +513,7 @@ public void testWatcherServiceDoesNotStartIfIndexTemplatesAreMissing() throws Ex
|
443 | 513 | when(watcherService.validate(eq(state))).thenReturn(true);
|
444 | 514 |
|
445 | 515 | lifeCycleService.clusterChanged(new ClusterChangedEvent("any", state, state));
|
446 |
| - verify(watcherService, times(0)).start(any(ClusterState.class), any()); |
| 516 | + verify(watcherService, times(0)).start(any(ClusterState.class), any(), any()); |
447 | 517 | }
|
448 | 518 |
|
449 | 519 | public void testWatcherStopsWhenMasterNodeIsMissing() {
|
|
0 commit comments