|
47 | 47 | import org.opensearch.dataprepper.model.plugin.PluginConfigObservable; |
48 | 48 | import org.opensearch.dataprepper.model.record.Record; |
49 | 49 | import org.opensearch.dataprepper.model.sink.SinkContext; |
| 50 | +import org.opensearch.dataprepper.plugins.sink.opensearch.BulkRetryStrategy; |
50 | 51 | import org.opensearch.dataprepper.plugins.sink.opensearch.configuration.OpenSearchSinkConfig; |
| 52 | +import org.opensearch.dataprepper.plugins.sink.opensearch.ConnectionConfiguration; |
| 53 | +import org.opensearch.dataprepper.plugins.sink.opensearch.DistributionVersion; |
51 | 54 | import org.opensearch.dataprepper.plugins.sink.opensearch.index.AbstractIndexManager; |
52 | 55 | import org.opensearch.dataprepper.plugins.sink.opensearch.index.IndexConfiguration; |
53 | 56 | import org.opensearch.dataprepper.plugins.sink.opensearch.index.IndexConstants; |
54 | 57 | import org.opensearch.dataprepper.plugins.sink.opensearch.index.IndexType; |
| 58 | +import org.opensearch.dataprepper.plugins.sink.opensearch.RetryConfiguration; |
55 | 59 |
|
56 | 60 | import javax.ws.rs.HttpMethod; |
57 | 61 | import java.io.BufferedReader; |
@@ -1637,6 +1641,131 @@ public void testOpenSearchIndexWithInvalidChars() throws IOException, Interrupte |
1637 | 1641 | Assert.assertThrows(RuntimeException.class, () -> sink.doInitialize()); |
1638 | 1642 | } |
1639 | 1643 |
|
| 1644 | + @Test |
| 1645 | + @DisabledIf(value = "isDataStreamNotSupported", disabledReason = "Data streams require OpenSearch 1.3.0+") |
| 1646 | + public void testDataStreamDetection() throws IOException, InterruptedException { |
| 1647 | + final String dataStreamName = "test-data-stream-" + UUID.randomUUID(); |
| 1648 | + final String templateName = dataStreamName + "-template"; |
| 1649 | + final File tempDirectory = Files.createTempDirectory("").toFile(); |
| 1650 | + final String dlqFile = tempDirectory.getAbsolutePath() + "/test-dlq.txt"; |
| 1651 | + |
| 1652 | + try { |
| 1653 | + // Create an index template for the data stream first |
| 1654 | + final Request createTemplateRequest = new Request(HttpMethod.PUT, "/_index_template/" + templateName); |
| 1655 | + final String templateBody = "{" + |
| 1656 | + "\"index_patterns\": [\"" + dataStreamName + "\"]," + |
| 1657 | + "\"data_stream\": {}," + |
| 1658 | + "\"template\": {" + |
| 1659 | + "\"mappings\": {" + |
| 1660 | + "\"properties\": {" + |
| 1661 | + "\"@timestamp\": {\"type\": \"date\"}" + |
| 1662 | + "}" + |
| 1663 | + "}" + |
| 1664 | + "}" + |
| 1665 | + "}"; |
| 1666 | + createTemplateRequest.setJsonEntity(templateBody); |
| 1667 | + client.performRequest(createTemplateRequest); |
| 1668 | + |
| 1669 | + // Create a data stream |
| 1670 | + final Request createDataStreamRequest = new Request(HttpMethod.PUT, "/_data_stream/" + dataStreamName); |
| 1671 | + client.performRequest(createDataStreamRequest); |
| 1672 | + |
| 1673 | + // Initialize sink AFTER creating the data stream so detection works |
| 1674 | + Map<String, Object> metadata = initializeConfigurationMetadata(null, dataStreamName, null); |
| 1675 | + metadata.put(RetryConfiguration.DLQ_FILE, dlqFile); |
| 1676 | + final OpenSearchSinkConfig openSearchSinkConfig = generateOpenSearchSinkConfigByMetadata(metadata); |
| 1677 | + final OpenSearchSink sink = createObjectUnderTest(openSearchSinkConfig, true); |
| 1678 | + |
| 1679 | + // Clear the index cache to force re-detection of data stream |
| 1680 | + sink.indexCache.clearAll(); |
| 1681 | + System.out.println("DEBUG: Index cache cleared after initialization"); |
| 1682 | + |
| 1683 | + // Test that the data stream is detected |
| 1684 | + final String testIdField = "someId"; |
| 1685 | + final String testId = "foo"; |
| 1686 | + final List<Record<Event>> testRecords = Collections.singletonList(jsonStringToRecord(generateCustomRecordJson(testIdField, testId))); |
| 1687 | + |
| 1688 | + System.out.println("DEBUG: Outputting records to sink..."); |
| 1689 | + System.out.println("DEBUG: Expected index name: " + dataStreamName); |
| 1690 | + |
| 1691 | + // Verify data stream is detected before writing |
| 1692 | + Request verifyRequest = new Request(HttpMethod.GET, "/_data_stream/" + dataStreamName); |
| 1693 | + Response verifyResponse = client.performRequest(verifyRequest); |
| 1694 | + System.out.println("DEBUG: Data stream exists before write: " + (verifyResponse.getStatusLine().getStatusCode() == 200)); |
| 1695 | + |
| 1696 | + sink.output(testRecords); |
| 1697 | + System.out.println("DEBUG: Records output complete, shutting down sink..."); |
| 1698 | + sink.shutdown(); |
| 1699 | + System.out.println("DEBUG: Sink shutdown complete"); |
| 1700 | + |
| 1701 | + // Wait for indexing to complete |
| 1702 | + Thread.sleep(2000); |
| 1703 | + System.out.println("DEBUG: Wait complete, checking for documents..."); |
| 1704 | + |
| 1705 | + // Verify the document was written to the data stream |
| 1706 | + System.out.println("DEBUG: Data stream name: " + dataStreamName); |
| 1707 | + System.out.println("DEBUG: Test record: " + testRecords.get(0).getData()); |
| 1708 | + |
| 1709 | + // Check if data stream exists |
| 1710 | + Request checkRequest = new Request(HttpMethod.GET, "/_data_stream/" + dataStreamName); |
| 1711 | + Response checkResponse = client.performRequest(checkRequest); |
| 1712 | + System.out.println("DEBUG: Data stream exists: " + checkResponse.getStatusLine().getStatusCode()); |
| 1713 | + System.out.println("DEBUG: Data stream info: " + EntityUtils.toString(checkResponse.getEntity())); |
| 1714 | + |
| 1715 | + // Check metrics for errors |
| 1716 | + final List<Measurement> documentErrors = MetricsTestUtil.getMeasurementList( |
| 1717 | + new StringJoiner(MetricNames.DELIMITER).add(PIPELINE_NAME).add(PLUGIN_NAME) |
| 1718 | + .add(BulkRetryStrategy.DOCUMENT_ERRORS).toString()); |
| 1719 | + System.out.println("DEBUG: Document errors: " + (documentErrors.isEmpty() ? "none" : documentErrors.get(0).getValue())); |
| 1720 | + |
| 1721 | + final List<Measurement> documentsSuccess = MetricsTestUtil.getMeasurementList( |
| 1722 | + new StringJoiner(MetricNames.DELIMITER).add(PIPELINE_NAME).add(PLUGIN_NAME) |
| 1723 | + .add(BulkRetryStrategy.DOCUMENTS_SUCCESS).toString()); |
| 1724 | + System.out.println("DEBUG: Documents success: " + (documentsSuccess.isEmpty() ? "none" : documentsSuccess.get(0).getValue())); |
| 1725 | + |
| 1726 | + // Check DLQ file for error details |
| 1727 | + if (new File(dlqFile).exists()) { |
| 1728 | + System.out.println("DEBUG: DLQ file contents:"); |
| 1729 | + Files.lines(Paths.get(dlqFile)).forEach(line -> System.out.println("DLQ: " + line)); |
| 1730 | + } else { |
| 1731 | + System.out.println("DEBUG: No DLQ file created"); |
| 1732 | + } |
| 1733 | + |
| 1734 | + // The issue is that DataStreamDetector.isDataStream() is returning false |
| 1735 | + // This happens because the sink is initialized BEFORE the data stream is created |
| 1736 | + // So the cache has a negative result |
| 1737 | + |
| 1738 | + final List<Map<String, Object>> retSources = getSearchResponseDocSources(dataStreamName); |
| 1739 | + System.out.println("DEBUG: Number of documents found: " + retSources.size()); |
| 1740 | + System.out.println("DEBUG: Documents: " + retSources); |
| 1741 | + assertThat("Expected 1 document in data stream " + dataStreamName + " but found " + retSources.size(), |
| 1742 | + retSources.size(), equalTo(1)); |
| 1743 | + } catch (Exception e) { |
| 1744 | + System.err.println("ERROR: Test failed with exception: " + e.getMessage()); |
| 1745 | + e.printStackTrace(); |
| 1746 | + throw e; |
| 1747 | + } finally { |
| 1748 | + // Clean up the data stream |
| 1749 | + final Request deleteDataStreamRequest = new Request(HttpMethod.DELETE, "/_data_stream/" + dataStreamName); |
| 1750 | + try { |
| 1751 | + client.performRequest(deleteDataStreamRequest); |
| 1752 | + } catch (IOException e) { |
| 1753 | + // Ignore cleanup errors |
| 1754 | + } |
| 1755 | + |
| 1756 | + // Clean up the index template |
| 1757 | + final Request deleteTemplateRequest = new Request(HttpMethod.DELETE, "/_index_template/" + templateName); |
| 1758 | + try { |
| 1759 | + client.performRequest(deleteTemplateRequest); |
| 1760 | + } catch (IOException e) { |
| 1761 | + // Ignore cleanup errors |
| 1762 | + } |
| 1763 | + |
| 1764 | + // Clean up DLQ |
| 1765 | + FileUtils.deleteQuietly(tempDirectory); |
| 1766 | + } |
| 1767 | + } |
| 1768 | + |
1640 | 1769 | @Test |
1641 | 1770 | @Timeout(value = 1, unit = TimeUnit.MINUTES) |
1642 | 1771 | @DisabledIf(value = "isES6", |
@@ -1962,6 +2091,11 @@ private static boolean isES6() { |
1962 | 2091 | return DeclaredOpenSearchVersion.OPENDISTRO_0_10.compareTo(OpenSearchIntegrationHelper.getVersion()) >= 0; |
1963 | 2092 | } |
1964 | 2093 |
|
| 2094 | + private static boolean isDataStreamNotSupported() { |
| 2095 | + // Data streams require OpenSearch 1.3.0+ |
| 2096 | + return OpenSearchIntegrationHelper.getVersion().compareTo(DeclaredOpenSearchVersion.parse("opensearch:1.3.0")) < 0; |
| 2097 | + } |
| 2098 | + |
1965 | 2099 | private static Stream<Object> getAttributeTestSpecialAndExtremeValues() { |
1966 | 2100 | return Stream.of( |
1967 | 2101 | null, |
|
0 commit comments