|
21 | 21 | import org.apache.lucene.util.BytesRef; |
22 | 22 | import org.opensearch.Version; |
23 | 23 | import org.opensearch.action.delete.DeleteResponse; |
| 24 | +import org.opensearch.action.search.SearchResponse; |
24 | 25 | import org.opensearch.cluster.ClusterName; |
25 | 26 | import org.opensearch.cluster.ClusterState; |
26 | 27 | import org.opensearch.cluster.OpenSearchAllocationTestCase; |
|
38 | 39 | import org.opensearch.common.action.ActionFuture; |
39 | 40 | import org.opensearch.common.settings.ClusterSettings; |
40 | 41 | import org.opensearch.common.settings.Setting; |
| 42 | +import org.opensearch.common.unit.TimeValue; |
41 | 43 | import org.opensearch.common.settings.Settings; |
42 | 44 | import org.opensearch.core.xcontent.NamedXContentRegistry; |
43 | 45 | import org.opensearch.core.index.Index; |
44 | 46 | import org.opensearch.index.engine.Engine; |
45 | 47 | import org.opensearch.index.mapper.ParseContext; |
46 | 48 | import org.opensearch.index.mapper.ParsedDocument; |
47 | 49 | import org.opensearch.core.index.shard.ShardId; |
| 50 | +import org.opensearch.core.rest.RestStatus; |
| 51 | +import org.opensearch.search.SearchHit; |
| 52 | +import org.opensearch.search.SearchHits; |
48 | 53 | import org.opensearch.test.ClusterServiceUtils; |
49 | 54 | import org.opensearch.test.OpenSearchTestCase; |
50 | 55 | import org.opensearch.threadpool.Scheduler; |
@@ -275,6 +280,111 @@ public void testSweep() throws IOException { |
275 | 280 | ); |
276 | 281 | } |
277 | 282 |
|
| 283 | + public void testSweepUsesSeqNoSort() throws IOException { |
| 284 | + SearchHit hit = new SearchHit(1, "doc-id", null, null); |
| 285 | + hit.sourceRef(this.getTestJsonSource()); |
| 286 | + hit.setSeqNo(42L); |
| 287 | + hit.setPrimaryTerm(1L); |
| 288 | + SearchHits hits = new SearchHits(new SearchHit[] { hit }, null, 1.0f); |
| 289 | + |
| 290 | + SearchHits emptyHits = new SearchHits(new SearchHit[0], null, 1.0f); |
| 291 | + |
| 292 | + SearchResponse firstResponse = Mockito.mock(SearchResponse.class); |
| 293 | + Mockito.when(firstResponse.status()).thenReturn(RestStatus.OK); |
| 294 | + Mockito.when(firstResponse.getHits()).thenReturn(hits); |
| 295 | + |
| 296 | + SearchResponse secondResponse = Mockito.mock(SearchResponse.class); |
| 297 | + Mockito.when(secondResponse.status()).thenReturn(RestStatus.OK); |
| 298 | + Mockito.when(secondResponse.getHits()).thenReturn(emptyHits); |
| 299 | + |
| 300 | + ActionFuture<SearchResponse> firstFuture = Mockito.mock(ActionFuture.class); |
| 301 | + Mockito.when(firstFuture.actionGet(Mockito.any(TimeValue.class))).thenReturn(firstResponse); |
| 302 | + |
| 303 | + ActionFuture<SearchResponse> secondFuture = Mockito.mock(ActionFuture.class); |
| 304 | + Mockito.when(secondFuture.actionGet(Mockito.any(TimeValue.class))).thenReturn(secondResponse); |
| 305 | + |
| 306 | + Mockito.when(this.client.search(Mockito.any())).thenReturn(firstFuture).thenReturn(secondFuture); |
| 307 | + |
| 308 | + JobSweeper testSweeper = Mockito.spy(this.sweeper); |
| 309 | + Mockito.doNothing() |
| 310 | + .when(testSweeper) |
| 311 | + .sweep(Mockito.any(), Mockito.anyString(), Mockito.any(BytesReference.class), Mockito.any(JobDocVersion.class)); |
| 312 | + |
| 313 | + ClusterState clusterState = buildSingleShardClusterState("index-name"); |
| 314 | + Mockito.when(this.clusterService.state()).thenReturn(clusterState); |
| 315 | + |
| 316 | + testSweeper.sweepIndex("index-name"); |
| 317 | + |
| 318 | + // verify search was called twice: once for the page with the hit, once for the empty page |
| 319 | + Mockito.verify(this.client, Mockito.times(2)).search(Mockito.any()); |
| 320 | + } |
| 321 | + |
| 322 | + public void testSweepAbortsOnNonOkResponse() { |
| 323 | + SearchResponse badResponse = Mockito.mock(SearchResponse.class); |
| 324 | + Mockito.when(badResponse.status()).thenReturn(RestStatus.INTERNAL_SERVER_ERROR); |
| 325 | + |
| 326 | + ActionFuture<SearchResponse> future = Mockito.mock(ActionFuture.class); |
| 327 | + Mockito.when(future.actionGet(Mockito.any(TimeValue.class))).thenReturn(badResponse); |
| 328 | + Mockito.when(this.client.search(Mockito.any())).thenReturn(future); |
| 329 | + |
| 330 | + JobSweeper testSweeper = Mockito.spy(this.sweeper); |
| 331 | + Mockito.doNothing() |
| 332 | + .when(testSweeper) |
| 333 | + .sweep(Mockito.any(), Mockito.anyString(), Mockito.any(BytesReference.class), Mockito.any(JobDocVersion.class)); |
| 334 | + |
| 335 | + ClusterState clusterState = buildSingleShardClusterState("index-name"); |
| 336 | + Mockito.when(this.clusterService.state()).thenReturn(clusterState); |
| 337 | + |
| 338 | + testSweeper.sweepIndex("index-name"); |
| 339 | + |
| 340 | + // search was called once, but sweep was never called due to non-OK status |
| 341 | + Mockito.verify(this.client, Mockito.times(1)).search(Mockito.any()); |
| 342 | + Mockito.verify(testSweeper, Mockito.times(0)) |
| 343 | + .sweep(Mockito.any(), Mockito.anyString(), Mockito.any(BytesReference.class), Mockito.any(JobDocVersion.class)); |
| 344 | + } |
| 345 | + |
| 346 | + public void testSweepAbortsOnSearchException() { |
| 347 | + ActionFuture<SearchResponse> failingFuture = Mockito.mock(ActionFuture.class); |
| 348 | + Mockito.when(failingFuture.actionGet(Mockito.any(TimeValue.class))) |
| 349 | + .thenThrow(new RuntimeException("fielddata access on _id disallowed")); |
| 350 | + Mockito.when(this.client.search(Mockito.any())).thenReturn(failingFuture); |
| 351 | + |
| 352 | + JobSweeper testSweeper = Mockito.spy(this.sweeper); |
| 353 | + Mockito.doNothing() |
| 354 | + .when(testSweeper) |
| 355 | + .sweep(Mockito.any(), Mockito.anyString(), Mockito.any(BytesReference.class), Mockito.any(JobDocVersion.class)); |
| 356 | + |
| 357 | + ClusterState clusterState = buildSingleShardClusterState("index-name"); |
| 358 | + Mockito.when(this.clusterService.state()).thenReturn(clusterState); |
| 359 | + |
| 360 | + // should not throw — exception is caught and logged |
| 361 | + testSweeper.sweepIndex("index-name"); |
| 362 | + |
| 363 | + // search was attempted once before the exception aborted the loop |
| 364 | + Mockito.verify(this.client, Mockito.times(1)).search(Mockito.any()); |
| 365 | + Mockito.verify(testSweeper, Mockito.times(0)) |
| 366 | + .sweep(Mockito.any(), Mockito.anyString(), Mockito.any(BytesReference.class), Mockito.any(JobDocVersion.class)); |
| 367 | + } |
| 368 | + |
| 369 | + private ClusterState buildSingleShardClusterState(String indexName) { |
| 370 | + Metadata metadata = Metadata.builder().put(createIndexMetadata(indexName, 0, 1)).build(); |
| 371 | + RoutingTable routingTable = new RoutingTable.Builder().add( |
| 372 | + new IndexRoutingTable.Builder(metadata.index(indexName).getIndex()).initializeAsNew(metadata.index(indexName)).build() |
| 373 | + ).build(); |
| 374 | + ClusterState clusterState = ClusterState.builder(new ClusterName("cluster-name")) |
| 375 | + .metadata(metadata) |
| 376 | + .routingTable(routingTable) |
| 377 | + .build(); |
| 378 | + clusterState = this.addNodesToCluter(clusterState, 1); |
| 379 | + clusterState = this.initializeAllShards(clusterState); |
| 380 | + // set local node so getLocalShards can match shards assigned to this node |
| 381 | + String firstNodeId = clusterState.getNodes().iterator().next().getId(); |
| 382 | + clusterState = ClusterState.builder(clusterState) |
| 383 | + .nodes(DiscoveryNodes.builder(clusterState.getNodes()).localNodeId(firstNodeId)) |
| 384 | + .build(); |
| 385 | + return clusterState; |
| 386 | + } |
| 387 | + |
278 | 388 | private ClusterState addNodesToCluter(ClusterState clusterState, int nodeCount) { |
279 | 389 | DiscoveryNodes.Builder nodeBuilder = DiscoveryNodes.builder(); |
280 | 390 | for (int i = 1; i <= nodeCount; i++) { |
|
0 commit comments