@@ -258,6 +258,91 @@ public void testExceptionOnStart() {
258258 assertThat (lifeCycleService .getState ().get (), equalTo (WatcherState .STARTED ));
259259 }
260260
261+ public void testReloadWithIdenticalRoutingTable () {
262+ /*
263+ * This tests that the identical routing table causes reload only once.
264+ */
265+ startWatcher ();
266+
267+ ClusterChangedEvent [] events = masterChangeScenario ();
268+ assertThat (events [1 ].previousState (), equalTo (events [0 ].state ()));
269+ assertFalse (events [1 ].routingTableChanged ());
270+
271+ for (ClusterChangedEvent event : events ) {
272+ when (watcherService .validate (event .state ())).thenReturn (true );
273+ lifeCycleService .clusterChanged (event );
274+ }
275+ // reload should occur on the first event
276+ verify (watcherService ).reload (eq (events [0 ].state ()), anyString (), any ());
277+ // but it shouldn't on the second event unless routing table changes
278+ verify (watcherService , never ()).reload (eq (events [1 ].state ()), anyString (), any ());
279+ }
280+
281+ public void testReloadWithIdenticalRoutingTableAfterException () {
282+ /*
283+ * This tests that even the identical routing table causes reload again if some exception (for example a timeout while loading
284+ * watches) interrupted the previous one.
285+ */
286+ startWatcher ();
287+
288+ ClusterChangedEvent [] events = masterChangeScenario ();
289+ assertThat (events [1 ].previousState (), equalTo (events [0 ].state ()));
290+ assertFalse (events [1 ].routingTableChanged ());
291+
292+ // simulate exception on the first event
293+ doAnswer (invocation -> {
294+ Consumer <Exception > exceptionConsumer = invocation .getArgument (2 );
295+ exceptionConsumer .accept (new ElasticsearchTimeoutException (new TimeoutException ("Artificial timeout" )));
296+ return null ;
297+ }).when (watcherService ).reload (eq (events [0 ].state ()), anyString (), any ());
298+
299+ for (ClusterChangedEvent event : events ) {
300+ when (watcherService .validate (event .state ())).thenReturn (true );
301+ lifeCycleService .clusterChanged (event );
302+ }
303+ // reload should occur on the first event but it fails
304+ verify (watcherService ).reload (eq (events [0 ].state ()), anyString (), any ());
305+ // reload should occur again on the second event because the previous one failed
306+ verify (watcherService ).reload (eq (events [1 ].state ()), anyString (), any ());
307+ }
308+
309+ private ClusterChangedEvent [] masterChangeScenario () {
310+ DiscoveryNodes nodes = new DiscoveryNodes .Builder ().localNodeId ("node_1" ).add (newNode ("node_1" )).add (newNode ("node_2" )).build ();
311+
312+ Index index = new Index (Watch .INDEX , "uuid" );
313+ IndexRoutingTable .Builder indexRoutingTableBuilder = IndexRoutingTable .builder (index );
314+ indexRoutingTableBuilder .addShard (
315+ TestShardRouting .newShardRouting (new ShardId (index , 0 ), "node_1" , true , ShardRoutingState .STARTED )
316+ );
317+ RoutingTable routingTable = RoutingTable .builder ().add (indexRoutingTableBuilder .build ()).build ();
318+
319+ IndexMetadata .Builder indexMetadataBuilder = IndexMetadata .builder (Watch .INDEX )
320+ .settings (settings (IndexVersion .current ()).put (IndexMetadata .INDEX_FORMAT_SETTING .getKey (), 6 )) // the internal index format,
321+ // required
322+ .numberOfShards (1 )
323+ .numberOfReplicas (0 );
324+ Metadata metadata = Metadata .builder ()
325+ .put (IndexTemplateMetadata .builder (HISTORY_TEMPLATE_NAME ).patterns (randomIndexPatterns ()))
326+ .put (indexMetadataBuilder )
327+ .build ();
328+
329+ ClusterState emptyState = ClusterState .builder (new ClusterName ("my-cluster" )).nodes (nodes ).metadata (metadata ).build ();
330+ ClusterState stateWithMasterNode1 = ClusterState .builder (new ClusterName ("my-cluster" ))
331+ .nodes (nodes .withMasterNodeId ("node_1" ))
332+ .metadata (metadata )
333+ .routingTable (routingTable )
334+ .build ();
335+ ClusterState stateWithMasterNode2 = ClusterState .builder (new ClusterName ("my-cluster" ))
336+ .nodes (nodes .withMasterNodeId ("node_2" ))
337+ .metadata (metadata )
338+ .routingTable (routingTable )
339+ .build ();
340+
341+ return new ClusterChangedEvent [] {
342+ new ClusterChangedEvent ("any" , stateWithMasterNode1 , emptyState ),
343+ new ClusterChangedEvent ("any" , stateWithMasterNode2 , stateWithMasterNode1 ) };
344+ }
345+
261346 public void testNoLocalShards () {
262347 Index watchIndex = new Index (Watch .INDEX , "foo" );
263348 ShardId shardId = new ShardId (watchIndex , 0 );
@@ -301,7 +386,7 @@ public void testNoLocalShards() {
301386 when (watcherService .validate (eq (clusterStateWithLocalShards ))).thenReturn (true );
302387 when (watcherService .validate (eq (clusterStateWithoutLocalShards ))).thenReturn (false );
303388 lifeCycleService .clusterChanged (new ClusterChangedEvent ("any" , clusterStateWithLocalShards , clusterStateWithoutLocalShards ));
304- verify (watcherService , times (1 )).reload (eq (clusterStateWithLocalShards ), eq ("new local watcher shard allocation ids" ));
389+ verify (watcherService , times (1 )).reload (eq (clusterStateWithLocalShards ), eq ("new local watcher shard allocation ids" ), any () );
305390 verify (watcherService , times (1 )).validate (eq (clusterStateWithLocalShards ));
306391 verifyNoMoreInteractions (watcherService );
307392
@@ -380,12 +465,12 @@ public void testReplicaWasAddedOrRemoved() {
380465
381466 when (watcherService .validate (eq (firstEvent .state ()))).thenReturn (true );
382467 lifeCycleService .clusterChanged (firstEvent );
383- verify (watcherService ).reload (eq (firstEvent .state ()), anyString ());
468+ verify (watcherService ).reload (eq (firstEvent .state ()), anyString (), any () );
384469
385470 reset (watcherService );
386471 when (watcherService .validate (eq (secondEvent .state ()))).thenReturn (true );
387472 lifeCycleService .clusterChanged (secondEvent );
388- verify (watcherService ).reload (eq (secondEvent .state ()), anyString ());
473+ verify (watcherService ).reload (eq (secondEvent .state ()), anyString (), any () );
389474 }
390475
391476 // make sure that cluster state changes can be processed on nodes that do not hold data
@@ -425,7 +510,7 @@ public void testNonDataNode() {
425510
426511 lifeCycleService .clusterChanged (new ClusterChangedEvent ("any" , currentState , previousState ));
427512 verify (watcherService , times (0 )).pauseExecution (any ());
428- verify (watcherService , times (0 )).reload (any (), any ());
513+ verify (watcherService , times (0 )).reload (any (), any (), any () );
429514 }
430515
431516 public void testThatMissingWatcherIndexMetadataOnlyResetsOnce () {
@@ -452,7 +537,7 @@ public void testThatMissingWatcherIndexMetadataOnlyResetsOnce() {
452537
453538 // first add the shard allocation ids, by going from empty cs to CS with watcher index
454539 lifeCycleService .clusterChanged (new ClusterChangedEvent ("any" , clusterStateWithWatcherIndex , clusterStateWithoutWatcherIndex ));
455- verify (watcherService ).reload (eq (clusterStateWithWatcherIndex ), anyString ());
540+ verify (watcherService ).reload (eq (clusterStateWithWatcherIndex ), anyString (), any () );
456541
457542 // now remove watches index, and ensure that pausing is only called once, no matter how often called (i.e. each CS update)
458543 lifeCycleService .clusterChanged (new ClusterChangedEvent ("any" , clusterStateWithoutWatcherIndex , clusterStateWithWatcherIndex ));
@@ -577,7 +662,7 @@ public void testWatcherReloadsOnNodeOutageWithWatcherShard() {
577662 when (watcherService .validate (any ())).thenReturn (true );
578663 ClusterChangedEvent event = new ClusterChangedEvent ("whatever" , currentState , previousState );
579664 lifeCycleService .clusterChanged (event );
580- verify (watcherService ).reload (eq (event .state ()), anyString ());
665+ verify (watcherService ).reload (eq (event .state ()), anyString (), any () );
581666 }
582667
583668 private void startWatcher () {
@@ -609,7 +694,7 @@ private void startWatcher() {
609694
610695 lifeCycleService .clusterChanged (new ClusterChangedEvent ("foo" , state , emptyState ));
611696 assertThat (lifeCycleService .getState ().get (), is (WatcherState .STARTED ));
612- verify (watcherService , times (1 )).reload (eq (state ), anyString ());
697+ verify (watcherService , times (1 )).reload (eq (state ), anyString (), any () );
613698 assertThat (lifeCycleService .shardRoutings (), hasSize (1 ));
614699
615700 // reset the mock, the user has to mock everything themselves again
0 commit comments