├── .github ├── ISSUE_TEMPLATE │ └── bug_template.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build.yml │ ├── docs.yml │ └── package.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── build.gradle ├── config └── checkstyle │ ├── checkstyle.xml │ └── suppressions.xml ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── docs │ ├── advance │ │ ├── dlq.md │ │ ├── errors.md │ │ ├── filters.md │ │ ├── generic.md │ │ ├── retries.md │ │ └── sink-pool.md │ ├── concepts │ │ ├── architecture.md │ │ ├── consumer.md │ │ ├── decorators.md │ │ ├── filters.md │ │ ├── monitoring.md │ │ ├── offsets.md │ │ ├── overview.md │ │ └── templating.md │ ├── contribute │ │ ├── contribution.md │ │ └── development.md │ ├── guides │ │ ├── create_firehose.md │ │ ├── deployment.md │ │ ├── jexl-based-filters.md │ │ ├── json-based-filters.md │ │ └── manage.md │ ├── introduction.md │ ├── reference │ │ ├── core-faqs.md │ │ ├── faq.md │ │ ├── glossary.md │ │ └── metrics.md │ ├── roadmap.md │ ├── sinks │ │ ├── bigquery-sink.md │ │ ├── bigtable-sink.md │ │ ├── blob-sink.md │ │ ├── elasticsearch-sink.md │ │ ├── grpc-sink.md │ │ ├── http-sink.md │ │ ├── influxdb-sink.md │ │ ├── jdbc-sink.md │ │ ├── mongo-sink.md │ │ ├── prometheus-sink.md │ │ └── redis-sink.md │ └── support.md ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src │ └── css │ │ ├── custom.css │ │ ├── icons.css │ │ └── theme.css ├── static │ ├── .nojekyll │ ├── assets │ │ ├── architecture.png │ │ ├── integration.png │ │ ├── metrics_flow.png │ │ ├── overview.svg │ │ ├── screenshot-from-2021-07-12-16-55-42.png │ │ ├── screenshot-from-2021-07-12-16-57-00.png │ │ ├── screenshot-from-2021-07-12-17-02-29.png │ │ ├── screenshot-from-2021-07-12-17-08-00.png │ │ ├── screenshot-from-2021-07-12-17-10-36.png │ │ ├── screenshot-from-2021-07-12-17-10-54.png │ │ ├── screenshot-from-2021-07-12-17-15-22.png │ │ ├── screenshot-from-2021-07-12-17-19-02.png │ │ ├── screenshot-from-2021-07-12-17-19-59.png │ │ ├── screenshot-from-2021-07-12-17-20-08.png │ │ ├── screenshot-from-2021-07-12-17-20-35.png │ │ ├── screenshot-from-2021-07-12-17-22-00.png │ │ ├── screenshot-from-2021-07-12-17-51-43.png │ │ ├── screenshot-from-2021-07-12-17-55-11.png │ │ └── screenshot-from-2021-07-12-17-55-26.png │ ├── firehose.png │ └── overview.svg └── yarn.lock ├── env └── local.properties ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lombok.config ├── settings.gradle └── src ├── main ├── java │ └── org │ │ └── raystack │ │ └── firehose │ │ ├── config │ │ ├── AppConfig.java │ │ ├── BlobSinkConfig.java │ │ ├── DlqConfig.java │ │ ├── DlqKafkaProducerConfig.java │ │ ├── ErrorConfig.java │ │ ├── EsSinkConfig.java │ │ ├── FilterConfig.java │ │ ├── GCSConfig.java │ │ ├── GrpcSinkConfig.java │ │ ├── HttpSinkConfig.java │ │ ├── InfluxSinkConfig.java │ │ ├── JdbcSinkConfig.java │ │ ├── KafkaConsumerConfig.java │ │ ├── MongoSinkConfig.java │ │ ├── PromSinkConfig.java │ │ ├── S3Config.java │ │ ├── SinkPoolConfig.java │ │ ├── converter │ │ │ ├── BlobSinkFilePartitionTypeConverter.java │ │ │ ├── BlobSinkLocalFileWriterTypeConverter.java │ │ │ ├── BlobStorageTypeConverter.java │ │ │ ├── ConsumerModeConverter.java │ │ │ ├── DlqWriterTypeConverter.java │ │ │ ├── EsSinkMessageTypeConverter.java │ │ │ ├── FilterDataSourceTypeConverter.java │ │ │ ├── FilterEngineTypeConverter.java │ │ │ ├── FilterMessageFormatTypeConverter.java │ │ │ ├── HttpSinkDataFormatTypeConverter.java │ │ │ ├── HttpSinkParameterDataFormatConverter.java │ │ │ ├── HttpSinkParameterPlacementTypeConverter.java │ │ │ ├── HttpSinkParameterSourceTypeConverter.java │ │ │ ├── HttpSinkRequestMethodConverter.java │ │ │ ├── InputSchemaTypeConverter.java │ │ │ ├── LabelMapConverter.java │ │ │ ├── MongoSinkMessageTypeConverter.java │ │ │ ├── ProtoIndexToFieldMapConverter.java │ │ │ ├── RangeToHashMapConverter.java │ │ │ ├── SchemaRegistryHeadersConverter.java │ │ │ ├── SchemaRegistryRefreshConverter.java │ │ │ ├── SetErrorTypeConverter.java │ │ │ └── SinkTypeConverter.java │ │ └── enums │ │ │ ├── EsSinkMessageType.java │ │ │ ├── EsSinkRequestType.java │ │ │ ├── FilterDataSourceType.java │ │ │ ├── FilterEngineType.java │ │ │ ├── FilterMessageFormatType.java │ │ │ ├── HttpSinkDataFormatType.java │ │ │ ├── HttpSinkParameterPlacementType.java │ │ │ ├── HttpSinkParameterSourceType.java │ │ │ ├── HttpSinkRequestMethodType.java │ │ │ ├── InputSchemaType.java │ │ │ ├── KafkaConsumerMode.java │ │ │ ├── MongoSinkMessageType.java │ │ │ ├── MongoSinkRequestType.java │ │ │ └── SinkType.java │ │ ├── consumer │ │ ├── FirehoseAsyncConsumer.java │ │ ├── FirehoseConsumer.java │ │ ├── FirehoseConsumerFactory.java │ │ ├── FirehoseFilter.java │ │ ├── FirehoseSyncConsumer.java │ │ └── kafka │ │ │ ├── ConsumerAndOffsetManager.java │ │ │ ├── FirehoseKafkaConsumer.java │ │ │ ├── OffsetManager.java │ │ │ └── OffsetNode.java │ │ ├── converter │ │ └── ProtoTimeConverter.java │ │ ├── error │ │ ├── ErrorHandler.java │ │ └── ErrorScope.java │ │ ├── exception │ │ ├── ConfigurationException.java │ │ ├── DefaultException.java │ │ ├── DeserializerException.java │ │ ├── EmptyMessageException.java │ │ ├── FirehoseConsumerFailedException.java │ │ ├── JsonParseException.java │ │ ├── NeedToRetry.java │ │ ├── OAuth2Exception.java │ │ ├── SinkException.java │ │ ├── SinkTaskFailedException.java │ │ └── UnknownFieldsException.java │ │ ├── filter │ │ ├── Filter.java │ │ ├── FilterException.java │ │ ├── FilteredMessages.java │ │ ├── NoOpFilter.java │ │ ├── jexl │ │ │ └── JexlFilter.java │ │ └── json │ │ │ ├── JsonFilter.java │ │ │ └── JsonFilterUtil.java │ │ ├── launch │ │ ├── Main.java │ │ └── Task.java │ │ ├── message │ │ ├── FirehoseMessageUtils.java │ │ └── Message.java │ │ ├── metrics │ │ ├── BigQueryMetrics.java │ │ ├── BlobStorageMetrics.java │ │ ├── FirehoseInstrumentation.java │ │ └── Metrics.java │ │ ├── parser │ │ └── KafkaEnvironmentVariables.java │ │ ├── proto │ │ ├── ProtoMessage.java │ │ ├── ProtoToFieldMapper.java │ │ └── ProtoUtils.java │ │ ├── serializer │ │ ├── JsonWrappedProtoByte.java │ │ ├── MessageJsonSerializer.java │ │ ├── MessageSerializer.java │ │ ├── MessageToJson.java │ │ └── MessageToTemplatizedJson.java │ │ ├── sink │ │ ├── AbstractSink.java │ │ ├── GenericSink.java │ │ ├── Sink.java │ │ ├── SinkFactory.java │ │ ├── SinkFactoryUtils.java │ │ ├── SinkPool.java │ │ ├── bigquery │ │ │ └── BigquerySinkUtils.java │ │ ├── blob │ │ │ ├── BlobSink.java │ │ │ ├── BlobSinkFactory.java │ │ │ ├── Constants.java │ │ │ ├── message │ │ │ │ ├── KafkaMetadataUtils.java │ │ │ │ ├── MessageDeSerializer.java │ │ │ │ └── Record.java │ │ │ ├── proto │ │ │ │ ├── KafkaMetadataProtoMessage.java │ │ │ │ ├── KafkaMetadataProtoMessageUtils.java │ │ │ │ ├── NestedKafkaMetadataProtoMessage.java │ │ │ │ └── TimestampMetadataProtoMessage.java │ │ │ └── writer │ │ │ │ ├── WriterOrchestrator.java │ │ │ │ ├── WriterOrchestratorStatus.java │ │ │ │ ├── local │ │ │ │ ├── LocalFileChecker.java │ │ │ │ ├── LocalFileMetadata.java │ │ │ │ ├── LocalFileWriter.java │ │ │ │ ├── LocalFileWriterFailedException.java │ │ │ │ ├── LocalParquetFileWriter.java │ │ │ │ ├── LocalStorage.java │ │ │ │ ├── path │ │ │ │ │ └── TimePartitionedPathUtils.java │ │ │ │ └── policy │ │ │ │ │ ├── SizeBasedRotatingPolicy.java │ │ │ │ │ ├── TimeBasedRotatingPolicy.java │ │ │ │ │ └── WriterPolicy.java │ │ │ │ └── remote │ │ │ │ ├── BlobStorageChecker.java │ │ │ │ ├── BlobStorageFailedException.java │ │ │ │ ├── BlobStorageWorker.java │ │ │ │ └── BlobStorageWriterFutureHandler.java │ │ ├── common │ │ │ ├── AbstractHttpSink.java │ │ │ ├── KeyOrMessageParser.java │ │ │ └── blobstorage │ │ │ │ ├── BlobStorage.java │ │ │ │ ├── BlobStorageException.java │ │ │ │ ├── BlobStorageFactory.java │ │ │ │ ├── BlobStorageType.java │ │ │ │ ├── gcs │ │ │ │ ├── GoogleCloudStorage.java │ │ │ │ └── error │ │ │ │ │ └── GCSErrorType.java │ │ │ │ └── s3 │ │ │ │ └── S3.java │ │ ├── dlq │ │ │ ├── DLQWriterType.java │ │ │ ├── DlqWriter.java │ │ │ ├── DlqWriterFactory.java │ │ │ ├── blobstorage │ │ │ │ ├── BlobStorageDlqWriter.java │ │ │ │ └── DlqMessage.java │ │ │ ├── kafka │ │ │ │ └── KafkaDlqWriter.java │ │ │ └── log │ │ │ │ └── LogDlqWriter.java │ │ ├── elasticsearch │ │ │ ├── EsSink.java │ │ │ ├── EsSinkFactory.java │ │ │ └── request │ │ │ │ ├── EsRequestHandler.java │ │ │ │ ├── EsRequestHandlerFactory.java │ │ │ │ ├── EsUpdateRequestHandler.java │ │ │ │ └── EsUpsertRequestHandler.java │ │ ├── grpc │ │ │ ├── GrpcSink.java │ │ │ ├── GrpcSinkFactory.java │ │ │ └── client │ │ │ │ └── GrpcClient.java │ │ ├── http │ │ │ ├── HttpSink.java │ │ │ ├── HttpSinkFactory.java │ │ │ ├── auth │ │ │ │ ├── OAuth2AccessToken.java │ │ │ │ ├── OAuth2Client.java │ │ │ │ └── OAuth2Credential.java │ │ │ ├── factory │ │ │ │ └── SerializerFactory.java │ │ │ └── request │ │ │ │ ├── HttpRequestMethodFactory.java │ │ │ │ ├── RequestFactory.java │ │ │ │ ├── body │ │ │ │ └── JsonBody.java │ │ │ │ ├── create │ │ │ │ ├── BatchRequestCreator.java │ │ │ │ ├── IndividualRequestCreator.java │ │ │ │ └── RequestCreator.java │ │ │ │ ├── entity │ │ │ │ └── RequestEntityBuilder.java │ │ │ │ ├── header │ │ │ │ └── HeaderBuilder.java │ │ │ │ ├── method │ │ │ │ └── HttpDeleteWithBody.java │ │ │ │ ├── types │ │ │ │ ├── DynamicUrlRequest.java │ │ │ │ ├── ParameterizedHeaderRequest.java │ │ │ │ ├── ParameterizedUriRequest.java │ │ │ │ ├── Request.java │ │ │ │ └── SimpleRequest.java │ │ │ │ └── uri │ │ │ │ ├── UriBuilder.java │ │ │ │ └── UriParser.java │ │ ├── influxdb │ │ │ ├── InfluxSink.java │ │ │ ├── InfluxSinkFactory.java │ │ │ └── builder │ │ │ │ └── PointBuilder.java │ │ ├── jdbc │ │ │ ├── HikariJdbcConnectionPool.java │ │ │ ├── JdbcConnectionPool.java │ │ │ ├── JdbcMapper.java │ │ │ ├── JdbcSink.java │ │ │ ├── JdbcSinkFactory.java │ │ │ ├── QueryTemplate.java │ │ │ └── field │ │ │ │ ├── JdbcDefaultField.java │ │ │ │ ├── JdbcField.java │ │ │ │ ├── JdbcFieldFactory.java │ │ │ │ ├── JdbcMapField.java │ │ │ │ └── message │ │ │ │ ├── JdbcCollectionField.java │ │ │ │ ├── JdbcDefaultMessageField.java │ │ │ │ └── JdbcTimestampField.java │ │ ├── mongodb │ │ │ ├── MongoSink.java │ │ │ ├── MongoSinkFactory.java │ │ │ ├── client │ │ │ │ ├── MongoSinkClient.java │ │ │ │ └── MongoSinkClientUtil.java │ │ │ ├── request │ │ │ │ ├── MongoRequestHandler.java │ │ │ │ ├── MongoRequestHandlerFactory.java │ │ │ │ ├── MongoUpdateRequestHandler.java │ │ │ │ └── MongoUpsertRequestHandler.java │ │ │ └── util │ │ │ │ └── MongoSinkFactoryUtil.java │ │ └── prometheus │ │ │ ├── PromSink.java │ │ │ ├── PromSinkConstants.java │ │ │ ├── PromSinkFactory.java │ │ │ ├── builder │ │ │ ├── HeaderBuilder.java │ │ │ ├── PrometheusLabel.java │ │ │ ├── PrometheusMetric.java │ │ │ ├── RequestEntityBuilder.java │ │ │ ├── TimeSeriesBuilder.java │ │ │ ├── TimeSeriesBuilderUtils.java │ │ │ └── WriteRequestBuilder.java │ │ │ └── request │ │ │ ├── PromRequest.java │ │ │ └── PromRequestCreator.java │ │ ├── sinkdecorator │ │ ├── BackOff.java │ │ ├── BackOffProvider.java │ │ ├── ExponentialBackOffProvider.java │ │ ├── SinkDecorator.java │ │ ├── SinkFinal.java │ │ ├── SinkWithDlq.java │ │ ├── SinkWithFailHandler.java │ │ └── SinkWithRetry.java │ │ ├── tracer │ │ ├── SinkTracer.java │ │ └── Traceable.java │ │ └── utils │ │ ├── ConsumerRebalancer.java │ │ ├── KafkaUtils.java │ │ └── StencilUtils.java ├── proto │ ├── cortex.proto │ └── gogo.proto └── resources │ ├── META-INF │ └── services │ │ ├── io.grpc.LoadBalancerProvider │ │ └── io.grpc.NameResolverProvider │ ├── application.yml │ ├── log4j.xml │ └── logback.xml └── test ├── java └── org │ └── raystack │ └── firehose │ ├── config │ ├── GCSConfigTest.java │ ├── HttpSinkDataFormatTypeConverterTest.java │ ├── ProtoIndexToFieldMapConverterTest.java │ ├── RangeToHashMapConverterTest.java │ ├── S3ConfigTest.java │ └── converter │ │ ├── InputSchemaTypeConverterTest.java │ │ └── SchemaRegistryHeadersConverterTest.java │ ├── consumer │ ├── ConsumerAndOffsetManagerTest.java │ ├── FirehoseAsyncConsumerTest.java │ ├── FirehoseFilterTest.java │ ├── FirehoseSyncConsumerTest.java │ └── kafka │ │ ├── FirehoseKafkaConsumerTest.java │ │ └── OffsetManagerTest.java │ ├── converter │ └── ProtoTimeConverterTest.java │ ├── error │ └── ErrorHandlerTest.java │ ├── filter │ ├── NoOpFilterTest.java │ ├── jexl │ │ └── JexlFilterTest.java │ └── json │ │ ├── JsonFilterTest.java │ │ └── JsonFilterUtilTest.java │ ├── launch │ └── TaskTest.java │ ├── message │ ├── FirehoseMessageUtilsTest.java │ └── MessageTest.java │ ├── metrics │ └── FirehoseInstrumentationTest.java │ ├── parser │ └── KafkaEnvironmentVariablesTest.java │ ├── proto │ ├── ProtoMessageTest.java │ └── ProtoUtilTest.java │ ├── serializer │ ├── JsonWrappedProtoByteTest.java │ ├── MessageToJsonTest.java │ └── MessageToTemplatizedJsonTest.java │ ├── sink │ ├── AbstractSinkTest.java │ ├── GenericSinkTest.java │ ├── SinkFactoryUtilsTest.java │ ├── SinkPoolTest.java │ ├── bigquery │ │ └── BigquerySinkUtilsTest.java │ ├── blob │ │ ├── BlobSinkTest.java │ │ ├── TestProtoMessage.java │ │ ├── TestUtils.java │ │ ├── message │ │ │ ├── KafkaMetadataUtilsTest.java │ │ │ ├── MessageDeSerializerTest.java │ │ │ └── RecordTest.java │ │ ├── proto │ │ │ ├── KafkaMetadataProtoFirehoseMessageUtilsTest.java │ │ │ ├── KafkaMetadataProtoMessageTest.java │ │ │ ├── NestedKafkaMetadataProtoMessageTest.java │ │ │ └── TimestampMetadataProtoMessageTest.java │ │ └── writer │ │ │ ├── WriterOrchestratorStatusTest.java │ │ │ ├── WriterOrchestratorTest.java │ │ │ ├── local │ │ │ ├── LocalFileCheckerTest.java │ │ │ ├── LocalStorageTest.java │ │ │ ├── TimePartitionedPathUtilsTest.java │ │ │ └── policy │ │ │ │ ├── SizeBasedRotatingPolicyTest.java │ │ │ │ └── TimeBasedRotatingPolicyTest.java │ │ │ └── remote │ │ │ ├── BlobStorageCheckerTest.java │ │ │ └── BlobStorageWriterFutureHandlerTest.java │ ├── common │ │ ├── KeyOrMessageParserTest.java │ │ ├── gcs │ │ │ └── GoogleCloudStorageTest.java │ │ └── s3 │ │ │ └── S3Test.java │ ├── dlq │ │ ├── KafkaDlqWriterTest.java │ │ ├── LogDlqWriterTest.java │ │ └── blobstorage │ │ │ └── BlobStorageDlqWriterTest.java │ ├── elasticsearch │ │ ├── EsSinkFactoryTest.java │ │ ├── EsSinkTest.java │ │ └── request │ │ │ ├── ESUpdateRequestHandlerTest.java │ │ │ ├── ESUpsertRequestHandlerTest.java │ │ │ └── EsRequestHandlerFactoryTest.java │ ├── grpc │ │ ├── GrpcClientTest.java │ │ ├── GrpcSinkFactoryTest.java │ │ └── GrpcSinkTest.java │ ├── http │ │ ├── HttpSinkFactoryTest.java │ │ ├── HttpSinkTest.java │ │ ├── auth │ │ │ ├── OAuth2AccessTokenTest.java │ │ │ ├── OAuth2ClientTest.java │ │ │ └── OAuth2CredentialTest.java │ │ └── request │ │ │ ├── RequestFactoryTest.java │ │ │ ├── body │ │ │ └── JsonBodyTest.java │ │ │ ├── create │ │ │ ├── BatchRequestCreatorTest.java │ │ │ └── IndividualRequestCreatorTest.java │ │ │ ├── entity │ │ │ └── RequestEntityBuilderTest.java │ │ │ ├── header │ │ │ └── HeaderBuilderTest.java │ │ │ ├── types │ │ │ ├── DynamicUrlRequestTest.java │ │ │ ├── ParameterizedHeaderRequestTest.java │ │ │ ├── ParameterizedUriRequestTest.java │ │ │ └── SimpleRequestTest.java │ │ │ └── uri │ │ │ ├── UriBuilderTest.java │ │ │ └── UriParserTest.java │ ├── influxdb │ │ ├── InfluxSinkTest.java │ │ └── builder │ │ │ └── PointBuilderTest.java │ ├── jdbc │ │ ├── HikariJdbcConnectionPoolTest.java │ │ ├── JdbcSinkTest.java │ │ ├── ProtoToFieldMapperTest.java │ │ ├── QueryTemplateTest.java │ │ └── field │ │ │ ├── JdbcMapFieldTest.java │ │ │ └── message │ │ │ ├── DBCollectionFieldTest.java │ │ │ ├── DBDefaultMessageFieldTest.java │ │ │ └── DBTimestampFieldTest.java │ ├── mongodb │ │ ├── MongoSinkFactoryTest.java │ │ ├── MongoSinkTest.java │ │ ├── client │ │ │ ├── MongoSinkClientTest.java │ │ │ └── MongoSinkClientUtilTest.java │ │ ├── request │ │ │ ├── MongoRequestHandlerFactoryTest.java │ │ │ ├── MongoUpdateRequestHandlerTest.java │ │ │ └── MongoUpsertRequestHandlerTest.java │ │ └── util │ │ │ └── MongoSinkFactoryUtilTest.java │ └── prometheus │ │ ├── PromSinkFactoryTest.java │ │ ├── PromSinkTest.java │ │ ├── builder │ │ ├── HeaderBuilderTest.java │ │ ├── RequestEntityBuilderTest.java │ │ ├── TimeSeriesBuilderTest.java │ │ ├── TimeSeriesBuilderUtilsTest.java │ │ └── WriteRequestBuilderTest.java │ │ └── request │ │ ├── PromRequestCreatorTest.java │ │ └── PromRequestTest.java │ ├── sinkdecorator │ ├── ExponentialBackOffProviderTest.java │ ├── SinkFinalTest.java │ ├── SinkWithDlqTest.java │ ├── SinkWithFailHandlerTest.java │ └── SinkWithRetryTest.java │ ├── test │ └── categories │ │ └── IntegrationTest.java │ └── tracer │ └── SinkTracerTest.java └── proto ├── TestGrpc.proto ├── TestLogMessage.proto ├── TestMessage.proto └── TestMessageBQ.proto /.github/ISSUE_TEMPLATE/bug_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Issue 3 | about: Use this template for reporting a bug 4 | labels: "type: bug" 5 | --- 6 | 7 | ## 🐛 Bug Report 8 | A clear and concise description of what the bug is. 9 | Include screenshots if needed. 10 | 11 | 12 | ## Expected Behavior 13 | A clear and concise description of what you expected to happen. 14 | 15 | ## Steps to Reproduce 16 | Steps to reproduce the behavior. 17 | 1. Step 1 18 | 2. Step 2 19 | 3. ... 20 | 21 | 22 | ## Code Example 23 | Please provide a link to a repository on GitHub, or provide a minimal code example that reproduces the problem. 24 | Issues without a reproduction link are likely to stall. 25 | 26 | 27 | ## Environment 28 | Describe the environment and provide any relevant configuration. 29 | - Operating System and Version (e.g. Ubuntu 14.04): 30 | - Version (1.2.2): 31 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Hey, I just made a Pull Request! 2 | Please describe what you added, and add a screenshot if possible. 3 | That makes it easier to understand the change so we can :shipit: faster. 4 | 5 | #### :heavy_check_mark: Checklist 6 | 7 | 8 | - [ ] A changelog describing the changes and affected packages. 9 | - [ ] Added or updated documentation 10 | - [ ] Tests for new functionality and regression tests for bug fixes 11 | - [ ] Screenshots attached (for UI changes) -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up JDK 1.8 17 | uses: actions/setup-java@v1 18 | with: 19 | java-version: 1.8 20 | - name: Grant execute permission for gradlew 21 | run: chmod +x gradlew 22 | - name: Build 23 | run: ./gradlew build 24 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | documentation: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | - name: Installation 16 | uses: bahmutov/npm-install@v1 17 | with: 18 | install-command: yarn 19 | working-directory: docs 20 | - name: Build docs 21 | working-directory: docs 22 | run: cd docs && yarn build 23 | - name: Deploy docs 24 | env: 25 | GIT_USER: ravisuhag 26 | GIT_PASS: ${{ secrets.DOCU_RS_TOKEN }} 27 | DEPLOYMENT_BRANCH: gh-pages 28 | CURRENT_BRANCH: master 29 | working-directory: docs 30 | run: | 31 | git config --global user.email "suhag.ravi@gmail.com" 32 | git config --global user.name "ravisuhag" 33 | yarn deploy 34 | -------------------------------------------------------------------------------- /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | name: Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up JDK 1.8 14 | uses: actions/setup-java@v1 15 | with: 16 | java-version: 1.8 17 | - name: Grant execute permission for gradlew 18 | run: chmod +x gradlew 19 | - name: Build 20 | run: ./gradlew build 21 | 22 | publish: 23 | needs: build 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v2 27 | - uses: actions/setup-java@v1 28 | with: 29 | java-version: 1.8 30 | - name: Get release tag 31 | id: get_version 32 | uses: battila7/get-version-action@v2 33 | - name: Publish package 34 | run: ./gradlew publish 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | - name: Login to DockerHub 38 | uses: docker/login-action@v1 39 | with: 40 | username: ${{ secrets.DOCKERHUB_USERNAME }} 41 | password: ${{ secrets.DOCKERHUB_TOKEN }} 42 | - name: Build and Push 43 | uses: docker/build-push-action@v2 44 | with: 45 | context: . 46 | push: true 47 | tags: | 48 | raystack/firehose:latest 49 | raystack/firehose:${{ steps.get_version.outputs.version-without-v }} 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .gradle 3 | *.iml 4 | build 5 | out 6 | classes 7 | .project 8 | generated 9 | *.ipr 10 | *.iws 11 | .classpath 12 | .project 13 | .settings 14 | bin 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk:8-jdk-openj9 AS GRADLE_BUILD 2 | RUN mkdir -p ./build/libs/ 3 | RUN curl -L http://search.maven.org/remotecontent?filepath=org/jolokia/jolokia-jvm/1.6.2/jolokia-jvm-1.6.2-agent.jar -o ./jolokia-jvm-agent.jar 4 | COPY ./ ./ 5 | RUN ./gradlew build 6 | 7 | FROM openjdk:8-jre 8 | COPY --from=GRADLE_BUILD ./build/libs/ /opt/firehose/bin 9 | COPY --from=GRADLE_BUILD ./jolokia-jvm-agent.jar /opt/firehose 10 | COPY --from=GRADLE_BUILD ./src/main/resources/log4j.xml /opt/firehose/etc/log4j.xml 11 | COPY --from=GRADLE_BUILD ./src/main/resources/logback.xml /opt/firehose/etc/logback.xml 12 | WORKDIR /opt/firehose 13 | CMD ["java", "-cp", "bin/*:/work-dir/*", "org.raystack.firehose.launch.Main", "-server", "-Dlogback.configurationFile=etc/firehose/logback.xml", "-Xloggc:/var/log/firehose"] 14 | -------------------------------------------------------------------------------- /config/checkstyle/suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | ``` 30 | $ GIT_USER= USE_SSH=true yarn deploy 31 | ``` 32 | 33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 34 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/advance/errors.md: -------------------------------------------------------------------------------- 1 | # Errors 2 | 3 | These errors are returned by sinks. One can configure to which errors should be processed by which decorator. The error 4 | type are: 5 | 6 | * DESERIALIZATION_ERROR 7 | * INVALID_MESSAGE_ERROR 8 | * UNKNOWN_FIELDS_ERROR 9 | * SINK_4XX_ERROR 10 | * SINK_5XX_ERROR 11 | * SINK_UNKNOWN_ERROR 12 | * DEFAULT_ERROR 13 | * If no error is specified 14 | 15 | ## `ERROR_TYPES_FOR_FAILING` 16 | 17 | * Example value: `DEFAULT_ERROR,SINK_UNKNOWN_ERROR` 18 | * Type: `optional` 19 | * Default value: `DESERIALIZATION_ERROR,INVALID_MESSAGE_ERROR,UNKNOWN_FIELDS_ERROR` 20 | 21 | ## `ERROR_TYPES_FOR_DLQ` 22 | 23 | * Example value: `DEFAULT_ERROR,SINK_UNKNOWN_ERROR` 24 | * Type: `optional` 25 | * Default value: `` 26 | 27 | ## `ERROR_TYPES_FOR_RETRY` 28 | 29 | * Example value: `DEFAULT_ERROR` 30 | * Type: `optional` 31 | * Default value: `DEFAULT_ERROR` 32 | -------------------------------------------------------------------------------- /docs/docs/advance/filters.md: -------------------------------------------------------------------------------- 1 | # Filters 2 | 3 | Following variables need to be set to enable JSON/JEXL filters. 4 | 5 | ## `FILTER_ENGINE` 6 | 7 | Defines whether to use `JSON` Schema-based filters or `JEXL`-based filters or `NO_OP` \(i.e. no filtering\) 8 | 9 | - Example value: `JSON` 10 | - Type: `optional` 11 | - Default value`: NO_OP` 12 | 13 | ## `FILTER_JSON_ESB_MESSAGE_TYPE` 14 | 15 | Defines the format type of the input ESB messages, i.e. JSON/Protobuf. This field is required only for JSON filters. 16 | 17 | - Example value: `JSON` 18 | - Type: `optional` 19 | 20 | ## `FILTER_SCHEMA_PROTO_CLASS` 21 | 22 | The fully qualified name of the proto schema so that the key/message in Kafka could be parsed. 23 | 24 | - Example value: `com.raystack.esb.driverlocation.DriverLocationLogKey` 25 | - Type: `optional` 26 | 27 | ## `FILTER_DATA_SOURCE` 28 | 29 | `key`/`message`/`none`depending on where to apply filter 30 | 31 | - Example value: `key` 32 | - Type: `optional` 33 | - Default value`: none` 34 | 35 | ## `FILTER_JEXL_EXPRESSION` 36 | 37 | JEXL filter expression 38 | 39 | - Example value: `driverLocationLogKey.getVehicleType()=="BIKE"` 40 | - Type: `optional` 41 | 42 | ## `FILTER_JSON_SCHEMA` 43 | 44 | JSON Schema string containing the filter rules to be applied. 45 | 46 | - Example value: `{"properties":{"order_number":{"const":"1253"}}}` 47 | - Type: `optional` 48 | -------------------------------------------------------------------------------- /docs/docs/advance/retries.md: -------------------------------------------------------------------------------- 1 | # Retries 2 | 3 | ## `RETRY_EXPONENTIAL_BACKOFF_INITIAL_MS` 4 | 5 | Initial expiry time in milliseconds for exponential backoff policy. 6 | 7 | * Example value: `10` 8 | * Type: `optional` 9 | * Default value: `10` 10 | 11 | ## `RETRY_EXPONENTIAL_BACKOFF_RATE` 12 | 13 | Backoff rate for exponential backoff policy. 14 | 15 | * Example value: `2` 16 | * Type: `optional` 17 | * Default value: `2` 18 | 19 | ## `RETRY_EXPONENTIAL_BACKOFF_MAX_MS` 20 | 21 | Maximum expiry time in milliseconds for exponential backoff policy. 22 | 23 | * Example value: `60000` 24 | * Type: `optional` 25 | * Default value: `60000` 26 | 27 | ## `RETRY_FAIL_AFTER_MAX_ATTEMPTS_ENABLE` 28 | 29 | Fail the firehose if the retries exceed 30 | 31 | * Example value: `true` 32 | * Type: `optional` 33 | * Default value: `false` 34 | 35 | ## `RETRY_MAX_ATTEMPTS` 36 | 37 | Max attempts for retries 38 | 39 | * Example value: `3` 40 | * Type: `optional` 41 | * Default value: `2147483647` 42 | 43 | -------------------------------------------------------------------------------- /docs/docs/advance/sink-pool.md: -------------------------------------------------------------------------------- 1 | # Sink Pool 2 | 3 | ## `SINK_POOL_NUM_THREADS` 4 | 5 | Number of sinks in the pool to process messages in parallel. 6 | 7 | * Example value: `10` 8 | * Type: `optional` 9 | * Default value: `1` 10 | 11 | ## `SINK_POOL_QUEUE_POLL_TIMEOUT_MS` 12 | 13 | Poll timeout when the worker queue is full. 14 | 15 | * Example value: `1` 16 | * Type: `optional` 17 | * Default value: `1000` 18 | -------------------------------------------------------------------------------- /docs/docs/concepts/consumer.md: -------------------------------------------------------------------------------- 1 | # Firehose Consumer 2 | 3 | There are two type of consumer that can be configured. 4 | `SOURCE_KAFKA_CONSUMER_MODE` can be set as `SYNC` or `ASYNC`. 5 | SyncConsumer run in one thread on the other hand AsyncConsumer 6 | has a SinkPool. SinkPool can be configured by setting `SINK_POOL_NUM_THREADS`. 7 | ## FirehoseSyncConsumer 8 | 9 | * Pull messages from kafka in batches. 10 | * Apply filter based on filter configuration 11 | * Add offsets of Not filtered messages into OffsetManager and set them committable. 12 | * call sink.pushMessages() with filtered messages. 13 | * Add offsets for remaining messages and set them committable. 14 | * Call consumer.commit() 15 | * Repeat. 16 | 17 | ## FirehoseAsyncConsumer 18 | * Pull messages from kafka in batches. 19 | * Apply filter based on filter configuration 20 | * Add offsets of Not filtered messages into OffsetManager and set them committable. 21 | * Schedule a task on SinkPool for these messages. 22 | * Add offsets of these messages with key as the returned `Future`, 23 | * Check SinkPool for finished tasks. 24 | * Set offsets to be committable for any finished future. 25 | * Call consumer.commit() 26 | * Repeat. 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/docs/concepts/decorators.md: -------------------------------------------------------------------------------- 1 | # Decorators 2 | 3 | Decorators implement the Sink interface, and they can wrap other sinks. Decorators can also wrap other decorators. They 4 | process messages returned by the wrapped Sink. Sink and SinkDecorator pushMessage() API return messages that were not 5 | successful. 6 | 7 | ## Type of Decorators 8 | 9 | This is the order of execution decorators after sink returns failed messages. 10 | A failed message will have one of these error types: 11 | * DESERIALIZATION_ERROR 12 | * INVALID_MESSAGE_ERROR 13 | * UNKNOWN_FIELDS_ERROR 14 | * SINK_4XX_ERROR 15 | * SINK_5XX_ERROR 16 | * SINK_UNKNOWN_ERROR 17 | * DEFAULT_ERROR 18 | 19 | ### SinkWithFailHandler 20 | 21 | This decorator is intended to be used to trigger consumer failure based on configured error types. 22 | Configuration `ERROR_TYPES_FOR_FAILING` is to be set with the comma separated list of Error types. 23 | 24 | ### SinkWithRetry 25 | 26 | This decorator retries to push messages based on the configuration set for error types `ERROR_TYPES_FOR_RETRY`. 27 | It will retry for the maximum of `RETRY_MAX_ATTEMPTS` with exponential back off. 28 | 29 | ### SinkWithDlq 30 | This decorator pushes messages to DLQ based on the error types set in `ERROR_TYPES_FOR_DLQ`. 31 | This decorator will only be added if `DLQ_SINK_ENABLE` is set to be true. 32 | There are three types of DLQWriter that can be configured by setting `DLQ_WRITER_TYPE`. 33 | #### Log 34 | This is just for debugging, it prints out the messages to standard output. 35 | 36 | #### Kafka 37 | Based on the configurations in `DlqKafkaProducerConfig` class, messages are pushed to kafka. 38 | 39 | #### Blob 40 | Blob Storage can also be used to DLQ messages. Currently, only GCS is supported as a store. 41 | The `BlobStorageDlqWriter` converts each message into json String, and appends multiple messages via new line. 42 | These messages are pushed to a blob storage. The object name for messages is`topic_name/consumed_timestamp/a-random-uuid`. 43 | 44 | ### SinkFinal 45 | 46 | This decorator is the black hole for messages. The messages reached here are ignored 47 | and no more processing is done on them. 48 | -------------------------------------------------------------------------------- /docs/docs/concepts/offsets.md: -------------------------------------------------------------------------------- 1 | # Offset manager 2 | 3 | Every kafka message has an incremental offset. Kafka API has method to commit offsets given as arguments. If a larger 4 | offset is committed, lower offsets are considered to be automatically committed. 5 | 6 | Offset manager is a data structure which calculates committable offsets for each partition. 7 | To use offset manager: 8 | * add message(s) with metadata about offset and partition with a batch key. 9 | * add offsets into a sorted map with committable flag to be false. 10 | * `addOffsetToBatch(Object batch, List messages)` 11 | * set messages to be committable once the processing is finished. 12 | * `setCommittable(Object batch)` to set the committable flag to be true. 13 | * `getCommittableOffset()` returns the largest offset that can be committed. 14 | 15 | ## Implementation 16 | ### Data Structures 17 | * OffsetNode: A combination of topic, partition and the offset. 18 | * toBeCommittableBatchOffsets: A map of batch-keys and a set of OffsetNodes. 19 | * sortedOffsets: A map of topic-partition to a sorted list of OffsetNode. 20 | ### Adding offsets 21 | When `addOffsetToBatch(Object batch, List messages)` is called, it creates a OffsetNode from the message. 22 | Each Topic-Partition has a sorted list by offsets. The OffsetNode is added into this sorted list. 23 | OffsetNode is also added into the map keyed by provided key. 24 | ### Setting a batch to be Committable. 25 | `setCommittable(Object batch)` sets a flag `isCommittable` to be true on each 26 | OffsetNode on the batch. It also removes from the map `toBeCommittableBatchOffsets`. 27 | ### Getting Committable offsets 28 | `getCommittableOffset()` 29 | * For each topic-partition: 30 | * Look for the contiguous offsets in the sorted list which are set to be committed. 31 | * Return the largest offset from the contiguous series. 32 | * Delete smaller OffsetNodes from the sorted list. 33 | 34 | -------------------------------------------------------------------------------- /docs/docs/concepts/overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | The following topics will describe key concepts of Firehose. 4 | 5 | ## [Architecture](architecture.md) 6 | 7 | Firehose is a fully managed service that allows you to consume messages to any sink from kafka streams in realtime at 8 | scale. This section explains the overall architecture of Firehose and describes its various components. 9 | 10 | ## [Monitoring Firehose with exposed metrics](monitoring.md) 11 | 12 | Always know what’s going on with your deployment with 13 | built-in [monitoring](https://github.com/raystack/firehose/blob/main/docs/assets/firehose-grafana-dashboard.json) of 14 | throughput, response times, errors and more. This section contains guides, best practices and advises related to 15 | managing Firehose in production. 16 | 17 | ## [Filters](filters.md) 18 | 19 | Use the Filter feature provided in Firehose which allows you to apply any filters on the fields present in the key or 20 | message of the event data set and helps you narrow it down to your use case-specific data. 21 | 22 | ## [Templating](templating.md) 23 | 24 | Firehose provides various templating features 25 | 26 | ## [Decorators](decorators.md) 27 | 28 | Decorators are used for chained processing of messages. 29 | 30 | - SinkWithFailHandler 31 | - SinkWithRetry 32 | - SinkWithDlq 33 | - SinkFinal 34 | 35 | ## [FirehoseConsumer](consumer.md) 36 | 37 | A firehose consumer read messages from kafka, pushes those messages to sink and commits offsets back to kafka based on certain strategies. 38 | 39 | ## [Offsets](offsets.md) 40 | 41 | Offset manager is a data structure used to manage offsets asynchronously. An offset should only be committed when a 42 | message is processed fully. Offset manager maintains a state of all the offsets of all topic-partitions, that can be 43 | committed. It can also be used by sinks to manage its own offsets. 44 | -------------------------------------------------------------------------------- /docs/docs/concepts/templating.md: -------------------------------------------------------------------------------- 1 | # Templating 2 | 3 | Firehose HTTP sink supports payload templating using [`SINK_HTTP_JSON_BODY_TEMPLATE`](../sinks/http-sink.md#sink_http_json_body_template) configuration. It uses [JsonPath](https://github.com/json-path/JsonPath) for creating Templates which is a DSL for basic JSON parsing. Playground for this: [https://jsonpath.com/](https://jsonpath.com/), where users can play around with a given JSON to extract out the elements as required and validate the `jsonpath`. The template works only when the output data format [`SINK_HTTP_DATA_FORMAT`](../sinks/http-sink.md#sink_http_data_format) is JSON. 4 | 5 | _**Creating Templates:**_ 6 | 7 | This is really simple. Find the paths you need to extract using the JSON path. Create a valid JSON template with the static field names + the paths that need to extract. \(Paths name starts with $.\). Firehose will simply replace the paths with the actual data in the path of the message accordingly. Paths can also be used on keys, but be careful that the element in the key must be a string data type. 8 | 9 | One sample configuration\(On XYZ proto\) : `{"test":"$.routes[0]", "$.order_number" : "xxx"}` If you want to dump the entire JSON as it is in the backend, use `"$._all_"` as a path. 10 | 11 | Limitations: 12 | 13 | - Works when the input DATA TYPE is a protobuf, not a JSON. 14 | - Supports only on messages, not keys. 15 | - validation on the level of valid JSON template. But after data has been replaced the resulting string may or may not be a valid JSON. Users must do proper testing/validation from the service side. 16 | - If selecting fields from complex data types like repeated/messages/map of proto, the user must do filtering based first as selecting a field that does not exist would fail. 17 | -------------------------------------------------------------------------------- /docs/docs/guides/manage.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | ## Consumer Lag 4 | 5 | When it comes to decreasing the topic lag, it often helps to have the environment variable - [`SOURCE_KAFKA_CONSUMER_CONFIG_MAX_POLL_RECORDS`](../advance/generic/#source_kafka_consumer_config_max_poll_records) config to be increased from the default of 500 to something higher. 6 | 7 | Additionally, you can increase the workers in the Firehose which will effectively multiply the number of records being processed by Firehose. However, please be mindful of the caveat mentioned below. 8 | 9 | ### The caveat to the aforementioned remedies: 10 | 11 | Be mindful of the fact that your sink also needs to be able to process this higher volume of data being pushed to it. Because if it is not, then this will only compound the problem of increasing lag. 12 | 13 | Alternatively, if your underlying sink is not able to handle increased \(or default\) volume of data being pushed to it, adding some sort of a filter condition in the Firehose to ignore unnecessary messages in the topic would help you bring down the volume of data being processed by the sink. 14 | -------------------------------------------------------------------------------- /docs/docs/roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | In the following section, you can learn about what features we're working on, what stage they're in, and when we expect to bring them to you. Have any questions or comments about items on the roadmap? Join the [discussions](https://github.com/orgs/raystack/discussions) on the Firehose Github forum. 4 | 5 | We’re planning to iterate on the format of the roadmap itself, and we see the potential to engage more in discussions about the future of Firehose features. If you have feedback about the roadmap section itself, such as how the issues are presented, let us know through [discussions](https://github.com/raystack/discussions). 6 | 7 | ## Firehose 0.4 8 | 9 | Following are some of the upcoming enhancements on Firehose. 10 | 11 | - Support for [Druid](https://druid.apache.org/) 12 | - Support for [Clickhouse](https://clickhouse.tech/) 13 | - Support for [BigTable](https://cloud.google.com/bigtable) 14 | - Support for JSON format 15 | - Support for [Avro](https://avro.apache.org/) format 16 | -------------------------------------------------------------------------------- /docs/docs/sinks/bigtable-sink.md: -------------------------------------------------------------------------------- 1 | # Bigtable Sink 2 | 3 | Bigtable Sink is implemented in Firehose using the Bigtable sink connector implementation in Depot. You can check out Depot Github repository [here](https://github.com/raystack/depot). 4 | 5 | ### Configuration 6 | 7 | For Bigtable sink in Firehose we need to set first (`SINK_TYPE`=`bigtable`). There are some generic configs which are common across different sink types which need to be set which are mentioned in [generic.md](../advance/generic.md). Bigtable sink specific configs are mentioned in Depot repository. You can check out the Bigtable Sink configs [here](https://github.com/raystack/depot/blob/main/docs/reference/configuration/bigtable.md) 8 | -------------------------------------------------------------------------------- /docs/docs/sinks/grpc-sink.md: -------------------------------------------------------------------------------- 1 | # GRPC 2 | 3 | [gRPC](https://grpc.io/) is a modern open source high performance Remote Procedure Call framework that can run in any environment. 4 | 5 | A GRPC sink Firehose \(`SINK_TYPE`=`grpc`\) requires the following variables to be set along with Generic ones 6 | 7 | ### `SINK_GRPC_SERVICE_HOST` 8 | 9 | Defines the host of the GRPC service. 10 | 11 | - Example value: `http://grpc-service.sample.io` 12 | - Type: `required` 13 | 14 | ### `SINK_GRPC_SERVICE_PORT` 15 | 16 | Defines the port of the GRPC service. 17 | 18 | - Example value: `8500` 19 | - Type: `required` 20 | 21 | ### `SINK_GRPC_METHOD_URL` 22 | 23 | Defines the URL of the GRPC method that needs to be called. 24 | 25 | - Example value: `com.tests.SampleServer/SomeMethod` 26 | - Type: `required` 27 | 28 | ### `SINK_GRPC_RESPONSE_SCHEMA_PROTO_CLASS` 29 | 30 | Defines the Proto which would be the response of the GRPC Method. 31 | 32 | - Example value: `com.tests.SampleGrpcResponse` 33 | - Type: `required` 34 | -------------------------------------------------------------------------------- /docs/docs/support.md: -------------------------------------------------------------------------------- 1 | # Need help? 2 | 3 | Need a bit of help? We're here for you. Check out our current issues, GitHub discussions 4 | 5 | ### Issues 6 | 7 | Have a general issue or bug that you've found? We'd love to hear about it in our GitHub issues. This can be feature requests too! 8 | [Go to issues](https://github.com/raystack/firehose/issues) 9 | 10 | ### Discussions 11 | 12 | For help and questions about best practices, join our GitHub discussions. Browse and ask questions. 13 | [Go to discussions](https://github.com/orgs/raystack/discussions) 14 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firehose", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "^0.0.0-4608", 18 | "@docusaurus/plugin-google-gtag": "^2.0.0-beta.6", 19 | "@docusaurus/preset-classic": "^0.0.0-4608", 20 | "@mdx-js/react": "^1.6.21", 21 | "@svgr/webpack": "^6.0.0", 22 | "classnames": "^2.3.1", 23 | "clsx": "^1.1.1", 24 | "file-loader": "^6.2.0", 25 | "prism-react-renderer": "^1.2.1", 26 | "react": "^17.0.1", 27 | "react-dom": "^17.0.1", 28 | "url-loader": "^4.1.1" 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.5%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | docsSidebar: [ 3 | 'introduction', 4 | { 5 | type: "category", 6 | label: "Guides", 7 | collapsed: false, 8 | items: [ 9 | "guides/create_firehose", 10 | "guides/json-based-filters", 11 | "guides/jexl-based-filters", 12 | "guides/deployment", 13 | "guides/manage", 14 | ], 15 | }, 16 | { 17 | type: "category", 18 | label: "Sinks", 19 | collapsed: false, 20 | items: [ 21 | "sinks/http-sink", 22 | "sinks/grpc-sink", 23 | "sinks/jdbc-sink", 24 | "sinks/bigquery-sink", 25 | "sinks/influxdb-sink", 26 | "sinks/prometheus-sink", 27 | "sinks/mongo-sink", 28 | "sinks/redis-sink", 29 | "sinks/elasticsearch-sink", 30 | "sinks/blob-sink", 31 | ], 32 | }, 33 | { 34 | type: "category", 35 | label: "Concepts", 36 | items: [ 37 | "concepts/overview", 38 | "concepts/architecture", 39 | "concepts/filters", 40 | "concepts/templating", 41 | "concepts/consumer", 42 | "concepts/decorators", 43 | "concepts/offsets", 44 | "concepts/monitoring", 45 | ], 46 | }, 47 | { 48 | type: "category", 49 | label: "Advance", 50 | items: [ 51 | "advance/generic", 52 | "advance/errors", 53 | "advance/dlq", 54 | "advance/filters", 55 | "advance/retries", 56 | "advance/sink-pool", 57 | ], 58 | }, 59 | { 60 | type: "category", 61 | label: "Reference", 62 | items: [ 63 | "reference/metrics", 64 | "reference/core-faqs", 65 | "reference/faq", 66 | "reference/glossary", 67 | ], 68 | }, 69 | { 70 | type: "category", 71 | label: "Contribute", 72 | items: [ 73 | "contribute/contribution", 74 | "contribute/development", 75 | ], 76 | }, 77 | 'roadmap', 78 | ], 79 | }; -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/assets/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/architecture.png -------------------------------------------------------------------------------- /docs/static/assets/integration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/integration.png -------------------------------------------------------------------------------- /docs/static/assets/metrics_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/metrics_flow.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-16-55-42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-16-55-42.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-16-57-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-16-57-00.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-17-02-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-17-02-29.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-17-08-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-17-08-00.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-17-10-36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-17-10-36.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-17-10-54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-17-10-54.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-17-15-22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-17-15-22.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-17-19-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-17-19-02.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-17-19-59.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-17-19-59.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-17-20-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-17-20-08.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-17-20-35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-17-20-35.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-17-22-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-17-22-00.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-17-51-43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-17-51-43.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-17-55-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-17-55-11.png -------------------------------------------------------------------------------- /docs/static/assets/screenshot-from-2021-07-12-17-55-26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/assets/screenshot-from-2021-07-12-17-55-26.png -------------------------------------------------------------------------------- /docs/static/firehose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/docs/static/firehose.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | systemProp.sonar.host.url=http://172.16.0.220:9000 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raystack/firehose/9a118de508b4a56ef0b9df1b426e065ec92a1027/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Aug 25 23:42:32 IST 2021 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | lombok.addLombokGeneratedAnnotation = true 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'firehose' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/DlqConfig.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | import org.raystack.firehose.config.converter.BlobStorageTypeConverter; 4 | import org.raystack.firehose.config.converter.DlqWriterTypeConverter; 5 | import org.raystack.firehose.sink.common.blobstorage.BlobStorageType; 6 | import org.raystack.firehose.sink.dlq.DLQWriterType; 7 | 8 | public interface DlqConfig extends AppConfig { 9 | 10 | @Key("DLQ_WRITER_TYPE") 11 | @ConverterClass(DlqWriterTypeConverter.class) 12 | @DefaultValue("LOG") 13 | DLQWriterType getDlqWriterType(); 14 | 15 | @Key("DLQ_BLOB_STORAGE_TYPE") 16 | @DefaultValue("GCS") 17 | @ConverterClass(BlobStorageTypeConverter.class) 18 | BlobStorageType getBlobStorageType(); 19 | 20 | @Key("DLQ_RETRY_MAX_ATTEMPTS") 21 | @DefaultValue("2147483647") 22 | Integer getDlqRetryMaxAttempts(); 23 | 24 | @Key("DLQ_RETRY_FAIL_AFTER_MAX_ATTEMPT_ENABLE") 25 | @DefaultValue("false") 26 | boolean getDlqRetryFailAfterMaxAttemptEnable(); 27 | 28 | @Key("DLQ_SINK_ENABLE") 29 | @DefaultValue("false") 30 | boolean getDlqSinkEnable(); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/DlqKafkaProducerConfig.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | public interface DlqKafkaProducerConfig extends DlqConfig { 4 | 5 | @Key("DLQ_KAFKA_ACKS") 6 | @DefaultValue("all") 7 | String getDlqKafkaAcks(); 8 | 9 | @Key("DLQ_KAFKA_RETRIES") 10 | @DefaultValue("2147483647") 11 | String getDlqKafkaRetries(); 12 | 13 | @Key("DLQ_KAFKA_BATCH_SIZE") 14 | @DefaultValue("16384") 15 | String getDlqKafkaBatchSize(); 16 | 17 | @Key("DLQ_KAFKA_LINGER_MS") 18 | @DefaultValue("0") 19 | String getDlqKafkaLingerMs(); 20 | 21 | @Key("DLQ_KAFKA_BUFFER_MEMORY") 22 | @DefaultValue("33554432") 23 | String getDlqKafkaBufferMemory(); 24 | 25 | @Key("DLQ_KAFKA_KEY_SERIALIZER") 26 | @DefaultValue("org.apache.kafka.common.serialization.ByteArraySerializer") 27 | String getDlqKafkaKeySerializer(); 28 | 29 | @Key("DLQ_KAFKA_VALUE_SERIALIZER") 30 | @DefaultValue("org.apache.kafka.common.serialization.ByteArraySerializer") 31 | String getDlqKafkaValueSerializer(); 32 | 33 | @Key("DLQ_KAFKA_BROKERS") 34 | String getDlqKafkaBrokers(); 35 | 36 | @Key("DLQ_KAFKA_TOPIC") 37 | @DefaultValue("firehose-retry-topic") 38 | String getDlqKafkaTopic(); 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/ErrorConfig.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | import org.raystack.firehose.config.converter.SetErrorTypeConverter; 4 | import org.raystack.depot.error.ErrorType; 5 | import org.aeonbits.owner.Config; 6 | import org.aeonbits.owner.Mutable; 7 | 8 | import java.util.Set; 9 | 10 | public interface ErrorConfig extends Config, Mutable { 11 | 12 | @ConverterClass(SetErrorTypeConverter.class) 13 | @Key("ERROR_TYPES_FOR_DLQ") 14 | @Separator(",") 15 | @DefaultValue("") 16 | Set getErrorTypesForDLQ(); 17 | 18 | @ConverterClass(SetErrorTypeConverter.class) 19 | @Key("ERROR_TYPES_FOR_RETRY") 20 | @Separator(",") 21 | @DefaultValue("DEFAULT_ERROR") 22 | Set getErrorTypesForRetry(); 23 | 24 | @ConverterClass(SetErrorTypeConverter.class) 25 | @Key("ERROR_TYPES_FOR_FAILING") 26 | @Separator(",") 27 | @DefaultValue("DESERIALIZATION_ERROR,INVALID_MESSAGE_ERROR,UNKNOWN_FIELDS_ERROR") 28 | Set getErrorTypesForFailing(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/EsSinkConfig.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | import org.raystack.firehose.config.converter.EsSinkMessageTypeConverter; 4 | import org.raystack.firehose.config.enums.EsSinkMessageType; 5 | 6 | 7 | public interface EsSinkConfig extends AppConfig { 8 | 9 | @Key("SINK_ES_SHARDS_ACTIVE_WAIT_COUNT") 10 | @DefaultValue("1") 11 | Integer getSinkEsShardsActiveWaitCount(); 12 | 13 | @Key("SINK_ES_REQUEST_TIMEOUT_MS") 14 | @DefaultValue("60000") 15 | Long getSinkEsRequestTimeoutMs(); 16 | 17 | @Key("SINK_ES_RETRY_STATUS_CODE_BLACKLIST") 18 | @DefaultValue("404") 19 | String getSinkEsRetryStatusCodeBlacklist(); 20 | 21 | @Key("SINK_ES_CONNECTION_URLS") 22 | String getSinkEsConnectionUrls(); 23 | 24 | @Key("SINK_ES_INDEX_NAME") 25 | String getSinkEsIndexName(); 26 | 27 | @Key("SINK_ES_TYPE_NAME") 28 | String getSinkEsTypeName(); 29 | 30 | @Key("SINK_ES_ID_FIELD") 31 | String getSinkEsIdField(); 32 | 33 | @Key("SINK_ES_MODE_UPDATE_ONLY_ENABLE") 34 | @DefaultValue("false") 35 | Boolean isSinkEsModeUpdateOnlyEnable(); 36 | 37 | @Key("SINK_ES_INPUT_MESSAGE_TYPE") 38 | @ConverterClass(EsSinkMessageTypeConverter.class) 39 | @DefaultValue("JSON") 40 | EsSinkMessageType getSinkEsInputMessageType(); 41 | 42 | @Key("SINK_ES_PRESERVE_PROTO_FIELD_NAMES_ENABLE") 43 | @DefaultValue("true") 44 | Boolean isSinkEsPreserveProtoFieldNamesEnable(); 45 | 46 | @Key("SINK_ES_ROUTING_KEY_NAME") 47 | String getSinkEsRoutingKeyName(); 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/FilterConfig.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | import org.raystack.firehose.config.converter.FilterDataSourceTypeConverter; 4 | import org.raystack.firehose.config.converter.FilterEngineTypeConverter; 5 | import org.raystack.firehose.config.converter.FilterMessageFormatTypeConverter; 6 | import org.raystack.firehose.config.enums.FilterDataSourceType; 7 | import org.raystack.firehose.config.enums.FilterEngineType; 8 | import org.raystack.firehose.config.enums.FilterMessageFormatType; 9 | import org.aeonbits.owner.Config; 10 | 11 | public interface FilterConfig extends Config { 12 | 13 | @Key("FILTER_ENGINE") 14 | @ConverterClass(FilterEngineTypeConverter.class) 15 | @DefaultValue("NO_OP") 16 | FilterEngineType getFilterEngine(); 17 | 18 | @Key("FILTER_SCHEMA_PROTO_CLASS") 19 | String getFilterSchemaProtoClass(); 20 | 21 | @Key("FILTER_ESB_MESSAGE_FORMAT") 22 | @ConverterClass(FilterMessageFormatTypeConverter.class) 23 | FilterMessageFormatType getFilterESBMessageFormat(); 24 | 25 | @Key("FILTER_DATA_SOURCE") 26 | @ConverterClass(FilterDataSourceTypeConverter.class) 27 | FilterDataSourceType getFilterDataSource(); 28 | 29 | @Key("FILTER_JEXL_EXPRESSION") 30 | String getFilterJexlExpression(); 31 | 32 | @Key("FILTER_JSON_SCHEMA") 33 | String getFilterJsonSchema(); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/GrpcSinkConfig.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | import org.aeonbits.owner.Config; 4 | 5 | 6 | public interface GrpcSinkConfig extends AppConfig { 7 | 8 | @Config.Key("SINK_GRPC_SERVICE_HOST") 9 | String getSinkGrpcServiceHost(); 10 | 11 | @Config.Key("SINK_GRPC_SERVICE_PORT") 12 | Integer getSinkGrpcServicePort(); 13 | 14 | @Config.Key("SINK_GRPC_METHOD_URL") 15 | String getSinkGrpcMethodUrl(); 16 | 17 | @Config.Key("SINK_GRPC_RESPONSE_SCHEMA_PROTO_CLASS") 18 | String getSinkGrpcResponseSchemaProtoClass(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/InfluxSinkConfig.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | import org.raystack.firehose.config.converter.ProtoIndexToFieldMapConverter; 4 | import org.aeonbits.owner.Config; 5 | 6 | import java.util.Properties; 7 | 8 | public interface InfluxSinkConfig extends AppConfig { 9 | @Config.Key("SINK_INFLUX_FIELD_NAME_PROTO_INDEX_MAPPING") 10 | @Config.ConverterClass(ProtoIndexToFieldMapConverter.class) 11 | Properties getSinkInfluxFieldNameProtoIndexMapping(); 12 | 13 | @Config.Key("SINK_INFLUX_TAG_NAME_PROTO_INDEX_MAPPING") 14 | @Config.ConverterClass(ProtoIndexToFieldMapConverter.class) 15 | Properties getSinkInfluxTagNameProtoIndexMapping(); 16 | 17 | @Config.Key("SINK_INFLUX_MEASUREMENT_NAME") 18 | String getSinkInfluxMeasurementName(); 19 | 20 | @Config.Key("SINK_INFLUX_PROTO_EVENT_TIMESTAMP_INDEX") 21 | Integer getSinkInfluxProtoEventTimestampIndex(); 22 | 23 | @Config.Key("SINK_INFLUX_DB_NAME") 24 | String getSinkInfluxDbName(); 25 | 26 | @Config.Key("SINK_INFLUX_RETENTION_POLICY") 27 | @DefaultValue("autogen") 28 | String getSinkInfluxRetentionPolicy(); 29 | 30 | @Config.Key("SINK_INFLUX_URL") 31 | String getSinkInfluxUrl(); 32 | 33 | @Config.Key("SINK_INFLUX_USERNAME") 34 | String getSinkInfluxUsername(); 35 | 36 | @Config.Key("SINK_INFLUX_PASSWORD") 37 | String getSinkInfluxPassword(); 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/JdbcSinkConfig.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | public interface JdbcSinkConfig extends AppConfig { 4 | 5 | @Key("SINK_JDBC_URL") 6 | String getSinkJdbcUrl(); 7 | 8 | @Key("SINK_JDBC_USERNAME") 9 | String getSinkJdbcUsername(); 10 | 11 | @Key("SINK_JDBC_PASSWORD") 12 | String getSinkJdbcPassword(); 13 | 14 | @Key("SINK_JDBC_TABLE_NAME") 15 | String getSinkJdbcTableName(); 16 | 17 | @Key("SINK_JDBC_UNIQUE_KEYS") 18 | @DefaultValue("") 19 | String getSinkJdbcUniqueKeys(); 20 | 21 | @Key("SINK_JDBC_CONNECTION_POOL_MAX_SIZE") 22 | Integer getSinkJdbcConnectionPoolMaxSize(); 23 | 24 | @Key("SINK_JDBC_CONNECTION_POOL_TIMEOUT_MS") 25 | Integer getSinkJdbcConnectionPoolTimeoutMs(); 26 | 27 | @Key("SINK_JDBC_CONNECTION_POOL_IDLE_TIMEOUT_MS") 28 | Integer getSinkJdbcConnectionPoolIdleTimeoutMs(); 29 | 30 | @Key("SINK_JDBC_CONNECTION_POOL_MIN_IDLE") 31 | Integer getSinkJdbcConnectionPoolMinIdle(); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/MongoSinkConfig.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | 4 | import org.raystack.firehose.config.converter.MongoSinkMessageTypeConverter; 5 | import org.raystack.firehose.config.enums.MongoSinkMessageType; 6 | 7 | public interface MongoSinkConfig extends AppConfig { 8 | 9 | @Key("SINK_MONGO_CONNECT_TIMEOUT_MS") 10 | @DefaultValue("30000") 11 | int getSinkMongoConnectTimeoutMs(); 12 | 13 | @Key("SINK_MONGO_CONNECTION_URLS") 14 | String getSinkMongoConnectionUrls(); 15 | 16 | @Key("SINK_MONGO_DB_NAME") 17 | String getSinkMongoDBName(); 18 | 19 | @Key("SINK_MONGO_RETRY_STATUS_CODE_BLACKLIST") 20 | @DefaultValue("11000") 21 | String getSinkMongoRetryStatusCodeBlacklist(); 22 | 23 | @Key("SINK_MONGO_PRESERVE_PROTO_FIELD_NAMES_ENABLE") 24 | @DefaultValue("true") 25 | Boolean isSinkMongoPreserveProtoFieldNamesEnable(); 26 | 27 | @Key("SINK_MONGO_AUTH_ENABLE") 28 | @DefaultValue("false") 29 | Boolean isSinkMongoAuthEnable(); 30 | 31 | @Key("SINK_MONGO_AUTH_USERNAME") 32 | String getSinkMongoAuthUsername(); 33 | 34 | @Key("SINK_MONGO_AUTH_PASSWORD") 35 | String getSinkMongoAuthPassword(); 36 | 37 | @Key("SINK_MONGO_AUTH_DB") 38 | String getSinkMongoAuthDB(); 39 | 40 | @Key("SINK_MONGO_INPUT_MESSAGE_TYPE") 41 | @ConverterClass(MongoSinkMessageTypeConverter.class) 42 | @DefaultValue("JSON") 43 | MongoSinkMessageType getSinkMongoInputMessageType(); 44 | 45 | @Key("SINK_MONGO_COLLECTION_NAME") 46 | String getSinkMongoCollectionName(); 47 | 48 | @Key("SINK_MONGO_PRIMARY_KEY") 49 | String getSinkMongoPrimaryKey(); 50 | 51 | @Key("SINK_MONGO_MODE_UPDATE_ONLY_ENABLE") 52 | @DefaultValue("false") 53 | Boolean isSinkMongoModeUpdateOnlyEnable(); 54 | 55 | @Key("SINK_MONGO_SERVER_SELECT_TIMEOUT_MS") 56 | @DefaultValue("30000") 57 | int getSinkMongoServerSelectTimeoutMs(); 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/PromSinkConfig.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | import org.raystack.firehose.config.converter.ProtoIndexToFieldMapConverter; 4 | import org.raystack.firehose.config.converter.RangeToHashMapConverter; 5 | 6 | import java.util.Map; 7 | import java.util.Properties; 8 | 9 | public interface PromSinkConfig extends AppConfig { 10 | 11 | @Key("SINK_PROM_RETRY_STATUS_CODE_RANGES") 12 | @DefaultValue("400-600") 13 | @ConverterClass(RangeToHashMapConverter.class) 14 | Map getSinkPromRetryStatusCodeRanges(); 15 | 16 | @Key("SINK_PROM_REQUEST_LOG_STATUS_CODE_RANGES") 17 | @DefaultValue("400-499") 18 | @ConverterClass(RangeToHashMapConverter.class) 19 | Map getSinkPromRequestLogStatusCodeRanges(); 20 | 21 | @Key("SINK_PROM_REQUEST_TIMEOUT_MS") 22 | @DefaultValue("10000") 23 | Integer getSinkPromRequestTimeoutMs(); 24 | 25 | @Key("SINK_PROM_MAX_CONNECTIONS") 26 | Integer getSinkPromMaxConnections(); 27 | 28 | @Key("SINK_PROM_SERVICE_URL") 29 | String getSinkPromServiceUrl(); 30 | 31 | @Key("SINK_PROM_HEADERS") 32 | @DefaultValue("") 33 | String getSinkPromHeaders(); 34 | 35 | @Key("SINK_PROM_METRIC_NAME_PROTO_INDEX_MAPPING") 36 | @ConverterClass(ProtoIndexToFieldMapConverter.class) 37 | Properties getSinkPromMetricNameProtoIndexMapping(); 38 | 39 | @Key("SINK_PROM_LABEL_NAME_PROTO_INDEX_MAPPING") 40 | @ConverterClass(ProtoIndexToFieldMapConverter.class) 41 | Properties getSinkPromLabelNameProtoIndexMapping(); 42 | 43 | @Key("SINK_PROM_PROTO_EVENT_TIMESTAMP_INDEX") 44 | @DefaultValue("1") 45 | Integer getSinkPromProtoEventTimestampIndex(); 46 | 47 | @Key("SINK_PROM_WITH_EVENT_TIMESTAMP") 48 | @DefaultValue("false") 49 | boolean isEventTimestampEnabled(); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/S3Config.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | import org.aeonbits.owner.Config; 4 | 5 | public interface S3Config extends Config { 6 | @Key("${S3_TYPE}_S3_REGION") 7 | String getS3Region(); 8 | 9 | @Key("${S3_TYPE}_S3_BUCKET_NAME") 10 | String getS3BucketName(); 11 | 12 | @Key("${S3_TYPE}_S3_ACCESS_KEY") 13 | String getS3AccessKey(); 14 | 15 | @Key("${S3_TYPE}_S3_SECRET_KEY") 16 | String getS3SecretKey(); 17 | 18 | @Key("${S3_TYPE}_S3_RETRY_MAX_ATTEMPTS") 19 | @DefaultValue("10") 20 | Integer getS3RetryMaxAttempts(); 21 | 22 | @Key("${S3_TYPE}_S3_BASE_DELAY_MS") 23 | @DefaultValue("1000") 24 | Long getS3BaseDelay(); 25 | 26 | @Key("${S3_TYPE}_S3_MAX_BACKOFF_MS") 27 | @DefaultValue("30000") 28 | Long getS3MaxBackoff(); 29 | 30 | @Key("${S3_TYPE}_S3_API_ATTEMPT_TIMEOUT_MS") 31 | @DefaultValue("10000") 32 | Long getS3ApiAttemptTimeout(); 33 | 34 | @Key("${S3_TYPE}_S3_API_TIMEOUT_MS") 35 | @DefaultValue("40000") 36 | Long getS3ApiTimeout(); 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/SinkPoolConfig.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | import org.aeonbits.owner.Config; 4 | 5 | public interface SinkPoolConfig extends AppConfig { 6 | @Config.Key("SINK_POOL_NUM_THREADS") 7 | @Config.DefaultValue("1") 8 | int getSinkPoolNumThreads(); 9 | 10 | @Config.Key("SINK_POOL_QUEUE_POLL_TIMEOUT_MS") 11 | @Config.DefaultValue("1000") 12 | int getSinkPoolQueuePollTimeoutMS(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/BlobSinkFilePartitionTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.sink.blob.Constants; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class BlobSinkFilePartitionTypeConverter implements Converter { 9 | @Override 10 | public Constants.FilePartitionType convert(Method method, String input) { 11 | return Constants.FilePartitionType.valueOf(input.toUpperCase()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/BlobSinkLocalFileWriterTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.sink.blob.Constants; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class BlobSinkLocalFileWriterTypeConverter implements Converter { 9 | @Override 10 | public Constants.WriterType convert(Method method, String input) { 11 | return Constants.WriterType.valueOf(input.toUpperCase()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/BlobStorageTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.sink.common.blobstorage.BlobStorageType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class BlobStorageTypeConverter implements Converter { 9 | @Override 10 | public BlobStorageType convert(Method method, String input) { 11 | return BlobStorageType.valueOf(input.toUpperCase()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/ConsumerModeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.KafkaConsumerMode; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class ConsumerModeConverter implements Converter { 9 | @Override 10 | public KafkaConsumerMode convert(Method method, String input) { 11 | return KafkaConsumerMode.valueOf(input.toUpperCase()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/DlqWriterTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.sink.dlq.DLQWriterType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class DlqWriterTypeConverter implements Converter { 9 | @Override 10 | public DLQWriterType convert(Method method, String input) { 11 | return DLQWriterType.valueOf(input.toUpperCase()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/EsSinkMessageTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.EsSinkMessageType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class EsSinkMessageTypeConverter implements Converter { 9 | @Override 10 | public EsSinkMessageType convert(Method method, String input) { 11 | return EsSinkMessageType.valueOf(input.toUpperCase()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/FilterDataSourceTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.FilterDataSourceType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class FilterDataSourceTypeConverter implements Converter { 9 | @Override 10 | public FilterDataSourceType convert(Method method, String input) { 11 | try { 12 | return FilterDataSourceType.valueOf(input.toUpperCase()); 13 | } catch (IllegalArgumentException e) { 14 | throw new IllegalArgumentException("FILTER_DATA_SOURCE must be or KEY or MESSAGE", e); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/FilterEngineTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.FilterEngineType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class FilterEngineTypeConverter implements Converter { 9 | @Override 10 | public FilterEngineType convert(Method method, String input) { 11 | try { 12 | return FilterEngineType.valueOf(input.toUpperCase()); 13 | } catch (IllegalArgumentException e) { 14 | throw new IllegalArgumentException("FILTER_ENGINE must be JSON or JEXL or NOOP", e); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/FilterMessageFormatTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.FilterMessageFormatType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class FilterMessageFormatTypeConverter implements Converter { 9 | @Override 10 | public FilterMessageFormatType convert(Method method, String input) { 11 | try { 12 | return FilterMessageFormatType.valueOf(input.toUpperCase()); 13 | } catch (IllegalArgumentException e) { 14 | throw new IllegalArgumentException("FILTER_INPUT_MESSAGE_TYPE must be JSON or PROTOBUF"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/HttpSinkDataFormatTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.HttpSinkDataFormatType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class HttpSinkDataFormatTypeConverter implements Converter { 9 | @Override 10 | public HttpSinkDataFormatType convert(Method method, String input) { 11 | return HttpSinkDataFormatType.valueOf(input.toUpperCase()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/HttpSinkParameterDataFormatConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.HttpSinkDataFormatType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | 9 | public class HttpSinkParameterDataFormatConverter implements Converter { 10 | @Override 11 | public HttpSinkDataFormatType convert(Method method, String input) { 12 | return HttpSinkDataFormatType.valueOf(input.toUpperCase()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/HttpSinkParameterPlacementTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.HttpSinkParameterPlacementType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class HttpSinkParameterPlacementTypeConverter implements Converter { 9 | @Override 10 | public HttpSinkParameterPlacementType convert(Method method, String input) { 11 | return HttpSinkParameterPlacementType.valueOf(input.toUpperCase()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/HttpSinkParameterSourceTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.HttpSinkParameterSourceType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class HttpSinkParameterSourceTypeConverter implements Converter { 9 | @Override 10 | public HttpSinkParameterSourceType convert(Method method, String input) { 11 | return HttpSinkParameterSourceType.valueOf(input.toUpperCase()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/HttpSinkRequestMethodConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.HttpSinkRequestMethodType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | 9 | public class HttpSinkRequestMethodConverter implements Converter { 10 | @Override 11 | public HttpSinkRequestMethodType convert(Method method, String input) { 12 | return HttpSinkRequestMethodType.valueOf(input.toUpperCase()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/InputSchemaTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.InputSchemaType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class InputSchemaTypeConverter implements Converter { 9 | @Override 10 | public InputSchemaType convert(Method method, String input) { 11 | return InputSchemaType.valueOf(input.trim().toUpperCase()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/LabelMapConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.aeonbits.owner.Converter; 4 | import java.lang.reflect.Method; 5 | import java.util.LinkedHashMap; 6 | import java.util.Map; 7 | 8 | public class LabelMapConverter implements Converter> { 9 | public static final String ELEMENT_SEPARATOR = ","; 10 | private static final String VALUE_SEPARATOR = "="; 11 | private static final int MAX_LENGTH = 63; 12 | 13 | public Map convert(Method method, String input) { 14 | Map result = new LinkedHashMap<>(); 15 | String[] chunks = input.split(ELEMENT_SEPARATOR, -1); 16 | for (String chunk : chunks) { 17 | String[] entry = chunk.split(VALUE_SEPARATOR, -1); 18 | if (entry.length <= 1) { 19 | continue; 20 | } 21 | String key = entry[0].trim(); 22 | if (key.isEmpty()) { 23 | continue; 24 | } 25 | 26 | String value = entry[1].trim(); 27 | value = value.length() > MAX_LENGTH ? value.substring(0, MAX_LENGTH) : value; 28 | result.put(key, value); 29 | } 30 | return result; 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/MongoSinkMessageTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.MongoSinkMessageType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class MongoSinkMessageTypeConverter implements Converter { 9 | @Override 10 | public MongoSinkMessageType convert(Method method, String input) { 11 | try { 12 | return MongoSinkMessageType.valueOf(input.toUpperCase()); 13 | } catch (IllegalArgumentException e) { 14 | throw new IllegalArgumentException("SINK_MONGO_INPUT_MESSAGE_TYPE must be JSON or PROTOBUF"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/RangeToHashMapConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.aeonbits.owner.Converter; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.Arrays; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.IntStream; 12 | 13 | public class RangeToHashMapConverter implements Converter> { 14 | 15 | @Override 16 | public Map convert(Method method, String input) { 17 | String[] ranges = input.split(","); 18 | Map statusMap = new HashMap(); 19 | 20 | Arrays.stream(ranges).forEach(range -> { 21 | List rangeList = Arrays.stream(range.split("-")).map(Integer::parseInt).collect(Collectors.toList()); 22 | IntStream.rangeClosed(rangeList.get(0), rangeList.get(1)).forEach(statusCode -> statusMap.put(statusCode, true)); 23 | }); 24 | return statusMap; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/SchemaRegistryHeadersConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.regex.Pattern; 5 | import java.util.stream.Collectors; 6 | 7 | import org.aeonbits.owner.Converter; 8 | import org.aeonbits.owner.Tokenizer; 9 | import org.apache.http.Header; 10 | import org.apache.http.message.BasicHeader; 11 | 12 | public class SchemaRegistryHeadersConverter implements Converter
, Tokenizer { 13 | 14 | @Override 15 | public Header convert(Method method, String input) { 16 | String[] split = input.split(":"); 17 | return new BasicHeader(split[0].trim(), split[1].trim()); 18 | } 19 | 20 | @Override 21 | public String[] tokens(String values) { 22 | String[] headers = Pattern.compile(",").splitAsStream(values).map(String::trim) 23 | .filter(s -> { 24 | String[] args = s.split(":"); 25 | return args.length == 2 && args[0].trim().length() > 0 && args[1].trim().length() > 0; 26 | }) 27 | .collect(Collectors.toList()) 28 | .toArray(new String[0]); 29 | if (headers.length == 0) { 30 | throw new IllegalArgumentException(String.format("provided headers %s is not valid", values)); 31 | } 32 | 33 | return headers; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/SchemaRegistryRefreshConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | import org.aeonbits.owner.Converter; 6 | 7 | import org.raystack.stencil.cache.SchemaRefreshStrategy; 8 | 9 | public class SchemaRegistryRefreshConverter implements Converter { 10 | 11 | @Override 12 | public SchemaRefreshStrategy convert(Method method, String input) { 13 | if ("VERSION_BASED_REFRESH".equalsIgnoreCase(input)) { 14 | return SchemaRefreshStrategy.versionBasedRefresh(); 15 | } 16 | return SchemaRefreshStrategy.longPollingStrategy(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/SetErrorTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.depot.error.ErrorType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class SetErrorTypeConverter implements Converter { 9 | @Override 10 | public ErrorType convert(Method method, String input) { 11 | return ErrorType.valueOf(input); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/converter/SinkTypeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.SinkType; 4 | import org.aeonbits.owner.Converter; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | public class SinkTypeConverter implements Converter { 9 | @Override 10 | public SinkType convert(Method method, String input) { 11 | return SinkType.valueOf(input.toUpperCase()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/EsSinkMessageType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum EsSinkMessageType { 4 | JSON, PROTOBUF 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/EsSinkRequestType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum EsSinkRequestType { 4 | UPDATE_ONLY, 5 | INSERT_OR_UPDATE 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/FilterDataSourceType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum FilterDataSourceType { 4 | KEY, MESSAGE 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/FilterEngineType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum FilterEngineType { 4 | JEXL, JSON, NO_OP 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/FilterMessageFormatType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum FilterMessageFormatType { 4 | JSON, PROTOBUF 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/HttpSinkDataFormatType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum HttpSinkDataFormatType { 4 | PROTO, 5 | JSON 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/HttpSinkParameterPlacementType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum HttpSinkParameterPlacementType { 4 | QUERY, 5 | HEADER 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/HttpSinkParameterSourceType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum HttpSinkParameterSourceType { 4 | KEY, 5 | MESSAGE, 6 | DISABLED 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/HttpSinkRequestMethodType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum HttpSinkRequestMethodType { 4 | PUT, 5 | POST, 6 | PATCH, 7 | DELETE 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/InputSchemaType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum InputSchemaType { 4 | PROTOBUF, 5 | JSON 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/KafkaConsumerMode.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum KafkaConsumerMode { 4 | ASYNC, 5 | SYNC 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/MongoSinkMessageType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum MongoSinkMessageType { 4 | JSON, PROTOBUF 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/MongoSinkRequestType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum MongoSinkRequestType { 4 | UPDATE_ONLY, 5 | UPSERT 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/config/enums/SinkType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.enums; 2 | 3 | public enum SinkType { 4 | JDBC, 5 | HTTP, 6 | LOG, 7 | CLEVERTAP, 8 | INFLUXDB, 9 | ELASTICSEARCH, 10 | REDIS, 11 | GRPC, 12 | PROMETHEUS, 13 | BLOB, 14 | BIGQUERY, 15 | BIGTABLE, 16 | MONGODB 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/consumer/FirehoseConsumer.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.consumer; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | 6 | public interface FirehoseConsumer extends Closeable { 7 | 8 | void process() throws IOException; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/consumer/FirehoseFilter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.consumer; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import org.raystack.firehose.filter.Filter; 5 | import org.raystack.firehose.filter.FilterException; 6 | import org.raystack.firehose.filter.FilteredMessages; 7 | import org.raystack.firehose.metrics.FirehoseInstrumentation; 8 | import org.raystack.firehose.metrics.Metrics; 9 | import lombok.AllArgsConstructor; 10 | 11 | import java.util.List; 12 | 13 | @AllArgsConstructor 14 | public class FirehoseFilter { 15 | private final Filter filter; 16 | private final FirehoseInstrumentation firehoseInstrumentation; 17 | 18 | public FilteredMessages applyFilter(List messages) throws FilterException { 19 | FilteredMessages filteredMessage = filter.filter(messages); 20 | int filteredMessageCount = filteredMessage.sizeOfInvalidMessages(); 21 | if (filteredMessageCount > 0) { 22 | firehoseInstrumentation.captureFilteredMessageCount(filteredMessageCount); 23 | firehoseInstrumentation.captureGlobalMessageMetrics(Metrics.MessageScope.FILTERED, filteredMessageCount); 24 | } 25 | return filteredMessage; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/consumer/kafka/OffsetNode.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.consumer.kafka; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import org.apache.kafka.clients.consumer.OffsetAndMetadata; 6 | import org.apache.kafka.common.TopicPartition; 7 | 8 | @Data 9 | @AllArgsConstructor 10 | public class OffsetNode { 11 | private TopicPartition topicPartition; 12 | private OffsetAndMetadata offsetAndMetadata; 13 | private boolean isCommittable; 14 | private boolean isRemovable; 15 | 16 | public OffsetNode(TopicPartition topicPartition, OffsetAndMetadata offsetAndMetadata) { 17 | this(topicPartition, offsetAndMetadata, false, false); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/converter/ProtoTimeConverter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.converter; 2 | 3 | import com.google.protobuf.Timestamp; 4 | 5 | import java.util.concurrent.TimeUnit; 6 | 7 | public class ProtoTimeConverter { 8 | 9 | public static long getEpochTimeInNanoSeconds(long timeInSeconds, int nanosOffset) { 10 | return TimeUnit.NANOSECONDS.convert(timeInSeconds, TimeUnit.SECONDS) + nanosOffset; 11 | } 12 | 13 | public static long getEpochTimeInNanoSeconds(Timestamp time) { 14 | return getEpochTimeInNanoSeconds(time.getSeconds(), time.getNanos()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/error/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.error; 2 | 3 | import org.raystack.firehose.config.ErrorConfig; 4 | import org.raystack.firehose.message.Message; 5 | import org.raystack.depot.error.ErrorType; 6 | import lombok.AllArgsConstructor; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.stream.Collectors; 11 | 12 | 13 | /** 14 | * Error handler for messages. 15 | */ 16 | @AllArgsConstructor 17 | public class ErrorHandler { 18 | 19 | private final ErrorConfig config; 20 | 21 | /** 22 | * @param message Message to filter 23 | * @param scope scope of the error 24 | * @return true 25 | * if no error info is present and 26 | * if error type matches the scope 27 | */ 28 | public boolean filter(Message message, ErrorScope scope) { 29 | if (message.getErrorInfo() == null) { 30 | return scope.equals(ErrorScope.RETRY); 31 | } 32 | ErrorType type = message.getErrorInfo().getErrorType(); 33 | switch (scope) { 34 | case DLQ: 35 | return config.getErrorTypesForDLQ().contains(type); 36 | case FAIL: 37 | return config.getErrorTypesForFailing().contains(type); 38 | case RETRY: 39 | return config.getErrorTypesForRetry().contains(type); 40 | default: 41 | throw new IllegalArgumentException("Unknown Error Scope"); 42 | } 43 | } 44 | 45 | public Map> split(List messages, ErrorScope scope) { 46 | return messages.stream().collect(Collectors.partitioningBy(m -> filter(m, scope))); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/error/ErrorScope.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.error; 2 | 3 | public enum ErrorScope { 4 | DLQ, 5 | RETRY, 6 | FAIL 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/exception/ConfigurationException.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.exception; 2 | 3 | /** 4 | * This exception is thrown when there is invalid configuration encountered. 5 | */ 6 | public class ConfigurationException extends RuntimeException { 7 | 8 | public ConfigurationException(String message) { 9 | super(message); 10 | } 11 | 12 | public ConfigurationException(String message, Exception e) { 13 | super(message, e); 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/exception/DefaultException.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.exception; 2 | 3 | import lombok.EqualsAndHashCode; 4 | 5 | @EqualsAndHashCode(callSuper = false) 6 | public class DefaultException extends Exception { 7 | public DefaultException(String message) { 8 | super(message); 9 | } 10 | 11 | @Override 12 | public String toString() { 13 | return getMessage(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/exception/DeserializerException.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.exception; 2 | 3 | /** 4 | * Deserializer exception is thrown when message from proto is not deserializable into the Java object. 5 | */ 6 | public class DeserializerException extends RuntimeException { 7 | 8 | public DeserializerException(String message) { 9 | super(message); 10 | } 11 | 12 | public DeserializerException(String message, Exception e) { 13 | super(message, e); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/exception/EmptyMessageException.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.exception; 2 | 3 | /** 4 | * Empty thrown when the message is contains zero bytes. 5 | */ 6 | public class EmptyMessageException extends DeserializerException { 7 | public EmptyMessageException() { 8 | super("log message is empty"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/exception/FirehoseConsumerFailedException.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.exception; 2 | 3 | public class FirehoseConsumerFailedException extends RuntimeException { 4 | public FirehoseConsumerFailedException(Throwable th) { 5 | super(th); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/exception/JsonParseException.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.exception; 2 | 3 | public class JsonParseException extends RuntimeException { 4 | public JsonParseException(String message, Throwable cause) { 5 | super(message, cause); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/exception/NeedToRetry.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.exception; 2 | 3 | public class NeedToRetry extends Exception { 4 | public NeedToRetry(String statusCode) { 5 | super(String.format("Status code fall under retry range. StatusCode: %s", statusCode)); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/exception/OAuth2Exception.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.exception; 2 | 3 | import java.io.IOException; 4 | 5 | public class OAuth2Exception extends IOException { 6 | public OAuth2Exception(String message) { 7 | super(message); 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/exception/SinkException.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.exception; 2 | 3 | public class SinkException extends RuntimeException { 4 | public SinkException(String message, Throwable cause) { 5 | super(message, cause); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/exception/SinkTaskFailedException.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.exception; 2 | 3 | public class SinkTaskFailedException extends RuntimeException { 4 | public SinkTaskFailedException(Throwable throwable) { 5 | super(throwable); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/exception/UnknownFieldsException.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.exception; 2 | 3 | 4 | import com.google.protobuf.DynamicMessage; 5 | 6 | /** 7 | * UnknownFieldsException is thrown when unknown fields is detected on the log message although the proto message was succesfuly parsed. 8 | * Unknown fields error can happen because multiple causes, and can be handled differently depends on the use case. 9 | * Unknown fields error by default should be handled by retry the processing because there is a probability that, message deserializer is not updated to the latest schema 10 | * When consumer is deliberately process message using different schema and intentionally ignore extra fields that missing from descriptor the error handling can be disabled. 11 | * On some use case that need zero data loss, for example data warehousing unknown fields error should be handled properly to prevent missing fields. 12 | */ 13 | public class UnknownFieldsException extends DeserializerException { 14 | 15 | public UnknownFieldsException(DynamicMessage dynamicMessage) { 16 | super(String.format("unknown fields found, message : %s", dynamicMessage)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/filter/Filter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.filter; 2 | 3 | import org.raystack.firehose.message.Message; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Interface for filtering the messages. 9 | */ 10 | public interface Filter { 11 | 12 | /** 13 | * The method used for filtering the messages. 14 | * 15 | * @param messages the protobuf records in binary format that are wrapped in {@link Message} 16 | * @return filtered messages. 17 | * @throws FilterException the filter exception 18 | */ 19 | FilteredMessages filter(List messages) throws FilterException; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/filter/FilterException.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.filter; 2 | 3 | public class FilterException extends Exception { 4 | 5 | public FilterException(String message, Exception e) { 6 | super(message, e); 7 | } 8 | 9 | public FilterException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/filter/FilteredMessages.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.filter; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Getter 11 | @EqualsAndHashCode 12 | public class FilteredMessages { 13 | private final List validMessages = new ArrayList<>(); 14 | private final List invalidMessages = new ArrayList<>(); 15 | 16 | public void addToValidMessages(Message message) { 17 | validMessages.add(message); 18 | } 19 | 20 | public void addToInvalidMessages(Message message) { 21 | invalidMessages.add(message); 22 | } 23 | 24 | public int sizeOfValidMessages() { 25 | return validMessages.size(); 26 | } 27 | 28 | public int sizeOfInvalidMessages() { 29 | return invalidMessages.size(); 30 | } 31 | } 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/filter/NoOpFilter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.filter; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import org.raystack.firehose.metrics.FirehoseInstrumentation; 5 | 6 | import java.util.List; 7 | 8 | public class NoOpFilter implements Filter { 9 | 10 | public NoOpFilter(FirehoseInstrumentation firehoseInstrumentation) { 11 | firehoseInstrumentation.logInfo("No filter is selected"); 12 | } 13 | 14 | /** 15 | * The method used for filtering the messages. 16 | * 17 | * @param messages the protobuf records in binary format that are wrapped in {@link Message} 18 | * @return filtered messages. 19 | * @throws FilterException the filter exception 20 | */ 21 | @Override 22 | public FilteredMessages filter(List messages) throws FilterException { 23 | FilteredMessages filteredMessages = new FilteredMessages(); 24 | messages.forEach(filteredMessages::addToValidMessages); 25 | return filteredMessages; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/message/FirehoseMessageUtils.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.message; 2 | 3 | import org.raystack.depot.common.Tuple; 4 | 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | 8 | public class FirehoseMessageUtils { 9 | 10 | public static List convertToDepotMessage(List messages) { 11 | return messages.stream().map(message -> 12 | new org.raystack.depot.message.Message( 13 | message.getLogKey(), 14 | message.getLogMessage(), 15 | new Tuple<>("message_topic", message.getTopic()), 16 | new Tuple<>("message_partition", message.getPartition()), 17 | new Tuple<>("message_offset", message.getOffset()), 18 | new Tuple<>("message_headers", message.getHeaders()), 19 | new Tuple<>("message_timestamp", message.getTimestamp()), 20 | new Tuple<>("load_time", message.getConsumeTimestamp()))) 21 | .collect(Collectors.toList()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/metrics/BigQueryMetrics.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.metrics; 2 | 3 | public class BigQueryMetrics { 4 | public enum BigQueryAPIType { 5 | TABLE_UPDATE, 6 | TABLE_CREATE, 7 | DATASET_UPDATE, 8 | DATASET_CREATE, 9 | TABLE_INSERT_ALL, 10 | } 11 | 12 | public enum BigQueryErrorType { 13 | UNKNOWN_ERROR, 14 | INVALID_SCHEMA_ERROR, 15 | OOB_ERROR, 16 | STOPPED_ERROR, 17 | } 18 | 19 | public static final String BIGQUERY_SINK_PREFIX = "bigquery_"; 20 | public static final String BIGQUERY_TABLE_TAG = "table=%s"; 21 | public static final String BIGQUERY_DATASET_TAG = "dataset=%s"; 22 | public static final String BIGQUERY_API_TAG = "api=%s"; 23 | public static final String BIGQUERY_ERROR_TAG = "error=%s"; 24 | // BigQuery SINK MEASUREMENTS 25 | public static final String SINK_BIGQUERY_OPERATION_TOTAL = Metrics.APPLICATION_PREFIX + Metrics.SINK_PREFIX + BIGQUERY_SINK_PREFIX + "operation_total"; 26 | public static final String SINK_BIGQUERY_OPERATION_LATENCY_MILLISECONDS = Metrics.APPLICATION_PREFIX + Metrics.SINK_PREFIX + BIGQUERY_SINK_PREFIX + "operation_latency_milliseconds"; 27 | public static final String SINK_BIGQUERY_ERRORS_TOTAL = Metrics.APPLICATION_PREFIX + Metrics.SINK_PREFIX + BIGQUERY_SINK_PREFIX + "errors_total"; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/metrics/BlobStorageMetrics.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.metrics; 2 | 3 | 4 | public class BlobStorageMetrics { 5 | public static final String LOCAL_FILE_OPEN_TOTAL = Metrics.APPLICATION_PREFIX + Metrics.SINK_PREFIX + Metrics.BLOB_SINK_PREFIX + "local_file_open_total"; 6 | public static final String LOCAL_FILE_CLOSE_TOTAL = Metrics.APPLICATION_PREFIX + Metrics.SINK_PREFIX + Metrics.BLOB_SINK_PREFIX + "local_file_close_total"; 7 | public static final String LOCAL_FILE_RECORDS_TOTAL = Metrics.APPLICATION_PREFIX + Metrics.SINK_PREFIX + Metrics.BLOB_SINK_PREFIX + "local_file_records_total"; 8 | public static final String LOCAL_FILE_CLOSING_TIME_MILLISECONDS = Metrics.APPLICATION_PREFIX + Metrics.SINK_PREFIX + Metrics.BLOB_SINK_PREFIX + "local_file_closing_time_milliseconds"; 9 | public static final String LOCAL_FILE_SIZE_BYTES = Metrics.APPLICATION_PREFIX + Metrics.SINK_PREFIX + Metrics.BLOB_SINK_PREFIX + "local_file_size_bytes"; 10 | public static final String FILE_UPLOAD_TIME_MILLISECONDS = Metrics.APPLICATION_PREFIX + Metrics.SINK_PREFIX + Metrics.BLOB_SINK_PREFIX + "remote_file_upload_time_milliseconds"; 11 | public static final String FILE_UPLOAD_TOTAL = Metrics.APPLICATION_PREFIX + Metrics.SINK_PREFIX + Metrics.BLOB_SINK_PREFIX + "remote_file_upload_total"; 12 | public static final String FILE_UPLOAD_BYTES = Metrics.APPLICATION_PREFIX + Metrics.SINK_PREFIX + Metrics.BLOB_SINK_PREFIX + "remote_file_upload_bytes"; 13 | public static final String FILE_UPLOAD_RECORDS_TOTAL = Metrics.APPLICATION_PREFIX + Metrics.SINK_PREFIX + Metrics.BLOB_SINK_PREFIX + "remote_file_upload_records_total"; 14 | 15 | public static final String BLOB_STORAGE_ERROR_TYPE_TAG = "error_type"; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/parser/KafkaEnvironmentVariables.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.parser; 2 | 3 | import java.util.Collections; 4 | import java.util.Map; 5 | import java.util.stream.Collectors; 6 | 7 | /** 8 | * Kafka environment variables for firehose. 9 | */ 10 | public class KafkaEnvironmentVariables { 11 | 12 | private static final String KAFKA_PREFIX = "source_kafka_consumer_config_"; 13 | 14 | /** 15 | * Converts environment variables to hashmap. 16 | * 17 | * @param envVars the env vars 18 | * @return the map 19 | */ 20 | public static Map parse(Map envVars) { 21 | if (envVars == null || envVars.isEmpty()) { 22 | return Collections.emptyMap(); 23 | } 24 | Map kafkaEnvVars = envVars.entrySet() 25 | .stream() 26 | .filter(a -> a.getKey().toLowerCase().startsWith(KAFKA_PREFIX)) 27 | .collect(Collectors.toMap(e -> parseVarName(e.getKey()), e -> e.getValue())); 28 | return kafkaEnvVars; 29 | } 30 | 31 | private static String parseVarName(String varName) { 32 | String[] names = varName.toLowerCase().replaceAll(KAFKA_PREFIX, "").split("_"); 33 | return String.join(".", names); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/proto/ProtoUtils.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.proto; 2 | 3 | import com.google.protobuf.DynamicMessage; 4 | 5 | import java.util.Collections; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | import java.util.Queue; 9 | import java.util.stream.Collectors; 10 | 11 | public class ProtoUtils { 12 | public static boolean hasUnknownField(DynamicMessage root) { 13 | List dynamicMessageFields = collectNestedFields(root); 14 | List messageWithUnknownFields = getMessageWithUnknownFields(dynamicMessageFields); 15 | return messageWithUnknownFields.size() > 0; 16 | } 17 | 18 | private static List collectNestedFields(DynamicMessage node) { 19 | List output = new LinkedList<>(); 20 | Queue stack = Collections.asLifoQueue(new LinkedList<>()); 21 | stack.add(node); 22 | while (true) { 23 | DynamicMessage current = stack.poll(); 24 | if (current == null) { 25 | break; 26 | } 27 | List nestedChildNodes = current.getAllFields().values().stream() 28 | .filter(field -> field instanceof DynamicMessage) 29 | .map(field -> (DynamicMessage) field) 30 | .collect(Collectors.toList()); 31 | stack.addAll(nestedChildNodes); 32 | 33 | output.add(current); 34 | } 35 | 36 | return output; 37 | } 38 | 39 | private static List getMessageWithUnknownFields(List messages) { 40 | return messages.stream().filter(message -> message.getUnknownFields().asMap().size() > 0).collect(Collectors.toList()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/serializer/JsonWrappedProtoByte.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.serializer; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import org.raystack.firehose.exception.DeserializerException; 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | 8 | /** 9 | * JsonWrappedProto wrap encoded64 protobuff message into json format. The 10 | * format would look like 11 | *
12 |  * {
13 |  *   "topic":"sample-topic",
14 |  *   "log_key":"CgYIyOm+xgUSBgiE6r7GBRgNIICAgIDA9/y0LigCMAM\u003d",
15 |  *   "log_message":"CgYIyOm+xgUSBgiE6r7GBRgNIICAgIDA9/y0LigCMAM\u003d"
16 |  * }
17 |  * 
18 | */ 19 | public class JsonWrappedProtoByte implements MessageSerializer { 20 | 21 | private Gson gson; 22 | 23 | /** 24 | * Instantiates a new Json wrapped proto byte. 25 | */ 26 | public JsonWrappedProtoByte() { 27 | this.gson = new GsonBuilder().registerTypeAdapter(Message.class, new MessageJsonSerializer()).create(); 28 | } 29 | 30 | /** 31 | * Serialize string. 32 | * 33 | * @param message the message 34 | * @return the string 35 | * @throws DeserializerException the deserializer exception 36 | */ 37 | @Override 38 | public String serialize(Message message) throws DeserializerException { 39 | return gson.toJson(message); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/serializer/MessageJsonSerializer.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.serializer; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import com.google.gson.JsonElement; 5 | import com.google.gson.JsonObject; 6 | import com.google.gson.JsonSerializationContext; 7 | import com.google.gson.JsonSerializer; 8 | 9 | import java.lang.reflect.Type; 10 | 11 | /** 12 | * Message Json serializer serialises Kafka message to json. 13 | */ 14 | public class MessageJsonSerializer implements JsonSerializer { 15 | 16 | /** 17 | * Serialize kafka message into json element. 18 | * 19 | * @param message the message 20 | * @param typeOfSrc the type of src 21 | * @param context the context 22 | * @return the json element 23 | */ 24 | @Override 25 | public JsonElement serialize(Message message, Type typeOfSrc, JsonSerializationContext context) { 26 | JsonObject object = new JsonObject(); 27 | object.addProperty("topic", message.getTopic()); 28 | object.addProperty("log_key", message.getSerializedKey()); 29 | object.addProperty("log_message", message.getSerializedMessage()); 30 | return object; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/serializer/MessageSerializer.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.serializer; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import org.raystack.firehose.exception.DeserializerException; 5 | 6 | /** 7 | * Serializer serialize Message into string format. 8 | */ 9 | public interface MessageSerializer { 10 | 11 | /** 12 | * Serialize kafka message into string. 13 | * 14 | * @param message the message 15 | * @return serialised message 16 | * @throws DeserializerException the deserializer exception 17 | */ 18 | String serialize(Message message) throws DeserializerException; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/GenericSink.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink; 2 | 3 | import org.raystack.depot.Sink; 4 | import org.raystack.depot.SinkResponse; 5 | import org.raystack.firehose.exception.DeserializerException; 6 | import org.raystack.firehose.message.FirehoseMessageUtils; 7 | import org.raystack.firehose.message.Message; 8 | import org.raystack.firehose.metrics.FirehoseInstrumentation; 9 | 10 | import java.io.IOException; 11 | import java.sql.SQLException; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | 16 | public class GenericSink extends AbstractSink { 17 | private final List messageList = new ArrayList<>(); 18 | private final Sink sink; 19 | 20 | public GenericSink(FirehoseInstrumentation firehoseInstrumentation, String sinkType, Sink sink) { 21 | super(firehoseInstrumentation, sinkType); 22 | this.sink = sink; 23 | } 24 | 25 | @Override 26 | protected List execute() throws Exception { 27 | List messages = FirehoseMessageUtils.convertToDepotMessage(messageList); 28 | SinkResponse response = sink.pushToSink(messages); 29 | return response.getErrors().keySet().stream() 30 | .map(index -> { 31 | Message message = messageList.get(index.intValue()); 32 | message.setErrorInfo(response.getErrorsFor(index)); 33 | return message; 34 | }).collect(Collectors.toList()); 35 | } 36 | 37 | @Override 38 | protected void prepare(List messages) throws DeserializerException, IOException, SQLException { 39 | messageList.clear(); 40 | messageList.addAll(messages); 41 | } 42 | 43 | @Override 44 | public void close() throws IOException { 45 | sink.close(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/Sink.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink; 2 | 3 | import org.raystack.firehose.message.Message; 4 | 5 | import java.io.Closeable; 6 | import java.io.IOException; 7 | import java.util.List; 8 | 9 | /** 10 | * An interface for developing custom Sinks to FirehoseConsumer. 11 | */ 12 | public interface Sink extends Closeable { 13 | 14 | /** 15 | * method to write batch of messages read from kafka. 16 | * The logic of how to persist the data goes in here. 17 | * in the future this method should return Response object instead of list of messages 18 | * 19 | * @param message list of {@see EsbMessage} 20 | * @return the list of failed messages 21 | * @throws IOException in case of error conditions while persisting it to the custom sink. 22 | */ 23 | List pushMessage(List message) throws IOException; 24 | 25 | /** 26 | * Method that inform that sink is managing kafka offset to be committed by its own. 27 | * 28 | * @return 29 | */ 30 | default boolean canManageOffsets() { 31 | return false; 32 | } 33 | 34 | /** 35 | * Method to register kafka offsets and setting it to be committed. 36 | * This method should be implemented when sink manages the commit offsets by themselves. 37 | * Or when {@link Sink#canManageOffsets()} return true 38 | * 39 | * @param messageList messages to be committed 40 | */ 41 | default void addOffsetsAndSetCommittable(List messageList) { 42 | 43 | } 44 | 45 | /** 46 | * Method to calculate offsets ready to be committed. 47 | * This method should be implemented when sink manages the commit offsets by themselves. 48 | */ 49 | default void calculateCommittableOffsets() { 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/SinkFactoryUtils.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink; 2 | 3 | import org.raystack.depot.message.SinkConnectorSchemaMessageMode; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class SinkFactoryUtils { 9 | protected static Map addAdditionalConfigsForSinkConnectors(Map env) { 10 | Map finalConfig = new HashMap<>(env); 11 | finalConfig.put("SINK_CONNECTOR_SCHEMA_PROTO_MESSAGE_CLASS", env.getOrDefault("INPUT_SCHEMA_PROTO_CLASS", "")); 12 | finalConfig.put("SINK_CONNECTOR_SCHEMA_PROTO_KEY_CLASS", env.getOrDefault("INPUT_SCHEMA_PROTO_CLASS", "")); 13 | finalConfig.put("SINK_CONNECTOR_SCHEMA_DATA_TYPE", env.getOrDefault("INPUT_SCHEMA_DATA_TYPE", "protobuf")); 14 | finalConfig.put("SINK_METRICS_APPLICATION_PREFIX", "firehose_"); 15 | finalConfig.put("SINK_CONNECTOR_SCHEMA_PROTO_ALLOW_UNKNOWN_FIELDS_ENABLE", env.getOrDefault("INPUT_SCHEMA_PROTO_ALLOW_UNKNOWN_FIELDS_ENABLE", "false")); 16 | finalConfig.put("SINK_CONNECTOR_SCHEMA_MESSAGE_MODE", 17 | env.getOrDefault("KAFKA_RECORD_PARSER_MODE", "").equals("key") ? SinkConnectorSchemaMessageMode.LOG_KEY.name() : SinkConnectorSchemaMessageMode.LOG_MESSAGE.name()); 18 | return finalConfig; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/bigquery/BigquerySinkUtils.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.bigquery; 2 | 3 | import java.util.Map; 4 | import java.util.function.Function; 5 | 6 | public class BigquerySinkUtils { 7 | public static Function, String> getRowIDCreator() { 8 | return (m -> String.format("%s_%d_%d", m.get("message_topic"), m.get("message_partition"), m.get("message_offset"))); 9 | } 10 | 11 | public static void addMetadataColumns(Map config) { 12 | config.put("SINK_BIGQUERY_METADATA_COLUMNS_TYPES", 13 | "message_offset=integer,message_topic=string,load_time=timestamp,message_timestamp=timestamp,message_partition=integer"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/blob/Constants.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob; 2 | 3 | public class Constants { 4 | public enum WriterType { 5 | PARQUET, 6 | } 7 | 8 | public enum FilePartitionType { 9 | NONE, 10 | DAY, 11 | HOUR 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/blob/message/KafkaMetadataUtils.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.message; 2 | 3 | import com.google.protobuf.Descriptors; 4 | import com.google.protobuf.DynamicMessage; 5 | import org.raystack.firehose.message.Message; 6 | import org.raystack.firehose.sink.blob.proto.KafkaMetadataProtoMessage; 7 | import org.raystack.firehose.sink.blob.proto.NestedKafkaMetadataProtoMessage; 8 | 9 | import java.time.Instant; 10 | 11 | /** 12 | * KafkaMetadataUtils utility class for creating kafka metadata {@link com.google.protobuf.DynamicMessage DynamicMessage} from {@link Message}. 13 | */ 14 | public class KafkaMetadataUtils { 15 | 16 | public static DynamicMessage createKafkaMetadata(Descriptors.FileDescriptor kafkaMetadataFileDescriptor, Message message, String kafkaMetadataColumnName) { 17 | Descriptors.Descriptor metadataDescriptor = kafkaMetadataFileDescriptor.findMessageTypeByName(KafkaMetadataProtoMessage.getTypeName()); 18 | 19 | Instant loadTime = Instant.now(); 20 | Instant messageTimestamp = Instant.ofEpochMilli(message.getTimestamp()); 21 | 22 | KafkaMetadataProtoMessage.MessageBuilder messageBuilder = KafkaMetadataProtoMessage.newBuilder(metadataDescriptor) 23 | .setLoadTime(loadTime) 24 | .setMessageTimestamp(messageTimestamp) 25 | .setOffset(message.getOffset()) 26 | .setPartition(message.getPartition()) 27 | .setTopic(message.getTopic()); 28 | 29 | DynamicMessage metadata = messageBuilder.build(); 30 | 31 | if (kafkaMetadataColumnName.isEmpty()) { 32 | return metadata; 33 | } 34 | 35 | Descriptors.Descriptor nestedMetadataDescriptor = kafkaMetadataFileDescriptor.findMessageTypeByName(NestedKafkaMetadataProtoMessage.getTypeName()); 36 | 37 | return NestedKafkaMetadataProtoMessage.newMessageBuilder(nestedMetadataDescriptor) 38 | .setMetadata(metadata) 39 | .setMetadataColumnName(kafkaMetadataColumnName).build(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/blob/message/Record.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.message; 2 | 3 | import com.google.protobuf.Descriptors; 4 | import com.google.protobuf.DynamicMessage; 5 | import org.raystack.firehose.sink.blob.proto.KafkaMetadataProtoMessage; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | 9 | import java.time.Instant; 10 | 11 | @AllArgsConstructor 12 | @Data 13 | public class Record { 14 | private DynamicMessage message; 15 | private DynamicMessage metadata; 16 | 17 | public String getTopic(String fieldName) { 18 | Descriptors.Descriptor metadataDescriptor = metadata.getDescriptorForType(); 19 | 20 | if (!fieldName.isEmpty()) { 21 | DynamicMessage nestedMetadataMessage = (DynamicMessage) metadata.getField(metadataDescriptor.findFieldByName(fieldName)); 22 | Descriptors.Descriptor nestedMetadataMessageDescriptor = nestedMetadataMessage.getDescriptorForType(); 23 | return (String) nestedMetadataMessage.getField(nestedMetadataMessageDescriptor.findFieldByName(KafkaMetadataProtoMessage.MESSAGE_TOPIC_FIELD_NAME)); 24 | } 25 | 26 | return (String) metadata.getField(metadataDescriptor.findFieldByName(KafkaMetadataProtoMessage.MESSAGE_TOPIC_FIELD_NAME)); 27 | } 28 | 29 | public Instant getTimestamp(String fieldName) { 30 | Descriptors.Descriptor descriptor = message.getDescriptorForType(); 31 | Descriptors.FieldDescriptor timestampField = descriptor.findFieldByName(fieldName); 32 | DynamicMessage timestamp = (DynamicMessage) message.getField(timestampField); 33 | long seconds = (long) timestamp.getField(timestamp.getDescriptorForType().findFieldByName("seconds")); 34 | int nanos = (int) timestamp.getField(timestamp.getDescriptorForType().findFieldByName("nanos")); 35 | return Instant.ofEpochSecond(seconds, nanos); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/blob/proto/TimestampMetadataProtoMessage.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.proto; 2 | 3 | import com.github.os72.protobuf.dynamic.MessageDefinition; 4 | import com.google.protobuf.Timestamp; 5 | 6 | /** 7 | * TimestampMetadataProtoMessage contains proto schema proto message of Timestamp. The schema equals to {@link com.google.protobuf.Timestamp Timestamp} message. 8 | * This class provides {@link com.github.os72.protobuf.dynamic.MessageDefinition} to generate protobuf descriptor and builder of {@link com.google.protobuf.Timestamp Timestamp} proto message. 9 | * 10 | * message Timestamp { 11 | * int64 seconds = 1 12 | * int42 nanos = 2; 13 | * } 14 | * 15 | */ 16 | public class TimestampMetadataProtoMessage { 17 | private static final String TYPE_NAME = "Timestamp"; 18 | public static final String SECONDS_FIELD_NAME = "seconds"; 19 | public static final String NANOS_FIELD_NAME = "nanos"; 20 | public static final int SECONDS_FIELD_NUMBER = 1; 21 | public static final int NANOS_FIELD_NUMBER = 2; 22 | 23 | public static MessageDefinition createMessageDefinition() { 24 | return MessageDefinition.newBuilder(TYPE_NAME) 25 | .addField("optional", "int64", SECONDS_FIELD_NAME, SECONDS_FIELD_NUMBER) 26 | .addField("optional", "int32", NANOS_FIELD_NAME, NANOS_FIELD_NUMBER) 27 | .build(); 28 | } 29 | 30 | public static String getTypeName() { 31 | return TYPE_NAME; 32 | } 33 | 34 | public static Timestamp.Builder newBuilder() { 35 | return Timestamp.newBuilder(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/blob/writer/local/LocalFileMetadata.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.writer.local; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | 7 | @AllArgsConstructor 8 | @Getter 9 | @EqualsAndHashCode 10 | public class LocalFileMetadata { 11 | private final String basePath; 12 | private final String fullPath; 13 | private final long createdTimestampMillis; 14 | private final long recordCount; 15 | private final long size; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/blob/writer/local/LocalFileWriter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.writer.local; 2 | 3 | import org.raystack.firehose.sink.blob.message.Record; 4 | 5 | import java.io.Closeable; 6 | import java.io.IOException; 7 | 8 | public interface LocalFileWriter extends Closeable { 9 | /** 10 | * @param record to write 11 | * @return true if write succeeds, false if the writer is closed. 12 | * @throws IOException if local file writing fails 13 | */ 14 | boolean write(Record record) throws IOException; 15 | 16 | LocalFileMetadata getMetadata(); 17 | 18 | LocalFileMetadata closeAndFetchMetaData() throws IOException; 19 | 20 | String getFullPath(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/blob/writer/local/LocalFileWriterFailedException.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.writer.local; 2 | 3 | import java.io.IOException; 4 | 5 | public class LocalFileWriterFailedException extends RuntimeException { 6 | public LocalFileWriterFailedException(IOException e) { 7 | super(e); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/blob/writer/local/policy/SizeBasedRotatingPolicy.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.writer.local.policy; 2 | 3 | import org.raystack.firehose.sink.blob.writer.local.LocalFileMetadata; 4 | 5 | public class SizeBasedRotatingPolicy implements WriterPolicy { 6 | 7 | private final long maxSize; 8 | 9 | public SizeBasedRotatingPolicy(long maxSize) { 10 | if (maxSize <= 0) { 11 | throw new IllegalArgumentException("The max size should be a positive integer"); 12 | } 13 | this.maxSize = maxSize; 14 | } 15 | 16 | @Override 17 | public boolean shouldRotate(LocalFileMetadata metadata) { 18 | return metadata.getSize() >= maxSize; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/blob/writer/local/policy/TimeBasedRotatingPolicy.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.writer.local.policy; 2 | 3 | import org.raystack.firehose.sink.blob.writer.local.LocalFileMetadata; 4 | 5 | public class TimeBasedRotatingPolicy implements WriterPolicy { 6 | 7 | private final long maxRotatingDurationMillis; 8 | 9 | public TimeBasedRotatingPolicy(long maxRotatingDurationMillis) { 10 | if (maxRotatingDurationMillis <= 0) { 11 | throw new IllegalArgumentException("The max duration should be a positive integer"); 12 | } 13 | this.maxRotatingDurationMillis = maxRotatingDurationMillis; 14 | } 15 | 16 | @Override 17 | public boolean shouldRotate(LocalFileMetadata metadata) { 18 | return System.currentTimeMillis() - metadata.getCreatedTimestampMillis() >= maxRotatingDurationMillis; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/blob/writer/local/policy/WriterPolicy.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.writer.local.policy; 2 | 3 | import org.raystack.firehose.sink.blob.writer.local.LocalFileMetadata; 4 | 5 | public interface WriterPolicy { 6 | boolean shouldRotate(LocalFileMetadata metadata); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/blob/writer/remote/BlobStorageFailedException.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.writer.remote; 2 | 3 | public class BlobStorageFailedException extends RuntimeException { 4 | public BlobStorageFailedException(Throwable th) { 5 | super(th); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/blob/writer/remote/BlobStorageWorker.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.writer.remote; 2 | 3 | import org.raystack.firehose.sink.common.blobstorage.BlobStorage; 4 | import org.raystack.firehose.sink.common.blobstorage.BlobStorageException; 5 | import org.raystack.firehose.sink.blob.writer.local.LocalFileMetadata; 6 | import lombok.AllArgsConstructor; 7 | 8 | import java.nio.file.Paths; 9 | import java.time.Duration; 10 | import java.time.Instant; 11 | import java.util.concurrent.Callable; 12 | 13 | /** 14 | * Uploads a local file to object-storage and returns the total time taken. 15 | */ 16 | @AllArgsConstructor 17 | public class BlobStorageWorker implements Callable { 18 | 19 | private final BlobStorage blobStorage; 20 | private final LocalFileMetadata metadata; 21 | 22 | @Override 23 | public Long call() throws BlobStorageException { 24 | Instant start = Instant.now(); 25 | String objectName = Paths.get(metadata.getBasePath()).relativize(Paths.get(metadata.getFullPath())).toString(); 26 | blobStorage.store(objectName, metadata.getFullPath()); 27 | return Duration.between(start, Instant.now()).toMillis(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/common/KeyOrMessageParser.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.common; 2 | 3 | 4 | import org.raystack.firehose.config.AppConfig; 5 | import org.raystack.firehose.message.Message; 6 | import com.google.protobuf.DynamicMessage; 7 | import com.google.protobuf.InvalidProtocolBufferException; 8 | import org.raystack.stencil.Parser; 9 | import lombok.AllArgsConstructor; 10 | 11 | import java.io.IOException; 12 | 13 | /** 14 | * Parser for Key or message. 15 | */ 16 | @AllArgsConstructor 17 | public class KeyOrMessageParser { 18 | 19 | private Parser protoParser; 20 | private AppConfig appConfig; 21 | 22 | /** 23 | * Parse dynamic message. 24 | * 25 | * @param message the message 26 | * @return the dynamic message 27 | * @throws IOException when invalid message is encountered 28 | */ 29 | public DynamicMessage parse(Message message) throws IOException { 30 | if (appConfig.getKafkaRecordParserMode().equals("key")) { 31 | return protoParse(message.getLogKey()); 32 | } 33 | return protoParse(message.getLogMessage()); 34 | } 35 | 36 | private DynamicMessage protoParse(byte[] data) throws IOException { 37 | try { 38 | return protoParser.parse(data); 39 | } catch (InvalidProtocolBufferException e) { 40 | throw new IOException(e); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/common/blobstorage/BlobStorage.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.common.blobstorage; 2 | 3 | /** 4 | * Abstraction of any storage that store binary bytes as file. 5 | */ 6 | public interface BlobStorage { 7 | void store(String objectName, String filePath) throws BlobStorageException; 8 | 9 | void store(String objectName, byte[] content) throws BlobStorageException; 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/common/blobstorage/BlobStorageException.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.common.blobstorage; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | 6 | /** 7 | * Should be thrown when there is exception thrown by blob storage client. 8 | */ 9 | @Getter 10 | @EqualsAndHashCode(callSuper = false) 11 | public class BlobStorageException extends Exception { 12 | private final String errorType; 13 | private final String message; 14 | 15 | public BlobStorageException(String errorType, String message, Throwable cause) { 16 | super(message, cause); 17 | this.errorType = errorType; 18 | this.message = message; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/common/blobstorage/BlobStorageFactory.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.common.blobstorage; 2 | 3 | import org.raystack.firehose.config.GCSConfig; 4 | import org.raystack.firehose.config.S3Config; 5 | import org.raystack.firehose.sink.common.blobstorage.gcs.GoogleCloudStorage; 6 | import org.raystack.firehose.sink.common.blobstorage.s3.S3; 7 | import org.aeonbits.owner.ConfigFactory; 8 | 9 | import java.io.IOException; 10 | import java.util.Map; 11 | 12 | public class BlobStorageFactory { 13 | 14 | public static BlobStorage createObjectStorage(BlobStorageType storageType, Map config) { 15 | switch (storageType) { 16 | case GCS: 17 | try { 18 | GCSConfig gcsConfig = ConfigFactory.create(GCSConfig.class, config); 19 | return new GoogleCloudStorage(gcsConfig); 20 | } catch (IOException e) { 21 | throw new IllegalArgumentException("Exception while creating GCS Storage", e); 22 | } 23 | case S3: 24 | try { 25 | S3Config s3Config = ConfigFactory.create(S3Config.class, config); 26 | return new S3(s3Config); 27 | } catch (Exception e) { 28 | throw new IllegalArgumentException("Exception while creating S3 Storage", e); 29 | } 30 | 31 | default: 32 | throw new IllegalArgumentException("Blob Storage Type " + storageType + " is not supported"); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/common/blobstorage/BlobStorageType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.common.blobstorage; 2 | 3 | public enum BlobStorageType { 4 | GCS, 5 | S3 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/common/blobstorage/gcs/error/GCSErrorType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.common.blobstorage.gcs.error; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Error types from exception thrown by google cloud storage client. 8 | * There might be newer error codes that thrown by gcs client, need to update this list of error. 9 | */ 10 | public enum GCSErrorType { 11 | FOUND(302), 12 | SEE_OTHER(303), 13 | NOT_MODIFIED(304), 14 | TEMPORARY_REDIRECT(305), 15 | RESUME_INCOMPLETE(308), 16 | UNAUTHORIZED(401), 17 | FORBIDDEN(403), 18 | NOT_FOUND(404), 19 | METHOD_NOT_ALLOWED(405), 20 | CONFLICT(409), 21 | GONE(410), 22 | LENGTH_REQUIRED(411), 23 | PRECONDITION_FAILED(412), 24 | PAYLOAD_TOO_LARGE(413), 25 | REQUESTED_RANGE_NOT_SATISFIABLE(416), 26 | CLIENT_CLOSED_REQUEST(499), 27 | BAD_REQUEST(400), 28 | GATEWAY_TIMEOUT(504), 29 | SERVICE_UNAVAILABLE(503), 30 | BAD_GATEWAY(502), 31 | INTERNAL_SERVER_ERROR(500), 32 | TOO_MANY_REQUEST(429), 33 | REQUEST_TIMEOUT(408), 34 | DEFAULT_ERROR; 35 | 36 | private static final Map ERROR_NUMBER_TYPE_MAP = new HashMap<>(); 37 | 38 | static { 39 | for (GCSErrorType errorType : values()) { 40 | ERROR_NUMBER_TYPE_MAP.put(errorType.codeValue, errorType); 41 | } 42 | } 43 | 44 | public static GCSErrorType valueOfCode(int code) { 45 | return ERROR_NUMBER_TYPE_MAP.getOrDefault(code, DEFAULT_ERROR); 46 | } 47 | 48 | private int codeValue; 49 | 50 | GCSErrorType(int codeValue) { 51 | this.codeValue = codeValue; 52 | } 53 | 54 | GCSErrorType() { 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/dlq/DLQWriterType.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.dlq; 2 | 3 | public enum DLQWriterType { 4 | KAFKA, 5 | BLOB_STORAGE, 6 | LOG 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/dlq/DlqWriter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.dlq; 2 | 3 | import org.raystack.firehose.message.Message; 4 | 5 | import java.io.IOException; 6 | import java.util.List; 7 | 8 | public interface DlqWriter { 9 | 10 | /** 11 | * Method to write messages to dead letter queues destination. 12 | * @param messages is collection of message that need to be sent to dead letter queue 13 | * @return collection of messages that failed to be processed 14 | * @throws IOException can be thrown for non retry able error 15 | */ 16 | List write(List messages) throws IOException; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/dlq/blobstorage/DlqMessage.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.dlq.blobstorage; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | public class DlqMessage { 10 | @JsonProperty("key") 11 | private String key; 12 | @JsonProperty("value") 13 | private String value; 14 | @JsonProperty("topic") 15 | private String topic; 16 | @JsonProperty("partition") 17 | private int partition; 18 | @JsonProperty("offset") 19 | private long offset; 20 | @JsonProperty("timestamp") 21 | private long timestamp; 22 | @JsonProperty("error") 23 | private String error; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/dlq/log/LogDlqWriter.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.dlq.log; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import org.raystack.firehose.metrics.FirehoseInstrumentation; 5 | import org.raystack.depot.error.ErrorInfo; 6 | import org.raystack.firehose.sink.dlq.DlqWriter; 7 | import org.apache.commons.lang3.exception.ExceptionUtils; 8 | 9 | import java.io.IOException; 10 | import java.util.LinkedList; 11 | import java.util.List; 12 | 13 | public class LogDlqWriter implements DlqWriter { 14 | private final FirehoseInstrumentation firehoseInstrumentation; 15 | 16 | public LogDlqWriter(FirehoseInstrumentation firehoseInstrumentation) { 17 | this.firehoseInstrumentation = firehoseInstrumentation; 18 | } 19 | 20 | @Override 21 | public List write(List messages) throws IOException { 22 | for (Message message : messages) { 23 | String key = message.getLogKey() == null ? "" : new String(message.getLogKey()); 24 | String value = message.getLogMessage() == null ? "" : new String(message.getLogMessage()); 25 | 26 | String error = ""; 27 | ErrorInfo errorInfo = message.getErrorInfo(); 28 | if (errorInfo != null) { 29 | if (errorInfo.getException() != null) { 30 | error = ExceptionUtils.getStackTrace(errorInfo.getException()); 31 | } 32 | } 33 | 34 | firehoseInstrumentation.logInfo("key: {}\nvalue: {}\nerror: {}", key, value, error); 35 | } 36 | return new LinkedList<>(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/elasticsearch/request/EsRequestHandler.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.elasticsearch.request; 2 | 3 | import org.raystack.firehose.config.enums.EsSinkMessageType; 4 | import org.raystack.firehose.exception.JsonParseException; 5 | import org.raystack.firehose.message.Message; 6 | import org.raystack.firehose.serializer.MessageToJson; 7 | import org.elasticsearch.action.DocWriteRequest; 8 | import org.json.simple.JSONObject; 9 | import org.json.simple.parser.JSONParser; 10 | import org.json.simple.parser.ParseException; 11 | 12 | import java.nio.charset.Charset; 13 | 14 | public abstract class EsRequestHandler { 15 | private final EsSinkMessageType messageType; 16 | private final MessageToJson jsonSerializer; 17 | private final JSONParser jsonParser; 18 | 19 | public EsRequestHandler(EsSinkMessageType messageType, MessageToJson jsonSerializer) { 20 | this.messageType = messageType; 21 | this.jsonSerializer = jsonSerializer; 22 | this.jsonParser = new JSONParser(); 23 | } 24 | 25 | public abstract boolean canCreate(); 26 | 27 | public abstract DocWriteRequest getRequest(Message message); 28 | 29 | String extractPayload(Message message) { 30 | if (messageType.equals(EsSinkMessageType.PROTOBUF)) { 31 | return getFieldFromJSON(jsonSerializer.serialize(message), "logMessage"); 32 | } 33 | return new String(message.getLogMessage(), Charset.defaultCharset()); 34 | } 35 | 36 | String getFieldFromJSON(String jsonString, String key) { 37 | try { 38 | JSONObject parse = (JSONObject) jsonParser.parse(jsonString); 39 | Object valueAtKey = parse.get(key); 40 | if (valueAtKey == null) { 41 | throw new IllegalArgumentException("Key: " + key + " not found in ESB Message"); 42 | } 43 | return valueAtKey.toString(); 44 | } catch (ParseException e) { 45 | throw new JsonParseException(e.getMessage(), e.getCause()); 46 | } finally { 47 | jsonParser.reset(); 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/elasticsearch/request/EsRequestHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.elasticsearch.request; 2 | 3 | import org.raystack.firehose.config.EsSinkConfig; 4 | import org.raystack.firehose.config.enums.EsSinkMessageType; 5 | import org.raystack.firehose.config.enums.EsSinkRequestType; 6 | import org.raystack.firehose.metrics.FirehoseInstrumentation; 7 | import org.raystack.firehose.serializer.MessageToJson; 8 | import lombok.AllArgsConstructor; 9 | 10 | import java.util.ArrayList; 11 | 12 | @AllArgsConstructor 13 | public class EsRequestHandlerFactory { 14 | 15 | private EsSinkConfig esSinkConfig; 16 | private FirehoseInstrumentation firehoseInstrumentation; 17 | private final String esIdFieldName; 18 | private final EsSinkMessageType messageType; 19 | private final MessageToJson jsonSerializer; 20 | private final String esTypeName; 21 | private final String esIndexName; 22 | private final String esRoutingKeyName; 23 | 24 | public EsRequestHandler getRequestHandler() { 25 | EsSinkRequestType esSinkRequestType = esSinkConfig.isSinkEsModeUpdateOnlyEnable() ? EsSinkRequestType.UPDATE_ONLY : EsSinkRequestType.INSERT_OR_UPDATE; 26 | firehoseInstrumentation.logInfo("ES request mode: {}", esSinkRequestType); 27 | 28 | ArrayList esRequestHandlers = new ArrayList<>(); 29 | esRequestHandlers.add(new EsUpdateRequestHandler(messageType, jsonSerializer, esTypeName, esIndexName, esSinkRequestType, esIdFieldName, esRoutingKeyName)); 30 | esRequestHandlers.add(new EsUpsertRequestHandler(messageType, jsonSerializer, esTypeName, esIndexName, esSinkRequestType, esIdFieldName, esRoutingKeyName)); 31 | 32 | return esRequestHandlers 33 | .stream() 34 | .filter(EsRequestHandler::canCreate) 35 | .findFirst() 36 | .orElseThrow(() -> new IllegalArgumentException("Es Request Type " + esSinkRequestType.name() + " not supported")); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/elasticsearch/request/EsUpdateRequestHandler.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.elasticsearch.request; 2 | 3 | import org.raystack.firehose.config.enums.EsSinkMessageType; 4 | import org.raystack.firehose.config.enums.EsSinkRequestType; 5 | import org.raystack.firehose.message.Message; 6 | import org.raystack.firehose.serializer.MessageToJson; 7 | import org.apache.commons.lang.StringUtils; 8 | import org.elasticsearch.action.DocWriteRequest; 9 | import org.elasticsearch.action.update.UpdateRequest; 10 | import org.elasticsearch.common.xcontent.XContentType; 11 | 12 | public class EsUpdateRequestHandler extends EsRequestHandler { 13 | 14 | private final String esTypeName; 15 | private final String esIndexName; 16 | private EsSinkRequestType esSinkRequestType; 17 | private String esIdFieldName; 18 | private String esRoutingKeyName; 19 | 20 | public EsUpdateRequestHandler(EsSinkMessageType messageType, MessageToJson jsonSerializer, String esTypeName, String esIndexName, EsSinkRequestType esSinkRequestType, String esIdFieldName, String esRoutingKeyName) { 21 | super(messageType, jsonSerializer); 22 | this.esTypeName = esTypeName; 23 | this.esIndexName = esIndexName; 24 | this.esSinkRequestType = esSinkRequestType; 25 | this.esIdFieldName = esIdFieldName; 26 | this.esRoutingKeyName = esRoutingKeyName; 27 | } 28 | 29 | @Override 30 | public boolean canCreate() { 31 | return esSinkRequestType == EsSinkRequestType.UPDATE_ONLY; 32 | } 33 | 34 | public DocWriteRequest getRequest(Message message) { 35 | String logMessage = extractPayload(message); 36 | UpdateRequest request = new UpdateRequest(esIndexName, esTypeName, getFieldFromJSON(logMessage, esIdFieldName)); 37 | if (StringUtils.isNotEmpty(esRoutingKeyName)) { 38 | request.routing(getFieldFromJSON(logMessage, esRoutingKeyName)); 39 | } 40 | request.doc(logMessage, XContentType.JSON); 41 | return request; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/elasticsearch/request/EsUpsertRequestHandler.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.elasticsearch.request; 2 | 3 | import org.raystack.firehose.config.enums.EsSinkMessageType; 4 | import org.raystack.firehose.config.enums.EsSinkRequestType; 5 | import org.raystack.firehose.message.Message; 6 | import org.raystack.firehose.serializer.MessageToJson; 7 | import org.apache.commons.lang.StringUtils; 8 | import org.elasticsearch.action.DocWriteRequest; 9 | import org.elasticsearch.action.index.IndexRequest; 10 | import org.elasticsearch.common.xcontent.XContentType; 11 | 12 | public class EsUpsertRequestHandler extends EsRequestHandler { 13 | private final String esTypeName; 14 | private final String esIndexName; 15 | private EsSinkRequestType esSinkRequestType; 16 | private String esIdFieldName; 17 | private String esRoutingKeyName; 18 | 19 | public EsUpsertRequestHandler(EsSinkMessageType messageType, MessageToJson jsonSerializer, String esTypeName, String esIndexName, EsSinkRequestType esSinkRequestType, String esIdFieldName, String esRoutingKeyName) { 20 | super(messageType, jsonSerializer); 21 | this.esTypeName = esTypeName; 22 | this.esIndexName = esIndexName; 23 | this.esSinkRequestType = esSinkRequestType; 24 | this.esIdFieldName = esIdFieldName; 25 | this.esRoutingKeyName = esRoutingKeyName; 26 | } 27 | 28 | @Override 29 | public boolean canCreate() { 30 | return esSinkRequestType == EsSinkRequestType.INSERT_OR_UPDATE; 31 | } 32 | 33 | public DocWriteRequest getRequest(Message message) { 34 | String logMessage = extractPayload(message); 35 | IndexRequest request = new IndexRequest(esIndexName, esTypeName, getFieldFromJSON(logMessage, esIdFieldName)); 36 | if (StringUtils.isNotEmpty(esRoutingKeyName)) { 37 | request.routing(getFieldFromJSON(logMessage, esRoutingKeyName)); 38 | } 39 | request.source(logMessage, XContentType.JSON); 40 | return request; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/http/auth/OAuth2AccessToken.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.http.auth; 2 | 3 | import org.joda.time.DateTimeUtils; 4 | 5 | public class OAuth2AccessToken { 6 | private final String accessToken; 7 | private final Long expirationTimeMs; 8 | private static final int DEFAULT_EXPIRATION_TIME = 3600; 9 | private static final long MILLIS = 1000L; 10 | 11 | public OAuth2AccessToken(String accessToken, Integer expiresIn) { 12 | this.accessToken = accessToken; 13 | expiresIn = expiresIn == null ? DEFAULT_EXPIRATION_TIME : expiresIn; 14 | this.expirationTimeMs = DateTimeUtils.currentTimeMillis() + (expiresIn * MILLIS); 15 | } 16 | 17 | public boolean isExpired() { 18 | final long oneMinute = 60L; 19 | return this.getExpiresIn() <= oneMinute; 20 | } 21 | 22 | public String toString() { 23 | return this.accessToken; 24 | } 25 | 26 | public Long getExpiresIn() { 27 | return (this.expirationTimeMs - DateTimeUtils.currentTimeMillis()) / MILLIS; 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/http/request/HttpRequestMethodFactory.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.http.request; 2 | 3 | import org.raystack.firehose.config.enums.HttpSinkRequestMethodType; 4 | import org.raystack.firehose.sink.http.request.method.HttpDeleteWithBody; 5 | import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; 6 | import org.apache.http.client.methods.HttpPatch; 7 | import org.apache.http.client.methods.HttpPost; 8 | import org.apache.http.client.methods.HttpPut; 9 | 10 | import java.net.URI; 11 | 12 | /** 13 | * The type Http request method factory. 14 | */ 15 | public class HttpRequestMethodFactory { 16 | /** 17 | * Create http entity enclosing request base. 18 | * 19 | * @param uri the uri 20 | * @param method the method 21 | * @return the http entity enclosing request base 22 | */ 23 | public static HttpEntityEnclosingRequestBase create(URI uri, HttpSinkRequestMethodType method) { 24 | switch (method) { 25 | case POST: 26 | return new HttpPost(uri); 27 | case PATCH: 28 | return new HttpPatch(uri); 29 | case DELETE: 30 | return new HttpDeleteWithBody(uri); 31 | default: 32 | return new HttpPut(uri); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/http/request/body/JsonBody.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.http.request.body; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.raystack.firehose.exception.DeserializerException; 7 | import org.raystack.firehose.message.Message; 8 | import org.raystack.firehose.serializer.MessageSerializer; 9 | 10 | /** 11 | * JsonBody Serialize the message according to injected serialzier and return it 12 | * as List of serialized string. 13 | */ 14 | public class JsonBody { 15 | 16 | private MessageSerializer jsonSerializer; 17 | 18 | /** 19 | * Instantiates a new Json body. 20 | * 21 | * @param jsonSerializer the json serializer 22 | */ 23 | public JsonBody(MessageSerializer jsonSerializer) { 24 | this.jsonSerializer = jsonSerializer; 25 | } 26 | 27 | /** 28 | * Serialize list. 29 | * 30 | * @param messages the messages 31 | * @return the list 32 | * @throws DeserializerException the deserializer exception 33 | */ 34 | public List serialize(List messages) throws DeserializerException { 35 | List serializedBody = new ArrayList(); 36 | for (Message message : messages) { 37 | serializedBody.add(jsonSerializer.serialize(message)); 38 | } 39 | return serializedBody; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/http/request/create/RequestCreator.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.http.request.create; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import org.raystack.firehose.sink.http.request.entity.RequestEntityBuilder; 5 | import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; 6 | 7 | import java.net.URISyntaxException; 8 | import java.util.List; 9 | 10 | /** 11 | * Creates http requests. 12 | */ 13 | public interface RequestCreator { 14 | 15 | List create(List bodyContents, RequestEntityBuilder entity) throws URISyntaxException; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/http/request/entity/RequestEntityBuilder.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.http.request.entity; 2 | 3 | import org.raystack.firehose.exception.DeserializerException; 4 | import org.apache.http.entity.ContentType; 5 | import org.apache.http.entity.StringEntity; 6 | 7 | import java.util.Collections; 8 | 9 | /** 10 | * Request entity builder. 11 | */ 12 | public class RequestEntityBuilder { 13 | private boolean wrapArray; 14 | 15 | /** 16 | * Instantiates a new Request entity builder. 17 | */ 18 | public RequestEntityBuilder() { 19 | this.wrapArray = false; 20 | } 21 | 22 | public RequestEntityBuilder setWrapping(boolean isArrayWrap) { 23 | this.wrapArray = isArrayWrap; 24 | return this; 25 | } 26 | 27 | /** 28 | * Build http entity string entity. 29 | * 30 | * @param bodyContent the body content 31 | * @return the string entity 32 | * @throws DeserializerException the deserializer exception 33 | */ 34 | public StringEntity buildHttpEntity(String bodyContent) throws DeserializerException { 35 | if (!wrapArray) { 36 | return new StringEntity(bodyContent, ContentType.APPLICATION_JSON); 37 | } else { 38 | String arrayWrappedBody = Collections.singletonList(bodyContent).toString(); 39 | return new StringEntity(arrayWrappedBody, ContentType.APPLICATION_JSON); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/http/request/method/HttpDeleteWithBody.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.http.request.method; 2 | 3 | import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; 4 | 5 | import java.net.URI; 6 | 7 | public class HttpDeleteWithBody extends HttpEntityEnclosingRequestBase { 8 | public static final String METHOD_NAME = "DELETE"; 9 | 10 | @Override 11 | public String getMethod() { 12 | return METHOD_NAME; 13 | } 14 | 15 | public HttpDeleteWithBody(final URI uri) { 16 | super(); 17 | setURI(uri); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/http/request/uri/UriBuilder.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.http.request.uri; 2 | 3 | import org.raystack.firehose.config.enums.HttpSinkParameterSourceType; 4 | import org.raystack.firehose.message.Message; 5 | import org.raystack.firehose.proto.ProtoToFieldMapper; 6 | 7 | import java.net.URI; 8 | import java.net.URISyntaxException; 9 | import java.util.Map; 10 | 11 | /** 12 | * Builds URI based on the requirement. 13 | */ 14 | public class UriBuilder { 15 | 16 | private String baseURL; 17 | private UriParser uriParser; 18 | private ProtoToFieldMapper protoToFieldMapper; 19 | private HttpSinkParameterSourceType httpSinkParameterSourceType; 20 | 21 | public UriBuilder(String baseURL, UriParser uriParser) { 22 | this.baseURL = baseURL; 23 | this.uriParser = uriParser; 24 | } 25 | 26 | public URI build() throws URISyntaxException { 27 | return new URI(baseURL); 28 | } 29 | 30 | public URI build(Message message) throws URISyntaxException { 31 | String url = uriParser.parse(message, baseURL); 32 | org.apache.http.client.utils.URIBuilder uriBuilder = new org.apache.http.client.utils.URIBuilder(url); 33 | if (protoToFieldMapper == null) { 34 | return uriBuilder.build(); 35 | } 36 | 37 | // flow for parameterized URI 38 | Map paramMap = protoToFieldMapper 39 | .getFields((httpSinkParameterSourceType == HttpSinkParameterSourceType.KEY) ? message.getLogKey() 40 | : message.getLogMessage()); 41 | paramMap.forEach((string, object) -> uriBuilder.addParameter(string, object.toString())); 42 | return uriBuilder.build(); 43 | } 44 | 45 | public UriBuilder withParameterizedURI(ProtoToFieldMapper protoToFieldmapper, HttpSinkParameterSourceType httpSinkParameterSource) { 46 | this.protoToFieldMapper = protoToFieldmapper; 47 | this.httpSinkParameterSourceType = httpSinkParameterSource; 48 | return this; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/influxdb/InfluxSinkFactory.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.influxdb; 2 | 3 | 4 | import org.raystack.firehose.config.InfluxSinkConfig; 5 | import org.raystack.firehose.metrics.FirehoseInstrumentation; 6 | import org.raystack.depot.metrics.StatsDReporter; 7 | import org.raystack.firehose.sink.AbstractSink; 8 | import org.raystack.stencil.client.StencilClient; 9 | import org.aeonbits.owner.ConfigFactory; 10 | import org.influxdb.InfluxDB; 11 | import org.influxdb.InfluxDBFactory; 12 | 13 | import java.util.Map; 14 | 15 | /** 16 | * Influx sink factory create influx sink. 17 | */ 18 | public class InfluxSinkFactory { 19 | /** 20 | * Create Influx sink. 21 | * 22 | * @param configProperties the config properties 23 | * @param statsDReporter the statsd reporter 24 | * @param stencilClient the stencil client 25 | * @return Influx sink 26 | */ 27 | public static AbstractSink create(Map configProperties, StatsDReporter statsDReporter, StencilClient stencilClient) { 28 | InfluxSinkConfig config = ConfigFactory.create(InfluxSinkConfig.class, configProperties); 29 | 30 | FirehoseInstrumentation firehoseInstrumentation = new FirehoseInstrumentation(statsDReporter, InfluxSinkFactory.class); 31 | firehoseInstrumentation.logDebug("\nInflux Url: {}\nInflux Username: {}", config.getSinkInfluxUrl(), config.getSinkInfluxUsername()); 32 | 33 | InfluxDB client = InfluxDBFactory.connect(config.getSinkInfluxUrl(), config.getSinkInfluxUsername(), config.getSinkInfluxPassword()); 34 | firehoseInstrumentation.logInfo("InfluxDB connection established"); 35 | 36 | return new InfluxSink(new FirehoseInstrumentation(statsDReporter, InfluxSink.class), "influx.db", config, stencilClient.getParser(config.getInputSchemaProtoClass()), client, stencilClient); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/jdbc/JdbcConnectionPool.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.jdbc; 2 | 3 | import java.sql.Connection; 4 | import java.sql.SQLException; 5 | 6 | /** 7 | * Interface for database connection pool. 8 | */ 9 | public interface JdbcConnectionPool { 10 | /** 11 | * Get connection from the pool. 12 | * 13 | * @return the connection 14 | * @throws SQLException the sql exception 15 | */ 16 | Connection getConnection() throws SQLException; 17 | 18 | /** 19 | * Release the connection held. 20 | * 21 | * @param connection the connection 22 | */ 23 | void release(Connection connection) throws SQLException; 24 | 25 | /** 26 | * Shutdown the connection pool. 27 | * 28 | * @throws InterruptedException the interrupted exception 29 | */ 30 | void shutdown() throws InterruptedException; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/jdbc/field/JdbcDefaultField.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.jdbc.field; 2 | 3 | public class JdbcDefaultField implements JdbcField { 4 | private Object columnValue; 5 | 6 | public JdbcDefaultField(Object columnValue) { 7 | this.columnValue = columnValue; 8 | } 9 | 10 | @Override 11 | public Object getColumn() throws RuntimeException { 12 | return columnValue; 13 | } 14 | 15 | @Override 16 | public boolean canProcess() { 17 | return false; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/jdbc/field/JdbcField.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.jdbc.field; 2 | 3 | /** 4 | * Jdbc field for JDBC sink. 5 | */ 6 | public interface JdbcField { 7 | /** 8 | * Get column value for the field. 9 | * 10 | * @return the column 11 | * @throws RuntimeException the runtime exception 12 | */ 13 | Object getColumn() throws RuntimeException; 14 | 15 | /** 16 | * @return true if this jdbc field is set by config 17 | */ 18 | boolean canProcess(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/jdbc/field/JdbcFieldFactory.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.jdbc.field; 2 | 3 | import org.raystack.firehose.sink.jdbc.field.message.JdbcDefaultMessageField; 4 | import org.raystack.firehose.sink.jdbc.field.message.JdbcCollectionField; 5 | import org.raystack.firehose.sink.jdbc.field.message.JdbcTimestampField; 6 | import com.google.protobuf.Descriptors; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | /** 12 | * Jdbc field factory. 13 | */ 14 | public class JdbcFieldFactory { 15 | /** 16 | * Returns the field based on configuration. 17 | * 18 | * @param columnValue the column value 19 | * @param fieldDescriptor the field descriptor 20 | * @return the field 21 | */ 22 | public static JdbcField getField(Object columnValue, Descriptors.FieldDescriptor fieldDescriptor) { 23 | List jdbcFields = Arrays.asList( 24 | new JdbcCollectionField(columnValue, fieldDescriptor), 25 | new JdbcMapField(columnValue, fieldDescriptor), 26 | new JdbcTimestampField(columnValue), 27 | new JdbcDefaultMessageField(columnValue)); 28 | return jdbcFields.stream() 29 | .filter(JdbcField::canProcess) 30 | .findFirst() 31 | .orElseGet(() -> new JdbcDefaultField(columnValue)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/jdbc/field/JdbcMapField.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.jdbc.field; 2 | 3 | import com.google.protobuf.Descriptors; 4 | import com.google.protobuf.DynamicMessage; 5 | import org.json.simple.JSONObject; 6 | 7 | import java.util.HashMap; 8 | import java.util.List; 9 | 10 | /** 11 | * Jdbc map field. 12 | */ 13 | public class JdbcMapField implements JdbcField { 14 | private Object columnValue; 15 | private Descriptors.FieldDescriptor fieldDescriptor; 16 | 17 | 18 | public JdbcMapField(Object columnValue, Descriptors.FieldDescriptor fieldDescriptor) { 19 | this.columnValue = columnValue; 20 | this.fieldDescriptor = fieldDescriptor; 21 | } 22 | 23 | @Override 24 | public Object getColumn() throws RuntimeException { 25 | HashMap columnFields = new HashMap<>(); 26 | List values = (List) this.columnValue; 27 | for (DynamicMessage dynamicMessage : values) { 28 | Object[] data = dynamicMessage.getAllFields().values().toArray(); 29 | Object mapValue = data.length > 1 ? data[1] : ""; 30 | columnFields.put((String) data[0], mapValue); 31 | } 32 | String columnEntry = JSONObject.toJSONString(columnFields); 33 | return columnEntry; 34 | } 35 | 36 | @Override 37 | public boolean canProcess() { 38 | return fieldDescriptor.isMapField(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/jdbc/field/message/JdbcCollectionField.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.jdbc.field.message; 2 | 3 | import org.raystack.firehose.sink.jdbc.field.JdbcField; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.protobuf.Descriptors; 6 | import com.google.protobuf.Message; 7 | 8 | import java.util.Collection; 9 | import java.util.Optional; 10 | import java.util.stream.Collectors; 11 | 12 | public class JdbcCollectionField implements JdbcField { 13 | private Object columnValue; 14 | private Descriptors.FieldDescriptor fieldDescriptor; 15 | 16 | public JdbcCollectionField(Object columnValue, Descriptors.FieldDescriptor fieldDescriptor) { 17 | this.columnValue = columnValue; 18 | this.fieldDescriptor = fieldDescriptor; 19 | } 20 | 21 | @Override 22 | public Object getColumn() throws RuntimeException { 23 | Collection collectionOfMessages = (Collection) columnValue; 24 | Optional first = collectionOfMessages.stream().findFirst(); 25 | if (first.isPresent() && first.get() instanceof Message) { 26 | Object messageJsons = collectionOfMessages 27 | .stream() 28 | .map(cValue -> new JdbcDefaultMessageField(cValue).getColumn().toString()) 29 | .collect(Collectors.joining(",")); 30 | return "[" + messageJsons + "]"; 31 | } else { 32 | return new GsonBuilder().create().toJson(collectionOfMessages); 33 | } 34 | } 35 | 36 | @Override 37 | public boolean canProcess() { 38 | return columnValue instanceof Collection && !fieldDescriptor.isMapField(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/jdbc/field/message/JdbcDefaultMessageField.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.jdbc.field.message; 2 | 3 | import org.raystack.firehose.sink.jdbc.field.JdbcField; 4 | import com.google.protobuf.InvalidProtocolBufferException; 5 | import com.google.protobuf.Message; 6 | import com.google.protobuf.util.JsonFormat; 7 | 8 | public class JdbcDefaultMessageField implements JdbcField { 9 | private Object columnValue; 10 | private JsonFormat.Printer jsonPrinter = JsonFormat.printer() 11 | .omittingInsignificantWhitespace() 12 | .preservingProtoFieldNames() 13 | .includingDefaultValueFields(); 14 | 15 | public JdbcDefaultMessageField(Object columnValue) { 16 | this.columnValue = columnValue; 17 | } 18 | 19 | @Override 20 | public Object getColumn() throws RuntimeException { 21 | try { 22 | columnValue = this.jsonPrinter.print((Message) columnValue); 23 | } catch (InvalidProtocolBufferException e) { 24 | throw new RuntimeException(e); 25 | } 26 | return columnValue; 27 | } 28 | 29 | @Override 30 | public boolean canProcess() { 31 | return columnValue instanceof Message; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/jdbc/field/message/JdbcTimestampField.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.jdbc.field.message; 2 | 3 | import org.raystack.firehose.sink.jdbc.field.JdbcField; 4 | import com.google.protobuf.Descriptors; 5 | import com.google.protobuf.DynamicMessage; 6 | import com.google.protobuf.Timestamp; 7 | 8 | import java.time.Instant; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class JdbcTimestampField implements JdbcField { 13 | private Object columnValue; 14 | 15 | public JdbcTimestampField(Object columnValue) { 16 | this.columnValue = columnValue; 17 | } 18 | 19 | @Override 20 | public Object getColumn() { 21 | List fieldDescriptors = ((DynamicMessage) columnValue).getDescriptorForType().getFields(); 22 | ArrayList timeFields = new ArrayList<>(); 23 | for (Descriptors.FieldDescriptor fieldDescriptor : fieldDescriptors) { 24 | timeFields.add(((DynamicMessage) columnValue).getField(fieldDescriptor)); 25 | } 26 | Instant instant = Instant.ofEpochSecond((long) timeFields.get(0), ((Integer) timeFields.get(1)).longValue()); 27 | return instant; 28 | } 29 | 30 | @Override 31 | public boolean canProcess() { 32 | return columnValue instanceof DynamicMessage && ((DynamicMessage) columnValue).getDescriptorForType().getName().equals(Timestamp.class.getSimpleName()); 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/prometheus/PromSinkConstants.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.prometheus; 2 | 3 | public class PromSinkConstants { 4 | 5 | public static final String CONTENT_ENCODING = "Content-Encoding"; 6 | public static final String PROMETHEUS_REMOTE_WRITE_VERSION = "X-Prometheus-Remote-Write-Version"; 7 | public static final String CONTENT_ENCODING_DEFAULT = "snappy"; 8 | public static final String PROMETHEUS_REMOTE_WRITE_VERSION_DEFAULT = "0.1.0"; 9 | 10 | public static final String FIELD_NAME_MAPPING_ERROR_MESSAGE = "field index mapping cannot be empty; at least one field value is required"; 11 | 12 | public static final String PROMETHEUS_LABEL_FOR_METRIC_NAME = "__name__"; 13 | public static final String KAFKA_PARTITION = "kafka_partition"; 14 | 15 | public static final long SECONDS_SCALED_TO_MILLI = 1000L; 16 | public static final long MILLIS_SCALED_TO_NANOS = 1000000L; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/prometheus/builder/HeaderBuilder.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.prometheus.builder; 2 | 3 | import java.util.Arrays; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.stream.Collectors; 7 | 8 | import static org.raystack.firehose.sink.prometheus.PromSinkConstants.*; 9 | 10 | /** 11 | * Builder for prometheus request header. 12 | */ 13 | public class HeaderBuilder { 14 | 15 | private final Map baseHeaders; 16 | 17 | /** 18 | * Instantiates a new Header builder. 19 | * 20 | * @param headerConfig the header config 21 | */ 22 | public HeaderBuilder(String headerConfig) { 23 | baseHeaders = Arrays.stream(headerConfig.split(",")) 24 | .filter(kv -> !kv.trim().isEmpty()).map(kv -> kv.split(":")) 25 | .collect(Collectors.toMap(kv -> kv[0], kv -> kv[1])); 26 | } 27 | 28 | /** 29 | * build prometheus request header. 30 | * 31 | * @return prometheus request header 32 | */ 33 | public Map build() { 34 | Map headers = new HashMap<>(baseHeaders); 35 | headers.put(CONTENT_ENCODING, CONTENT_ENCODING_DEFAULT); 36 | headers.put(PROMETHEUS_REMOTE_WRITE_VERSION, PROMETHEUS_REMOTE_WRITE_VERSION_DEFAULT); 37 | return headers; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/prometheus/builder/PrometheusLabel.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.prometheus.builder; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class PrometheusLabel { 11 | private String name; 12 | private String value; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/prometheus/builder/PrometheusMetric.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.prometheus.builder; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class PrometheusMetric { 11 | private String name; 12 | private double value; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sink/prometheus/builder/RequestEntityBuilder.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.prometheus.builder; 2 | 3 | import cortexpb.Cortex; 4 | import org.apache.http.entity.ByteArrayEntity; 5 | import org.xerial.snappy.Snappy; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * Builder for prometheus request entity. 11 | */ 12 | public class RequestEntityBuilder { 13 | 14 | /** 15 | * Build prometheus request entity. 16 | * the cortex write request object will be compressed using snappy 17 | * 18 | * @param writeRequest the cortex write request object 19 | * @return ByteArrayEntity 20 | * @throws IOException the io exception 21 | */ 22 | public ByteArrayEntity buildHttpEntity(Cortex.WriteRequest writeRequest) throws IOException { 23 | byte[] compressed = Snappy.compress(writeRequest.toByteArray()); 24 | return new ByteArrayEntity(compressed); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sinkdecorator/BackOff.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sinkdecorator; 2 | 3 | import org.raystack.firehose.metrics.FirehoseInstrumentation; 4 | 5 | import lombok.AllArgsConstructor; 6 | 7 | @AllArgsConstructor 8 | public class BackOff { 9 | 10 | private FirehoseInstrumentation firehoseInstrumentation; 11 | 12 | public void inMilliSeconds(long milliseconds) { 13 | try { 14 | Thread.sleep(milliseconds); 15 | } catch (InterruptedException e) { 16 | firehoseInstrumentation.captureNonFatalError("firehose_error_event", e, "Backoff thread sleep for {} milliseconds interrupted : {} {}", 17 | milliseconds, e.getClass(), e.getMessage()); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sinkdecorator/BackOffProvider.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sinkdecorator; 2 | 3 | /** 4 | * Interface to provide back-off functionality. 5 | * Various implementations can use different backoff strategies 6 | * and plug it in for use in retry scenario. 7 | */ 8 | public interface BackOffProvider { 9 | /** 10 | * backs off for a specific duration depending on the number of attempts. 11 | * 12 | * @param attemptCount the number of attempt. 13 | */ 14 | void backOff(int attemptCount); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sinkdecorator/SinkDecorator.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sinkdecorator; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import org.raystack.firehose.exception.DeserializerException; 5 | import org.raystack.firehose.sink.Sink; 6 | 7 | import java.io.IOException; 8 | import java.util.List; 9 | 10 | /** 11 | * Sink decorator provides internal processing on the use provided sink type. 12 | */ 13 | public class SinkDecorator implements Sink { 14 | 15 | private final Sink sink; 16 | 17 | /** 18 | * Instantiates a new Sink decorator. 19 | * 20 | * @param sink wrapped sink object 21 | */ 22 | public SinkDecorator(Sink sink) { 23 | this.sink = sink; 24 | } 25 | 26 | @Override 27 | public List pushMessage(List message) throws IOException, DeserializerException { 28 | return sink.pushMessage(message); 29 | } 30 | 31 | @Override 32 | public void close() throws IOException { 33 | sink.close(); 34 | } 35 | 36 | @Override 37 | public void calculateCommittableOffsets() { 38 | sink.calculateCommittableOffsets(); 39 | } 40 | 41 | @Override 42 | public boolean canManageOffsets() { 43 | return sink.canManageOffsets(); 44 | } 45 | 46 | @Override 47 | public void addOffsetsAndSetCommittable(List messageList) { 48 | sink.addOffsetsAndSetCommittable(messageList); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sinkdecorator/SinkFinal.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sinkdecorator; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import org.raystack.firehose.exception.DeserializerException; 5 | import org.raystack.firehose.metrics.FirehoseInstrumentation; 6 | import org.raystack.firehose.metrics.Metrics; 7 | import org.raystack.firehose.sink.Sink; 8 | 9 | import java.io.IOException; 10 | import java.util.List; 11 | 12 | public class SinkFinal extends SinkDecorator { 13 | private final FirehoseInstrumentation firehoseInstrumentation; 14 | 15 | /** 16 | * Instantiates a new Sink decorator. 17 | * 18 | * @param sink wrapped sink object 19 | */ 20 | 21 | public SinkFinal(Sink sink, FirehoseInstrumentation firehoseInstrumentation) { 22 | super(sink); 23 | this.firehoseInstrumentation = firehoseInstrumentation; 24 | } 25 | 26 | @Override 27 | public List pushMessage(List inputMessages) throws IOException, DeserializerException { 28 | List failedMessages = super.pushMessage(inputMessages); 29 | if (failedMessages.size() > 0) { 30 | firehoseInstrumentation.logInfo("Ignoring messages {}", failedMessages.size()); 31 | firehoseInstrumentation.captureGlobalMessageMetrics(Metrics.MessageScope.IGNORED, failedMessages.size()); 32 | } 33 | return failedMessages; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/sinkdecorator/SinkWithFailHandler.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sinkdecorator; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import org.raystack.firehose.error.ErrorHandler; 5 | import org.raystack.firehose.error.ErrorScope; 6 | import org.raystack.firehose.exception.DeserializerException; 7 | import org.raystack.firehose.exception.SinkException; 8 | import org.raystack.firehose.sink.Sink; 9 | 10 | import java.io.IOException; 11 | import java.util.List; 12 | import java.util.Optional; 13 | 14 | /** 15 | * Sink that will throw exception when error match configuration. 16 | * This is intended to be used to trigger consumer failure based on configured error types 17 | */ 18 | public class SinkWithFailHandler extends SinkDecorator { 19 | private final ErrorHandler errorHandler; 20 | 21 | /** 22 | * Instantiates a new Sink decorator. 23 | * 24 | * @param sink wrapped sink object 25 | * @param errorHandler to process errors 26 | */ 27 | public SinkWithFailHandler(Sink sink, ErrorHandler errorHandler) { 28 | super(sink); 29 | this.errorHandler = errorHandler; 30 | } 31 | 32 | @Override 33 | public List pushMessage(List inputMessages) throws IOException, DeserializerException { 34 | List messages = super.pushMessage(inputMessages); 35 | Optional messageOptional = messages.stream().filter(x -> errorHandler.filter(x, ErrorScope.FAIL)).findFirst(); 36 | if (messageOptional.isPresent()) { 37 | throw new SinkException("Failing Firehose for error " + messageOptional.get().getErrorInfo().getErrorType(), 38 | messageOptional.get().getErrorInfo().getException()); 39 | } 40 | return messages; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/tracer/SinkTracer.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.tracer; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import io.opentracing.References; 5 | import io.opentracing.Span; 6 | import io.opentracing.SpanContext; 7 | import io.opentracing.Tracer; 8 | import io.opentracing.contrib.kafka.TracingKafkaUtils; 9 | import io.opentracing.tag.Tags; 10 | import lombok.AllArgsConstructor; 11 | import lombok.Getter; 12 | 13 | import java.io.Closeable; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | 18 | /** 19 | * Sink Tracer. 20 | */ 21 | @AllArgsConstructor 22 | @Getter 23 | public class SinkTracer implements Traceable, Closeable { 24 | private Tracer tracer; 25 | private String name; 26 | private boolean enabled; 27 | 28 | @Override 29 | public List startTrace(List messages) { 30 | if (enabled) { 31 | return messages.stream().map(m -> traceMessage(m)).collect(Collectors.toList()); 32 | } else { 33 | return new ArrayList<>(); 34 | } 35 | } 36 | 37 | private Span traceMessage(Message message) { 38 | SpanContext parentContext = null; 39 | if (message.getHeaders() != null) { 40 | parentContext = TracingKafkaUtils.extractSpanContext(message.getHeaders(), tracer); 41 | } 42 | 43 | Tracer.SpanBuilder spanBuilder = tracer 44 | .buildSpan(name) 45 | .withTag(Tags.COMPONENT, name) 46 | .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CONSUMER); 47 | 48 | if (parentContext != null) { 49 | spanBuilder.addReference(References.FOLLOWS_FROM, parentContext); 50 | } 51 | return spanBuilder.start(); 52 | 53 | } 54 | 55 | @Override 56 | public void close() { 57 | tracer.close(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/tracer/Traceable.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.tracer; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import io.opentracing.Span; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * An interface to trace the transactions. 10 | */ 11 | public interface Traceable { 12 | 13 | /** 14 | * Start trace. 15 | * 16 | * @param messages messages to trace 17 | * @return the list of spans 18 | */ 19 | List startTrace(List messages); 20 | 21 | /** 22 | * Finish trace. 23 | * 24 | * @param spans the spans 25 | */ 26 | default void finishTrace(List spans) { 27 | spans.forEach(span -> span.finish()); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/utils/ConsumerRebalancer.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.utils; 2 | 3 | import org.raystack.firehose.metrics.FirehoseInstrumentation; 4 | import lombok.AllArgsConstructor; 5 | import org.apache.kafka.clients.consumer.ConsumerRebalanceListener; 6 | import org.apache.kafka.common.TopicPartition; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collection; 10 | 11 | /** 12 | * A callback to log when the partition rebalancing happens. 13 | */ 14 | @AllArgsConstructor 15 | public class ConsumerRebalancer implements ConsumerRebalanceListener { 16 | 17 | private FirehoseInstrumentation firehoseInstrumentation; 18 | 19 | /** 20 | * Function to run On partitions revoked. 21 | * 22 | * @param partitions list of partitions 23 | */ 24 | @Override 25 | public void onPartitionsRevoked(Collection partitions) { 26 | firehoseInstrumentation.logWarn("Partitions Revoked {}", Arrays.toString(partitions.toArray())); 27 | } 28 | 29 | /** 30 | * Function to run On partitions assigned. 31 | * 32 | * @param partitions list of partitions 33 | */ 34 | @Override 35 | public void onPartitionsAssigned(Collection partitions) { 36 | firehoseInstrumentation.logInfo("Partitions Assigned {}", Arrays.toString(partitions.toArray())); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/raystack/firehose/utils/StencilUtils.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.utils; 2 | 3 | import org.raystack.firehose.config.AppConfig; 4 | import com.timgroup.statsd.StatsDClient; 5 | import org.raystack.stencil.SchemaUpdateListener; 6 | import org.raystack.stencil.config.StencilConfig; 7 | 8 | public class StencilUtils { 9 | public static StencilConfig getStencilConfig( 10 | AppConfig appconfig, 11 | StatsDClient statsDClient, 12 | SchemaUpdateListener schemaUpdateListener) { 13 | return StencilConfig.builder() 14 | .cacheAutoRefresh(appconfig.getSchemaRegistryStencilCacheAutoRefresh()) 15 | .cacheTtlMs(appconfig.getSchemaRegistryStencilCacheTtlMs()) 16 | .statsDClient(statsDClient) 17 | .fetchHeaders(appconfig.getSchemaRegistryFetchHeaders()) 18 | .fetchBackoffMinMs(appconfig.getSchemaRegistryStencilFetchBackoffMinMs()) 19 | .fetchRetries(appconfig.getSchemaRegistryStencilFetchRetries()) 20 | .fetchTimeoutMs(appconfig.getSchemaRegistryStencilFetchTimeoutMs()) 21 | .refreshStrategy(appconfig.getSchemaRegistryStencilRefreshStrategy()) 22 | .updateListener(schemaUpdateListener) 23 | .build(); 24 | } 25 | 26 | public static StencilConfig getStencilConfig(AppConfig appconfig, StatsDClient statsDClient) { 27 | return getStencilConfig(appconfig, statsDClient, null); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/proto/cortex.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package cortexpb; 4 | 5 | option go_package = "cortexpb"; 6 | 7 | import "gogo.proto"; 8 | 9 | option (gogoproto.marshaler_all) = true; 10 | option (gogoproto.unmarshaler_all) = true; 11 | 12 | message WriteRequest { 13 | repeated TimeSeries timeseries = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "PreallocTimeseries"]; 14 | enum SourceEnum { 15 | API = 0; 16 | RULE = 1; 17 | } 18 | SourceEnum Source = 2; 19 | repeated MetricMetadata metadata = 3 [(gogoproto.nullable) = true]; 20 | 21 | bool skip_label_name_validation = 1000; //set intentionally high to keep WriteRequest compatible with upstream Prometheus 22 | } 23 | 24 | message WriteResponse {} 25 | 26 | message TimeSeries { 27 | repeated LabelPair labels = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "LabelAdapter"]; 28 | // Sorted by time, oldest sample first. 29 | repeated Sample samples = 2 [(gogoproto.nullable) = false]; 30 | } 31 | 32 | message LabelPair { 33 | string name = 1; 34 | string value = 2; 35 | } 36 | 37 | message Sample { 38 | double value = 1; 39 | int64 timestamp_ms = 2; 40 | } 41 | 42 | message MetricMetadata { 43 | enum MetricType { 44 | UNKNOWN = 0; 45 | COUNTER = 1; 46 | GAUGE = 2; 47 | HISTOGRAM = 3; 48 | GAUGEHISTOGRAM = 4; 49 | SUMMARY = 5; 50 | INFO = 6; 51 | STATESET = 7; 52 | } 53 | 54 | MetricType type = 1; 55 | string metric_family_name = 2; 56 | string help = 4; 57 | string unit = 5; 58 | } 59 | 60 | message Metric { 61 | repeated LabelPair labels = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "LabelAdapter"]; 62 | } -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.grpc.LoadBalancerProvider: -------------------------------------------------------------------------------- 1 | io.grpc.internal.PickFirstLoadBalancerProvider -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.grpc.NameResolverProvider: -------------------------------------------------------------------------------- 1 | io.grpc.internal.DnsNameResolverProvider -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | REFRESH_SERVICE_CATALOG_ON_DISCOVERY: true -------------------------------------------------------------------------------- /src/main/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %date{"yyyy-MM-dd'T'HH:mm:ss,SSSXXX", UTC} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | true 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/config/GCSConfigTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | import org.aeonbits.owner.ConfigFactory; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class GCSConfigTest { 11 | 12 | @Test 13 | public void shouldParseConfigForSink() { 14 | Map properties = new HashMap() {{ 15 | put("SINK_OBJECT_STORAGE_LOCAL_DIRECTORY", "/tmp/test"); 16 | put("SINK_OBJECT_STORAGE_GCS_GOOGLE_CLOUD_PROJECT_ID", "GCloud-ID"); 17 | put("SINK_OBJECT_STORAGE_GCS_BUCKET_NAME", "testing"); 18 | put("SINK_OBJECT_STORAGE_GCS_CREDENTIAL_PATH", "/tmp/path/to/cred"); 19 | put("GCS_TYPE", "SINK_OBJECT_STORAGE"); 20 | }}; 21 | GCSConfig gcsConfig = ConfigFactory.create(GCSConfig.class, properties); 22 | Assert.assertEquals("GCloud-ID", gcsConfig.getGCloudProjectID()); 23 | Assert.assertEquals("testing", gcsConfig.getGCSBucketName()); 24 | Assert.assertEquals("/tmp/path/to/cred", gcsConfig.getGCSCredentialPath()); 25 | } 26 | 27 | @Test 28 | public void shouldParseConfigForDLQ() { 29 | Map properties = new HashMap() {{ 30 | put("DLQ_OBJECT_STORAGE_LOCAL_DIRECTORY", "/tmp/test"); 31 | put("DLQ_OBJECT_STORAGE_GCS_GOOGLE_CLOUD_PROJECT_ID", "GCloud-ID"); 32 | put("DLQ_OBJECT_STORAGE_GCS_BUCKET_NAME", "testing"); 33 | put("DLQ_OBJECT_STORAGE_GCS_CREDENTIAL_PATH", "/tmp/path/to/cred"); 34 | put("GCS_TYPE", "DLQ_OBJECT_STORAGE"); 35 | }}; 36 | GCSConfig gcsConfig = ConfigFactory.create(GCSConfig.class, properties); 37 | Assert.assertEquals("GCloud-ID", gcsConfig.getGCloudProjectID()); 38 | Assert.assertEquals("testing", gcsConfig.getGCSBucketName()); 39 | Assert.assertEquals("/tmp/path/to/cred", gcsConfig.getGCSCredentialPath()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/config/RangeToHashMapConverterTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config; 2 | 3 | import org.raystack.firehose.config.converter.RangeToHashMapConverter; 4 | import org.junit.Test; 5 | 6 | import java.util.Map; 7 | 8 | import static org.junit.Assert.assertArrayEquals; 9 | 10 | public class RangeToHashMapConverterTest { 11 | 12 | @Test 13 | public void shouldConvertRangeToHashMap() { 14 | Map actualHashedRanges = new RangeToHashMapConverter().convert(null, "100-103"); 15 | assertArrayEquals(new Integer[]{100, 101, 102, 103}, actualHashedRanges.keySet().toArray()); 16 | } 17 | 18 | @Test 19 | public void shouldConvertRangesToHashMap() { 20 | Map actualHashedRanges = new RangeToHashMapConverter().convert(null, "100-103,200-203"); 21 | assertArrayEquals(new Integer[]{100, 101, 102, 103, 200, 201, 202, 203}, actualHashedRanges.keySet().toArray()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/config/converter/InputSchemaTypeConverterTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import org.raystack.firehose.config.enums.InputSchemaType; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | public class InputSchemaTypeConverterTest { 8 | 9 | @Test 10 | public void shouldConvertSchemaType() { 11 | InputSchemaTypeConverter converter = new InputSchemaTypeConverter(); 12 | InputSchemaType schemaType = converter.convert(null, "PROTOBUF"); 13 | Assert.assertEquals(InputSchemaType.PROTOBUF, schemaType); 14 | schemaType = converter.convert(null, "JSON"); 15 | Assert.assertEquals(InputSchemaType.JSON, schemaType); 16 | } 17 | 18 | @Test 19 | public void shouldConvertSchemaTypeWithLowerCase() { 20 | InputSchemaTypeConverter converter = new InputSchemaTypeConverter(); 21 | InputSchemaType schemaType = converter.convert(null, " json "); 22 | Assert.assertEquals(InputSchemaType.JSON, schemaType); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/config/converter/SchemaRegistryHeadersConverterTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.config.converter; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.raystack.firehose.config.AppConfig; 7 | import org.aeonbits.owner.ConfigFactory; 8 | import org.apache.http.message.BasicHeader; 9 | import org.junit.Assert; 10 | import org.junit.Test; 11 | 12 | public class SchemaRegistryHeadersConverterTest { 13 | @Test 14 | public void testConvertIfFetchHeadersValueEmpty() { 15 | Map properties = new HashMap() { 16 | { 17 | put("SCHEMA_REGISTRY_STENCIL_FETCH_HEADERS", ""); 18 | } 19 | }; 20 | AppConfig config = ConfigFactory.create(AppConfig.class, properties); 21 | Assert.assertEquals(0, config.getSchemaRegistryFetchHeaders().size()); 22 | } 23 | 24 | @Test 25 | public void shouldReturnZeroIfPropertyNotMentioned() { 26 | Map properties = new HashMap() { 27 | }; 28 | AppConfig config = ConfigFactory.create(AppConfig.class, properties); 29 | Assert.assertEquals(0, config.getSchemaRegistryFetchHeaders().size()); 30 | } 31 | 32 | @Test 33 | public void shouldConvertHeaderKeyValuesWithHeaderObject() { 34 | Map properties = new HashMap() { 35 | { 36 | put("SCHEMA_REGISTRY_STENCIL_FETCH_HEADERS", "key1:value1 ,,, key2 : value2,"); 37 | } 38 | }; 39 | AppConfig config = ConfigFactory.create(AppConfig.class, properties); 40 | Assert.assertEquals((new BasicHeader("key1", "value1")).toString(), config.getSchemaRegistryFetchHeaders().get(0).toString()); 41 | Assert.assertEquals((new BasicHeader("key2", "value2")).toString(), config.getSchemaRegistryFetchHeaders().get(1).toString()); 42 | Assert.assertEquals(2, config.getSchemaRegistryFetchHeaders().size()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/converter/ProtoTimeConverterTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.converter; 2 | 3 | import com.google.protobuf.Timestamp; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class ProtoTimeConverterTest { 9 | @Test 10 | public void shouldReturnProperTimestamp() throws Exception { 11 | Timestamp timestamp = Timestamp.newBuilder().setSeconds(1479135490).setNanos(333).build(); 12 | assertEquals(1479135490000000333L, ProtoTimeConverter.getEpochTimeInNanoSeconds(timestamp)); 13 | } 14 | 15 | 16 | @Test 17 | public void shouldReturnProperTimestampForOnlySeconds() throws Exception { 18 | Timestamp timestamp = Timestamp.newBuilder().setSeconds(1479135490).build(); 19 | assertEquals(1479135490000000000L, ProtoTimeConverter.getEpochTimeInNanoSeconds(timestamp)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/serializer/JsonWrappedProtoByteTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.serializer; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.Base64; 6 | 7 | import org.raystack.firehose.exception.DeserializerException; 8 | import org.raystack.firehose.message.Message; 9 | 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | 13 | public class JsonWrappedProtoByteTest { 14 | 15 | private Message message; 16 | 17 | @Before 18 | public void setup() { 19 | String logMessage = "CgYIyOm+xgUSBgiE6r7GBRgNIICAgIDA9/y0LigCMAM\u003d"; 20 | String logKey = "CgYIyOm+xgUSBgiE6r7GBRgNIICAgIDA9/y0LigC"; 21 | message = new Message(Base64.getDecoder().decode(logKey.getBytes()), 22 | Base64.getDecoder().decode(logMessage.getBytes()), "sample-topic", 0, 100); 23 | } 24 | 25 | @Test 26 | public void shouldWrapProtoByteInsideJson() throws DeserializerException { 27 | JsonWrappedProtoByte jsonWrappedProtoByte = new JsonWrappedProtoByte(); 28 | assertEquals("{\"topic\":\"sample-topic\",\"log_key\":\"CgYIyOm+xgUSBgiE6r7GBRgNIICAgIDA9/y0LigC\",\"log_message\":\"CgYIyOm+xgUSBgiE6r7GBRgNIICAgIDA9/y0LigCMAM\\u003d\"}", jsonWrappedProtoByte.serialize(message)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/SinkFactoryUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public class SinkFactoryUtilsTest { 10 | 11 | @Test 12 | public void shouldAddSinkConnectorConfigs() { 13 | Map env = new HashMap() {{ 14 | put("INPUT_SCHEMA_PROTO_CLASS", "com.test.SomeProtoClass"); 15 | put("INPUT_SCHEMA_PROTO_ALLOW_UNKNOWN_FIELDS_ENABLE", "true"); 16 | }}; 17 | Map configs = SinkFactoryUtils.addAdditionalConfigsForSinkConnectors(env); 18 | Assert.assertEquals("com.test.SomeProtoClass", configs.get("SINK_CONNECTOR_SCHEMA_PROTO_MESSAGE_CLASS")); 19 | Assert.assertEquals("com.test.SomeProtoClass", configs.get("SINK_CONNECTOR_SCHEMA_PROTO_KEY_CLASS")); 20 | Assert.assertEquals("firehose_", configs.get("SINK_METRICS_APPLICATION_PREFIX")); 21 | Assert.assertEquals("true", configs.get("SINK_CONNECTOR_SCHEMA_PROTO_ALLOW_UNKNOWN_FIELDS_ENABLE")); 22 | Assert.assertEquals("LOG_MESSAGE", configs.get("SINK_CONNECTOR_SCHEMA_MESSAGE_MODE")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/bigquery/BigquerySinkUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.bigquery; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.function.Function; 9 | 10 | public class BigquerySinkUtilsTest { 11 | @Test 12 | public void shouldGetRowIdCreator() { 13 | Function, String> rowIDCreator = BigquerySinkUtils.getRowIDCreator(); 14 | String rowId = rowIDCreator.apply(new HashMap() {{ 15 | put("message_topic", "test"); 16 | put("message_partition", 10); 17 | put("message_offset", 2); 18 | put("something_else", false); 19 | }}); 20 | Assert.assertEquals("test_10_2", rowId); 21 | } 22 | 23 | @Test 24 | public void shouldAddMetadataColumns() { 25 | Map config = new HashMap() {{ 26 | put("test", "test"); 27 | }}; 28 | BigquerySinkUtils.addMetadataColumns(config); 29 | Assert.assertEquals(config.get("SINK_BIGQUERY_METADATA_COLUMNS_TYPES"), "message_offset=integer,message_topic=string,load_time=timestamp,message_timestamp=timestamp,message_partition=integer"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/blob/TestUtils.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob; 2 | 3 | import com.google.protobuf.Descriptors; 4 | import com.google.protobuf.DynamicMessage; 5 | import org.raystack.firehose.message.Message; 6 | import org.raystack.firehose.sink.blob.message.KafkaMetadataUtils; 7 | import org.raystack.firehose.sink.blob.proto.KafkaMetadataProtoMessageUtils; 8 | 9 | import java.time.Instant; 10 | 11 | public class TestUtils { 12 | 13 | public static DynamicMessage createMetadata(String kafkaMetadataColumnName, Instant eventTimestamp, long offset, int partition, String topic) { 14 | Message message = new Message("".getBytes(), "".getBytes(), topic, partition, offset, null, eventTimestamp.toEpochMilli(), eventTimestamp.toEpochMilli()); 15 | Descriptors.FileDescriptor fileDescriptor = KafkaMetadataProtoMessageUtils.createFileDescriptor(kafkaMetadataColumnName); 16 | return KafkaMetadataUtils.createKafkaMetadata(fileDescriptor, message, kafkaMetadataColumnName); 17 | } 18 | 19 | public static DynamicMessage createMessage(Instant timestamp, int orderNum) { 20 | TestProtoMessage.MessageBuilder messageBuilder = TestProtoMessage.createMessageBuilder(); 21 | return messageBuilder 22 | .setCreatedTime(timestamp) 23 | .setOrderNumber(orderNum).build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/blob/message/RecordTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.message; 2 | 3 | import com.google.protobuf.DynamicMessage; 4 | import org.raystack.firehose.sink.blob.TestUtils; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | import java.time.Instant; 9 | 10 | public class RecordTest { 11 | 12 | private final Instant defaultTimestamp = Instant.parse("2020-01-01T10:00:00.000Z"); 13 | private final int defaultOrderNumber = 100; 14 | private final long defaultOffset = 1L; 15 | private final int defaultPartition = 1; 16 | private final String defaultTopic = "booking-log"; 17 | 18 | 19 | @Test 20 | public void shouldGetTopicFromMetaData() { 21 | DynamicMessage message = TestUtils.createMessage(defaultTimestamp, defaultOrderNumber); 22 | DynamicMessage metadata = TestUtils.createMetadata("", defaultTimestamp, defaultOffset, defaultPartition, defaultTopic); 23 | Record record = new Record(message, metadata); 24 | Assert.assertEquals("booking-log", record.getTopic("")); 25 | } 26 | 27 | @Test 28 | public void shouldGetTopicFromNestedMetaData() { 29 | DynamicMessage message = TestUtils.createMessage(defaultTimestamp, defaultOrderNumber); 30 | DynamicMessage metadata = TestUtils.createMetadata("nested_field", defaultTimestamp, defaultOffset, defaultPartition, defaultTopic); 31 | Record record = new Record(message, metadata); 32 | Assert.assertEquals("booking-log", record.getTopic("nested_field")); 33 | } 34 | 35 | @Test 36 | public void shouldGetTimeStampFromMessage() { 37 | DynamicMessage message = TestUtils.createMessage(defaultTimestamp, defaultOrderNumber); 38 | DynamicMessage metadata = TestUtils.createMetadata("nested_field", defaultTimestamp, defaultOffset, defaultPartition, defaultTopic); 39 | Record record = new Record(message, metadata); 40 | Assert.assertEquals(defaultTimestamp, record.getTimestamp("created_time")); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/blob/proto/TimestampMetadataProtoMessageTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.proto; 2 | 3 | import com.github.os72.protobuf.dynamic.DynamicSchema; 4 | import com.google.protobuf.Descriptors; 5 | import com.google.protobuf.Timestamp; 6 | import org.junit.Test; 7 | 8 | import static org.raystack.firehose.sink.blob.proto.KafkaMetadataProtoMessageUtils.FILE_NAME; 9 | import static org.raystack.firehose.sink.blob.proto.KafkaMetadataProtoMessageUtils.PACKAGE; 10 | import static org.junit.Assert.*; 11 | 12 | public class TimestampMetadataProtoMessageTest { 13 | 14 | private DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder().setName(FILE_NAME).setPackage(PACKAGE); 15 | 16 | @Test 17 | public void shouldGenerateMessageDefinition() throws Descriptors.DescriptorValidationException { 18 | schemaBuilder.addMessageDefinition(TimestampMetadataProtoMessage.createMessageDefinition()); 19 | DynamicSchema dynamicSchema = schemaBuilder.build(); 20 | 21 | Descriptors.Descriptor descriptor = dynamicSchema.getMessageDescriptor(TimestampMetadataProtoMessage.getTypeName()); 22 | assertNotNull(descriptor.findFieldByName(TimestampMetadataProtoMessage.NANOS_FIELD_NAME)); 23 | assertNotNull(descriptor.findFieldByName(TimestampMetadataProtoMessage.SECONDS_FIELD_NAME)); 24 | } 25 | 26 | @Test 27 | public void shouldCreateDynamicMessage() { 28 | Timestamp timestamp = TimestampMetadataProtoMessage.newBuilder().setNanos(1).setSeconds(1) 29 | .build(); 30 | assertEquals(1, timestamp.getNanos()); 31 | assertEquals(1, timestamp.getSeconds()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/blob/writer/local/LocalStorageTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.writer.local; 2 | 3 | 4 | import com.google.protobuf.Descriptors; 5 | import org.raystack.firehose.config.BlobSinkConfig; 6 | import org.raystack.firehose.metrics.FirehoseInstrumentation; 7 | import org.raystack.firehose.sink.blob.Constants; 8 | import org.raystack.firehose.sink.blob.writer.local.policy.WriterPolicy; 9 | import org.junit.Test; 10 | import org.mockito.Mockito; 11 | 12 | import java.nio.file.Paths; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public class LocalStorageTest { 17 | 18 | @Test 19 | public void shouldDeleteFiles() throws Exception { 20 | BlobSinkConfig sinkConfig = Mockito.mock(BlobSinkConfig.class); 21 | List metadataFieldDescriptor = new ArrayList<>(); 22 | List policies = new ArrayList<>(); 23 | FirehoseInstrumentation firehoseInstrumentation = Mockito.mock(FirehoseInstrumentation.class); 24 | LocalStorage storage = new LocalStorage(sinkConfig, null, metadataFieldDescriptor, policies, firehoseInstrumentation); 25 | LocalStorage spy = Mockito.spy(storage); 26 | Mockito.doNothing().when(spy).deleteLocalFile(Paths.get("/tmp/a"), Paths.get("/tmp/.a.crc")); 27 | Mockito.when(sinkConfig.getLocalFileWriterType()).thenReturn(Constants.WriterType.PARQUET); 28 | spy.deleteLocalFile("/tmp/a"); 29 | Mockito.verify(spy, Mockito.times(1)).deleteLocalFile(Paths.get("/tmp/a"), Paths.get("/tmp/.a.crc")); 30 | Mockito.verify(firehoseInstrumentation, Mockito.times(1)).logInfo("Deleting Local File {}", Paths.get("/tmp/a")); 31 | Mockito.verify(firehoseInstrumentation, Mockito.times(1)).logInfo("Deleting Local File {}", Paths.get("/tmp/.a.crc")); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/blob/writer/local/policy/SizeBasedRotatingPolicyTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.writer.local.policy; 2 | 3 | import org.raystack.firehose.sink.blob.writer.local.LocalFileMetadata; 4 | import org.junit.Assert; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.junit.rules.ExpectedException; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.junit.MockitoJUnitRunner; 10 | 11 | @RunWith(MockitoJUnitRunner.class) 12 | public class SizeBasedRotatingPolicyTest { 13 | 14 | private final SizeBasedRotatingPolicy sizeBasedRotatingPolicy = new SizeBasedRotatingPolicy(256); 15 | @Rule 16 | public ExpectedException thrown = ExpectedException.none(); 17 | 18 | @Test 19 | public void shouldNeedRotateWhenWriterDataSizeGreaterThanEqualToMaxFileSize() { 20 | long dataSize = 258L; 21 | LocalFileMetadata metadata = new LocalFileMetadata("/tmp", "/tmp/a/random-file-name-1", 1L, 100L, dataSize); 22 | boolean shouldRotate = sizeBasedRotatingPolicy.shouldRotate(metadata); 23 | Assert.assertTrue(shouldRotate); 24 | } 25 | 26 | @Test 27 | public void shouldNotNeedRotateWhenSizeBelowTheLimit() { 28 | long dataSize = 100L; 29 | LocalFileMetadata metadata = new LocalFileMetadata("/tmp", "/tmp/a/random-file-name-1", 1L, 100L, dataSize); 30 | boolean shouldRotate = sizeBasedRotatingPolicy.shouldRotate(metadata); 31 | Assert.assertFalse(shouldRotate); 32 | } 33 | 34 | @Test 35 | public void shouldThrowExceptionIfInvalid() { 36 | thrown.expect(IllegalArgumentException.class); 37 | thrown.expectMessage("The max size should be a positive integer"); 38 | new SizeBasedRotatingPolicy(-100); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/blob/writer/local/policy/TimeBasedRotatingPolicyTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.blob.writer.local.policy; 2 | 3 | import org.raystack.firehose.sink.blob.writer.local.LocalFileMetadata; 4 | import org.junit.Assert; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.junit.rules.ExpectedException; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.junit.MockitoJUnitRunner; 10 | 11 | @RunWith(MockitoJUnitRunner.class) 12 | public class TimeBasedRotatingPolicyTest { 13 | 14 | private final TimeBasedRotatingPolicy rotatingPolicy = new TimeBasedRotatingPolicy(200); 15 | @Rule 16 | public ExpectedException thrown = ExpectedException.none(); 17 | 18 | @Test 19 | public void shouldRotateWhenElapsedTimeGreaterThanMaxRotatingDuration() throws InterruptedException { 20 | long createdTimestamp = System.currentTimeMillis(); 21 | LocalFileMetadata metadata = new LocalFileMetadata("/tmp", "/tmp/a/random-file-name-1", createdTimestamp, 100L, 100L); 22 | Thread.sleep(300); 23 | boolean shouldRotate = rotatingPolicy.shouldRotate(metadata); 24 | Assert.assertTrue(shouldRotate); 25 | } 26 | 27 | @Test 28 | public void shouldNotRotateWhenElapsedTimeLessThanMaxRotatingDuration() throws InterruptedException { 29 | long createdTimestamp = System.currentTimeMillis(); 30 | LocalFileMetadata metadata = new LocalFileMetadata("/tmp", "/tmp/a/random-file-name-1", createdTimestamp, 100L, 100L); 31 | Thread.sleep(100); 32 | boolean shouldRotate = rotatingPolicy.shouldRotate(metadata); 33 | Assert.assertFalse(shouldRotate); 34 | } 35 | 36 | @Test 37 | public void shouldThrowExceptionIfInvalid() { 38 | thrown.expect(IllegalArgumentException.class); 39 | thrown.expectMessage("The max duration should be a positive integer"); 40 | new TimeBasedRotatingPolicy(-100); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/common/KeyOrMessageParserTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.common; 2 | 3 | 4 | import org.raystack.firehose.config.AppConfig; 5 | import org.raystack.firehose.message.Message; 6 | import org.raystack.firehose.consumer.TestMessage; 7 | import com.google.protobuf.DynamicMessage; 8 | import org.raystack.stencil.Parser; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.mockito.Mock; 13 | import org.mockito.Mockito; 14 | import org.mockito.junit.MockitoJUnitRunner; 15 | 16 | import java.io.IOException; 17 | 18 | @RunWith(MockitoJUnitRunner.class) 19 | public class KeyOrMessageParserTest { 20 | 21 | @Mock 22 | private AppConfig appConfig; 23 | 24 | @Mock 25 | private Parser protoParser; 26 | 27 | private DynamicMessage dynamicMessage; 28 | 29 | private Message message; 30 | 31 | private KeyOrMessageParser parser; 32 | 33 | @Before 34 | public void setup() throws IOException { 35 | dynamicMessage = DynamicMessage.newBuilder(TestMessage.getDescriptor()).build(); 36 | 37 | Mockito.when(appConfig.getKafkaRecordParserMode()).thenReturn("message"); 38 | Mockito.when(protoParser.parse(Mockito.any(byte[].class))).thenReturn(dynamicMessage); 39 | 40 | message = new Message("logKey".getBytes(), "logMessage".getBytes(), "topic", 0, 10); 41 | parser = new KeyOrMessageParser(protoParser, appConfig); 42 | } 43 | 44 | @Test 45 | public void shouldParseMessageByDefault() throws IOException { 46 | parser.parse(message); 47 | 48 | Mockito.verify(protoParser, Mockito.times(1)).parse("logMessage".getBytes()); 49 | } 50 | 51 | @Test 52 | public void shouldParseKeyWhenKafkaMessageParserModeSetToKey() throws IOException { 53 | Mockito.when(appConfig.getKafkaRecordParserMode()).thenReturn("key"); 54 | parser.parse(message); 55 | 56 | Mockito.verify(protoParser, Mockito.times(1)).parse("logKey".getBytes()); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/grpc/GrpcSinkFactoryTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.grpc; 2 | 3 | import org.raystack.firehose.exception.DeserializerException; 4 | import org.raystack.firehose.sink.Sink; 5 | import org.raystack.depot.metrics.StatsDReporter; 6 | import org.raystack.firehose.consumer.TestServerGrpc; 7 | import io.grpc.Server; 8 | import io.grpc.ServerBuilder; 9 | import org.raystack.stencil.client.StencilClient; 10 | import org.junit.Assert; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.mockito.Mock; 14 | 15 | import java.io.IOException; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | import static org.mockito.Mockito.when; 20 | import static org.mockito.MockitoAnnotations.initMocks; 21 | 22 | public class GrpcSinkFactoryTest { 23 | 24 | @Mock 25 | private StatsDReporter statsDReporter; 26 | 27 | @Mock 28 | private TestServerGrpc.TestServerImplBase testGrpcService; 29 | 30 | @Mock 31 | private StencilClient stencilClient; 32 | 33 | // private static ConsulClient consulClient; 34 | 35 | @Before 36 | public void setUp() { 37 | initMocks(this); 38 | } 39 | 40 | 41 | @Test 42 | public void shouldCreateChannelPoolWithHostAndPort() throws IOException, DeserializerException { 43 | when(testGrpcService.bindService()).thenCallRealMethod(); 44 | 45 | Server server = ServerBuilder 46 | .forPort(5000) 47 | .addService(testGrpcService.bindService()) 48 | .build() 49 | .start(); 50 | 51 | Map config = new HashMap<>(); 52 | config.put("SINK_GRPC_METHOD_URL", "org.raystack.firehose.consumer.TestServer/TestRpcMethod"); 53 | config.put("SINK_GRPC_SERVICE_HOST", "localhost"); 54 | config.put("SINK_GRPC_SERVICE_PORT", "5000"); 55 | 56 | 57 | Sink sink = GrpcSinkFactory.create(config, statsDReporter, stencilClient); 58 | 59 | Assert.assertNotNull(sink); 60 | server.shutdownNow(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/http/auth/OAuth2AccessTokenTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.http.auth; 2 | 3 | import org.joda.time.DateTimeUtils; 4 | import org.junit.Assert; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | public class OAuth2AccessTokenTest { 9 | 10 | private OAuth2AccessToken oAuth2AccessToken; 11 | private String accessToken; 12 | 13 | @Before 14 | public void setUp() { 15 | DateTimeUtils.setCurrentMillisFixed(System.currentTimeMillis()); 16 | accessToken = "SAMPLE-TOKEN"; 17 | } 18 | 19 | @Test 20 | public void shouldReturnTrueWhenExpireTimeIsLessThan60Sec() { 21 | oAuth2AccessToken = new OAuth2AccessToken(accessToken, 55); 22 | 23 | Assert.assertTrue(oAuth2AccessToken.isExpired()); 24 | } 25 | 26 | @Test 27 | public void shouldReturnTrueWhenExpireTimeIs60Sec() { 28 | oAuth2AccessToken = new OAuth2AccessToken(accessToken, 60); 29 | 30 | Assert.assertTrue(oAuth2AccessToken.isExpired()); 31 | } 32 | 33 | @Test 34 | public void shouldReturnFalseWhenExpireTimeIsMoreThan60Sec() { 35 | oAuth2AccessToken = new OAuth2AccessToken(accessToken, 62); 36 | 37 | Assert.assertFalse(oAuth2AccessToken.isExpired()); 38 | } 39 | 40 | @Test 41 | public void shouldReturnExpirationTimeAsPassedInParamaters() { 42 | Long expiresIn = 65L; 43 | oAuth2AccessToken = new OAuth2AccessToken(accessToken, 65); 44 | 45 | Assert.assertEquals(expiresIn, oAuth2AccessToken.getExpiresIn()); 46 | } 47 | 48 | @Test 49 | public void shouldReturnDefaultExpirationTimeWhenNotPassedInParamaters() { 50 | Long expiresIn = 3600L; 51 | oAuth2AccessToken = new OAuth2AccessToken(accessToken, null); 52 | 53 | Assert.assertEquals(expiresIn, oAuth2AccessToken.getExpiresIn()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/http/request/body/JsonBodyTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.http.request.body; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.mockito.Mockito.when; 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | import org.raystack.firehose.exception.DeserializerException; 10 | import org.raystack.firehose.message.Message; 11 | import org.raystack.firehose.serializer.MessageSerializer; 12 | 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import org.junit.runner.RunWith; 16 | import org.mockito.Mock; 17 | import org.mockito.junit.MockitoJUnitRunner; 18 | 19 | @RunWith(MockitoJUnitRunner.class) 20 | public class JsonBodyTest { 21 | 22 | @Mock 23 | private MessageSerializer messageSerializer; 24 | 25 | private Message message; 26 | private List messages; 27 | 28 | @Before 29 | public void setUp() { 30 | message = new Message(new byte[] {10, 20 }, new byte[] {1, 2 }, "sample-topic", 0, 100); 31 | messages = Collections.singletonList(message); 32 | } 33 | 34 | @Test 35 | public void shouldReturnSameSizeOfBodyAsEsbMessage() { 36 | JsonBody jsonBody = new JsonBody(messageSerializer); 37 | 38 | List bodyContent; 39 | try { 40 | bodyContent = jsonBody.serialize(messages); 41 | } catch (DeserializerException e) { 42 | throw new RuntimeException(e.toString()); 43 | } 44 | assertEquals(1, bodyContent.size()); 45 | } 46 | 47 | @Test 48 | public void shouldReturnSerializedValueOfMessage() { 49 | List contentString; 50 | String mockSerializeResult = "{\"MockSerializer\": []}"; 51 | try { 52 | when(messageSerializer.serialize(message)).thenReturn(mockSerializeResult); 53 | 54 | JsonBody jsonBody = new JsonBody(messageSerializer); 55 | contentString = jsonBody.serialize(messages); 56 | 57 | } catch (DeserializerException e) { 58 | throw new RuntimeException(e.toString()); 59 | } 60 | 61 | assertEquals(mockSerializeResult, contentString.get(0)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/http/request/entity/RequestEntityBuilderTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.http.request.entity; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | import org.apache.http.entity.StringEntity; 5 | import org.junit.Assert; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import java.io.IOException; 10 | 11 | import static org.mockito.MockitoAnnotations.initMocks; 12 | 13 | public class RequestEntityBuilderTest { 14 | 15 | private String bodyContent; 16 | 17 | @Before 18 | public void setUp() { 19 | initMocks(this); 20 | bodyContent = "dummyContent"; 21 | } 22 | 23 | @Test 24 | public void shouldCreateStringEntity() throws IOException { 25 | RequestEntityBuilder requestEntityBuilder = new RequestEntityBuilder(); 26 | 27 | StringEntity stringEntity = requestEntityBuilder.buildHttpEntity(bodyContent); 28 | byte[] bytes = IOUtils.toByteArray(stringEntity.getContent()); 29 | Assert.assertEquals("dummyContent", new String(bytes)); 30 | } 31 | 32 | @Test 33 | public void shouldWrapEntityIfSet() throws IOException { 34 | RequestEntityBuilder requestEntityBuilder = new RequestEntityBuilder(); 35 | 36 | StringEntity stringEntity = requestEntityBuilder.setWrapping(true).buildHttpEntity(bodyContent); 37 | byte[] bytes = IOUtils.toByteArray(stringEntity.getContent()); 38 | Assert.assertEquals("[dummyContent]", new String(bytes)); 39 | } 40 | 41 | @Test 42 | public void shouldNotWrapEntityIfNotSet() throws IOException { 43 | RequestEntityBuilder requestEntityBuilder = new RequestEntityBuilder(); 44 | 45 | StringEntity stringEntity = requestEntityBuilder.setWrapping(false).buildHttpEntity(bodyContent); 46 | byte[] bytes = IOUtils.toByteArray(stringEntity.getContent()); 47 | Assert.assertEquals("dummyContent", new String(bytes)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/jdbc/HikariJdbcConnectionPoolTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.jdbc; 2 | 3 | import com.zaxxer.hikari.HikariDataSource; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.Mock; 8 | import org.mockito.Mockito; 9 | import org.mockito.junit.MockitoJUnitRunner; 10 | 11 | import java.sql.Connection; 12 | import java.sql.SQLException; 13 | 14 | @RunWith(MockitoJUnitRunner.class) 15 | public class HikariJdbcConnectionPoolTest { 16 | @Mock 17 | private HikariDataSource hikariDataSource; 18 | @Mock 19 | private Connection connection; 20 | 21 | private JdbcConnectionPool jdbcConnectionPool; 22 | 23 | @Before 24 | public void setup() { 25 | jdbcConnectionPool = new HikariJdbcConnectionPool(hikariDataSource); 26 | } 27 | 28 | @Test 29 | public void shouldGetConnectionFromDataSource() throws SQLException { 30 | jdbcConnectionPool.getConnection(); 31 | 32 | Mockito.verify(hikariDataSource).getConnection(); 33 | } 34 | 35 | @Test 36 | public void shouldCloseTheConnectionWhenReleased() throws SQLException { 37 | jdbcConnectionPool.release(connection); 38 | 39 | Mockito.verify(connection).close(); 40 | } 41 | 42 | @Test 43 | public void shouldCloseDataSourceWhenShutdown() throws SQLException, InterruptedException { 44 | jdbcConnectionPool.shutdown(); 45 | 46 | Mockito.verify(hikariDataSource).close(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/prometheus/PromSinkFactoryTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.prometheus; 2 | 3 | 4 | import org.raystack.firehose.exception.DeserializerException; 5 | import org.raystack.firehose.sink.AbstractSink; 6 | import org.raystack.depot.metrics.StatsDReporter; 7 | import org.raystack.stencil.client.StencilClient; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.Mock; 11 | import org.mockito.junit.MockitoJUnitRunner; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | import static org.junit.Assert.assertEquals; 17 | 18 | @RunWith(MockitoJUnitRunner.class) 19 | public class PromSinkFactoryTest { 20 | @Mock 21 | private StatsDReporter statsDReporter; 22 | @Mock 23 | private StencilClient stencilClient; 24 | 25 | @Test 26 | public void shouldCreatePromSink() throws DeserializerException { 27 | 28 | Map configuration = new HashMap<>(); 29 | configuration.put("SINK_PROM_SERVICE_URL", "dummyEndpoint"); 30 | AbstractSink sink = PromSinkFactory.create(configuration, statsDReporter, stencilClient); 31 | 32 | assertEquals(PromSink.class, sink.getClass()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/prometheus/builder/RequestEntityBuilderTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.prometheus.builder; 2 | 3 | import com.google.protobuf.DynamicMessage; 4 | import org.raystack.firehose.sink.prometheus.PromSinkConstants; 5 | import cortexpb.Cortex; 6 | import org.apache.commons.io.IOUtils; 7 | import org.apache.http.entity.ByteArrayEntity; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | import org.xerial.snappy.Snappy; 11 | 12 | import java.io.IOException; 13 | 14 | public class RequestEntityBuilderTest { 15 | 16 | @Test 17 | public void shouldCreateSnappyCompressedByteArrayEntity() throws IOException { 18 | Cortex.Sample sample = Cortex.Sample.newBuilder().setValue(10).setTimestampMs(System.currentTimeMillis()).build(); 19 | Cortex.LabelPair labelPair = Cortex.LabelPair.newBuilder().setName(PromSinkConstants.PROMETHEUS_LABEL_FOR_METRIC_NAME).setValue("test_metric").build(); 20 | Cortex.TimeSeries timeSeries = Cortex.TimeSeries.newBuilder() 21 | .addSamples(sample).addLabels(labelPair) 22 | .build(); 23 | Cortex.WriteRequest writeRequest = Cortex.WriteRequest.newBuilder().addTimeseries(timeSeries).build(); 24 | 25 | RequestEntityBuilder requestEntityBuilder = new RequestEntityBuilder(); 26 | ByteArrayEntity byteArrayEntity = requestEntityBuilder.buildHttpEntity(writeRequest); 27 | 28 | byte[] uncompressedSnappy = Snappy.uncompress(IOUtils.toByteArray(byteArrayEntity.getContent())); 29 | DynamicMessage actual = DynamicMessage.parseFrom(Cortex.WriteRequest.getDescriptor(), uncompressedSnappy); 30 | DynamicMessage expected = DynamicMessage.parseFrom(Cortex.WriteRequest.getDescriptor(), writeRequest.toByteArray()); 31 | 32 | Assert.assertEquals(expected, actual); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sink/prometheus/request/PromRequestCreatorTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sink.prometheus.request; 2 | 3 | 4 | import org.raystack.firehose.config.PromSinkConfig; 5 | import org.raystack.depot.metrics.StatsDReporter; 6 | import org.raystack.stencil.Parser; 7 | import org.aeonbits.owner.ConfigFactory; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.mockito.Mock; 11 | 12 | import java.util.Properties; 13 | 14 | import static org.junit.Assert.assertNotNull; 15 | import static org.mockito.MockitoAnnotations.initMocks; 16 | 17 | public class PromRequestCreatorTest { 18 | 19 | @Mock 20 | private StatsDReporter statsDReporter; 21 | 22 | @Mock 23 | private Parser protoParser; 24 | 25 | @Before 26 | public void setup() { 27 | initMocks(this); 28 | } 29 | 30 | @Test 31 | public void shouldCreateNotNullRequest() { 32 | 33 | Properties promConfigProps = new Properties(); 34 | 35 | PromSinkConfig promSinkConfig = ConfigFactory.create(PromSinkConfig.class, promConfigProps); 36 | PromRequestCreator promRequestCreator = new PromRequestCreator(statsDReporter, promSinkConfig, protoParser); 37 | 38 | PromRequest request = promRequestCreator.createRequest(); 39 | 40 | assertNotNull(request); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/sinkdecorator/SinkFinalTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.sinkdecorator; 2 | 3 | import org.raystack.firehose.message.Message; 4 | import org.raystack.firehose.metrics.FirehoseInstrumentation; 5 | import org.raystack.firehose.metrics.Metrics; 6 | import org.raystack.firehose.sink.Sink; 7 | import org.junit.Test; 8 | import org.mockito.Mockito; 9 | 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class SinkFinalTest { 15 | 16 | @Test 17 | public void shouldIgnoreMessages() throws IOException { 18 | Sink sink = Mockito.mock(Sink.class); 19 | FirehoseInstrumentation firehoseInstrumentation = Mockito.mock(FirehoseInstrumentation.class); 20 | SinkFinal sinkFinal = new SinkFinal(sink, firehoseInstrumentation); 21 | List messages = new ArrayList() {{ 22 | add(new Message("".getBytes(), "".getBytes(), "", 0, 0)); 23 | add(new Message("".getBytes(), "".getBytes(), "", 0, 0)); 24 | }}; 25 | Mockito.when(sink.pushMessage(messages)).thenReturn(messages); 26 | 27 | sinkFinal.pushMessage(messages); 28 | Mockito.verify(firehoseInstrumentation, Mockito.times(1)).logInfo("Ignoring messages {}", 2); 29 | Mockito.verify(firehoseInstrumentation, Mockito.times(1)).captureGlobalMessageMetrics(Metrics.MessageScope.IGNORED, 2); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/raystack/firehose/test/categories/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package org.raystack.firehose.test.categories; 2 | 3 | public interface IntegrationTest { 4 | } 5 | -------------------------------------------------------------------------------- /src/test/proto/TestGrpc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package org.raystack.firehose.consumer; 4 | 5 | option java_multiple_files = true; 6 | option java_package = "org.raystack.firehose.consumer"; 7 | option java_outer_classname = "SampleGrpcServerProto"; 8 | 9 | service TestServer { 10 | rpc TestRpcMethod (TestGrpcRequest) returns (TestGrpcResponse) {} 11 | } 12 | 13 | message TestGrpcRequest { 14 | string field1 = 1; 15 | string field2 = 2; 16 | } 17 | 18 | message Error { 19 | string code = 1; 20 | string entity = 2; 21 | } 22 | 23 | message TestGrpcResponse { 24 | bool success = 1; 25 | repeated Error error = 2; 26 | string field3 = 3; 27 | string field4 = 4; 28 | } 29 | -------------------------------------------------------------------------------- /src/test/proto/TestMessageBQ.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package raystack.firehose; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | import "google/protobuf/duration.proto"; 7 | import "google/protobuf/struct.proto"; 8 | import "google/type/date.proto"; 9 | 10 | option java_multiple_files = true; 11 | option java_package = "org.raystack.firehose"; 12 | option java_outer_classname = "TestMessageProtoBQ"; 13 | 14 | message TestKeyBQ { 15 | string order_number = 1; 16 | string order_url = 2; 17 | } 18 | 19 | message TestMessageBQ { 20 | string order_number = 1; 21 | string order_url = 2; 22 | string order_details = 3; 23 | google.protobuf.Timestamp created_at = 4; 24 | raystack.firehose.StatusBQ status = 5; 25 | int64 discount = 6; 26 | bool success = 7; 27 | float price = 8; 28 | map current_state = 9; 29 | bytes user_token = 10; 30 | google.protobuf.Duration trip_duration = 11; 31 | repeated string aliases = 12; 32 | google.protobuf.Struct properties = 13; 33 | google.type.Date order_date = 14; 34 | repeated google.protobuf.Timestamp updated_at = 15; 35 | repeated google.protobuf.Struct attributes = 16; 36 | } 37 | 38 | message TestMessageChildBQ { 39 | string order_number = 1; 40 | bool success = 7; 41 | } 42 | 43 | message TestNestedMessageBQ { 44 | string nested_id = 1; 45 | TestMessageBQ single_message = 2; 46 | } 47 | 48 | message TestRecursiveMessageBQ { 49 | string string_value = 1; 50 | float float_value = 2; 51 | TestRecursiveMessageBQ recursive_message = 3; 52 | } 53 | 54 | message TestNestedRepeatedMessageBQ { 55 | TestMessageBQ single_message = 1; 56 | repeated TestMessageBQ repeated_message = 2; 57 | int32 number_field = 3; 58 | repeated int32 repeated_number_field = 4; 59 | } 60 | 61 | enum StatusBQ { 62 | COMPLETED = 0; 63 | CANCELLED = 1; 64 | } 65 | --------------------------------------------------------------------------------