|
@@ -221,7 +221,8 @@ public class TransformIndexerFailureHandlingTests extends ESTestCase {
|
|
|
|
|
|
@Override
|
|
|
void doGetInitialProgress(SearchRequest request, ActionListener<SearchResponse> responseListener) {
|
|
|
- responseListener.onResponse(
|
|
|
+ ActionListener.respondAndRelease(
|
|
|
+ responseListener,
|
|
|
new SearchResponse(
|
|
|
new SearchHits(new SearchHit[0], new TotalHits(0L, TotalHits.Relation.EQUAL_TO), 0.0f),
|
|
|
// Simulate completely null aggs
|
|
@@ -388,29 +389,33 @@ public class TransformIndexerFailureHandlingTests extends ESTestCase {
|
|
|
ShardSearchFailure.EMPTY_ARRAY,
|
|
|
SearchResponse.Clusters.EMPTY
|
|
|
);
|
|
|
- AtomicReference<IndexerState> state = new AtomicReference<>(IndexerState.STOPPED);
|
|
|
- Function<SearchRequest, SearchResponse> searchFunction = searchRequest -> searchResponse;
|
|
|
- Function<BulkRequest, BulkResponse> bulkFunction = bulkRequest -> new BulkResponse(new BulkItemResponse[0], 100);
|
|
|
-
|
|
|
- TransformAuditor auditor = mock(TransformAuditor.class);
|
|
|
- TransformContext context = new TransformContext(TransformTaskState.STARTED, "", 0, mock(TransformContext.Listener.class));
|
|
|
-
|
|
|
- MockedTransformIndexer indexer = createMockIndexer(
|
|
|
- config,
|
|
|
- state,
|
|
|
- searchFunction,
|
|
|
- bulkFunction,
|
|
|
- null,
|
|
|
- threadPool,
|
|
|
- ThreadPool.Names.GENERIC,
|
|
|
- auditor,
|
|
|
- context
|
|
|
- );
|
|
|
+ try {
|
|
|
+ AtomicReference<IndexerState> state = new AtomicReference<>(IndexerState.STOPPED);
|
|
|
+ Function<SearchRequest, SearchResponse> searchFunction = searchRequest -> searchResponse;
|
|
|
+ Function<BulkRequest, BulkResponse> bulkFunction = bulkRequest -> new BulkResponse(new BulkItemResponse[0], 100);
|
|
|
+
|
|
|
+ TransformAuditor auditor = mock(TransformAuditor.class);
|
|
|
+ TransformContext context = new TransformContext(TransformTaskState.STARTED, "", 0, mock(TransformContext.Listener.class));
|
|
|
+
|
|
|
+ MockedTransformIndexer indexer = createMockIndexer(
|
|
|
+ config,
|
|
|
+ state,
|
|
|
+ searchFunction,
|
|
|
+ bulkFunction,
|
|
|
+ null,
|
|
|
+ threadPool,
|
|
|
+ ThreadPool.Names.GENERIC,
|
|
|
+ auditor,
|
|
|
+ context
|
|
|
+ );
|
|
|
|
|
|
- IterationResult<TransformIndexerPosition> newPosition = indexer.doProcess(searchResponse);
|
|
|
- assertThat(newPosition.getToIndex().collect(Collectors.toList()), is(empty()));
|
|
|
- assertThat(newPosition.getPosition(), is(nullValue()));
|
|
|
- assertThat(newPosition.isDone(), is(true));
|
|
|
+ IterationResult<TransformIndexerPosition> newPosition = indexer.doProcess(searchResponse);
|
|
|
+ assertThat(newPosition.getToIndex().collect(Collectors.toList()), is(empty()));
|
|
|
+ assertThat(newPosition.getPosition(), is(nullValue()));
|
|
|
+ assertThat(newPosition.isDone(), is(true));
|
|
|
+ } finally {
|
|
|
+ searchResponse.decRef();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public void testScriptError() throws Exception {
|
|
@@ -524,58 +529,61 @@ public class TransformIndexerFailureHandlingTests extends ESTestCase {
|
|
|
ShardSearchFailure.EMPTY_ARRAY,
|
|
|
SearchResponse.Clusters.EMPTY
|
|
|
);
|
|
|
-
|
|
|
- AtomicReference<IndexerState> state = new AtomicReference<>(IndexerState.STOPPED);
|
|
|
- Function<SearchRequest, SearchResponse> searchFunction = searchRequest -> searchResponse;
|
|
|
-
|
|
|
- Function<BulkRequest, BulkResponse> bulkFunction = bulkRequest -> new BulkResponse(new BulkItemResponse[0], 100);
|
|
|
-
|
|
|
- Function<DeleteByQueryRequest, BulkByScrollResponse> deleteByQueryFunction = deleteByQueryRequest -> {
|
|
|
- throw new SearchPhaseExecutionException(
|
|
|
- "query",
|
|
|
- "Partial shards failure",
|
|
|
- new ShardSearchFailure[] {
|
|
|
- new ShardSearchFailure(
|
|
|
- new ElasticsearchParseException("failed to parse date field", new IllegalArgumentException("illegal format"))
|
|
|
- ) }
|
|
|
+ try {
|
|
|
+ AtomicReference<IndexerState> state = new AtomicReference<>(IndexerState.STOPPED);
|
|
|
+ Function<SearchRequest, SearchResponse> searchFunction = searchRequest -> searchResponse;
|
|
|
+
|
|
|
+ Function<BulkRequest, BulkResponse> bulkFunction = bulkRequest -> new BulkResponse(new BulkItemResponse[0], 100);
|
|
|
+
|
|
|
+ Function<DeleteByQueryRequest, BulkByScrollResponse> deleteByQueryFunction = deleteByQueryRequest -> {
|
|
|
+ throw new SearchPhaseExecutionException(
|
|
|
+ "query",
|
|
|
+ "Partial shards failure",
|
|
|
+ new ShardSearchFailure[] {
|
|
|
+ new ShardSearchFailure(
|
|
|
+ new ElasticsearchParseException("failed to parse date field", new IllegalArgumentException("illegal format"))
|
|
|
+ ) }
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ final AtomicBoolean failIndexerCalled = new AtomicBoolean(false);
|
|
|
+ final AtomicReference<String> failureMessage = new AtomicReference<>();
|
|
|
+
|
|
|
+ MockTransformAuditor auditor = MockTransformAuditor.createMockAuditor();
|
|
|
+ TransformContext.Listener contextListener = createContextListener(failIndexerCalled, failureMessage);
|
|
|
+ TransformContext context = new TransformContext(TransformTaskState.STARTED, "", 0, contextListener);
|
|
|
+
|
|
|
+ MockedTransformIndexer indexer = createMockIndexer(
|
|
|
+ config,
|
|
|
+ state,
|
|
|
+ searchFunction,
|
|
|
+ bulkFunction,
|
|
|
+ deleteByQueryFunction,
|
|
|
+ threadPool,
|
|
|
+ ThreadPool.Names.GENERIC,
|
|
|
+ auditor,
|
|
|
+ context
|
|
|
);
|
|
|
- };
|
|
|
|
|
|
- final AtomicBoolean failIndexerCalled = new AtomicBoolean(false);
|
|
|
- final AtomicReference<String> failureMessage = new AtomicReference<>();
|
|
|
+ final CountDownLatch latch = indexer.newLatch(1);
|
|
|
|
|
|
- MockTransformAuditor auditor = MockTransformAuditor.createMockAuditor();
|
|
|
- TransformContext.Listener contextListener = createContextListener(failIndexerCalled, failureMessage);
|
|
|
- TransformContext context = new TransformContext(TransformTaskState.STARTED, "", 0, contextListener);
|
|
|
+ indexer.start();
|
|
|
+ assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
+ assertTrue(indexer.maybeTriggerAsyncJob(System.currentTimeMillis()));
|
|
|
+ assertThat(indexer.getState(), equalTo(IndexerState.INDEXING));
|
|
|
|
|
|
- MockedTransformIndexer indexer = createMockIndexer(
|
|
|
- config,
|
|
|
- state,
|
|
|
- searchFunction,
|
|
|
- bulkFunction,
|
|
|
- deleteByQueryFunction,
|
|
|
- threadPool,
|
|
|
- ThreadPool.Names.GENERIC,
|
|
|
- auditor,
|
|
|
- context
|
|
|
- );
|
|
|
-
|
|
|
- final CountDownLatch latch = indexer.newLatch(1);
|
|
|
-
|
|
|
- indexer.start();
|
|
|
- assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
- assertTrue(indexer.maybeTriggerAsyncJob(System.currentTimeMillis()));
|
|
|
- assertThat(indexer.getState(), equalTo(IndexerState.INDEXING));
|
|
|
-
|
|
|
- latch.countDown();
|
|
|
- assertBusy(() -> assertThat(indexer.getState(), equalTo(IndexerState.STARTED)), 10, TimeUnit.SECONDS);
|
|
|
- assertTrue(failIndexerCalled.get());
|
|
|
- assertThat(
|
|
|
- failureMessage.get(),
|
|
|
- matchesRegex(
|
|
|
- "task encountered irrecoverable failure: org.elasticsearch.ElasticsearchParseException: failed to parse date field;.*"
|
|
|
- )
|
|
|
- );
|
|
|
+ latch.countDown();
|
|
|
+ assertBusy(() -> assertThat(indexer.getState(), equalTo(IndexerState.STARTED)), 10, TimeUnit.SECONDS);
|
|
|
+ assertTrue(failIndexerCalled.get());
|
|
|
+ assertThat(
|
|
|
+ failureMessage.get(),
|
|
|
+ matchesRegex(
|
|
|
+ "task encountered irrecoverable failure: org.elasticsearch.ElasticsearchParseException: failed to parse date field;.*"
|
|
|
+ )
|
|
|
+ );
|
|
|
+ } finally {
|
|
|
+ searchResponse.decRef();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public void testRetentionPolicyDeleteByQueryThrowsTemporaryProblem() throws Exception {
|
|
@@ -614,61 +622,64 @@ public class TransformIndexerFailureHandlingTests extends ESTestCase {
|
|
|
ShardSearchFailure.EMPTY_ARRAY,
|
|
|
SearchResponse.Clusters.EMPTY
|
|
|
);
|
|
|
-
|
|
|
- AtomicReference<IndexerState> state = new AtomicReference<>(IndexerState.STOPPED);
|
|
|
- Function<SearchRequest, SearchResponse> searchFunction = searchRequest -> searchResponse;
|
|
|
-
|
|
|
- Function<BulkRequest, BulkResponse> bulkFunction = bulkRequest -> new BulkResponse(new BulkItemResponse[0], 100);
|
|
|
-
|
|
|
- Function<DeleteByQueryRequest, BulkByScrollResponse> deleteByQueryFunction = deleteByQueryRequest -> {
|
|
|
- throw new SearchPhaseExecutionException(
|
|
|
- "query",
|
|
|
- "Partial shards failure",
|
|
|
- new ShardSearchFailure[] { new ShardSearchFailure(new ElasticsearchTimeoutException("timed out during dbq")) }
|
|
|
+ try {
|
|
|
+ AtomicReference<IndexerState> state = new AtomicReference<>(IndexerState.STOPPED);
|
|
|
+ Function<SearchRequest, SearchResponse> searchFunction = searchRequest -> searchResponse;
|
|
|
+
|
|
|
+ Function<BulkRequest, BulkResponse> bulkFunction = bulkRequest -> new BulkResponse(new BulkItemResponse[0], 100);
|
|
|
+
|
|
|
+ Function<DeleteByQueryRequest, BulkByScrollResponse> deleteByQueryFunction = deleteByQueryRequest -> {
|
|
|
+ throw new SearchPhaseExecutionException(
|
|
|
+ "query",
|
|
|
+ "Partial shards failure",
|
|
|
+ new ShardSearchFailure[] { new ShardSearchFailure(new ElasticsearchTimeoutException("timed out during dbq")) }
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ final AtomicBoolean failIndexerCalled = new AtomicBoolean(false);
|
|
|
+ final AtomicReference<String> failureMessage = new AtomicReference<>();
|
|
|
+
|
|
|
+ MockTransformAuditor auditor = MockTransformAuditor.createMockAuditor();
|
|
|
+ auditor.addExpectation(
|
|
|
+ new MockTransformAuditor.SeenAuditExpectation(
|
|
|
+ "timed out during dbq",
|
|
|
+ Level.WARNING,
|
|
|
+ transformId,
|
|
|
+ "Transform encountered an exception: [org.elasticsearch.ElasticsearchTimeoutException: timed out during dbq];"
|
|
|
+ + " Will automatically retry [1/10]"
|
|
|
+ )
|
|
|
+ );
|
|
|
+ TransformContext.Listener contextListener = createContextListener(failIndexerCalled, failureMessage);
|
|
|
+ TransformContext context = new TransformContext(TransformTaskState.STARTED, "", 0, contextListener);
|
|
|
+
|
|
|
+ MockedTransformIndexer indexer = createMockIndexer(
|
|
|
+ config,
|
|
|
+ state,
|
|
|
+ searchFunction,
|
|
|
+ bulkFunction,
|
|
|
+ deleteByQueryFunction,
|
|
|
+ threadPool,
|
|
|
+ ThreadPool.Names.GENERIC,
|
|
|
+ auditor,
|
|
|
+ context
|
|
|
);
|
|
|
- };
|
|
|
-
|
|
|
- final AtomicBoolean failIndexerCalled = new AtomicBoolean(false);
|
|
|
- final AtomicReference<String> failureMessage = new AtomicReference<>();
|
|
|
-
|
|
|
- MockTransformAuditor auditor = MockTransformAuditor.createMockAuditor();
|
|
|
- auditor.addExpectation(
|
|
|
- new MockTransformAuditor.SeenAuditExpectation(
|
|
|
- "timed out during dbq",
|
|
|
- Level.WARNING,
|
|
|
- transformId,
|
|
|
- "Transform encountered an exception: [org.elasticsearch.ElasticsearchTimeoutException: timed out during dbq];"
|
|
|
- + " Will automatically retry [1/10]"
|
|
|
- )
|
|
|
- );
|
|
|
- TransformContext.Listener contextListener = createContextListener(failIndexerCalled, failureMessage);
|
|
|
- TransformContext context = new TransformContext(TransformTaskState.STARTED, "", 0, contextListener);
|
|
|
-
|
|
|
- MockedTransformIndexer indexer = createMockIndexer(
|
|
|
- config,
|
|
|
- state,
|
|
|
- searchFunction,
|
|
|
- bulkFunction,
|
|
|
- deleteByQueryFunction,
|
|
|
- threadPool,
|
|
|
- ThreadPool.Names.GENERIC,
|
|
|
- auditor,
|
|
|
- context
|
|
|
- );
|
|
|
|
|
|
- final CountDownLatch latch = indexer.newLatch(1);
|
|
|
+ final CountDownLatch latch = indexer.newLatch(1);
|
|
|
|
|
|
- indexer.start();
|
|
|
- assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
- assertTrue(indexer.maybeTriggerAsyncJob(System.currentTimeMillis()));
|
|
|
- assertThat(indexer.getState(), equalTo(IndexerState.INDEXING));
|
|
|
+ indexer.start();
|
|
|
+ assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
+ assertTrue(indexer.maybeTriggerAsyncJob(System.currentTimeMillis()));
|
|
|
+ assertThat(indexer.getState(), equalTo(IndexerState.INDEXING));
|
|
|
|
|
|
- latch.countDown();
|
|
|
- assertBusy(() -> assertThat(indexer.getState(), equalTo(IndexerState.STARTED)), 10, TimeUnit.SECONDS);
|
|
|
- assertFalse(failIndexerCalled.get());
|
|
|
- assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
- auditor.assertAllExpectationsMatched();
|
|
|
- assertEquals(1, context.getFailureCount());
|
|
|
+ latch.countDown();
|
|
|
+ assertBusy(() -> assertThat(indexer.getState(), equalTo(IndexerState.STARTED)), 10, TimeUnit.SECONDS);
|
|
|
+ assertFalse(failIndexerCalled.get());
|
|
|
+ assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
+ auditor.assertAllExpectationsMatched();
|
|
|
+ assertEquals(1, context.getFailureCount());
|
|
|
+ } finally {
|
|
|
+ searchResponse.decRef();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
public void testFailureCounterIsResetOnSuccess() throws Exception {
|
|
@@ -707,72 +718,75 @@ public class TransformIndexerFailureHandlingTests extends ESTestCase {
|
|
|
ShardSearchFailure.EMPTY_ARRAY,
|
|
|
SearchResponse.Clusters.EMPTY
|
|
|
);
|
|
|
-
|
|
|
- AtomicReference<IndexerState> state = new AtomicReference<>(IndexerState.STOPPED);
|
|
|
- Function<SearchRequest, SearchResponse> searchFunction = new Function<>() {
|
|
|
- final AtomicInteger calls = new AtomicInteger(0);
|
|
|
-
|
|
|
- @Override
|
|
|
- public SearchResponse apply(SearchRequest searchRequest) {
|
|
|
- int call = calls.getAndIncrement();
|
|
|
- if (call == 0) {
|
|
|
- throw new SearchPhaseExecutionException(
|
|
|
- "query",
|
|
|
- "Partial shards failure",
|
|
|
- new ShardSearchFailure[] { new ShardSearchFailure(new Exception()) }
|
|
|
- );
|
|
|
+ try {
|
|
|
+ AtomicReference<IndexerState> state = new AtomicReference<>(IndexerState.STOPPED);
|
|
|
+ Function<SearchRequest, SearchResponse> searchFunction = new Function<>() {
|
|
|
+ final AtomicInteger calls = new AtomicInteger(0);
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SearchResponse apply(SearchRequest searchRequest) {
|
|
|
+ int call = calls.getAndIncrement();
|
|
|
+ if (call == 0) {
|
|
|
+ throw new SearchPhaseExecutionException(
|
|
|
+ "query",
|
|
|
+ "Partial shards failure",
|
|
|
+ new ShardSearchFailure[] { new ShardSearchFailure(new Exception()) }
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return searchResponse;
|
|
|
}
|
|
|
- return searchResponse;
|
|
|
- }
|
|
|
- };
|
|
|
+ };
|
|
|
|
|
|
- Function<BulkRequest, BulkResponse> bulkFunction = request -> new BulkResponse(new BulkItemResponse[0], 1);
|
|
|
+ Function<BulkRequest, BulkResponse> bulkFunction = request -> new BulkResponse(new BulkItemResponse[0], 1);
|
|
|
|
|
|
- final AtomicBoolean failIndexerCalled = new AtomicBoolean(false);
|
|
|
- final AtomicReference<String> failureMessage = new AtomicReference<>();
|
|
|
+ final AtomicBoolean failIndexerCalled = new AtomicBoolean(false);
|
|
|
+ final AtomicReference<String> failureMessage = new AtomicReference<>();
|
|
|
|
|
|
- MockTransformAuditor auditor = MockTransformAuditor.createMockAuditor();
|
|
|
- TransformContext.Listener contextListener = createContextListener(failIndexerCalled, failureMessage);
|
|
|
- TransformContext context = new TransformContext(TransformTaskState.STARTED, "", 0, contextListener);
|
|
|
+ MockTransformAuditor auditor = MockTransformAuditor.createMockAuditor();
|
|
|
+ TransformContext.Listener contextListener = createContextListener(failIndexerCalled, failureMessage);
|
|
|
+ TransformContext context = new TransformContext(TransformTaskState.STARTED, "", 0, contextListener);
|
|
|
|
|
|
- MockedTransformIndexer indexer = createMockIndexer(
|
|
|
- config,
|
|
|
- state,
|
|
|
- searchFunction,
|
|
|
- bulkFunction,
|
|
|
- null,
|
|
|
- threadPool,
|
|
|
- ThreadPool.Names.GENERIC,
|
|
|
- auditor,
|
|
|
- context
|
|
|
- );
|
|
|
+ MockedTransformIndexer indexer = createMockIndexer(
|
|
|
+ config,
|
|
|
+ state,
|
|
|
+ searchFunction,
|
|
|
+ bulkFunction,
|
|
|
+ null,
|
|
|
+ threadPool,
|
|
|
+ ThreadPool.Names.GENERIC,
|
|
|
+ auditor,
|
|
|
+ context
|
|
|
+ );
|
|
|
|
|
|
- final CountDownLatch latch = indexer.newLatch(1);
|
|
|
+ final CountDownLatch latch = indexer.newLatch(1);
|
|
|
|
|
|
- indexer.start();
|
|
|
- assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
- assertTrue(indexer.maybeTriggerAsyncJob(System.currentTimeMillis()));
|
|
|
- assertThat(indexer.getState(), equalTo(IndexerState.INDEXING));
|
|
|
+ indexer.start();
|
|
|
+ assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
+ assertTrue(indexer.maybeTriggerAsyncJob(System.currentTimeMillis()));
|
|
|
+ assertThat(indexer.getState(), equalTo(IndexerState.INDEXING));
|
|
|
|
|
|
- latch.countDown();
|
|
|
- assertBusy(() -> assertThat(indexer.getState(), equalTo(IndexerState.STARTED)), 10, TimeUnit.SECONDS);
|
|
|
- assertFalse(failIndexerCalled.get());
|
|
|
- assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
- assertEquals(1, context.getFailureCount());
|
|
|
+ latch.countDown();
|
|
|
+ assertBusy(() -> assertThat(indexer.getState(), equalTo(IndexerState.STARTED)), 10, TimeUnit.SECONDS);
|
|
|
+ assertFalse(failIndexerCalled.get());
|
|
|
+ assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
+ assertEquals(1, context.getFailureCount());
|
|
|
|
|
|
- final CountDownLatch secondLatch = indexer.newLatch(1);
|
|
|
+ final CountDownLatch secondLatch = indexer.newLatch(1);
|
|
|
|
|
|
- indexer.start();
|
|
|
- assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
- assertBusy(() -> assertTrue(indexer.maybeTriggerAsyncJob(System.currentTimeMillis())));
|
|
|
- assertThat(indexer.getState(), equalTo(IndexerState.INDEXING));
|
|
|
+ indexer.start();
|
|
|
+ assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
+ assertBusy(() -> assertTrue(indexer.maybeTriggerAsyncJob(System.currentTimeMillis())));
|
|
|
+ assertThat(indexer.getState(), equalTo(IndexerState.INDEXING));
|
|
|
|
|
|
- secondLatch.countDown();
|
|
|
- assertBusy(() -> assertThat(indexer.getState(), equalTo(IndexerState.STARTED)), 10, TimeUnit.SECONDS);
|
|
|
- assertFalse(failIndexerCalled.get());
|
|
|
- assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
- auditor.assertAllExpectationsMatched();
|
|
|
- assertEquals(0, context.getFailureCount());
|
|
|
+ secondLatch.countDown();
|
|
|
+ assertBusy(() -> assertThat(indexer.getState(), equalTo(IndexerState.STARTED)), 10, TimeUnit.SECONDS);
|
|
|
+ assertFalse(failIndexerCalled.get());
|
|
|
+ assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
|
|
|
+ auditor.assertAllExpectationsMatched();
|
|
|
+ assertEquals(0, context.getFailureCount());
|
|
|
+ } finally {
|
|
|
+ searchResponse.decRef();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// tests throttling of audits on logs based on repeated exception types
|