From e736cb0e47cf22b2fb5ebe20cfb466f84daa2160 Mon Sep 17 00:00:00 2001 From: ysc <2725843507@qq.com> Date: Tue, 11 Mar 2025 17:21:10 +0800 Subject: [PATCH 1/5] migrate pipe sink --- iotdb-collector/collector-core/pom.xml | 107 ++- .../collector/config/IoTDBCommonOptions.java | 24 + .../iotdb/collector/config/Options.java | 1 + .../iotdb/collector/config/PipeOptions.java | 279 ++++++ .../CollectorRuntimeEnvironment.java | 5 + .../plugin/builtin/annotation/TableModel.java | 39 + .../plugin/builtin/annotation/TreeModel.java | 39 + .../sink/compressor/PipeCompressor.java | 76 ++ .../sink/compressor/PipeCompressorConfig.java | 39 + .../compressor/PipeCompressorFactory.java | 116 +++ .../sink/compressor/PipeGZIPCompressor.java | 53 + .../sink/compressor/PipeLZ4Compressor.java | 53 + .../sink/compressor/PipeLZMA2Compressor.java | 53 + .../sink/compressor/PipeSnappyCompressor.java | 53 + .../sink/compressor/PipeZSTDCompressor.java | 48 + .../sink/constant/ColumnHeaderConstant.java | 649 +++++++++++++ .../builtin/sink/constant/IoTDBConstant.java | 371 +++++++ .../sink/constant/PipeConnectorConstant.java | 274 ++++++ .../sink/constant/PipeExtractorConstant.java | 149 +++ .../PipeTransferHandshakeConstant.java | 36 + .../builtin/sink/constant/SystemConstant.java | 60 ++ .../sink/event/row/PipeBinaryTransformer.java | 37 + .../event/row/PipeDataTypeTransformer.java | 73 ++ .../sink/event/row/PipeResetTabletRow.java | 54 ++ .../builtin/sink/event/row/PipeRow.java | 211 ++++ .../sink/event/row/PipeRowCollector.java | 146 +++ .../tablet/PipeRawTabletInsertionEvent.java | 334 +++++++ .../tsfile/PipeTsFileInsertionEvent.java | 644 +++++++++++++ ...PipeRuntimeConnectorCriticalException.java | 74 ++ ...nectorRetryTimesConfigurableException.java | 37 + .../pipe/PipeRuntimeCriticalException.java | 70 ++ .../exception/pipe/PipeRuntimeException.java | 54 ++ .../pipe/PipeRuntimeExceptionType.java | 53 + .../pipe/PipeRuntimeNonCriticalException.java | 75 ++ ...peRuntimeOutOfMemoryCriticalException.java | 76 ++ .../sink/limiter/GlobalRateLimiter.java | 84 ++ .../sink/limiter/PipeEndPointRateLimiter.java | 79 ++ .../airgap/AirGapELanguageConstant.java | 34 + .../payload/airgap/AirGapOneByteResponse.java | 30 + .../AirGapPseudoTPipeTransferRequest.java | 24 + .../evolvable/batch/PipeTabletEventBatch.java | 146 +++ .../batch/PipeTabletEventPlainBatch.java | 160 ++++ .../batch/PipeTabletEventTsFileBatch.java | 207 ++++ .../batch/PipeTransferBatchReqBuilder.java | 196 ++++ .../PipeTransferDataNodeHandshakeV1Req.java | 71 ++ .../PipeTransferDataNodeHandshakeV2Req.java | 71 ++ .../PipeTransferSchemaSnapshotPieceReq.java | 73 ++ .../PipeTransferSchemaSnapshotSealReq.java | 168 ++++ .../request/PipeTransferTabletBatchReq.java | 220 +++++ .../request/PipeTransferTabletBatchReqV2.java | 251 +++++ .../request/PipeTransferTabletBinaryReq.java | 124 +++ .../PipeTransferTabletBinaryReqV2.java | 169 ++++ .../request/PipeTransferTabletRawReq.java | 166 ++++ .../request/PipeTransferTabletRawReqV2.java | 186 ++++ .../request/PipeTransferTsFilePieceReq.java | 72 ++ .../PipeTransferTsFilePieceWithModReq.java | 73 ++ .../request/PipeTransferTsFileSealReq.java | 70 ++ .../PipeTransferTsFileSealWithModReq.java | 150 +++ .../common/PipeTransferHandshakeConstant.java | 36 + .../common/PipeTransferSliceReqHandler.java | 134 +++ .../request/IoTDBConnectorRequestVersion.java | 36 + .../thrift/request/PipeRequestType.java | 90 ++ .../request/PipeTransferCompressedReq.java | 149 +++ .../request/PipeTransferFilePieceReq.java | 127 +++ .../request/PipeTransferFileSealReqV1.java | 113 +++ .../request/PipeTransferFileSealReqV2.java | 169 ++++ .../request/PipeTransferHandshakeV1Req.java | 102 ++ .../request/PipeTransferHandshakeV2Req.java | 118 +++ .../thrift/request/PipeTransferSliceReq.java | 169 ++++ .../response/PipeTransferFilePieceResp.java | 104 ++ .../sink/property/ClientPoolProperty.java | 108 +++ .../property/PipeConsensusClientProperty.java | 99 ++ .../builtin/sink/property/ThriftClient.java | 123 +++ .../sink/property/ThriftClientProperty.java | 122 +++ .../protocol/airgap/IoTDBAirGapConnector.java | 433 +++++++++ .../sink/protocol/airgap/IoTDBConnector.java | 495 ++++++++++ .../airgap/IoTDBDataNodeAirGapConnector.java | 111 +++ .../IoTDBDataRegionAirGapConnector.java | 213 +++++ .../async/IoTDBDataRegionAsyncConnector.java | 575 +++++++++++ .../PipeTransferTabletBatchEventHandler.java | 144 +++ ...peTransferTabletInsertionEventHandler.java | 107 +++ .../PipeTransferTabletRawEventHandler.java | 50 + .../handler/PipeTransferTrackableHandler.java | 108 +++ .../handler/PipeTransferTsFileHandler.java | 442 +++++++++ .../protocol/thrift/client/ClientManager.java | 97 ++ .../thrift/client/IClientManager.java | 55 ++ .../thrift/client/IoTDBClientManager.java | 117 +++ .../IoTDBDataNodeAsyncClientManager.java | 399 ++++++++ ...IoTDBDataNodeCacheLeaderClientManager.java | 117 +++ .../IoTDBDataNodeSyncClientManager.java | 129 +++ .../thrift/client/IoTDBSyncClient.java | 181 ++++ .../thrift/client/IoTDBSyncClientManager.java | 372 ++++++++ .../AsyncPipeDataTransferServiceClient.java | 225 +++++ .../factory/AsyncThriftClientFactory.java | 61 ++ .../client/factory/BaseClientFactory.java | 39 + .../client/factory/ClientPoolFactory.java | 64 ++ .../client/factory/IClientPoolFactory.java | 35 + .../client/factory/ThriftClientFactory.java | 34 + .../sync/IoTDBDataNodeSyncConnector.java | 106 ++ .../sync/IoTDBDataRegionSyncConnector.java | 502 ++++++++++ .../thrift/sync/IoTDBSslSyncConnector.java | 257 +++++ .../builtin/sink/utils/DateTimeUtils.java | 903 ++++++++++++++++++ .../plugin/builtin/sink/utils/FileUtils.java | 430 +++++++++ .../plugin/builtin/sink/utils/MemUtils.java | 241 +++++ .../builtin/sink/utils/NodeUrlUtils.java | 246 +++++ .../sink/utils/PathDeserializeUtil.java | 46 + .../plugin/builtin/sink/utils/PathUtils.java | 224 +++++ .../sink/utils/PipeMemoryWeightUtil.java | 358 +++++++ .../plugin/builtin/sink/utils/RetryUtils.java | 48 + .../plugin/builtin/sink/utils/TestOnly.java | 34 + .../sink/utils/TimestampPrecisionUtils.java | 98 ++ .../sink/utils/TsTableColumnCategory.java | 108 +++ .../builder/PipeTableModeTsFileBuilder.java | 271 ++++++ .../builder/PipeTreeModelTsFileBuilder.java | 267 ++++++ .../sink/utils/builder/PipeTsFileBuilder.java | 155 +++ .../sink/utils/cacher/LeaderCacheUtils.java | 67 ++ .../PipeTableModelTabletEventSorter.java | 272 ++++++ .../utils/sorter/PipeTabletEventSorter.java | 104 ++ .../PipeTreeModelTabletEventSorter.java | 122 +++ .../runtime/task/processor/ProcessorTask.java | 9 +- .../collector/runtime/task/sink/SinkTask.java | 9 +- .../task/source/pull/PullSourceTask.java | 14 +- pom.xml | 83 ++ 123 files changed, 18632 insertions(+), 30 deletions(-) create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/config/IoTDBCommonOptions.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/config/PipeOptions.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/annotation/TableModel.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/annotation/TreeModel.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeCompressor.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeCompressorConfig.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeCompressorFactory.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeGZIPCompressor.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeLZ4Compressor.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeLZMA2Compressor.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeSnappyCompressor.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeZSTDCompressor.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/ColumnHeaderConstant.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/IoTDBConstant.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/PipeConnectorConstant.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/PipeExtractorConstant.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/PipeTransferHandshakeConstant.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/SystemConstant.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeBinaryTransformer.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeDataTypeTransformer.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeResetTabletRow.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeRow.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeRowCollector.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/tablet/PipeRawTabletInsertionEvent.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/tsfile/PipeTsFileInsertionEvent.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeConnectorCriticalException.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeConnectorRetryTimesConfigurableException.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeCriticalException.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeException.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeExceptionType.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeNonCriticalException.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeOutOfMemoryCriticalException.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/limiter/GlobalRateLimiter.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/limiter/PipeEndPointRateLimiter.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/airgap/AirGapELanguageConstant.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/airgap/AirGapOneByteResponse.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/airgap/AirGapPseudoTPipeTransferRequest.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventBatch.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventPlainBatch.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventTsFileBatch.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTransferBatchReqBuilder.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferDataNodeHandshakeV1Req.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferDataNodeHandshakeV2Req.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferSchemaSnapshotPieceReq.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferSchemaSnapshotSealReq.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBatchReq.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBinaryReq.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBinaryReqV2.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletRawReq.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletRawReqV2.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFilePieceReq.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFilePieceWithModReq.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFileSealReq.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFileSealWithModReq.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/common/PipeTransferHandshakeConstant.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/common/PipeTransferSliceReqHandler.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/IoTDBConnectorRequestVersion.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeRequestType.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferCompressedReq.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferFilePieceReq.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferFileSealReqV1.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferFileSealReqV2.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferHandshakeV1Req.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferHandshakeV2Req.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferSliceReq.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/response/PipeTransferFilePieceResp.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ClientPoolProperty.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/PipeConsensusClientProperty.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ThriftClient.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ThriftClientProperty.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBAirGapConnector.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBConnector.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBDataNodeAirGapConnector.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBDataRegionAirGapConnector.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/IoTDBDataRegionAsyncConnector.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTabletBatchEventHandler.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTabletInsertionEventHandler.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTabletRawEventHandler.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTrackableHandler.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTsFileHandler.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/ClientManager.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IClientManager.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBClientManager.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBDataNodeAsyncClientManager.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBDataNodeCacheLeaderClientManager.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBDataNodeSyncClientManager.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBSyncClient.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBSyncClientManager.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/async/AsyncPipeDataTransferServiceClient.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/AsyncThriftClientFactory.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/BaseClientFactory.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/ClientPoolFactory.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/IClientPoolFactory.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/ThriftClientFactory.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/sync/IoTDBDataNodeSyncConnector.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/sync/IoTDBDataRegionSyncConnector.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/sync/IoTDBSslSyncConnector.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/DateTimeUtils.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/FileUtils.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/MemUtils.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/NodeUrlUtils.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathDeserializeUtil.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathUtils.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PipeMemoryWeightUtil.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/RetryUtils.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TestOnly.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TimestampPrecisionUtils.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TsTableColumnCategory.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/builder/PipeTableModeTsFileBuilder.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/builder/PipeTreeModelTsFileBuilder.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/builder/PipeTsFileBuilder.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/cacher/LeaderCacheUtils.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/sorter/PipeTableModelTabletEventSorter.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/sorter/PipeTabletEventSorter.java create mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/sorter/PipeTreeModelTabletEventSorter.java diff --git a/iotdb-collector/collector-core/pom.xml b/iotdb-collector/collector-core/pom.xml index f6ed16d9..a09252b3 100644 --- a/iotdb-collector/collector-core/pom.xml +++ b/iotdb-collector/collector-core/pom.xml @@ -48,20 +48,15 @@ - - org.apache.iotdb - node-commons - 1.3.2 - org.apache.iotdb service-rpc - 1.3.2 + 2.0.1-beta org.apache.iotdb pipe-api - 1.3.2 + 2.0.1-beta jakarta.servlet @@ -103,6 +98,102 @@ com.lmax disruptor + + org.java-websocket + Java-WebSocket + + + org.apache.commons + commons-lang3 + + + org.apache.commons + commons-collections4 + + + org.apache.commons + commons-pool2 + + + org.apache.tsfile + tsfile + + + + + + + + org.apache.iotdb + iotdb-thrift-commons + 2.0.1-beta + + + org.apache.iotdb + iotdb-thrift + 2.0.1-beta + + + org.apache.iotdb + iotdb-session + 2.0.1-beta + + + net.java.dev.jna + jna + + + net.java.dev.jna + jna-platform + + + com.google.code.findbugs + jsr305 + + + org.apache.thrift + libthrift + + + com.google.guava + guava + + + org.eclipse.milo + stack-core + + + org.eclipse.milo + sdk-core + + + org.eclipse.milo + stack-server + + + org.eclipse.milo + sdk-server + + + commons-io + commons-io + + + com.github.luben + zstd-jni + + + org.apache.tsfile + common + + + com.github.ben-manes.caffeine + caffeine + + + io.moquette + moquette-broker + @@ -117,7 +208,7 @@ org.glassfish.jersey.inject:jersey-hk2 - + org.apache.maven.pluginsmaven-compiler-plugin88 diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/config/IoTDBCommonOptions.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/config/IoTDBCommonOptions.java new file mode 100644 index 00000000..4b252783 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/config/IoTDBCommonOptions.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.config; + +public class IoTDBCommonOptions extends Options{ + +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/config/Options.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/config/Options.java index e0670863..97d4d8da 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/config/Options.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/config/Options.java @@ -36,6 +36,7 @@ public class Options { try { Class.forName(ApiServiceOptions.class.getName()); Class.forName(TaskRuntimeOptions.class.getName()); + Class.forName(PipeOptions.class.getName()); } catch (final ClassNotFoundException e) { throw new RuntimeException("Failed to load options", e); } diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/config/PipeOptions.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/config/PipeOptions.java new file mode 100644 index 00000000..69d7cba5 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/config/PipeOptions.java @@ -0,0 +1,279 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.config; + +import org.apache.iotdb.collector.plugin.builtin.sink.constant.IoTDBConstant; +import org.apache.iotdb.rpc.RpcUtils; + +import java.io.File; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +public class PipeOptions extends Options { + + public static volatile Option PIPE_CHECK_MEMORY_ENOUGH_INTERVAL_MS = + new Option("pipe_check_memory_enough_interval_ms", 10L) { + @Override + public void setValue(final String valueString) { + value = Long.parseLong(valueString); + } + }; + + public static final Option PIPE_CONNECTOR_READ_FILE_BUFFER_MEMORY_CONTROL = + new Option("pipe_connector_read_file_buffer_memory_control", false) { + @Override + public void setValue(final String valueString) { + value = Boolean.parseBoolean(valueString); + } + }; + + public static final Option PIPE_CONNECTOR_TRANSFER_TIMEOUT_MS = + new Option("pipe_connector_transfer_timeout_ms", 15 * 60 * 1000) { + @Override + public void setValue(final String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option PIPE_SUBTASK_EXECUTOR_MAX_THREAD_NUM = + new Option( + "pipe_subtask_executor_max_thread_num", + Math.max(5, Runtime.getRuntime().availableProcessors() / 2)) { + @Override + public void setValue(final String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option PIPE_CONNECTOR_READ_FILE_BUFFER_SIZE = + new Option("pipe_connector_read_file_buffer_size", 8388608) { + @Override + public void setValue(final String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option PIPE_EVENT_REFERENCE_TRACKING_ENABLED = + new Option("pipe_event_reference_tracking_enabled", true) { + @Override + public void setValue(final String valueString) { + value = Boolean.parseBoolean(valueString); + } + }; + + public static final Option RATE_LIMITER_HOT_RELOAD_CHECK_INTERVAL_MS = + new Option("rate_limiter_hot_reload_check_interval_ms", 1000) { + @Override + public void setValue(final String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option PIPE_ALL_SINK_RATE_LIMIT_BYTES_PER_SECOND = + new Option("pipe_all_sink_rate_limit_bytes_per_second", -1d) { + @Override + public void setValue(final String valueString) { + value = Double.parseDouble(valueString); + } + }; + + public static final Option PIPE_DATA_STRUCTURE_TABLET_SIZE_IN_BYTES = + new Option("pipe_data_structure_tablet_size_in_bytes", 2097152) { + @Override + public void setValue(final String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option PIPE_DATA_STRUCTURE_TABLET_ROW_SIZE = + new Option("pipe_data_structure_tablet_row_size", 2048) { + @Override + public void setValue(final String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option PIPE_AIR_GAP_RECEIVER_ENABLED = + new Option("pipe_air_gap_receiver_enabled", true) { + + @Override + public void setValue(final String valueString) { + value = Boolean.parseBoolean(valueString); + } + }; + + public static final Option PIPE_AIR_GAP_RECEIVER_PORT = + new Option("pipe_air_gap_receiver_port", 9780) { + @Override + public void setValue(final String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option RPC_ADDRESS = + new Option("rpc_address", "0.0.0.0") { + @Override + public void setValue(final String valueString) { + value = valueString; + } + }; + + public static final Option RPC_PORT = + new Option("rpc_port", 6667) { + @Override + public void setValue(String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option TIMESTAMP_PRECISION = + new Option("timestamp_precision", "ms") { + @Override + public void setValue(final String valueString) { + value = valueString; + } + }; + + public static final Option PIPE_SUBTASK_EXECUTOR_PENDING_QUEUE_MAX_BLOCKING_TIME_MS = + new Option("pipe_subtask_executor_pending_queue_max_blocking_time_ms", 1000L) { + @Override + public void setValue(final String valueString) { + value = Long.parseLong(valueString); + } + }; + + public static final Option TIMESTAMP_PRECISION_CHECK_ENABLED = + new Option("timestamp_precision_check_enabled", true) { + @Override + public void setValue(final String valueString) { + value = Boolean.parseBoolean(valueString); + } + }; + + public static final Option DN_CONNECTION_TIMEOUT_IN_MS = + new Option("dn_connection_timeout_in_ms", (int) TimeUnit.SECONDS.toMillis(60)) { + @Override + public void setValue(final String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option IS_RPC_THRIFT_COMPRESSION_ENABLED = + new Option("is_rpc_thrift_compression_enabled", false) { + @Override + public void setValue(final String valueString) { + value = Boolean.parseBoolean(valueString); + } + }; + + public static final Option PIPE_CONNECTOR_REQUEST_SLICE_THRESHOLD_BYTES = + new Option( + "pipe_connector_request_slice_threshold_bytes", + (int) (RpcUtils.THRIFT_FRAME_MAX_SIZE * 0.8)) { + @Override + public void setValue(final String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option CLUSTER_ID = + new Option("cluster_id", "") { + @Override + public void setValue(final String valueString) { + value = valueString; + } + }; + + public static final Option PIPE_CONNECTOR_HANDSHAKE_TIMEOUT_MS = + new Option("pipe_connector_handshake_timeout_ms", 10 * 1000) { + @Override + public void setValue(final String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option PIPE_CONNECTOR_RPC_THRIFT_COMPRESSION_ENABLED = + new Option("pipe_connector_rpc_thrift_compression_enabled", false) { + @Override + public void setValue(final String valueString) { + value = Boolean.parseBoolean(valueString); + } + }; + + public static final Option PIPE_ASYNC_CONNECTOR_SELECTOR_NUMBER = + new Option( + "pipe_async_connector_selector_number", + Math.max(4, Runtime.getRuntime().availableProcessors() / 2)) { + @Override + public void setValue(final String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option PIPE_ASYNC_CONNECTOR_MAX_CLIENT_NUMBER = + new Option( + "pipe_async_connector_max_client_number", + Math.max(16, Runtime.getRuntime().availableProcessors() / 2)) { + @Override + public void setValue(final String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option SYSTEM_DIR = + new Option( + "system_dir", + IoTDBConstant.DN_DEFAULT_DATA_DIR + File.separator + IoTDBConstant.SYSTEM_FOLDER_NAME) { + @Override + public void setValue(final String valueString) { + value = valueString; + } + }; + + public static final Option PIPE_RECEIVER_FILE_DIRS = + new Option("pipe_receiver_file_dirs", new String[0]) { + @Override + public void setValue(final String valueString) { + value = + (Objects.isNull(valueString) || valueString.split(",").length == 0) + ? new String[] { + SYSTEM_DIR.value() + File.separator + "pipe" + File.separator + "receiver" + } + : valueString.split(","); + } + }; + + public static volatile Option DATA_NODE_ID = + new Option("data_node_id", -1) { + @Override + public void setValue(final String valueString) { + value = Integer.parseInt(valueString); + } + }; + + public static final Option PIPE_MEMORY_ALLOCATE_FOR_TS_FILE_SEQUENCE_READER_IN_BYTES = + new Option( + "pipe_memory_allocate_for_ts_file_sequence_reader_in_bytes", 2 * 1024 * 1024L) { + @Override + public void setValue(final String valueString) { + value = Long.parseLong(valueString); + } + }; +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/api/customizer/CollectorRuntimeEnvironment.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/api/customizer/CollectorRuntimeEnvironment.java index 8ace81c5..07c60a20 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/api/customizer/CollectorRuntimeEnvironment.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/api/customizer/CollectorRuntimeEnvironment.java @@ -49,6 +49,11 @@ public long getCreationTime() { return creationTime; } + @Override + public int getRegionId() { + return getInstanceIndex(); + } + public int getParallelism() { return parallelism; } diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/annotation/TableModel.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/annotation/TableModel.java new file mode 100644 index 00000000..f73a1af0 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/annotation/TableModel.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a plugin can be used in table model environments. + * + *

When implementing a custom {@link org.apache.iotdb.pipe.api.PipePlugin} that needs to operate + * under table model settings, declare this annotation on the plugin class. Through the {@code + * CREATE PIPEPLUGIN} statement, a plugin annotated with {@link TableModel} is valid for both tree + * model connections and table model connections. + * + * @since 2.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface TableModel {} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/annotation/TreeModel.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/annotation/TreeModel.java new file mode 100644 index 00000000..7e163fe7 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/annotation/TreeModel.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that a plugin can be used in tree model environments. + * + *

When implementing a custom {@link org.apache.iotdb.pipe.api.PipePlugin} that needs to operate + * under tree model settings, declare this annotation on the plugin class. Through the {@code CREATE + * PIPEPLUGIN} statement, a plugin annotated with {@link TreeModel} is valid for both tree model + * connections and tree model connections. + * + * @since 2.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface TreeModel {} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeCompressor.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeCompressor.java new file mode 100644 index 00000000..dc3f1e79 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeCompressor.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.compressor; + +import java.io.IOException; + +public abstract class PipeCompressor { + + public enum PipeCompressionType { + SNAPPY((byte) 0), + GZIP((byte) 1), + LZ4((byte) 2), + ZSTD((byte) 3), + LZMA2((byte) 4); + + final byte index; + + PipeCompressionType(byte index) { + this.index = index; + } + + public byte getIndex() { + return index; + } + } + + private final PipeCompressionType compressionType; + + protected PipeCompressor(PipeCompressionType compressionType) { + this.compressionType = compressionType; + } + + public abstract byte[] compress(byte[] data) throws IOException; + + /** + * Decompress the byte array to a byte array. NOTE: the length of the decompressed byte array is + * not provided in this method, and some decompressors (LZ4) may construct large byte arrays, + * leading to potential OOM. + * + * @param byteArray the byte array to be decompressed + * @return the decompressed byte array + * @throws IOException + */ + public abstract byte[] decompress(byte[] byteArray) throws IOException; + + /** + * Decompress the byte array to a byte array with a known length. + * + * @param byteArray the byte array to be decompressed + * @param decompressedLength the length of the decompressed byte array + * @return the decompressed byte array + * @throws IOException + */ + public abstract byte[] decompress(byte[] byteArray, int decompressedLength) throws IOException; + + public byte serialize() { + return compressionType.getIndex(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeCompressorConfig.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeCompressorConfig.java new file mode 100644 index 00000000..fc54f3af --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeCompressorConfig.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.compressor; + +public class PipeCompressorConfig { + + private final String name; + private final int zstdCompressionLevel; + + public PipeCompressorConfig(String name, int zstdCompressionLevel) { + this.name = name; + this.zstdCompressionLevel = zstdCompressionLevel; + } + + public String getName() { + return name; + } + + public int getZstdCompressionLevel() { + return zstdCompressionLevel; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeCompressorFactory.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeCompressorFactory.java new file mode 100644 index 00000000..df941f48 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeCompressorFactory.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.compressor; + + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_COMPRESSOR_GZIP; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_COMPRESSOR_LZ4; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_COMPRESSOR_LZMA2; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_COMPRESSOR_SNAPPY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_COMPRESSOR_ZSTD; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_COMPRESSOR_ZSTD_LEVEL_DEFAULT_VALUE; + +public class PipeCompressorFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(PipeCompressorFactory.class); + + private static final Map COMPRESSOR_NAME_TO_INSTANCE = + new ConcurrentHashMap<>(); + + static { + COMPRESSOR_NAME_TO_INSTANCE.put(CONNECTOR_COMPRESSOR_SNAPPY, new PipeSnappyCompressor()); + COMPRESSOR_NAME_TO_INSTANCE.put(CONNECTOR_COMPRESSOR_GZIP, new PipeGZIPCompressor()); + COMPRESSOR_NAME_TO_INSTANCE.put(CONNECTOR_COMPRESSOR_LZ4, new PipeLZ4Compressor()); + COMPRESSOR_NAME_TO_INSTANCE.put( + CONNECTOR_COMPRESSOR_ZSTD, + new PipeZSTDCompressor(CONNECTOR_COMPRESSOR_ZSTD_LEVEL_DEFAULT_VALUE)); + COMPRESSOR_NAME_TO_INSTANCE.put(CONNECTOR_COMPRESSOR_LZMA2, new PipeLZMA2Compressor()); + } + + public static PipeCompressor getCompressor(PipeCompressorConfig config) { + if (config == null) { + throw new IllegalArgumentException("PipeCompressorConfig is null"); + } + if (config.getName() == null) { + throw new IllegalArgumentException("PipeCompressorConfig.getName() is null"); + } + + final String compressorName = config.getName(); + + // For ZSTD compressor, we need to consider the compression level + if (compressorName.equals(CONNECTOR_COMPRESSOR_ZSTD)) { + final int zstdCompressionLevel = config.getZstdCompressionLevel(); + return COMPRESSOR_NAME_TO_INSTANCE.computeIfAbsent( + CONNECTOR_COMPRESSOR_ZSTD + "_" + zstdCompressionLevel, + key -> { + LOGGER.info("Create new PipeZSTDCompressor with level: {}", zstdCompressionLevel); + return new PipeZSTDCompressor(zstdCompressionLevel); + }); + } + + // For other compressors, we can directly get the instance by name + final PipeCompressor compressor = COMPRESSOR_NAME_TO_INSTANCE.get(compressorName); + if (compressor != null) { + return compressor; + } + + throw new UnsupportedOperationException("PipeCompressor not found for name: " + compressorName); + } + + private static Map COMPRESSOR_INDEX_TO_INSTANCE = new HashMap<>(); + + static { + COMPRESSOR_INDEX_TO_INSTANCE.put( + PipeCompressor.PipeCompressionType.SNAPPY.getIndex(), + COMPRESSOR_NAME_TO_INSTANCE.get(CONNECTOR_COMPRESSOR_SNAPPY)); + COMPRESSOR_INDEX_TO_INSTANCE.put( + PipeCompressor.PipeCompressionType.GZIP.getIndex(), + COMPRESSOR_NAME_TO_INSTANCE.get(CONNECTOR_COMPRESSOR_GZIP)); + COMPRESSOR_INDEX_TO_INSTANCE.put( + PipeCompressor.PipeCompressionType.LZ4.getIndex(), + COMPRESSOR_NAME_TO_INSTANCE.get(CONNECTOR_COMPRESSOR_LZ4)); + COMPRESSOR_INDEX_TO_INSTANCE.put( + PipeCompressor.PipeCompressionType.ZSTD.getIndex(), + COMPRESSOR_NAME_TO_INSTANCE.get(CONNECTOR_COMPRESSOR_ZSTD)); + COMPRESSOR_INDEX_TO_INSTANCE.put( + PipeCompressor.PipeCompressionType.LZMA2.getIndex(), + COMPRESSOR_NAME_TO_INSTANCE.get(CONNECTOR_COMPRESSOR_LZMA2)); + COMPRESSOR_INDEX_TO_INSTANCE = Collections.unmodifiableMap(COMPRESSOR_INDEX_TO_INSTANCE); + } + + public static PipeCompressor getCompressor(byte index) { + final PipeCompressor compressor = COMPRESSOR_INDEX_TO_INSTANCE.get(index); + if (compressor == null) { + throw new UnsupportedOperationException("PipeCompressor not found for index: " + index); + } + return compressor; + } + + private PipeCompressorFactory() { + // Empty constructor + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeGZIPCompressor.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeGZIPCompressor.java new file mode 100644 index 00000000..155af20e --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeGZIPCompressor.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.compressor; + +import java.io.IOException; +import org.apache.tsfile.compress.ICompressor; +import org.apache.tsfile.compress.IUnCompressor; +import org.apache.tsfile.file.metadata.enums.CompressionType; + +public class PipeGZIPCompressor extends PipeCompressor { + + private static final ICompressor COMPRESSOR = ICompressor.getCompressor(CompressionType.GZIP); + private static final IUnCompressor DECOMPRESSOR = + IUnCompressor.getUnCompressor(CompressionType.GZIP); + + public PipeGZIPCompressor() { + super(PipeCompressionType.GZIP); + } + + @Override + public byte[] compress(byte[] data) throws IOException { + return COMPRESSOR.compress(data); + } + + @Override + public byte[] decompress(byte[] byteArray) throws IOException { + return DECOMPRESSOR.uncompress(byteArray); + } + + @Override + public byte[] decompress(byte[] byteArray, int decompressedLength) throws IOException { + byte[] uncompressed = new byte[decompressedLength]; + DECOMPRESSOR.uncompress(byteArray, 0, byteArray.length, uncompressed, 0); + return uncompressed; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeLZ4Compressor.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeLZ4Compressor.java new file mode 100644 index 00000000..2c0a5297 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeLZ4Compressor.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.compressor; + +import java.io.IOException; +import org.apache.tsfile.compress.ICompressor; +import org.apache.tsfile.compress.IUnCompressor; +import org.apache.tsfile.file.metadata.enums.CompressionType; + +public class PipeLZ4Compressor extends PipeCompressor { + + private static final ICompressor COMPRESSOR = ICompressor.getCompressor(CompressionType.LZ4); + private static final IUnCompressor DECOMPRESSOR = + IUnCompressor.getUnCompressor(CompressionType.LZ4); + + public PipeLZ4Compressor() { + super(PipeCompressionType.LZ4); + } + + @Override + public byte[] compress(byte[] data) throws IOException { + return COMPRESSOR.compress(data); + } + + @Override + public byte[] decompress(byte[] byteArray) throws IOException { + return DECOMPRESSOR.uncompress(byteArray); + } + + @Override + public byte[] decompress(byte[] byteArray, int decompressedLength) throws IOException { + byte[] uncompressed = new byte[decompressedLength]; + DECOMPRESSOR.uncompress(byteArray, 0, byteArray.length, uncompressed, 0); + return uncompressed; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeLZMA2Compressor.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeLZMA2Compressor.java new file mode 100644 index 00000000..e964c951 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeLZMA2Compressor.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.compressor; + +import java.io.IOException; +import org.apache.tsfile.compress.ICompressor; +import org.apache.tsfile.compress.IUnCompressor; +import org.apache.tsfile.file.metadata.enums.CompressionType; + +public class PipeLZMA2Compressor extends PipeCompressor { + + private static final ICompressor COMPRESSOR = ICompressor.getCompressor(CompressionType.LZMA2); + private static final IUnCompressor DECOMPRESSOR = + IUnCompressor.getUnCompressor(CompressionType.LZMA2); + + public PipeLZMA2Compressor() { + super(PipeCompressionType.LZMA2); + } + + @Override + public byte[] compress(byte[] data) throws IOException { + return COMPRESSOR.compress(data); + } + + @Override + public byte[] decompress(byte[] byteArray) throws IOException { + return DECOMPRESSOR.uncompress(byteArray); + } + + @Override + public byte[] decompress(byte[] byteArray, int decompressedLength) throws IOException { + byte[] uncompressed = new byte[decompressedLength]; + DECOMPRESSOR.uncompress(byteArray, 0, byteArray.length, uncompressed, 0); + return uncompressed; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeSnappyCompressor.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeSnappyCompressor.java new file mode 100644 index 00000000..7e84db51 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeSnappyCompressor.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.compressor; + +import java.io.IOException; +import org.apache.tsfile.compress.ICompressor; +import org.apache.tsfile.compress.IUnCompressor; +import org.apache.tsfile.file.metadata.enums.CompressionType; + +public class PipeSnappyCompressor extends PipeCompressor { + + private static final ICompressor COMPRESSOR = ICompressor.getCompressor(CompressionType.SNAPPY); + private static final IUnCompressor DECOMPRESSOR = + IUnCompressor.getUnCompressor(CompressionType.SNAPPY); + + public PipeSnappyCompressor() { + super(PipeCompressionType.SNAPPY); + } + + @Override + public byte[] compress(byte[] data) throws IOException { + return COMPRESSOR.compress(data); + } + + @Override + public byte[] decompress(byte[] byteArray) throws IOException { + return DECOMPRESSOR.uncompress(byteArray); + } + + @Override + public byte[] decompress(byte[] byteArray, int decompressedLength) throws IOException { + byte[] uncompressed = new byte[decompressedLength]; + DECOMPRESSOR.uncompress(byteArray, 0, byteArray.length, uncompressed, 0); + return uncompressed; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeZSTDCompressor.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeZSTDCompressor.java new file mode 100644 index 00000000..25687705 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/compressor/PipeZSTDCompressor.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.compressor; + +import com.github.luben.zstd.Zstd; +import java.io.IOException; + +public class PipeZSTDCompressor extends PipeCompressor { + + private final int compressionLevel; + + public PipeZSTDCompressor(int compressionLevel) { + super(PipeCompressionType.ZSTD); + this.compressionLevel = compressionLevel; + } + + @Override + public byte[] compress(byte[] data) throws IOException { + return Zstd.compress(data, compressionLevel); + } + + @Override + public byte[] decompress(byte[] byteArray) { + return Zstd.decompress(byteArray, (int) Zstd.decompressedSize(byteArray, 0, byteArray.length)); + } + + @Override + public byte[] decompress(byte[] byteArray, int decompressedLength) { + return Zstd.decompress(byteArray, decompressedLength); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/ColumnHeaderConstant.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/ColumnHeaderConstant.java new file mode 100644 index 00000000..23a4b42c --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/ColumnHeaderConstant.java @@ -0,0 +1,649 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.constant; + + +public class ColumnHeaderConstant { + + private ColumnHeaderConstant() { + // forbidding instantiation + } + + // column names for query statement + public static final String TIME = "Time"; + public static final String ENDTIME = "__endTime"; + public static final String VALUE = "Value"; + public static final String DEVICE = "Device"; + public static final String EXPLAIN_ANALYZE = "Explain Analyze"; + + // column names for schema statement + public static final String DATABASE = "Database"; + public static final String TIMESERIES = "Timeseries"; + public static final String ALIAS = "Alias"; + public static final String DATATYPE = "DataType"; + public static final String ENCODING = "Encoding"; + public static final String COMPRESSION = "Compression"; + public static final String TAGS = "Tags"; + public static final String ATTRIBUTES = "Attributes"; + public static final String NOTES = "Notes"; + public static final String DEADBAND = "Deadband"; + public static final String DEADBAND_PARAMETERS = "DeadbandParameters"; + public static final String IS_ALIGNED = "IsAligned"; + public static final String TEMPLATE = "Template"; + + public static final String COUNT = "Count"; + public static final String COLUMN_TTL = "TTL(ms)"; + public static final String SCHEMA_REPLICATION_FACTOR = "SchemaReplicationFactor"; + public static final String DATA_REPLICATION_FACTOR = "DataReplicationFactor"; + public static final String TIME_PARTITION_ORIGIN = "TimePartitionOrigin"; + public static final String TIME_PARTITION_INTERVAL = "TimePartitionInterval"; + public static final String SCHEMA_REGION_GROUP_NUM = "SchemaRegionGroupNum"; + public static final String MIN_SCHEMA_REGION_GROUP_NUM = "MinSchemaRegionGroupNum"; + public static final String MAX_SCHEMA_REGION_GROUP_NUM = "MaxSchemaRegionGroupNum"; + public static final String DATA_REGION_GROUP_NUM = "DataRegionGroupNum"; + public static final String MIN_DATA_REGION_GROUP_NUM = "MinDataRegionGroupNum"; + public static final String MAX_DATA_REGION_GROUP_NUM = "MaxDataRegionGroupNum"; + public static final String CHILD_PATHS = "ChildPaths"; + public static final String NODE_TYPES = "NodeTypes"; + public static final String CHILD_NODES = "ChildNodes"; + public static final String VERSION = "Version"; + public static final String BUILD_INFO = "BuildInfo"; + public static final String PATHS = "Paths"; + public static final String PATH = "Path"; + public static final String VARIABLE = "Variable"; + public static final String SCOPE = "Scope"; + + // column names for count statement + public static final String COLUMN = "Column"; + public static final String COUNT_DEVICES = "count(devices)"; + public static final String COUNT_NODES = "count(nodes)"; + public static final String COUNT_TIMESERIES = "count(timeseries)"; + public static final String COUNT_DATABASE = "count(database)"; + + // column names for show cluster and show cluster details statements + public static final String NODE_ID = "NodeID"; + public static final String NODE_TYPE = "NodeType"; + public static final String STATUS = "Status"; + public static final String INTERNAL_ADDRESS = "InternalAddress"; + public static final String INTERNAL_PORT = "InternalPort"; + public static final String CONFIG_CONSENSUS_PORT = "ConfigConsensusPort"; + public static final String RPC_ADDRESS = "RpcAddress"; + public static final String RPC_PORT = "RpcPort"; + public static final String DATA_CONSENSUS_PORT = "DataConsensusPort"; + public static final String SCHEMA_CONSENSUS_PORT = "SchemaConsensusPort"; + public static final String MPP_PORT = "MppPort"; + + // column names for show clusterId statement + public static final String CLUSTER_ID = "ClusterId"; + + // column names for verify connection statement + public static final String SERVICE_PROVIDER = "ServiceProvider"; + public static final String SENDER = "Sender"; + public static final String CONNECTION = "Connection"; + + // column names for show functions statement + public static final String FUNCTION_NAME = "FunctionName"; + public static final String FUNCTION_TYPE = "FunctionType"; + public static final String CLASS_NAME_UDF = "ClassName(UDF)"; + public static final String FUNCTION_STATE = "State"; + + // column names for show triggers statement + public static final String TRIGGER_NAME = "TriggerName"; + public static final String EVENT = "Event"; + public static final String STATE = "State"; + public static final String MODEL_TYPE = "ModelType"; + public static final String CONFIGS = "Configs"; + public static final String PATH_PATTERN = "PathPattern"; + public static final String CLASS_NAME = "ClassName"; + + // column names for show pipe plugins statement + public static final String PLUGIN_NAME = "PluginName"; + public static final String PLUGIN_TYPE = "PluginType"; + public static final String PLUGIN_JAR = "PluginJar"; + + // column names for show topics statement + public static final String TOPIC_NAME = "TopicName"; + public static final String TOPIC_CONFIGS = "TopicConfigs"; + + // column names for show subscriptions statement + public static final String CONSUMER_GROUP_NAME = "ConsumerGroupName"; + public static final String SUBSCRIBED_CONSUMERS = "SubscribedConsumers"; + + // show cluster status + public static final String NODE_TYPE_CONFIG_NODE = "ConfigNode"; + public static final String NODE_TYPE_DATA_NODE = "DataNode"; + public static final String NODE_TYPE_AI_NODE = "AINode"; + public static final String COLUMN_CLUSTER_NAME = "ClusterName"; + public static final String CONFIG_NODE_CONSENSUS_PROTOCOL_CLASS = + "ConfigNodeConsensusProtocolClass"; + public static final String DATA_REGION_CONSENSUS_PROTOCOL_CLASS = + "DataRegionConsensusProtocolClass"; + public static final String SCHEMA_REGION_CONSENSUS_PROTOCOL_CLASS = + "SchemaRegionConsensusProtocolClass"; + public static final String SERIES_SLOT_NUM = "SeriesSlotNum"; + public static final String SERIES_SLOT_EXECUTOR_CLASS = "SeriesSlotExecutorClass"; + public static final String SCHEMA_REGION_PER_DATA_NODE = "SchemaRegionPerDataNode"; + public static final String DATA_REGION_PER_DATA_NODE = "DataRegionPerDataNode"; + public static final String READ_CONSISTENCY_LEVEL = "ReadConsistencyLevel"; + public static final String DISK_SPACE_WARNING_THRESHOLD = "DiskSpaceWarningThreshold"; + + public static final String TIMESTAMP_PRECISION = "TimestampPrecision"; + + // column names for show region statement + public static final String REGION_ID = "RegionId"; + public static final String TYPE = "Type"; + public static final String DATA_NODE_ID = "DataNodeId"; + public static final String TIME_SLOT_NUM = "TimeSlotNum"; + public static final String SERIES_SLOT_ID = "SeriesSlotId"; + public static final String TIME_PARTITION = "TimePartition"; + public static final String COUNT_TIME_PARTITION = "count(timePartition)"; + public static final String START_TIME = "StartTime"; + public static final String ROLE = "Role"; + public static final String CREATE_TIME = "CreateTime"; + public static final String TSFILE_SIZE = "TsFileSize"; + + // column names for show datanodes + public static final String SCHEMA_REGION_NUM = "SchemaRegionNum"; + public static final String DATA_REGION_NUM = "DataRegionNum"; + + // column names for show device template statement + public static final String TEMPLATE_NAME = "TemplateName"; + + // column names for show pipe sink + public static final String NAME = "Name"; + + // column names for show pipe + public static final String ID = "ID"; + public static final String CREATION_TIME = "CreationTime"; + public static final String PIPE_EXTRACTOR = "PipeSource"; + public static final String PIPE_PROCESSOR = "PipeProcessor"; + public static final String PIPE_CONNECTOR = "PipeSink"; + public static final String EXCEPTION_MESSAGE = "ExceptionMessage"; + public static final String REMAINING_EVENT_COUNT = "RemainingEventCount"; + public static final String ESTIMATED_REMAINING_SECONDS = "EstimatedRemainingSeconds"; + + // column names for select into + public static final String SOURCE_DEVICE = "SourceDevice"; + public static final String SOURCE_COLUMN = "SourceColumn"; + public static final String TARGET_TIMESERIES = "TargetTimeseries"; + public static final String WRITTEN = "Written"; + + // column names for show cq + public static final String CQID = "CQId"; + public static final String QUERY = "Query"; + + // column names for show query processlist + public static final String QUERY_ID = "QueryId"; + public static final String ELAPSED_TIME = "ElapsedTime"; + public static final String STATEMENT = "Statement"; + + public static final String QUERY_ID_TABLE_MODEL = "query_id"; + public static final String QUERY_ID_START_TIME_TABLE_MODEL = "start_time"; + public static final String DATA_NODE_ID_TABLE_MODEL = "datanode_id"; + public static final String START_TIME_TABLE_MODEL = "start_time"; + public static final String ELAPSED_TIME_TABLE_MODEL = "elapsed_time"; + + public static final String TABLE_NAME_TABLE_MODEL = "table_name"; + public static final String COLUMN_NAME_TABLE_MODEL = "column_name"; + + public static final String SCHEMA_REPLICATION_FACTOR_TABLE_MODEL = "schema_replication_factor"; + public static final String DATA_REPLICATION_FACTOR_TABLE_MODEL = "data_replication_factor"; + public static final String TIME_PARTITION_INTERVAL_TABLE_MODEL = "time_partition_interval"; + public static final String SCHEMA_REGION_GROUP_NUM_TABLE_MODEL = "schema_region_group_num"; + public static final String DATA_REGION_GROUP_NUM_TABLE_MODEL = "data_region_group_num"; + + public static final String REGION_ID_TABLE_MODEL = "region_id"; + public static final String DATANODE_ID_TABLE_MODEL = "datanode_id"; + public static final String SERIES_SLOT_NUM_TABLE_MODEL = "series_slot_num"; + public static final String TIME_SLOT_NUM_TABLE_MODEL = "time_slot_num"; + public static final String RPC_ADDRESS_TABLE_MODEL = "rpc_address"; + public static final String RPC_PORT_TABLE_MODEL = "rpc_port"; + public static final String INTERNAL_ADDRESS_TABLE_MODEL = "internal_address"; + public static final String CREATE_TIME_TABLE_MODEL = "create_time"; + public static final String TS_FILE_SIZE_BYTES_TABLE_MODEL = "tsfile_size_bytes"; + + public static final String CREATION_TIME_TABLE_MODEL = "creation_time"; + public static final String PIPE_SOURCE_TABLE_MODEL = "pipe_source"; + public static final String PIPE_PROCESSOR_TABLE_MODEL = "pipe_processor"; + public static final String PIPE_SINK_TABLE_MODEL = "pipe_sink"; + public static final String EXCEPTION_MESSAGE_TABLE_MODEL = "exception_message"; + public static final String REMAINING_EVENT_COUNT_TABLE_MODEL = "remaining_event_count"; + public static final String ESTIMATED_REMAINING_SECONDS_TABLE_MODEL = + "estimated_remaining_seconds"; + + public static final String PLUGIN_NAME_TABLE_MODEL = "plugin_name"; + public static final String PLUGIN_TYPE_TABLE_MODEL = "plugin_type"; + public static final String CLASS_NAME_TABLE_MODEL = "class_name"; + public static final String PLUGIN_JAR_TABLE_MODEL = "plugin_jar"; + + public static final String TOPIC_NAME_TABLE_MODEL = "topic_name"; + public static final String TOPIC_CONFIGS_TABLE_MODEL = "topic_configs"; + + public static final String CONSUMER_GROUP_NAME_TABLE_MODEL = "consumer_group_name"; + public static final String SUBSCRIBED_CONSUMERS_TABLE_MODEL = "subscribed_consumers"; + + // column names for show space quota + public static final String QUOTA_TYPE = "QuotaType"; + public static final String LIMIT = "Limit"; + public static final String USED = "Used"; + + // column names for show throttle quota + public static final String USER = "User"; + public static final String READ_WRITE = "Read/Write"; + + // column names for show models/trials + public static final String MODEL_ID = "ModelId"; + + // column names for views (e.g. logical view) + public static final String VIEW_TYPE = "ViewType"; + public static final String SOURCE = "Source"; + + // column names for show current timestamp + public static final String CURRENT_TIMESTAMP = "CurrentTimestamp"; + + // column names for table query + public static final String COLUMN_NAME = "ColumnName"; + public static final String COLUMN_DATA_TYPE = "DataType"; + public static final String COLUMN_CATEGORY = "Category"; + public static final String TABLE_NAME = "TableName"; + public static final String PRIVILEGES = "Privileges"; + + public static final String GRANT_OPTION = "GrantOption"; + + public static final String CURRENT_USER = "CurrentUser"; + + public static final String CURRENT_DATABASE = "CurrentDatabase"; + + public static final String CURRENT_SQL_DIALECT = "CurrentSqlDialect"; +/* + public static final List lastQueryColumnHeaders = + ImmutableList.of( + new ColumnHeader(TIMESERIES, TSDataType.TEXT), + new ColumnHeader(VALUE, TSDataType.TEXT), + new ColumnHeader(DATATYPE, TSDataType.TEXT)); + + public static final List showTimeSeriesColumnHeaders = + ImmutableList.of( + new ColumnHeader(TIMESERIES, TSDataType.TEXT), + new ColumnHeader(ALIAS, TSDataType.TEXT), + new ColumnHeader(DATABASE, TSDataType.TEXT), + new ColumnHeader(DATATYPE, TSDataType.TEXT), + new ColumnHeader(ENCODING, TSDataType.TEXT), + new ColumnHeader(COMPRESSION, TSDataType.TEXT), + new ColumnHeader(TAGS, TSDataType.TEXT), + new ColumnHeader(ATTRIBUTES, TSDataType.TEXT), + new ColumnHeader(DEADBAND, TSDataType.TEXT), + new ColumnHeader(DEADBAND_PARAMETERS, TSDataType.TEXT), + new ColumnHeader(VIEW_TYPE, TSDataType.TEXT)); + + public static final List showDevicesWithSgColumnHeaders = + ImmutableList.of( + new ColumnHeader(DEVICE, TSDataType.TEXT), + new ColumnHeader(DATABASE, TSDataType.TEXT), + new ColumnHeader(IS_ALIGNED, TSDataType.TEXT), + new ColumnHeader(TEMPLATE, TSDataType.TEXT), + new ColumnHeader(COLUMN_TTL, TSDataType.TEXT)); + + public static final List showDevicesColumnHeaders = + ImmutableList.of( + new ColumnHeader(DEVICE, TSDataType.TEXT), + new ColumnHeader(IS_ALIGNED, TSDataType.TEXT), + new ColumnHeader(TEMPLATE, TSDataType.TEXT), + new ColumnHeader(COLUMN_TTL, TSDataType.TEXT)); + public static final List showTTLColumnHeaders = + ImmutableList.of( + new ColumnHeader(DEVICE, TSDataType.TEXT), new ColumnHeader(COLUMN_TTL, TSDataType.TEXT)); + + public static final List showStorageGroupsColumnHeaders = + ImmutableList.of( + new ColumnHeader(DATABASE, TSDataType.TEXT), + new ColumnHeader(SCHEMA_REPLICATION_FACTOR, TSDataType.INT32), + new ColumnHeader(DATA_REPLICATION_FACTOR, TSDataType.INT32), + new ColumnHeader(TIME_PARTITION_ORIGIN, TSDataType.INT64), + new ColumnHeader(TIME_PARTITION_INTERVAL, TSDataType.INT64)); + + public static final List showStorageGroupsDetailColumnHeaders = + ImmutableList.of( + new ColumnHeader(DATABASE, TSDataType.TEXT), + new ColumnHeader(SCHEMA_REPLICATION_FACTOR, TSDataType.INT32), + new ColumnHeader(DATA_REPLICATION_FACTOR, TSDataType.INT32), + new ColumnHeader(TIME_PARTITION_ORIGIN, TSDataType.INT64), + new ColumnHeader(TIME_PARTITION_INTERVAL, TSDataType.INT64), + new ColumnHeader(SCHEMA_REGION_GROUP_NUM, TSDataType.INT32), + new ColumnHeader(MIN_SCHEMA_REGION_GROUP_NUM, TSDataType.INT32), + new ColumnHeader(MAX_SCHEMA_REGION_GROUP_NUM, TSDataType.INT32), + new ColumnHeader(DATA_REGION_GROUP_NUM, TSDataType.INT32), + new ColumnHeader(MIN_DATA_REGION_GROUP_NUM, TSDataType.INT32), + new ColumnHeader(MAX_DATA_REGION_GROUP_NUM, TSDataType.INT32)); + + public static final List showChildPathsColumnHeaders = + ImmutableList.of( + new ColumnHeader(CHILD_PATHS, TSDataType.TEXT), + new ColumnHeader(NODE_TYPES, TSDataType.TEXT)); + + public static final List showNodesInSchemaTemplateHeaders = + ImmutableList.of( + new ColumnHeader(CHILD_NODES, TSDataType.TEXT), + new ColumnHeader(DATATYPE, TSDataType.TEXT), + new ColumnHeader(ENCODING, TSDataType.TEXT), + new ColumnHeader(COMPRESSION, TSDataType.TEXT)); + + public static final List showChildNodesColumnHeaders = + ImmutableList.of(new ColumnHeader(CHILD_NODES, TSDataType.TEXT)); + + public static final List showVersionColumnHeaders = + ImmutableList.of( + new ColumnHeader(VERSION, TSDataType.TEXT), + new ColumnHeader(BUILD_INFO, TSDataType.TEXT)); + + public static final List showPathsUsingTemplateHeaders = + ImmutableList.of(new ColumnHeader(PATHS, TSDataType.TEXT)); + + public static final List showPathSetTemplateHeaders = + ImmutableList.of(new ColumnHeader(PATHS, TSDataType.TEXT)); + + public static final List countDevicesColumnHeaders = + ImmutableList.of(new ColumnHeader(COUNT_DEVICES, TSDataType.INT64)); + + public static final List countNodesColumnHeaders = + ImmutableList.of(new ColumnHeader(COUNT_NODES, TSDataType.INT64)); + + public static final List countLevelTimeSeriesColumnHeaders = + ImmutableList.of( + new ColumnHeader(COLUMN, TSDataType.TEXT), + new ColumnHeader(COUNT_TIMESERIES, TSDataType.INT64)); + + public static final List countTimeSeriesColumnHeaders = + ImmutableList.of(new ColumnHeader(COUNT_TIMESERIES, TSDataType.INT64)); + + public static final List countStorageGroupColumnHeaders = + ImmutableList.of(new ColumnHeader(COUNT_DATABASE, TSDataType.INT32)); + + public static final List showRegionColumnHeaders = + ImmutableList.of( + new ColumnHeader(REGION_ID, TSDataType.INT32), + new ColumnHeader(TYPE, TSDataType.TEXT), + new ColumnHeader(STATUS, TSDataType.TEXT), + new ColumnHeader(DATABASE, TSDataType.TEXT), + new ColumnHeader(SERIES_SLOT_NUM, TSDataType.INT32), + new ColumnHeader(TIME_SLOT_NUM, TSDataType.INT64), + new ColumnHeader(DATA_NODE_ID, TSDataType.INT32), + new ColumnHeader(RPC_ADDRESS, TSDataType.TEXT), + new ColumnHeader(RPC_PORT, TSDataType.INT32), + new ColumnHeader(INTERNAL_ADDRESS, TSDataType.TEXT), + new ColumnHeader(ROLE, TSDataType.TEXT), + new ColumnHeader(CREATE_TIME, TSDataType.TEXT), + new ColumnHeader(TSFILE_SIZE, TSDataType.TEXT)); + + public static final List showAINodesColumnHeaders = + ImmutableList.of( + new ColumnHeader(NODE_ID, TSDataType.INT32), + new ColumnHeader(STATUS, TSDataType.TEXT), + new ColumnHeader(RPC_ADDRESS, TSDataType.TEXT), + new ColumnHeader(RPC_PORT, TSDataType.INT32)); + + public static final List showDataNodesColumnHeaders = + ImmutableList.of( + new ColumnHeader(NODE_ID, TSDataType.INT32), + new ColumnHeader(STATUS, TSDataType.TEXT), + new ColumnHeader(RPC_ADDRESS, TSDataType.TEXT), + new ColumnHeader(RPC_PORT, TSDataType.INT32), + new ColumnHeader(DATA_REGION_NUM, TSDataType.INT32), + new ColumnHeader(SCHEMA_REGION_NUM, TSDataType.INT32)); + + public static final List showConfigNodesColumnHeaders = + ImmutableList.of( + new ColumnHeader(NODE_ID, TSDataType.INT32), + new ColumnHeader(STATUS, TSDataType.TEXT), + new ColumnHeader(INTERNAL_ADDRESS, TSDataType.TEXT), + new ColumnHeader(INTERNAL_PORT, TSDataType.INT32), + new ColumnHeader(ROLE, TSDataType.TEXT)); + + public static final List showClusterColumnHeaders = + ImmutableList.of( + new ColumnHeader(NODE_ID, TSDataType.INT32), + new ColumnHeader(NODE_TYPE, TSDataType.TEXT), + new ColumnHeader(STATUS, TSDataType.TEXT), + new ColumnHeader(INTERNAL_ADDRESS, TSDataType.TEXT), + new ColumnHeader(INTERNAL_PORT, TSDataType.INT32), + new ColumnHeader(VERSION, TSDataType.TEXT), + new ColumnHeader(BUILD_INFO, TSDataType.TEXT)); + + public static final List showClusterDetailsColumnHeaders = + ImmutableList.of( + new ColumnHeader(NODE_ID, TSDataType.INT32), + new ColumnHeader(NODE_TYPE, TSDataType.TEXT), + new ColumnHeader(STATUS, TSDataType.TEXT), + new ColumnHeader(INTERNAL_ADDRESS, TSDataType.TEXT), + new ColumnHeader(INTERNAL_PORT, TSDataType.INT32), + new ColumnHeader(CONFIG_CONSENSUS_PORT, TSDataType.TEXT), + new ColumnHeader(RPC_ADDRESS, TSDataType.TEXT), + new ColumnHeader(RPC_PORT, TSDataType.TEXT), + new ColumnHeader(MPP_PORT, TSDataType.TEXT), + new ColumnHeader(SCHEMA_CONSENSUS_PORT, TSDataType.TEXT), + new ColumnHeader(DATA_CONSENSUS_PORT, TSDataType.TEXT), + new ColumnHeader(VERSION, TSDataType.TEXT), + new ColumnHeader(BUILD_INFO, TSDataType.TEXT)); + + public static final List showClusterIdColumnHeaders = + ImmutableList.of(new ColumnHeader(CLUSTER_ID, TSDataType.TEXT)); + + public static final List testConnectionColumnHeaders = + ImmutableList.of( + new ColumnHeader(SERVICE_PROVIDER, TSDataType.TEXT), + new ColumnHeader(SENDER, TSDataType.TEXT), + new ColumnHeader(CONNECTION, TSDataType.TEXT)); + + public static final List showVariablesColumnHeaders = + ImmutableList.of( + new ColumnHeader(VARIABLE, TSDataType.TEXT), new ColumnHeader(VALUE, TSDataType.TEXT)); + + public static final List showFunctionsColumnHeaders = + ImmutableList.of( + new ColumnHeader(FUNCTION_NAME, TSDataType.TEXT), + new ColumnHeader(FUNCTION_TYPE, TSDataType.TEXT), + new ColumnHeader(CLASS_NAME_UDF, TSDataType.TEXT), + new ColumnHeader(FUNCTION_STATE, TSDataType.TEXT)); + + public static final List showTriggersColumnHeaders = + ImmutableList.of( + new ColumnHeader(TRIGGER_NAME, TSDataType.TEXT), + new ColumnHeader(EVENT, TSDataType.TEXT), + new ColumnHeader(TYPE, TSDataType.TEXT), + new ColumnHeader(STATE, TSDataType.TEXT), + new ColumnHeader(PATH_PATTERN, TSDataType.TEXT), + new ColumnHeader(CLASS_NAME, TSDataType.TEXT), + new ColumnHeader(NODE_ID, TSDataType.TEXT)); + + public static final List showPipePluginsColumnHeaders = + ImmutableList.of( + new ColumnHeader(PLUGIN_NAME, TSDataType.TEXT), + new ColumnHeader(PLUGIN_TYPE, TSDataType.TEXT), + new ColumnHeader(CLASS_NAME, TSDataType.TEXT), + new ColumnHeader(PLUGIN_JAR, TSDataType.TEXT)); + + public static final List showSchemaTemplateHeaders = + ImmutableList.of(new ColumnHeader(TEMPLATE_NAME, TSDataType.TEXT)); + + public static final List showPipeSinkTypeColumnHeaders = + ImmutableList.of(new ColumnHeader(TYPE, TSDataType.TEXT)); + + public static final List showPipeSinkColumnHeaders = + ImmutableList.of( + new ColumnHeader(NAME, TSDataType.TEXT), + new ColumnHeader(TYPE, TSDataType.TEXT), + new ColumnHeader(ATTRIBUTES, TSDataType.TEXT)); + + public static final List showPipeColumnHeaders = + ImmutableList.of( + new ColumnHeader(ID, TSDataType.TEXT), + new ColumnHeader(CREATION_TIME, TSDataType.TEXT), + new ColumnHeader(STATE, TSDataType.TEXT), + new ColumnHeader(PIPE_EXTRACTOR, TSDataType.TEXT), + new ColumnHeader(PIPE_PROCESSOR, TSDataType.TEXT), + new ColumnHeader(PIPE_CONNECTOR, TSDataType.TEXT), + new ColumnHeader(EXCEPTION_MESSAGE, TSDataType.TEXT), + new ColumnHeader(REMAINING_EVENT_COUNT, TSDataType.TEXT), + new ColumnHeader(ESTIMATED_REMAINING_SECONDS, TSDataType.TEXT)); + + public static final List showTopicColumnHeaders = + ImmutableList.of( + new ColumnHeader(TOPIC_NAME, TSDataType.TEXT), + new ColumnHeader(TOPIC_CONFIGS, TSDataType.TEXT)); + + public static final List showSubscriptionColumnHeaders = + ImmutableList.of( + new ColumnHeader(TOPIC_NAME, TSDataType.TEXT), + new ColumnHeader(CONSUMER_GROUP_NAME, TSDataType.TEXT), + new ColumnHeader(SUBSCRIBED_CONSUMERS, TSDataType.TEXT)); + + public static final List selectIntoColumnHeaders = + ImmutableList.of( + new ColumnHeader(SOURCE_COLUMN, TSDataType.TEXT), + new ColumnHeader(TARGET_TIMESERIES, TSDataType.TEXT), + new ColumnHeader(WRITTEN, TSDataType.INT32)); + + public static final List selectIntoAlignByDeviceColumnHeaders = + ImmutableList.of( + new ColumnHeader(SOURCE_DEVICE, TSDataType.TEXT), + new ColumnHeader(SOURCE_COLUMN, TSDataType.TEXT), + new ColumnHeader(TARGET_TIMESERIES, TSDataType.TEXT), + new ColumnHeader(WRITTEN, TSDataType.INT32)); + + public static final List getRegionIdColumnHeaders = + ImmutableList.of(new ColumnHeader(REGION_ID, TSDataType.INT32)); + + public static final List getTimeSlotListColumnHeaders = + ImmutableList.of( + new ColumnHeader(TIME_PARTITION, TSDataType.INT64), + new ColumnHeader(START_TIME, TSDataType.TEXT)); + + public static final List countTimeSlotListColumnHeaders = + ImmutableList.of(new ColumnHeader(COUNT_TIME_PARTITION, TSDataType.INT64)); + + public static final List getSeriesSlotListColumnHeaders = + ImmutableList.of(new ColumnHeader(SERIES_SLOT_ID, TSDataType.INT32)); + + public static final List showContinuousQueriesColumnHeaders = + ImmutableList.of( + new ColumnHeader(CQID, TSDataType.TEXT), + new ColumnHeader(QUERY, TSDataType.TEXT), + new ColumnHeader(STATE, TSDataType.TEXT)); + + public static final List showQueriesColumnHeaders = + ImmutableList.of( + new ColumnHeader(QUERY_ID, TSDataType.TEXT), + new ColumnHeader(DATA_NODE_ID, TSDataType.INT32), + new ColumnHeader(ELAPSED_TIME, TSDataType.FLOAT), + new ColumnHeader(STATEMENT, TSDataType.TEXT)); + + public static final List showSpaceQuotaColumnHeaders = + ImmutableList.of( + new ColumnHeader(DATABASE, TSDataType.TEXT), + new ColumnHeader(QUOTA_TYPE, TSDataType.TEXT), + new ColumnHeader(LIMIT, TSDataType.TEXT), + new ColumnHeader(USED, TSDataType.TEXT)); + + public static final List showThrottleQuotaColumnHeaders = + ImmutableList.of( + new ColumnHeader(USER, TSDataType.TEXT), + new ColumnHeader(QUOTA_TYPE, TSDataType.TEXT), + new ColumnHeader(LIMIT, TSDataType.TEXT), + new ColumnHeader(READ_WRITE, TSDataType.TEXT)); + + public static final List showModelsColumnHeaders = + ImmutableList.of( + new ColumnHeader(MODEL_ID, TSDataType.TEXT), + new ColumnHeader(MODEL_TYPE, TSDataType.TEXT), + new ColumnHeader(STATE, TSDataType.TEXT), + new ColumnHeader(CONFIGS, TSDataType.TEXT), + new ColumnHeader(NOTES, TSDataType.TEXT)); + + public static final List showLogicalViewColumnHeaders = + ImmutableList.of( + new ColumnHeader(TIMESERIES, TSDataType.TEXT), + new ColumnHeader(DATABASE, TSDataType.TEXT), + new ColumnHeader(DATATYPE, TSDataType.TEXT), + new ColumnHeader(TAGS, TSDataType.TEXT), + new ColumnHeader(ATTRIBUTES, TSDataType.TEXT), + new ColumnHeader(VIEW_TYPE, TSDataType.TEXT), + new ColumnHeader(SOURCE, TSDataType.TEXT)); + + public static final List showDBColumnHeaders = + ImmutableList.of( + new ColumnHeader(DATABASE, TSDataType.TEXT), + new ColumnHeader(COLUMN_TTL, TSDataType.TEXT), + new ColumnHeader(SCHEMA_REPLICATION_FACTOR, TSDataType.INT32), + new ColumnHeader(DATA_REPLICATION_FACTOR, TSDataType.INT32), + new ColumnHeader(TIME_PARTITION_INTERVAL, TSDataType.INT64)); + + public static final List showDBDetailsColumnHeaders = + ImmutableList.of( + new ColumnHeader(DATABASE, TSDataType.TEXT), + new ColumnHeader(COLUMN_TTL, TSDataType.TEXT), + new ColumnHeader(SCHEMA_REPLICATION_FACTOR, TSDataType.INT32), + new ColumnHeader(DATA_REPLICATION_FACTOR, TSDataType.INT32), + new ColumnHeader(TIME_PARTITION_INTERVAL, TSDataType.INT64), + new ColumnHeader(SCHEMA_REGION_GROUP_NUM, TSDataType.INT32), + new ColumnHeader(DATA_REGION_GROUP_NUM, TSDataType.INT32)); + + public static final List describeTableColumnHeaders = + ImmutableList.of( + new ColumnHeader(COLUMN_NAME, TSDataType.TEXT), + new ColumnHeader(COLUMN_DATA_TYPE, TSDataType.TEXT), + new ColumnHeader(COLUMN_CATEGORY, TSDataType.TEXT)); + + public static final List describeTableDetailsColumnHeaders = + ImmutableList.of( + new ColumnHeader(COLUMN_NAME, TSDataType.TEXT), + new ColumnHeader(COLUMN_DATA_TYPE, TSDataType.TEXT), + new ColumnHeader(COLUMN_CATEGORY, TSDataType.TEXT), + new ColumnHeader(STATUS, TSDataType.TEXT)); + + public static final List showTablesColumnHeaders = + ImmutableList.of( + new ColumnHeader(TABLE_NAME, TSDataType.TEXT), + new ColumnHeader(COLUMN_TTL, TSDataType.TEXT)); + + public static final List showTablesDetailsColumnHeaders = + ImmutableList.of( + new ColumnHeader(TABLE_NAME, TSDataType.TEXT), + new ColumnHeader(COLUMN_TTL, TSDataType.TEXT), + new ColumnHeader(STATUS, TSDataType.TEXT)); + + public static final List LIST_USER_OR_ROLE_PRIVILEGES_COLUMN_HEADERS = + ImmutableList.of( + new ColumnHeader(ROLE, TSDataType.TEXT), + new ColumnHeader(SCOPE, TSDataType.TEXT), + new ColumnHeader(PRIVILEGES, TSDataType.TEXT), + new ColumnHeader(GRANT_OPTION, TSDataType.BOOLEAN)); + + public static final List SHOW_CURRENT_USER_COLUMN_HEADERS = + ImmutableList.of(new ColumnHeader(CURRENT_USER, TSDataType.STRING)); + + public static final List SHOW_CURRENT_DATABASE_COLUMN_HEADERS = + ImmutableList.of(new ColumnHeader(CURRENT_DATABASE, TSDataType.STRING)); + + public static final List SHOW_CURRENT_SQL_DIALECT_COLUMN_HEADERS = + ImmutableList.of(new ColumnHeader(CURRENT_SQL_DIALECT, TSDataType.STRING)); + + public static final List SHOW_CURRENT_TIMESTAMP_COLUMN_HEADERS = + ImmutableList.of(new ColumnHeader(CURRENT_TIMESTAMP, TSDataType.TIMESTAMP));*/ +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/IoTDBConstant.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/IoTDBConstant.java new file mode 100644 index 00000000..03aabc95 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/IoTDBConstant.java @@ -0,0 +1,371 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.constant; + +import java.io.File; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; +import java.util.regex.Pattern; + +public class IoTDBConstant { + + private IoTDBConstant() {} + + static { + Properties prop = new Properties(); + String finalBuildInfo = "UNKNOWN"; + try { + prop.load( + new InputStreamReader( + Objects.requireNonNull(IoTDBConstant.class.getResourceAsStream("/git.properties")), + StandardCharsets.UTF_8)); + finalBuildInfo = prop.getProperty("git.commit.id.abbrev", "UNKNOWN"); + String isDirty = prop.getProperty("git.dirty", "false"); + if (isDirty.equalsIgnoreCase("true")) { + finalBuildInfo += "-dev"; + } + } catch (Exception e) { + System.err.println("get git.properties error: " + e.getMessage()); + } + BUILD_INFO = finalBuildInfo; + } + + public static final String BUILD_INFO; + + public static final String DN_ENV_FILE_NAME = "datanode-env"; + public static final String CN_ENV_FILE_NAME = "confignode-env"; + public static final String IOTDB_CONF = "IOTDB_CONF"; + public static final String GLOBAL_DB_NAME = "IoTDB"; + public static final String CN_ROLE = "confignode"; + public static final String DN_ROLE = "datanode"; + + public static final String DATA_NODE_CONF_FILE_NAME = "iotdb-datanode.properties"; + + public static final String DN_RPC_ADDRESS = "dn_rpc_address"; + public static final String DN_RPC_PORT = "dn_rpc_port"; + + public static final String CN_INTERNAL_ADDRESS = "cn_internal_address"; + public static final String DN_INTERNAL_ADDRESS = "dn_internal_address"; + + public static final String CN_METRIC_PROMETHEUS_REPORTER_PORT = + "cn_metric_prometheus_reporter_port"; + public static final String DN_METRIC_PROMETHEUS_REPORTER_PORT = + "dn_metric_prometheus_reporter_port"; + + public static final String CN_INTERNAL_PORT = "cn_internal_port"; + public static final String DN_INTERNAL_PORT = "dn_internal_port"; + public static final String CN_CONSENSUS_PORT = "cn_consensus_port"; + + public static final String CN_SEED_CONFIG_NODE = "cn_seed_config_node"; + public static final String CN_TARGET_CONFIG_NODE_LIST = "cn_target_config_node_list"; + public static final String DN_SEED_CONFIG_NODE = "dn_seed_config_node"; + public static final String DN_TARGET_CONFIG_NODE_LIST = "dn_target_config_node_list"; + + public static final String CLUSTER_NAME = "cluster_name"; + public static final String DEFAULT_CLUSTER_NAME = "defaultCluster"; + public static final String LOGO = + "" + + " _____ _________ ______ ______ \n" + + "|_ _| | _ _ ||_ _ `.|_ _ \\ \n" + + " | | .--.|_/ | | \\_| | | `. \\ | |_) | \n" + + " | | / .'`\\ \\ | | | | | | | __'. \n" + + " _| |_| \\__. | _| |_ _| |_.' /_| |__) | \n" + + "|_____|'.__.' |_____| |______.'|_______/ "; + + // when running the program in IDE, we can not get the version info using + // getImplementationVersion() + public static final String VERSION = + IoTDBConstant.class.getPackage().getImplementationVersion() != null + ? IoTDBConstant.class.getPackage().getImplementationVersion() + : "UNKNOWN"; + public static final String MAJOR_VERSION = + "UNKNOWN".equals(VERSION) + ? "UNKNOWN" + : VERSION.split("\\.")[0] + "." + VERSION.split("\\.")[1]; + public static final String VERSION_WITH_BUILD = VERSION + " (Build: " + BUILD_INFO + ")"; + + public static final String AUDIT_LOGGER_NAME = "IoTDB_AUDIT_LOGGER"; + public static final String SLOW_SQL_LOGGER_NAME = "SLOW_SQL"; + public static final String SAMPLED_QUERIES_LOGGER_NAME = "SAMPLED_QUERIES"; + + public static final String COMPACTION_LOGGER_NAME = "COMPACTION"; + public static final String EXPLAIN_ANALYZE_LOGGER_NAME = "EXPLAIN_ANALYZE"; + + public static final String IOTDB_JMX_LOCAL = "iotdb.jmx.local"; + public static final String IOTDB_JMX_PORT = "com.sun.management.jmxremote.port"; + + public static final String IOTDB_SERVICE_JMX_NAME = "org.apache.iotdb.service"; + public static final String IOTDB_THREADPOOL_JMX_NAME = "org.apache.iotdb.threadpool"; + public static final String JMX_TYPE = "type"; + + public static final long PB = 1L << 50; + public static final long TB = 1L << 40; + public static final long GB = 1L << 30; + public static final long MB = 1L << 20; + public static final long KB = 1L << 10; + + public static final String IOTDB_HOME = "IOTDB_HOME"; + + public static final String IOTDB_DATA_HOME = "IOTDB_DATA_HOME"; + + public static final String SEQFILE_LOG_NODE_SUFFIX = "-seq"; + public static final String UNSEQFILE_LOG_NODE_SUFFIX = "-unseq"; + + public static final String PATH_ROOT = "root"; + public static final char PATH_SEPARATOR = '.'; + public static final String PROFILE_SUFFIX = ".profile"; + public static final String MAX_TIME = "max_time"; + public static final String MIN_TIME = "min_time"; + public static final String LAST_VALUE = "last_value"; + public static final int MIN_SUPPORTED_JDK_VERSION = 8; + public static final Set reservedWords = new HashSet<>(); + + static { + reservedWords.add("TIME"); + reservedWords.add("TIMESTAMP"); + reservedWords.add("ROOT"); + } + + // show info + public static final String COLUMN_ITEM = " item"; + public static final String COLUMN_VALUE = "value"; + public static final String COLUMN_VERSION = "version"; + public static final String COLUMN_BUILD_INFO = "build info"; + public static final String COLUMN_TIMESERIES = "timeseries"; + public static final String COLUMN_TIMESERIES_ALIAS = "alias"; + public static final String COLUMN_TIMESERIES_DATATYPE = "dataType"; + public static final String COLUMN_TIMESERIES_ENCODING = "encoding"; + public static final String COLUMN_TIMESERIES_COMPRESSION = "compression"; + public static final String COLUMN_TIMESERIES_COMPRESSOR = "compressor"; + public static final String COLUMN_CHILD_PATHS = "child paths"; + public static final String COLUMN_CHILD_PATHS_TYPES = "node types"; + public static final String COLUMN_CHILD_NODES = "child nodes"; + public static final String COLUMN_DEVICES = "devices"; + public static final String COLUMN_DELETED_DEVICE_NUM = "num_of_deleted_devices"; + public static final String COLUMN_COLUMN = "column"; + public static final String COLUMN_COUNT = "count"; + public static final String COLUMN_TAGS = "tags"; + public static final String COLUMN_ATTRIBUTES = "attributes"; + public static final String COLUMN_IS_ALIGNED = "isAligned"; + public static final String COLUMN_DISTRIBUTION_PLAN = "distribution plan"; + public static final String QUERY_ID = "queryId"; + public static final String STATEMENT = "statement"; + + public static final String COLUMN_DATABASE = "database"; + + public static final String COLUMN_FUNCTION_NAME = "function name"; + public static final String COLUMN_FUNCTION_TYPE = "function type"; + public static final String COLUMN_FUNCTION_CLASS = "class name (UDF)"; + + public static final String COLUMN_SCHEMA_TEMPLATE = "template name"; + + // for tree model + public static final String FUNCTION_TYPE_NATIVE = "native"; + public static final String FUNCTION_TYPE_BUILTIN_SCALAR = "built-in scalar"; + public static final String FUNCTION_TYPE_BUILTIN_UDAF = "built-in UDAF"; + public static final String FUNCTION_TYPE_BUILTIN_UDTF = "built-in UDTF"; + public static final String FUNCTION_TYPE_EXTERNAL_UDAF = "external UDAF"; + public static final String FUNCTION_TYPE_EXTERNAL_UDTF = "external UDTF"; + // for table model + public static final String FUNCTION_TYPE_BUILTIN_SCALAR_FUNC = "built-in scalar function"; + public static final String FUNCTION_TYPE_BUILTIN_AGG_FUNC = "built-in aggregate function"; + public static final String FUNCTION_TYPE_BUILTIN_TABLE_FUNC = "built-in table function"; + public static final String FUNCTION_TYPE_USER_DEFINED_SCALAR_FUNC = + "user-defined scalar function"; + public static final String FUNCTION_TYPE_USER_DEFINED_AGG_FUNC = + "user-defined aggregate function"; + public static final String FUNCTION_TYPE_USER_DEFINED_TABLE_FUNC = "user-defined table function"; + // common + public static final String FUNCTION_TYPE_UNKNOWN = "UNKNOWN"; + public static final String FUNCTION_STATE_AVAILABLE = "AVAILABLE"; + public static final String FUNCTION_STATE_UNAVAILABLE = "UNAVAILABLE"; + + public static final String COLUMN_TRIGGER_NAME = "trigger name"; + public static final String COLUMN_TRIGGER_STATUS = "status"; + public static final String COLUMN_TRIGGER_EVENT = "event"; + public static final String COLUMN_TRIGGER_PATH = "path"; + public static final String COLUMN_TRIGGER_CLASS = "class name"; + public static final String COLUMN_TRIGGER_ATTRIBUTES = "attributes"; + + public static final String COLUMN_TRIGGER_STATUS_STARTED = "started"; + public static final String COLUMN_TRIGGER_STATUS_STOPPED = "stopped"; + + public static final String ONE_LEVEL_PATH_WILDCARD = "*"; + public static final String MULTI_LEVEL_PATH_WILDCARD = "**"; + public static final String TIME = "time"; + + // sdt parameters + public static final String LOSS = "loss"; + public static final String SDT = "sdt"; + public static final String SDT_COMP_DEV = "compdev"; + public static final String SDT_COMP_MIN_TIME = "compmintime"; + public static final String SDT_COMP_MAX_TIME = "compmaxtime"; + public static final String[] SDT_PARAMETERS = + new String[] {SDT_COMP_DEV, SDT_COMP_MIN_TIME, SDT_COMP_MAX_TIME}; + + public static final String DEADBAND = "deadband"; + public static final String MAX_POINT_NUMBER = "max_point_number"; + public static final String MAX_STRING_LENGTH = "max_string_length"; + public static final Set ALLOWED_SCHEMA_PROPS = + new HashSet<>( + Arrays.asList( + DEADBAND, + LOSS, + SDT, + SDT_COMP_DEV, + SDT_COMP_MIN_TIME, + SDT_COMP_MAX_TIME, + MAX_POINT_NUMBER, + MAX_STRING_LENGTH)); + + // default base dir, stores all IoTDB runtime files + public static final String CN_DEFAULT_DATA_DIR = "data" + File.separator + CN_ROLE; + public static final String DN_DEFAULT_DATA_DIR = "data" + File.separator + DN_ROLE; + + // data folder name + public static final String DATA_FOLDER_NAME = "data"; + public static final String SEQUENCE_FOLDER_NAME = "sequence"; + public static final String UNSEQUENCE_FOLDER_NAME = "unsequence"; + public static final String FILE_NAME_SEPARATOR = "-"; + public static final String CONSENSUS_FOLDER_NAME = "consensus"; + public static final String DATA_REGION_FOLDER_NAME = "data_region"; + public static final String INVALID_DATA_REGION_FOLDER_NAME = "invalid_data_region"; + public static final String SCHEMA_REGION_FOLDER_NAME = "schema_region"; + public static final String SNAPSHOT_FOLDER_NAME = "snapshot"; + + // system folder name + public static final String SYSTEM_FOLDER_NAME = "system"; + public static final String SCHEMA_FOLDER_NAME = "schema"; + public static final String LOAD_TSFILE_FOLDER_NAME = "load"; + public static final String LOAD_TSFILE_ACTIVE_LISTENING_PENDING_FOLDER_NAME = "pending"; + public static final String LOAD_TSFILE_ACTIVE_LISTENING_FAILED_FOLDER_NAME = "failed"; + public static final String SYNC_FOLDER_NAME = "sync"; + public static final String QUERY_FOLDER_NAME = "query"; + public static final String EXT_FOLDER_NAME = "ext"; + public static final String UDF_FOLDER_NAME = "udf"; + public static final String TRIGGER_FOLDER_NAME = "trigger"; + public static final String PIPE_FOLDER_NAME = "pipe"; + public static final String TMP_FOLDER_NAME = "tmp"; + public static final String DELETION_FOLDER_NAME = "deletion"; + + public static final String MQTT_FOLDER_NAME = "mqtt"; + public static final String WAL_FOLDER_NAME = "wal"; + public static final String EXT_PIPE_FOLDER_NAME = "extPipe"; + + // mqtt + public static final String ENABLE_MQTT = "enable_mqtt_service"; + public static final String MQTT_HOST_NAME = "mqtt_host"; + public static final String MQTT_PORT_NAME = "mqtt_port"; + public static final String MQTT_HANDLER_POOL_SIZE_NAME = "mqtt_handler_pool_size"; + public static final String MQTT_PAYLOAD_FORMATTER_NAME = "mqtt_payload_formatter"; + public static final String MQTT_DATA_PATH = "mqtt_data_path"; + public static final String MQTT_MAX_MESSAGE_SIZE = "mqtt_max_message_size"; + + // thrift + public static final int LEFT_SIZE_IN_REQUEST = 4 * 1024 * 1024; + public static final int DEFAULT_FETCH_SIZE = 5000; + public static final int DEFAULT_CONNECTION_TIMEOUT_MS = 0; + + // ratis + public static final int RAFT_LOG_BASIC_SIZE = 48; + + // inner space compaction + public static final String INNER_COMPACTION_TMP_FILE_SUFFIX = ".inner"; + + // cross space compaction + public static final String CROSS_COMPACTION_TMP_FILE_SUFFIX = ".cross"; + + public static final String SETTLE_SUFFIX = ".settle"; + public static final String MODS_SETTLE_FILE_SUFFIX = ".mods.settle"; + public static final String BLANK = ""; + + // write ahead log + public static final String WAL_FILE_PREFIX = "_"; + public static final String WAL_FILE_SUFFIX = ".wal"; + public static final String WAL_CHECKPOINT_FILE_SUFFIX = ".checkpoint"; + public static final String WAL_VERSION_ID = "versionId"; + public static final String WAL_START_SEARCH_INDEX = "startSearchIndex"; + public static final String WAL_STATUS_CODE = "statusCode"; + + public static final String IOTDB_FOREGROUND = "iotdb-foreground"; + public static final String IOTDB_PIDFILE = "iotdb-pidfile"; + + // quota + public static final String SPACE_QUOTA_DISK = "disk"; + public static final String QUOTA_UNLIMITED = "unlimited"; + public static final String REQUEST_NUM_PER_UNIT_TIME = "request"; + public static final String REQUEST_SIZE_PER_UNIT_TIME = "size"; + public static final String MEMORY_SIZE_PER_READ = "mem"; + public static final String CPU_NUMBER_PER_READ = "cpu"; + public static final String REQUEST_TYPE = "type"; + public static final String REQUEST_TYPE_READ = "read"; + public static final String REQUEST_TYPE_WRITE = "write"; + public static final String REQ_UNIT = "req"; + public static final String REQ_SPLIT_UNIT = "req/"; + public static final int UNLIMITED_VALUE = -1; + public static final int DEFAULT_VALUE = 0; + public static final float B_FLOAT = 1024.0F; + + // SizeUnit + public static final String B_UNIT = "B"; + public static final String KB_UNIT = "K"; + public static final String MB_UNIT = "M"; + public static final String GB_UNIT = "G"; + public static final String TB_UNIT = "T"; + public static final String PB_UNIT = "P"; + + // Time + public static final int SEC = 1000; + public static final int MIN = 60 * 1000; + public static final int HOUR = 60 * 60 * 1000; + public static final int DAY = 24 * 60 * 60 * 1000; + + // TimeUnit + public static final String SEC_UNIT = "sec"; + public static final String MIN_UNIT = "min"; + public static final String HOUR_UNIT = "hour"; + public static final String DAY_UNIT = "day"; + + // client version number + public enum ClientVersion { + V_0_12, + V_0_13, + V_1_0 + } + + // select into + public static final Pattern LEVELED_PATH_TEMPLATE_PATTERN = Pattern.compile("\\$\\{\\w+}"); + public static final String DOUBLE_COLONS = "::"; + + public static final int MAX_DATABASE_NAME_LENGTH = 64; + + public static final String TIER_SEPARATOR = ";"; + + public static final String OBJECT_STORAGE_DIR = "object_storage"; + + public static final String TTL_INFINITE = "INF"; + + public static final String INTEGRATION_TEST_KILL_POINTS = "integrationTestKillPoints"; +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/PipeConnectorConstant.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/PipeConnectorConstant.java new file mode 100644 index 00000000..706a2ae2 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/PipeConnectorConstant.java @@ -0,0 +1,274 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.constant; + +import com.github.luben.zstd.Zstd; +import org.apache.iotdb.collector.config.PipeOptions; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import static io.moquette.BrokerConstants.MB; + +public class PipeConnectorConstant { + + public static final String CONNECTOR_KEY = "connector"; + public static final String SINK_KEY = "sink"; + + public static final String CONNECTOR_IOTDB_IP_KEY = "connector.ip"; + public static final String SINK_IOTDB_IP_KEY = "sink.ip"; + public static final String CONNECTOR_IOTDB_HOST_KEY = "connector.host"; + public static final String SINK_IOTDB_HOST_KEY = "sink.host"; + public static final String CONNECTOR_IOTDB_PORT_KEY = "connector.port"; + public static final String SINK_IOTDB_PORT_KEY = "sink.port"; + public static final String CONNECTOR_IOTDB_NODE_URLS_KEY = "connector.node-urls"; + public static final String SINK_IOTDB_NODE_URLS_KEY = "sink.node-urls"; + + public static final String SINK_IOTDB_SSL_ENABLE_KEY = "sink.ssl.enable"; + public static final String SINK_IOTDB_SSL_TRUST_STORE_PATH_KEY = "sink.ssl.trust-store-path"; + public static final String SINK_IOTDB_SSL_TRUST_STORE_PWD_KEY = "sink.ssl.trust-store-pwd"; + + public static final String CONNECTOR_IOTDB_PARALLEL_TASKS_KEY = "connector.parallel.tasks"; + public static final String SINK_IOTDB_PARALLEL_TASKS_KEY = "sink.parallel.tasks"; + public static final int CONNECTOR_IOTDB_PARALLEL_TASKS_DEFAULT_VALUE = + PipeOptions.PIPE_SUBTASK_EXECUTOR_MAX_THREAD_NUM.value(); + + public static final String CONNECTOR_REALTIME_FIRST_KEY = "connector.realtime-first"; + public static final String SINK_REALTIME_FIRST_KEY = "sink.realtime-first"; + public static final boolean CONNECTOR_REALTIME_FIRST_DEFAULT_VALUE = true; + + public static final String CONNECTOR_IOTDB_BATCH_MODE_ENABLE_KEY = "connector.batch.enable"; + public static final String SINK_IOTDB_BATCH_MODE_ENABLE_KEY = "sink.batch.enable"; + public static final boolean CONNECTOR_IOTDB_BATCH_MODE_ENABLE_DEFAULT_VALUE = true; + + public static final String CONNECTOR_IOTDB_BATCH_DELAY_KEY = "connector.batch.max-delay-seconds"; + public static final String SINK_IOTDB_BATCH_DELAY_KEY = "sink.batch.max-delay-seconds"; + public static final int CONNECTOR_IOTDB_PLAIN_BATCH_DELAY_DEFAULT_VALUE = 1; + public static final int CONNECTOR_IOTDB_TS_FILE_BATCH_DELAY_DEFAULT_VALUE = 5; + + public static final String CONNECTOR_IOTDB_BATCH_SIZE_KEY = "connector.batch.size-bytes"; + public static final String SINK_IOTDB_BATCH_SIZE_KEY = "sink.batch.size-bytes"; + public static final long CONNECTOR_IOTDB_PLAIN_BATCH_SIZE_DEFAULT_VALUE = 16 * MB; + public static final long CONNECTOR_IOTDB_TS_FILE_BATCH_SIZE_DEFAULT_VALUE = 80 * MB; + + public static final String CONNECTOR_IOTDB_USER_KEY = "connector.user"; + public static final String SINK_IOTDB_USER_KEY = "sink.user"; + public static final String CONNECTOR_IOTDB_USERNAME_KEY = "connector.username"; + public static final String SINK_IOTDB_USERNAME_KEY = "sink.username"; + public static final String CONNECTOR_IOTDB_USER_DEFAULT_VALUE = "root"; + + public static final String CONNECTOR_IOTDB_PASSWORD_KEY = "connector.password"; + public static final String SINK_IOTDB_PASSWORD_KEY = "sink.password"; + public static final String CONNECTOR_IOTDB_PASSWORD_DEFAULT_VALUE = "root"; + + public static final String CONNECTOR_EXCEPTION_DATA_CONVERT_ON_TYPE_MISMATCH_KEY = + "connector.exception.data.convert-on-type-mismatch"; + public static final String SINK_EXCEPTION_DATA_CONVERT_ON_TYPE_MISMATCH_KEY = + "sink.exception.data.convert-on-type-mismatch"; + public static final boolean CONNECTOR_EXCEPTION_DATA_CONVERT_ON_TYPE_MISMATCH_DEFAULT_VALUE = + true; + + public static final String CONNECTOR_EXCEPTION_CONFLICT_RESOLVE_STRATEGY_KEY = + "connector.exception.conflict.resolve-strategy"; + public static final String SINK_EXCEPTION_CONFLICT_RESOLVE_STRATEGY_KEY = + "sink.exception.conflict.resolve-strategy"; + public static final String CONNECTOR_EXCEPTION_CONFLICT_RESOLVE_STRATEGY_DEFAULT_VALUE = "retry"; + + public static final String CONNECTOR_EXCEPTION_CONFLICT_RETRY_MAX_TIME_SECONDS_KEY = + "connector.exception.conflict.retry-max-time-seconds"; + public static final String SINK_EXCEPTION_CONFLICT_RETRY_MAX_TIME_SECONDS_KEY = + "sink.exception.conflict.retry-max-time-seconds"; + public static final long CONNECTOR_EXCEPTION_CONFLICT_RETRY_MAX_TIME_SECONDS_DEFAULT_VALUE = 60; + + public static final String CONNECTOR_EXCEPTION_CONFLICT_RECORD_IGNORED_DATA_KEY = + "connector.exception.conflict.record-ignored-data"; + public static final String SINK_EXCEPTION_CONFLICT_RECORD_IGNORED_DATA_KEY = + "sink.exception.conflict.record-ignored-data"; + public static final boolean CONNECTOR_EXCEPTION_CONFLICT_RECORD_IGNORED_DATA_DEFAULT_VALUE = true; + + public static final String CONNECTOR_EXCEPTION_OTHERS_RETRY_MAX_TIME_SECONDS_KEY = + "connector.exception.others.retry-max-time-seconds"; + public static final String SINK_EXCEPTION_OTHERS_RETRY_MAX_TIME_SECONDS_KEY = + "sink.exception.others.retry-max-time-seconds"; + public static final long CONNECTOR_EXCEPTION_OTHERS_RETRY_MAX_TIME_SECONDS_DEFAULT_VALUE = -1; + + public static final String CONNECTOR_EXCEPTION_OTHERS_RECORD_IGNORED_DATA_KEY = + "connector.exception.others.record-ignored-data"; + public static final String SINK_EXCEPTION_OTHERS_RECORD_IGNORED_DATA_KEY = + "sink.exception.others.record-ignored-data"; + public static final boolean CONNECTOR_EXCEPTION_OTHERS_RECORD_IGNORED_DATA_DEFAULT_VALUE = true; + + public static final String CONNECTOR_AIR_GAP_E_LANGUAGE_ENABLE_KEY = + "connector.air-gap.e-language.enable"; + public static final String SINK_AIR_GAP_E_LANGUAGE_ENABLE_KEY = "sink.air-gap.e-language.enable"; + public static final boolean CONNECTOR_AIR_GAP_E_LANGUAGE_ENABLE_DEFAULT_VALUE = false; + + public static final String CONNECTOR_AIR_GAP_HANDSHAKE_TIMEOUT_MS_KEY = + "connector.air-gap.handshake-timeout-ms"; + public static final String SINK_AIR_GAP_HANDSHAKE_TIMEOUT_MS_KEY = + "sink.air-gap.handshake-timeout-ms"; + public static final int CONNECTOR_AIR_GAP_HANDSHAKE_TIMEOUT_MS_DEFAULT_VALUE = 5000; + + public static final String CONNECTOR_IOTDB_SYNC_CONNECTOR_VERSION_KEY = "connector.version"; + public static final String SINK_IOTDB_SYNC_CONNECTOR_VERSION_KEY = "sink.version"; + public static final String CONNECTOR_IOTDB_SYNC_CONNECTOR_VERSION_DEFAULT_VALUE = "1.1"; + + public static final String CONNECTOR_WEBSOCKET_PORT_KEY = "connector.websocket.port"; + public static final String SINK_WEBSOCKET_PORT_KEY = "sink.websocket.port"; + public static final int CONNECTOR_WEBSOCKET_PORT_DEFAULT_VALUE = 8080; + + public static final String CONNECTOR_OPC_UA_MODEL_KEY = "connector.opcua.model"; + public static final String SINK_OPC_UA_MODEL_KEY = "sink.opcua.model"; + public static final String CONNECTOR_OPC_UA_MODEL_CLIENT_SERVER_VALUE = "client-server"; + public static final String CONNECTOR_OPC_UA_MODEL_PUB_SUB_VALUE = "pub-sub"; + public static final String CONNECTOR_OPC_UA_MODEL_DEFAULT_VALUE = + CONNECTOR_OPC_UA_MODEL_CLIENT_SERVER_VALUE; + + public static final String CONNECTOR_OPC_UA_TCP_BIND_PORT_KEY = "connector.opcua.tcp.port"; + public static final String SINK_OPC_UA_TCP_BIND_PORT_KEY = "sink.opcua.tcp.port"; + public static final int CONNECTOR_OPC_UA_TCP_BIND_PORT_DEFAULT_VALUE = 12686; + + public static final String CONNECTOR_OPC_UA_HTTPS_BIND_PORT_KEY = "connector.opcua.https.port"; + public static final String SINK_OPC_UA_HTTPS_BIND_PORT_KEY = "sink.opcua.https.port"; + public static final int CONNECTOR_OPC_UA_HTTPS_BIND_PORT_DEFAULT_VALUE = 8443; + + public static final String CONNECTOR_OPC_UA_SECURITY_DIR_KEY = "connector.opcua.security.dir"; + public static final String SINK_OPC_UA_SECURITY_DIR_KEY = "sink.opcua.security.dir"; + public static final String CONNECTOR_OPC_UA_SECURITY_DIR_DEFAULT_VALUE = + getConfDir() != null + ? getConfDir() + File.separatorChar + "opc_security" + : System.getProperty("user.home") + File.separatorChar + "iotdb_opc_security"; + + public static final String CONNECTOR_OPC_UA_ENABLE_ANONYMOUS_ACCESS_KEY = + "connector.opcua.enable-anonymous-access"; + public static final String SINK_OPC_UA_ENABLE_ANONYMOUS_ACCESS_KEY = + "sink.opcua.enable-anonymous-access"; + public static final boolean CONNECTOR_OPC_UA_ENABLE_ANONYMOUS_ACCESS_DEFAULT_VALUE = true; + + public static final String CONNECTOR_OPC_UA_PLACEHOLDER_KEY = "connector.opcua.placeholder"; + public static final String SINK_OPC_UA_PLACEHOLDER_KEY = "sink.opcua.placeholder"; + public static final String CONNECTOR_OPC_UA_PLACEHOLDER_DEFAULT_VALUE = "null"; + + public static final String CONNECTOR_LEADER_CACHE_ENABLE_KEY = "connector.leader-cache.enable"; + public static final String SINK_LEADER_CACHE_ENABLE_KEY = "sink.leader-cache.enable"; + public static final boolean CONNECTOR_LEADER_CACHE_ENABLE_DEFAULT_VALUE = true; + + public static final String CONNECTOR_LOAD_BALANCE_STRATEGY_KEY = + "connector.load-balance-strategy"; + public static final String SINK_LOAD_BALANCE_STRATEGY_KEY = "sink.load-balance-strategy"; + public static final String CONNECTOR_LOAD_BALANCE_ROUND_ROBIN_STRATEGY = "round-robin"; + public static final String CONNECTOR_LOAD_BALANCE_RANDOM_STRATEGY = "random"; + public static final String CONNECTOR_LOAD_BALANCE_PRIORITY_STRATEGY = "priority"; + public static final Set CONNECTOR_LOAD_BALANCE_STRATEGY_SET = + Collections.unmodifiableSet( + new HashSet<>( + Arrays.asList( + CONNECTOR_LOAD_BALANCE_ROUND_ROBIN_STRATEGY, + CONNECTOR_LOAD_BALANCE_RANDOM_STRATEGY, + CONNECTOR_LOAD_BALANCE_PRIORITY_STRATEGY))); + + public static final String CONNECTOR_COMPRESSOR_KEY = "connector.compressor"; + public static final String SINK_COMPRESSOR_KEY = "sink.compressor"; + public static final String CONNECTOR_COMPRESSOR_DEFAULT_VALUE = ""; + public static final String CONNECTOR_COMPRESSOR_SNAPPY = "snappy"; + public static final String CONNECTOR_COMPRESSOR_GZIP = "gzip"; + public static final String CONNECTOR_COMPRESSOR_LZ4 = "lz4"; + public static final String CONNECTOR_COMPRESSOR_ZSTD = "zstd"; + public static final String CONNECTOR_COMPRESSOR_LZMA2 = "lzma2"; + public static final Set CONNECTOR_COMPRESSOR_SET = + Collections.unmodifiableSet( + new HashSet<>( + Arrays.asList( + CONNECTOR_COMPRESSOR_SNAPPY, + CONNECTOR_COMPRESSOR_GZIP, + CONNECTOR_COMPRESSOR_LZ4, + CONNECTOR_COMPRESSOR_ZSTD, + CONNECTOR_COMPRESSOR_LZMA2))); + + public static final String CONNECTOR_COMPRESSOR_ZSTD_LEVEL_KEY = + "connector.compressor.zstd.level"; + public static final String SINK_COMPRESSOR_ZSTD_LEVEL_KEY = "sink.compressor.zstd.level"; + public static final int CONNECTOR_COMPRESSOR_ZSTD_LEVEL_DEFAULT_VALUE = + Zstd.defaultCompressionLevel(); + public static final int CONNECTOR_COMPRESSOR_ZSTD_LEVEL_MIN_VALUE = Zstd.minCompressionLevel(); + public static final int CONNECTOR_COMPRESSOR_ZSTD_LEVEL_MAX_VALUE = Zstd.maxCompressionLevel(); + + public static final String CONNECTOR_RATE_LIMIT_KEY = "connector.rate-limit-bytes-per-second"; + public static final String SINK_RATE_LIMIT_KEY = "sink.rate-limit-bytes-per-second"; + public static final double CONNECTOR_RATE_LIMIT_DEFAULT_VALUE = -1; + + public static final String CONNECTOR_FORMAT_KEY = "connector.format"; + public static final String SINK_FORMAT_KEY = "sink.format"; + public static final String CONNECTOR_FORMAT_TABLET_VALUE = "tablet"; + public static final String CONNECTOR_FORMAT_TS_FILE_VALUE = "tsfile"; + public static final String CONNECTOR_FORMAT_HYBRID_VALUE = "hybrid"; + + public static final String SINK_TOPIC_KEY = "sink.topic"; + public static final String SINK_CONSUMER_GROUP_KEY = "sink.consumer-group"; + + public static final String CONNECTOR_CONSENSUS_GROUP_ID_KEY = "connector.consensus.group-id"; + public static final String CONNECTOR_CONSENSUS_PIPE_NAME = "connector.consensus.pipe-name"; + + public static final String CONNECTOR_LOAD_TSFILE_STRATEGY_KEY = "connector.load-tsfile-strategy"; + public static final String SINK_LOAD_TSFILE_STRATEGY_KEY = "sink.load-tsfile-strategy"; + public static final String CONNECTOR_LOAD_TSFILE_STRATEGY_ASYNC_VALUE = "async"; + public static final String CONNECTOR_LOAD_TSFILE_STRATEGY_SYNC_VALUE = "sync"; + public static final Set CONNECTOR_LOAD_TSFILE_STRATEGY_SET = + Collections.unmodifiableSet( + new HashSet<>( + Arrays.asList( + CONNECTOR_LOAD_TSFILE_STRATEGY_ASYNC_VALUE, + CONNECTOR_LOAD_TSFILE_STRATEGY_SYNC_VALUE))); + + public static final String CONNECTOR_LOAD_TSFILE_VALIDATION_KEY = + "connector.load-tsfile-validation"; + public static final String SINK_LOAD_TSFILE_VALIDATION_KEY = "sink.load-tsfile-validation"; + public static final boolean CONNECTOR_LOAD_TSFILE_VALIDATION_DEFAULT_VALUE = true; + + public static final String CONNECTOR_MARK_AS_PIPE_REQUEST_KEY = "connector.mark-as-pipe-request"; + public static final String SINK_MARK_AS_PIPE_REQUEST_KEY = "sink.mark-as-pipe-request"; + public static final boolean CONNECTOR_MARK_AS_PIPE_REQUEST_DEFAULT_VALUE = true; + + public static final String CONNECTOR_OPC_DA_CLSID_KEY = "connector.opcda.clsid"; + public static final String SINK_OPC_DA_CLSID_KEY = "sink.opcda.clsid"; + + public static final String CONNECTOR_OPC_DA_PROGID_KEY = "connector.opcda.progid"; + public static final String SINK_OPC_DA_PROGID_KEY = "sink.opcda.progid"; + + public static String getConfDir() { + // Check if a config-directory was specified first. + String confString = System.getProperty(IoTDBConstant.IOTDB_CONF, null); + // If it wasn't, check if a home directory was provided (This usually contains a config) + if (confString == null) { + confString = System.getProperty(IoTDBConstant.IOTDB_HOME, null); + if (confString != null) { + confString = confString + File.separatorChar + "conf"; + } + } + return confString; + } + + private PipeConnectorConstant() { + throw new IllegalStateException("Utility class"); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/PipeExtractorConstant.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/PipeExtractorConstant.java new file mode 100644 index 00000000..10b0e4ca --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/PipeExtractorConstant.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.constant; + +public class PipeExtractorConstant { + + public static final String EXTRACTOR_KEY = "extractor"; + public static final String SOURCE_KEY = "source"; + + public static final String EXTRACTOR_CAPTURE_TREE_KEY = "extractor.capture.tree"; + public static final String SOURCE_CAPTURE_TREE_KEY = "source.capture.tree"; + public static final String EXTRACTOR_CAPTURE_TABLE_KEY = "extractor.capture.table"; + public static final String SOURCE_CAPTURE_TABLE_KEY = "source.capture.table"; + + public static final String EXTRACTOR_INCLUSION_KEY = "extractor.inclusion"; + public static final String SOURCE_INCLUSION_KEY = "source.inclusion"; + public static final String EXTRACTOR_INCLUSION_DEFAULT_VALUE = "data.insert"; + + public static final String EXTRACTOR_EXCLUSION_KEY = "extractor.inclusion.exclusion"; + public static final String SOURCE_EXCLUSION_KEY = "source.inclusion.exclusion"; + public static final String EXTRACTOR_EXCLUSION_DEFAULT_VALUE = ""; + + public static final String EXTRACTOR_MODE_KEY = "extractor.mode"; + public static final String SOURCE_MODE_KEY = "source.mode"; + public static final String EXTRACTOR_MODE_QUERY_VALUE = "query"; + public static final String EXTRACTOR_MODE_SNAPSHOT_VALUE = "snapshot"; + public static final String EXTRACTOR_MODE_SUBSCRIBE_VALUE = "subscribe"; + public static final String EXTRACTOR_MODE_LIVE_VALUE = "live"; + public static final String EXTRACTOR_MODE_DEFAULT_VALUE = EXTRACTOR_MODE_LIVE_VALUE; + + public static final String EXTRACTOR_PATTERN_KEY = "extractor.pattern"; + public static final String SOURCE_PATTERN_KEY = "source.pattern"; + public static final String EXTRACTOR_PATH_KEY = "extractor.path"; + public static final String SOURCE_PATH_KEY = "source.path"; + public static final String EXTRACTOR_PATTERN_FORMAT_KEY = "extractor.pattern.format"; + public static final String SOURCE_PATTERN_FORMAT_KEY = "source.pattern.format"; + public static final String EXTRACTOR_PATTERN_FORMAT_PREFIX_VALUE = "prefix"; + public static final String EXTRACTOR_PATTERN_FORMAT_IOTDB_VALUE = "iotdb"; + public static final String EXTRACTOR_PATTERN_PREFIX_DEFAULT_VALUE = "root"; + public static final String EXTRACTOR_PATTERN_IOTDB_DEFAULT_VALUE = "root.**"; + public static final String EXTRACTOR_DATABASE_NAME_KEY = "extractor.database-name"; + public static final String SOURCE_DATABASE_NAME_KEY = "source.database-name"; + public static final String EXTRACTOR_TABLE_NAME_KEY = "extractor.table-name"; + public static final String SOURCE_TABLE_NAME_KEY = "source.table-name"; + public static final String EXTRACTOR_DATABASE_NAME_DEFAULT_VALUE = ".*"; + public static final String EXTRACTOR_TABLE_NAME_DEFAULT_VALUE = ".*"; + public static final String EXTRACTOR_DATABASE_KEY = "extractor.database"; + public static final String SOURCE_DATABASE_KEY = "source.database"; + public static final String EXTRACTOR_TABLE_KEY = "extractor.table"; + public static final String SOURCE_TABLE_KEY = "source.table"; + + public static final String EXTRACTOR_FORWARDING_PIPE_REQUESTS_KEY = + "extractor.forwarding-pipe-requests"; + public static final String SOURCE_FORWARDING_PIPE_REQUESTS_KEY = + "source.forwarding-pipe-requests"; + public static final boolean EXTRACTOR_FORWARDING_PIPE_REQUESTS_DEFAULT_VALUE = true; + + public static final String EXTRACTOR_HISTORY_ENABLE_KEY = "extractor.history.enable"; + public static final String SOURCE_HISTORY_ENABLE_KEY = "source.history.enable"; + public static final boolean EXTRACTOR_HISTORY_ENABLE_DEFAULT_VALUE = true; + public static final String EXTRACTOR_HISTORY_START_TIME_KEY = "extractor.history.start-time"; + public static final String SOURCE_HISTORY_START_TIME_KEY = "source.history.start-time"; + public static final String EXTRACTOR_HISTORY_END_TIME_KEY = "extractor.history.end-time"; + public static final String SOURCE_HISTORY_END_TIME_KEY = "source.history.end-time"; + public static final String EXTRACTOR_HISTORY_LOOSE_RANGE_KEY = "extractor.history.loose-range"; + public static final String SOURCE_HISTORY_LOOSE_RANGE_KEY = "source.history.loose-range"; + public static final String EXTRACTOR_HISTORY_LOOSE_RANGE_TIME_VALUE = "time"; + public static final String EXTRACTOR_HISTORY_LOOSE_RANGE_PATH_VALUE = "path"; + public static final String EXTRACTOR_HISTORY_LOOSE_RANGE_ALL_VALUE = "all"; + public static final String EXTRACTOR_HISTORY_LOOSE_RANGE_DEFAULT_VALUE = ""; + public static final String EXTRACTOR_MODS_ENABLE_KEY = "extractor.mods.enable"; + public static final String SOURCE_MODS_ENABLE_KEY = "source.mods.enable"; + public static final boolean EXTRACTOR_MODS_ENABLE_DEFAULT_VALUE = false; + public static final String EXTRACTOR_MODS_KEY = "extractor.mods"; + public static final String SOURCE_MODS_KEY = "source.mods"; + public static final boolean EXTRACTOR_MODS_DEFAULT_VALUE = EXTRACTOR_MODS_ENABLE_DEFAULT_VALUE; + + public static final String EXTRACTOR_REALTIME_ENABLE_KEY = "extractor.realtime.enable"; + public static final String SOURCE_REALTIME_ENABLE_KEY = "source.realtime.enable"; + public static final boolean EXTRACTOR_REALTIME_ENABLE_DEFAULT_VALUE = true; + public static final String EXTRACTOR_REALTIME_MODE_KEY = "extractor.realtime.mode"; + public static final String SOURCE_REALTIME_MODE_KEY = "source.realtime.mode"; + public static final String EXTRACTOR_REALTIME_MODE_HYBRID_VALUE = "hybrid"; + public static final String EXTRACTOR_REALTIME_MODE_FILE_VALUE = "file"; + public static final String EXTRACTOR_REALTIME_MODE_LOG_VALUE = "log"; + public static final String EXTRACTOR_REALTIME_MODE_FORCED_LOG_VALUE = "forced-log"; + public static final String EXTRACTOR_REALTIME_MODE_STREAM_MODE_VALUE = "stream"; + public static final String EXTRACTOR_REALTIME_MODE_BATCH_MODE_VALUE = "batch"; + public static final String EXTRACTOR_REALTIME_LOOSE_RANGE_KEY = "extractor.realtime.loose-range"; + public static final String SOURCE_REALTIME_LOOSE_RANGE_KEY = "source.realtime.loose-range"; + public static final String EXTRACTOR_REALTIME_LOOSE_RANGE_TIME_VALUE = "time"; + public static final String EXTRACTOR_REALTIME_LOOSE_RANGE_PATH_VALUE = "path"; + public static final String EXTRACTOR_REALTIME_LOOSE_RANGE_ALL_VALUE = "all"; + public static final String EXTRACTOR_REALTIME_LOOSE_RANGE_DEFAULT_VALUE = ""; + + public static final String EXTRACTOR_MODE_STREAMING_KEY = "extractor.mode.streaming"; + public static final String SOURCE_MODE_STREAMING_KEY = "source.mode.streaming"; + public static final boolean EXTRACTOR_MODE_STREAMING_DEFAULT_VALUE = true; + public static final String EXTRACTOR_MODE_STRICT_KEY = "extractor.mode.strict"; + public static final String SOURCE_MODE_STRICT_KEY = "source.mode.strict"; + public static final boolean EXTRACTOR_MODE_STRICT_DEFAULT_VALUE = true; + public static final String EXTRACTOR_MODE_SNAPSHOT_KEY = "extractor.mode.snapshot"; + public static final String SOURCE_MODE_SNAPSHOT_KEY = "source.mode.snapshot"; + public static final boolean EXTRACTOR_MODE_SNAPSHOT_DEFAULT_VALUE = false; + public static final String EXTRACTOR_MODE_DOUBLE_LIVING_KEY = "extractor.mode.double-living"; + public static final String SOURCE_MODE_DOUBLE_LIVING_KEY = "source.mode.double-living"; + public static final boolean EXTRACTOR_MODE_DOUBLE_LIVING_DEFAULT_VALUE = false; + + public static final String EXTRACTOR_START_TIME_KEY = "extractor.start-time"; + public static final String SOURCE_START_TIME_KEY = "source.start-time"; + public static final String EXTRACTOR_END_TIME_KEY = "extractor.end-time"; + public static final String SOURCE_END_TIME_KEY = "source.end-time"; + public static final String NOW_TIME_VALUE = "now"; + + public static final String _EXTRACTOR_WATERMARK_INTERVAL_KEY = "extractor.watermark-interval-ms"; + public static final String _SOURCE_WATERMARK_INTERVAL_KEY = "source.watermark-interval-ms"; + public static final long EXTRACTOR_WATERMARK_INTERVAL_DEFAULT_VALUE = -1; // -1 means no watermark + public static final String EXTRACTOR_WATERMARK_INTERVAL_KEY = "extractor.watermark.interval-ms"; + public static final String SOURCE_WATERMARK_INTERVAL_KEY = "source.watermark.interval-ms"; + + ///////////////////// pipe consensus ///////////////////// + + public static final String EXTRACTOR_CONSENSUS_GROUP_ID_KEY = "extractor.consensus.group-id"; + public static final String EXTRACTOR_CONSENSUS_SENDER_DATANODE_ID_KEY = + "extractor.consensus.sender-dn-id"; + public static final String EXTRACTOR_CONSENSUS_RECEIVER_DATANODE_ID_KEY = + "extractor.consensus.receiver-dn-id"; + + private PipeExtractorConstant() { + throw new IllegalStateException("Utility class"); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/PipeTransferHandshakeConstant.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/PipeTransferHandshakeConstant.java new file mode 100644 index 00000000..1adcfabb --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/PipeTransferHandshakeConstant.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.constant; + +public class PipeTransferHandshakeConstant { + + public static final String HANDSHAKE_KEY_TIME_PRECISION = "timestampPrecision"; + public static final String HANDSHAKE_KEY_CLUSTER_ID = "clusterID"; + public static final String HANDSHAKE_KEY_CONVERT_ON_TYPE_MISMATCH = "convertOnTypeMismatch"; + public static final String HANDSHAKE_KEY_LOAD_TSFILE_STRATEGY = "loadTsFileStrategy"; + public static final String HANDSHAKE_KEY_USERNAME = "username"; + public static final String HANDSHAKE_KEY_PASSWORD = "password"; + public static final String HANDSHAKE_KEY_VALIDATE_TSFILE = "validateTsFile"; + public static final String HANDSHAKE_KEY_MARK_AS_PIPE_REQUEST = "markAsPipeRequest"; + + private PipeTransferHandshakeConstant() { + // Utility class + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/SystemConstant.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/SystemConstant.java new file mode 100644 index 00000000..c196336c --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/constant/SystemConstant.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.constant; + +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class SystemConstant { + + public static final String SYSTEM_PREFIX_KEY = "__system"; + + public static final String RESTART_KEY = "__system.restart"; + public static final boolean RESTART_DEFAULT_VALUE = false; + + public static final String SQL_DIALECT_KEY = "__system.sql-dialect"; + public static final String SQL_DIALECT_TREE_VALUE = "tree"; + public static final String SQL_DIALECT_TABLE_VALUE = "table"; + + /////////////////////////////////// Utility /////////////////////////////////// + + public static final Set SYSTEM_KEYS = new HashSet<>(); + + static { + SYSTEM_KEYS.add(RESTART_KEY); + SYSTEM_KEYS.add(SQL_DIALECT_KEY); + } + + public static PipeParameters addSystemKeysIfNecessary(final PipeParameters givenPipeParameters) { + final Map attributes = new HashMap<>(givenPipeParameters.getAttribute()); + attributes.putIfAbsent(SQL_DIALECT_KEY, SQL_DIALECT_TREE_VALUE); + return new PipeParameters(attributes); + } + + /////////////////////////////////// Private Constructor /////////////////////////////////// + + private SystemConstant() { + throw new IllegalStateException("Utility class"); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeBinaryTransformer.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeBinaryTransformer.java new file mode 100644 index 00000000..06c3fdcf --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeBinaryTransformer.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.event.row; + +public class PipeBinaryTransformer { + + public static org.apache.tsfile.utils.Binary transformToBinary( + org.apache.iotdb.pipe.api.type.Binary binary) { + return binary == null ? null : new org.apache.tsfile.utils.Binary(binary.getValues()); + } + + public static org.apache.iotdb.pipe.api.type.Binary transformToPipeBinary( + org.apache.tsfile.utils.Binary binary) { + return binary == null ? null : new org.apache.iotdb.pipe.api.type.Binary(binary.getValues()); + } + + private PipeBinaryTransformer() { + // util class + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeDataTypeTransformer.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeDataTypeTransformer.java new file mode 100644 index 00000000..a4ee4813 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeDataTypeTransformer.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.event.row; + +import org.apache.iotdb.pipe.api.type.Type; +import org.apache.tsfile.enums.TSDataType; + +import java.util.List; +import java.util.stream.Collectors; + +/** Transform between {@link TSDataType} and {@link Type}. */ +public class PipeDataTypeTransformer { + + public static List transformToPipeDataTypeList(final List tsDataTypeList) { + return tsDataTypeList == null + ? null + : tsDataTypeList.stream() + .map(PipeDataTypeTransformer::transformToPipeDataType) + .collect(Collectors.toList()); + } + + public static Type transformToPipeDataType(final TSDataType tsDataType) { + return tsDataType == null ? null : getPipeDataType(tsDataType.getType()); + } + + private static Type getPipeDataType(final byte type) { + switch (type) { + case 0: + return Type.BOOLEAN; + case 1: + return Type.INT32; + case 2: + return Type.INT64; + case 3: + return Type.FLOAT; + case 4: + return Type.DOUBLE; + case 5: + return Type.TEXT; + case 8: + return Type.TIMESTAMP; + case 9: + return Type.DATE; + case 10: + return Type.BLOB; + case 11: + return Type.STRING; + default: + throw new IllegalArgumentException("Invalid input: " + type); + } + } + + private PipeDataTypeTransformer() { + // util class + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeResetTabletRow.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeResetTabletRow.java new file mode 100644 index 00000000..a30aca16 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeResetTabletRow.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.event.row; + +import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.utils.BitMap; +import org.apache.tsfile.write.record.Tablet; +import org.apache.tsfile.write.schema.MeasurementSchema; + +/** + * The pipe framework will reset a new {@link Tablet} when this kind of {@link PipeRow} is + * encountered. + */ +public class PipeResetTabletRow extends PipeRow { + + public PipeResetTabletRow( + int rowIndex, + String deviceId, + boolean isAligned, + MeasurementSchema[] measurementSchemaList, + long[] timestampColumn, + TSDataType[] valueColumnTypes, + Object[] valueColumns, + BitMap[] bitMaps, + String[] columnNameStringList) { + super( + rowIndex, + deviceId, + isAligned, + measurementSchemaList, + timestampColumn, + valueColumnTypes, + valueColumns, + bitMaps, + columnNameStringList); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeRow.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeRow.java new file mode 100644 index 00000000..84d074e7 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeRow.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.event.row; + +import org.apache.iotdb.pipe.api.access.Row; +import org.apache.iotdb.pipe.api.exception.PipeParameterNotValidException; +import org.apache.iotdb.pipe.api.type.Binary; +import org.apache.iotdb.pipe.api.type.Type; +import org.apache.tsfile.common.conf.TSFileConfig; +import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.read.common.Path; +import org.apache.tsfile.utils.BitMap; +import org.apache.tsfile.write.schema.IMeasurementSchema; + +import java.time.LocalDate; +import java.util.Arrays; +import java.util.List; + +public class PipeRow implements Row { + + protected final int rowIndex; + + protected final String deviceId; + protected final boolean isAligned; + protected final IMeasurementSchema[] measurementSchemaList; + + protected final long[] timestampColumn; + protected final TSDataType[] valueColumnTypes; + protected final Object[] valueColumns; + protected final BitMap[] bitMaps; + + protected final String[] columnNameStringList; + + public PipeRow( + final int rowIndex, + final String deviceId, + final boolean isAligned, + final IMeasurementSchema[] measurementSchemaList, + final long[] timestampColumn, + final TSDataType[] valueColumnTypes, + final Object[] valueColumns, + final BitMap[] bitMaps, + final String[] columnNameStringList) { + this.rowIndex = rowIndex; + this.deviceId = deviceId; + this.isAligned = isAligned; + this.measurementSchemaList = measurementSchemaList; + this.timestampColumn = timestampColumn; + this.valueColumnTypes = valueColumnTypes; + this.valueColumns = valueColumns; + this.bitMaps = bitMaps; + this.columnNameStringList = columnNameStringList; + } + + @Override + public long getTime() { + return timestampColumn[rowIndex]; + } + + @Override + public int getInt(final int columnIndex) { + return ((int[]) valueColumns[columnIndex])[rowIndex]; + } + + @Override + public LocalDate getDate(final int columnIndex) { + return ((LocalDate[]) valueColumns[columnIndex])[rowIndex]; + } + + @Override + public long getLong(final int columnIndex) { + return ((long[]) valueColumns[columnIndex])[rowIndex]; + } + + @Override + public float getFloat(final int columnIndex) { + return ((float[]) valueColumns[columnIndex])[rowIndex]; + } + + @Override + public double getDouble(final int columnIndex) { + return ((double[]) valueColumns[columnIndex])[rowIndex]; + } + + @Override + public boolean getBoolean(final int columnIndex) { + return ((boolean[]) valueColumns[columnIndex])[rowIndex]; + } + + @Override + public Binary getBinary(final int columnIndex) { + return PipeBinaryTransformer.transformToPipeBinary( + ((org.apache.tsfile.utils.Binary[]) valueColumns[columnIndex])[rowIndex]); + } + + @Override + public String getString(final int columnIndex) { + final org.apache.tsfile.utils.Binary binary = + ((org.apache.tsfile.utils.Binary[]) valueColumns[columnIndex])[rowIndex]; + return binary == null ? null : binary.getStringValue(TSFileConfig.STRING_CHARSET); + } + + @Override + public Object getObject(final int columnIndex) { + switch (getDataType(columnIndex)) { + case INT32: + return getInt(columnIndex); + case DATE: + return getDate(columnIndex); + case INT64: + case TIMESTAMP: + return getLong(columnIndex); + case FLOAT: + return getFloat(columnIndex); + case DOUBLE: + return getDouble(columnIndex); + case BOOLEAN: + return getBoolean(columnIndex); + case TEXT: + case BLOB: + case STRING: + return getBinary(columnIndex); + default: + throw new UnsupportedOperationException( + String.format( + "unsupported data type %s for column %s", + getDataType(columnIndex), columnNameStringList[columnIndex])); + } + } + + @Override + public Type getDataType(final int columnIndex) { + return PipeDataTypeTransformer.transformToPipeDataType(valueColumnTypes[columnIndex]); + } + + @Override + public boolean isNull(final int columnIndex) { + return bitMaps[columnIndex].isMarked(rowIndex); + } + + @Override + public int size() { + return valueColumns.length; + } + + @Override + public int getColumnIndex(final Path columnName) throws PipeParameterNotValidException { + for (int i = 0; i < columnNameStringList.length; i++) { + if (columnNameStringList[i].equals(columnName.getFullPath())) { + return i; + } + } + throw new PipeParameterNotValidException( + String.format("column %s not found", columnName.getFullPath())); + } + + @Override + public String getColumnName(final int columnIndex) { + return columnNameStringList[columnIndex]; + } + + @Override + public List getColumnTypes() { + return PipeDataTypeTransformer.transformToPipeDataTypeList(Arrays.asList(valueColumnTypes)); + } + + @Override + public String getDeviceId() { + return deviceId; + } + + public boolean isAligned() { + return isAligned; + } + + public int getCurrentRowSize() { + int rowSize = 0; + rowSize += 8; // timestamp + for (int i = 0; i < valueColumnTypes.length; i++) { + if (valueColumnTypes[i] != null) { + if (valueColumnTypes[i].isBinary()) { + rowSize += getBinary(i) != null ? getBinary(i).getLength() : 0; + } else { + rowSize += valueColumnTypes[i].getDataTypeSize(); + } + } + } + return rowSize; + } + + public IMeasurementSchema[] getMeasurementSchemaList() { + return measurementSchemaList; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeRowCollector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeRowCollector.java new file mode 100644 index 00000000..38d8258f --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/row/PipeRowCollector.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.event.row; + +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.PipeMemoryWeightUtil; +import org.apache.iotdb.pipe.api.access.Row; +import org.apache.iotdb.pipe.api.collector.RowCollector; +import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent; +import org.apache.iotdb.pipe.api.exception.PipeException; +import org.apache.iotdb.pipe.api.type.Binary; +import org.apache.tsfile.utils.Pair; +import org.apache.tsfile.write.record.Tablet; +import org.apache.tsfile.write.schema.IMeasurementSchema; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class PipeRowCollector implements RowCollector { + + private final List tabletInsertionEventList = new ArrayList<>(); + private Tablet tablet = null; + private boolean isAligned = false; + private final PipeRawTabletInsertionEvent sourceEvent; // Used to report progress + private final String sourceEventDataBaseName; + private final Boolean isTableModel; + + public PipeRowCollector(PipeRawTabletInsertionEvent sourceEvent) { + this.sourceEvent = sourceEvent; + if (sourceEvent instanceof PipeInsertionEvent) { + sourceEventDataBaseName = + ((PipeInsertionEvent) sourceEvent).getSourceDatabaseNameFromDataRegion(); + isTableModel = ((PipeInsertionEvent) sourceEvent).getRawIsTableModelEvent(); + } else { + sourceEventDataBaseName = null; + isTableModel = null; + } + } + + public PipeRowCollector( + PipeRawTabletInsertionEvent sourceEvent, + String sourceEventDataBase, + Boolean isTableModel) { + this.sourceEvent = sourceEvent; + this.sourceEventDataBaseName = sourceEventDataBase; + this.isTableModel = isTableModel; + } + + @Override + public void collectRow(Row row) { + if (!(row instanceof PipeRow)) { + throw new PipeException("Row can not be customized"); + } + + final PipeRow pipeRow = (PipeRow) row; + final IMeasurementSchema[] measurementSchemaArray = pipeRow.getMeasurementSchemaList(); + + // Trigger collection when a PipeResetTabletRow is encountered + if (row instanceof PipeResetTabletRow) { + collectTabletInsertionEvent(); + } + + if (tablet == null) { + final String deviceId = pipeRow.getDeviceId(); + final List measurementSchemaList = + new ArrayList<>(Arrays.asList(measurementSchemaArray)); + // Calculate row count and memory size of the tablet based on the first row + Pair rowCountAndMemorySize = + PipeMemoryWeightUtil.calculateTabletRowCountAndMemory(pipeRow); + tablet = new Tablet(deviceId, measurementSchemaList, rowCountAndMemorySize.getLeft()); + tablet.initBitMaps(); + isAligned = pipeRow.isAligned(); + } + + final int rowIndex = tablet.getRowSize(); + tablet.addTimestamp(rowIndex, row.getTime()); + for (int i = 0; i < row.size(); i++) { + final Object value = row.getObject(i); + if (value instanceof Binary) { + tablet.addValue( + measurementSchemaArray[i].getMeasurementName(), + rowIndex, + PipeBinaryTransformer.transformToBinary((Binary) value)); + } else { + tablet.addValue(measurementSchemaArray[i].getMeasurementName(), rowIndex, value); + } + if (row.isNull(i)) { + tablet.getBitMaps()[i].mark(rowIndex); + } + } + + if (tablet.getRowSize() == tablet.getMaxRowNumber()) { + collectTabletInsertionEvent(); + } + } + + private void collectTabletInsertionEvent() { + if (tablet != null) { + // TODO: non-PipeInsertionEvent sourceEvent is not supported? + final PipeInsertionEvent pipeInsertionEvent = + sourceEvent instanceof PipeInsertionEvent ? ((PipeInsertionEvent) sourceEvent) : null; + tabletInsertionEventList.add( + new PipeRawTabletInsertionEvent( + isTableModel, + sourceEventDataBaseName, + pipeInsertionEvent == null ? null : pipeInsertionEvent.getRawTableModelDataBase(), + pipeInsertionEvent == null ? null : pipeInsertionEvent.getRawTreeModelDataBase(), + tablet, + isAligned, + sourceEvent == null ? null : sourceEvent.getPipeName(), + sourceEvent == null ? 0 : sourceEvent.getCreationTime(), + sourceEvent, + false)); + } + this.tablet = null; + } + + public List convertToTabletInsertionEvents(final boolean shouldReport) { + collectTabletInsertionEvent(); + + final int eventListSize = tabletInsertionEventList.size(); + if (eventListSize > 0 && shouldReport) { // The last event should report progress + ((PipeRawTabletInsertionEvent) tabletInsertionEventList.get(eventListSize - 1)) + .markAsNeedToReport(); + } + return tabletInsertionEventList; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/tablet/PipeRawTabletInsertionEvent.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/tablet/PipeRawTabletInsertionEvent.java new file mode 100644 index 00000000..f249143b --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/tablet/PipeRawTabletInsertionEvent.java @@ -0,0 +1,334 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.event.tablet; + +import org.apache.iotdb.collector.plugin.builtin.sink.datastructure.pattern.TablePattern; +import org.apache.iotdb.collector.plugin.builtin.sink.datastructure.pattern.TreePattern; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.parser.TabletInsertionEventParser; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.parser.TabletInsertionEventTablePatternParser; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.parser.TabletInsertionEventTreePatternParser; +import org.apache.iotdb.collector.plugin.builtin.sink.resource.ref.PipePhantomReferenceManager; +import org.apache.iotdb.pipe.api.access.Row; +import org.apache.iotdb.pipe.api.collector.RowCollector; +import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent; +import org.apache.tsfile.utils.RamUsageEstimator; +import org.apache.tsfile.write.record.Tablet; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; + +public class PipeRawTabletInsertionEvent extends PipeInsertionEvent + implements TabletInsertionEvent, ReferenceTrackableEvent, AutoCloseable { + + // For better calculation + private static final long INSTANCE_SIZE = + RamUsageEstimator.shallowSizeOfInstance(PipeRawTabletInsertionEvent.class); + private Tablet tablet; + private String deviceId; // Only used when the tablet is released. + private final boolean isAligned; + + private final PipeRawTabletInsertionEvent sourceEvent; + private boolean needToReport; + + // private final PipeTabletMemoryBlock allocatedMemoryBlock; + + private TabletInsertionEventParser eventParser; + + private PipeRawTabletInsertionEvent( + final Boolean isTableModelEvent, + final String databaseName, + final String tableModelDataBaseName, + final String treeModelDataBaseName, + final Tablet tablet, + final boolean isAligned, + final PipeRawTabletInsertionEvent sourceEvent, + final boolean needToReport, + final String pipeName, + final long creationTime, + final TreePattern treePattern, + final TablePattern tablePattern, + final long startTime, + final long endTime) { + super( + pipeName, + creationTime, + treePattern, + tablePattern, + startTime, + endTime, + isTableModelEvent, + databaseName, + tableModelDataBaseName, + treeModelDataBaseName); + this.tablet = Objects.requireNonNull(tablet); + this.isAligned = isAligned; + this.sourceEvent = sourceEvent; + this.needToReport = needToReport; + + // Allocate empty memory block, will be resized later. + // this.allocatedMemoryBlock = + // PipeDataNodeResourceManager.memory().forceAllocateForTabletWithRetry(0); + } + + public PipeRawTabletInsertionEvent( + final Boolean isTableModelEvent, + final String databaseName, + final String tableModelDataBaseName, + final String treeModelDataBaseName, + final Tablet tablet, + final boolean isAligned, + final String pipeName, + final long creationTime, + final PipeRawTabletInsertionEvent sourceEvent, + final boolean needToReport) { + this( + isTableModelEvent, + databaseName, + tableModelDataBaseName, + treeModelDataBaseName, + tablet, + isAligned, + sourceEvent, + needToReport, + pipeName, + creationTime, + null, + null, + Long.MIN_VALUE, + Long.MAX_VALUE); + } + + @Override + public boolean internallyIncreaseResourceReferenceCount(final String holderMessage) { + // PipeDataNodeResourceManager.memory() + // .forceResize( + // allocatedMemoryBlock, + // PipeMemoryWeightUtil.calculateTabletSizeInBytes(tablet) + INSTANCE_SIZE); + return true; + } + + @Override + public boolean internallyDecreaseResourceReferenceCount(final String holderMessage) { + // allocatedMemoryBlock.close(); + + // Record the deviceId before the memory is released, + // for later possibly updating the leader cache. + deviceId = tablet.getDeviceId(); + + // Actually release the occupied memory. + tablet = null; + eventParser = null; + return true; + } + + @Override + public PipeRawTabletInsertionEvent shallowCopySelfAndBindPipeTaskMetaForProgressReport( + final String pipeName, + final long creationTime, + final TreePattern treePattern, + final TablePattern tablePattern, + final long startTime, + final long endTime) { + return new PipeRawTabletInsertionEvent( + getRawIsTableModelEvent(), + getSourceDatabaseNameFromDataRegion(), + getRawTableModelDataBase(), + getRawTreeModelDataBase(), + tablet, + isAligned, + sourceEvent, + needToReport, + pipeName, + creationTime, + treePattern, + tablePattern, + startTime, + endTime); + } + + @Override + public boolean isGeneratedByPipe() { + throw new UnsupportedOperationException("isGeneratedByPipe() is not supported!"); + } + + @Override + public boolean mayEventTimeOverlappedWithTimeRange() { + final long[] timestamps = tablet.getTimestamps(); + if (Objects.isNull(timestamps) || timestamps.length == 0) { + return false; + } + // We assume that `timestamps` is ordered. + return startTime <= timestamps[timestamps.length - 1] && timestamps[0] <= endTime; + } + + @Override + public boolean mayEventPathsOverlappedWithPattern() { + return sourceEvent == null || sourceEvent.mayEventPathsOverlappedWithPattern(); + } + + public void markAsNeedToReport() { + this.needToReport = true; + } + + public String getDeviceId() { + // NonNull indicates that the internallyDecreaseResourceReferenceCount has not been called. + return Objects.nonNull(tablet) ? tablet.getDeviceId() : deviceId; + } + + public PipeRawTabletInsertionEvent getSourceEvent() { + return sourceEvent; + } + + /////////////////////////// TabletInsertionEvent /////////////////////////// + + @Override + public Iterable processRowByRow( + final BiConsumer consumer) { + return initEventParser().processRowByRow(consumer); + } + + @Override + public Iterable processTablet( + final BiConsumer consumer) { + return initEventParser().processTablet(consumer); + } + + /////////////////////////// convertToTablet /////////////////////////// + + public boolean isAligned() { + return isAligned; + } + + public Tablet convertToTablet() { + if (!shouldParseTimeOrPattern()) { + return tablet; + } + return initEventParser().convertToTablet(); + } + + /////////////////////////// event parser /////////////////////////// + + private TabletInsertionEventParser initEventParser() { + if (eventParser == null) { + eventParser = + tablet.getDeviceId().startsWith("root.") + ? new TabletInsertionEventTreePatternParser(this, tablet, isAligned, treePattern) + : new TabletInsertionEventTablePatternParser(this, tablet, isAligned, tablePattern); + } + return eventParser; + } + + public long count() { + final Tablet covertedTablet = shouldParseTimeOrPattern() ? convertToTablet() : tablet; + return (long) covertedTablet.getRowSize() * covertedTablet.getSchemas().size(); + } + + /////////////////////////// parsePatternOrTime /////////////////////////// + + public PipeRawTabletInsertionEvent parseEventWithPatternOrTime() { + return new PipeRawTabletInsertionEvent( + getRawIsTableModelEvent(), + getSourceDatabaseNameFromDataRegion(), + getRawTableModelDataBase(), + getRawTreeModelDataBase(), + convertToTablet(), + isAligned, + pipeName, + creationTime, + this, + needToReport); + } + + public boolean hasNoNeedParsingAndIsEmpty() { + return !shouldParseTimeOrPattern() && isTabletEmpty(tablet); + } + + public static boolean isTabletEmpty(final Tablet tablet) { + return Objects.isNull(tablet) + || tablet.getRowSize() == 0 + || Objects.isNull(tablet.getSchemas()) + || tablet.getSchemas().isEmpty(); + } + + /////////////////////////// Object /////////////////////////// + + @Override + public String toString() { + return String.format( + "PipeRawTabletInsertionEvent{tablet=%s, isAligned=%s, sourceEvent=%s, needToReport=%s, eventParser=%s}", + tablet, isAligned, sourceEvent, needToReport, eventParser) + + " - " + + super.toString(); + } + + @Override + public String coreReportMessage() { + return String.format( + "PipeRawTabletInsertionEvent{tablet=%s, isAligned=%s, sourceEvent=%s, needToReport=%s}", + tablet, + isAligned, + sourceEvent == null ? "null" : sourceEvent.coreReportMessage(), + needToReport) + + " - " + + super.coreReportMessage(); + } + + /////////////////////////// ReferenceTrackableEvent /////////////////////////// + + @Override + protected void trackResource() { + // PipeDataNodeResourceManager.ref().trackPipeEventResource(this, eventResourceBuilder()); + } + + @Override + public PipePhantomReferenceManager.PipeEventResource eventResourceBuilder() { + return new PipeRawTabletInsertionEventResource(this.isReleased, this.referenceCount); + } + + private static class PipeRawTabletInsertionEventResource + extends PipePhantomReferenceManager.PipeEventResource { + + // private final PipeTabletMemoryBlock allocatedMemoryBlock; + + private PipeRawTabletInsertionEventResource( + final AtomicBoolean isReleased, final AtomicInteger referenceCount) { + super(isReleased, referenceCount); + } + + @Override + protected void finalizeResource() { + // allocatedMemoryBlock.close(); + } + } + + /////////////////////////// AutoCloseable /////////////////////////// + + @Override + public void close() { + // The semantic of close is to release the memory occupied by parsing, this method does nothing + // to unify the external close semantic: + // 1. PipeRawTabletInsertionEvent: the tablet occupying memory upon construction, even when + // parsing is involved. + // 2. PipeInsertNodeTabletInsertionEvent: the tablet is only constructed when it's actually + // involved in parsing. + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/tsfile/PipeTsFileInsertionEvent.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/tsfile/PipeTsFileInsertionEvent.java new file mode 100644 index 00000000..e3832849 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/tsfile/PipeTsFileInsertionEvent.java @@ -0,0 +1,644 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.event.tsfile; + +import static org.apache.tsfile.common.constant.TsFileConstant.PATH_ROOT; +import static org.apache.tsfile.common.constant.TsFileConstant.PATH_SEPARATOR; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.iotdb.collector.plugin.builtin.sink.datastructure.pattern.TablePattern; +import org.apache.iotdb.collector.plugin.builtin.sink.datastructure.pattern.TreePattern; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tsfile.aggregator.TsFileInsertionPointCounter; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tsfile.parser.TsFileInsertionEventParser; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tsfile.parser.TsFileInsertionEventParserProvider; +import org.apache.iotdb.collector.plugin.builtin.sink.resource.ref.PipePhantomReferenceManager.PipeEventResource; +import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent; +import org.apache.iotdb.pipe.api.event.dml.insertion.TsFileInsertionEvent; +import org.apache.iotdb.pipe.api.exception.PipeException; +import org.apache.tsfile.file.metadata.IDeviceID; +import org.apache.tsfile.file.metadata.PlainDeviceID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PipeTsFileInsertionEvent extends PipeInsertionEvent + implements TsFileInsertionEvent, ReferenceTrackableEvent { + + private static final Logger LOGGER = LoggerFactory.getLogger(PipeTsFileInsertionEvent.class); + + private static final String TREE_MODEL_EVENT_TABLE_NAME_PREFIX = PATH_ROOT + PATH_SEPARATOR; + + private final TsFileResource resource; + private File tsFile; + + // This is true iff the modFile exists and should be transferred + private boolean isWithMod; + private File modFile; + private final File sharedModFile; + + private final boolean isLoaded; + private final boolean isGeneratedByPipe; + private final boolean isGeneratedByPipeConsensus; + private final boolean isGeneratedByHistoricalExtractor; + + private final AtomicBoolean isClosed; + private final AtomicReference eventParser; + + // The point count of the TsFile. Used for metrics on PipeConsensus' receiver side. + // May be updated after it is flushed. Should be negative if not set. + // private long flushPointCount = TsFileProcessor.FLUSH_POINT_COUNT_NOT_SET; + + // public PipeTsFileInsertionEvent( + // final Boolean isTableModelEvent, + // final String databaseNameFromDataRegion, + // final TsFileResource resource, + // final boolean isLoaded, + // final boolean isGeneratedByPipe, + // final boolean isGeneratedByHistoricalExtractor) { + // // The modFile must be copied before the event is assigned to the listening pipes + // this( + // isTableModelEvent, + // databaseNameFromDataRegion, + // resource, + // true, + // isLoaded, + // isGeneratedByPipe, + // isGeneratedByHistoricalExtractor, + // null, + // 0, + // null, + // null, + // Long.MIN_VALUE, + // Long.MAX_VALUE); + // } + + public PipeTsFileInsertionEvent( + final Boolean isTableModelEvent, + final String databaseNameFromDataRegion, + final TsFileResource resource, + final boolean isWithMod, + final boolean isLoaded, + final boolean isGeneratedByPipe, + final boolean isGeneratedByHistoricalExtractor, + final String pipeName, + final long creationTime, + final TreePattern treePattern, + final TablePattern tablePattern, + final long startTime, + final long endTime) { + super( + pipeName, + creationTime, + treePattern, + tablePattern, + startTime, + endTime, + isTableModelEvent, + databaseNameFromDataRegion); + + this.resource = resource; + tsFile = resource.getTsFile(); + + this.isWithMod = isWithMod && resource.anyModFileExists(); + this.modFile = this.isWithMod ? resource.getExclusiveModFile().getFile() : null; + // TODO: process the shared mod file + this.sharedModFile = + resource.getSharedModFile() != null ? resource.getSharedModFile().getFile() : null; + + this.isLoaded = isLoaded; + this.isGeneratedByPipe = isGeneratedByPipe; + this.isGeneratedByPipeConsensus = resource.isGeneratedByPipeConsensus(); + this.isGeneratedByHistoricalExtractor = isGeneratedByHistoricalExtractor; + + isClosed = new AtomicBoolean(resource.isClosed()); + // Register close listener if TsFile is not closed + // if (!isClosed.get()) { + // final TsFileProcessor processor = resource.getProcessor(); + // if (processor != null) { + // processor.addCloseFileListener( + // o -> { + // synchronized (isClosed) { + // isClosed.set(true); + // isClosed.notifyAll(); + // // Update flushPointCount after TsFile is closed + // flushPointCount = processor.getMemTableFlushPointCount(); + // } + // }); + // } + // } + // Check again after register close listener in case TsFile is closed during the process + // TsFile flushing steps: + // 1. Flush tsFile + // 2. First listener (Set resource status "closed" -> Set processor == null -> processor == null + // is seen) + // 3. Other listeners (Set "closed" status for events) + // Then we can imply that: + // 1. If the listener cannot be executed because all listeners passed, then resources status is + // set "closed" and can be set here + // 2. If the listener cannot be executed because processor == null is seen, then resources + // status is set "closed" and can be set here + // Then we know: + // 1. The status in the event can be closed eventually. + // 2. If the status is "closed", then the resource status is "closed". + // Then we know: + // If the status is "closed", then the resource status is "closed", the tsFile won't be altered + // and can be sent. + isClosed.set(resource.isClosed()); + + this.eventParser = new AtomicReference<>(null); + } + + /** + * @return {@code false} if this file can't be sent by pipe because it is empty. {@code true} + * otherwise. + */ + public boolean waitForTsFileClose() throws InterruptedException { + if (!isClosed.get()) { + isClosed.set(resource.isClosed()); + + synchronized (isClosed) { + while (!isClosed.get()) { + isClosed.wait(100); + + final boolean isClosedNow = resource.isClosed(); + if (isClosedNow) { + isClosed.set(true); + isClosed.notifyAll(); + + // Update flushPointCount after TsFile is closed + // final TsFileProcessor processor = resource.getProcessor(); + // if (processor != null) { + // flushPointCount = processor.getMemTableFlushPointCount(); + // } + + break; + } + } + } + } + + // From illustrations above we know If the status is "closed", then the tsFile is flushed + // And here we guarantee that the isEmpty() is set before flushing if tsFile is empty + // Then we know: "isClosed" --> tsFile flushed --> (isEmpty() <--> tsFile is empty) + return !resource.isEmpty(); + } + + public File getTsFile() { + return tsFile; + } + + public File getModFile() { + return modFile; + } + + public File getSharedModFile() { + return sharedModFile; + } + + public boolean isWithMod() { + return isWithMod; + } + + // If the previous "isWithMod" is false, the modFile has been set to "null", then the isWithMod + // can't be set to true + public void disableMod4NonTransferPipes(final boolean isWithMod) { + this.isWithMod = isWithMod && this.isWithMod; + } + + public boolean isLoaded() { + return isLoaded; + } + + public long getFileStartTime() { + return resource.getFileStartTime(); + } + + /** + * Only used for metrics on PipeConsensus' receiver side. If the event is recovered after data + * node's restart, the flushPointCount can be not set. It's totally fine for the PipeConsensus' + * receiver side. The receiver side will count the actual point count from the TsFile. + * + *

If you want to get the actual point count with no risk, you can call {@link + * #count(boolean)}. + */ + // public long getFlushPointCount() { + // return flushPointCount; + // } + + public long getTimePartitionId() { + return resource.getTimePartition(); + } + + /////////////////////////// PipeRawTabletInsertionEvent /////////////////////////// + + @Override + public boolean internallyIncreaseResourceReferenceCount(final String holderMessage) { + try { + // tsFile = PipeDataNodeResourceManager.tsfile().increaseFileReference(tsFile, true, resource); + // if (isWithMod) { + // modFile = PipeDataNodeResourceManager.tsfile().increaseFileReference(modFile, false, null); + // } + return true; + } catch (final Exception e) { + LOGGER.warn( + String.format( + "Increase reference count for TsFile %s or modFile %s error. Holder Message: %s", + tsFile, modFile, holderMessage), + e); + return false; + } + } + + @Override + public boolean internallyDecreaseResourceReferenceCount(final String holderMessage) { + try { + // PipeDataNodeResourceManager.tsfile().decreaseFileReference(tsFile); + // if (isWithMod) { + // PipeDataNodeResourceManager.tsfile().decreaseFileReference(modFile); + // } + close(); + return true; + } catch (final Exception e) { + LOGGER.warn( + String.format( + "Decrease reference count for TsFile %s error. Holder Message: %s", + tsFile.getPath(), holderMessage), + e); + return false; + } + } + + @Override + public PipeTsFileInsertionEvent shallowCopySelfAndBindPipeTaskMetaForProgressReport( + final String pipeName, + final long creationTime, + final TreePattern treePattern, + final TablePattern tablePattern, + final long startTime, + final long endTime) { + return new PipeTsFileInsertionEvent( + getRawIsTableModelEvent(), + getSourceDatabaseNameFromDataRegion(), + resource, + isWithMod, + isLoaded, + isGeneratedByPipe, + isGeneratedByHistoricalExtractor, + pipeName, + creationTime, + treePattern, + tablePattern, + startTime, + endTime); + } + + @Override + public boolean isGeneratedByPipe() { + return isGeneratedByPipe; + } + + @Override + public boolean mayEventTimeOverlappedWithTimeRange() { + // If the tsFile is not closed the resource.getFileEndTime() will be Long.MIN_VALUE + // In that case we only judge the resource.getFileStartTime() to avoid losing data + return isClosed.get() + ? startTime <= resource.getFileEndTime() && resource.getFileStartTime() <= endTime + : resource.getFileStartTime() <= endTime; + } + + @Override + public boolean mayEventPathsOverlappedWithPattern() { + if (!resource.isClosed()) { + return true; + } + + try { + final Map deviceIsAlignedMap = null; + // PipeDataNodeResourceManager.tsfile() + // .getDeviceIsAlignedMapFromCache( + // PipeTsFileResourceManager.getHardlinkOrCopiedFileInPipeDir(resource.getTsFile()), + // false); + final Set deviceSet = + Objects.nonNull(deviceIsAlignedMap) ? deviceIsAlignedMap.keySet() : resource.getDevices(); + return deviceSet.stream() + .anyMatch( + deviceID -> { + // Tree model + if (Boolean.FALSE.equals(getRawIsTableModelEvent()) + || deviceID instanceof PlainDeviceID + || deviceID.getTableName().startsWith(TREE_MODEL_EVENT_TABLE_NAME_PREFIX) + || deviceID.getTableName().equals(PATH_ROOT)) { + markAsTreeModelEvent(); + return treePattern.mayOverlapWithDevice(deviceID); + } + + // Table model + markAsTableModelEvent(); + return true; + }); + } catch (final Exception e) { + LOGGER.warn( + "Pipe {}: failed to get devices from TsFile {}, extract it anyway", + pipeName, + resource.getTsFilePath(), + e); + return true; + } + } + + /////////////////////////// PipeInsertionEvent /////////////////////////// + + @Override + public boolean isTableModelEvent() { + if (getRawIsTableModelEvent() == null) { + if (getSourceDatabaseNameFromDataRegion() != null) { + return super.isTableModelEvent(); + } + + try { + final Map deviceIsAlignedMap = null; + // PipeDataNodeResourceManager.tsfile() + // .getDeviceIsAlignedMapFromCache( + // PipeTsFileResourceManager.getHardlinkOrCopiedFileInPipeDir( + // resource.getTsFile()), + // false); + final Set deviceSet = + Objects.nonNull(deviceIsAlignedMap) + ? deviceIsAlignedMap.keySet() + : resource.getDevices(); + for (final IDeviceID deviceID : deviceSet) { + if (deviceID instanceof PlainDeviceID + || deviceID.getTableName().startsWith(TREE_MODEL_EVENT_TABLE_NAME_PREFIX) + || deviceID.getTableName().equals(PATH_ROOT)) { + markAsTreeModelEvent(); + } else { + markAsTableModelEvent(); + } + break; + } + } catch (final Exception e) { + throw new PipeException( + String.format( + "Pipe %s: failed to judge whether TsFile %s is table model or tree model", + pipeName, resource.getTsFilePath()), + e); + } + } + + return getRawIsTableModelEvent(); + } + + /////////////////////////// TsFileInsertionEvent /////////////////////////// + + @Override + public Iterable toTabletInsertionEvents() throws PipeException { + return toTabletInsertionEvents(Long.MAX_VALUE); + } + + public Iterable toTabletInsertionEvents(final long timeoutMs) + throws PipeException { + try { + if (!waitForTsFileClose()) { + LOGGER.warn( + "Pipe skipping temporary TsFile's parsing which shouldn't be transferred: {}", tsFile); + return Collections.emptyList(); + } + waitForResourceEnough4Parsing(timeoutMs); + return initEventParser().toTabletInsertionEvents(); + } catch (final Exception e) { + close(); + + // close() should be called before re-interrupting the thread + if (e instanceof InterruptedException) { + Thread.currentThread().interrupt(); + } + + final String errorMsg = + e instanceof InterruptedException + ? String.format( + "Interrupted when waiting for closing TsFile %s.", resource.getTsFilePath()) + : String.format( + "Parse TsFile %s error. Because: %s", resource.getTsFilePath(), e.getMessage()); + LOGGER.warn(errorMsg, e); + throw new PipeException(errorMsg); + } + } + + private void waitForResourceEnough4Parsing(final long timeoutMs) throws InterruptedException { + // final PipeMemoryManager memoryManager = PipeDataNodeResourceManager.memory(); + // if (memoryManager.isEnough4TabletParsing()) { + // return; + // } + + final long startTime = System.currentTimeMillis(); + // long lastRecordTime = startTime; + // + // final long memoryCheckIntervalMs = PipeOptions.PIPE_CHECK_MEMORY_ENOUGH_INTERVAL_MS.value(); + // while (!memoryManager.isEnough4TabletParsing()) { + // Thread.sleep(memoryCheckIntervalMs); + // + // final long currentTime = System.currentTimeMillis(); + // final double elapsedRecordTimeSeconds = (currentTime - lastRecordTime) / 1000.0; + // final double waitTimeSeconds = (currentTime - startTime) / 1000.0; + // if (elapsedRecordTimeSeconds > 10.0) { + // LOGGER.info( + // "Wait for resource enough for parsing {} for {} seconds.", + // resource != null ? resource.getTsFilePath() : "tsfile", + // waitTimeSeconds); + // lastRecordTime = currentTime; + // } else if (LOGGER.isDebugEnabled()) { + // LOGGER.debug( + // "Wait for resource enough for parsing {} for {} seconds.", + // resource != null ? resource.getTsFilePath() : "tsfile", + // waitTimeSeconds); + // } + // + // if (waitTimeSeconds * 1000 > timeoutMs) { + // // should contain 'TimeoutException' in exception message + // throw new PipeException( + // String.format("TimeoutException: Waited %s seconds", waitTimeSeconds)); + // } + // } + + final long currentTime = System.currentTimeMillis(); + final double waitTimeSeconds = (currentTime - startTime) / 1000.0; + LOGGER.info( + "Wait for resource enough for parsing {} for {} seconds.", + resource != null ? resource.getTsFilePath() : "tsfile", + waitTimeSeconds); + } + + /** The method is used to prevent circular replication in PipeConsensus */ + public boolean isGeneratedByPipeConsensus() { + return isGeneratedByPipeConsensus; + } + + public boolean isGeneratedByHistoricalExtractor() { + return isGeneratedByHistoricalExtractor; + } + + private TsFileInsertionEventParser initEventParser() { + try { + eventParser.compareAndSet( + null, + new TsFileInsertionEventParserProvider( + tsFile, treePattern, tablePattern, startTime, endTime, this) + .provide()); + return eventParser.get(); + } catch (final IOException e) { + close(); + + final String errorMsg = String.format("Read TsFile %s error.", resource.getTsFilePath()); + LOGGER.warn(errorMsg, e); + throw new PipeException(errorMsg); + } + } + + public long count(final boolean skipReportOnCommit) throws IOException { + long count = 0; + + if (shouldParseTime()) { + try { + for (final TabletInsertionEvent event : toTabletInsertionEvents()) { + final PipeRawTabletInsertionEvent rawEvent = ((PipeRawTabletInsertionEvent) event); + count += rawEvent.count(); + // if (skipReportOnCommit) { + // rawEvent.skipReportOnCommit(); + // } + } + return count; + } finally { + close(); + } + } + + try (final TsFileInsertionPointCounter counter = + new TsFileInsertionPointCounter(tsFile, treePattern)) { + return counter.count(); + } + } + + /** Release the resource of {@link TsFileInsertionEventParser}. */ + @Override + public void close() { + eventParser.getAndUpdate( + parser -> { + if (Objects.nonNull(parser)) { + parser.close(); + } + return null; + }); + } + + /////////////////////////// Object /////////////////////////// + + @Override + public String toString() { + return String.format( + "PipeTsFileInsertionEvent{resource=%s, tsFile=%s, isLoaded=%s, isGeneratedByPipe=%s, isClosed=%s, eventParser=%s}", + resource, tsFile, isLoaded, isGeneratedByPipe, isClosed.get(), eventParser) + + " - " + + super.toString(); + } + + @Override + public String coreReportMessage() { + return String.format( + "PipeTsFileInsertionEvent{resource=%s, tsFile=%s, isLoaded=%s, isGeneratedByPipe=%s, isClosed=%s}", + resource, tsFile, isLoaded, isGeneratedByPipe, isClosed.get()) + + " - " + + super.coreReportMessage(); + } + + /////////////////////////// ReferenceTrackableEvent /////////////////////////// + + @Override + public void trackResource() { + // PipeDataNodeResourceManager.ref().trackPipeEventResource(this, eventResourceBuilder()); + } + + @Override + public PipeEventResource eventResourceBuilder() { + return new PipeTsFileInsertionEventResource( + this.isReleased, + this.referenceCount, + this.tsFile, + this.isWithMod, + this.modFile, + this.sharedModFile, + this.eventParser); + } + + private static class PipeTsFileInsertionEventResource extends PipeEventResource { + + private final File tsFile; + private final boolean isWithMod; + private final File modFile; + private final File sharedModFile; // unused now + private final AtomicReference eventParser; + + private PipeTsFileInsertionEventResource( + final AtomicBoolean isReleased, + final AtomicInteger referenceCount, + final File tsFile, + final boolean isWithMod, + final File modFile, + final File sharedModFile, + final AtomicReference eventParser) { + super(isReleased, referenceCount); + this.tsFile = tsFile; + this.isWithMod = isWithMod; + this.modFile = modFile; + this.sharedModFile = sharedModFile; + this.eventParser = eventParser; + } + + @Override + protected void finalizeResource() { + try { + // decrease reference count + // PipeDataNodeResourceManager.tsfile().decreaseFileReference(tsFile); + // if (isWithMod) { + // PipeDataNodeResourceManager.tsfile().decreaseFileReference(modFile); + // } + + // close event parser + eventParser.getAndUpdate( + parser -> { + if (Objects.nonNull(parser)) { + parser.close(); + } + return null; + }); + } catch (final Exception e) { + LOGGER.warn("Decrease reference count for TsFile {} error.", tsFile.getPath(), e); + } + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeConnectorCriticalException.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeConnectorCriticalException.java new file mode 100644 index 00000000..f84a263e --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeConnectorCriticalException.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.exception.pipe; + +import org.apache.tsfile.utils.ReadWriteIOUtils; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Objects; + +public class PipeRuntimeConnectorCriticalException extends PipeRuntimeCriticalException { + + public PipeRuntimeConnectorCriticalException(final String message) { + super(message); + } + + public PipeRuntimeConnectorCriticalException(final String message, final long timeStamp) { + super(message, timeStamp); + } + + @Override + public boolean equals(final Object obj) { + return obj instanceof PipeRuntimeConnectorCriticalException + && Objects.equals(getMessage(), ((PipeRuntimeConnectorCriticalException) obj).getMessage()) + && Objects.equals(getTimeStamp(), ((PipeRuntimeException) obj).getTimeStamp()); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public void serialize(final ByteBuffer byteBuffer) { + PipeRuntimeExceptionType.CONNECTOR_CRITICAL_EXCEPTION.serialize(byteBuffer); + ReadWriteIOUtils.write(getMessage(), byteBuffer); + ReadWriteIOUtils.write(getTimeStamp(), byteBuffer); + } + + @Override + public void serialize(final OutputStream stream) throws IOException { + PipeRuntimeExceptionType.CONNECTOR_CRITICAL_EXCEPTION.serialize(stream); + ReadWriteIOUtils.write(getMessage(), stream); + ReadWriteIOUtils.write(getTimeStamp(), stream); + } + + @Override + public String toString() { + return "PipeRuntimeConnectorCriticalException{" + + "message='" + + getMessage() + + "', timeStamp=" + + getTimeStamp() + + "}"; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeConnectorRetryTimesConfigurableException.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeConnectorRetryTimesConfigurableException.java new file mode 100644 index 00000000..8d215d8b --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeConnectorRetryTimesConfigurableException.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.exception.pipe; + + +public class PipeRuntimeConnectorRetryTimesConfigurableException + extends PipeRuntimeConnectorCriticalException { + + private final int retryTimes; + + public PipeRuntimeConnectorRetryTimesConfigurableException( + final String message, final int retryTimes) { + super(message); + this.retryTimes = retryTimes; + } + + public int getRetryTimes() { + return retryTimes; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeCriticalException.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeCriticalException.java new file mode 100644 index 00000000..fda9c614 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeCriticalException.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.exception.pipe; + +import org.apache.tsfile.utils.ReadWriteIOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Objects; + +public class PipeRuntimeCriticalException extends PipeRuntimeException { + + public PipeRuntimeCriticalException(final String message) { + super(message); + } + + public PipeRuntimeCriticalException(final String message, final long timeStamp) { + super(message, timeStamp); + } + + @Override + public boolean equals(final Object obj) { + return obj instanceof PipeRuntimeCriticalException + && Objects.equals(getMessage(), ((PipeRuntimeCriticalException) obj).getMessage()) + && Objects.equals(getTimeStamp(), ((PipeRuntimeException) obj).getTimeStamp()); + } + + @Override + public void serialize(final ByteBuffer byteBuffer) { + PipeRuntimeExceptionType.CRITICAL_EXCEPTION.serialize(byteBuffer); + ReadWriteIOUtils.write(getMessage(), byteBuffer); + ReadWriteIOUtils.write(getTimeStamp(), byteBuffer); + } + + @Override + public void serialize(final OutputStream stream) throws IOException { + PipeRuntimeExceptionType.CRITICAL_EXCEPTION.serialize(stream); + ReadWriteIOUtils.write(getMessage(), stream); + ReadWriteIOUtils.write(getTimeStamp(), stream); + } + + @Override + public String toString() { + return "PipeRuntimeCriticalException{" + + "message='" + + getMessage() + + "', timeStamp=" + + getTimeStamp() + + "}"; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeException.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeException.java new file mode 100644 index 00000000..8e85ce86 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeException.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.exception.pipe; + +import org.apache.iotdb.pipe.api.exception.PipeException; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Objects; + +public abstract class PipeRuntimeException extends PipeException { + + protected PipeRuntimeException(final String message) { + super(message); + } + + protected PipeRuntimeException(final String message, final long timeStamp) { + super(message, timeStamp); + } + + @Override + public boolean equals(final Object obj) { + return obj instanceof PipeRuntimeException + && Objects.equals(getMessage(), ((PipeRuntimeException) obj).getMessage()) + && Objects.equals(getTimeStamp(), ((PipeRuntimeException) obj).getTimeStamp()); + } + + @Override + public int hashCode() { + return Objects.hash(getMessage(), getTimeStamp()); + } + + public abstract void serialize(final ByteBuffer byteBuffer); + + public abstract void serialize(final OutputStream stream) throws IOException; +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeExceptionType.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeExceptionType.java new file mode 100644 index 00000000..9f39441d --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeExceptionType.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.exception.pipe; + +import org.apache.tsfile.utils.ReadWriteIOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +public enum PipeRuntimeExceptionType { + NON_CRITICAL_EXCEPTION((short) 1), + CRITICAL_EXCEPTION((short) 2), + CONNECTOR_CRITICAL_EXCEPTION((short) 3), + OUT_OF_MEMORY_CRITICAL_EXCEPTION((short) 4), + ; + + private final short type; + + PipeRuntimeExceptionType(short type) { + this.type = type; + } + + public short getType() { + return type; + } + + public void serialize(ByteBuffer byteBuffer) { + ReadWriteIOUtils.write(type, byteBuffer); + } + + public void serialize(OutputStream stream) throws IOException { + ReadWriteIOUtils.write(type, stream); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeNonCriticalException.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeNonCriticalException.java new file mode 100644 index 00000000..a539d2e3 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeNonCriticalException.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.exception.pipe; + +import org.apache.tsfile.utils.ReadWriteIOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Objects; + +public class PipeRuntimeNonCriticalException extends PipeRuntimeException { + + public PipeRuntimeNonCriticalException(String message) { + super(message); + } + + public PipeRuntimeNonCriticalException(String message, long timeStamp) { + super(message, timeStamp); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof PipeRuntimeNonCriticalException + && Objects.equals(getMessage(), ((PipeRuntimeNonCriticalException) obj).getMessage()) + && Objects.equals(getTimeStamp(), ((PipeRuntimeException) obj).getTimeStamp()); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public void serialize(ByteBuffer byteBuffer) { + PipeRuntimeExceptionType.NON_CRITICAL_EXCEPTION.serialize(byteBuffer); + ReadWriteIOUtils.write(getMessage(), byteBuffer); + ReadWriteIOUtils.write(getTimeStamp(), byteBuffer); + } + + @Override + public void serialize(OutputStream stream) throws IOException { + PipeRuntimeExceptionType.NON_CRITICAL_EXCEPTION.serialize(stream); + ReadWriteIOUtils.write(getMessage(), stream); + ReadWriteIOUtils.write(getTimeStamp(), stream); + } + + @Override + public String toString() { + return "PipeRuntimeNonCriticalException{" + + "message='" + + getMessage() + + "', timeStamp=" + + getTimeStamp() + + "}"; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeOutOfMemoryCriticalException.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeOutOfMemoryCriticalException.java new file mode 100644 index 00000000..82cc89bf --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeOutOfMemoryCriticalException.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.exception.pipe; + +import org.apache.tsfile.utils.ReadWriteIOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Objects; + +public class PipeRuntimeOutOfMemoryCriticalException extends PipeRuntimeCriticalException { + + public PipeRuntimeOutOfMemoryCriticalException(String message) { + super(message); + } + + public PipeRuntimeOutOfMemoryCriticalException(String message, long timeStamp) { + super(message, timeStamp); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof PipeRuntimeOutOfMemoryCriticalException + && Objects.equals( + getMessage(), ((PipeRuntimeOutOfMemoryCriticalException) obj).getMessage()) + && Objects.equals(getTimeStamp(), ((PipeRuntimeException) obj).getTimeStamp()); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public void serialize(ByteBuffer byteBuffer) { + PipeRuntimeExceptionType.OUT_OF_MEMORY_CRITICAL_EXCEPTION.serialize(byteBuffer); + ReadWriteIOUtils.write(getMessage(), byteBuffer); + ReadWriteIOUtils.write(getTimeStamp(), byteBuffer); + } + + @Override + public void serialize(OutputStream stream) throws IOException { + PipeRuntimeExceptionType.OUT_OF_MEMORY_CRITICAL_EXCEPTION.serialize(stream); + ReadWriteIOUtils.write(getMessage(), stream); + ReadWriteIOUtils.write(getTimeStamp(), stream); + } + + @Override + public String toString() { + return "PipeRuntimeOutOfMemoryException{" + + "message='" + + getMessage() + + "', timeStamp=" + + getTimeStamp() + + "}"; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/limiter/GlobalRateLimiter.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/limiter/GlobalRateLimiter.java new file mode 100644 index 00000000..fbd7062a --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/limiter/GlobalRateLimiter.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.limiter; + +import com.google.common.util.concurrent.AtomicDouble; +import com.google.common.util.concurrent.RateLimiter; +import java.util.concurrent.TimeUnit; + +import org.apache.iotdb.collector.config.PipeOptions; + +/** This is a global rate limiter for all connectors. */ +public class GlobalRateLimiter { + + private final AtomicDouble throughputBytesPerSecond = + new AtomicDouble(PipeOptions.PIPE_ALL_SINK_RATE_LIMIT_BYTES_PER_SECOND.value()); + private final RateLimiter rateLimiter; + + public GlobalRateLimiter() { + final double throughputBytesPerSecondLimit = throughputBytesPerSecond.get(); + rateLimiter = + throughputBytesPerSecondLimit <= 0 + ? RateLimiter.create(Double.MAX_VALUE) + : RateLimiter.create(throughputBytesPerSecondLimit); + } + + public void acquire(long bytes) { + if (reloadParams()) { + return; + } + + while (bytes > 0) { + if (bytes > Integer.MAX_VALUE) { + tryAcquireWithRateCheck(Integer.MAX_VALUE); + bytes -= Integer.MAX_VALUE; + } else { + tryAcquireWithRateCheck((int) bytes); + return; + } + } + } + + private void tryAcquireWithRateCheck(final int bytes) { + while (!rateLimiter.tryAcquire( + bytes, + PipeOptions.RATE_LIMITER_HOT_RELOAD_CHECK_INTERVAL_MS.value(), + TimeUnit.MILLISECONDS)) { + if (reloadParams()) { + return; + } + } + } + + private boolean reloadParams() { + final double throughputBytesPerSecondLimit = + PipeOptions.PIPE_ALL_SINK_RATE_LIMIT_BYTES_PER_SECOND.value(); + + if (throughputBytesPerSecond.get() != throughputBytesPerSecondLimit) { + throughputBytesPerSecond.set(throughputBytesPerSecondLimit); + rateLimiter.setRate( + // if throughput <= 0, disable rate limiting + throughputBytesPerSecondLimit <= 0 ? Double.MAX_VALUE : throughputBytesPerSecondLimit); + } + + // For performance, we don't need to acquire rate limiter if throughput <= 0 + return throughputBytesPerSecondLimit <= 0; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/limiter/PipeEndPointRateLimiter.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/limiter/PipeEndPointRateLimiter.java new file mode 100644 index 00000000..39ac4ba2 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/limiter/PipeEndPointRateLimiter.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.limiter; + +import com.google.common.util.concurrent.RateLimiter; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; + +public class PipeEndPointRateLimiter { + + // The task agent is used to check if the pipe is still alive + + // private final String pipeName; + // private final long creationTime; + + private final double bytesPerSecondLimit; + + private final ConcurrentMap endPointRateLimiterMap; + + public PipeEndPointRateLimiter( + final String pipeName, final long creationTime, final double bytesPerSecondLimit) { + // this.pipeName = pipeName; + // this.creationTime = creationTime; + this.bytesPerSecondLimit = bytesPerSecondLimit; + endPointRateLimiterMap = new ConcurrentHashMap<>(); + } + + public void acquire(final TEndPoint endPoint, long bytes) { + if (endPoint == null) { + return; + } + + final RateLimiter rateLimiter = + endPointRateLimiterMap.computeIfAbsent( + endPoint, e -> RateLimiter.create(bytesPerSecondLimit)); + + while (bytes > 0) { + if (bytes > Integer.MAX_VALUE) { + if (!tryAcquireWithPipeCheck(rateLimiter, Integer.MAX_VALUE)) { + return; + } + bytes -= Integer.MAX_VALUE; + } else { + tryAcquireWithPipeCheck(rateLimiter, (int) bytes); + return; + } + } + } + + private boolean tryAcquireWithPipeCheck(final RateLimiter rateLimiter, final int bytes) { + while (!rateLimiter.tryAcquire( + bytes, + PipeOptions.RATE_LIMITER_HOT_RELOAD_CHECK_INTERVAL_MS.value(), + TimeUnit.MILLISECONDS)) { + } + return true; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/airgap/AirGapELanguageConstant.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/airgap/AirGapELanguageConstant.java new file mode 100644 index 00000000..3544652d --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/airgap/AirGapELanguageConstant.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.airgap; + +import java.nio.charset.StandardCharsets; + +public class AirGapELanguageConstant { + public static final byte[] E_LANGUAGE_PREFIX = + ("" + "\n" + "" + "\n") + .getBytes(StandardCharsets.UTF_8); + public static final byte[] E_LANGUAGE_SUFFIX = + ("\n" + "").getBytes(StandardCharsets.UTF_8); + + private AirGapELanguageConstant() { + // Utility class + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/airgap/AirGapOneByteResponse.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/airgap/AirGapOneByteResponse.java new file mode 100644 index 00000000..3ff4a9bd --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/airgap/AirGapOneByteResponse.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.airgap; + +public class AirGapOneByteResponse { + + public static final byte[] OK = new byte[] {0}; + public static final byte[] FAIL = new byte[] {(byte) 0xFF}; + + private AirGapOneByteResponse() { + // Utility class + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/airgap/AirGapPseudoTPipeTransferRequest.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/airgap/AirGapPseudoTPipeTransferRequest.java new file mode 100644 index 00000000..0e80a038 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/airgap/AirGapPseudoTPipeTransferRequest.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.airgap; + +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; + +public class AirGapPseudoTPipeTransferRequest extends TPipeTransferReq {} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventBatch.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventBatch.java new file mode 100644 index 00000000..716d119e --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventBatch.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.IoTDBDataRegionAsyncConnector; +import org.apache.iotdb.pipe.api.event.Event; +import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class PipeTabletEventBatch implements AutoCloseable { + + private static final Logger LOGGER = LoggerFactory.getLogger(PipeTabletEventBatch.class); + + protected final List events = new ArrayList<>(); + + private final int maxDelayInMs; + private long firstEventProcessingTime = Long.MIN_VALUE; + + protected long totalBufferSize = 0; + // private final PipeMemoryBlock allocatedMemoryBlock; + + protected volatile boolean isClosed = false; + + protected PipeTabletEventBatch(final int maxDelayInMs, final long requestMaxBatchSizeInBytes) { + this.maxDelayInMs = maxDelayInMs; + + // limit in buffer size + // this.allocatedMemoryBlock = + // PipeDataNodeResourceManager.memory() + // .tryAllocate(requestMaxBatchSizeInBytes) + // .setShrinkMethod(oldMemory -> Math.max(oldMemory / 2, 0)) + // .setShrinkCallback( + // (oldMemory, newMemory) -> + // LOGGER.info( + // "The batch size limit has shrunk from {} to {}.", oldMemory, newMemory)) + // .setExpandMethod( + // oldMemory -> Math.min(Math.max(oldMemory, 1) * 2, requestMaxBatchSizeInBytes)) + // .setExpandCallback( + // (oldMemory, newMemory) -> + // LOGGER.info( + // "The batch size limit has expanded from {} to {}.", oldMemory, newMemory)); + + if (getMaxBatchSizeInBytes() != requestMaxBatchSizeInBytes) { + LOGGER.info( + "PipeTabletEventBatch: the max batch size is adjusted from {} to {} due to the " + + "memory restriction", + requestMaxBatchSizeInBytes, + getMaxBatchSizeInBytes()); + } + } + + /** + * Try offer {@link Event} into batch if the given {@link Event} is not duplicated. + * + * @param event the given {@link Event} + * @return {@code true} if the batch can be transferred + */ + public synchronized boolean onEvent(final TabletInsertionEvent event) + throws IOException { + if (isClosed || !(event instanceof PipeRawTabletInsertionEvent)) { + return false; + } + + // The deduplication logic here is to avoid the accumulation of + // the same event in a batch when retrying. + if (events.isEmpty() || !Objects.equals(events.get(events.size() - 1), event)) { + if (constructBatch(event)) { + events.add((PipeRawTabletInsertionEvent) event); + } + + if (firstEventProcessingTime == Long.MIN_VALUE) { + firstEventProcessingTime = System.currentTimeMillis(); + } + } + + return shouldEmit(); + } + + /** + * Added an {@link TabletInsertionEvent} into batch. + * + * @param event the {@link TabletInsertionEvent} in batch + * @return {@code true} if the event is calculated into batch, {@code false} if the event is + * cached and not emitted in this batch. If there are failure encountered, just throw + * exceptions and do not return {@code false} here. + */ + protected abstract boolean constructBatch(final TabletInsertionEvent event) + throws IOException; + + public boolean shouldEmit() { + return totalBufferSize >= getMaxBatchSizeInBytes() + || System.currentTimeMillis() - firstEventProcessingTime >= maxDelayInMs; + } + + private long getMaxBatchSizeInBytes() { + // return allocatedMemoryBlock.getMemoryUsageInBytes(); + return 0; + } + + public synchronized void onSuccess() { + events.clear(); + + totalBufferSize = 0; + + firstEventProcessingTime = Long.MIN_VALUE; + } + + @Override + public synchronized void close() { + isClosed = true; + + events.clear(); + } + + public List deepCopyEvents() { + return new ArrayList<>(events); + } + + public boolean isEmpty() { + return events.isEmpty(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventPlainBatch.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventPlainBatch.java new file mode 100644 index 00000000..03e536bc --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventPlainBatch.java @@ -0,0 +1,160 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTabletBatchReqV2; +import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent; +import org.apache.tsfile.utils.Pair; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PipeTabletEventPlainBatch extends PipeTabletEventBatch { + + private static final Logger LOGGER = LoggerFactory.getLogger(PipeTabletEventPlainBatch.class); + + private final List binaryBuffers = new ArrayList<>(); + private final List insertNodeBuffers = new ArrayList<>(); + private final List tabletBuffers = new ArrayList<>(); + + private static final String TREE_MODEL_DATABASE_PLACEHOLDER = null; + private final List binaryDataBases = new ArrayList<>(); + private final List insertNodeDataBases = new ArrayList<>(); + private final List tabletDataBases = new ArrayList<>(); + + // Used to rate limit when transferring data + private final Map, Long> pipe2BytesAccumulated = new HashMap<>(); + + PipeTabletEventPlainBatch(final int maxDelayInMs, final long requestMaxBatchSizeInBytes) { + super(maxDelayInMs, requestMaxBatchSizeInBytes); + } + + @Override + protected boolean constructBatch(final TabletInsertionEvent event) + throws IOException { + final int bufferSize = buildTabletInsertionBuffer(event); + totalBufferSize += bufferSize; + pipe2BytesAccumulated.compute( + new Pair<>( + ((PipeRawTabletInsertionEvent) event).getPipeName(), ((PipeRawTabletInsertionEvent) event).getCreationTime()), + (pipeName, bytesAccumulated) -> + bytesAccumulated == null ? bufferSize : bytesAccumulated + bufferSize); + return true; + } + + @Override + public synchronized void onSuccess() { + super.onSuccess(); + + binaryBuffers.clear(); + insertNodeBuffers.clear(); + tabletBuffers.clear(); + + binaryDataBases.clear(); + insertNodeDataBases.clear(); + tabletDataBases.clear(); + + pipe2BytesAccumulated.clear(); + } + + public PipeTransferTabletBatchReqV2 toTPipeTransferReq() throws IOException { + return PipeTransferTabletBatchReqV2.toTPipeTransferReq( + binaryBuffers, + insertNodeBuffers, + tabletBuffers, + binaryDataBases, + insertNodeDataBases, + tabletDataBases); + } + + public Map, Long> deepCopyPipeName2BytesAccumulated() { + return new HashMap<>(pipe2BytesAccumulated); + } + + public Map, Long> getPipe2BytesAccumulated() { + return pipe2BytesAccumulated; + } + + private int buildTabletInsertionBuffer(final TabletInsertionEvent event) + throws IOException { + int databaseEstimateSize = 0; + final ByteBuffer buffer; + // if (event instanceof PipeInsertNodeTabletInsertionEvent) { + // final PipeInsertNodeTabletInsertionEvent pipeInsertNodeTabletInsertionEvent = + // (PipeInsertNodeTabletInsertionEvent) event; + // // Read the bytebuffer from the wal file and transfer it directly without serializing or + // // deserializing if possible + // final InsertNode insertNode = + // pipeInsertNodeTabletInsertionEvent.getInsertNodeViaCacheIfPossible(); + // if (Objects.isNull(insertNode)) { + // buffer = pipeInsertNodeTabletInsertionEvent.getByteBuffer(); + // binaryBuffers.add(buffer); + // if (pipeInsertNodeTabletInsertionEvent.isTableModelEvent()) { + // databaseEstimateSize = + // pipeInsertNodeTabletInsertionEvent.getTableModelDatabaseName().length(); + // binaryDataBases.add(pipeInsertNodeTabletInsertionEvent.getTableModelDatabaseName()); + // } else { + // databaseEstimateSize = 4; + // binaryDataBases.add(TREE_MODEL_DATABASE_PLACEHOLDER); + // } + // } else { + // buffer = insertNode.serializeToByteBuffer(); + // insertNodeBuffers.add(buffer); + // if (pipeInsertNodeTabletInsertionEvent.isTableModelEvent()) { + // databaseEstimateSize = + // pipeInsertNodeTabletInsertionEvent.getTableModelDatabaseName().length(); + // insertNodeDataBases.add(pipeInsertNodeTabletInsertionEvent.getTableModelDatabaseName()); + // } else { + // databaseEstimateSize = 4; + // insertNodeDataBases.add(TREE_MODEL_DATABASE_PLACEHOLDER); + // } + // } + // } else + { + final PipeRawTabletInsertionEvent pipeRawTabletInsertionEvent = + (PipeRawTabletInsertionEvent) event; + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + pipeRawTabletInsertionEvent.convertToTablet().serialize(outputStream); + ReadWriteIOUtils.write(pipeRawTabletInsertionEvent.isAligned(), outputStream); + buffer = ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + tabletBuffers.add(buffer); + if (pipeRawTabletInsertionEvent.isTableModelEvent()) { + databaseEstimateSize = pipeRawTabletInsertionEvent.getTableModelDatabaseName().length(); + tabletDataBases.add(pipeRawTabletInsertionEvent.getTableModelDatabaseName()); + } else { + databaseEstimateSize = 4; + tabletDataBases.add(TREE_MODEL_DATABASE_PLACEHOLDER); + } + } + return buffer.limit() + databaseEstimateSize; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventTsFileBatch.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventTsFileBatch.java new file mode 100644 index 00000000..f1805cc3 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventTsFileBatch.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.PipeMemoryWeightUtil; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.builder.PipeTableModeTsFileBuilder; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.builder.PipeTreeModelTsFileBuilder; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.builder.PipeTsFileBuilder; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.sorter.PipeTableModelTabletEventSorter; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.sorter.PipeTreeModelTabletEventSorter; +import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent; +import org.apache.tsfile.exception.write.WriteProcessException; +import org.apache.tsfile.utils.Pair; +import org.apache.tsfile.write.record.Tablet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PipeTabletEventTsFileBatch extends PipeTabletEventBatch { + + private static final Logger LOGGER = LoggerFactory.getLogger(PipeTabletEventTsFileBatch.class); + + private static final AtomicLong BATCH_ID_GENERATOR = new AtomicLong(0); + private final AtomicLong currentBatchId = new AtomicLong(BATCH_ID_GENERATOR.incrementAndGet()); + + private final PipeTsFileBuilder treeModeTsFileBuilder; + private final PipeTsFileBuilder tableModeTsFileBuilder; + + private final Map, Double> pipeName2WeightMap = new HashMap<>(); + + public PipeTabletEventTsFileBatch(final int maxDelayInMs, final long requestMaxBatchSizeInBytes) { + super(maxDelayInMs, requestMaxBatchSizeInBytes); + + final AtomicLong tsFileIdGenerator = new AtomicLong(0); + treeModeTsFileBuilder = new PipeTreeModelTsFileBuilder(currentBatchId, tsFileIdGenerator); + tableModeTsFileBuilder = new PipeTableModeTsFileBuilder(currentBatchId, tsFileIdGenerator); + } + + @Override + protected boolean constructBatch(final TabletInsertionEvent event) { + // if (event instanceof PipeInsertNodeTabletInsertionEvent) { + // final PipeInsertNodeTabletInsertionEvent insertNodeTabletInsertionEvent = + // (PipeInsertNodeTabletInsertionEvent) event; + // final boolean isTableModel = insertNodeTabletInsertionEvent.isTableModelEvent(); + // final List tablets = insertNodeTabletInsertionEvent.convertToTablets(); + // for (int i = 0; i < tablets.size(); ++i) { + // final Tablet tablet = tablets.get(i); + // if (tablet.getRowSize() == 0) { + // continue; + // } + // if (isTableModel) { + // // table Model + // bufferTableModelTablet( + // insertNodeTabletInsertionEvent.getPipeName(), + // insertNodeTabletInsertionEvent.getCreationTime(), + // tablet, + // insertNodeTabletInsertionEvent.getTableModelDatabaseName()); + // } else { + // // tree Model + // bufferTreeModelTablet( + // insertNodeTabletInsertionEvent.getPipeName(), + // insertNodeTabletInsertionEvent.getCreationTime(), + // tablet, + // insertNodeTabletInsertionEvent.isAligned(i)); + // } + // } + // } else + if (event instanceof PipeRawTabletInsertionEvent) { + final PipeRawTabletInsertionEvent rawTabletInsertionEvent = + (PipeRawTabletInsertionEvent) event; + final Tablet tablet = rawTabletInsertionEvent.convertToTablet(); + if (tablet.getRowSize() == 0) { + return true; + } + if (rawTabletInsertionEvent.isTableModelEvent()) { + // table Model + bufferTableModelTablet( + rawTabletInsertionEvent.getPipeName(), + rawTabletInsertionEvent.getCreationTime(), + tablet, + rawTabletInsertionEvent.getTableModelDatabaseName()); + } else { + // tree Model + bufferTreeModelTablet( + rawTabletInsertionEvent.getPipeName(), + rawTabletInsertionEvent.getCreationTime(), + tablet, + rawTabletInsertionEvent.isAligned()); + } + } else { + LOGGER.warn( + "Batch id = {}: Unsupported event {} type {} when constructing tsfile batch", + currentBatchId.get(), + event, + event.getClass()); + } + return true; + } + + private void bufferTreeModelTablet( + final String pipeName, + final long creationTime, + final Tablet tablet, + final boolean isAligned) { + new PipeTreeModelTabletEventSorter(tablet).deduplicateAndSortTimestampsIfNecessary(); + + totalBufferSize += PipeMemoryWeightUtil.calculateTabletSizeInBytes(tablet); + + pipeName2WeightMap.compute( + new Pair<>(pipeName, creationTime), + (pipe, weight) -> Objects.nonNull(weight) ? ++weight : 1); + + treeModeTsFileBuilder.bufferTreeModelTablet(tablet, isAligned); + } + + private void bufferTableModelTablet( + final String pipeName, final long creationTime, final Tablet tablet, final String dataBase) { + new PipeTableModelTabletEventSorter(tablet).sortAndDeduplicateByDevIdTimestamp(); + + totalBufferSize += PipeMemoryWeightUtil.calculateTabletSizeInBytes(tablet); + + pipeName2WeightMap.compute( + new Pair<>(pipeName, creationTime), + (pipe, weight) -> Objects.nonNull(weight) ? ++weight : 1); + + tableModeTsFileBuilder.bufferTableModelTablet(dataBase, tablet); + } + + public Map, Double> deepCopyPipe2WeightMap() { + final double sum = pipeName2WeightMap.values().stream().reduce(Double::sum).orElse(0.0); + if (sum == 0.0) { + return Collections.emptyMap(); + } + pipeName2WeightMap.entrySet().forEach(entry -> entry.setValue(entry.getValue() / sum)); + return new HashMap<>(pipeName2WeightMap); + } + + /** + * Converts a Tablet to a TSFile and returns the generated TSFile along with its corresponding + * database name. + * + * @return a list of pairs containing the database name and the generated TSFile + * @throws IOException if an I/O error occurs during the conversion process + * @throws WriteProcessException if an error occurs during the write process + */ + public synchronized List> sealTsFiles() + throws IOException, WriteProcessException { + if (isClosed) { + return Collections.emptyList(); + } + + final List> list = new ArrayList<>(); + if (!treeModeTsFileBuilder.isEmpty()) { + list.addAll(treeModeTsFileBuilder.convertTabletToTsFileWithDBInfo()); + } + if (!tableModeTsFileBuilder.isEmpty()) { + list.addAll(tableModeTsFileBuilder.convertTabletToTsFileWithDBInfo()); + } + return list; + } + + @Override + public synchronized void onSuccess() { + super.onSuccess(); + + pipeName2WeightMap.clear(); + tableModeTsFileBuilder.onSuccess(); + treeModeTsFileBuilder.onSuccess(); + } + + @Override + public synchronized void close() { + super.close(); + + pipeName2WeightMap.clear(); + + tableModeTsFileBuilder.close(); + treeModeTsFileBuilder.close(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTransferBatchReqBuilder.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTransferBatchReqBuilder.java new file mode 100644 index 00000000..5bbfffc2 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTransferBatchReqBuilder.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch; + + + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +// import org.apache.iotdb.db.pipe.connector.client.IoTDBDataNodeCacheLeaderClientManager; +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters; +import org.apache.iotdb.pipe.api.event.Event; +import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent; +import org.apache.tsfile.exception.write.WriteProcessException; +import org.apache.tsfile.utils.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_FORMAT_HYBRID_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_FORMAT_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_FORMAT_TS_FILE_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_BATCH_DELAY_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_BATCH_SIZE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_PLAIN_BATCH_SIZE_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_TS_FILE_BATCH_DELAY_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_TS_FILE_BATCH_SIZE_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LEADER_CACHE_ENABLE_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LEADER_CACHE_ENABLE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_FORMAT_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_BATCH_DELAY_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_BATCH_SIZE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_LEADER_CACHE_ENABLE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_PLAIN_BATCH_DELAY_DEFAULT_VALUE; + +public class PipeTransferBatchReqBuilder implements AutoCloseable { + + private static final Logger LOGGER = LoggerFactory.getLogger(PipeTransferBatchReqBuilder.class); + + private final boolean useLeaderCache; + + private final int requestMaxDelayInMs; + private final long requestMaxBatchSizeInBytes; + + // If the leader cache is disabled (or unable to find the endpoint of event in the leader cache), + // the event will be stored in the default batch. + private final PipeTabletEventBatch defaultBatch; + // If the leader cache is enabled, the batch will be divided by the leader endpoint, + // each endpoint has a batch. + // This is only used in plain batch since tsfile does not return redirection info. + private final Map endPointToBatch = new HashMap<>(); + + public PipeTransferBatchReqBuilder(final PipeParameters parameters) { + final boolean usingTsFileBatch = + parameters + .getStringOrDefault( + Arrays.asList(CONNECTOR_FORMAT_KEY, SINK_FORMAT_KEY), CONNECTOR_FORMAT_HYBRID_VALUE) + .equals(CONNECTOR_FORMAT_TS_FILE_VALUE); + + useLeaderCache = + !usingTsFileBatch + && parameters.getBooleanOrDefault( + Arrays.asList(SINK_LEADER_CACHE_ENABLE_KEY, CONNECTOR_LEADER_CACHE_ENABLE_KEY), + CONNECTOR_LEADER_CACHE_ENABLE_DEFAULT_VALUE); + + final int requestMaxDelayInSeconds; + if (usingTsFileBatch) { + requestMaxDelayInSeconds = + parameters.getIntOrDefault( + Arrays.asList(CONNECTOR_IOTDB_BATCH_DELAY_KEY, SINK_IOTDB_BATCH_DELAY_KEY), + CONNECTOR_IOTDB_TS_FILE_BATCH_DELAY_DEFAULT_VALUE); + requestMaxDelayInMs = + requestMaxDelayInSeconds < 0 ? Integer.MAX_VALUE : requestMaxDelayInSeconds * 1000; + requestMaxBatchSizeInBytes = + parameters.getLongOrDefault( + Arrays.asList(CONNECTOR_IOTDB_BATCH_SIZE_KEY, SINK_IOTDB_BATCH_SIZE_KEY), + CONNECTOR_IOTDB_TS_FILE_BATCH_SIZE_DEFAULT_VALUE); + this.defaultBatch = + new PipeTabletEventTsFileBatch(requestMaxDelayInMs, requestMaxBatchSizeInBytes); + } else { + requestMaxDelayInSeconds = + parameters.getIntOrDefault( + Arrays.asList(CONNECTOR_IOTDB_BATCH_DELAY_KEY, SINK_IOTDB_BATCH_DELAY_KEY), + CONNECTOR_IOTDB_PLAIN_BATCH_DELAY_DEFAULT_VALUE); + requestMaxDelayInMs = + requestMaxDelayInSeconds < 0 ? Integer.MAX_VALUE : requestMaxDelayInSeconds * 1000; + requestMaxBatchSizeInBytes = + parameters.getLongOrDefault( + Arrays.asList(CONNECTOR_IOTDB_BATCH_SIZE_KEY, SINK_IOTDB_BATCH_SIZE_KEY), + CONNECTOR_IOTDB_PLAIN_BATCH_SIZE_DEFAULT_VALUE); + this.defaultBatch = + new PipeTabletEventPlainBatch(requestMaxDelayInMs, requestMaxBatchSizeInBytes); + } + } + + /** + * Try offer {@link Event} into the corresponding batch if the given {@link Event} is not + * duplicated. + * + * @param event the given {@link Event} + * @return {@link Pair}<{@link TEndPoint}, {@link PipeTabletEventPlainBatch}> not null means this + * {@link PipeTabletEventPlainBatch} can be transferred. the first element is the leader + * endpoint to transfer to (might be null), the second element is the batch to be transferred. + */ + public synchronized Pair onEvent( + final TabletInsertionEvent event) + throws IOException, WALPipeException, WriteProcessException { + if (!(event instanceof PipeRawTabletInsertionEvent)) { + LOGGER.warn( + "Unsupported event {} type {} when building transfer request", event, event.getClass()); + return null; + } + + if (!useLeaderCache) { + return defaultBatch.onEvent(event) ? new Pair<>(null, defaultBatch) : null; + } + + String deviceId = null; + if (event instanceof PipeRawTabletInsertionEvent) { + deviceId = ((PipeRawTabletInsertionEvent) event).getDeviceId(); + } + // else if (event instanceof PipeInsertNodeTabletInsertionEvent) { + // deviceId = ((PipeInsertNodeTabletInsertionEvent) event).getDeviceId(); + // } + + if (Objects.isNull(deviceId)) { + return defaultBatch.onEvent(event) ? new Pair<>(null, defaultBatch) : null; + } + + final TEndPoint endPoint = null; + // IoTDBDataNodeCacheLeaderClientManager.LEADER_CACHE_MANAGER.getLeaderEndPoint(deviceId); + if (Objects.isNull(endPoint)) { + return defaultBatch.onEvent(event) ? new Pair<>(null, defaultBatch) : null; + } + + final PipeTabletEventPlainBatch batch = + endPointToBatch.computeIfAbsent( + endPoint, + k -> new PipeTabletEventPlainBatch(requestMaxDelayInMs, requestMaxBatchSizeInBytes)); + return batch.onEvent(event) ? new Pair<>(endPoint, batch) : null; + } + + /** Get all batches that have at least 1 event. */ + public synchronized List> getAllNonEmptyBatches() { + final List> nonEmptyBatches = new ArrayList<>(); + if (!defaultBatch.isEmpty()) { + nonEmptyBatches.add(new Pair<>(null, defaultBatch)); + } + endPointToBatch.forEach( + (endPoint, batch) -> { + if (!batch.isEmpty()) { + nonEmptyBatches.add(new Pair<>(endPoint, batch)); + } + }); + return nonEmptyBatches; + } + + public boolean isEmpty() { + return defaultBatch.isEmpty() + && endPointToBatch.values().stream().allMatch(PipeTabletEventPlainBatch::isEmpty); + } + + public synchronized void discardEventsOfPipe(final String pipeNameToDrop, final int regionId) { + defaultBatch.discardEventsOfPipe(pipeNameToDrop, regionId); + endPointToBatch.values().forEach(batch -> batch.discardEventsOfPipe(pipeNameToDrop, regionId)); + } + + @Override + public synchronized void close() { + defaultBatch.close(); + endPointToBatch.values().forEach(PipeTabletEventPlainBatch::close); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferDataNodeHandshakeV1Req.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferDataNodeHandshakeV1Req.java new file mode 100644 index 00000000..e277e155 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferDataNodeHandshakeV1Req.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + +import java.io.IOException; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferHandshakeV1Req; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; + +public class PipeTransferDataNodeHandshakeV1Req extends PipeTransferHandshakeV1Req { + + private PipeTransferDataNodeHandshakeV1Req() { + // Empty constructor + } + + @Override + protected PipeRequestType getPlanType() { + return PipeRequestType.HANDSHAKE_DATANODE_V1; + } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferDataNodeHandshakeV1Req toTPipeTransferReq( + final String timestampPrecision) throws IOException { + return (PipeTransferDataNodeHandshakeV1Req) + new PipeTransferDataNodeHandshakeV1Req().convertToTPipeTransferReq(timestampPrecision); + } + + public static PipeTransferDataNodeHandshakeV1Req fromTPipeTransferReq( + final TPipeTransferReq transferReq) { + return (PipeTransferDataNodeHandshakeV1Req) + new PipeTransferDataNodeHandshakeV1Req().translateFromTPipeTransferReq(transferReq); + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public static byte[] toTPipeTransferBytes(final String timestampPrecision) throws IOException { + return new PipeTransferDataNodeHandshakeV1Req() + .convertToTransferHandshakeBytes(timestampPrecision); + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(final Object obj) { + return obj instanceof PipeTransferDataNodeHandshakeV1Req && super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferDataNodeHandshakeV2Req.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferDataNodeHandshakeV2Req.java new file mode 100644 index 00000000..207de7d2 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferDataNodeHandshakeV2Req.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + +import java.io.IOException; +import java.util.Map; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferHandshakeV2Req; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; + +public class PipeTransferDataNodeHandshakeV2Req extends PipeTransferHandshakeV2Req { + + private PipeTransferDataNodeHandshakeV2Req() { + // Empty constructor + } + + @Override + protected PipeRequestType getPlanType() { + return PipeRequestType.HANDSHAKE_DATANODE_V2; + } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferDataNodeHandshakeV2Req toTPipeTransferReq(Map params) + throws IOException { + return (PipeTransferDataNodeHandshakeV2Req) + new PipeTransferDataNodeHandshakeV2Req().convertToTPipeTransferReq(params); + } + + public static PipeTransferDataNodeHandshakeV2Req fromTPipeTransferReq( + TPipeTransferReq transferReq) { + return (PipeTransferDataNodeHandshakeV2Req) + new PipeTransferDataNodeHandshakeV2Req().translateFromTPipeTransferReq(transferReq); + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public static byte[] toTPipeTransferBytes(Map params) throws IOException { + return new PipeTransferDataNodeHandshakeV2Req().convertToTransferHandshakeBytes(params); + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(Object obj) { + return obj instanceof PipeTransferDataNodeHandshakeV2Req && super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferSchemaSnapshotPieceReq.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferSchemaSnapshotPieceReq.java new file mode 100644 index 00000000..beb41190 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferSchemaSnapshotPieceReq.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + +import java.io.IOException; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferFilePieceReq; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; + +public class PipeTransferSchemaSnapshotPieceReq extends PipeTransferFilePieceReq { + + private PipeTransferSchemaSnapshotPieceReq() { + // Empty constructor + } + + @Override + protected PipeRequestType getPlanType() { + return PipeRequestType.TRANSFER_SCHEMA_SNAPSHOT_PIECE; + } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferSchemaSnapshotPieceReq toTPipeTransferReq( + String fileName, long startWritingOffset, byte[] filePiece) throws IOException { + return (PipeTransferSchemaSnapshotPieceReq) + new PipeTransferSchemaSnapshotPieceReq() + .convertToTPipeTransferReq(fileName, startWritingOffset, filePiece); + } + + public static PipeTransferSchemaSnapshotPieceReq fromTPipeTransferReq( + TPipeTransferReq transferReq) { + return (PipeTransferSchemaSnapshotPieceReq) + new PipeTransferSchemaSnapshotPieceReq().translateFromTPipeTransferReq(transferReq); + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public static byte[] toTPipeTransferBytes( + String fileName, long startWritingOffset, byte[] filePiece) throws IOException { + return new PipeTransferSchemaSnapshotPieceReq() + .convertToTPipeTransferBytes(fileName, startWritingOffset, filePiece); + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(Object obj) { + return obj instanceof PipeTransferSchemaSnapshotPieceReq && super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferSchemaSnapshotSealReq.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferSchemaSnapshotSealReq.java new file mode 100644 index 00000000..b3b7d065 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferSchemaSnapshotSealReq.java @@ -0,0 +1,168 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.iotdb.collector.plugin.builtin.sink.constant.ColumnHeaderConstant; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferFileSealReqV2; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.session.IClientSession; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; + +public class PipeTransferSchemaSnapshotSealReq extends PipeTransferFileSealReqV2 { + + private PipeTransferSchemaSnapshotSealReq() { + // Empty constructor + } + + @Override + protected PipeRequestType getPlanType() { + return PipeRequestType.TRANSFER_SCHEMA_SNAPSHOT_SEAL; + } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferSchemaSnapshotSealReq toTPipeTransferReq( + final String treePattern, + final String tablePatternDatabase, + final String tablePatternTable, + final boolean isTreeCaptured, + final boolean isTableCaptured, + final String mTreeSnapshotName, + final long mTreeSnapshotLength, + final String tLogName, + final long tLogLength, + final String attributeSnapshotName, + final long attributeSnapshotLength, + final String databaseName, + final String typeString) + throws IOException { + final Map parameters = new HashMap<>(); + parameters.put(ColumnHeaderConstant.PATH_PATTERN, treePattern); + parameters.put(DATABASE_PATTERN, tablePatternDatabase); + parameters.put(ColumnHeaderConstant.TABLE_NAME, tablePatternTable); + if (isTreeCaptured) { + parameters.put(IClientSession.SqlDialect.TREE.toString(), ""); + } + if (isTableCaptured) { + parameters.put(IClientSession.SqlDialect.TABLE.toString(), ""); + } + parameters.put(ColumnHeaderConstant.DATABASE, databaseName); + parameters.put(ColumnHeaderConstant.TYPE, typeString); + + final List fileNameList; + final List fileLengthList; + + // Tree model sync + if (Objects.isNull(attributeSnapshotName)) { + fileNameList = + Objects.nonNull(tLogName) + ? Arrays.asList(mTreeSnapshotName, tLogName) + : Collections.singletonList(mTreeSnapshotName); + fileLengthList = + Objects.nonNull(tLogName) + ? Arrays.asList(mTreeSnapshotLength, tLogLength) + : Collections.singletonList(mTreeSnapshotLength); + } else { + fileNameList = Arrays.asList(mTreeSnapshotName, tLogName, attributeSnapshotName); + fileLengthList = Arrays.asList(mTreeSnapshotLength, tLogLength, attributeSnapshotLength); + } + + return (PipeTransferSchemaSnapshotSealReq) + new PipeTransferSchemaSnapshotSealReq() + .convertToTPipeTransferReq(fileNameList, fileLengthList, parameters); + } + + public static PipeTransferSchemaSnapshotSealReq fromTPipeTransferReq(final TPipeTransferReq req) { + return (PipeTransferSchemaSnapshotSealReq) + new PipeTransferSchemaSnapshotSealReq().translateFromTPipeTransferReq(req); + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public static byte[] toTPipeTransferBytes( + final String treePattern, + final String tablePatternDatabase, + final String tablePatternTable, + final boolean isTreeCaptured, + final boolean isTableCaptured, + final String mTreeSnapshotName, + final long mTreeSnapshotLength, + final String tLogName, + final long tLogLength, + final String attributeSnapshotName, + final long attributeSnapshotLength, + final String databaseName, + final String typeString) + throws IOException { + final Map parameters = new HashMap<>(); + parameters.put(ColumnHeaderConstant.PATH_PATTERN, treePattern); + parameters.put(DATABASE_PATTERN, tablePatternDatabase); + parameters.put(ColumnHeaderConstant.TABLE_NAME, tablePatternTable); + if (isTreeCaptured) { + parameters.put(TREE, ""); + } + if (isTableCaptured) { + parameters.put(TABLE, ""); + } + parameters.put(ColumnHeaderConstant.DATABASE, databaseName); + parameters.put(ColumnHeaderConstant.TYPE, typeString); + + final List fileNameList; + final List fileLengthList; + + // Tree model sync + if (Objects.isNull(attributeSnapshotName)) { + fileNameList = + Objects.nonNull(tLogName) + ? Arrays.asList(mTreeSnapshotName, tLogName) + : Collections.singletonList(mTreeSnapshotName); + fileLengthList = + Objects.nonNull(tLogName) + ? Arrays.asList(mTreeSnapshotLength, tLogLength) + : Collections.singletonList(mTreeSnapshotLength); + } else { + fileNameList = Arrays.asList(mTreeSnapshotName, tLogName, attributeSnapshotName); + fileLengthList = Arrays.asList(mTreeSnapshotLength, tLogLength, attributeSnapshotLength); + } + + return new PipeTransferSchemaSnapshotSealReq() + .convertToTPipeTransferSnapshotSealBytes(fileNameList, fileLengthList, parameters); + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(final Object obj) { + return obj instanceof PipeTransferSchemaSnapshotSealReq && super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBatchReq.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBatchReq.java new file mode 100644 index 00000000..cf154193 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBatchReq.java @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.IoTDBConnectorRequestVersion; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.TestOnly; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; + +public class PipeTransferTabletBatchReq extends TPipeTransferReq { + + private final transient List binaryReqs = new ArrayList<>(); + // private final transient List insertNodeReqs = new ArrayList<>(); + private final transient List tabletReqs = new ArrayList<>(); + + private PipeTransferTabletBatchReq() { + // Empty constructor + } + + // public Pair constructStatements() { + // final InsertRowsStatement insertRowsStatement = new InsertRowsStatement(); + // final InsertMultiTabletsStatement insertMultiTabletsStatement = + // new InsertMultiTabletsStatement(); + // + // final List insertRowStatementList = new ArrayList<>(); + // final List insertTabletStatementList = new ArrayList<>(); + // + // for (final PipeTransferTabletBinaryReq binaryReq : binaryReqs) { + // final InsertBaseStatement statement = binaryReq.constructStatement(); + // if (statement.isEmpty()) { + // continue; + // } + // if (statement instanceof InsertRowStatement) { + // insertRowStatementList.add((InsertRowStatement) statement); + // } else if (statement instanceof InsertTabletStatement) { + // insertTabletStatementList.add((InsertTabletStatement) statement); + // } else if (statement instanceof InsertRowsStatement) { + // insertRowStatementList.addAll( + // ((InsertRowsStatement) statement).getInsertRowStatementList()); + // } else { + // throw new UnsupportedOperationException( + // String.format( + // "unknown InsertBaseStatement %s constructed from PipeTransferTabletBinaryReq.", + // binaryReq)); + // } + // } + // + // for (final PipeTransferTabletInsertNodeReq insertNodeReq : insertNodeReqs) { + // final InsertBaseStatement statement = insertNodeReq.constructStatement(); + // if (statement.isEmpty()) { + // continue; + // } + // if (statement instanceof InsertRowStatement) { + // insertRowStatementList.add((InsertRowStatement) statement); + // } else if (statement instanceof InsertTabletStatement) { + // insertTabletStatementList.add((InsertTabletStatement) statement); + // } else if (statement instanceof InsertRowsStatement) { + // insertRowStatementList.addAll( + // ((InsertRowsStatement) statement).getInsertRowStatementList()); + // } else { + // throw new UnsupportedOperationException( + // String.format( + // "Unknown InsertBaseStatement %s constructed from PipeTransferTabletInsertNodeReq.", + // statement)); + // } + // } + // + // for (final PipeTransferTabletRawReq tabletReq : tabletReqs) { + // final InsertTabletStatement statement = tabletReq.constructStatement(); + // if (statement.isEmpty()) { + // continue; + // } + // insertTabletStatementList.add(statement); + // } + // + // insertRowsStatement.setInsertRowStatementList(insertRowStatementList); + // insertMultiTabletsStatement.setInsertTabletStatementList(insertTabletStatementList); + // return new Pair<>(insertRowsStatement, insertMultiTabletsStatement); + // } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferTabletBatchReq toTPipeTransferReq( + final List binaryBuffers, + final List insertNodeBuffers, + final List tabletBuffers) + throws IOException { + final PipeTransferTabletBatchReq batchReq = new PipeTransferTabletBatchReq(); + + // batchReq.binaryReqs, batchReq.insertNodeReqs, batchReq.tabletReqs are empty + // when this method is called from PipeTransferTabletBatchReqBuilder.toTPipeTransferReq() + + batchReq.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + batchReq.type = PipeRequestType.TRANSFER_TABLET_BATCH.getType(); + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(binaryBuffers.size(), outputStream); + for (final ByteBuffer binaryBuffer : binaryBuffers) { + ReadWriteIOUtils.write(binaryBuffer.limit(), outputStream); + outputStream.write(binaryBuffer.array(), 0, binaryBuffer.limit()); + } + + ReadWriteIOUtils.write(insertNodeBuffers.size(), outputStream); + for (final ByteBuffer insertNodeBuffer : insertNodeBuffers) { + outputStream.write(insertNodeBuffer.array(), 0, insertNodeBuffer.limit()); + } + + ReadWriteIOUtils.write(tabletBuffers.size(), outputStream); + for (final ByteBuffer tabletBuffer : tabletBuffers) { + outputStream.write(tabletBuffer.array(), 0, tabletBuffer.limit()); + } + + batchReq.body = + ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + + return batchReq; + } + + // public static PipeTransferTabletBatchReq fromTPipeTransferReq( + // final TPipeTransferReq transferReq) { + // final PipeTransferTabletBatchReq batchReq = new PipeTransferTabletBatchReq(); + // + // int size = ReadWriteIOUtils.readInt(transferReq.body); + // for (int i = 0; i < size; ++i) { + // final int length = ReadWriteIOUtils.readInt(transferReq.body); + // final byte[] body = new byte[length]; + // transferReq.body.get(body); + // batchReq.binaryReqs.add( + // PipeTransferTabletBinaryReq.toTPipeTransferReq(ByteBuffer.wrap(body))); + // } + // + // size = ReadWriteIOUtils.readInt(transferReq.body); + // for (int i = 0; i < size; ++i) { + // batchReq.insertNodeReqs.add( + // PipeTransferTabletInsertNodeReq.toTPipeTransferRawReq( + // (InsertNode) PlanFragment.deserializeHelper(transferReq.body, null))); + // } + // + // size = ReadWriteIOUtils.readInt(transferReq.body); + // for (int i = 0; i < size; ++i) { + // batchReq.tabletReqs.add( + // PipeTransferTabletRawReq.toTPipeTransferRawReq( + // Tablet.deserialize(transferReq.body), ReadWriteIOUtils.readBool(transferReq.body))); + // } + // + // batchReq.version = transferReq.version; + // batchReq.type = transferReq.type; + // batchReq.body = transferReq.body; + // + // return batchReq; + // } + + /////////////////////////////// TestOnly /////////////////////////////// + + @TestOnly + public List getBinaryReqs() { + return binaryReqs; + } + + // @TestOnly + // public List getInsertNodeReqs() { + // return insertNodeReqs; + // } + + @TestOnly + public List getTabletReqs() { + return tabletReqs; + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final PipeTransferTabletBatchReq that = (PipeTransferTabletBatchReq) obj; + return binaryReqs.equals(that.binaryReqs) + // && insertNodeReqs.equals(that.insertNodeReqs) + && tabletReqs.equals(that.tabletReqs) + && version == that.version + && type == that.type + && body.equals(that.body); + } + + @Override + public int hashCode() { + return Objects.hash(binaryReqs, /*insertNodeReqs,*/ tabletReqs, version, type, body); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2.java new file mode 100644 index 00000000..573c2290 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2.java @@ -0,0 +1,251 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.IoTDBConnectorRequestVersion; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.TestOnly; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; + +public class PipeTransferTabletBatchReqV2 extends TPipeTransferReq { + + private final transient List binaryReqs = new ArrayList<>(); + private final transient List insertNodeReqs = + new ArrayList<>(); + private final transient List tabletReqs = new ArrayList<>(); + + private PipeTransferTabletBatchReqV2() { + // Empty constructor + } + + // public List constructStatements() { + // final List statements = new ArrayList<>(); + // + // final InsertRowsStatement insertRowsStatement = new InsertRowsStatement(); + // final InsertMultiTabletsStatement insertMultiTabletsStatement = + // new InsertMultiTabletsStatement(); + // + // final List insertRowStatementList = new ArrayList<>(); + // final List insertTabletStatementList = new ArrayList<>(); + // + // for (final PipeTransferTabletBinaryReqV2 binaryReq : binaryReqs) { + // final InsertBaseStatement statement = binaryReq.constructStatement(); + // if (statement.isEmpty()) { + // continue; + // } + // if (statement.isWriteToTable()) { + // statements.add(statement); + // continue; + // } + // if (statement instanceof InsertRowStatement) { + // insertRowStatementList.add((InsertRowStatement) statement); + // } else if (statement instanceof InsertTabletStatement) { + // insertTabletStatementList.add((InsertTabletStatement) statement); + // } else if (statement instanceof InsertRowsStatement) { + // insertRowStatementList.addAll( + // ((InsertRowsStatement) statement).getInsertRowStatementList()); + // } else { + // throw new UnsupportedOperationException( + // String.format( + // "unknown InsertBaseStatement %s constructed from PipeTransferTabletBinaryReqV2.", + // binaryReq)); + // } + // } + // + // for (final PipeTransferTabletInsertNodeReqV2 insertNodeReq : insertNodeReqs) { + // final InsertBaseStatement statement = insertNodeReq.constructStatement(); + // if (statement.isEmpty()) { + // continue; + // } + // if (statement.isWriteToTable()) { + // statements.add(statement); + // continue; + // } + // if (statement instanceof InsertRowStatement) { + // insertRowStatementList.add((InsertRowStatement) statement); + // } else if (statement instanceof InsertTabletStatement) { + // insertTabletStatementList.add((InsertTabletStatement) statement); + // } else if (statement instanceof InsertRowsStatement) { + // insertRowStatementList.addAll( + // ((InsertRowsStatement) statement).getInsertRowStatementList()); + // } else { + // throw new UnsupportedOperationException( + // String.format( + // "Unknown InsertBaseStatement %s constructed from PipeTransferTabletInsertNodeReqV2.", + // statement)); + // } + // } + // + // for (final PipeTransferTabletRawReqV2 tabletReq : tabletReqs) { + // final InsertTabletStatement statement = tabletReq.constructStatement(); + // if (statement.isEmpty()) { + // continue; + // } + // if (statement.isWriteToTable()) { + // statements.add(statement); + // continue; + // } + // insertTabletStatementList.add(statement); + // } + // + // insertRowsStatement.setInsertRowStatementList(insertRowStatementList); + // insertMultiTabletsStatement.setInsertTabletStatementList(insertTabletStatementList); + // if (!insertRowsStatement.isEmpty()) { + // statements.add(insertRowsStatement); + // } + // if (!insertMultiTabletsStatement.isEmpty()) { + // statements.add(insertMultiTabletsStatement); + // } + // return statements; + // } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferTabletBatchReqV2 toTPipeTransferReq( + final List binaryBuffers, + final List insertNodeBuffers, + final List tabletBuffers, + final List binaryDataBases, + final List insertNodeDataBases, + final List tabletDataBases) + throws IOException { + final PipeTransferTabletBatchReqV2 batchReq = new PipeTransferTabletBatchReqV2(); + + batchReq.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + batchReq.type = PipeRequestType.TRANSFER_TABLET_BATCH_V2.getType(); + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(binaryBuffers.size(), outputStream); + for (int i = 0; i < binaryBuffers.size(); i++) { + final ByteBuffer binaryBuffer = binaryBuffers.get(i); + ReadWriteIOUtils.write(binaryBuffer.limit(), outputStream); + outputStream.write(binaryBuffer.array(), 0, binaryBuffer.limit()); + ReadWriteIOUtils.write(binaryDataBases.get(i), outputStream); + } + + ReadWriteIOUtils.write(insertNodeBuffers.size(), outputStream); + for (int i = 0; i < insertNodeBuffers.size(); i++) { + final ByteBuffer insertNodeBuffer = insertNodeBuffers.get(i); + outputStream.write(insertNodeBuffer.array(), 0, insertNodeBuffer.limit()); + ReadWriteIOUtils.write(insertNodeDataBases.get(i), outputStream); + } + + ReadWriteIOUtils.write(tabletBuffers.size(), outputStream); + for (int i = 0; i < tabletBuffers.size(); i++) { + final ByteBuffer tabletBuffer = tabletBuffers.get(i); + outputStream.write(tabletBuffer.array(), 0, tabletBuffer.limit()); + ReadWriteIOUtils.write(tabletDataBases.get(i), outputStream); + } + + batchReq.body = + ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + + return batchReq; + } + + // public static PipeTransferTabletBatchReqV2 fromTPipeTransferReq( + // final TPipeTransferReq transferReq) { + // final PipeTransferTabletBatchReqV2 batchReq = new PipeTransferTabletBatchReqV2(); + // + // int size = ReadWriteIOUtils.readInt(transferReq.body); + // for (int i = 0; i < size; ++i) { + // final int length = ReadWriteIOUtils.readInt(transferReq.body); + // final byte[] body = new byte[length]; + // transferReq.body.get(body); + // batchReq.binaryReqs.add( + // PipeTransferTabletBinaryReqV2.toTPipeTransferBinaryReq( + // ByteBuffer.wrap(body), ReadWriteIOUtils.readString(transferReq.body))); + // } + // + // size = ReadWriteIOUtils.readInt(transferReq.body); + // for (int i = 0; i < size; ++i) { + // batchReq.insertNodeReqs.add( + // PipeTransferTabletInsertNodeReqV2.toTabletInsertNodeReq( + // (InsertNode) PlanFragment.deserializeHelper(transferReq.body, null), + // ReadWriteIOUtils.readString(transferReq.body))); + // } + // + // size = ReadWriteIOUtils.readInt(transferReq.body); + // for (int i = 0; i < size; ++i) { + // batchReq.tabletReqs.add( + // PipeTransferTabletRawReqV2.toTPipeTransferRawReq( + // Tablet.deserialize(transferReq.body), + // ReadWriteIOUtils.readBool(transferReq.body), + // ReadWriteIOUtils.readString(transferReq.body))); + // } + // + // batchReq.version = transferReq.version; + // batchReq.type = transferReq.type; + // batchReq.body = transferReq.body; + // + // return batchReq; + // } + + /////////////////////////////// TestOnly /////////////////////////////// + + @TestOnly + public List getBinaryReqs() { + return binaryReqs; + } + + @TestOnly + public List getInsertNodeReqs() { + return insertNodeReqs; + } + + @TestOnly + public List getTabletReqs() { + return tabletReqs; + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final PipeTransferTabletBatchReqV2 that = (PipeTransferTabletBatchReqV2) obj; + return Objects.equals(binaryReqs, that.binaryReqs) + && Objects.equals(insertNodeReqs, that.insertNodeReqs) + && Objects.equals(tabletReqs, that.tabletReqs) + && version == that.version + && type == that.type + && Objects.equals(body, that.body); + } + + @Override + public int hashCode() { + return Objects.hash(binaryReqs, insertNodeReqs, tabletReqs, version, type, body); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBinaryReq.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBinaryReq.java new file mode 100644 index 00000000..e1d31020 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBinaryReq.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Objects; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.IoTDBConnectorRequestVersion; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.BytesUtils; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; + +public class PipeTransferTabletBinaryReq extends TPipeTransferReq { + + protected transient ByteBuffer byteBuffer; + + protected PipeTransferTabletBinaryReq() { + // Do nothing + } + + public ByteBuffer getByteBuffer() { + return byteBuffer; + } + + // public InsertBaseStatement constructStatement() { + // final InsertNode insertNode = parseByteBuffer(); + // + // if (!(insertNode instanceof InsertRowNode + // || insertNode instanceof InsertTabletNode + // || insertNode instanceof InsertRowsNode)) { + // throw new UnsupportedOperationException( + // String.format( + // "Unknown InsertNode type %s when constructing statement from insert node.", + // insertNode)); + // } + // + // return (InsertBaseStatement) + // IoTDBDataNodeReceiver.PLAN_TO_STATEMENT_VISITOR.process(insertNode, null); + // } + // + // protected InsertNode parseByteBuffer() { + // final PlanNode node = WALEntry.deserializeForConsensus(byteBuffer); + // return node instanceof InsertNode ? (InsertNode) node : null; + // } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferTabletBinaryReq toTPipeTransferReq(final ByteBuffer byteBuffer) { + final PipeTransferTabletBinaryReq req = new PipeTransferTabletBinaryReq(); + req.byteBuffer = byteBuffer; + + req.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + req.type = PipeRequestType.TRANSFER_TABLET_BINARY.getType(); + req.body = byteBuffer; + + return req; + } + + public static PipeTransferTabletBinaryReq fromTPipeTransferReq( + final TPipeTransferReq transferReq) { + final PipeTransferTabletBinaryReq binaryReq = new PipeTransferTabletBinaryReq(); + binaryReq.byteBuffer = transferReq.body; + + binaryReq.version = transferReq.version; + binaryReq.type = transferReq.type; + binaryReq.body = transferReq.body; + + return binaryReq; + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public static byte[] toTPipeTransferBytes(final ByteBuffer byteBuffer) throws IOException { + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(IoTDBConnectorRequestVersion.VERSION_1.getVersion(), outputStream); + ReadWriteIOUtils.write(PipeRequestType.TRANSFER_TABLET_BINARY.getType(), outputStream); + return BytesUtils.concatByteArray(byteArrayOutputStream.toByteArray(), byteBuffer.array()); + } + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final PipeTransferTabletBinaryReq that = (PipeTransferTabletBinaryReq) obj; + return byteBuffer.equals(that.byteBuffer) + && version == that.version + && type == that.type + && body.equals(that.body); + } + + @Override + public int hashCode() { + return Objects.hash(byteBuffer, version, type, body); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBinaryReqV2.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBinaryReqV2.java new file mode 100644 index 00000000..22fc43b6 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletBinaryReqV2.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Objects; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.IoTDBConnectorRequestVersion; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; + +public class PipeTransferTabletBinaryReqV2 extends PipeTransferTabletBinaryReq { + + private transient String dataBaseName; + + protected PipeTransferTabletBinaryReqV2() { + // Do nothing + } + + public String getDataBaseName() { + return dataBaseName; + } + + // public InsertBaseStatement constructStatement() { + // final InsertNode insertNode = parseByteBuffer(); + // + // if (!(insertNode instanceof InsertRowNode + // || insertNode instanceof InsertTabletNode + // || insertNode instanceof InsertRowsNode)) { + // throw new UnsupportedOperationException( + // String.format( + // "Unknown InsertNode type %s when constructing statement from insert node.", + // insertNode)); + // } + // + // final InsertBaseStatement statement = + // (InsertBaseStatement) + // IoTDBDataNodeReceiver.PLAN_TO_STATEMENT_VISITOR.process(insertNode, null); + // + // // Tree model + // if (Objects.isNull(dataBaseName)) { + // return statement; + // } + // + // // Table model + // statement.setWriteToTable(true); + // if (statement instanceof InsertRowsStatement) { + // List rowStatements = + // ((InsertRowsStatement) statement).getInsertRowStatementList(); + // if (rowStatements != null && !rowStatements.isEmpty()) { + // for (InsertRowStatement insertRowStatement : rowStatements) { + // insertRowStatement.setWriteToTable(true); + // insertRowStatement.setDatabaseName(dataBaseName); + // } + // } + // } + // statement.setDatabaseName(dataBaseName); + // return statement; + // } + + /////////////////////////////// Batch /////////////////////////////// + + public static PipeTransferTabletBinaryReqV2 toTPipeTransferBinaryReq( + final ByteBuffer byteBuffer, final String dataBaseName) { + final PipeTransferTabletBinaryReqV2 req = new PipeTransferTabletBinaryReqV2(); + + req.byteBuffer = byteBuffer; + req.dataBaseName = dataBaseName; + req.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + req.type = PipeRequestType.TRANSFER_TABLET_BINARY_V2.getType(); + + return req; + } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferTabletBinaryReqV2 toTPipeTransferReq( + final ByteBuffer byteBuffer, final String dataBaseName) throws IOException { + final PipeTransferTabletBinaryReqV2 req = new PipeTransferTabletBinaryReqV2(); + req.byteBuffer = byteBuffer; + req.dataBaseName = dataBaseName; + + req.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + req.type = PipeRequestType.TRANSFER_TABLET_BINARY_V2.getType(); + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(byteBuffer.limit(), outputStream); + outputStream.write(byteBuffer.array(), 0, byteBuffer.limit()); + ReadWriteIOUtils.write(dataBaseName, outputStream); + req.body = ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + + return req; + } + + public static PipeTransferTabletBinaryReqV2 fromTPipeTransferReq( + final org.apache.iotdb.service.rpc.thrift.TPipeTransferReq transferReq) { + final PipeTransferTabletBinaryReqV2 binaryReq = new PipeTransferTabletBinaryReqV2(); + + final int length = ReadWriteIOUtils.readInt(transferReq.body); + final byte[] body = new byte[length]; + transferReq.body.get(body); + binaryReq.byteBuffer = ByteBuffer.wrap(body); + binaryReq.dataBaseName = ReadWriteIOUtils.readString(transferReq.body); + + binaryReq.version = transferReq.version; + binaryReq.type = transferReq.type; + binaryReq.body = transferReq.body; + + return binaryReq; + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public static byte[] toTPipeTransferBytes(final ByteBuffer byteBuffer, final String dataBaseName) + throws IOException { + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(IoTDBConnectorRequestVersion.VERSION_1.getVersion(), outputStream); + ReadWriteIOUtils.write(PipeRequestType.TRANSFER_TABLET_BINARY_V2.getType(), outputStream); + ReadWriteIOUtils.write(byteBuffer.limit(), outputStream); + outputStream.write(byteBuffer.array(), 0, byteBuffer.limit()); + ReadWriteIOUtils.write(dataBaseName, outputStream); + return byteArrayOutputStream.toByteArray(); + } + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + final PipeTransferTabletBinaryReqV2 that = (PipeTransferTabletBinaryReqV2) o; + return Objects.equals(dataBaseName, that.dataBaseName); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), dataBaseName); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletRawReq.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletRawReq.java new file mode 100644 index 00000000..226e28b7 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletRawReq.java @@ -0,0 +1,166 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Objects; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.IoTDBConnectorRequestVersion; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; +import org.apache.tsfile.write.record.Tablet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PipeTransferTabletRawReq extends TPipeTransferReq { + + private static final Logger LOGGER = LoggerFactory.getLogger(PipeTransferTabletRawReq.class); + + protected transient Tablet tablet; + protected transient boolean isAligned; + + public Tablet getTablet() { + return tablet; + } + + public boolean getIsAligned() { + return isAligned; + } + + // public InsertTabletStatement constructStatement() { + // new PipeTreeModelTabletEventSorter(tablet).deduplicateAndSortTimestampsIfNecessary(); + // + // try { + // if (isTabletEmpty(tablet)) { + // // Empty statement, will be filtered after construction + // return new InsertTabletStatement(); + // } + // + // final TSInsertTabletReq request = new TSInsertTabletReq(); + // + // for (final IMeasurementSchema measurementSchema : tablet.getSchemas()) { + // request.addToMeasurements(measurementSchema.getMeasurementName()); + // request.addToTypes(measurementSchema.getType().ordinal()); + // } + // + // request.setPrefixPath(tablet.getDeviceId()); + // request.setIsAligned(isAligned); + // request.setTimestamps(SessionUtils.getTimeBuffer(tablet)); + // request.setValues(SessionUtils.getValueBuffer(tablet)); + // request.setSize(tablet.getRowSize()); + // request.setMeasurements( + // PathUtils.checkIsLegalSingleMeasurementsAndUpdate(request.getMeasurements())); + // + // return StatementGenerator.createStatement(request); + // } catch (final MetadataException e) { + // LOGGER.warn("Generate Statement from tablet {} error.", tablet, e); + // return null; + // } + // } + + /////////////////////////////// WriteBack & Batch /////////////////////////////// + + public static PipeTransferTabletRawReq toTPipeTransferRawReq( + final Tablet tablet, final boolean isAligned) { + final PipeTransferTabletRawReq tabletReq = new PipeTransferTabletRawReq(); + + tabletReq.tablet = tablet; + tabletReq.isAligned = isAligned; + + return tabletReq; + } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferTabletRawReq toTPipeTransferReq( + final Tablet tablet, final boolean isAligned) throws IOException { + final PipeTransferTabletRawReq tabletReq = new PipeTransferTabletRawReq(); + + tabletReq.tablet = tablet; + tabletReq.isAligned = isAligned; + + tabletReq.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + tabletReq.type = PipeRequestType.TRANSFER_TABLET_RAW.getType(); + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + tablet.serialize(outputStream); + ReadWriteIOUtils.write(isAligned, outputStream); + tabletReq.body = + ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + + return tabletReq; + } + + public static PipeTransferTabletRawReq fromTPipeTransferReq(final TPipeTransferReq transferReq) { + final PipeTransferTabletRawReq tabletReq = new PipeTransferTabletRawReq(); + + tabletReq.tablet = Tablet.deserialize(transferReq.body); + tabletReq.isAligned = ReadWriteIOUtils.readBool(transferReq.body); + + tabletReq.version = transferReq.version; + tabletReq.type = transferReq.type; + tabletReq.body = transferReq.body; + + return tabletReq; + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public static byte[] toTPipeTransferBytes(final Tablet tablet, final boolean isAligned) + throws IOException { + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(IoTDBConnectorRequestVersion.VERSION_1.getVersion(), outputStream); + ReadWriteIOUtils.write(PipeRequestType.TRANSFER_TABLET_RAW.getType(), outputStream); + tablet.serialize(outputStream); + ReadWriteIOUtils.write(isAligned, outputStream); + return byteArrayOutputStream.toByteArray(); + } + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final PipeTransferTabletRawReq that = (PipeTransferTabletRawReq) obj; + return Objects.equals(tablet, that.tablet) + && isAligned == that.isAligned + && version == that.version + && type == that.type + && Objects.equals(body, that.body); + } + + @Override + public int hashCode() { + return Objects.hash(tablet, isAligned, version, type, body); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletRawReqV2.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletRawReqV2.java new file mode 100644 index 00000000..7e5eb180 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTabletRawReqV2.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Objects; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.IoTDBConnectorRequestVersion; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; +import org.apache.tsfile.write.record.Tablet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PipeTransferTabletRawReqV2 extends PipeTransferTabletRawReq { + + private static final Logger LOGGER = LoggerFactory.getLogger(PipeTransferTabletRawReqV2.class); + + protected transient String dataBaseName; + + public String getDataBaseName() { + return dataBaseName; + } + + // @Override + // public InsertTabletStatement constructStatement() { + // if (Objects.isNull(dataBaseName)) { + // new PipeTreeModelTabletEventSorter(tablet).deduplicateAndSortTimestampsIfNecessary(); + // } else { + // new PipeTableModelTabletEventSorter(tablet).sortAndDeduplicateByTimestampIfNecessary(); + // } + // + // try { + // if (isTabletEmpty(tablet)) { + // // Empty statement, will be filtered after construction + // return new InsertTabletStatement(); + // } + // + // final TSInsertTabletReq request = new TSInsertTabletReq(); + // + // for (final IMeasurementSchema measurementSchema : tablet.getSchemas()) { + // request.addToMeasurements(measurementSchema.getMeasurementName()); + // request.addToTypes(measurementSchema.getType().ordinal()); + // } + // + // request.setPrefixPath(tablet.getDeviceId()); + // request.setIsAligned(isAligned); + // request.setTimestamps(SessionUtils.getTimeBuffer(tablet)); + // request.setValues(SessionUtils.getValueBuffer(tablet)); + // request.setSize(tablet.getRowSize()); + // + // // Tree model + // if (Objects.isNull(dataBaseName)) { + // request.setMeasurements( + // PathUtils.checkIsLegalSingleMeasurementsAndUpdate(request.getMeasurements())); + // return StatementGenerator.createStatement(request); + // } + // + // // Table model + // request.setWriteToTable(true); + // request.columnCategories = + // tablet.getColumnTypes().stream() + // .map(t -> (byte) t.ordinal()) + // .collect(Collectors.toList()); + // final InsertTabletStatement statement = StatementGenerator.createStatement(request); + // statement.setDatabaseName(dataBaseName); + // return statement; + // } catch (final MetadataException e) { + // LOGGER.warn("Generate Statement from tablet {} error.", tablet, e); + // return null; + // } + // } + + /////////////////////////////// WriteBack & Batch /////////////////////////////// + + public static PipeTransferTabletRawReqV2 toTPipeTransferRawReq( + final Tablet tablet, final boolean isAligned, final String dataBaseName) { + final PipeTransferTabletRawReqV2 tabletReq = new PipeTransferTabletRawReqV2(); + + tabletReq.tablet = tablet; + tabletReq.isAligned = isAligned; + tabletReq.dataBaseName = dataBaseName; + tabletReq.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + tabletReq.type = PipeRequestType.TRANSFER_TABLET_RAW_V2.getType(); + + return tabletReq; + } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferTabletRawReqV2 toTPipeTransferReq( + final Tablet tablet, final boolean isAligned, final String dataBaseName) throws IOException { + final PipeTransferTabletRawReqV2 tabletReq = new PipeTransferTabletRawReqV2(); + + tabletReq.tablet = tablet; + tabletReq.isAligned = isAligned; + tabletReq.dataBaseName = dataBaseName; + + tabletReq.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + tabletReq.type = PipeRequestType.TRANSFER_TABLET_RAW_V2.getType(); + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + tablet.serialize(outputStream); + ReadWriteIOUtils.write(isAligned, outputStream); + ReadWriteIOUtils.write(dataBaseName, outputStream); + tabletReq.body = + ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + + return tabletReq; + } + + public static PipeTransferTabletRawReqV2 fromTPipeTransferReq( + final TPipeTransferReq transferReq) { + final PipeTransferTabletRawReqV2 tabletReq = new PipeTransferTabletRawReqV2(); + + tabletReq.tablet = Tablet.deserialize(transferReq.body); + tabletReq.isAligned = ReadWriteIOUtils.readBool(transferReq.body); + tabletReq.dataBaseName = ReadWriteIOUtils.readString(transferReq.body); + + tabletReq.version = transferReq.version; + tabletReq.type = transferReq.type; + tabletReq.body = transferReq.body; + + return tabletReq; + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public static byte[] toTPipeTransferBytes( + final Tablet tablet, final boolean isAligned, final String dataBaseName) throws IOException { + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(IoTDBConnectorRequestVersion.VERSION_1.getVersion(), outputStream); + ReadWriteIOUtils.write(PipeRequestType.TRANSFER_TABLET_RAW_V2.getType(), outputStream); + tablet.serialize(outputStream); + ReadWriteIOUtils.write(isAligned, outputStream); + ReadWriteIOUtils.write(dataBaseName, outputStream); + return byteArrayOutputStream.toByteArray(); + } + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + final PipeTransferTabletRawReqV2 that = (PipeTransferTabletRawReqV2) o; + return Objects.equals(dataBaseName, that.dataBaseName); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), dataBaseName); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFilePieceReq.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFilePieceReq.java new file mode 100644 index 00000000..6cf4eac6 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFilePieceReq.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + +import java.io.IOException; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferFilePieceReq; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; + +public class PipeTransferTsFilePieceReq extends PipeTransferFilePieceReq { + + private PipeTransferTsFilePieceReq() { + // Empty constructor + } + + @Override + protected PipeRequestType getPlanType() { + return PipeRequestType.TRANSFER_TS_FILE_PIECE; + } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferTsFilePieceReq toTPipeTransferReq( + String fileName, long startWritingOffset, byte[] filePiece) throws IOException { + return (PipeTransferTsFilePieceReq) + new PipeTransferTsFilePieceReq() + .convertToTPipeTransferReq(fileName, startWritingOffset, filePiece); + } + + public static PipeTransferTsFilePieceReq fromTPipeTransferReq(TPipeTransferReq transferReq) { + return (PipeTransferTsFilePieceReq) + new PipeTransferTsFilePieceReq().translateFromTPipeTransferReq(transferReq); + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public static byte[] toTPipeTransferBytes( + String fileName, long startWritingOffset, byte[] filePiece) throws IOException { + return new PipeTransferTsFilePieceReq() + .convertToTPipeTransferBytes(fileName, startWritingOffset, filePiece); + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(Object obj) { + return obj instanceof PipeTransferTsFilePieceReq && super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFilePieceWithModReq.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFilePieceWithModReq.java new file mode 100644 index 00000000..16e87e91 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFilePieceWithModReq.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + +import java.io.IOException; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferFilePieceReq; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; + +public class PipeTransferTsFilePieceWithModReq extends PipeTransferFilePieceReq { + + private PipeTransferTsFilePieceWithModReq() { + // Empty constructor + } + + @Override + protected PipeRequestType getPlanType() { + return PipeRequestType.TRANSFER_TS_FILE_PIECE_WITH_MOD; + } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferTsFilePieceWithModReq toTPipeTransferReq( + String fileName, long startWritingOffset, byte[] filePiece) throws IOException { + return (PipeTransferTsFilePieceWithModReq) + new PipeTransferTsFilePieceWithModReq() + .convertToTPipeTransferReq(fileName, startWritingOffset, filePiece); + } + + public static PipeTransferTsFilePieceWithModReq fromTPipeTransferReq( + TPipeTransferReq transferReq) { + return (PipeTransferTsFilePieceWithModReq) + new PipeTransferTsFilePieceWithModReq().translateFromTPipeTransferReq(transferReq); + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public static byte[] toTPipeTransferBytes( + String fileName, long startWritingOffset, byte[] filePiece) throws IOException { + return new PipeTransferTsFilePieceWithModReq() + .convertToTPipeTransferBytes(fileName, startWritingOffset, filePiece); + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(Object obj) { + return obj instanceof PipeTransferTsFilePieceWithModReq && super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFileSealReq.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFileSealReq.java new file mode 100644 index 00000000..a53039e9 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFileSealReq.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + +import java.io.IOException; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferFileSealReqV1; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; + +public class PipeTransferTsFileSealReq extends PipeTransferFileSealReqV1 { + + private PipeTransferTsFileSealReq() { + // Empty constructor + } + + @Override + protected PipeRequestType getPlanType() { + return PipeRequestType.TRANSFER_TS_FILE_SEAL; + } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferTsFileSealReq toTPipeTransferReq(String fileName, long fileLength) + throws IOException { + return (PipeTransferTsFileSealReq) + new PipeTransferTsFileSealReq().convertToTPipeTransferReq(fileName, fileLength); + } + + public static PipeTransferTsFileSealReq fromTPipeTransferReq(TPipeTransferReq req) { + return (PipeTransferTsFileSealReq) + new PipeTransferTsFileSealReq().translateFromTPipeTransferReq(req); + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public static byte[] toTPipeTransferBytes(String fileName, long fileLength) throws IOException { + return new PipeTransferTsFileSealReq() + .convertToTPipeTransferSnapshotSealBytes(fileName, fileLength); + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(Object obj) { + return obj instanceof PipeTransferTsFileSealReq && super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFileSealWithModReq.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFileSealWithModReq.java new file mode 100644 index 00000000..3bda972a --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/request/PipeTransferTsFileSealWithModReq.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeRequestType; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferFileSealReqV2; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; + +public class PipeTransferTsFileSealWithModReq extends PipeTransferFileSealReqV2 { + + private PipeTransferTsFileSealWithModReq() { + // Empty constructor + } + + @Override + protected PipeRequestType getPlanType() { + return PipeRequestType.TRANSFER_TS_FILE_SEAL_WITH_MOD; + } + + protected static final String DATABASE_NAME_KEY_PREFIX = "DATABASE_NAME_"; + + public String getDatabaseNameByTsFileName() { + return parameters == null + ? null + : parameters.get(generateDatabaseNameWithFileNameKey(fileNames.get(fileNames.size() - 1))); + } + + protected static String generateDatabaseNameWithFileNameKey(final String fileName) { + return DATABASE_NAME_KEY_PREFIX + fileName; + } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferTsFileSealWithModReq toTPipeTransferReq( + final String modFileName, + final long modFileLength, + final String tsFileName, + final long tsFileLength) + throws IOException { + return toTPipeTransferReq(modFileName, modFileLength, tsFileName, tsFileLength, null); + } + + public static PipeTransferTsFileSealWithModReq toTPipeTransferReq( + final String modFileName, + final long modFileLength, + final String tsFileName, + final long tsFileLength, + final String dataBaseName) + throws IOException { + return (PipeTransferTsFileSealWithModReq) + new PipeTransferTsFileSealWithModReq() + .convertToTPipeTransferReq( + Arrays.asList(modFileName, tsFileName), + Arrays.asList(modFileLength, tsFileLength), + Collections.singletonMap( + generateDatabaseNameWithFileNameKey(tsFileName), dataBaseName)); + } + + public static PipeTransferTsFileSealWithModReq toTPipeTransferReq( + final String tsFileName, final long tsFileLength, final String dataBaseName) + throws IOException { + return (PipeTransferTsFileSealWithModReq) + new PipeTransferTsFileSealWithModReq() + .convertToTPipeTransferReq( + Collections.singletonList(tsFileName), + Collections.singletonList(tsFileLength), + Collections.singletonMap( + generateDatabaseNameWithFileNameKey(tsFileName), dataBaseName)); + } + + public static PipeTransferTsFileSealWithModReq fromTPipeTransferReq(final TPipeTransferReq req) { + return (PipeTransferTsFileSealWithModReq) + new PipeTransferTsFileSealWithModReq().translateFromTPipeTransferReq(req); + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public static byte[] toTPipeTransferBytes( + final String modFileName, + final long modFileLength, + final String tsFileName, + final long tsFileLength) + throws IOException { + return new PipeTransferTsFileSealWithModReq() + .convertToTPipeTransferSnapshotSealBytes( + Arrays.asList(modFileName, tsFileName), + Arrays.asList(modFileLength, tsFileLength), + new HashMap<>()); + } + + public static byte[] toTPipeTransferBytes( + final String modFileName, + final long modFileLength, + final String tsFileName, + final long tsFileLength, + final String dataBaseName) + throws IOException { + return new PipeTransferTsFileSealWithModReq() + .convertToTPipeTransferSnapshotSealBytes( + Arrays.asList(modFileName, tsFileName), + Arrays.asList(modFileLength, tsFileLength), + Collections.singletonMap( + generateDatabaseNameWithFileNameKey(tsFileName), dataBaseName)); + } + + public static byte[] toTPipeTransferBytes( + final String tsFileName, final long tsFileLength, final String dataBaseName) + throws IOException { + return new PipeTransferTsFileSealWithModReq() + .convertToTPipeTransferSnapshotSealBytes( + Collections.singletonList(tsFileName), + Collections.singletonList(tsFileLength), + Collections.singletonMap( + generateDatabaseNameWithFileNameKey(tsFileName), dataBaseName)); + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(final Object obj) { + return obj instanceof PipeTransferTsFileSealWithModReq && super.equals(obj); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/common/PipeTransferHandshakeConstant.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/common/PipeTransferHandshakeConstant.java new file mode 100644 index 00000000..f2a7b3ab --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/common/PipeTransferHandshakeConstant.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.common; + +public class PipeTransferHandshakeConstant { + + public static final String HANDSHAKE_KEY_TIME_PRECISION = "timestampPrecision"; + public static final String HANDSHAKE_KEY_CLUSTER_ID = "clusterID"; + public static final String HANDSHAKE_KEY_CONVERT_ON_TYPE_MISMATCH = "convertOnTypeMismatch"; + public static final String HANDSHAKE_KEY_LOAD_TSFILE_STRATEGY = "loadTsFileStrategy"; + public static final String HANDSHAKE_KEY_USERNAME = "username"; + public static final String HANDSHAKE_KEY_PASSWORD = "password"; + public static final String HANDSHAKE_KEY_VALIDATE_TSFILE = "validateTsFile"; + public static final String HANDSHAKE_KEY_MARK_AS_PIPE_REQUEST = "markAsPipeRequest"; + + private PipeTransferHandshakeConstant() { + // Utility class + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/common/PipeTransferSliceReqHandler.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/common/PipeTransferSliceReqHandler.java new file mode 100644 index 00000000..4491a99d --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/common/PipeTransferSliceReqHandler.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.common; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.IoTDBConnectorRequestVersion; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferSliceReq; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PipeTransferSliceReqHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(PipeTransferSliceReqHandler.class); + + private int orderId = -1; + + private short originReqType = -1; + private int originBodySize = -1; + + private int sliceCount = -1; + private final List sliceBodies = new ArrayList<>(); + + public boolean receiveSlice(final PipeTransferSliceReq req) { + if (orderId == -1 + || originReqType == -1 + || originBodySize == -1 + || sliceCount == -1 + || sliceBodies.isEmpty()) { + if (orderId == -1 + && originReqType == -1 + && originBodySize == -1 + && sliceCount == -1 + && sliceBodies.isEmpty()) { + orderId = req.getOrderId(); + originReqType = req.getOriginReqType(); + originBodySize = req.getOriginBodySize(); + sliceCount = req.getSliceCount(); + } else { + LOGGER.warn( + "Invalid state: orderId={}, originReqType={}, originBodySize={}, sliceCount={}, sliceBodies.size={}", + orderId, + originReqType, + originBodySize, + sliceCount, + sliceBodies.size()); + clear(); + return false; + } + } + + if (orderId != req.getOrderId()) { + LOGGER.warn("Order ID mismatch: expected {}, actual {}", orderId, req.getOrderId()); + clear(); + return false; + } + if (originReqType != req.getOriginReqType()) { + LOGGER.warn( + "Origin request type mismatch: expected {}, actual {}", + originReqType, + req.getOriginReqType()); + clear(); + return false; + } + if (originBodySize != req.getOriginBodySize()) { + LOGGER.warn( + "Origin body size mismatch: expected {}, actual {}", + originBodySize, + req.getOriginBodySize()); + clear(); + return false; + } + if (sliceCount != req.getSliceCount()) { + LOGGER.warn("Slice count mismatch: expected {}, actual {}", sliceCount, req.getSliceCount()); + clear(); + return false; + } + if (sliceBodies.size() != req.getSliceIndex()) { + LOGGER.warn( + "Invalid slice index: expected {}, actual {}", sliceBodies.size(), req.getSliceIndex()); + clear(); + return false; + } + + sliceBodies.add(req.getSliceBody()); + return true; + } + + public Optional makeReqIfComplete() { + if (sliceBodies.size() != sliceCount) { + return Optional.empty(); + } + + final TPipeTransferReq req = new TPipeTransferReq(); + req.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + req.type = originReqType; + + final ByteBuffer body = ByteBuffer.allocate(originBodySize); + sliceBodies.forEach(body::put); + body.flip(); + req.body = body; + + return Optional.of(req); + } + + public void clear() { + orderId = -1; + originReqType = -1; + originBodySize = -1; + sliceCount = -1; + sliceBodies.clear(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/IoTDBConnectorRequestVersion.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/IoTDBConnectorRequestVersion.java new file mode 100644 index 00000000..54f10f23 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/IoTDBConnectorRequestVersion.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request; + +public enum IoTDBConnectorRequestVersion { + VERSION_1((byte) 1), + VERSION_2((byte) 2), + ; + + private final byte version; + + IoTDBConnectorRequestVersion(byte type) { + this.version = type; + } + + public byte getVersion() { + return version; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeRequestType.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeRequestType.java new file mode 100644 index 00000000..aab7926a --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeRequestType.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public enum PipeRequestType { + + // Handshake + HANDSHAKE_CONFIGNODE_V1((short) 0), + HANDSHAKE_DATANODE_V1((short) 1), + HANDSHAKE_CONFIGNODE_V2((short) 50), + HANDSHAKE_DATANODE_V2((short) 51), + + // Data region + TRANSFER_TABLET_INSERT_NODE((short) 2), + TRANSFER_TABLET_RAW((short) 3), + TRANSFER_TS_FILE_PIECE((short) 4), + TRANSFER_TS_FILE_SEAL((short) 5), + TRANSFER_TABLET_BATCH((short) 6), + TRANSFER_TABLET_BINARY((short) 7), + TRANSFER_TS_FILE_PIECE_WITH_MOD((short) 8), + TRANSFER_TS_FILE_SEAL_WITH_MOD((short) 9), + + TRANSFER_TABLET_INSERT_NODE_V2((short) 10), + TRANSFER_TABLET_RAW_V2((short) 11), + TRANSFER_TABLET_BINARY_V2((short) 12), + TRANSFER_TABLET_BATCH_V2((short) 13), + + // Schema region / Delete Data + TRANSFER_PLAN_NODE((short) 100), + TRANSFER_SCHEMA_SNAPSHOT_PIECE((short) 101), + TRANSFER_SCHEMA_SNAPSHOT_SEAL((short) 102), + + // Config region + TRANSFER_CONFIG_PLAN((short) 200), + TRANSFER_CONFIG_SNAPSHOT_PIECE((short) 201), + TRANSFER_CONFIG_SNAPSHOT_SEAL((short) 202), + + // RPC Compression + TRANSFER_COMPRESSED((short) 300), + + // Fallback Handling + TRANSFER_SLICE((short) 400), + ; + + private final short type; + + PipeRequestType(short type) { + this.type = type; + } + + public short getType() { + return type; + } + + private static final Map TYPE_MAP = + Arrays.stream(PipeRequestType.values()) + .collect( + HashMap::new, + (typeMap, pipeRequestType) -> typeMap.put(pipeRequestType.getType(), pipeRequestType), + HashMap::putAll); + + public static boolean isValidatedRequestType(short type) { + return TYPE_MAP.containsKey(type); + } + + public static PipeRequestType valueOf(short type) { + return TYPE_MAP.get(type); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferCompressedReq.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferCompressedReq.java new file mode 100644 index 00000000..975e2f7f --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferCompressedReq.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.iotdb.collector.plugin.builtin.sink.compressor.PipeCompressor; +import org.apache.iotdb.collector.plugin.builtin.sink.compressor.PipeCompressorFactory; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.BytesUtils; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; + +public class PipeTransferCompressedReq extends TPipeTransferReq { + + /** Generate a compressed req with provided compressors. */ + public static TPipeTransferReq toTPipeTransferReq( + final TPipeTransferReq originalReq, final List compressors) + throws IOException { + // The generated PipeTransferCompressedReq consists of: + // version + // type: TRANSFER_COMPRESSED + // body: + // (byte) count of compressors (n) + // (n*3 bytes) for each compressor: + // (byte) compressor type + // (int) length of uncompressed bytes + // compressed req: + // (byte) version + // (2 bytes) type + // (bytes) body + final PipeTransferCompressedReq compressedReq = new PipeTransferCompressedReq(); + compressedReq.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + compressedReq.type = PipeRequestType.TRANSFER_COMPRESSED.getType(); + + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + byte[] body = + BytesUtils.concatByteArrayList( + Arrays.asList( + new byte[] {originalReq.version}, + BytesUtils.shortToBytes(originalReq.type), + originalReq.getBody())); + + ReadWriteIOUtils.write((byte) compressors.size(), outputStream); + for (final PipeCompressor compressor : compressors) { + ReadWriteIOUtils.write(compressor.serialize(), outputStream); + ReadWriteIOUtils.write(body.length, outputStream); + body = compressor.compress(body); + } + outputStream.write(body); + + compressedReq.body = + ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + return compressedReq; + } + + /** Get the original req from a compressed req. */ + public static TPipeTransferReq fromTPipeTransferReq(final TPipeTransferReq transferReq) + throws IOException { + final ByteBuffer compressedBuffer = transferReq.body; + + final List compressors = new ArrayList<>(); + final List uncompressedLengths = new ArrayList<>(); + final int compressorsSize = ReadWriteIOUtils.readByte(compressedBuffer); + for (int i = 0; i < compressorsSize; ++i) { + compressors.add( + PipeCompressorFactory.getCompressor(ReadWriteIOUtils.readByte(compressedBuffer))); + uncompressedLengths.add(ReadWriteIOUtils.readInt(compressedBuffer)); + } + + byte[] body = new byte[compressedBuffer.remaining()]; + compressedBuffer.get(body); + + for (int i = compressors.size() - 1; i >= 0; --i) { + body = compressors.get(i).decompress(body, uncompressedLengths.get(i)); + } + + final ByteBuffer decompressedBuffer = ByteBuffer.wrap(body); + + final TPipeTransferReq decompressedReq = new TPipeTransferReq(); + decompressedReq.version = ReadWriteIOUtils.readByte(decompressedBuffer); + decompressedReq.type = ReadWriteIOUtils.readShort(decompressedBuffer); + decompressedReq.body = decompressedBuffer.slice(); + + return decompressedReq; + } + + /** + * For air-gap connectors. Generate the bytes of a compressed req from the bytes of original req. + */ + public static byte[] toTPipeTransferReqBytes( + final byte[] rawReqInBytes, final List compressors) throws IOException { + // The generated bytes consists of: + // (byte) version + // (2 bytes) type: TRANSFER_COMPRESSED + // (byte) count of compressors (n) + // (n*3 bytes) for each compressor: + // (byte) compressor type + // (int) length of uncompressed bytes + // compressed req: + // (byte) version + // (2 bytes) type + // (bytes) body + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + byte[] body = rawReqInBytes; + + ReadWriteIOUtils.write(IoTDBConnectorRequestVersion.VERSION_1.getVersion(), outputStream); + ReadWriteIOUtils.write(PipeRequestType.TRANSFER_COMPRESSED.getType(), outputStream); + ReadWriteIOUtils.write((byte) compressors.size(), outputStream); + for (final PipeCompressor compressor : compressors) { + ReadWriteIOUtils.write(compressor.serialize(), outputStream); + ReadWriteIOUtils.write(body.length, outputStream); + body = compressor.compress(body); + } + outputStream.write(body); + + return byteArrayOutputStream.toByteArray(); + } + } + + private PipeTransferCompressedReq() { + // Empty constructor + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferFilePieceReq.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferFilePieceReq.java new file mode 100644 index 00000000..9b8cb161 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferFilePieceReq.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Objects; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.Binary; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; + +public abstract class PipeTransferFilePieceReq extends TPipeTransferReq { + + private transient String fileName; + private transient long startWritingOffset; + private transient byte[] filePiece; + + public final String getFileName() { + return fileName; + } + + public final long getStartWritingOffset() { + return startWritingOffset; + } + + public final byte[] getFilePiece() { + return filePiece; + } + + protected abstract PipeRequestType getPlanType(); + + /////////////////////////////// Thrift /////////////////////////////// + + protected final PipeTransferFilePieceReq convertToTPipeTransferReq( + String snapshotName, long startWritingOffset, byte[] snapshotPiece) throws IOException { + + this.fileName = snapshotName; + this.startWritingOffset = startWritingOffset; + this.filePiece = snapshotPiece; + + this.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + this.type = getPlanType().getType(); + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(snapshotName, outputStream); + ReadWriteIOUtils.write(startWritingOffset, outputStream); + ReadWriteIOUtils.write(new Binary(snapshotPiece), outputStream); + body = ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + + return this; + } + + protected final PipeTransferFilePieceReq translateFromTPipeTransferReq( + TPipeTransferReq transferReq) { + + fileName = ReadWriteIOUtils.readString(transferReq.body); + startWritingOffset = ReadWriteIOUtils.readLong(transferReq.body); + filePiece = ReadWriteIOUtils.readBinary(transferReq.body).getValues(); + + version = transferReq.version; + type = transferReq.type; + body = transferReq.body; + + return this; + } + + /////////////////////////////// Air Gap /////////////////////////////// + + protected final byte[] convertToTPipeTransferBytes( + String snapshotName, long startWritingOffset, byte[] snapshotPiece) throws IOException { + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(IoTDBConnectorRequestVersion.VERSION_1.getVersion(), outputStream); + ReadWriteIOUtils.write(getPlanType().getType(), outputStream); + ReadWriteIOUtils.write(snapshotName, outputStream); + ReadWriteIOUtils.write(startWritingOffset, outputStream); + ReadWriteIOUtils.write(new Binary(snapshotPiece), outputStream); + return byteArrayOutputStream.toByteArray(); + } + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + PipeTransferFilePieceReq that = (PipeTransferFilePieceReq) obj; + return fileName.equals(that.fileName) + && startWritingOffset == that.startWritingOffset + && Arrays.equals(filePiece, that.filePiece) + && version == that.version + && type == that.type + && body.equals(that.body); + } + + @Override + public int hashCode() { + return Objects.hash( + fileName, startWritingOffset, Arrays.hashCode(filePiece), version, type, body); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferFileSealReqV1.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferFileSealReqV1.java new file mode 100644 index 00000000..549c3868 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferFileSealReqV1.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Objects; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; + +public abstract class PipeTransferFileSealReqV1 extends TPipeTransferReq { + + private transient String fileName; + private transient long fileLength; + + public final String getFileName() { + return fileName; + } + + public final long getFileLength() { + return fileLength; + } + + protected abstract PipeRequestType getPlanType(); + + /////////////////////////////// Thrift /////////////////////////////// + + protected PipeTransferFileSealReqV1 convertToTPipeTransferReq(String fileName, long fileLength) + throws IOException { + + this.fileName = fileName; + this.fileLength = fileLength; + + this.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + this.type = getPlanType().getType(); + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(fileName, outputStream); + ReadWriteIOUtils.write(fileLength, outputStream); + this.body = ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + + return this; + } + + public PipeTransferFileSealReqV1 translateFromTPipeTransferReq(TPipeTransferReq req) { + + fileName = ReadWriteIOUtils.readString(req.body); + fileLength = ReadWriteIOUtils.readLong(req.body); + + version = req.version; + type = req.type; + body = req.body; + + return this; + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public byte[] convertToTPipeTransferSnapshotSealBytes(String fileName, long fileLength) + throws IOException { + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(IoTDBConnectorRequestVersion.VERSION_1.getVersion(), outputStream); + ReadWriteIOUtils.write(getPlanType().getType(), outputStream); + ReadWriteIOUtils.write(fileName, outputStream); + ReadWriteIOUtils.write(fileLength, outputStream); + return byteArrayOutputStream.toByteArray(); + } + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + PipeTransferFileSealReqV1 that = (PipeTransferFileSealReqV1) obj; + return fileName.equals(that.fileName) + && fileLength == that.fileLength + && version == that.version + && type == that.type + && body.equals(that.body); + } + + @Override + public int hashCode() { + return Objects.hash(fileName, fileLength, version, type, body); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferFileSealReqV2.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferFileSealReqV2.java new file mode 100644 index 00000000..08c5d973 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferFileSealReqV2.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; + +public abstract class PipeTransferFileSealReqV2 extends TPipeTransferReq { + + public static final String DATABASE_PATTERN = "database_pattern"; + public static final String TREE = "tree"; + public static final String TABLE = "table"; + protected transient List fileNames; + protected transient List fileLengths; + protected transient Map parameters; + + public final List getFileNames() { + return fileNames; + } + + public final List getFileLengths() { + return fileLengths; + } + + public final Map getParameters() { + return parameters; + } + + protected abstract PipeRequestType getPlanType(); + + /////////////////////////////// Thrift /////////////////////////////// + + protected PipeTransferFileSealReqV2 convertToTPipeTransferReq( + final List fileNames, + final List fileLengths, + final Map parameters) + throws IOException { + + this.fileNames = fileNames; + this.fileLengths = fileLengths; + this.parameters = parameters; + + this.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + this.type = getPlanType().getType(); + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(fileNames.size(), outputStream); + for (final String fileName : fileNames) { + ReadWriteIOUtils.write(fileName, outputStream); + } + ReadWriteIOUtils.write(fileLengths.size(), outputStream); + for (final Long fileLength : fileLengths) { + ReadWriteIOUtils.write(fileLength, outputStream); + } + ReadWriteIOUtils.write(parameters.size(), outputStream); + for (final Map.Entry entry : parameters.entrySet()) { + ReadWriteIOUtils.write(entry.getKey(), outputStream); + ReadWriteIOUtils.write(entry.getValue(), outputStream); + } + this.body = ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + + return this; + } + + public PipeTransferFileSealReqV2 translateFromTPipeTransferReq(final TPipeTransferReq req) { + fileNames = new ArrayList<>(); + int size = ReadWriteIOUtils.readInt(req.body); + for (int i = 0; i < size; ++i) { + fileNames.add(ReadWriteIOUtils.readString(req.body)); + } + + fileLengths = new ArrayList<>(); + size = ReadWriteIOUtils.readInt(req.body); + for (int i = 0; i < size; ++i) { + fileLengths.add(ReadWriteIOUtils.readLong(req.body)); + } + + parameters = new HashMap<>(); + size = ReadWriteIOUtils.readInt(req.body); + for (int i = 0; i < size; ++i) { + final String key = ReadWriteIOUtils.readString(req.body); + final String value = ReadWriteIOUtils.readString(req.body); + parameters.put(key, value); + } + + version = req.version; + type = req.type; + body = req.body; + + return this; + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public byte[] convertToTPipeTransferSnapshotSealBytes( + List fileNames, List fileLengths, Map parameters) + throws IOException { + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(IoTDBConnectorRequestVersion.VERSION_1.getVersion(), outputStream); + ReadWriteIOUtils.write(getPlanType().getType(), outputStream); + ReadWriteIOUtils.write(fileNames.size(), outputStream); + for (String fileName : fileNames) { + ReadWriteIOUtils.write(fileName, outputStream); + } + ReadWriteIOUtils.write(fileLengths.size(), outputStream); + for (Long fileLength : fileLengths) { + ReadWriteIOUtils.write(fileLength, outputStream); + } + ReadWriteIOUtils.write(parameters.size(), outputStream); + for (final Map.Entry entry : parameters.entrySet()) { + ReadWriteIOUtils.write(entry.getKey(), outputStream); + ReadWriteIOUtils.write(entry.getValue(), outputStream); + } + return byteArrayOutputStream.toByteArray(); + } + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + PipeTransferFileSealReqV2 that = (PipeTransferFileSealReqV2) obj; + return Objects.equals(fileNames, that.fileNames) + && Objects.equals(fileLengths, that.fileLengths) + && Objects.equals(parameters, that.parameters) + && Objects.equals(version, that.version) + && Objects.equals(type, that.type) + && Objects.equals(body, that.body); + } + + @Override + public int hashCode() { + return Objects.hash(fileNames, fileLengths, parameters, version, type, body); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferHandshakeV1Req.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferHandshakeV1Req.java new file mode 100644 index 00000000..5ed7eebb --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferHandshakeV1Req.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Objects; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; + +public abstract class PipeTransferHandshakeV1Req extends TPipeTransferReq { + + private transient String timestampPrecision; + + public final String getTimestampPrecision() { + return timestampPrecision; + } + + protected abstract PipeRequestType getPlanType(); + + /////////////////////////////// Thrift /////////////////////////////// + + public final PipeTransferHandshakeV1Req convertToTPipeTransferReq(String timestampPrecision) + throws IOException { + this.timestampPrecision = timestampPrecision; + + this.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + this.type = getPlanType().getType(); + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(timestampPrecision, outputStream); + this.body = ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + + return this; + } + + protected final PipeTransferHandshakeV1Req translateFromTPipeTransferReq( + TPipeTransferReq transferReq) { + timestampPrecision = ReadWriteIOUtils.readString(transferReq.body); + + version = transferReq.version; + type = transferReq.type; + body = transferReq.body; + + return this; + } + + /////////////////////////////// Air Gap /////////////////////////////// + + protected final byte[] convertToTransferHandshakeBytes(String timestampPrecision) + throws IOException { + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(IoTDBConnectorRequestVersion.VERSION_1.getVersion(), outputStream); + ReadWriteIOUtils.write(getPlanType().getType(), outputStream); + ReadWriteIOUtils.write(timestampPrecision, outputStream); + return byteArrayOutputStream.toByteArray(); + } + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + PipeTransferHandshakeV1Req that = (PipeTransferHandshakeV1Req) obj; + return timestampPrecision.equals(that.timestampPrecision) + && version == that.version + && type == that.type + && body.equals(that.body); + } + + @Override + public int hashCode() { + return Objects.hash(timestampPrecision, version, type, body); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferHandshakeV2Req.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferHandshakeV2Req.java new file mode 100644 index 00000000..58fe726a --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferHandshakeV2Req.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; + +public abstract class PipeTransferHandshakeV2Req extends TPipeTransferReq { + + private transient Map params; + + public Map getParams() { + return params; + } + + protected abstract PipeRequestType getPlanType(); + + /////////////////////////////// Thrift /////////////////////////////// + + protected final PipeTransferHandshakeV2Req convertToTPipeTransferReq(Map params) + throws IOException { + this.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + this.type = getPlanType().getType(); + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(params.size(), outputStream); + for (final Map.Entry entry : params.entrySet()) { + ReadWriteIOUtils.write(entry.getKey(), outputStream); + ReadWriteIOUtils.write(entry.getValue(), outputStream); + } + this.body = ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + + this.params = params; + return this; + } + + protected final PipeTransferHandshakeV2Req translateFromTPipeTransferReq( + TPipeTransferReq transferReq) { + Map params = new HashMap<>(); + final int size = ReadWriteIOUtils.readInt(transferReq.body); + for (int i = 0; i < size; ++i) { + final String key = ReadWriteIOUtils.readString(transferReq.body); + final String value = ReadWriteIOUtils.readString(transferReq.body); + params.put(key, value); + } + this.params = params; + + version = transferReq.version; + type = transferReq.type; + body = transferReq.body; + + return this; + } + + /////////////////////////////// Air Gap /////////////////////////////// + + public final byte[] convertToTransferHandshakeBytes(Map params) + throws IOException { + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(IoTDBConnectorRequestVersion.VERSION_1.getVersion(), outputStream); + ReadWriteIOUtils.write(getPlanType().getType(), outputStream); + ReadWriteIOUtils.write(params.size(), outputStream); + for (final Map.Entry entry : params.entrySet()) { + ReadWriteIOUtils.write(entry.getKey(), outputStream); + ReadWriteIOUtils.write(entry.getValue(), outputStream); + } + return byteArrayOutputStream.toByteArray(); + } + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + PipeTransferHandshakeV2Req that = (PipeTransferHandshakeV2Req) obj; + return Objects.equals(params, that.params) + && version == that.version + && type == that.type + && Objects.equals(body, that.body); + } + + @Override + public int hashCode() { + return Objects.hash(params, version, type, body); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferSliceReq.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferSliceReq.java new file mode 100644 index 00000000..4541b84a --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/request/PipeTransferSliceReq.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Objects; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.Binary; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; + +public class PipeTransferSliceReq extends TPipeTransferReq { + + private transient int orderId; + + private transient short originReqType; + private transient int originBodySize; + + private transient byte[] sliceBody; + + private transient int sliceIndex; + private transient int sliceCount; + + public int getOrderId() { + return orderId; + } + + public short getOriginReqType() { + return originReqType; + } + + public int getOriginBodySize() { + return originBodySize; + } + + public byte[] getSliceBody() { + return sliceBody; + } + + public int getSliceIndex() { + return sliceIndex; + } + + public int getSliceCount() { + return sliceCount; + } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferSliceReq toTPipeTransferReq( + final int orderId, + final short originReqType, + final int sliceIndex, + final int sliceCount, + final ByteBuffer duplicatedOriginBody, + final int startIndexInBody, + final int endIndexInBody) + throws IOException { + final PipeTransferSliceReq sliceReq = new PipeTransferSliceReq(); + + sliceReq.orderId = orderId; + + sliceReq.originReqType = originReqType; + sliceReq.originBodySize = duplicatedOriginBody.limit(); + + sliceReq.sliceBody = new byte[endIndexInBody - startIndexInBody]; + duplicatedOriginBody.position(startIndexInBody); + duplicatedOriginBody.get(sliceReq.sliceBody); + + sliceReq.sliceIndex = sliceIndex; + sliceReq.sliceCount = sliceCount; + + sliceReq.version = IoTDBConnectorRequestVersion.VERSION_1.getVersion(); + sliceReq.type = PipeRequestType.TRANSFER_SLICE.getType(); + try (final PublicBAOS byteArrayOutputStream = new PublicBAOS(); + final DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(sliceReq.orderId, outputStream); + + ReadWriteIOUtils.write(sliceReq.originReqType, outputStream); + ReadWriteIOUtils.write(sliceReq.originBodySize, outputStream); + + ReadWriteIOUtils.write(new Binary(sliceReq.sliceBody), outputStream); + + ReadWriteIOUtils.write(sliceReq.sliceIndex, outputStream); + ReadWriteIOUtils.write(sliceReq.sliceCount, outputStream); + + sliceReq.body = + ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + + return sliceReq; + } + + public static PipeTransferSliceReq fromTPipeTransferReq(final TPipeTransferReq transferReq) { + final PipeTransferSliceReq sliceReq = new PipeTransferSliceReq(); + + sliceReq.orderId = ReadWriteIOUtils.readInt(transferReq.body); + + sliceReq.originReqType = ReadWriteIOUtils.readShort(transferReq.body); + sliceReq.originBodySize = ReadWriteIOUtils.readInt(transferReq.body); + + sliceReq.sliceBody = ReadWriteIOUtils.readBinary(transferReq.body).getValues(); + + sliceReq.sliceIndex = ReadWriteIOUtils.readInt(transferReq.body); + sliceReq.sliceCount = ReadWriteIOUtils.readInt(transferReq.body); + + sliceReq.version = transferReq.version; + sliceReq.type = transferReq.type; + sliceReq.body = transferReq.body; + + return sliceReq; + } + + /////////////////////////////// Object /////////////////////////////// + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final PipeTransferSliceReq that = (PipeTransferSliceReq) obj; + return Objects.equals(orderId, that.orderId) + && Objects.equals(originReqType, that.originReqType) + && Objects.equals(originBodySize, that.originBodySize) + && Arrays.equals(sliceBody, that.sliceBody) + && Objects.equals(sliceIndex, that.sliceIndex) + && Objects.equals(sliceCount, that.sliceCount) + && Objects.equals(version, that.version) + && Objects.equals(type, that.type) + && Objects.equals(body, that.body); + } + + @Override + public int hashCode() { + return Objects.hash( + orderId, + originReqType, + originBodySize, + Arrays.hashCode(sliceBody), + sliceIndex, + sliceCount, + version, + type, + body); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/response/PipeTransferFilePieceResp.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/response/PipeTransferFilePieceResp.java new file mode 100644 index 00000000..180ea293 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/thrift/response/PipeTransferFilePieceResp.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.response; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Objects; +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferResp; +import org.apache.tsfile.utils.PublicBAOS; +import org.apache.tsfile.utils.ReadWriteIOUtils; + +public class PipeTransferFilePieceResp extends TPipeTransferResp { + + public static final long ERROR_END_OFFSET = -1; + + private long endWritingOffset; + + private PipeTransferFilePieceResp() { + // Empty constructor + } + + public long getEndWritingOffset() { + return endWritingOffset; + } + + /////////////////////////////// Thrift /////////////////////////////// + + public static PipeTransferFilePieceResp toTPipeTransferResp( + TSStatus status, long endWritingOffset) throws IOException { + final PipeTransferFilePieceResp filePieceResp = new PipeTransferFilePieceResp(); + + filePieceResp.status = status; + + filePieceResp.endWritingOffset = endWritingOffset; + try (PublicBAOS byteArrayOutputStream = new PublicBAOS(); + DataOutputStream outputStream = new DataOutputStream(byteArrayOutputStream)) { + ReadWriteIOUtils.write(endWritingOffset, outputStream); + filePieceResp.body = + ByteBuffer.wrap(byteArrayOutputStream.getBuf(), 0, byteArrayOutputStream.size()); + } + + return filePieceResp; + } + + public static PipeTransferFilePieceResp toTPipeTransferResp(TSStatus status) { + final PipeTransferFilePieceResp filePieceResp = new PipeTransferFilePieceResp(); + + filePieceResp.status = status; + + return filePieceResp; + } + + public static PipeTransferFilePieceResp fromTPipeTransferResp(TPipeTransferResp transferResp) { + final PipeTransferFilePieceResp filePieceResp = new PipeTransferFilePieceResp(); + + filePieceResp.status = transferResp.status; + + if (transferResp.isSetBody()) { + filePieceResp.endWritingOffset = ReadWriteIOUtils.readLong(transferResp.body); + filePieceResp.body = transferResp.body; + } + + return filePieceResp; + } + + /////////////////////////////// Object /////////////////////////////// + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + PipeTransferFilePieceResp that = (PipeTransferFilePieceResp) obj; + return endWritingOffset == that.endWritingOffset + && status.equals(that.status) + && body.equals(that.body); + } + + @Override + public int hashCode() { + return Objects.hash(endWritingOffset, status, body); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ClientPoolProperty.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ClientPoolProperty.java new file mode 100644 index 00000000..5026093c --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ClientPoolProperty.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.property; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; + +public class ClientPoolProperty { + + private final GenericKeyedObjectPoolConfig config; + + private ClientPoolProperty(GenericKeyedObjectPoolConfig config) { + this.config = config; + } + + public GenericKeyedObjectPoolConfig getConfig() { + return config; + } + + public static class Builder { + + /** + * when the number of the client to a single node exceeds maxClientNumForEachNode, the thread + * for applying for a client will be blocked for waitClientTimeoutMs, then ClientManager will + * throw ClientManagerException if there are no clients after the block time. + */ + private long waitClientTimeoutMs = DefaultProperty.WAIT_CLIENT_TIMEOUT_MS; + + /** + * the maximum number of clients that can be allocated for a node. When some clients are idle + * for more than {@code maxIdleTimeForClient}, they will be cleaned up. + */ + private int maxClientNumForEachNode = DefaultProperty.MAX_CLIENT_NUM_FOR_EACH_NODE; + + /** + * the minimum amount of time a client may sit idle in the pool before it is eligible for + * eviction by the idle object evictor. + */ + private long minIdleTimeForClient = DefaultProperty.MIN_IDLE_TIME_FOR_CLIENT_MS; + + /** + * the duration to sleep between runs of the idle object evictor thread. When non-positive, no + * idle object evictor thread will be run, which means clients that are idle for more than + * {@code minIdleTimeForClient} will never be cleaned up. + */ + private long timeBetweenEvictionRuns = DefaultProperty.TIME_BETWEEN_EVICTION_RUNS_MS; + + public Builder setWaitClientTimeoutMs(long waitClientTimeoutMs) { + this.waitClientTimeoutMs = waitClientTimeoutMs; + return this; + } + + public Builder setMaxClientNumForEachNode(int maxClientNumForEachNode) { + this.maxClientNumForEachNode = maxClientNumForEachNode; + return this; + } + + public Builder setMinIdleTimeForClient(long minIdleTimeForClient) { + this.minIdleTimeForClient = minIdleTimeForClient; + return this; + } + + public Builder setTimeBetweenEvictionRuns(long timeBetweenEvictionRuns) { + this.timeBetweenEvictionRuns = timeBetweenEvictionRuns; + return this; + } + + public ClientPoolProperty build() { + GenericKeyedObjectPoolConfig poolConfig = new GenericKeyedObjectPoolConfig<>(); + poolConfig.setMaxTotalPerKey(maxClientNumForEachNode); + poolConfig.setMaxIdlePerKey(maxClientNumForEachNode); + poolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(timeBetweenEvictionRuns)); + poolConfig.setMinEvictableIdleTime(Duration.ofMillis(minIdleTimeForClient)); + poolConfig.setMaxWait(Duration.ofMillis(waitClientTimeoutMs)); + poolConfig.setTestOnReturn(true); + poolConfig.setTestOnBorrow(true); + return new ClientPoolProperty<>(poolConfig); + } + } + + public static class DefaultProperty { + + private DefaultProperty() {} + + public static final long WAIT_CLIENT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30); + public static final long MIN_IDLE_TIME_FOR_CLIENT_MS = TimeUnit.MINUTES.toMillis(1); + public static final long TIME_BETWEEN_EVICTION_RUNS_MS = TimeUnit.MINUTES.toMillis(1); + public static final int MAX_CLIENT_NUM_FOR_EACH_NODE = 1000; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/PipeConsensusClientProperty.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/PipeConsensusClientProperty.java new file mode 100644 index 00000000..219e3477 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/PipeConsensusClientProperty.java @@ -0,0 +1,99 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.property; + +/** This class defines the configurations used by the PipeConsensus Client. */ +public class PipeConsensusClientProperty { + private final boolean isRpcThriftCompressionEnabled; + private final int selectorNumOfClientManager; + private final boolean printLogWhenThriftClientEncounterException; + private final int maxClientNumForEachNode; + + public PipeConsensusClientProperty( + boolean isRpcThriftCompressionEnabled, + int selectorNumOfClientManager, + boolean printLogWhenThriftClientEncounterException, + int maxClientNumForEachNode) { + this.isRpcThriftCompressionEnabled = isRpcThriftCompressionEnabled; + this.selectorNumOfClientManager = selectorNumOfClientManager; + this.printLogWhenThriftClientEncounterException = printLogWhenThriftClientEncounterException; + this.maxClientNumForEachNode = maxClientNumForEachNode; + } + + public boolean isRpcThriftCompressionEnabled() { + return isRpcThriftCompressionEnabled; + } + + public int getSelectorNumOfClientManager() { + return selectorNumOfClientManager; + } + + public boolean isPrintLogWhenThriftClientEncounterException() { + return printLogWhenThriftClientEncounterException; + } + + public int getMaxClientNumForEachNode() { + return maxClientNumForEachNode; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + private boolean isRpcThriftCompressionEnabled = false; + private int selectorNumOfClientManager = 1; + private boolean printLogWhenThriftClientEncounterException = true; + private int maxClientNumForEachNode = + ClientPoolProperty.DefaultProperty.MAX_CLIENT_NUM_FOR_EACH_NODE; + + public Builder setIsRpcThriftCompressionEnabled( + boolean isRpcThriftCompressionEnabled) { + this.isRpcThriftCompressionEnabled = isRpcThriftCompressionEnabled; + return this; + } + + public Builder setSelectorNumOfClientManager( + int selectorNumOfClientManager) { + this.selectorNumOfClientManager = selectorNumOfClientManager; + return this; + } + + public Builder setPrintLogWhenThriftClientEncounterException( + boolean printLogWhenThriftClientEncounterException) { + this.printLogWhenThriftClientEncounterException = printLogWhenThriftClientEncounterException; + return this; + } + + public Builder setMaxClientNumForEachNode( + int maxClientNumForEachNode) { + this.maxClientNumForEachNode = maxClientNumForEachNode; + return this; + } + + public PipeConsensusClientProperty build() { + return new PipeConsensusClientProperty( + isRpcThriftCompressionEnabled, + selectorNumOfClientManager, + printLogWhenThriftClientEncounterException, + maxClientNumForEachNode); + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ThriftClient.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ThriftClient.java new file mode 100644 index 00000000..4ed09dfe --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ThriftClient.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.property; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.ConnectException; +import java.net.SocketException; +import java.util.Optional; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.thrift.TException; +import org.apache.thrift.transport.TTransportException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class defines the failed interfaces that thrift client needs to support so that the Thrift + * Client can clean up the clientManager when it receives the corresponding exception. + */ +public interface ThriftClient { + + Logger logger = LoggerFactory.getLogger(ThriftClient.class); + + /** Close this connection. */ + void invalidate(); + + /** Removing all pooled instances corresponding to current instance's endpoint. */ + void invalidateAll(); + + /** + * Whether to print logs when exceptions are encountered. + * + * @return result + */ + boolean printLogWhenEncounterException(); + + /** + * Perform corresponding operations on ThriftClient o based on the Throwable t. + * + * @param t Throwable + * @param o ThriftClient + */ + static void resolveException(Throwable t, ThriftClient o) { + Throwable origin = t; + if (t instanceof InvocationTargetException) { + origin = ((InvocationTargetException) t).getTargetException(); + } + Throwable cur = origin; + if (cur instanceof TException) { + int level = 0; + while (cur != null) { + logger.debug( + "level-{} Exception class {}, message {}", + level, + cur.getClass().getName(), + cur.getMessage()); + cur = cur.getCause(); + level++; + } + o.invalidate(); + } + + Throwable rootCause = ExceptionUtils.getRootCause(origin); + if (rootCause != null) { + // if the exception is SocketException and its error message is Broken pipe, it means that + // the remote node may restart and all the connection we cached before should be cleared. + logger.debug( + "root cause message {}, LocalizedMessage {}, ", + rootCause.getMessage(), + rootCause.getLocalizedMessage(), + rootCause); + if (isConnectionBroken(rootCause)) { + if (o.printLogWhenEncounterException()) { + logger.info( + "Broken pipe error happened in sending RPC," + + " we need to clear all previous cached connection, error msg is {}", + rootCause.toString()); + } + o.invalidateAll(); + } + } + } + + /** + * Determine whether the target node has gone offline once based on the cause. + * + * @param cause Throwable + * @return true/false + */ + static boolean isConnectionBroken(Throwable cause) { + return (cause instanceof SocketException && cause.getMessage().contains("Broken pipe")) + || (cause instanceof TTransportException + && (hasExpectedMessage(cause, "Socket is closed by peer") + || hasExpectedMessage(cause, "Read call frame size failed"))) + || (cause instanceof IOException + && (hasExpectedMessage(cause, "Connection reset by peer") + || hasExpectedMessage(cause, "Broken pipe"))) + || (cause instanceof ConnectException && hasExpectedMessage(cause, "Connection refused")); + } + + static boolean hasExpectedMessage(Throwable cause, String expectedMessage) { + return Optional.ofNullable(cause.getMessage()) + .map(m -> m.contains(expectedMessage)) + .orElse(false); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ThriftClientProperty.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ThriftClientProperty.java new file mode 100644 index 00000000..1d4e7777 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ThriftClientProperty.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.property; + +import java.util.concurrent.TimeUnit; +import org.apache.thrift.protocol.TBinaryProtocol; +import org.apache.thrift.protocol.TCompactProtocol; +import org.apache.thrift.protocol.TProtocolFactory; + +/** This class defines the configurations commonly used by the Thrift Client. */ +public class ThriftClientProperty { + + private final TProtocolFactory protocolFactory; + private final int connectionTimeoutMs; + private final int selectorNumOfAsyncClientPool; + private final boolean printLogWhenEncounterException; + + private ThriftClientProperty( + TProtocolFactory protocolFactory, + int connectionTimeoutMs, + int selectorNumOfAsyncClientPool, + boolean printLogWhenEncounterException) { + this.protocolFactory = protocolFactory; + this.connectionTimeoutMs = connectionTimeoutMs; + this.selectorNumOfAsyncClientPool = selectorNumOfAsyncClientPool; + this.printLogWhenEncounterException = printLogWhenEncounterException; + } + + public TProtocolFactory getProtocolFactory() { + return protocolFactory; + } + + public int getConnectionTimeoutMs() { + return connectionTimeoutMs; + } + + public int getSelectorNumOfAsyncClientPool() { + return selectorNumOfAsyncClientPool; + } + + public boolean isPrintLogWhenEncounterException() { + return printLogWhenEncounterException; + } + + public static class Builder { + + /** whether to use thrift compression. */ + private boolean rpcThriftCompressionEnabled = DefaultProperty.RPC_THRIFT_COMPRESSED_ENABLED; + + /** socket timeout for thrift client. */ + private int connectionTimeoutMs = DefaultProperty.CONNECTION_TIMEOUT_MS; + + /** number of selector threads for asynchronous thrift client in a clientManager. */ + private int selectorNumOfAsyncClientManager = + DefaultProperty.SELECTOR_NUM_OF_ASYNC_CLIENT_MANAGER; + + /** + * Whether to print logs when the client encounters exceptions. For example, logs are not + * printed in the heartbeat client. + */ + private boolean printLogWhenEncounterException = + DefaultProperty.PRINT_LOG_WHEN_ENCOUNTER_EXCEPTION; + + public Builder setRpcThriftCompressionEnabled(boolean rpcThriftCompressionEnabled) { + this.rpcThriftCompressionEnabled = rpcThriftCompressionEnabled; + return this; + } + + public Builder setConnectionTimeoutMs(int connectionTimeoutMs) { + this.connectionTimeoutMs = connectionTimeoutMs; + return this; + } + + public Builder setSelectorNumOfAsyncClientManager(int selectorNumOfAsyncClientManager) { + this.selectorNumOfAsyncClientManager = selectorNumOfAsyncClientManager; + return this; + } + + public Builder setPrintLogWhenEncounterException(boolean printLogWhenEncounterException) { + this.printLogWhenEncounterException = printLogWhenEncounterException; + return this; + } + + public ThriftClientProperty build() { + return new ThriftClientProperty( + rpcThriftCompressionEnabled + ? new TCompactProtocol.Factory() + : new TBinaryProtocol.Factory(), + connectionTimeoutMs, + selectorNumOfAsyncClientManager, + printLogWhenEncounterException); + } + } + + public static class DefaultProperty { + + private DefaultProperty() {} + + public static final boolean RPC_THRIFT_COMPRESSED_ENABLED = false; + public static final int CONNECTION_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(20); + public static final int CONNECTION_NEVER_TIMEOUT_MS = 0; + public static final int SELECTOR_NUM_OF_ASYNC_CLIENT_MANAGER = 1; + public static final boolean PRINT_LOG_WHEN_ENCOUNTER_EXCEPTION = true; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBAirGapConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBAirGapConnector.java new file mode 100644 index 00000000..d50ffed8 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBAirGapConnector.java @@ -0,0 +1,433 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.airgap; + +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.collector.plugin.builtin.annotation.TableModel; +import org.apache.iotdb.collector.plugin.builtin.annotation.TreeModel; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.airgap.AirGapELanguageConstant; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.airgap.AirGapOneByteResponse; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.pipe.api.customizer.configuration.PipeConnectorRuntimeConfiguration; +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters; +import org.apache.iotdb.pipe.api.exception.PipeConnectionException; +import org.apache.iotdb.pipe.api.exception.PipeException; +import org.apache.iotdb.rpc.TSStatusCode; +import org.apache.tsfile.utils.BytesUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.CRC32; + +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_AIR_GAP_E_LANGUAGE_ENABLE_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_AIR_GAP_E_LANGUAGE_ENABLE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_AIR_GAP_HANDSHAKE_TIMEOUT_MS_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_AIR_GAP_HANDSHAKE_TIMEOUT_MS_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_BALANCE_PRIORITY_STRATEGY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_BALANCE_RANDOM_STRATEGY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_BALANCE_ROUND_ROBIN_STRATEGY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_AIR_GAP_E_LANGUAGE_ENABLE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_AIR_GAP_HANDSHAKE_TIMEOUT_MS_KEY; +import static org.apache.tsfile.utils.ReadWriteIOUtils.LONG_LEN; + +@TreeModel +@TableModel +public abstract class IoTDBAirGapConnector extends IoTDBConnector { + + protected static class AirGapSocket extends Socket { + + private final TEndPoint endPoint; + + public AirGapSocket(final String ip, final int port) { + this.endPoint = new TEndPoint(ip, port); + } + + public TEndPoint getEndPoint() { + return endPoint; + } + + @Override + public String toString() { + return "AirGapSocket{" + "endPoint=" + endPoint + "} (" + super.toString() + ")"; + } + } + + private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBAirGapConnector.class); + + protected final List sockets = new ArrayList<>(); + protected final List isSocketAlive = new ArrayList<>(); + + private LoadBalancer loadBalancer; + private long currentClientIndex = 0; + + private int handshakeTimeoutMs; + + private boolean eLanguageEnable; + + // The air gap connector does not use clientManager thus we put handshake type here + protected boolean supportModsIfIsDataNodeReceiver = true; + + private final Map failLogTimes = new HashMap<>(); + + @Override + public void customize( + final PipeParameters parameters, final PipeConnectorRuntimeConfiguration configuration) + throws Exception { + super.customize(parameters, configuration); + + if (isTabletBatchModeEnabled) { + LOGGER.warn( + "Batch mode is enabled by the given parameters. " + + "IoTDBAirGapConnector does not support batch mode. " + + "Disable batch mode."); + } + + for (int i = 0; i < nodeUrls.size(); i++) { + isSocketAlive.add(false); + sockets.add(null); + } + + switch (loadBalanceStrategy) { + case CONNECTOR_LOAD_BALANCE_ROUND_ROBIN_STRATEGY: + loadBalancer = new RoundRobinLoadBalancer(); + break; + case CONNECTOR_LOAD_BALANCE_RANDOM_STRATEGY: + loadBalancer = new RandomLoadBalancer(); + break; + case CONNECTOR_LOAD_BALANCE_PRIORITY_STRATEGY: + loadBalancer = new PriorityLoadBalancer(); + break; + default: + LOGGER.warn( + "Unknown load balance strategy: {}, use round-robin strategy instead.", + loadBalanceStrategy); + loadBalancer = new RoundRobinLoadBalancer(); + } + + handshakeTimeoutMs = + parameters.getIntOrDefault( + Arrays.asList( + CONNECTOR_AIR_GAP_HANDSHAKE_TIMEOUT_MS_KEY, SINK_AIR_GAP_HANDSHAKE_TIMEOUT_MS_KEY), + CONNECTOR_AIR_GAP_HANDSHAKE_TIMEOUT_MS_DEFAULT_VALUE); + LOGGER.info( + "IoTDBAirGapConnector is customized with handshakeTimeoutMs: {}.", handshakeTimeoutMs); + + eLanguageEnable = + parameters.getBooleanOrDefault( + Arrays.asList( + CONNECTOR_AIR_GAP_E_LANGUAGE_ENABLE_KEY, SINK_AIR_GAP_E_LANGUAGE_ENABLE_KEY), + CONNECTOR_AIR_GAP_E_LANGUAGE_ENABLE_DEFAULT_VALUE); + LOGGER.info("IoTDBAirGapConnector is customized with eLanguageEnable: {}.", eLanguageEnable); + } + + @Override + @SuppressWarnings("java:S2095") + public void handshake() throws Exception { + for (int i = 0; i < sockets.size(); i++) { + if (Boolean.TRUE.equals(isSocketAlive.get(i))) { + continue; + } + + final String ip = nodeUrls.get(i).getIp(); + final int port = nodeUrls.get(i).getPort(); + + // Close the socket if necessary + if (sockets.get(i) != null) { + try { + sockets.set(i, null).close(); + } catch (final Exception e) { + LOGGER.warn( + "Failed to close socket with target server ip: {}, port: {}, because: {}. Ignore it.", + ip, + port, + e.getMessage()); + } + } + + final AirGapSocket socket = new AirGapSocket(ip, port); + + try { + socket.connect(new InetSocketAddress(ip, port), handshakeTimeoutMs); + socket.setKeepAlive(true); + sockets.set(i, socket); + LOGGER.info("Successfully connected to target server ip: {}, port: {}.", ip, port); + failLogTimes.remove(nodeUrls.get(i)); + } catch (final Exception e) { + final TEndPoint endPoint = nodeUrls.get(i); + final long currentTimeMillis = System.currentTimeMillis(); + final Long lastFailLogTime = failLogTimes.get(endPoint); + if (lastFailLogTime == null || currentTimeMillis - lastFailLogTime > 60000) { + failLogTimes.put(endPoint, currentTimeMillis); + LOGGER.warn( + "Failed to connect to target server ip: {}, port: {}, because: {}. Ignore it.", + ip, + port, + e.getMessage()); + } + continue; + } + + try { + sendHandshakeReq(socket); + isSocketAlive.set(i, true); + } catch (Exception e) { + LOGGER.warn( + "Handshake error occurs. It may be caused by an error on the receiving end. Ignore it.", + e); + } + } + + for (int i = 0; i < sockets.size(); i++) { + if (Boolean.TRUE.equals(isSocketAlive.get(i))) { + return; + } + } + throw new PipeConnectionException( + String.format("All target servers %s are not available.", nodeUrls)); + } + + protected void sendHandshakeReq(final AirGapSocket socket) throws IOException { + socket.setSoTimeout(handshakeTimeoutMs); + // Try to handshake by PipeTransferHandshakeV2Req. If failed, retry to handshake by + // PipeTransferHandshakeV1Req. If failed again, throw PipeConnectionException. + if (!send(socket, generateHandShakeV2Payload())) { + supportModsIfIsDataNodeReceiver = false; + if (!send(socket, generateHandShakeV1Payload())) { + throw new PipeConnectionException("Handshake error with target server, socket: " + socket); + } + } else { + supportModsIfIsDataNodeReceiver = true; + } + socket.setSoTimeout(PipeOptions.PIPE_CONNECTOR_TRANSFER_TIMEOUT_MS.value()); + LOGGER.info("Handshake success. Socket: {}", socket); + } + + protected abstract byte[] generateHandShakeV1Payload() throws IOException; + + protected abstract byte[] generateHandShakeV2Payload() throws IOException; + + @Override + public void heartbeat() { + try { + handshake(); + } catch (final Exception e) { + LOGGER.warn( + "Failed to reconnect to target server, because: {}. Try to reconnect later.", + e.getMessage(), + e); + } + } + + protected void transferFilePieces( + final String pipeName, + final long creationTime, + final File file, + final AirGapSocket socket, + final boolean isMultiFile) + throws PipeException, IOException { + final int readFileBufferSize = PipeOptions.PIPE_CONNECTOR_READ_FILE_BUFFER_SIZE.value(); + final byte[] readBuffer = new byte[readFileBufferSize]; + long position = 0; + try (final RandomAccessFile reader = new RandomAccessFile(file, "r")) { + while (true) { + final int readLength = reader.read(readBuffer); + if (readLength == -1) { + break; + } + + final byte[] payload = + readLength == readFileBufferSize + ? readBuffer + : Arrays.copyOfRange(readBuffer, 0, readLength); + if (!send( + pipeName, + creationTime, + socket, + isMultiFile + ? getTransferMultiFilePieceBytes(file.getName(), position, payload) + : getTransferSingleFilePieceBytes(file.getName(), position, payload))) { + final String errorMessage = + String.format("Transfer file %s error. Socket %s.", file, socket); + if (mayNeedHandshakeWhenFail()) { + // Send handshake because we don't know whether the receiver side configNode + // has set up a new one + sendHandshakeReq(socket); + } + receiverStatusHandler.handle( + new TSStatus(TSStatusCode.PIPE_RECEIVER_USER_CONFLICT_EXCEPTION.getStatusCode()) + .setMessage(errorMessage), + errorMessage, + file.toString()); + } else { + position += readLength; + } + } + } + } + + protected abstract boolean mayNeedHandshakeWhenFail(); + + protected abstract byte[] getTransferSingleFilePieceBytes( + final String fileName, final long position, final byte[] payLoad) throws IOException; + + protected abstract byte[] getTransferMultiFilePieceBytes( + final String fileName, final long position, final byte[] payLoad) throws IOException; + + protected int nextSocketIndex() { + return loadBalancer.nextSocketIndex(); + } + + protected boolean send( + final String pipeName, final long creationTime, final AirGapSocket socket, byte[] bytes) + throws IOException { + if (!socket.isConnected()) { + throw new SocketException( + String.format("Socket %s is closed, will try to handshake", socket)); + } + + bytes = compressIfNeeded(bytes); + + rateLimitIfNeeded(pipeName, creationTime, socket.getEndPoint(), bytes.length); + + final BufferedOutputStream outputStream = new BufferedOutputStream(socket.getOutputStream()); + bytes = enrichWithLengthAndChecksum(bytes); + outputStream.write(eLanguageEnable ? enrichWithELanguage(bytes) : bytes); + outputStream.flush(); + + final byte[] response = new byte[1]; + final int size = socket.getInputStream().read(response); + return size > 0 && Arrays.equals(AirGapOneByteResponse.OK, response); + } + + protected boolean send(final AirGapSocket socket, final byte[] bytes) throws IOException { + return send(null, 0, socket, bytes); + } + + private byte[] enrichWithLengthAndChecksum(final byte[] bytes) { + // Length of checksum and bytes payload + final byte[] length = BytesUtils.intToBytes(bytes.length + LONG_LEN); + + final CRC32 crc32 = new CRC32(); + crc32.update(bytes, 0, bytes.length); + + // Double length as simple checksum + return BytesUtils.concatByteArrayList( + Arrays.asList(length, length, BytesUtils.longToBytes(crc32.getValue()), bytes)); + } + + private byte[] enrichWithELanguage(final byte[] bytes) { + return BytesUtils.concatByteArrayList( + Arrays.asList( + AirGapELanguageConstant.E_LANGUAGE_PREFIX, + bytes, + AirGapELanguageConstant.E_LANGUAGE_SUFFIX)); + } + + @Override + public void close() { + for (int i = 0; i < sockets.size(); ++i) { + try { + if (sockets.get(i) != null) { + sockets.set(i, null).close(); + } + } catch (final Exception e) { + LOGGER.warn("Failed to close client {}.", i, e); + } finally { + isSocketAlive.set(i, false); + } + } + + super.close(); + } + + /////////////////////// Strategies for load balance ////////////////////////// + + private interface LoadBalancer { + int nextSocketIndex(); + } + + private class RoundRobinLoadBalancer implements LoadBalancer { + @Override + public int nextSocketIndex() { + final int socketSize = sockets.size(); + // Round-robin, find the next alive client + for (int tryCount = 0; tryCount < socketSize; ++tryCount) { + final int clientIndex = (int) (currentClientIndex++ % socketSize); + if (Boolean.TRUE.equals(isSocketAlive.get(clientIndex))) { + return clientIndex; + } + } + + throw new PipeConnectionException( + "All sockets are dead, please check the connection to the receiver."); + } + } + + private class RandomLoadBalancer implements LoadBalancer { + @Override + public int nextSocketIndex() { + final int socketSize = sockets.size(); + final int clientIndex = (int) (Math.random() * socketSize); + if (Boolean.TRUE.equals(isSocketAlive.get(clientIndex))) { + return clientIndex; + } + + // Random, find the next alive client + for (int tryCount = 0; tryCount < socketSize - 1; ++tryCount) { + final int nextClientIndex = (clientIndex + tryCount + 1) % socketSize; + if (Boolean.TRUE.equals(isSocketAlive.get(nextClientIndex))) { + return nextClientIndex; + } + } + + throw new PipeConnectionException( + "All sockets are dead, please check the connection to the receiver."); + } + } + + private class PriorityLoadBalancer implements LoadBalancer { + @Override + public int nextSocketIndex() { + // Priority, find the first alive client + final int socketSize = sockets.size(); + for (int i = 0; i < socketSize; ++i) { + if (Boolean.TRUE.equals(isSocketAlive.get(i))) { + return i; + } + } + + throw new PipeConnectionException( + "All sockets are dead, please check the connection to the receiver."); + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBConnector.java new file mode 100644 index 00000000..f1c4da29 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBConnector.java @@ -0,0 +1,495 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.airgap; + +import org.apache.iotdb.collector.plugin.builtin.annotation.TableModel; +import org.apache.iotdb.collector.plugin.builtin.annotation.TreeModel; +import org.apache.iotdb.collector.plugin.builtin.sink.compressor.PipeCompressor; +import org.apache.iotdb.collector.plugin.builtin.sink.compressor.PipeCompressorConfig; +import org.apache.iotdb.collector.plugin.builtin.sink.compressor.PipeCompressorFactory; +import org.apache.iotdb.collector.plugin.builtin.sink.limiter.GlobalRateLimiter; +import org.apache.iotdb.collector.plugin.builtin.sink.limiter.PipeEndPointRateLimiter; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferCompressedReq; +import org.apache.iotdb.collector.plugin.builtin.sink.receiver.PipeReceiverStatusHandler; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.NodeUrlUtils; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.apache.iotdb.pipe.api.PipeConnector; +import org.apache.iotdb.pipe.api.customizer.configuration.PipeConnectorRuntimeConfiguration; +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator; +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters; +import org.apache.iotdb.pipe.api.exception.PipeParameterNotValidException; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_COMPRESSOR_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_COMPRESSOR_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_COMPRESSOR_SET; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_COMPRESSOR_ZSTD_LEVEL_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_COMPRESSOR_ZSTD_LEVEL_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_COMPRESSOR_ZSTD_LEVEL_MAX_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_COMPRESSOR_ZSTD_LEVEL_MIN_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_EXCEPTION_CONFLICT_RESOLVE_STRATEGY_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_EXCEPTION_CONFLICT_RESOLVE_STRATEGY_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_EXCEPTION_DATA_CONVERT_ON_TYPE_MISMATCH_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_EXCEPTION_DATA_CONVERT_ON_TYPE_MISMATCH_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_FORMAT_HYBRID_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_FORMAT_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_FORMAT_TABLET_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_FORMAT_TS_FILE_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_BATCH_MODE_ENABLE_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_BATCH_MODE_ENABLE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_BATCH_SIZE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_HOST_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_IP_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_NODE_URLS_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_PASSWORD_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_PASSWORD_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_PLAIN_BATCH_SIZE_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_PORT_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_USERNAME_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_USER_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_IOTDB_USER_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_BALANCE_ROUND_ROBIN_STRATEGY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_BALANCE_STRATEGY_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_BALANCE_STRATEGY_SET; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_TSFILE_STRATEGY_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_TSFILE_STRATEGY_SET; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_TSFILE_STRATEGY_SYNC_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_TSFILE_VALIDATION_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_TSFILE_VALIDATION_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_MARK_AS_PIPE_REQUEST_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_MARK_AS_PIPE_REQUEST_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_RATE_LIMIT_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_RATE_LIMIT_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_COMPRESSOR_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_COMPRESSOR_ZSTD_LEVEL_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_EXCEPTION_CONFLICT_RESOLVE_STRATEGY_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_EXCEPTION_DATA_CONVERT_ON_TYPE_MISMATCH_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_FORMAT_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_BATCH_MODE_ENABLE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_BATCH_SIZE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_HOST_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_IP_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_NODE_URLS_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_PASSWORD_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_PORT_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_USERNAME_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_USER_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_LOAD_BALANCE_STRATEGY_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_LOAD_TSFILE_STRATEGY_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_LOAD_TSFILE_VALIDATION_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_MARK_AS_PIPE_REQUEST_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_RATE_LIMIT_KEY; + +@TreeModel +@TableModel +public abstract class IoTDBConnector implements PipeConnector { + + private static final String PARSE_URL_ERROR_FORMATTER = + "Exception occurred while parsing node urls from target servers: {}"; + private static final String PARSE_URL_ERROR_MESSAGE = + "Error occurred while parsing node urls from target servers, please check the specified 'host':'port' or 'node-urls'"; + + private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBConnector.class); + + protected final List nodeUrls = new ArrayList<>(); + + protected String username = CONNECTOR_IOTDB_USER_DEFAULT_VALUE; + protected String password = CONNECTOR_IOTDB_PASSWORD_DEFAULT_VALUE; + + protected String loadBalanceStrategy; + + protected String loadTsFileStrategy; + protected boolean loadTsFileValidation; + + protected boolean shouldMarkAsPipeRequest; + + private boolean isRpcCompressionEnabled; + private final List compressors = new ArrayList<>(); + + private static final Map, PipeEndPointRateLimiter> + PIPE_END_POINT_RATE_LIMITER_MAP = new ConcurrentHashMap<>(); + private double endPointRateLimitBytesPerSecond = -1; + private static final GlobalRateLimiter GLOBAL_RATE_LIMITER = new GlobalRateLimiter(); + + protected boolean isTabletBatchModeEnabled = true; + + protected PipeReceiverStatusHandler receiverStatusHandler; + protected boolean shouldReceiverConvertOnTypeMismatch = + CONNECTOR_EXCEPTION_DATA_CONVERT_ON_TYPE_MISMATCH_DEFAULT_VALUE; + + @Override + public void validate(final PipeParameterValidator validator) throws Exception { + final PipeParameters parameters = validator.getParameters(); + + validator.validate( + args -> + (boolean) args[0] + || (((boolean) args[1] || (boolean) args[2]) && (boolean) args[3]) + || (boolean) args[4] + || (((boolean) args[5] || (boolean) args[6]) && (boolean) args[7]), + String.format( + "One of %s, %s:%s, %s, %s:%s must be specified", + CONNECTOR_IOTDB_NODE_URLS_KEY, + CONNECTOR_IOTDB_HOST_KEY, + CONNECTOR_IOTDB_PORT_KEY, + SINK_IOTDB_NODE_URLS_KEY, + SINK_IOTDB_HOST_KEY, + SINK_IOTDB_PORT_KEY), + parameters.hasAttribute(CONNECTOR_IOTDB_NODE_URLS_KEY), + parameters.hasAttribute(CONNECTOR_IOTDB_IP_KEY), + parameters.hasAttribute(CONNECTOR_IOTDB_HOST_KEY), + parameters.hasAttribute(CONNECTOR_IOTDB_PORT_KEY), + parameters.hasAttribute(SINK_IOTDB_NODE_URLS_KEY), + parameters.hasAttribute(SINK_IOTDB_IP_KEY), + parameters.hasAttribute(SINK_IOTDB_HOST_KEY), + parameters.hasAttribute(SINK_IOTDB_PORT_KEY)); + + validator.validate( + requestMaxBatchSizeInBytes -> (long) requestMaxBatchSizeInBytes > 0, + String.format( + "%s must be > 0, but got %s", + SINK_IOTDB_BATCH_SIZE_KEY, + parameters.getLongOrDefault( + Arrays.asList(CONNECTOR_IOTDB_BATCH_SIZE_KEY, SINK_IOTDB_BATCH_SIZE_KEY), + CONNECTOR_IOTDB_PLAIN_BATCH_SIZE_DEFAULT_VALUE)), + parameters.getLongOrDefault( + Arrays.asList(CONNECTOR_IOTDB_BATCH_SIZE_KEY, SINK_IOTDB_BATCH_SIZE_KEY), + CONNECTOR_IOTDB_PLAIN_BATCH_SIZE_DEFAULT_VALUE)); + + // Check coexistence of user and username + validator.validateSynonymAttributes( + Arrays.asList(CONNECTOR_IOTDB_USER_KEY, SINK_IOTDB_USER_KEY), + Arrays.asList(CONNECTOR_IOTDB_USERNAME_KEY, SINK_IOTDB_USERNAME_KEY), + false); + + username = + parameters.getStringOrDefault( + Arrays.asList( + CONNECTOR_IOTDB_USER_KEY, + SINK_IOTDB_USER_KEY, + CONNECTOR_IOTDB_USERNAME_KEY, + SINK_IOTDB_USERNAME_KEY), + CONNECTOR_IOTDB_USER_DEFAULT_VALUE); + password = + parameters.getStringOrDefault( + Arrays.asList(CONNECTOR_IOTDB_PASSWORD_KEY, SINK_IOTDB_PASSWORD_KEY), + CONNECTOR_IOTDB_PASSWORD_DEFAULT_VALUE); + + loadBalanceStrategy = + parameters + .getStringOrDefault( + Arrays.asList(CONNECTOR_LOAD_BALANCE_STRATEGY_KEY, SINK_LOAD_BALANCE_STRATEGY_KEY), + CONNECTOR_LOAD_BALANCE_ROUND_ROBIN_STRATEGY) + .trim() + .toLowerCase(); + validator.validate( + arg -> CONNECTOR_LOAD_BALANCE_STRATEGY_SET.contains(loadBalanceStrategy), + String.format( + "Load balance strategy should be one of %s, but got %s.", + CONNECTOR_LOAD_BALANCE_STRATEGY_SET, loadBalanceStrategy), + loadBalanceStrategy); + + loadTsFileStrategy = + parameters + .getStringOrDefault( + Arrays.asList(CONNECTOR_LOAD_TSFILE_STRATEGY_KEY, SINK_LOAD_TSFILE_STRATEGY_KEY), + CONNECTOR_LOAD_TSFILE_STRATEGY_SYNC_VALUE) + .trim() + .toLowerCase(); + validator.validate( + arg -> CONNECTOR_LOAD_TSFILE_STRATEGY_SET.contains(loadTsFileStrategy), + String.format( + "Load tsfile strategy should be one of %s, but got %s.", + CONNECTOR_LOAD_TSFILE_STRATEGY_SET, loadTsFileStrategy), + loadTsFileStrategy); + loadTsFileValidation = + parameters.getBooleanOrDefault( + Arrays.asList(CONNECTOR_LOAD_TSFILE_VALIDATION_KEY, SINK_LOAD_TSFILE_VALIDATION_KEY), + CONNECTOR_LOAD_TSFILE_VALIDATION_DEFAULT_VALUE); + + final int zstdCompressionLevel = + parameters.getIntOrDefault( + Arrays.asList(CONNECTOR_COMPRESSOR_ZSTD_LEVEL_KEY, SINK_COMPRESSOR_ZSTD_LEVEL_KEY), + CONNECTOR_COMPRESSOR_ZSTD_LEVEL_DEFAULT_VALUE); + validator.validate( + arg -> + (int) arg >= CONNECTOR_COMPRESSOR_ZSTD_LEVEL_MIN_VALUE + && (int) arg <= CONNECTOR_COMPRESSOR_ZSTD_LEVEL_MAX_VALUE, + String.format( + "Zstd compression level should be in the range [%d, %d], but got %d.", + CONNECTOR_COMPRESSOR_ZSTD_LEVEL_MIN_VALUE, + CONNECTOR_COMPRESSOR_ZSTD_LEVEL_MAX_VALUE, + zstdCompressionLevel), + zstdCompressionLevel); + + final String compressionTypes = + parameters + .getStringOrDefault( + Arrays.asList(CONNECTOR_COMPRESSOR_KEY, SINK_COMPRESSOR_KEY), + CONNECTOR_COMPRESSOR_DEFAULT_VALUE) + .toLowerCase(); + if (!compressionTypes.isEmpty()) { + for (final String compressionType : compressionTypes.split(",")) { + final String trimmedCompressionType = compressionType.trim(); + if (trimmedCompressionType.isEmpty()) { + continue; + } + + validator.validate( + arg -> CONNECTOR_COMPRESSOR_SET.contains(trimmedCompressionType), + String.format( + "Compressor should be one of %s, but got %s.", + CONNECTOR_COMPRESSOR_SET, trimmedCompressionType), + trimmedCompressionType); + compressors.add( + PipeCompressorFactory.getCompressor( + new PipeCompressorConfig(trimmedCompressionType, zstdCompressionLevel))); + } + } + validator.validate( + arg -> compressors.size() <= Byte.MAX_VALUE, + String.format( + "The number of compressors should be less than or equal to %d, but got %d.", + Byte.MAX_VALUE, compressors.size()), + compressors.size()); + isRpcCompressionEnabled = !compressors.isEmpty(); + + endPointRateLimitBytesPerSecond = + parameters.getDoubleOrDefault( + Arrays.asList(CONNECTOR_RATE_LIMIT_KEY, SINK_RATE_LIMIT_KEY), + CONNECTOR_RATE_LIMIT_DEFAULT_VALUE); + validator.validate( + arg -> endPointRateLimitBytesPerSecond <= Double.MAX_VALUE, + String.format( + "Rate limit should be in the range (0, %f], but got %f.", + Double.MAX_VALUE, endPointRateLimitBytesPerSecond), + endPointRateLimitBytesPerSecond); + + validator.validate( + arg -> arg.equals("retry") || arg.equals("ignore"), + String.format( + "The value of key %s or %s must be either 'retry' or 'ignore'.", + CONNECTOR_EXCEPTION_CONFLICT_RESOLVE_STRATEGY_KEY, + SINK_EXCEPTION_CONFLICT_RESOLVE_STRATEGY_KEY), + parameters + .getStringOrDefault( + Arrays.asList( + CONNECTOR_EXCEPTION_CONFLICT_RESOLVE_STRATEGY_KEY, + SINK_EXCEPTION_CONFLICT_RESOLVE_STRATEGY_KEY), + CONNECTOR_EXCEPTION_CONFLICT_RESOLVE_STRATEGY_DEFAULT_VALUE) + .trim() + .toLowerCase()); + + validator.validateAttributeValueRange( + validator.getParameters().hasAttribute(CONNECTOR_FORMAT_KEY) + ? CONNECTOR_FORMAT_KEY + : SINK_FORMAT_KEY, + true, + CONNECTOR_FORMAT_TABLET_VALUE, + CONNECTOR_FORMAT_HYBRID_VALUE, + CONNECTOR_FORMAT_TS_FILE_VALUE); + } + + @Override + public void customize( + final PipeParameters parameters, final PipeConnectorRuntimeConfiguration configuration) + throws Exception { + nodeUrls.clear(); + nodeUrls.addAll(parseNodeUrls(parameters)); + LOGGER.info("IoTDBConnector nodeUrls: {}", nodeUrls); + + isTabletBatchModeEnabled = + parameters.getBooleanOrDefault( + Arrays.asList( + CONNECTOR_IOTDB_BATCH_MODE_ENABLE_KEY, SINK_IOTDB_BATCH_MODE_ENABLE_KEY), + CONNECTOR_IOTDB_BATCH_MODE_ENABLE_DEFAULT_VALUE) + || parameters + .getStringOrDefault( + Arrays.asList(CONNECTOR_FORMAT_KEY, SINK_FORMAT_KEY), + CONNECTOR_FORMAT_HYBRID_VALUE) + .equals(CONNECTOR_FORMAT_TS_FILE_VALUE); + LOGGER.info("IoTDBConnector isTabletBatchModeEnabled: {}", isTabletBatchModeEnabled); + + shouldMarkAsPipeRequest = + parameters.getBooleanOrDefault( + Arrays.asList(CONNECTOR_MARK_AS_PIPE_REQUEST_KEY, SINK_MARK_AS_PIPE_REQUEST_KEY), + CONNECTOR_MARK_AS_PIPE_REQUEST_DEFAULT_VALUE); + LOGGER.info("IoTDBConnector shouldMarkAsPipeRequest: {}", shouldMarkAsPipeRequest); + + shouldReceiverConvertOnTypeMismatch = + parameters.getBooleanOrDefault( + Arrays.asList( + CONNECTOR_EXCEPTION_DATA_CONVERT_ON_TYPE_MISMATCH_KEY, + SINK_EXCEPTION_DATA_CONVERT_ON_TYPE_MISMATCH_KEY), + CONNECTOR_EXCEPTION_DATA_CONVERT_ON_TYPE_MISMATCH_DEFAULT_VALUE); + LOGGER.info( + "IoTDBConnector {} = {}", + CONNECTOR_EXCEPTION_DATA_CONVERT_ON_TYPE_MISMATCH_KEY, + shouldReceiverConvertOnTypeMismatch); + } + + protected LinkedHashSet parseNodeUrls(final PipeParameters parameters) + throws PipeParameterNotValidException { + final LinkedHashSet givenNodeUrls = new LinkedHashSet<>(nodeUrls); + + try { + if (parameters.hasAttribute(CONNECTOR_IOTDB_IP_KEY) + && parameters.hasAttribute(CONNECTOR_IOTDB_PORT_KEY)) { + givenNodeUrls.add( + new TEndPoint( + parameters.getStringByKeys(CONNECTOR_IOTDB_IP_KEY), + parameters.getIntByKeys(CONNECTOR_IOTDB_PORT_KEY))); + } + + if (parameters.hasAttribute(SINK_IOTDB_IP_KEY) + && parameters.hasAttribute(SINK_IOTDB_PORT_KEY)) { + givenNodeUrls.add( + new TEndPoint( + parameters.getStringByKeys(SINK_IOTDB_IP_KEY), + parameters.getIntByKeys(SINK_IOTDB_PORT_KEY))); + } + + if (parameters.hasAttribute(CONNECTOR_IOTDB_HOST_KEY) + && parameters.hasAttribute(CONNECTOR_IOTDB_PORT_KEY)) { + givenNodeUrls.add( + new TEndPoint( + parameters.getStringByKeys(CONNECTOR_IOTDB_HOST_KEY), + parameters.getIntByKeys(CONNECTOR_IOTDB_PORT_KEY))); + } + + if (parameters.hasAttribute(SINK_IOTDB_HOST_KEY) + && parameters.hasAttribute(SINK_IOTDB_PORT_KEY)) { + givenNodeUrls.add( + new TEndPoint( + parameters.getStringByKeys(SINK_IOTDB_HOST_KEY), + parameters.getIntByKeys(SINK_IOTDB_PORT_KEY))); + } + + if (parameters.hasAttribute(CONNECTOR_IOTDB_NODE_URLS_KEY)) { + givenNodeUrls.addAll( + NodeUrlUtils.parseTEndPointUrls( + Arrays.asList( + parameters + .getStringByKeys(CONNECTOR_IOTDB_NODE_URLS_KEY) + .replace(" ", "") + .split(",")))); + } + + if (parameters.hasAttribute(SINK_IOTDB_NODE_URLS_KEY)) { + givenNodeUrls.addAll( + NodeUrlUtils.parseTEndPointUrls( + Arrays.asList( + parameters + .getStringByKeys(SINK_IOTDB_NODE_URLS_KEY) + .replace(" ", "") + .split(",")))); + } + } catch (final Exception e) { + LOGGER.warn(PARSE_URL_ERROR_FORMATTER, e.toString()); + throw new PipeParameterNotValidException(PARSE_URL_ERROR_MESSAGE); + } + + checkNodeUrls(givenNodeUrls); + + return givenNodeUrls; + } + + private void checkNodeUrls(final Set nodeUrls) throws PipeParameterNotValidException { + for (final TEndPoint nodeUrl : nodeUrls) { + if (Objects.isNull(nodeUrl.ip) || nodeUrl.ip.isEmpty()) { + LOGGER.warn(PARSE_URL_ERROR_FORMATTER, "host cannot be empty"); + throw new PipeParameterNotValidException(PARSE_URL_ERROR_MESSAGE); + } + if (nodeUrl.port == 0) { + LOGGER.warn(PARSE_URL_ERROR_FORMATTER, "port cannot be empty"); + throw new PipeParameterNotValidException(PARSE_URL_ERROR_MESSAGE); + } + } + } + + @Override + public void close() { + // TODO: Not all the limiters should be closed here, but it's fine for now. + PIPE_END_POINT_RATE_LIMITER_MAP.clear(); + } + + protected TPipeTransferReq compressIfNeeded(final TPipeTransferReq req) throws IOException { + return isRpcCompressionEnabled + ? PipeTransferCompressedReq.toTPipeTransferReq(req, compressors) + : req; + } + + protected byte[] compressIfNeeded(final byte[] reqInBytes) throws IOException { + return isRpcCompressionEnabled + ? PipeTransferCompressedReq.toTPipeTransferReqBytes(reqInBytes, compressors) + : reqInBytes; + } + + public boolean isRpcCompressionEnabled() { + return isRpcCompressionEnabled; + } + + public List getCompressors() { + return compressors; + } + + public void rateLimitIfNeeded( + final String pipeName, + final long creationTime, + final TEndPoint endPoint, + final long bytesLength) { + if (pipeName != null && endPointRateLimitBytesPerSecond > 0) { + PIPE_END_POINT_RATE_LIMITER_MAP + .computeIfAbsent( + new Pair<>(pipeName, creationTime), + endpoint -> + new PipeEndPointRateLimiter( + pipeName, creationTime, endPointRateLimitBytesPerSecond)) + .acquire(endPoint, bytesLength); + } + + GLOBAL_RATE_LIMITER.acquire(bytesLength); + } + + /** + * When a pipe is dropped, the connector maybe reused and will not be closed. We need to discard + * its batched or queued events in the output pipe connector. + */ + public synchronized void discardEventsOfPipe(final String pipeName, final int regionId) { + // Do nothing by default + } + + public PipeReceiverStatusHandler statusHandler() { + return receiverStatusHandler; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBDataNodeAirGapConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBDataNodeAirGapConnector.java new file mode 100644 index 00000000..f1188192 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBDataNodeAirGapConnector.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.airgap; + +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.collector.plugin.builtin.annotation.TableModel; +import org.apache.iotdb.collector.plugin.builtin.annotation.TreeModel; +import org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeTransferHandshakeConstant; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferDataNodeHandshakeV1Req; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferDataNodeHandshakeV2Req; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.NodeUrlUtils; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.Set; +import java.util.stream.Collectors; + +@TreeModel +@TableModel +public abstract class IoTDBDataNodeAirGapConnector extends IoTDBAirGapConnector { + + private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBDataNodeAirGapConnector.class); + + @Override + public void validate(final PipeParameterValidator validator) throws Exception { + super.validate(validator); + + final Set givenNodeUrls = parseNodeUrls(validator.getParameters()); + + validator.validate( + empty -> { + try { + // Ensure the sink doesn't point to the air gap receiver on DataNode itself + return !(PipeOptions.PIPE_AIR_GAP_RECEIVER_ENABLED.value() + && NodeUrlUtils.containsLocalAddress( + givenNodeUrls.stream() + .filter( + tEndPoint -> + tEndPoint.getPort() + == PipeOptions.PIPE_AIR_GAP_RECEIVER_PORT.value()) + .map(TEndPoint::getIp) + .collect(Collectors.toList()))); + } catch (final UnknownHostException e) { + LOGGER.warn("Unknown host when checking pipe sink IP.", e); + return false; + } + }, + String.format( + "One of the endpoints %s of the receivers is pointing back to the air gap receiver %s on sender itself, or unknown host when checking pipe sink IP.", + givenNodeUrls, + new TEndPoint( + PipeOptions.RPC_ADDRESS.value(), PipeOptions.PIPE_AIR_GAP_RECEIVER_PORT.value()))); + } + + @Override + protected boolean mayNeedHandshakeWhenFail() { + return false; + } + + @Override + protected byte[] generateHandShakeV1Payload() throws IOException { + return PipeTransferDataNodeHandshakeV1Req.toTPipeTransferBytes( + PipeOptions.TIMESTAMP_PRECISION.value()); + } + + @Override + protected byte[] generateHandShakeV2Payload() throws IOException { + final HashMap params = new HashMap<>(); + params.put(PipeTransferHandshakeConstant.HANDSHAKE_KEY_CLUSTER_ID, ""); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_TIME_PRECISION, + PipeOptions.TIMESTAMP_PRECISION.value()); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_CONVERT_ON_TYPE_MISMATCH, + Boolean.toString(shouldReceiverConvertOnTypeMismatch)); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_LOAD_TSFILE_STRATEGY, loadTsFileStrategy); + params.put(PipeTransferHandshakeConstant.HANDSHAKE_KEY_USERNAME, username); + params.put(PipeTransferHandshakeConstant.HANDSHAKE_KEY_PASSWORD, password); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_VALIDATE_TSFILE, + Boolean.toString(loadTsFileValidation)); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_MARK_AS_PIPE_REQUEST, + Boolean.toString(shouldMarkAsPipeRequest)); + + return PipeTransferDataNodeHandshakeV2Req.toTPipeTransferBytes(params); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBDataRegionAirGapConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBDataRegionAirGapConnector.java new file mode 100644 index 00000000..0397351f --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/airgap/IoTDBDataRegionAirGapConnector.java @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.airgap; + +import org.apache.iotdb.collector.plugin.builtin.annotation.TableModel; +import org.apache.iotdb.collector.plugin.builtin.annotation.TreeModel; +import org.apache.iotdb.collector.plugin.builtin.sink.event.heartbeat.PipeHeartbeatEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.terminate.PipeTerminateEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tsfile.PipeTsFileInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTabletRawReqV2; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTsFilePieceReq; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTsFilePieceWithModReq; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTsFileSealWithModReq; +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.pipe.api.event.Event; +import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent; +import org.apache.iotdb.pipe.api.event.dml.insertion.TsFileInsertionEvent; +import org.apache.iotdb.pipe.api.exception.PipeConnectionException; +import org.apache.iotdb.pipe.api.exception.PipeException; +import org.apache.iotdb.rpc.TSStatusCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; + +@TreeModel +@TableModel +public class IoTDBDataRegionAirGapConnector extends IoTDBDataNodeAirGapConnector { + + private static final Logger LOGGER = + LoggerFactory.getLogger(IoTDBDataRegionAirGapConnector.class); + + @Override + public void transfer(final TabletInsertionEvent tabletInsertionEvent) throws Exception { + /*// PipeProcessor can change the type of TabletInsertionEvent + if (!(tabletInsertionEvent instanceof PipeRawTabletInsertionEvent)) { + LOGGER.warn( + "IoTDBDataRegionAirGapConnector only support " + + "PipeInsertNodeTabletInsertionEvent and PipeRawTabletInsertionEvent. " + + "Ignore {}.", + tabletInsertionEvent); + return; + } + + final int socketIndex = nextSocketIndex(); + final AirGapSocket socket = sockets.get(socketIndex); + + try { + doTransferWrapper(socket, (PipeRawTabletInsertionEvent) tabletInsertionEvent); + } catch (final IOException e) { + isSocketAlive.set(socketIndex, false); + + throw new PipeConnectionException( + String.format( + "Network error when transfer tablet insertion event %s, because %s.", + ((PipeRawTabletInsertionEvent) tabletInsertionEvent).coreReportMessage(), e.getMessage()), + e); + }*/ + } + + @Override + public void transfer(final TsFileInsertionEvent tsFileInsertionEvent) throws Exception { + // PipeProcessor can change the type of tsFileInsertionEvent + if (!(tsFileInsertionEvent instanceof PipeTsFileInsertionEvent)) { + LOGGER.warn( + "IoTDBDataRegionAirGapConnector only support PipeTsFileInsertionEvent. Ignore {}.", + tsFileInsertionEvent); + return; + } + + if (!((PipeTsFileInsertionEvent) tsFileInsertionEvent).waitForTsFileClose()) { + LOGGER.warn( + "Pipe skipping temporary TsFile which shouldn't be transferred: {}", + ((PipeTsFileInsertionEvent) tsFileInsertionEvent).getTsFile()); + return; + } + + final int socketIndex = nextSocketIndex(); + final AirGapSocket socket = sockets.get(socketIndex); + + try { + doTransferWrapper(socket, (PipeTsFileInsertionEvent) tsFileInsertionEvent); + } catch (final IOException e) { + isSocketAlive.set(socketIndex, false); + + throw new PipeConnectionException( + String.format( + "Network error when transfer tsfile insertion event %s, because %s.", + ((PipeTsFileInsertionEvent) tsFileInsertionEvent).coreReportMessage(), + e.getMessage()), + e); + } + } + + @Override + public void transfer(final Event event) throws Exception { + final int socketIndex = nextSocketIndex(); + final AirGapSocket socket = sockets.get(socketIndex); + + // if (event instanceof PipeDeleteDataNodeEvent) { + // doTransferWrapper(socket, (PipeDeleteDataNodeEvent) event); + // } else + if (!(event instanceof PipeHeartbeatEvent || event instanceof PipeTerminateEvent)) { + LOGGER.warn( + "IoTDBDataRegionAirGapConnector does not support transferring generic event: {}.", event); + } + } + + private void doTransferWrapper( + final AirGapSocket socket, final PipeTsFileInsertionEvent pipeTsFileInsertionEvent) + throws PipeException, IOException { + // We increase the reference count for this event to determine if the event may be released. + if (!pipeTsFileInsertionEvent.increaseReferenceCount( + IoTDBDataRegionAirGapConnector.class.getName())) { + return; + } + try { + doTransfer(socket, pipeTsFileInsertionEvent); + } finally { + pipeTsFileInsertionEvent.decreaseReferenceCount( + IoTDBDataRegionAirGapConnector.class.getName(), false); + } + } + + private void doTransfer( + final AirGapSocket socket, final PipeTsFileInsertionEvent pipeTsFileInsertionEvent) + throws PipeException, IOException { + final String pipeName = pipeTsFileInsertionEvent.getPipeName(); + final long creationTime = pipeTsFileInsertionEvent.getCreationTime(); + final File tsFile = pipeTsFileInsertionEvent.getTsFile(); + final String errorMessage = String.format("Seal file %s error. Socket %s.", tsFile, socket); + + // 1. Transfer file piece by piece, and mod if needed + if (pipeTsFileInsertionEvent.isWithMod() && supportModsIfIsDataNodeReceiver) { + final File modFile = pipeTsFileInsertionEvent.getModFile(); + transferFilePieces(pipeName, creationTime, modFile, socket, true); + transferFilePieces(pipeName, creationTime, tsFile, socket, true); + // 2. Transfer file seal signal with mod, which means the file is transferred completely + if (!send( + pipeName, + creationTime, + socket, + PipeTransferTsFileSealWithModReq.toTPipeTransferBytes( + modFile.getName(), + modFile.length(), + tsFile.getName(), + tsFile.length(), + pipeTsFileInsertionEvent.isTableModelEvent() + ? pipeTsFileInsertionEvent.getTableModelDatabaseName() + : null))) { + receiverStatusHandler.handle( + new TSStatus(TSStatusCode.PIPE_RECEIVER_USER_CONFLICT_EXCEPTION.getStatusCode()) + .setMessage(errorMessage), + errorMessage, + pipeTsFileInsertionEvent.toString()); + } else { + LOGGER.info("Successfully transferred file {}.", tsFile); + } + } else { + transferFilePieces(pipeName, creationTime, tsFile, socket, false); + // 2. Transfer file seal signal without mod, which means the file is transferred completely + if (!send( + pipeName, + creationTime, + socket, + PipeTransferTsFileSealWithModReq.toTPipeTransferBytes( + tsFile.getName(), + tsFile.length(), + pipeTsFileInsertionEvent.isTableModelEvent() + ? pipeTsFileInsertionEvent.getTableModelDatabaseName() + : null))) { + receiverStatusHandler.handle( + new TSStatus(TSStatusCode.PIPE_RECEIVER_USER_CONFLICT_EXCEPTION.getStatusCode()) + .setMessage(errorMessage), + errorMessage, + pipeTsFileInsertionEvent.toString()); + } else { + LOGGER.info("Successfully transferred file {}.", tsFile); + } + } + } + + @Override + protected byte[] getTransferSingleFilePieceBytes( + final String fileName, final long position, final byte[] payLoad) throws IOException { + return PipeTransferTsFilePieceReq.toTPipeTransferBytes(fileName, position, payLoad); + } + + @Override + protected byte[] getTransferMultiFilePieceBytes( + final String fileName, final long position, final byte[] payLoad) throws IOException { + return PipeTransferTsFilePieceWithModReq.toTPipeTransferBytes(fileName, position, payLoad); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/IoTDBDataRegionAsyncConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/IoTDBDataRegionAsyncConnector.java new file mode 100644 index 00000000..974aad9c --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/IoTDBDataRegionAsyncConnector.java @@ -0,0 +1,575 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async; + +import com.google.common.collect.ImmutableSet; +import org.apache.iotdb.collector.plugin.builtin.annotation.TableModel; +import org.apache.iotdb.collector.plugin.builtin.annotation.TreeModel; +import org.apache.iotdb.collector.plugin.builtin.sink.event.heartbeat.PipeHeartbeatEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.terminate.PipeTerminateEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tsfile.PipeTsFileInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch.PipeTabletEventBatch; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch.PipeTabletEventPlainBatch; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch.PipeTabletEventTsFileBatch; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch.PipeTransferBatchReqBuilder; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTabletRawReqV2; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.airgap.IoTDBConnector; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.handler.PipeTransferTabletBatchEventHandler; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.handler.PipeTransferTabletRawEventHandler; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.handler.PipeTransferTrackableHandler; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.handler.PipeTransferTsFileHandler; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.IoTDBDataNodeAsyncClientManager; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.async.AsyncPipeDataTransferServiceClient; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.sync.IoTDBDataRegionSyncConnector; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +// import org.apache.iotdb.db.pipe.agent.task.subtask.connector.PipeConnectorSubtask; +import org.apache.iotdb.pipe.api.PipeConnector; +import org.apache.iotdb.pipe.api.customizer.configuration.PipeConnectorRuntimeConfiguration; +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator; +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters; +import org.apache.iotdb.pipe.api.event.Event; +import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent; +import org.apache.iotdb.pipe.api.event.dml.insertion.TsFileInsertionEvent; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.exception.write.WriteProcessException; +import org.apache.tsfile.utils.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LEADER_CACHE_ENABLE_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LEADER_CACHE_ENABLE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_SSL_ENABLE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_SSL_TRUST_STORE_PATH_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_SSL_TRUST_STORE_PWD_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_LEADER_CACHE_ENABLE_KEY; + +@TreeModel +@TableModel +public class IoTDBDataRegionAsyncConnector extends IoTDBConnector { + + private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBDataRegionAsyncConnector.class); + + private static final String THRIFT_ERROR_FORMATTER_WITHOUT_ENDPOINT = + "Failed to borrow client from client pool when sending to receiver."; + private static final String THRIFT_ERROR_FORMATTER_WITH_ENDPOINT = + "Exception occurred while sending to receiver %s:%s."; + + private final IoTDBDataRegionSyncConnector retryConnector = new IoTDBDataRegionSyncConnector(); + private final BlockingQueue retryEventQueue = new LinkedBlockingQueue<>(); + + private IoTDBDataNodeAsyncClientManager clientManager; + + private PipeTransferBatchReqBuilder tabletBatchBuilder; + + // use these variables to prevent reference count leaks under some corner cases when closing + private final AtomicBoolean isClosed = new AtomicBoolean(false); + private final Map pendingHandlers = + new ConcurrentHashMap<>(); + + @Override + public void validate(final PipeParameterValidator validator) throws Exception { + super.validate(validator); + retryConnector.validate(validator); + + final PipeParameters parameters = validator.getParameters(); + + validator.validate( + args -> !((boolean) args[0] || (boolean) args[1] || (boolean) args[2]), + "Only 'iotdb-thrift-ssl-sink' supports SSL transmission currently.", + parameters.getBooleanOrDefault(SINK_IOTDB_SSL_ENABLE_KEY, false), + parameters.hasAttribute(SINK_IOTDB_SSL_TRUST_STORE_PATH_KEY), + parameters.hasAttribute(SINK_IOTDB_SSL_TRUST_STORE_PWD_KEY)); + } + + @Override + public void customize( + final PipeParameters parameters, final PipeConnectorRuntimeConfiguration configuration) + throws Exception { + super.customize(parameters, configuration); + retryConnector.customize(parameters, configuration); + + clientManager = + new IoTDBDataNodeAsyncClientManager( + nodeUrls, + parameters.getBooleanOrDefault( + Arrays.asList(SINK_LEADER_CACHE_ENABLE_KEY, CONNECTOR_LEADER_CACHE_ENABLE_KEY), + CONNECTOR_LEADER_CACHE_ENABLE_DEFAULT_VALUE), + loadBalanceStrategy, + username, + password, + shouldReceiverConvertOnTypeMismatch, + loadTsFileStrategy, + loadTsFileValidation, + shouldMarkAsPipeRequest); + + if (isTabletBatchModeEnabled) { + tabletBatchBuilder = new PipeTransferBatchReqBuilder(parameters); + } + } + + @Override + // Synchronized to avoid close connector when transfer event + public synchronized void handshake() throws Exception { + retryConnector.handshake(); + } + + @Override + public void heartbeat() { + retryConnector.heartbeat(); + } + + @Override + public void transfer(final TabletInsertionEvent tabletInsertionEvent) throws Exception { + transferQueuedEventsIfNecessary(); + + if (!(tabletInsertionEvent instanceof PipeRawTabletInsertionEvent)) { + LOGGER.warn( + "IoTDBThriftAsyncConnector only support PipeRawTabletInsertionEvent. " + + "Current event: {}.", + tabletInsertionEvent); + return; + } + + if (isTabletBatchModeEnabled) { + final Pair endPointAndBatch = + tabletBatchBuilder.onEvent(tabletInsertionEvent); + if (Objects.isNull(endPointAndBatch)) { + return; + } + transferInBatchWithoutCheck(endPointAndBatch); + } else { + transferInEventWithoutCheck(tabletInsertionEvent); + } + } + + private void transferInBatchWithoutCheck( + final Pair endPointAndBatch) + throws IOException, WriteProcessException, InterruptedException { + final PipeTabletEventBatch batch = endPointAndBatch.getRight(); + + if (batch instanceof PipeTabletEventPlainBatch) { + transfer( + endPointAndBatch.getLeft(), + new PipeTransferTabletBatchEventHandler((PipeTabletEventPlainBatch) batch, this)); + } else if (batch instanceof PipeTabletEventTsFileBatch) { + final PipeTabletEventTsFileBatch tsFileBatch = (PipeTabletEventTsFileBatch) batch; + final List> dbTsFilePairs = tsFileBatch.sealTsFiles(); + final Map, Double> pipe2WeightMap = tsFileBatch.deepCopyPipe2WeightMap(); + final List events = tsFileBatch.deepCopyEvents(); + final AtomicInteger eventsReferenceCount = new AtomicInteger(dbTsFilePairs.size()); + final AtomicBoolean eventsHadBeenAddedToRetryQueue = new AtomicBoolean(false); + + for (final Pair sealedFile : dbTsFilePairs) { + transfer( + new PipeTransferTsFileHandler( + this, + pipe2WeightMap, + events, + eventsReferenceCount, + eventsHadBeenAddedToRetryQueue, + sealedFile.right, + null, + false, + sealedFile.left)); + } + } else { + LOGGER.warn( + "Unsupported batch type {} when transferring tablet insertion event.", batch.getClass()); + } + + endPointAndBatch.getRight().onSuccess(); + } + + private void transferInEventWithoutCheck(final TabletInsertionEvent tabletInsertionEvent) + throws Exception { + // tabletInsertionEvent instanceof PipeRawTabletInsertionEvent + final PipeRawTabletInsertionEvent pipeRawTabletInsertionEvent = + (PipeRawTabletInsertionEvent) tabletInsertionEvent; + // We increase the reference count for this event to determine if the event may be released. + if (!pipeRawTabletInsertionEvent.increaseReferenceCount( + IoTDBDataRegionAsyncConnector.class.getName())) { + return; + } + + final TPipeTransferReq pipeTransferTabletRawReq = + compressIfNeeded( + PipeTransferTabletRawReqV2.toTPipeTransferReq( + pipeRawTabletInsertionEvent.convertToTablet(), + pipeRawTabletInsertionEvent.isAligned(), + pipeRawTabletInsertionEvent.isTableModelEvent() + ? pipeRawTabletInsertionEvent.getTableModelDatabaseName() + : null)); + final PipeTransferTabletRawEventHandler pipeTransferTabletReqHandler = + new PipeTransferTabletRawEventHandler( + pipeRawTabletInsertionEvent, pipeTransferTabletRawReq, this); + + transfer(pipeRawTabletInsertionEvent.getDeviceId(), pipeTransferTabletReqHandler); + } + + private void transfer( + final TEndPoint endPoint, + final PipeTransferTabletBatchEventHandler pipeTransferTabletBatchEventHandler) { + AsyncPipeDataTransferServiceClient client = null; + try { + client = clientManager.borrowClient(endPoint); + pipeTransferTabletBatchEventHandler.transfer(client); + } catch (final Exception ex) { + logOnClientException(client, ex); + pipeTransferTabletBatchEventHandler.onError(ex); + } + } + + private void transfer( + final String deviceId, final PipeTransferTabletRawEventHandler pipeTransferTabletReqHandler) { + AsyncPipeDataTransferServiceClient client = null; + try { + client = clientManager.borrowClient(deviceId); + pipeTransferTabletReqHandler.transfer(client); + } catch (final Exception ex) { + logOnClientException(client, ex); + pipeTransferTabletReqHandler.onError(ex); + } + } + + @Override + public void transfer(final TsFileInsertionEvent tsFileInsertionEvent) throws Exception { + transferQueuedEventsIfNecessary(); + transferBatchedEventsIfNecessary(); + + if (!(tsFileInsertionEvent instanceof PipeTsFileInsertionEvent)) { + LOGGER.warn( + "IoTDBThriftAsyncConnector only support PipeTsFileInsertionEvent. Current event: {}.", + tsFileInsertionEvent); + return; + } + + transferWithoutCheck(tsFileInsertionEvent); + } + + private void transferWithoutCheck(final TsFileInsertionEvent tsFileInsertionEvent) + throws Exception { + final PipeTsFileInsertionEvent pipeTsFileInsertionEvent = + (PipeTsFileInsertionEvent) tsFileInsertionEvent; + // We increase the reference count for this event to determine if the event may be released. + if (!pipeTsFileInsertionEvent.increaseReferenceCount( + IoTDBDataRegionAsyncConnector.class.getName())) { + return; + } + + // We assume that no exceptions will be thrown after reference count is increased. + try { + // Just in case. To avoid the case that exception occurred when constructing the handler. + if (!pipeTsFileInsertionEvent.getTsFile().exists()) { + throw new FileNotFoundException(pipeTsFileInsertionEvent.getTsFile().getAbsolutePath()); + } + + final PipeTransferTsFileHandler pipeTransferTsFileHandler = + new PipeTransferTsFileHandler( + this, + Collections.singletonMap( + new Pair<>( + pipeTsFileInsertionEvent.getPipeName(), + pipeTsFileInsertionEvent.getCreationTime()), + 1.0), + Collections.singletonList(pipeTsFileInsertionEvent), + new AtomicInteger(1), + new AtomicBoolean(false), + pipeTsFileInsertionEvent.getTsFile(), + pipeTsFileInsertionEvent.getModFile(), + pipeTsFileInsertionEvent.isWithMod() + && clientManager.supportModsIfIsDataNodeReceiver(), + pipeTsFileInsertionEvent.isTableModelEvent() + ? pipeTsFileInsertionEvent.getTableModelDatabaseName() + : null); + + transfer(pipeTransferTsFileHandler); + } catch (final Exception e) { + throw e; + } + } + + private void transfer(final PipeTransferTsFileHandler pipeTransferTsFileHandler) { + AsyncPipeDataTransferServiceClient client = null; + try { + client = clientManager.borrowClient(); + pipeTransferTsFileHandler.transfer(clientManager, client); + } catch (final Exception ex) { + logOnClientException(client, ex); + pipeTransferTsFileHandler.onError(ex); + } + } + + @Override + public void transfer(final Event event) throws Exception { + transferQueuedEventsIfNecessary(); + transferBatchedEventsIfNecessary(); + + retryConnector.transfer(event); + } + + //////////////////////////// Leader cache update //////////////////////////// + + public void updateLeaderCache(final String deviceId, final TEndPoint endPoint) { + clientManager.updateLeaderCache(deviceId, endPoint); + } + + //////////////////////////// Exception handlers //////////////////////////// + + private void logOnClientException( + final AsyncPipeDataTransferServiceClient client, final Exception e) { + if (client == null) { + LOGGER.warn(THRIFT_ERROR_FORMATTER_WITHOUT_ENDPOINT); + } else { + client.resetMethodStateIfStopped(); + LOGGER.warn( + String.format(THRIFT_ERROR_FORMATTER_WITH_ENDPOINT, client.getIp(), client.getPort()), e); + } + } + + /** + * Transfer queued {@link Event}s which are waiting for retry. + * + * @throws Exception if an error occurs. The error will be handled by pipe framework, which will + * retry the {@link Event} and mark the {@link Event} as failure and stop the pipe if the + * retry times exceeds the threshold. + * @see PipeConnector#transfer(Event) for more details. + * @see PipeConnector#transfer(TabletInsertionEvent) for more details. + * @see PipeConnector#transfer(TsFileInsertionEvent) for more details. + */ + private void transferQueuedEventsIfNecessary() throws Exception { + while (!retryEventQueue.isEmpty()) { + synchronized (this) { + if (isClosed.get() || retryEventQueue.isEmpty()) { + return; + } + + final Event peekedEvent = retryEventQueue.peek(); + + if (peekedEvent instanceof PipeRawTabletInsertionEvent) { + retryConnector.transfer((PipeRawTabletInsertionEvent) peekedEvent); + } else if (peekedEvent instanceof PipeTsFileInsertionEvent) { + retryConnector.transfer((PipeTsFileInsertionEvent) peekedEvent); + } else { + LOGGER.warn( + "IoTDBThriftAsyncConnector does not support transfer generic event: {}.", + peekedEvent); + } + + if (peekedEvent instanceof PipeRawTabletInsertionEvent) { + ((PipeRawTabletInsertionEvent) peekedEvent) + .decreaseReferenceCount(IoTDBDataRegionAsyncConnector.class.getName(), true); + } + + final Event polledEvent = retryEventQueue.poll(); + if (polledEvent != peekedEvent) { + LOGGER.error( + "The event polled from the queue is not the same as the event peeked from the queue. " + + "Peeked event: {}, polled event: {}.", + peekedEvent, + polledEvent); + } + if (polledEvent != null && LOGGER.isDebugEnabled()) { + LOGGER.debug("Polled event {} from retry queue.", polledEvent); + } + } + } + + // Trigger cron heartbeat event in retry connector to send batch in time + // TODO + // retryConnector.transfer(PipeConnectorSubtask.CRON_HEARTBEAT_EVENT); + } + + /** Try its best to commit data in order. Flush can also be a trigger to transfer batched data. */ + private void transferBatchedEventsIfNecessary() + throws IOException, WriteProcessException, InterruptedException { + if (!isTabletBatchModeEnabled || tabletBatchBuilder.isEmpty()) { + return; + } + + for (final Pair endPointAndBatch : + tabletBatchBuilder.getAllNonEmptyBatches()) { + transferInBatchWithoutCheck(endPointAndBatch); + } + } + + /** + * Add failure {@link Event} to retry queue. + * + * @param event {@link Event} to retry + */ + @SuppressWarnings("java:S899") + public void addFailureEventToRetryQueue(final Event event) { + if (event instanceof PipeRawTabletInsertionEvent && ((PipeRawTabletInsertionEvent) event).isReleased()) { + return; + } + + if (isClosed.get()) { + if (event instanceof PipeRawTabletInsertionEvent) { + ((PipeRawTabletInsertionEvent) event).clearReferenceCount(IoTDBDataRegionAsyncConnector.class.getName()); + } + return; + } + + retryEventQueue.offer(event); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Added event {} to retry queue.", event); + } + + if (isClosed.get()) { + if (event instanceof PipeRawTabletInsertionEvent) { + ((PipeRawTabletInsertionEvent) event).clearReferenceCount(IoTDBDataRegionAsyncConnector.class.getName()); + } + } + } + + /** + * Add failure {@link PipeRawTabletInsertionEvent}s to retry queue. + * + * @param events {@link PipeRawTabletInsertionEvent}s to retry + */ + public void addFailureEventsToRetryQueue(final Iterable events) { + events.forEach(this::addFailureEventToRetryQueue); + } + + public synchronized void clearRetryEventsReferenceCount() { + while (!retryEventQueue.isEmpty()) { + final Event event = retryEventQueue.poll(); + if (event instanceof PipeRawTabletInsertionEvent) { + ((PipeRawTabletInsertionEvent) event).clearReferenceCount(IoTDBDataRegionAsyncConnector.class.getName()); + } + } + } + + //////////////////////////// Operations for close //////////////////////////// + + @Override + public synchronized void discardEventsOfPipe(final String pipeNameToDrop, final int regionId) { + if (isTabletBatchModeEnabled) { + tabletBatchBuilder.discardEventsOfPipe(pipeNameToDrop, regionId); + } + retryEventQueue.removeIf( + event -> { + if (event instanceof PipeRawTabletInsertionEvent + && pipeNameToDrop.equals(((PipeRawTabletInsertionEvent) event).getPipeName()) + && regionId == ((PipeRawTabletInsertionEvent) event).getRegionId()) { + ((PipeRawTabletInsertionEvent) event) + .clearReferenceCount(IoTDBDataRegionAsyncConnector.class.getName()); + return true; + } + return false; + }); + } + + @Override + // synchronized to avoid close connector when transfer event + public synchronized void close() { + isClosed.set(true); + + retryConnector.close(); + + if (tabletBatchBuilder != null) { + tabletBatchBuilder.close(); + } + + // ensure all on-the-fly handlers have been cleared + if (hasPendingHandlers()) { + ImmutableSet.copyOf(pendingHandlers.keySet()) + .forEach( + handler -> { + handler.clearEventsReferenceCount(); + eliminateHandler(handler); + }); + } + + try { + if (clientManager != null) { + clientManager.close(); + } + } catch (final Exception e) { + LOGGER.warn("Failed to close client manager.", e); + } + + // clear reference count of events in retry queue after closing async client + clearRetryEventsReferenceCount(); + + super.close(); + } + + //////////////////////// APIs provided for metric framework //////////////////////// + + public int getRetryEventQueueSize() { + return retryEventQueue.size(); + } + + // For performance, this will not acquire lock and does not guarantee the correct + // result. However, this shall not cause any exceptions when concurrently read & written. + public int getRetryEventCount(final String pipeName) { + final AtomicInteger count = new AtomicInteger(0); + try { + retryEventQueue.forEach( + event -> { + if (event instanceof PipeRawTabletInsertionEvent + && pipeName.equals(((PipeRawTabletInsertionEvent) event).getPipeName())) { + count.incrementAndGet(); + } + }); + return count.get(); + } catch (final Exception e) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Failed to get retry event count for pipe {}.", pipeName, e); + } + return count.get(); + } + } + + //////////////////////// APIs provided for PipeTransferTrackableHandler //////////////////////// + + public boolean isClosed() { + return isClosed.get(); + } + + public void trackHandler(final PipeTransferTrackableHandler handler) { + pendingHandlers.put(handler, handler); + } + + public void eliminateHandler(final PipeTransferTrackableHandler handler) { + handler.close(); + pendingHandlers.remove(handler); + } + + public boolean hasPendingHandlers() { + return !pendingHandlers.isEmpty(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTabletBatchEventHandler.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTabletBatchEventHandler.java new file mode 100644 index 00000000..ce633c0c --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTabletBatchEventHandler.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.handler; + +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch.PipeTabletEventPlainBatch; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferCompressedReq; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.IoTDBDataRegionAsyncConnector; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.async.AsyncPipeDataTransferServiceClient; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.cacher.LeaderCacheUtils; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.pipe.api.exception.PipeException; +import org.apache.iotdb.rpc.TSStatusCode; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferResp; +import org.apache.thrift.TException; +import org.apache.tsfile.utils.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class PipeTransferTabletBatchEventHandler extends PipeTransferTrackableHandler { + + private static final Logger LOGGER = + LoggerFactory.getLogger(PipeTransferTabletBatchEventHandler.class); + + private final List events; + private final Map, Long> pipeName2BytesAccumulated; + + private final TPipeTransferReq req; + private final double reqCompressionRatio; + + public PipeTransferTabletBatchEventHandler( + final PipeTabletEventPlainBatch batch, final IoTDBDataRegionAsyncConnector connector) + throws IOException { + super(connector); + + // Deep copy to keep events' reference + events = batch.deepCopyEvents(); + pipeName2BytesAccumulated = batch.deepCopyPipeName2BytesAccumulated(); + + final TPipeTransferReq uncompressedReq = batch.toTPipeTransferReq(); + req = + connector.isRpcCompressionEnabled() + ? PipeTransferCompressedReq.toTPipeTransferReq( + uncompressedReq, connector.getCompressors()) + : uncompressedReq; + reqCompressionRatio = (double) req.getBody().length / uncompressedReq.getBody().length; + } + + public void transfer(final AsyncPipeDataTransferServiceClient client) throws TException { + for (final Map.Entry, Long> entry : pipeName2BytesAccumulated.entrySet()) { + connector.rateLimitIfNeeded( + entry.getKey().getLeft(), + entry.getKey().getRight(), + client.getEndPoint(), + (long) (entry.getValue() * reqCompressionRatio)); + } + + tryTransfer(client, req); + } + + @Override + protected boolean onCompleteInternal(final TPipeTransferResp response) { + // Just in case + if (response == null) { + onError(new PipeException("TPipeTransferResp is null")); + return false; + } + + try { + final TSStatus status = response.getStatus(); + // Only handle the failed statuses to avoid string format performance overhead + if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode() + && status.getCode() != TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode()) { + connector + .statusHandler() + .handle(status, response.getStatus().getMessage(), events.toString()); + } + for (final Pair redirectPair : + LeaderCacheUtils.parseRecommendedRedirections(status)) { + connector.updateLeaderCache(redirectPair.getLeft(), redirectPair.getRight()); + } + + events.forEach( + event -> + event.decreaseReferenceCount( + PipeTransferTabletBatchEventHandler.class.getName(), true)); + } catch (final Exception e) { + onError(e); + return false; + } + + return true; + } + + @Override + protected void onErrorInternal(final Exception exception) { + try { + LOGGER.warn( + "Failed to transfer TabletInsertionEvent batch. Total failed events: {}, related pipe names: {}", + events.size(), + events.stream().map(PipeRawTabletInsertionEvent::getPipeName).collect(Collectors.toSet()), + exception); + } finally { + connector.addFailureEventsToRetryQueue(events); + } + } + + @Override + protected void doTransfer( + final AsyncPipeDataTransferServiceClient client, final TPipeTransferReq req) + throws TException { + client.pipeTransfer(req, this); + } + + @Override + public void clearEventsReferenceCount() { + events.forEach( + event -> event.clearReferenceCount(PipeTransferTabletBatchEventHandler.class.getName())); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTabletInsertionEventHandler.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTabletInsertionEventHandler.java new file mode 100644 index 00000000..d0b705be --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTabletInsertionEventHandler.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.handler; + +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.IoTDBDataRegionAsyncConnector; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.async.AsyncPipeDataTransferServiceClient; +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.pipe.api.exception.PipeException; +import org.apache.iotdb.rpc.TSStatusCode; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferResp; +import org.apache.thrift.TException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class PipeTransferTabletInsertionEventHandler extends PipeTransferTrackableHandler { + + private static final Logger LOGGER = + LoggerFactory.getLogger(PipeTransferTabletInsertionEventHandler.class); + + protected final PipeInsertionEvent event; + protected final TPipeTransferReq req; + + protected PipeTransferTabletInsertionEventHandler( + final PipeInsertionEvent event, + final TPipeTransferReq req, + final IoTDBDataRegionAsyncConnector connector) { + super(connector); + + this.event = event; + this.req = req; + } + + public void transfer(final AsyncPipeDataTransferServiceClient client) throws TException { + connector.rateLimitIfNeeded( + event.getPipeName(), event.getCreationTime(), client.getEndPoint(), req.getBody().length); + + tryTransfer(client, req); + } + + @Override + protected boolean onCompleteInternal(final TPipeTransferResp response) { + // Just in case + if (response == null) { + onError(new PipeException("TPipeTransferResp is null")); + return false; + } + + final TSStatus status = response.getStatus(); + try { + // Only handle the failed statuses to avoid string format performance overhead + if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode() + && status.getCode() != TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode()) { + connector + .statusHandler() + .handle(response.getStatus(), response.getStatus().getMessage(), event.toString()); + } + event.decreaseReferenceCount(PipeTransferTabletInsertionEventHandler.class.getName(), true); + if (status.isSetRedirectNode()) { + updateLeaderCache(status); + } + } catch (final Exception e) { + onError(e); + return false; + } + + return true; + } + + @Override + protected void onErrorInternal(final Exception exception) { + try { + LOGGER.warn( + "Failed to transfer TabletInsertionEvent {} (committer key={}, commit id={}).", + event.coreReportMessage(), + event.getCommitterKey(), + event.getCommitId(), + exception); + } finally { + connector.addFailureEventToRetryQueue(event); + } + } + + @Override + public void clearEventsReferenceCount() { + event.clearReferenceCount(PipeTransferTabletInsertionEventHandler.class.getName()); + } + + protected abstract void updateLeaderCache(final TSStatus status); +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTabletRawEventHandler.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTabletRawEventHandler.java new file mode 100644 index 00000000..a7162200 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTabletRawEventHandler.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.handler; + +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.IoTDBDataRegionAsyncConnector; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.async.AsyncPipeDataTransferServiceClient; +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.thrift.TException; + +public class PipeTransferTabletRawEventHandler extends PipeTransferTabletInsertionEventHandler { + + public PipeTransferTabletRawEventHandler( + final PipeRawTabletInsertionEvent event, + final TPipeTransferReq req, + final IoTDBDataRegionAsyncConnector connector) { + super(event, req, connector); + } + + @Override + protected void doTransfer( + final AsyncPipeDataTransferServiceClient client, final TPipeTransferReq req) + throws TException { + client.pipeTransfer(req, this); + } + + @Override + protected void updateLeaderCache(final TSStatus status) { + connector.updateLeaderCache( + ((PipeRawTabletInsertionEvent) event).getDeviceId(), status.getRedirectNode()); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTrackableHandler.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTrackableHandler.java new file mode 100644 index 00000000..7524029a --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTrackableHandler.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.handler; + +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.IoTDBDataRegionAsyncConnector; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.async.AsyncPipeDataTransferServiceClient; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferResp; +import org.apache.thrift.TException; +import org.apache.thrift.async.AsyncMethodCallback; + +public abstract class PipeTransferTrackableHandler + implements AsyncMethodCallback, AutoCloseable { + + protected final IoTDBDataRegionAsyncConnector connector; + + public PipeTransferTrackableHandler(final IoTDBDataRegionAsyncConnector connector) { + this.connector = connector; + } + + @Override + public void onComplete(final TPipeTransferResp response) { + if (connector.isClosed()) { + clearEventsReferenceCount(); + connector.eliminateHandler(this); + return; + } + + if (onCompleteInternal(response)) { + // eliminate handler only when all transmissions corresponding to the handler have been + // completed + // NOTE: We should not clear the reference count of events, as this would cause the + // `org.apache.iotdb.pipe.it.dual.tablemodel.manual.basic.IoTDBPipeDataSinkIT#testSinkTsFileFormat3` test to fail. + connector.eliminateHandler(this); + } + } + + @Override + public void onError(final Exception exception) { + if (connector.isClosed()) { + clearEventsReferenceCount(); + connector.eliminateHandler(this); + return; + } + + onErrorInternal(exception); + connector.eliminateHandler(this); + } + + /** + * Attempts to transfer data using the provided client and request. + * + * @param client the client used for data transfer + * @param req the request containing transfer details + * @return {@code true} if the transfer was initiated successfully, {@code false} if the connector + * is closed + * @throws TException if an error occurs during the transfer + */ + protected boolean tryTransfer( + final AsyncPipeDataTransferServiceClient client, final TPipeTransferReq req) + throws TException { + // track handler before checking if connector is closed + connector.trackHandler(this); + if (connector.isClosed()) { + clearEventsReferenceCount(); + connector.eliminateHandler(this); + return false; + } + doTransfer(client, req); + return true; + } + + /** + * @return {@code true} if all transmissions corresponding to the handler have been completed, + * {@code false} otherwise + */ + protected abstract boolean onCompleteInternal(final TPipeTransferResp response); + + protected abstract void onErrorInternal(final Exception exception); + + protected abstract void doTransfer( + final AsyncPipeDataTransferServiceClient client, final TPipeTransferReq req) + throws TException; + + public abstract void clearEventsReferenceCount(); + + @Override + public void close() { + // do nothing + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTsFileHandler.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTsFileHandler.java new file mode 100644 index 00000000..f2bd8673 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/async/handler/PipeTransferTsFileHandler.java @@ -0,0 +1,442 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.handler; + +import org.apache.commons.io.FileUtils; +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tsfile.PipeTsFileInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTsFilePieceReq; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTsFilePieceWithModReq; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTsFileSealWithModReq; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferCompressedReq; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.response.PipeTransferFilePieceResp; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.async.IoTDBDataRegionAsyncConnector; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.IoTDBDataNodeAsyncClientManager; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.async.AsyncPipeDataTransferServiceClient; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.RetryUtils; +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.rpc.TSStatusCode; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferResp; +import org.apache.thrift.TException; +import org.apache.tsfile.utils.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +public class PipeTransferTsFileHandler extends PipeTransferTrackableHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(PipeTransferTsFileHandler.class); + + // Used to rate limit the transfer + private final Map, Double> pipeName2WeightMap; + + // The original events to be transferred, can be multiple events if + // the file is batched with other events + private final List events; + private final AtomicInteger eventsReferenceCount; + private final AtomicBoolean eventsHadBeenAddedToRetryQueue; + + private final File tsFile; + private final File modFile; + private File currentFile; + + private final boolean transferMod; + + private final String dataBaseName; + + private final int readFileBufferSize; + // private final PipeTsFileMemoryBlock memoryBlock; + private final byte[] readBuffer; + private long position; + + private RandomAccessFile reader; + + private final AtomicBoolean isSealSignalSent; + + private IoTDBDataNodeAsyncClientManager clientManager; + private AsyncPipeDataTransferServiceClient client; + + public PipeTransferTsFileHandler( + final IoTDBDataRegionAsyncConnector connector, + final Map, Double> pipeName2WeightMap, + final List events, + final AtomicInteger eventsReferenceCount, + final AtomicBoolean eventsHadBeenAddedToRetryQueue, + final File tsFile, + final File modFile, + final boolean transferMod, + final String dataBaseName) + throws FileNotFoundException, InterruptedException { + super(connector); + + this.pipeName2WeightMap = pipeName2WeightMap; + + this.events = events; + this.eventsReferenceCount = eventsReferenceCount; + this.eventsHadBeenAddedToRetryQueue = eventsHadBeenAddedToRetryQueue; + + this.tsFile = tsFile; + this.modFile = modFile; + this.transferMod = transferMod; + this.dataBaseName = dataBaseName; + currentFile = transferMod ? modFile : tsFile; + + waitForResourceEnough4Slicing(Integer.MAX_VALUE); + readFileBufferSize = + (int) + Math.min( + PipeOptions.PIPE_CONNECTOR_READ_FILE_BUFFER_SIZE.value(), + transferMod ? Math.max(tsFile.length(), modFile.length()) : tsFile.length()); + // memoryBlock = + // PipeDataNodeResourceManager.memory() + // .forceAllocateForTsFileWithRetry( + // PipeOptions.PIPE_CONNECTOR_READ_FILE_BUFFER_MEMORY_CONTROL.value() + // ? readFileBufferSize + // : 0); + readBuffer = new byte[readFileBufferSize]; + position = 0; + + reader = + Objects.nonNull(modFile) + ? new RandomAccessFile(modFile, "r") + : new RandomAccessFile(tsFile, "r"); + + isSealSignalSent = new AtomicBoolean(false); + } + + public void transfer( + final IoTDBDataNodeAsyncClientManager clientManager, + final AsyncPipeDataTransferServiceClient client) + throws TException, IOException { + this.clientManager = clientManager; + this.client = client; + + client.setShouldReturnSelf(false); + client.setTimeoutDynamically(clientManager.getConnectionTimeout()); + + final int readLength = reader.read(readBuffer); + + if (readLength == -1) { + if (currentFile == modFile) { + currentFile = tsFile; + position = 0; + try { + reader.close(); + } catch (final IOException e) { + LOGGER.warn("Failed to close file reader when successfully transferred mod file.", e); + } + reader = new RandomAccessFile(tsFile, "r"); + transfer(clientManager, client); + } else if (currentFile == tsFile) { + isSealSignalSent.set(true); + + final TPipeTransferReq uncompressedReq = + transferMod + ? PipeTransferTsFileSealWithModReq.toTPipeTransferReq( + modFile.getName(), + modFile.length(), + tsFile.getName(), + tsFile.length(), + dataBaseName) + : PipeTransferTsFileSealWithModReq.toTPipeTransferReq( + tsFile.getName(), tsFile.length(), dataBaseName); + final TPipeTransferReq req = + connector.isRpcCompressionEnabled() + ? PipeTransferCompressedReq.toTPipeTransferReq( + uncompressedReq, connector.getCompressors()) + : uncompressedReq; + + pipeName2WeightMap.forEach( + (pipePair, weight) -> + connector.rateLimitIfNeeded( + pipePair.getLeft(), + pipePair.getRight(), + client.getEndPoint(), + (long) (req.getBody().length * weight))); + + if (!tryTransfer(client, req)) { + return; + } + } + return; + } + + final byte[] payload = + readLength == readFileBufferSize + ? readBuffer + : Arrays.copyOfRange(readBuffer, 0, readLength); + final TPipeTransferReq uncompressedReq = + transferMod + ? PipeTransferTsFilePieceWithModReq.toTPipeTransferReq( + currentFile.getName(), position, payload) + : PipeTransferTsFilePieceReq.toTPipeTransferReq( + currentFile.getName(), position, payload); + final TPipeTransferReq req = + connector.isRpcCompressionEnabled() + ? PipeTransferCompressedReq.toTPipeTransferReq( + uncompressedReq, connector.getCompressors()) + : uncompressedReq; + + pipeName2WeightMap.forEach( + (pipePair, weight) -> + connector.rateLimitIfNeeded( + pipePair.getLeft(), + pipePair.getRight(), + client.getEndPoint(), + (long) (req.getBody().length * weight))); + + if (!tryTransfer(client, req)) { + return; + } + + position += readLength; + } + + @Override + protected boolean onCompleteInternal(final TPipeTransferResp response) { + if (isSealSignalSent.get()) { + try { + final TSStatus status = response.getStatus(); + // Only handle the failed statuses to avoid string format performance overhead + if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode() + && status.getCode() != TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode()) { + connector + .statusHandler() + .handle( + status, + String.format( + "Seal file %s error, result status %s.", tsFile, response.getStatus()), + tsFile.getName()); + } + } catch (final Exception e) { + onError(e); + return false; + } + + try { + if (reader != null) { + reader.close(); + } + + // Delete current file when using tsFile as batch + if (events.stream().anyMatch(event -> !(event instanceof PipeTsFileInsertionEvent))) { + RetryUtils.retryOnException( + () -> { + FileUtils.delete(currentFile); + return null; + }); + } + } catch (final IOException e) { + LOGGER.warn( + "Failed to close file reader or delete tsFile when successfully transferred file.", e); + } finally { + final int referenceCount = eventsReferenceCount.decrementAndGet(); + if (referenceCount <= 0) { + events.forEach( + event -> + event.decreaseReferenceCount(PipeTransferTsFileHandler.class.getName(), true)); + } + + if (events.size() <= 1 || LOGGER.isDebugEnabled()) { + LOGGER.info( + "Successfully transferred file {} (committer key={}, commit id={}, reference count={}).", + tsFile, + events.stream().map(PipeRawTabletInsertionEvent::getCommitterKey).collect(Collectors.toList()), + events.stream().map(PipeRawTabletInsertionEvent::getCommitId).collect(Collectors.toList()), + referenceCount); + } else { + LOGGER.info( + "Successfully transferred file {} (batched TableInsertionEvents, reference count={}).", + tsFile, + referenceCount); + } + + if (client != null) { + client.setShouldReturnSelf(true); + client.returnSelf(); + } + } + + return true; + } + + // If the isSealSignalSent is false, then the response must be a PipeTransferFilePieceResp + try { + final PipeTransferFilePieceResp resp = + PipeTransferFilePieceResp.fromTPipeTransferResp(response); + + // This case only happens when the connection is broken, and the connector is reconnected + // to the receiver, then the receiver will redirect the file position to the last position + final long code = resp.getStatus().getCode(); + + if (code == TSStatusCode.PIPE_TRANSFER_FILE_OFFSET_RESET.getStatusCode()) { + position = resp.getEndWritingOffset(); + reader.seek(position); + LOGGER.info("Redirect file position to {}.", position); + } else { + final TSStatus status = response.getStatus(); + // Only handle the failed statuses to avoid string format performance overhead + if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode() + && status.getCode() != TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode()) { + connector + .statusHandler() + .handle(status, response.getStatus().getMessage(), tsFile.getName()); + } + } + + transfer(clientManager, client); + } catch (final Exception e) { + onError(e); + return false; + } + + return false; // due to seal transfer not yet completed + } + + @Override + protected void onErrorInternal(final Exception exception) { + try { + if (events.size() <= 1 || LOGGER.isDebugEnabled()) { + LOGGER.warn( + "Failed to transfer TsFileInsertionEvent {} (committer key {}, commit id {}).", + tsFile, + events.stream().map(PipeRawTabletInsertionEvent::getCommitterKey).collect(Collectors.toList()), + events.stream().map(PipeRawTabletInsertionEvent::getCommitId).collect(Collectors.toList()), + exception); + } else { + LOGGER.warn( + "Failed to transfer TsFileInsertionEvent {} (batched TableInsertionEvents)", + tsFile, + exception); + } + } catch (final Exception e) { + LOGGER.warn("Failed to log error when failed to transfer file.", e); + } + + try { + if (Objects.nonNull(clientManager)) { + clientManager.adjustTimeoutIfNecessary(exception); + } + } catch (final Exception e) { + LOGGER.warn("Failed to adjust timeout when failed to transfer file.", e); + } + + try { + if (reader != null) { + reader.close(); + } + + // Delete current file when using tsFile as batch + if (events.stream().anyMatch(event -> !(event instanceof PipeTsFileInsertionEvent))) { + RetryUtils.retryOnException( + () -> { + FileUtils.delete(currentFile); + return null; + }); + } + } catch (final IOException e) { + LOGGER.warn("Failed to close file reader or delete tsFile when failed to transfer file.", e); + } finally { + try { + if (client != null) { + client.setShouldReturnSelf(true); + client.returnSelf(); + } + } finally { + if (eventsHadBeenAddedToRetryQueue.compareAndSet(false, true)) { + connector.addFailureEventsToRetryQueue(events); + } + } + } + } + + @Override + protected void doTransfer( + final AsyncPipeDataTransferServiceClient client, final TPipeTransferReq req) + throws TException { + client.pipeTransfer(req, this); + } + + @Override + public void clearEventsReferenceCount() { + events.forEach(event -> event.clearReferenceCount(PipeTransferTsFileHandler.class.getName())); + } + + @Override + public void close() { + super.close(); + // memoryBlock.close(); + } + + private void waitForResourceEnough4Slicing(final long timeoutMs) throws InterruptedException { + // final PipeMemoryManager memoryManager = PipeDataNodeResourceManager.memory(); + // if (memoryManager.isEnough4TsFileSlicing()) { + // return; + // } + + final long startTime = System.currentTimeMillis(); + long lastRecordTime = startTime; + + final long memoryCheckIntervalMs = PipeOptions.PIPE_CHECK_MEMORY_ENOUGH_INTERVAL_MS.value(); + // while (!memoryManager.isEnough4TsFileSlicing()) { + // Thread.sleep(memoryCheckIntervalMs); + // + // final long currentTime = System.currentTimeMillis(); + // final double elapsedRecordTimeSeconds = (currentTime - lastRecordTime) / 1000.0; + // final double waitTimeSeconds = (currentTime - startTime) / 1000.0; + // if (elapsedRecordTimeSeconds > 10.0) { + // LOGGER.info( + // "Wait for resource enough for slicing tsfile {} for {} seconds.", + // tsFile, + // waitTimeSeconds); + // lastRecordTime = currentTime; + // } else if (LOGGER.isDebugEnabled()) { + // LOGGER.debug( + // "Wait for resource enough for slicing tsfile {} for {} seconds.", + // tsFile, + // waitTimeSeconds); + // } + // + // if (waitTimeSeconds * 1000 > timeoutMs) { + // // should contain 'TimeoutException' in exception message + // throw new PipeException( + // String.format("TimeoutException: Waited %s seconds", waitTimeSeconds)); + // } + // } + + final long currentTime = System.currentTimeMillis(); + final double waitTimeSeconds = (currentTime - startTime) / 1000.0; + LOGGER.info( + "Wait for resource enough for slicing tsfile {} for {} seconds.", tsFile, waitTimeSeconds); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/ClientManager.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/ClientManager.java new file mode 100644 index 00000000..b26c7060 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/ClientManager.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client; + +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.factory.AsyncThriftClientFactory; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.factory.IClientPoolFactory; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.TestOnly; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +public class ClientManager implements IClientManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(ClientManager.class); + + private final GenericKeyedObjectPool pool; + + ClientManager(IClientPoolFactory factory) { + pool = factory.createClientPool(this); + } + + @TestOnly + public GenericKeyedObjectPool getPool() { + return pool; + } + + @Override + public V borrowClient(K node) throws ClientManagerException { + if (node == null) { + throw new BorrowNullClientManagerException(); + } + try { + return pool.borrowObject(node); + } catch (Exception e) { + throw new ClientManagerException(e); + } + } + + /** + * return a client V for node K to the {@link ClientManager}. + * + *

Note: We do not define this interface in {@link IClientManager} to make you aware that the + * return of a client is automatic whenever a particular client is used. + */ + public void returnClient(K node, V client) { + Optional.ofNullable(node) + .ifPresent( + x -> { + try { + pool.returnObject(node, client); + } catch (Exception e) { + LOGGER.warn("Return client {} for node {} to pool failed.", client, node, e); + } + }); + } + + @Override + public void clear(K node) { + Optional.ofNullable(node) + .ifPresent( + x -> { + try { + pool.clear(node); + } catch (Exception e) { + LOGGER.warn("Clear all client in pool for node {} failed.", node, e); + } + }); + } + + @Override + public void close() { + pool.close(); + // we need to release tManagers for AsyncThriftClientFactory + if (pool.getFactory() instanceof AsyncThriftClientFactory) { + ((AsyncThriftClientFactory) pool.getFactory()).close(); + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IClientManager.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IClientManager.java new file mode 100644 index 00000000..85d85e13 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IClientManager.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client; + +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.factory.IClientPoolFactory; + +import javax.annotation.concurrent.ThreadSafe; + +@ThreadSafe +public interface IClientManager { + + /** + * get a client V for node K from the IClientManager. + * + * @param node target node + * @return client + * @throws BorrowNullClientManagerException if node is null + * @throws ClientManagerException for other exceptions + */ + V borrowClient(K node) throws ClientManagerException, ClientManagerException; + + /** + * clear all clients for node K. + * + * @param node target node + */ + void clear(K node); + + /** close IClientManager, which means closing all clients for all nodes. */ + void close(); + + class Factory { + + public IClientManager createClientManager(IClientPoolFactory clientPoolFactory) { + return new ClientManager<>(clientPoolFactory); + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBClientManager.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBClientManager.java new file mode 100644 index 00000000..19427014 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBClientManager.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client; + +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.SocketTimeoutException; +import java.util.List; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +public abstract class IoTDBClientManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBClientManager.class); + + protected final List endPointList; + protected long currentClientIndex = 0; + + protected final boolean useLeaderCache; + + protected final String username; + protected final String password; + + protected final boolean validateTsFile; + + protected final boolean shouldReceiverConvertOnTypeMismatch; + protected final String loadTsFileStrategy; + + protected final boolean shouldMarkAsPipeRequest; + + // This flag indicates whether the receiver supports mods transferring if + // it is a DataNode receiver. The flag is useless for configNode receiver. + protected boolean supportModsIfIsDataNodeReceiver = true; + + private static final int MAX_CONNECTION_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 1 day + private static final int FIRST_ADJUSTMENT_TIMEOUT_MS = 6 * 60 * 60 * 1000; // 6 hours + protected static final AtomicInteger CONNECTION_TIMEOUT_MS = + new AtomicInteger(PipeOptions.PIPE_CONNECTOR_TRANSFER_TIMEOUT_MS.value()); + + protected IoTDBClientManager( + final List endPointList, + /* The following parameters are used locally. */ + final boolean useLeaderCache, + /* The following parameters are used to handshake with the receiver. */ + final String username, + final String password, + final boolean shouldReceiverConvertOnTypeMismatch, + final String loadTsFileStrategy, + final boolean validateTsFile, + final boolean shouldMarkAsPipeRequest) { + this.endPointList = endPointList; + + this.useLeaderCache = useLeaderCache; + + this.username = username; + this.password = password; + this.shouldReceiverConvertOnTypeMismatch = shouldReceiverConvertOnTypeMismatch; + this.loadTsFileStrategy = loadTsFileStrategy; + this.validateTsFile = validateTsFile; + this.shouldMarkAsPipeRequest = shouldMarkAsPipeRequest; + } + + public boolean supportModsIfIsDataNodeReceiver() { + return supportModsIfIsDataNodeReceiver; + } + + public void adjustTimeoutIfNecessary(Throwable e) { + do { + if (e instanceof SocketTimeoutException || e instanceof TimeoutException) { + int newConnectionTimeout; + try { + newConnectionTimeout = + Math.min( + Math.max( + FIRST_ADJUSTMENT_TIMEOUT_MS, + Math.toIntExact(CONNECTION_TIMEOUT_MS.get() * 2L)), + MAX_CONNECTION_TIMEOUT_MS); + } catch (ArithmeticException arithmeticException) { + newConnectionTimeout = MAX_CONNECTION_TIMEOUT_MS; + } + + if (newConnectionTimeout != CONNECTION_TIMEOUT_MS.get()) { + CONNECTION_TIMEOUT_MS.set(newConnectionTimeout); + LOGGER.info( + "Pipe connection timeout is adjusted to {} ms ({} mins)", + newConnectionTimeout, + newConnectionTimeout / 60000.0); + } + return; + } + } while ((e = e.getCause()) != null); + } + + public int getConnectionTimeout() { + return CONNECTION_TIMEOUT_MS.get(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBDataNodeAsyncClientManager.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBDataNodeAsyncClientManager.java new file mode 100644 index 00000000..e3f61bf6 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBDataNodeAsyncClientManager.java @@ -0,0 +1,399 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client; + +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeTransferHandshakeConstant; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferDataNodeHandshakeV1Req; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferDataNodeHandshakeV2Req; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.async.AsyncPipeDataTransferServiceClient; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.factory.ClientPoolFactory; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.apache.iotdb.pipe.api.exception.PipeConnectionException; +import org.apache.iotdb.pipe.api.exception.PipeException; +import org.apache.iotdb.rpc.TSStatusCode; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferResp; +import org.apache.thrift.TException; +import org.apache.thrift.async.AsyncMethodCallback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Base64; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_BALANCE_PRIORITY_STRATEGY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_BALANCE_RANDOM_STRATEGY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_BALANCE_ROUND_ROBIN_STRATEGY; + +public class IoTDBDataNodeAsyncClientManager extends IoTDBClientManager + implements IoTDBDataNodeCacheLeaderClientManager { + + private static final Logger LOGGER = + LoggerFactory.getLogger(IoTDBDataNodeAsyncClientManager.class); + + private final Set endPointSet; + + private static final Map RECEIVER_ATTRIBUTES_REF_COUNT = + new ConcurrentHashMap<>(); + private final String receiverAttributes; + + // receiverAttributes -> IClientManager + private static final Map> + ASYNC_PIPE_DATA_TRANSFER_CLIENT_MANAGER_HOLDER = new ConcurrentHashMap<>(); + private final IClientManager endPoint2Client; + + private final LoadBalancer loadBalancer; + + private volatile boolean isClosed = false; + + public IoTDBDataNodeAsyncClientManager( + final List endPoints, + /* The following parameters are used locally. */ + final boolean useLeaderCache, + final String loadBalanceStrategy, + /* The following parameters are used to handshake with the receiver. */ + final String username, + final String password, + final boolean shouldReceiverConvertOnTypeMismatch, + final String loadTsFileStrategy, + final boolean validateTsFile, + final boolean shouldMarkAsPipeRequest) { + super( + endPoints, + useLeaderCache, + username, + password, + shouldReceiverConvertOnTypeMismatch, + loadTsFileStrategy, + validateTsFile, + shouldMarkAsPipeRequest); + + endPointSet = new HashSet<>(endPoints); + + receiverAttributes = + String.format( + "%s-%s-%s", + Base64.getEncoder().encodeToString((username + ":" + password).getBytes()), + shouldReceiverConvertOnTypeMismatch, + loadTsFileStrategy); + synchronized (IoTDBDataNodeAsyncClientManager.class) { + if (!ASYNC_PIPE_DATA_TRANSFER_CLIENT_MANAGER_HOLDER.containsKey(receiverAttributes)) { + ASYNC_PIPE_DATA_TRANSFER_CLIENT_MANAGER_HOLDER.putIfAbsent( + receiverAttributes, + new IClientManager.Factory() + .createClientManager( + new ClientPoolFactory.AsyncPipeDataTransferServiceClientPoolFactory())); + } + endPoint2Client = ASYNC_PIPE_DATA_TRANSFER_CLIENT_MANAGER_HOLDER.get(receiverAttributes); + + RECEIVER_ATTRIBUTES_REF_COUNT.compute( + receiverAttributes, (attributes, refCount) -> refCount == null ? 1 : refCount + 1); + } + + switch (loadBalanceStrategy) { + case CONNECTOR_LOAD_BALANCE_ROUND_ROBIN_STRATEGY: + loadBalancer = new RoundRobinLoadBalancer(); + break; + case CONNECTOR_LOAD_BALANCE_RANDOM_STRATEGY: + loadBalancer = new RandomLoadBalancer(); + break; + case CONNECTOR_LOAD_BALANCE_PRIORITY_STRATEGY: + loadBalancer = new PriorityLoadBalancer(); + break; + default: + LOGGER.warn( + "Unknown load balance strategy: {}, use round-robin strategy instead.", + loadBalanceStrategy); + loadBalancer = new RoundRobinLoadBalancer(); + } + } + + public AsyncPipeDataTransferServiceClient borrowClient() throws Exception { + return loadBalancer.borrowClient(); + } + + public AsyncPipeDataTransferServiceClient borrowClient(final String deviceId) throws Exception { + if (!useLeaderCache || Objects.isNull(deviceId)) { + return borrowClient(); + } + + return borrowClient(LEADER_CACHE_MANAGER.getLeaderEndPoint(deviceId)); + } + + public AsyncPipeDataTransferServiceClient borrowClient(final TEndPoint endPoint) + throws Exception { + if (!useLeaderCache || Objects.isNull(endPoint)) { + return borrowClient(); + } + + try { + final AsyncPipeDataTransferServiceClient client = endPoint2Client.borrowClient(endPoint); + if (handshakeIfNecessary(endPoint, client)) { + return client; + } + } catch (final Exception e) { + LOGGER.warn( + "failed to borrow client {}:{} for cached leader.", + endPoint.getIp(), + endPoint.getPort(), + e); + } + + return borrowClient(); + } + + /** + * Handshake with the target if necessary. + * + * @param client client to handshake + * @return {@code true} if the handshake is already finished, {@code false} if the handshake is + * not finished yet and finished in this method + * @throws Exception if an error occurs. + */ + private boolean handshakeIfNecessary( + final TEndPoint targetNodeUrl, final AsyncPipeDataTransferServiceClient client) + throws Exception { + if (client.isHandshakeFinished()) { + return true; + } + + final AtomicBoolean isHandshakeFinished = new AtomicBoolean(false); + final AtomicReference resp = new AtomicReference<>(); + final AtomicReference exception = new AtomicReference<>(); + + final AsyncMethodCallback callback = + new AsyncMethodCallback() { + @Override + public void onComplete(final TPipeTransferResp response) { + resp.set(response); + + if (response.getStatus().getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + LOGGER.warn( + "Handshake error with receiver {}:{}, code: {}, message: {}.", + targetNodeUrl.getIp(), + targetNodeUrl.getPort(), + response.getStatus().getCode(), + response.getStatus().getMessage()); + exception.set( + new PipeConnectionException( + String.format( + "Handshake error with receiver %s:%s, code: %d, message: %s.", + targetNodeUrl.getIp(), + targetNodeUrl.getPort(), + response.getStatus().getCode(), + response.getStatus().getMessage()))); + } else { + LOGGER.info( + "Handshake successfully with receiver {}:{}.", + targetNodeUrl.getIp(), + targetNodeUrl.getPort()); + client.markHandshakeFinished(); + } + + isHandshakeFinished.set(true); + } + + @Override + public void onError(final Exception e) { + LOGGER.warn( + "Handshake error with receiver {}:{}.", + targetNodeUrl.getIp(), + targetNodeUrl.getPort(), + e); + exception.set(e); + + isHandshakeFinished.set(true); + } + }; + + try { + client.setShouldReturnSelf(false); + // Try to handshake by PipeTransferHandshakeV2Req. + final HashMap params = new HashMap<>(); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_CLUSTER_ID, PipeOptions.CLUSTER_ID.value()); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_TIME_PRECISION, + PipeOptions.TIMESTAMP_PRECISION.value()); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_CONVERT_ON_TYPE_MISMATCH, + Boolean.toString(shouldReceiverConvertOnTypeMismatch)); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_LOAD_TSFILE_STRATEGY, loadTsFileStrategy); + params.put(PipeTransferHandshakeConstant.HANDSHAKE_KEY_USERNAME, username); + params.put(PipeTransferHandshakeConstant.HANDSHAKE_KEY_PASSWORD, password); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_VALIDATE_TSFILE, + Boolean.toString(validateTsFile)); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_MARK_AS_PIPE_REQUEST, + Boolean.toString(shouldMarkAsPipeRequest)); + + client.setTimeoutDynamically(PipeOptions.PIPE_CONNECTOR_HANDSHAKE_TIMEOUT_MS.value()); + client.pipeTransfer(PipeTransferDataNodeHandshakeV2Req.toTPipeTransferReq(params), callback); + waitHandshakeFinished(isHandshakeFinished); + + // Retry to handshake by PipeTransferHandshakeV1Req. + if (resp.get() != null + && resp.get().getStatus().getCode() == TSStatusCode.PIPE_TYPE_ERROR.getStatusCode()) { + LOGGER.warn( + "Handshake error by PipeTransferHandshakeV2Req with receiver {}:{} " + + "retry to handshake by PipeTransferHandshakeV1Req.", + targetNodeUrl.getIp(), + targetNodeUrl.getPort()); + + supportModsIfIsDataNodeReceiver = false; + isHandshakeFinished.set(false); + resp.set(null); + exception.set(null); + + client.setTimeoutDynamically(PipeOptions.PIPE_CONNECTOR_HANDSHAKE_TIMEOUT_MS.value()); + client.pipeTransfer( + PipeTransferDataNodeHandshakeV1Req.toTPipeTransferReq( + PipeOptions.TIMESTAMP_PRECISION.value()), + callback); + waitHandshakeFinished(isHandshakeFinished); + } + if (exception.get() != null) { + throw new PipeConnectionException("Failed to handshake.", exception.get()); + } + } catch (TException e) { + client.resetMethodStateIfStopped(); + throw e; + } finally { + client.setShouldReturnSelf(true); + client.returnSelf(); + } + + return false; + } + + private void waitHandshakeFinished(final AtomicBoolean isHandshakeFinished) { + try { + final long startTime = System.currentTimeMillis(); + while (!isHandshakeFinished.get()) { + if (isClosed + || System.currentTimeMillis() - startTime + > PipeOptions.PIPE_CONNECTOR_HANDSHAKE_TIMEOUT_MS.value() * 2L) { + throw new PipeConnectionException("Timed out when waiting for client handshake finish."); + } + Thread.sleep(10); + } + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + throw new PipeException("Interrupted while waiting for handshake response.", e); + } + } + + public void updateLeaderCache(final String deviceId, final TEndPoint endPoint) { + if (!useLeaderCache || deviceId == null || endPoint == null) { + return; + } + + if (!endPointSet.contains(endPoint)) { + endPointList.add(endPoint); + endPointSet.add(endPoint); + } + + LEADER_CACHE_MANAGER.updateLeaderEndPoint(deviceId, endPoint); + } + + public void close() { + isClosed = true; + synchronized (IoTDBDataNodeAsyncClientManager.class) { + RECEIVER_ATTRIBUTES_REF_COUNT.computeIfPresent( + receiverAttributes, + (attributes, refCount) -> { + if (refCount <= 1) { + final IClientManager clientManager = + ASYNC_PIPE_DATA_TRANSFER_CLIENT_MANAGER_HOLDER.remove(receiverAttributes); + if (clientManager != null) { + try { + clientManager.close(); + } catch (final Exception e) { + LOGGER.warn("Failed to close client manager.", e); + } + } + return null; + } + return refCount - 1; + }); + } + } + + /////////////////////// Strategies for load balance ////////////////////////// + + private interface LoadBalancer { + AsyncPipeDataTransferServiceClient borrowClient() throws Exception; + } + + private class RoundRobinLoadBalancer implements LoadBalancer { + @Override + public AsyncPipeDataTransferServiceClient borrowClient() throws Exception { + final int clientSize = endPointList.size(); + while (true) { + final TEndPoint targetNodeUrl = endPointList.get((int) (currentClientIndex++ % clientSize)); + final AsyncPipeDataTransferServiceClient client = + endPoint2Client.borrowClient(targetNodeUrl); + if (handshakeIfNecessary(targetNodeUrl, client)) { + return client; + } + } + } + } + + private class RandomLoadBalancer implements LoadBalancer { + @Override + public AsyncPipeDataTransferServiceClient borrowClient() throws Exception { + final int clientSize = endPointList.size(); + while (true) { + final TEndPoint targetNodeUrl = endPointList.get((int) (Math.random() * clientSize)); + final AsyncPipeDataTransferServiceClient client = + endPoint2Client.borrowClient(targetNodeUrl); + if (handshakeIfNecessary(targetNodeUrl, client)) { + return client; + } + } + } + } + + private class PriorityLoadBalancer implements LoadBalancer { + @Override + public AsyncPipeDataTransferServiceClient borrowClient() throws Exception { + while (true) { + for (final TEndPoint targetNodeUrl : endPointList) { + final AsyncPipeDataTransferServiceClient client = + endPoint2Client.borrowClient(targetNodeUrl); + if (handshakeIfNecessary(targetNodeUrl, client)) { + return client; + } + } + } + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBDataNodeCacheLeaderClientManager.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBDataNodeCacheLeaderClientManager.java new file mode 100644 index 00000000..28069547 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBDataNodeCacheLeaderClientManager.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.Weigher; +import com.google.common.util.concurrent.AtomicDouble; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ConcurrentHashMap; + +public interface IoTDBDataNodeCacheLeaderClientManager { + + LeaderCacheManager LEADER_CACHE_MANAGER = new LeaderCacheManager(); + + class LeaderCacheManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(LeaderCacheManager.class); + // private static final PipeConfig CONFIG = PipeConfig.getInstance(); + + private final AtomicDouble memoryUsageCheatFactor = new AtomicDouble(1); + + // leader cache built by LRU + private final Cache device2endpoint; + // a hashmap to reuse the created endpoint + private final ConcurrentHashMap endPoints = new ConcurrentHashMap<>(); + + public LeaderCacheManager() { + // final long initMemorySizeInBytes = + // PipeDataNodeResourceManager.memory().getTotalMemorySizeInBytes() / 10; + // final long maxMemorySizeInBytes = + // (long) + // (PipeDataNodeResourceManager.memory().getTotalMemorySizeInBytes() + // * CONFIG.getPipeLeaderCacheMemoryUsagePercentage()); + // + // // properties required by pipe memory control framework + // final PipeMemoryBlock allocatedMemoryBlock = + // PipeDataNodeResourceManager.memory() + // .tryAllocate(initMemorySizeInBytes) + // .setShrinkMethod(oldMemory -> Math.max(oldMemory / 2, 1)) + // .setShrinkCallback( + // (oldMemory, newMemory) -> { + // memoryUsageCheatFactor.updateAndGet( + // factor -> factor * ((double) oldMemory / newMemory)); + // LOGGER.info( + // "LeaderCacheManager.allocatedMemoryBlock has shrunk from {} to {}.", + // oldMemory, + // newMemory); + // }) + // .setExpandMethod( + // oldMemory -> Math.min(Math.max(oldMemory, 1) * 2, maxMemorySizeInBytes)) + // .setExpandCallback( + // (oldMemory, newMemory) -> { + // memoryUsageCheatFactor.updateAndGet( + // factor -> factor / ((double) newMemory / oldMemory)); + // LOGGER.info( + // "LeaderCacheManager.allocatedMemoryBlock has expanded from {} to {}.", + // oldMemory, + // newMemory); + // }); + + device2endpoint = + Caffeine.newBuilder() + // .maximumWeight(allocatedMemoryBlock.getMemoryUsageInBytes()) + .weigher( + (Weigher) + (device, endPoint) -> { + final long weightInLong = + (long) (device.getBytes().length * memoryUsageCheatFactor.get()); + if (weightInLong <= 0) { + return Integer.MAX_VALUE; + } + final int weightInInt = (int) weightInLong; + return weightInInt != weightInLong ? Integer.MAX_VALUE : weightInInt; + }) + .recordStats() + .build(); + } + + public TEndPoint getLeaderEndPoint(final String deviceId) { + return deviceId == null ? null : device2endpoint.getIfPresent(deviceId); + } + + public void updateLeaderEndPoint(final String deviceId, final TEndPoint endPoint) { + if (deviceId == null || endPoint == null) { + return; + } + + final TEndPoint endPointFromMap = endPoints.putIfAbsent(endPoint, endPoint); + if (endPointFromMap != null) { + device2endpoint.put(deviceId, endPointFromMap); + } else { + device2endpoint.put(deviceId, endPoint); + } + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBDataNodeSyncClientManager.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBDataNodeSyncClientManager.java new file mode 100644 index 00000000..fa78420a --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBDataNodeSyncClientManager.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client; + +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferDataNodeHandshakeV1Req; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferDataNodeHandshakeV2Req; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferHandshakeV2Req; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.apache.tsfile.utils.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class IoTDBDataNodeSyncClientManager extends IoTDBSyncClientManager + implements IoTDBDataNodeCacheLeaderClientManager { + + private static final Logger LOGGER = + LoggerFactory.getLogger(IoTDBDataNodeSyncClientManager.class); + + public IoTDBDataNodeSyncClientManager( + final List endPoints, + final boolean useSSL, + final String trustStorePath, + final String trustStorePwd, + /* The following parameters are used locally. */ + final boolean useLeaderCache, + final String loadBalanceStrategy, + /* The following parameters are used to handshake with the receiver. */ + final String username, + final String password, + final boolean shouldReceiverConvertOnTypeMismatch, + final String loadTsFileStrategy, + final boolean validateTsFile, + final boolean shouldMarkAsPipeRequest) { + super( + endPoints, + useSSL, + trustStorePath, + trustStorePwd, + useLeaderCache, + loadBalanceStrategy, + username, + password, + shouldReceiverConvertOnTypeMismatch, + loadTsFileStrategy, + validateTsFile, + shouldMarkAsPipeRequest); + } + + @Override + protected PipeTransferDataNodeHandshakeV1Req buildHandshakeV1Req() throws IOException { + return PipeTransferDataNodeHandshakeV1Req.toTPipeTransferReq( + PipeOptions.TIMESTAMP_PRECISION.value()); + } + + @Override + protected PipeTransferHandshakeV2Req buildHandshakeV2Req(final Map params) + throws IOException { + return PipeTransferDataNodeHandshakeV2Req.toTPipeTransferReq(params); + } + + @Override + protected String getClusterId() { + return PipeOptions.CLUSTER_ID.value(); + } + + public Pair getClient(final String deviceId) { + final TEndPoint endPoint = LEADER_CACHE_MANAGER.getLeaderEndPoint(deviceId); + return useLeaderCache + && endPoint != null + && endPoint2ClientAndStatus.containsKey(endPoint) + && Boolean.TRUE.equals(endPoint2ClientAndStatus.get(endPoint).getRight()) + ? endPoint2ClientAndStatus.get(endPoint) + : getClient(); + } + + public Pair getClient(final TEndPoint endPoint) { + return useLeaderCache + && endPoint != null + && endPoint2ClientAndStatus.containsKey(endPoint) + && Boolean.TRUE.equals(endPoint2ClientAndStatus.get(endPoint).getRight()) + ? endPoint2ClientAndStatus.get(endPoint) + : getClient(); + } + + public void updateLeaderCache(final String deviceId, final TEndPoint endPoint) { + if (!useLeaderCache || deviceId == null || endPoint == null) { + return; + } + + try { + if (!endPoint2ClientAndStatus.containsKey(endPoint)) { + endPointList.add(endPoint); + endPoint2ClientAndStatus.put(endPoint, new Pair<>(null, false)); + reconstructClient(endPoint); + } + + LEADER_CACHE_MANAGER.updateLeaderEndPoint(deviceId, endPoint); + } catch (final Exception e) { + LOGGER.warn( + "Failed to update leader cache for device {} with endpoint {}:{}.", + deviceId, + endPoint.getIp(), + endPoint.getPort(), + e); + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBSyncClient.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBSyncClient.java new file mode 100644 index 00000000..bbc7ee3d --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBSyncClient.java @@ -0,0 +1,181 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client; + +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.IoTDBConnectorRequestVersion; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferSliceReq; +import org.apache.iotdb.collector.plugin.builtin.sink.property.ThriftClient; +import org.apache.iotdb.collector.plugin.builtin.sink.property.ThriftClientProperty; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.apache.iotdb.pipe.api.exception.PipeConnectionException; +import org.apache.iotdb.rpc.DeepCopyRpcTransportFactory; +import org.apache.iotdb.rpc.TSStatusCode; +import org.apache.iotdb.rpc.TimeoutChangeableTransport; +import org.apache.iotdb.service.rpc.thrift.IClientRPCService; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferResp; +import org.apache.thrift.TException; +import org.apache.thrift.transport.TTransport; +import org.apache.thrift.transport.TTransportException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.atomic.AtomicInteger; + +public class IoTDBSyncClient extends IClientRPCService.Client + implements ThriftClient, AutoCloseable { + + private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBSyncClient.class); + + private static final AtomicInteger SLICE_ORDER_ID_GENERATOR = new AtomicInteger(0); + + private final String ipAddress; + private final int port; + private final TEndPoint endPoint; + + public IoTDBSyncClient( + ThriftClientProperty property, + String ipAddress, + int port, + boolean useSSL, + String trustStore, + String trustStorePwd) + throws TTransportException { + super( + property + .getProtocolFactory() + .getProtocol( + useSSL + ? DeepCopyRpcTransportFactory.INSTANCE.getTransport( + ipAddress, + port, + property.getConnectionTimeoutMs(), + trustStore, + trustStorePwd) + : DeepCopyRpcTransportFactory.INSTANCE.getTransport( + ipAddress, port, property.getConnectionTimeoutMs()))); + this.ipAddress = ipAddress; + this.port = port; + this.endPoint = new TEndPoint(ipAddress, port); + final TTransport transport = getInputProtocol().getTransport(); + if (!transport.isOpen()) { + transport.open(); + } + } + + public String getIpAddress() { + return ipAddress; + } + + public int getPort() { + return port; + } + + public TEndPoint getEndPoint() { + return endPoint; + } + + public void setTimeout(int timeout) { + ((TimeoutChangeableTransport) (getInputProtocol().getTransport())).setTimeout(timeout); + } + + @Override + public TPipeTransferResp pipeTransfer(final TPipeTransferReq req) throws TException { + final int bodySizeLimit = PipeOptions.PIPE_CONNECTOR_REQUEST_SLICE_THRESHOLD_BYTES.value(); + if (req.getVersion() != IoTDBConnectorRequestVersion.VERSION_1.getVersion() + || req.body.limit() < bodySizeLimit) { + return super.pipeTransfer(req); + } + + LOGGER.warn( + "The body size of the request is too large. The request will be sliced. Origin req: {}-{}. " + + "Request body size: {}, threshold: {}", + req.getVersion(), + req.getType(), + req.body.limit(), + bodySizeLimit); + + try { + final int sliceOrderId = SLICE_ORDER_ID_GENERATOR.getAndIncrement(); + // Slice the buffer to avoid the buffer being too large + final int sliceCount = + req.body.limit() / bodySizeLimit + (req.body.limit() % bodySizeLimit == 0 ? 0 : 1); + for (int i = 0; i < sliceCount; ++i) { + final int startIndexInBody = i * bodySizeLimit; + final int endIndexInBody = Math.min((i + 1) * bodySizeLimit, req.body.limit()); + final TPipeTransferResp sliceResp = + super.pipeTransfer( + PipeTransferSliceReq.toTPipeTransferReq( + sliceOrderId, + req.getType(), + i, + sliceCount, + req.body.duplicate(), + startIndexInBody, + endIndexInBody)); + + if (i == sliceCount - 1) { + return sliceResp; + } + + if (sliceResp.getStatus().getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + throw new PipeConnectionException( + String.format( + "Failed to transfer slice. Origin req: %s-%s, slice index: %d, slice count: %d. Reason: %s", + req.getVersion(), req.getType(), i, sliceCount, sliceResp.getStatus())); + } + } + + // Should not reach here + return super.pipeTransfer(req); + } catch (final Exception e) { + LOGGER.warn( + "Failed to transfer slice. Origin req: {}-{}. Retry the whole transfer.", + req.getVersion(), + req.getType(), + e); + // Fall back to the original behavior + return super.pipeTransfer(req); + } + } + + @Override + public void close() throws Exception { + invalidate(); + } + + @Override + public void invalidate() { + if (getInputProtocol().getTransport().isOpen()) { + getInputProtocol().getTransport().close(); + } + } + + @Override + public void invalidateAll() { + invalidate(); + } + + @Override + public boolean printLogWhenEncounterException() { + return true; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBSyncClientManager.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBSyncClientManager.java new file mode 100644 index 00000000..0ea8abb1 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/IoTDBSyncClientManager.java @@ -0,0 +1,372 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client; + +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeTransferHandshakeConstant; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferHandshakeV1Req; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferHandshakeV2Req; +import org.apache.iotdb.collector.plugin.builtin.sink.property.ThriftClientProperty; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.apache.iotdb.pipe.api.exception.PipeConnectionException; +import org.apache.iotdb.rpc.TSStatusCode; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferResp; +import org.apache.tsfile.utils.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Closeable; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_BALANCE_PRIORITY_STRATEGY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_BALANCE_RANDOM_STRATEGY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LOAD_BALANCE_ROUND_ROBIN_STRATEGY; + +public abstract class IoTDBSyncClientManager extends IoTDBClientManager implements Closeable { + + private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBSyncClientManager.class); + + private final boolean useSSL; + private final String trustStorePath; + private final String trustStorePwd; + + protected final Map> endPoint2ClientAndStatus = + new ConcurrentHashMap<>(); + private final Map endPoint2HandshakeErrorMessage = new ConcurrentHashMap<>(); + + private final LoadBalancer loadBalancer; + + protected IoTDBSyncClientManager( + List endPoints, + boolean useSSL, + String trustStorePath, + String trustStorePwd, + /* The following parameters are used locally. */ + boolean useLeaderCache, + String loadBalanceStrategy, + /* The following parameters are used to handshake with the receiver. */ + String username, + String password, + boolean shouldReceiverConvertOnTypeMismatch, + String loadTsFileStrategy, + boolean validateTsFile, + boolean shouldMarkAsPipeRequest) { + super( + endPoints, + useLeaderCache, + username, + password, + shouldReceiverConvertOnTypeMismatch, + loadTsFileStrategy, + validateTsFile, + shouldMarkAsPipeRequest); + + this.useSSL = useSSL; + this.trustStorePath = trustStorePath; + this.trustStorePwd = trustStorePwd; + + for (final TEndPoint endPoint : endPoints) { + endPoint2ClientAndStatus.put(endPoint, new Pair<>(null, false)); + } + + switch (loadBalanceStrategy) { + case CONNECTOR_LOAD_BALANCE_ROUND_ROBIN_STRATEGY: + loadBalancer = new RoundRobinLoadBalancer(); + break; + case CONNECTOR_LOAD_BALANCE_RANDOM_STRATEGY: + loadBalancer = new RandomLoadBalancer(); + break; + case CONNECTOR_LOAD_BALANCE_PRIORITY_STRATEGY: + loadBalancer = new PriorityLoadBalancer(); + break; + default: + LOGGER.warn( + "Unknown load balance strategy: {}, use round-robin strategy instead.", + loadBalanceStrategy); + loadBalancer = new RoundRobinLoadBalancer(); + } + } + + public void checkClientStatusAndTryReconstructIfNecessary() { + // Reconstruct all dead clients + for (final Map.Entry> entry : + endPoint2ClientAndStatus.entrySet()) { + if (Boolean.TRUE.equals(entry.getValue().getRight())) { + continue; + } + + reconstructClient(entry.getKey()); + } + + // Check whether any clients are available + for (final Pair clientAndStatus : endPoint2ClientAndStatus.values()) { + if (Boolean.TRUE.equals(clientAndStatus.getRight())) { + return; + } + } + + // If all clients are not available, throw an exception + final StringBuilder errorMessage = + new StringBuilder( + String.format( + "All target servers %s are not available.", endPoint2ClientAndStatus.keySet())); + for (final Map.Entry entry : endPoint2HandshakeErrorMessage.entrySet()) { + errorMessage + .append(" (") + .append("host: ") + .append(entry.getKey().getIp()) + .append(", port: ") + .append(entry.getKey().getPort()) + .append(", because: ") + .append(entry.getValue()) + .append(")"); + } + throw new PipeConnectionException(errorMessage.toString()); + } + + protected void reconstructClient(TEndPoint endPoint) { + endPoint2HandshakeErrorMessage.remove(endPoint); + + final Pair clientAndStatus = endPoint2ClientAndStatus.get(endPoint); + + if (clientAndStatus.getLeft() != null) { + try { + clientAndStatus.getLeft().close(); + } catch (Exception e) { + LOGGER.warn( + "Failed to close client with target server ip: {}, port: {}, because: {}. Ignore it.", + endPoint.getIp(), + endPoint.getPort(), + e.getMessage()); + } + } + + // It is necessary to ensure that the client is initialized successfully and not null. If false + // is returned, it means that the initialization is not successful and the handshake operation + // is not performed. + if (initClientAndStatus(clientAndStatus, endPoint)) { + sendHandshakeReq(clientAndStatus); + } + } + + private boolean initClientAndStatus( + final Pair clientAndStatus, final TEndPoint endPoint) { + try { + clientAndStatus.setLeft( + new IoTDBSyncClient( + new ThriftClientProperty.Builder() + .setConnectionTimeoutMs(PipeOptions.PIPE_CONNECTOR_HANDSHAKE_TIMEOUT_MS.value()) + .setRpcThriftCompressionEnabled( + PipeOptions.PIPE_CONNECTOR_RPC_THRIFT_COMPRESSION_ENABLED.value()) + .build(), + endPoint.getIp(), + endPoint.getPort(), + useSSL, + trustStorePath, + trustStorePwd)); + return true; + } catch (Exception e) { + endPoint2HandshakeErrorMessage.put(endPoint, e.getMessage()); + LOGGER.warn( + "Failed to initialize client with target server ip: {}, port: {}, because {}", + endPoint.getIp(), + endPoint.getPort(), + e.getMessage(), + e); + return false; + } + } + + public void sendHandshakeReq(final Pair clientAndStatus) { + final IoTDBSyncClient client = clientAndStatus.getLeft(); + try { + final HashMap params = new HashMap<>(); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_TIME_PRECISION, + PipeOptions.TIMESTAMP_PRECISION.value()); + params.put(PipeTransferHandshakeConstant.HANDSHAKE_KEY_CLUSTER_ID, getClusterId()); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_CONVERT_ON_TYPE_MISMATCH, + Boolean.toString(shouldReceiverConvertOnTypeMismatch)); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_LOAD_TSFILE_STRATEGY, loadTsFileStrategy); + params.put(PipeTransferHandshakeConstant.HANDSHAKE_KEY_USERNAME, username); + params.put(PipeTransferHandshakeConstant.HANDSHAKE_KEY_PASSWORD, password); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_VALIDATE_TSFILE, + Boolean.toString(validateTsFile)); + params.put( + PipeTransferHandshakeConstant.HANDSHAKE_KEY_MARK_AS_PIPE_REQUEST, + Boolean.toString(shouldMarkAsPipeRequest)); + + // Try to handshake by PipeTransferHandshakeV2Req. + TPipeTransferResp resp = client.pipeTransfer(buildHandshakeV2Req(params)); + // Receiver may be an old version, so we need to retry to handshake by + // PipeTransferHandshakeV1Req. + if (resp.getStatus().getCode() == TSStatusCode.PIPE_TYPE_ERROR.getStatusCode()) { + LOGGER.warn( + "Handshake error with target server ip: {}, port: {}, because: {}. " + + "Retry to handshake by PipeTransferHandshakeV1Req.", + client.getIpAddress(), + client.getPort(), + resp.getStatus()); + supportModsIfIsDataNodeReceiver = false; + resp = client.pipeTransfer(buildHandshakeV1Req()); + } + + if (resp.getStatus().getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + LOGGER.warn( + "Handshake error with target server ip: {}, port: {}, because: {}.", + client.getIpAddress(), + client.getPort(), + resp.getStatus()); + endPoint2HandshakeErrorMessage.put(client.getEndPoint(), resp.getStatus().getMessage()); + } else { + clientAndStatus.setRight(true); + client.setTimeout(CONNECTION_TIMEOUT_MS.get()); + LOGGER.info( + "Handshake success. Target server ip: {}, port: {}", + client.getIpAddress(), + client.getPort()); + } + } catch (Exception e) { + LOGGER.warn( + "Handshake error with target server ip: {}, port: {}, because: {}.", + client.getIpAddress(), + client.getPort(), + e.getMessage(), + e); + endPoint2HandshakeErrorMessage.put(client.getEndPoint(), e.getMessage()); + } + } + + protected abstract PipeTransferHandshakeV1Req buildHandshakeV1Req() throws IOException; + + protected abstract PipeTransferHandshakeV2Req buildHandshakeV2Req(Map params) + throws IOException; + + protected abstract String getClusterId(); + + public Pair getClient() { + return loadBalancer.getClient(); + } + + @Override + public void close() { + for (final Map.Entry> entry : + endPoint2ClientAndStatus.entrySet()) { + final TEndPoint endPoint = entry.getKey(); + final Pair clientAndStatus = entry.getValue(); + + if (clientAndStatus == null) { + continue; + } + + try { + if (clientAndStatus.getLeft() != null) { + clientAndStatus.getLeft().close(); + clientAndStatus.setLeft(null); + } + LOGGER.info("Client {}:{} closed.", endPoint.getIp(), endPoint.getPort()); + } catch (Exception e) { + LOGGER.warn( + "Failed to close client {}:{}, because: {}.", + endPoint.getIp(), + endPoint.getPort(), + e.getMessage(), + e); + } finally { + clientAndStatus.setRight(false); + } + } + } + + /////////////////////// Strategies for load balance ////////////////////////// + + private interface LoadBalancer { + Pair getClient(); + } + + private class RoundRobinLoadBalancer implements LoadBalancer { + @Override + public Pair getClient() { + final int clientSize = endPointList.size(); + // Round-robin, find the next alive client + for (int tryCount = 0; tryCount < clientSize; ++tryCount) { + final int clientIndex = (int) (currentClientIndex++ % clientSize); + final Pair clientAndStatus = + endPoint2ClientAndStatus.get(endPointList.get(clientIndex)); + if (Boolean.TRUE.equals(clientAndStatus.getRight())) { + return clientAndStatus; + } + } + + throw new PipeConnectionException( + "All clients are dead, please check the connection to the receiver."); + } + } + + private class RandomLoadBalancer implements LoadBalancer { + @Override + public Pair getClient() { + final int clientSize = endPointList.size(); + final int clientIndex = (int) (Math.random() * clientSize); + final Pair clientAndStatus = + endPoint2ClientAndStatus.get(endPointList.get(clientIndex)); + if (Boolean.TRUE.equals(clientAndStatus.getRight())) { + return clientAndStatus; + } + + // Random, find the next alive client + for (int tryCount = 0; tryCount < clientSize - 1; ++tryCount) { + final int nextClientIndex = (clientIndex + tryCount + 1) % clientSize; + final Pair nextClientAndStatus = + endPoint2ClientAndStatus.get(endPointList.get(nextClientIndex)); + if (Boolean.TRUE.equals(nextClientAndStatus.getRight())) { + return nextClientAndStatus; + } + } + + throw new PipeConnectionException( + "All clients are dead, please check the connection to the receiver."); + } + } + + private class PriorityLoadBalancer implements LoadBalancer { + @Override + public Pair getClient() { + // Priority, find the first alive client + for (final TEndPoint endPoint : endPointList) { + final Pair clientAndStatus = + endPoint2ClientAndStatus.get(endPoint); + if (Boolean.TRUE.equals(clientAndStatus.getRight())) { + return clientAndStatus; + } + } + + throw new PipeConnectionException( + "All clients are dead, please check the connection to the receiver."); + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/async/AsyncPipeDataTransferServiceClient.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/async/AsyncPipeDataTransferServiceClient.java new file mode 100644 index 00000000..9daa53d8 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/async/AsyncPipeDataTransferServiceClient.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.async; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.apache.iotdb.collector.plugin.builtin.sink.property.ThriftClient; +import org.apache.iotdb.collector.plugin.builtin.sink.property.ThriftClientProperty; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.ClientManager; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.factory.AsyncThriftClientFactory; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.apache.iotdb.rpc.TNonblockingSocketWrapper; +import org.apache.iotdb.service.rpc.thrift.IClientRPCService; +import org.apache.thrift.async.TAsyncClientManager; +import org.apache.thrift.transport.TNonblockingSocket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +public class AsyncPipeDataTransferServiceClient extends IClientRPCService.AsyncClient + implements ThriftClient { + + private static final Logger LOGGER = + LoggerFactory.getLogger(AsyncPipeDataTransferServiceClient.class); + + private static final AtomicInteger idGenerator = new AtomicInteger(0); + private final int id = idGenerator.incrementAndGet(); + + private final boolean printLogWhenEncounterException; + + private final TEndPoint endpoint; + private final ClientManager clientManager; + + private final AtomicBoolean shouldReturnSelf = new AtomicBoolean(true); + + private final AtomicBoolean isHandshakeFinished = new AtomicBoolean(false); + + public AsyncPipeDataTransferServiceClient( + final ThriftClientProperty property, + final TEndPoint endpoint, + final TAsyncClientManager tClientManager, + final ClientManager clientManager) + throws IOException { + super( + property.getProtocolFactory(), + tClientManager, + TNonblockingSocketWrapper.wrap( + endpoint.getIp(), endpoint.getPort(), property.getConnectionTimeoutMs())); + setTimeout(property.getConnectionTimeoutMs()); + this.printLogWhenEncounterException = property.isPrintLogWhenEncounterException(); + this.endpoint = endpoint; + this.clientManager = clientManager; + } + + @Override + public void onComplete() { + super.onComplete(); + returnSelf(); + } + + @Override + public void onError(final Exception e) { + super.onError(e); + ThriftClient.resolveException(e, this); + returnSelf(); + } + + @Override + public void invalidate() { + if (!hasError()) { + super.onError(new Exception(String.format("This client %d has been invalidated", id))); + } + } + + @Override + public void invalidateAll() { + clientManager.clear(endpoint); + } + + @Override + public boolean printLogWhenEncounterException() { + return printLogWhenEncounterException; + } + + /** + * return self, the method doesn't need to be called by the user and will be triggered after the + * RPC is finished. + */ + public void returnSelf() { + if (shouldReturnSelf.get()) { + clientManager.returnClient(endpoint, this); + } + } + + public void setShouldReturnSelf(final boolean shouldReturnSelf) { + this.shouldReturnSelf.set(shouldReturnSelf); + } + + public void setTimeoutDynamically(final int timeout) { + try { + ((TNonblockingSocket) ___transport).setTimeout(timeout); + } catch (Exception e) { + setTimeout(timeout); + LOGGER.error("Failed to set timeout dynamically, set it statically", e); + } + } + + private void close() { + ___transport.close(); + ___currentMethod = null; + } + + private boolean isReady() { + try { + checkReady(); + return true; + } catch (Exception e) { + if (printLogWhenEncounterException) { + LOGGER.error( + "Unexpected exception occurs in {}, error msg is {}", + this, + ExceptionUtils.getRootCause(e).toString(), + e); + } + return false; + } + } + + public boolean isHandshakeFinished() { + return isHandshakeFinished.get(); + } + + public void markHandshakeFinished() { + isHandshakeFinished.set(true); + LOGGER.info("Handshake finished for client {}", this); + } + + // To ensure that the socket will be closed eventually, we need to manually close the socket here, + // because the Client object may have thrown an exception before entering the asynchronous thread, + // and the returnSelf method may not be called, resulting in resource leakage. + public void resetMethodStateIfStopped() { + if (!___manager.isRunning()) { + if (___transport != null && ___transport.isOpen()) { + ___transport.close(); + LOGGER.warn("Manually closing transport to prevent resource leakage."); + } + ___currentMethod = null; + LOGGER.info("Method state has been reset due to manager not running."); + } + } + + public String getIp() { + return endpoint.getIp(); + } + + public int getPort() { + return endpoint.getPort(); + } + + public TEndPoint getEndPoint() { + return endpoint; + } + + @Override + public String toString() { + return String.format("AsyncPipeDataTransferServiceClient{%s}, id = {%d}", endpoint, id); + } + + public static class Factory + extends AsyncThriftClientFactory { + + public Factory( + final ClientManager clientManager, + final ThriftClientProperty thriftClientProperty, + final String threadName) { + super(clientManager, thriftClientProperty, threadName); + } + + @Override + public void destroyObject( + final TEndPoint endPoint, + final PooledObject pooledObject) { + pooledObject.getObject().close(); + } + + @Override + public PooledObject makeObject(final TEndPoint endPoint) + throws Exception { + return new DefaultPooledObject<>( + new AsyncPipeDataTransferServiceClient( + thriftClientProperty, + endPoint, + tManagers[clientCnt.incrementAndGet() % tManagers.length], + clientManager)); + } + + @Override + public boolean validateObject( + final TEndPoint endPoint, + final PooledObject pooledObject) { + return pooledObject.getObject().isReady(); + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/AsyncThriftClientFactory.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/AsyncThriftClientFactory.java new file mode 100644 index 00000000..affd0cc9 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/AsyncThriftClientFactory.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.factory; + +import org.apache.iotdb.collector.plugin.builtin.sink.property.ThriftClientProperty; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.ClientManager; +import org.apache.thrift.async.TAsyncClientManager; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +public abstract class AsyncThriftClientFactory extends ThriftClientFactory { + + protected final TAsyncClientManager[] tManagers; + protected final AtomicInteger clientCnt = new AtomicInteger(); + private static final String THRIFT_THREAD_NAME = "TAsyncClientManager#SelectorThread"; + + protected AsyncThriftClientFactory( + ClientManager clientManager, + ThriftClientProperty thriftClientProperty, + String threadName) { + super(clientManager, thriftClientProperty); + try { + tManagers = new TAsyncClientManager[thriftClientProperty.getSelectorNumOfAsyncClientPool()]; + for (int i = 0; i < tManagers.length; i++) { + tManagers[i] = new TAsyncClientManager(); + } + } catch (IOException e) { + throw new CreateTAsyncClientManagerException( + String.format("Cannot create Async thrift client factory %s", threadName), e); + } + Thread.getAllStackTraces().keySet().stream() + .filter(thread -> thread.getName().contains(THRIFT_THREAD_NAME)) + .collect(Collectors.toList()) + .forEach(thread -> thread.setName(threadName + "-selector-" + thread.getId())); + } + + public void close() { + for (TAsyncClientManager tManager : tManagers) { + tManager.stop(); + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/BaseClientFactory.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/BaseClientFactory.java new file mode 100644 index 00000000..df6ed1fc --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/BaseClientFactory.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.factory; + +import org.apache.commons.pool2.KeyedPooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.ClientManager; + +public abstract class BaseClientFactory implements KeyedPooledObjectFactory { + + protected ClientManager clientManager; + + protected BaseClientFactory(ClientManager clientManager) { + this.clientManager = clientManager; + } + + @Override + public void activateObject(K node, PooledObject pooledObject) {} + + @Override + public void passivateObject(K node, PooledObject pooledObject) {} +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/ClientPoolFactory.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/ClientPoolFactory.java new file mode 100644 index 00000000..aa7193ec --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/ClientPoolFactory.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.factory; + +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.collector.plugin.builtin.sink.property.ClientPoolProperty; +import org.apache.iotdb.collector.plugin.builtin.sink.property.ThriftClientProperty; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.ClientManager; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.async.AsyncPipeDataTransferServiceClient; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +// import org.apache.iotdb.commons.concurrent.ThreadName; + +public class ClientPoolFactory { + + private ClientPoolFactory() {} + + public static class AsyncPipeDataTransferServiceClientPoolFactory + implements IClientPoolFactory { + + @Override + public GenericKeyedObjectPool createClientPool( + ClientManager manager) { + final GenericKeyedObjectPool clientPool = + new GenericKeyedObjectPool<>( + new AsyncPipeDataTransferServiceClient.Factory( + manager, + new ThriftClientProperty.Builder() + .setConnectionTimeoutMs( + PipeOptions.PIPE_CONNECTOR_TRANSFER_TIMEOUT_MS.value()) + .setRpcThriftCompressionEnabled( + PipeOptions.PIPE_CONNECTOR_RPC_THRIFT_COMPRESSION_ENABLED.value()) + .setSelectorNumOfAsyncClientManager( + PipeOptions.PIPE_ASYNC_CONNECTOR_SELECTOR_NUMBER.value()) + .build(), + "" + // ThreadName.PIPE_ASYNC_CONNECTOR_CLIENT_POOL.getName() + ), + new ClientPoolProperty.Builder() + .setMaxClientNumForEachNode( + PipeOptions.PIPE_ASYNC_CONNECTOR_MAX_CLIENT_NUMBER.value()) + .build() + .getConfig()); + return clientPool; + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/IClientPoolFactory.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/IClientPoolFactory.java new file mode 100644 index 00000000..ef38f82e --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/IClientPoolFactory.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.factory; + +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.ClientManager; + +public interface IClientPoolFactory { + + /** + * We can implement this interface in other modules and then set the corresponding expected + * parameters and client factory classes. + * + * @param manager the reference to the clientManager + * @return A concurrency safe object pool + */ + GenericKeyedObjectPool createClientPool(ClientManager manager); +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/ThriftClientFactory.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/ThriftClientFactory.java new file mode 100644 index 00000000..f9ce7df6 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/client/factory/ThriftClientFactory.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.factory; + +import org.apache.iotdb.collector.plugin.builtin.sink.property.ThriftClientProperty; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.ClientManager; + +public abstract class ThriftClientFactory extends BaseClientFactory { + + protected ThriftClientProperty thriftClientProperty; + + protected ThriftClientFactory( + ClientManager clientManager, ThriftClientProperty thriftClientProperty) { + super(clientManager); + this.thriftClientProperty = thriftClientProperty; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/sync/IoTDBDataNodeSyncConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/sync/IoTDBDataNodeSyncConnector.java new file mode 100644 index 00000000..bd86795f --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/sync/IoTDBDataNodeSyncConnector.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.sync; + +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.collector.plugin.builtin.annotation.TableModel; +import org.apache.iotdb.collector.plugin.builtin.annotation.TreeModel; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.IoTDBDataNodeSyncClientManager; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.IoTDBSyncClientManager; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.NodeUrlUtils; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.UnknownHostException; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@TreeModel +@TableModel +public abstract class IoTDBDataNodeSyncConnector extends IoTDBSslSyncConnector { + + private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBDataNodeSyncConnector.class); + + protected IoTDBDataNodeSyncClientManager clientManager; + + @Override + public void validate(final PipeParameterValidator validator) throws Exception { + super.validate(validator); + + final Set givenNodeUrls = parseNodeUrls(validator.getParameters()); + + validator.validate( + empty -> { + try { + // Ensure the sink doesn't point to the thrift receiver on DataNode itself + return !NodeUrlUtils.containsLocalAddress( + givenNodeUrls.stream() + .filter(tEndPoint -> tEndPoint.getPort() == PipeOptions.RPC_PORT.value()) + .map(TEndPoint::getIp) + .collect(Collectors.toList())); + } catch (final UnknownHostException e) { + LOGGER.warn("Unknown host when checking pipe sink IP.", e); + return false; + } + }, + String.format( + "One of the endpoints %s of the receivers is pointing back to the thrift receiver %s on sender itself, " + + "or unknown host when checking pipe sink IP.", + givenNodeUrls, + new TEndPoint(PipeOptions.RPC_ADDRESS.value(), PipeOptions.RPC_PORT.value()))); + } + + @Override + protected IoTDBSyncClientManager constructClient( + final List nodeUrls, + final boolean useSSL, + final String trustStorePath, + final String trustStorePwd, + /* The following parameters are used locally. */ + final boolean useLeaderCache, + final String loadBalanceStrategy, + /* The following parameters are used to handshake with the receiver. */ + final String username, + final String password, + final boolean shouldReceiverConvertOnTypeMismatch, + final String loadTsFileStrategy, + final boolean validateTsFile, + final boolean shouldMarkAsPipeRequest) { + clientManager = + new IoTDBDataNodeSyncClientManager( + nodeUrls, + useSSL, + Objects.nonNull(trustStorePath) ? /*IoTDBConfig.addDataHomeDir(trustStorePath)*/ "" : null, + trustStorePwd, + useLeaderCache, + loadBalanceStrategy, + username, + password, + shouldReceiverConvertOnTypeMismatch, + loadTsFileStrategy, + validateTsFile, + shouldMarkAsPipeRequest); + return clientManager; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/sync/IoTDBDataRegionSyncConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/sync/IoTDBDataRegionSyncConnector.java new file mode 100644 index 00000000..2a0e1e07 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/sync/IoTDBDataRegionSyncConnector.java @@ -0,0 +1,502 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.sync; + +import org.apache.commons.io.FileUtils; +import org.apache.iotdb.collector.plugin.builtin.annotation.TableModel; +import org.apache.iotdb.collector.plugin.builtin.annotation.TreeModel; +import org.apache.iotdb.collector.plugin.builtin.sink.event.heartbeat.PipeHeartbeatEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.terminate.PipeTerminateEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.tsfile.PipeTsFileInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch.PipeTabletEventBatch; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch.PipeTabletEventPlainBatch; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch.PipeTabletEventTsFileBatch; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch.PipeTransferBatchReqBuilder; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTabletRawReqV2; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTsFilePieceReq; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTsFilePieceWithModReq; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTsFileSealWithModReq; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferFilePieceReq; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.IoTDBSyncClient; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.RetryUtils; +import org.apache.iotdb.collector.plugin.builtin.sink.utils.cacher.LeaderCacheUtils; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.pipe.api.customizer.configuration.PipeConnectorRuntimeConfiguration; +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters; +import org.apache.iotdb.pipe.api.event.Event; +import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent; +import org.apache.iotdb.pipe.api.event.dml.insertion.TsFileInsertionEvent; +import org.apache.iotdb.pipe.api.exception.PipeConnectionException; +import org.apache.iotdb.pipe.api.exception.PipeException; +import org.apache.iotdb.rpc.TSStatusCode; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferResp; +import org.apache.tsfile.exception.write.WriteProcessException; +import org.apache.tsfile.utils.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.file.NoSuchFileException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@TreeModel +@TableModel +public class IoTDBDataRegionSyncConnector extends IoTDBDataNodeSyncConnector { + + private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBDataRegionSyncConnector.class); + + private PipeTransferBatchReqBuilder tabletBatchBuilder; + + @Override + public void customize( + final PipeParameters parameters, final PipeConnectorRuntimeConfiguration configuration) + throws Exception { + super.customize(parameters, configuration); + + // tablet batch mode configuration + if (isTabletBatchModeEnabled) { + tabletBatchBuilder = new PipeTransferBatchReqBuilder(parameters); + } + } + + @Override + protected PipeTransferFilePieceReq getTransferSingleFilePieceReq( + final String fileName, final long position, final byte[] payLoad) throws IOException { + return PipeTransferTsFilePieceReq.toTPipeTransferReq(fileName, position, payLoad); + } + + @Override + protected PipeTransferFilePieceReq getTransferMultiFilePieceReq( + final String fileName, final long position, final byte[] payLoad) throws IOException { + return PipeTransferTsFilePieceWithModReq.toTPipeTransferReq(fileName, position, payLoad); + } + + @Override + public void transfer(final TabletInsertionEvent tabletInsertionEvent) throws Exception { + // PipeProcessor can change the type of TabletInsertionEvent + if (!(tabletInsertionEvent instanceof PipeRawTabletInsertionEvent)) { + LOGGER.warn( + "IoTDBThriftSyncConnector only support " + "PipeRawTabletInsertionEvent. " + "Ignore {}.", + tabletInsertionEvent); + return; + } + + try { + if (isTabletBatchModeEnabled) { + final Pair endPointAndBatch = + tabletBatchBuilder.onEvent(tabletInsertionEvent); + if (Objects.nonNull(endPointAndBatch)) { + doTransferWrapper(endPointAndBatch); + } + } else { + doTransferWrapper((PipeRawTabletInsertionEvent) tabletInsertionEvent); + } + } catch (final Exception e) { + throw new PipeConnectionException( + String.format( + "Failed to transfer tablet insertion event %s, because %s.", + ((PipeRawTabletInsertionEvent) tabletInsertionEvent).coreReportMessage(), e.getMessage()), + e); + } + } + + @Override + public void transfer(final TsFileInsertionEvent tsFileInsertionEvent) throws Exception { + // PipeProcessor can change the type of tsFileInsertionEvent + if (!(tsFileInsertionEvent instanceof PipeTsFileInsertionEvent)) { + LOGGER.warn( + "IoTDBThriftSyncConnector only support PipeTsFileInsertionEvent. Ignore {}.", + tsFileInsertionEvent); + return; + } + + try { + // In order to commit in order + if (isTabletBatchModeEnabled && !tabletBatchBuilder.isEmpty()) { + doTransferWrapper(); + } + + doTransferWrapper((PipeTsFileInsertionEvent) tsFileInsertionEvent); + } catch (final Exception e) { + throw new PipeConnectionException( + String.format( + "Failed to transfer tsfile insertion event %s, because %s.", + ((PipeTsFileInsertionEvent) tsFileInsertionEvent).coreReportMessage(), + e.getMessage()), + e); + } + } + + @Override + public void transfer(final Event event) throws Exception { + // if (event instanceof PipeDeleteDataNodeEvent) { + // doTransferWrapper((PipeDeleteDataNodeEvent) event); + // return; + // } + + // in order to commit in order + if (isTabletBatchModeEnabled && !tabletBatchBuilder.isEmpty()) { + doTransferWrapper(); + } + + if (!(event instanceof PipeHeartbeatEvent || event instanceof PipeTerminateEvent)) { + LOGGER.warn( + "IoTDBThriftSyncConnector does not support transferring generic event: {}.", event); + } + } + + // private void doTransferWrapper(final PipeDeleteDataNodeEvent pipeDeleteDataNodeEvent) + // throws PipeException { + // // We increase the reference count for this event to determine if the event may be released. + // if (!pipeDeleteDataNodeEvent.increaseReferenceCount( + // IoTDBDataRegionSyncConnector.class.getName())) { + // return; + // } + // try { + // doTransfer(pipeDeleteDataNodeEvent); + // } finally { + // pipeDeleteDataNodeEvent.decreaseReferenceCount( + // IoTDBDataRegionSyncConnector.class.getName(), false); + // } + // } + + // private void doTransfer(final PipeDeleteDataNodeEvent pipeDeleteDataNodeEvent) + // throws PipeException { + // final Pair clientAndStatus = clientManager.getClient(); + // + // final TPipeTransferResp resp; + // try { + // final TPipeTransferReq req = + // compressIfNeeded( + // PipeTransferPlanNodeReq.toTPipeTransferReq( + // pipeDeleteDataNodeEvent.getDeleteDataNode())); + // rateLimitIfNeeded( + // pipeDeleteDataNodeEvent.getPipeName(), + // pipeDeleteDataNodeEvent.getCreationTime(), + // clientAndStatus.getLeft().getEndPoint(), + // req.getBody().length); + // resp = clientAndStatus.getLeft().pipeTransfer(req); + // } catch (final Exception e) { + // clientAndStatus.setRight(false); + // throw new PipeConnectionException( + // String.format( + // "Network error when transfer deletion %s, because %s.", + // pipeDeleteDataNodeEvent.getDeleteDataNode().getType(), e.getMessage()), + // e); + // } + // + // final TSStatus status = resp.getStatus(); + // // Only handle the failed statuses to avoid string format performance overhead + // if (resp.getStatus().getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode() + // && resp.getStatus().getCode() != TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode()) { + // receiverStatusHandler.handle( + // status, + // String.format( + // "Transfer deletion %s error, result status %s.", + // pipeDeleteDataNodeEvent.getDeleteDataNode().getType(), status), + // pipeDeleteDataNodeEvent.getDeletionResource().toString()); + // } + // + // if (LOGGER.isDebugEnabled()) { + // LOGGER.debug("Successfully transferred deletion event {}.", pipeDeleteDataNodeEvent); + // } + // } + + private void doTransferWrapper() throws IOException, WriteProcessException { + for (final Pair nonEmptyBatch : + tabletBatchBuilder.getAllNonEmptyBatches()) { + doTransferWrapper(nonEmptyBatch); + } + } + + private void doTransferWrapper(final Pair endPointAndBatch) + throws IOException, WriteProcessException { + final PipeTabletEventBatch batch = endPointAndBatch.getRight(); + if (batch instanceof PipeTabletEventPlainBatch) { + doTransfer(endPointAndBatch.getLeft(), (PipeTabletEventPlainBatch) batch); + } else if (batch instanceof PipeTabletEventTsFileBatch) { + doTransfer((PipeTabletEventTsFileBatch) batch); + } else { + LOGGER.warn("Unsupported batch type {}.", batch.getClass()); + } + batch.decreaseEventsReferenceCount(IoTDBDataRegionSyncConnector.class.getName(), true); + batch.onSuccess(); + } + + private void doTransfer( + final TEndPoint endPoint, final PipeTabletEventPlainBatch batchToTransfer) { + final Pair clientAndStatus = clientManager.getClient(endPoint); + + final TPipeTransferResp resp; + try { + final TPipeTransferReq uncompressedReq = batchToTransfer.toTPipeTransferReq(); + final long uncompressedSize = uncompressedReq.getBody().length; + + final TPipeTransferReq req = compressIfNeeded(uncompressedReq); + final long compressedSize = req.getBody().length; + + final double compressionRatio = (double) compressedSize / uncompressedSize; + + for (final Map.Entry, Long> entry : + batchToTransfer.getPipe2BytesAccumulated().entrySet()) { + rateLimitIfNeeded( + entry.getKey().getLeft(), + entry.getKey().getRight(), + clientAndStatus.getLeft().getEndPoint(), + (long) (entry.getValue() * compressionRatio)); + } + + resp = clientAndStatus.getLeft().pipeTransfer(req); + } catch (final Exception e) { + clientAndStatus.setRight(false); + throw new PipeConnectionException( + String.format("Network error when transfer tablet batch, because %s.", e.getMessage()), + e); + } + + final TSStatus status = resp.getStatus(); + // Only handle the failed statuses to avoid string format performance overhead + if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode() + && status.getCode() != TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode()) { + receiverStatusHandler.handle( + resp.getStatus(), + String.format("Transfer PipeTransferTabletBatchReq error, result status %s", resp.status), + batchToTransfer.deepCopyEvents().toString()); + } + + for (final Pair redirectPair : + LeaderCacheUtils.parseRecommendedRedirections(status)) { + clientManager.updateLeaderCache(redirectPair.getLeft(), redirectPair.getRight()); + } + } + + private void doTransfer(final PipeTabletEventTsFileBatch batchToTransfer) + throws IOException, WriteProcessException { + final List> dbTsFilePairs = batchToTransfer.sealTsFiles(); + final Map, Double> pipe2WeightMap = batchToTransfer.deepCopyPipe2WeightMap(); + + for (final Pair dbTsFile : dbTsFilePairs) { + doTransfer(pipe2WeightMap, dbTsFile.right, null, dbTsFile.left); + try { + RetryUtils.retryOnException( + () -> { + FileUtils.delete(dbTsFile.right); + return null; + }); + } catch (final NoSuchFileException e) { + LOGGER.info("The file {} is not found, may already be deleted.", dbTsFile); + } catch (final Exception e) { + LOGGER.warn( + "Failed to delete batch file {}, this file should be deleted manually later", dbTsFile); + } + } + } + + private void doTransferWrapper(final PipeRawTabletInsertionEvent pipeRawTabletInsertionEvent) + throws PipeException { + // We increase the reference count for this event to determine if the event may be released. + if (!pipeRawTabletInsertionEvent.increaseReferenceCount( + IoTDBDataRegionSyncConnector.class.getName())) { + return; + } + try { + doTransfer(pipeRawTabletInsertionEvent); + } finally { + pipeRawTabletInsertionEvent.decreaseReferenceCount( + IoTDBDataRegionSyncConnector.class.getName(), false); + } + } + + private void doTransfer(final PipeRawTabletInsertionEvent pipeRawTabletInsertionEvent) + throws PipeException { + final Pair clientAndStatus = + clientManager.getClient(pipeRawTabletInsertionEvent.getDeviceId()); + final TPipeTransferResp resp; + + try { + final TPipeTransferReq req = + compressIfNeeded( + PipeTransferTabletRawReqV2.toTPipeTransferReq( + pipeRawTabletInsertionEvent.convertToTablet(), + pipeRawTabletInsertionEvent.isAligned(), + pipeRawTabletInsertionEvent.isTableModelEvent() + ? pipeRawTabletInsertionEvent.getTableModelDatabaseName() + : null)); + rateLimitIfNeeded( + pipeRawTabletInsertionEvent.getPipeName(), + pipeRawTabletInsertionEvent.getCreationTime(), + clientAndStatus.getLeft().getEndPoint(), + req.getBody().length); + resp = clientAndStatus.getLeft().pipeTransfer(req); + } catch (final Exception e) { + clientAndStatus.setRight(false); + throw new PipeConnectionException( + String.format( + "Network error when transfer raw tablet insertion event, because %s.", + e.getMessage()), + e); + } + + final TSStatus status = resp.getStatus(); + // Only handle the failed statuses to avoid string format performance overhead + if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode() + && status.getCode() != TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode()) { + receiverStatusHandler.handle( + status, + String.format( + "Transfer PipeRawTabletInsertionEvent %s error, result status %s", + pipeRawTabletInsertionEvent.coreReportMessage(), status), + pipeRawTabletInsertionEvent.toString()); + } + if (status.isSetRedirectNode()) { + clientManager.updateLeaderCache( + pipeRawTabletInsertionEvent.getDeviceId(), status.getRedirectNode()); + } + } + + private void doTransferWrapper(final PipeTsFileInsertionEvent pipeTsFileInsertionEvent) + throws PipeException, IOException { + // We increase the reference count for this event to determine if the event may be released. + if (!pipeTsFileInsertionEvent.increaseReferenceCount( + IoTDBDataRegionSyncConnector.class.getName())) { + return; + } + try { + doTransfer( + Collections.singletonMap( + new Pair<>( + pipeTsFileInsertionEvent.getPipeName(), + pipeTsFileInsertionEvent.getCreationTime()), + 1.0), + pipeTsFileInsertionEvent.getTsFile(), + pipeTsFileInsertionEvent.isWithMod() ? pipeTsFileInsertionEvent.getModFile() : null, + pipeTsFileInsertionEvent.isTableModelEvent() + ? pipeTsFileInsertionEvent.getTableModelDatabaseName() + : null); + } finally { + pipeTsFileInsertionEvent.decreaseReferenceCount( + IoTDBDataRegionSyncConnector.class.getName(), false); + } + } + + private void doTransfer( + final Map, Double> pipeName2WeightMap, + final File tsFile, + final File modFile, + final String dataBaseName) + throws PipeException, IOException { + + final Pair clientAndStatus = clientManager.getClient(); + final TPipeTransferResp resp; + + // 1. Transfer tsFile, and mod file if exists and receiver's version >= 2 + if (Objects.nonNull(modFile) && clientManager.supportModsIfIsDataNodeReceiver()) { + transferFilePieces(pipeName2WeightMap, modFile, clientAndStatus, true); + transferFilePieces(pipeName2WeightMap, tsFile, clientAndStatus, true); + + // 2. Transfer file seal signal with mod, which means the file is transferred completely + try { + final TPipeTransferReq req = + compressIfNeeded( + PipeTransferTsFileSealWithModReq.toTPipeTransferReq( + modFile.getName(), + modFile.length(), + tsFile.getName(), + tsFile.length(), + dataBaseName)); + + pipeName2WeightMap.forEach( + (pipePair, weight) -> + rateLimitIfNeeded( + pipePair.getLeft(), + pipePair.getRight(), + clientAndStatus.getLeft().getEndPoint(), + (long) (req.getBody().length * weight))); + + resp = clientAndStatus.getLeft().pipeTransfer(req); + } catch (final Exception e) { + clientAndStatus.setRight(false); + clientManager.adjustTimeoutIfNecessary(e); + throw new PipeConnectionException( + String.format("Network error when seal file %s, because %s.", tsFile, e.getMessage()), + e); + } + } else { + transferFilePieces(pipeName2WeightMap, tsFile, clientAndStatus, false); + + // 2. Transfer file seal signal without mod, which means the file is transferred completely + try { + final TPipeTransferReq req = + compressIfNeeded( + PipeTransferTsFileSealWithModReq.toTPipeTransferReq( + tsFile.getName(), tsFile.length(), dataBaseName)); + + pipeName2WeightMap.forEach( + (pipePair, weight) -> + rateLimitIfNeeded( + pipePair.getLeft(), + pipePair.getRight(), + clientAndStatus.getLeft().getEndPoint(), + (long) (req.getBody().length * weight))); + + resp = clientAndStatus.getLeft().pipeTransfer(req); + } catch (final Exception e) { + clientAndStatus.setRight(false); + clientManager.adjustTimeoutIfNecessary(e); + throw new PipeConnectionException( + String.format("Network error when seal file %s, because %s.", tsFile, e.getMessage()), + e); + } + } + + final TSStatus status = resp.getStatus(); + // Only handle the failed statuses to avoid string format performance overhead + if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode() + && status.getCode() != TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode()) { + receiverStatusHandler.handle( + resp.getStatus(), + String.format("Seal file %s error, result status %s.", tsFile, resp.getStatus()), + tsFile.getName()); + } + + LOGGER.info("Successfully transferred file {}.", tsFile); + } + + @Override + public synchronized void discardEventsOfPipe(final String pipeNameToDrop, final int regionId) { + tabletBatchBuilder.discardEventsOfPipe(pipeNameToDrop, regionId); + } + + @Override + public void close() { + if (tabletBatchBuilder != null) { + tabletBatchBuilder.close(); + } + + super.close(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/sync/IoTDBSslSyncConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/sync/IoTDBSslSyncConnector.java new file mode 100644 index 00000000..67cfc52c --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/sync/IoTDBSslSyncConnector.java @@ -0,0 +1,257 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.sync; + +import com.google.common.collect.ImmutableList; +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.collector.plugin.builtin.annotation.TableModel; +import org.apache.iotdb.collector.plugin.builtin.annotation.TreeModel; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferFilePieceReq; +import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.response.PipeTransferFilePieceResp; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.airgap.IoTDBConnector; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.IoTDBSyncClient; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift.client.IoTDBSyncClientManager; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.pipe.api.customizer.configuration.PipeConnectorRuntimeConfiguration; +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator; +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters; +import org.apache.iotdb.pipe.api.exception.PipeConnectionException; +import org.apache.iotdb.pipe.api.exception.PipeException; +import org.apache.iotdb.rpc.TSStatusCode; +import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; +import org.apache.tsfile.utils.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LEADER_CACHE_ENABLE_DEFAULT_VALUE; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.CONNECTOR_LEADER_CACHE_ENABLE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_SSL_ENABLE_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_SSL_TRUST_STORE_PATH_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_IOTDB_SSL_TRUST_STORE_PWD_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_KEY; +import static org.apache.iotdb.collector.plugin.builtin.sink.constant.PipeConnectorConstant.SINK_LEADER_CACHE_ENABLE_KEY; + +@TreeModel +@TableModel +public abstract class IoTDBSslSyncConnector extends IoTDBConnector { + + private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBSslSyncConnector.class); + + protected IoTDBSyncClientManager clientManager; + + @Override + public void validate(final PipeParameterValidator validator) throws Exception { + super.validate(validator); + + final PipeParameters parameters = validator.getParameters(); + + final String userSpecifiedConnectorName = + parameters + .getStringOrDefault( + ImmutableList.of(CONNECTOR_KEY, SINK_KEY), "" + /*IOTDB_THRIFT_CONNECTOR.getPipePluginName()*/) + .toLowerCase(); + + validator.validate( + args -> !((boolean) args[0]) || ((boolean) args[1] && (boolean) args[2]), + String.format( + "When ssl transport is enabled, %s and %s must be specified", + SINK_IOTDB_SSL_TRUST_STORE_PATH_KEY, SINK_IOTDB_SSL_TRUST_STORE_PWD_KEY), + // IOTDB_THRIFT_SSL_CONNECTOR.getPipePluginName().equals(userSpecifiedConnectorName) + // || IOTDB_THRIFT_SSL_SINK.getPipePluginName().equals(userSpecifiedConnectorName) + /*|| */parameters.getBooleanOrDefault(SINK_IOTDB_SSL_ENABLE_KEY, false), + parameters.hasAttribute(SINK_IOTDB_SSL_TRUST_STORE_PATH_KEY), + parameters.hasAttribute(SINK_IOTDB_SSL_TRUST_STORE_PWD_KEY)); + } + + @Override + public void customize( + final PipeParameters parameters, final PipeConnectorRuntimeConfiguration configuration) + throws Exception { + super.customize(parameters, configuration); + + // ssl transport configuration + // final String userSpecifiedConnectorName = + // parameters + // .getStringOrDefault( + // ImmutableList.of(CONNECTOR_KEY, SINK_KEY), + // IOTDB_THRIFT_CONNECTOR.getPipePluginName()) + // .toLowerCase(); + // final boolean useSSL = + // IOTDB_THRIFT_SSL_CONNECTOR.getPipePluginName().equals(userSpecifiedConnectorName) + // || IOTDB_THRIFT_SSL_SINK.getPipePluginName().equals(userSpecifiedConnectorName) + // || parameters.getBooleanOrDefault(SINK_IOTDB_SSL_ENABLE_KEY, false); + final String trustStorePath = parameters.getString(SINK_IOTDB_SSL_TRUST_STORE_PATH_KEY); + final String trustStorePwd = parameters.getString(SINK_IOTDB_SSL_TRUST_STORE_PWD_KEY); + + // leader cache configuration + final boolean useLeaderCache = + parameters.getBooleanOrDefault( + Arrays.asList(SINK_LEADER_CACHE_ENABLE_KEY, CONNECTOR_LEADER_CACHE_ENABLE_KEY), + CONNECTOR_LEADER_CACHE_ENABLE_DEFAULT_VALUE); + + clientManager = + constructClient( + nodeUrls, + // useSSL, + true, + trustStorePath, + trustStorePwd, + useLeaderCache, + loadBalanceStrategy, + username, + password, + shouldReceiverConvertOnTypeMismatch, + loadTsFileStrategy, + loadTsFileValidation, + shouldMarkAsPipeRequest); + } + + protected abstract IoTDBSyncClientManager constructClient( + final List nodeUrls, + final boolean useSSL, + final String trustStorePath, + final String trustStorePwd, + /* The following parameters are used locally. */ + final boolean useLeaderCache, + final String loadBalanceStrategy, + /* The following parameters are used to handshake with the receiver. */ + final String username, + final String password, + final boolean shouldReceiverConvertOnTypeMismatch, + final String loadTsFileStrategy, + final boolean validateTsFile, + final boolean shouldMarkAsPipeRequest); + + @Override + public void handshake() throws Exception { + clientManager.checkClientStatusAndTryReconstructIfNecessary(); + } + + @Override + public void heartbeat() { + try { + handshake(); + } catch (final Exception e) { + LOGGER.warn( + "Failed to reconnect to target server, because: {}. Try to reconnect later.", + e.getMessage(), + e); + } + } + + protected void transferFilePieces( + final Map, Double> pipe2WeightMap, + final File file, + final Pair clientAndStatus, + final boolean isMultiFile) + throws PipeException, IOException { + final int readFileBufferSize = PipeOptions.PIPE_CONNECTOR_READ_FILE_BUFFER_SIZE.value(); + final byte[] readBuffer = new byte[readFileBufferSize]; + long position = 0; + try (final RandomAccessFile reader = new RandomAccessFile(file, "r")) { + while (true) { + final int readLength = reader.read(readBuffer); + if (readLength == -1) { + break; + } + + final byte[] payLoad = + readLength == readFileBufferSize + ? readBuffer + : Arrays.copyOfRange(readBuffer, 0, readLength); + final PipeTransferFilePieceResp resp; + try { + final TPipeTransferReq req = + compressIfNeeded( + isMultiFile + ? getTransferMultiFilePieceReq(file.getName(), position, payLoad) + : getTransferSingleFilePieceReq(file.getName(), position, payLoad)); + pipe2WeightMap.forEach( + (namePair, weight) -> + rateLimitIfNeeded( + namePair.getLeft(), + namePair.getRight(), + clientAndStatus.getLeft().getEndPoint(), + (long) (req.getBody().length * weight))); + resp = + PipeTransferFilePieceResp.fromTPipeTransferResp( + clientAndStatus.getLeft().pipeTransfer(req)); + } catch (final Exception e) { + clientAndStatus.setRight(false); + throw new PipeConnectionException( + String.format( + "Network error when transfer file %s, because %s.", file, e.getMessage()), + e); + } + + position += readLength; + + final TSStatus status = resp.getStatus(); + // This case only happens when the connection is broken, and the connector is reconnected + // to the receiver, then the receiver will redirect the file position to the last position + if (status.getCode() == TSStatusCode.PIPE_TRANSFER_FILE_OFFSET_RESET.getStatusCode()) { + position = resp.getEndWritingOffset(); + reader.seek(position); + LOGGER.info("Redirect file position to {}.", position); + continue; + } + + // Send handshake req and then re-transfer the event + if (status.getCode() + == TSStatusCode.PIPE_CONFIG_RECEIVER_HANDSHAKE_NEEDED.getStatusCode()) { + clientManager.sendHandshakeReq(clientAndStatus); + } + // Only handle the failed statuses to avoid string format performance overhead + if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode() + && status.getCode() != TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode()) { + receiverStatusHandler.handle( + resp.getStatus(), + String.format("Transfer file %s error, result status %s.", file, resp.getStatus()), + file.getName()); + } + } + } + } + + protected abstract PipeTransferFilePieceReq getTransferSingleFilePieceReq( + final String fileName, final long position, final byte[] payLoad) throws IOException; + + protected abstract PipeTransferFilePieceReq getTransferMultiFilePieceReq( + final String fileName, final long position, final byte[] payLoad) throws IOException; + + @Override + public void close() { + if (clientManager != null) { + clientManager.close(); + } + + super.close(); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/DateTimeUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/DateTimeUtils.java new file mode 100644 index 00000000..963b86c5 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/DateTimeUtils.java @@ -0,0 +1,903 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.iotdb.collector.plugin.builtin.sink.utils; + +import static org.apache.iotdb.rpc.TSStatusCode.NUMERIC_VALUE_OUT_OF_RANGE; + +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.format.ResolverStyle; +import java.time.format.SignStyle; +import java.time.temporal.ChronoField; +import java.util.Calendar; +import java.util.TimeZone; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.tsfile.utils.DateUtils; +import org.apache.tsfile.utils.TimeDuration; + +public class DateTimeUtils { + + private DateTimeUtils() { + // forbidding instantiation + } + + public static final String TIMESTAMP_PRECISION = PipeOptions.TIMESTAMP_PRECISION.value(); + + public static long correctPrecision(long millis) { + try { + switch (TIMESTAMP_PRECISION) { + case "us": + case "microsecond": + return Math.multiplyExact(millis, 1_000L); + case "ns": + case "nanosecond": + return Math.multiplyExact(millis, 1_000_000L); + case "ms": + case "millisecond": + default: + return millis; + } + } catch (ArithmeticException e) { + throw new IoTDBRuntimeException( + String.format( + "Timestamp overflow, Millisecond: %s , Timestamp precision: %s", + millis, TIMESTAMP_PRECISION), + NUMERIC_VALUE_OUT_OF_RANGE.getStatusCode(), + true); + } + } + + private static Function CAST_TIMESTAMP_TO_MS; + + static { + switch (PipeOptions.TIMESTAMP_PRECISION.value()) { + case "us": + case "microsecond": + CAST_TIMESTAMP_TO_MS = timestamp -> timestamp / 1000; + break; + case "ns": + case "nanosecond": + CAST_TIMESTAMP_TO_MS = timestamp -> timestamp / 1000000; + break; + case "ms": + case "millisecond": + default: + CAST_TIMESTAMP_TO_MS = timestamp -> timestamp; + break; + } + } + + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + public static final DateTimeFormatter ISO_LOCAL_DATE_WIDTH_1_2; + + static { + ISO_LOCAL_DATE_WIDTH_1_2 = + new DateTimeFormatterBuilder() + .appendValue(ChronoField.YEAR, 4, 19, SignStyle.NEVER) + .appendLiteral('-') + .appendValue(ChronoField.MONTH_OF_YEAR, 1, 2, SignStyle.NEVER) + .appendLiteral('-') + .appendValue(ChronoField.DAY_OF_MONTH, 1, 2, SignStyle.NEVER) + .toFormatter(); + } + + /** such as '2011/12/03'. */ + public static final DateTimeFormatter ISO_LOCAL_DATE_WITH_SLASH; + + static { + ISO_LOCAL_DATE_WITH_SLASH = + new DateTimeFormatterBuilder() + .appendValue(ChronoField.YEAR, 4, 19, SignStyle.NEVER) + .appendLiteral('/') + .appendValue(ChronoField.MONTH_OF_YEAR, 1, 2, SignStyle.NEVER) + .appendLiteral('/') + .appendValue(ChronoField.DAY_OF_MONTH, 1, 2, SignStyle.NEVER) + .toFormatter(); + } + + /** such as '2011.12.03'. */ + public static final DateTimeFormatter ISO_LOCAL_DATE_WITH_DOT; + + static { + ISO_LOCAL_DATE_WITH_DOT = + new DateTimeFormatterBuilder() + .appendValue(ChronoField.YEAR, 4, 19, SignStyle.NEVER) + .appendLiteral('.') + .appendValue(ChronoField.MONTH_OF_YEAR, 1, 2, SignStyle.NEVER) + .appendLiteral('.') + .appendValue(ChronoField.DAY_OF_MONTH, 1, 2, SignStyle.NEVER) + .toFormatter(); + } + + /** such as '10:15:30' or '10:15:30.123'. */ + public static final DateTimeFormatter ISO_LOCAL_TIME_WITH_MS; + + static { + ISO_LOCAL_TIME_WITH_MS = + new DateTimeFormatterBuilder() + .appendValue(ChronoField.HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(ChronoField.MINUTE_OF_HOUR, 2) + .appendLiteral(':') + .appendValue(ChronoField.SECOND_OF_MINUTE, 2) + .optionalStart() + .appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true) + .toFormatter(); + } + + /** such as '10:15:30' or '10:15:30.123456'. */ + public static final DateTimeFormatter ISO_LOCAL_TIME_WITH_US; + + static { + ISO_LOCAL_TIME_WITH_US = + new DateTimeFormatterBuilder() + .appendValue(ChronoField.HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(ChronoField.MINUTE_OF_HOUR, 2) + .appendLiteral(':') + .appendValue(ChronoField.SECOND_OF_MINUTE, 2) + .optionalStart() + .appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true) + .toFormatter(); + } + + /** such as '10:15:30' or '10:15:30.123456789'. */ + public static final DateTimeFormatter ISO_LOCAL_TIME_WITH_NS; + + static { + ISO_LOCAL_TIME_WITH_NS = + new DateTimeFormatterBuilder() + .appendValue(ChronoField.HOUR_OF_DAY, 2) + .appendLiteral(':') + .appendValue(ChronoField.MINUTE_OF_HOUR, 2) + .appendLiteral(':') + .appendValue(ChronoField.SECOND_OF_MINUTE, 2) + .optionalStart() + .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true) + .optionalEnd() + .toFormatter(); + } + + /** such as '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30.123+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_MS; + + static { + ISO_OFFSET_DATE_TIME_WITH_MS = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WIDTH_1_2) + .appendLiteral('T') + .append(ISO_LOCAL_TIME_WITH_MS) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30.123456+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_US; + + static { + ISO_OFFSET_DATE_TIME_WITH_US = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WIDTH_1_2) + .appendLiteral('T') + .append(ISO_LOCAL_TIME_WITH_US) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30.123456789+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_NS; + + static { + ISO_OFFSET_DATE_TIME_WITH_NS = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WIDTH_1_2) + .appendLiteral('T') + .append(ISO_LOCAL_TIME_WITH_NS) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011/12/03T10:15:30+01:00' or '2011/12/03T10:15:30.123+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SLASH; + + static { + ISO_OFFSET_DATE_TIME_WITH_SLASH = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WITH_SLASH) + .appendLiteral('T') + .append(ISO_LOCAL_TIME_WITH_MS) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011/12/03T10:15:30+01:00' or '2011/12/03T10:15:30.123456+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SLASH_US; + + static { + ISO_OFFSET_DATE_TIME_WITH_SLASH_US = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WITH_SLASH) + .appendLiteral('T') + .append(ISO_LOCAL_TIME_WITH_US) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011/12/03T10:15:30+01:00' or '2011/12/03T10:15:30.123456789+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SLASH_NS; + + static { + ISO_OFFSET_DATE_TIME_WITH_SLASH_NS = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WITH_SLASH) + .appendLiteral('T') + .append(ISO_LOCAL_TIME_WITH_NS) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011.12.03T10:15:30+01:00' or '2011.12.03T10:15:30.123+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_DOT; + + static { + ISO_OFFSET_DATE_TIME_WITH_DOT = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WITH_DOT) + .appendLiteral('T') + .append(ISO_LOCAL_TIME_WITH_MS) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011.12.03T10:15:30+01:00' or '2011.12.03T10:15:30.123456+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_DOT_US; + + static { + ISO_OFFSET_DATE_TIME_WITH_DOT_US = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WITH_DOT) + .appendLiteral('T') + .append(ISO_LOCAL_TIME_WITH_US) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011.12.03T10:15:30+01:00' or '2011.12.03T10:15:30.123456789+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_DOT_NS; + + static { + ISO_OFFSET_DATE_TIME_WITH_DOT_NS = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WITH_DOT) + .appendLiteral('T') + .append(ISO_LOCAL_TIME_WITH_NS) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011-12-03 10:15:30+01:00' or '2011-12-03 10:15:30.123+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SPACE; + + static { + ISO_OFFSET_DATE_TIME_WITH_SPACE = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(DateTimeFormatter.ISO_LOCAL_DATE) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME_WITH_MS) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011-12-03 10:15:30+01:00' or '2011-12-03 10:15:30.123456+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SPACE_US; + + static { + ISO_OFFSET_DATE_TIME_WITH_SPACE_US = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(DateTimeFormatter.ISO_LOCAL_DATE) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME_WITH_US) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011-12-03 10:15:30+01:00' or '2011-12-03 10:15:30.123456789+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SPACE_NS; + + static { + ISO_OFFSET_DATE_TIME_WITH_SPACE_NS = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(DateTimeFormatter.ISO_LOCAL_DATE) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME_WITH_NS) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011/12/03 10:15:30+01:00' or '2011/12/03 10:15:30.123+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE; + + static { + ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WITH_SLASH) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME_WITH_MS) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011/12/03 10:15:30+01:00' or '2011/12/03 10:15:30.123456+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE_US; + + static { + ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE_US = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WITH_SLASH) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME_WITH_US) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011/12/03 10:15:30+01:00' or '2011/12/03 10:15:30.123456789+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE_NS; + + static { + ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE_NS = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WITH_SLASH) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME_WITH_NS) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011.12.03 10:15:30+01:00' or '2011.12.03 10:15:30.123+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE; + + static { + ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WITH_DOT) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME_WITH_MS) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011.12.03 10:15:30+01:00' or '2011.12.03 10:15:30.123456+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE_US; + + static { + ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE_US = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WITH_DOT) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME_WITH_US) + .appendOffsetId() + .toFormatter(); + } + + /** such as '2011.12.03 10:15:30+01:00' or '2011.12.03 10:15:30.123456789+01:00'. */ + public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE_NS; + + static { + ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE_NS = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE_WITH_DOT) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME_WITH_NS) + .appendOffsetId() + .toFormatter(); + } + + public static final DateTimeFormatter formatter = + new DateTimeFormatterBuilder() + /** + * The ISO date-time formatter that formats or parses a date-time with an offset, such as + * '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30.123+01:00'. + */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_MS) + + /** such as '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30.123456+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_US) + + /** such as '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30.123456789+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_NS) + + /** such as '2011/12/03T10:15:30+01:00' or '2011/12/03T10:15:30.123+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SLASH) + + /** such as '2011/12/03T10:15:30+01:00' or '2011/12/03T10:15:30.123456+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SLASH_US) + + /** such as '2011/12/03T10:15:30+01:00' or '2011/12/03T10:15:30.123456789+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SLASH_NS) + + /** such as '2011.12.03T10:15:30+01:00' or '2011.12.03T10:15:30.123+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_DOT) + + /** such as '2011.12.03T10:15:30+01:00' or '2011.12.03T10:15:30.123456+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_DOT_US) + + /** such as '2011.12.03T10:15:30+01:00' or '2011.12.03T10:15:30.123456789+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_DOT_NS) + + /** such as '2011-12-03 10:15:30+01:00' or '2011-12-03 10:15:30.123+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SPACE) + + /** such as '2011-12-03 10:15:30+01:00' or '2011-12-03 10:15:30.123456+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SPACE_US) + + /** such as '2011-12-03 10:15:30+01:00' or '2011-12-03 10:15:30.123456789+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SPACE_NS) + + /** such as '2011/12/03 10:15:30+01:00' or '2011/12/03 10:15:30.123+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE) + + /** such as '2011/12/03 10:15:30+01:00' or '2011/12/03 10:15:30.123456+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE_US) + + /** such as '2011/12/03 10:15:30+01:00' or '2011/12/03 10:15:30.123456789+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE_NS) + + /** such as '2011.12.03 10:15:30+01:00' or '2011.12.03 10:15:30.123+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE) + + /** such as '2011.12.03 10:15:30+01:00' or '2011.12.03 10:15:30.123456+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE_US) + + /** such as '2011.12.03 10:15:30+01:00' or '2011.12.03 10:15:30.123456789+01:00'. */ + .appendOptional(ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE_NS) + .toFormatter() + .withResolverStyle(ResolverStyle.STRICT); + + public static long convertTimestampOrDatetimeStrToLongWithDefaultZone(String timeStr) { + try { + return Long.parseLong(timeStr); + } catch (NumberFormatException e) { + return DateTimeUtils.convertDatetimeStrToLong(timeStr, ZoneId.systemDefault()); + } + } + + public static long convertDatetimeStrToLong(String str, ZoneId zoneId) { + return convertDatetimeStrToLong( + str, toZoneOffset(zoneId), 0, PipeOptions.TIMESTAMP_PRECISION.value()); + } + + public static long convertDatetimeStrToLong( + String str, ZoneId zoneId, String timestampPrecision) { + return convertDatetimeStrToLong(str, toZoneOffset(zoneId), 0, timestampPrecision); + } + + public static long getInstantWithPrecision(String str, String timestampPrecision) { + try { + ZonedDateTime zonedDateTime = ZonedDateTime.parse(str, formatter); + Instant instant = zonedDateTime.toInstant(); + if ("us".equals(timestampPrecision) || "microsecond".equals(timestampPrecision)) { + if (instant.getEpochSecond() < 0 && instant.getNano() > 0) { + // adjustment can reduce the loss of the division + long millis = Math.multiplyExact(instant.getEpochSecond() + 1, 1000_000L); + long adjustment = instant.getNano() / 1000 - 1L; + return Math.addExact(millis, adjustment); + } else { + long millis = Math.multiplyExact(instant.getEpochSecond(), 1000_000L); + return Math.addExact(millis, instant.getNano() / 1000); + } + } else if ("ns".equals(timestampPrecision) || "nanosecond".equals(timestampPrecision)) { + long millis = Math.multiplyExact(instant.getEpochSecond(), 1000_000_000L); + return Math.addExact(millis, instant.getNano()); + } + return instant.toEpochMilli(); + } catch (DateTimeParseException e) { + throw new RuntimeException(e.getMessage()); + } + } + + /** convert date time string to millisecond, microsecond or nanosecond. */ + public static long convertDatetimeStrToLong( + String str, ZoneOffset offset, int depth, String timestampPrecision) { + if (depth >= 2) { + throw new DateTimeException( + String.format( + "Failed to convert %s to millisecond, zone offset is %s, " + + "please input like 2011-12-03T10:15:30 or 2011-12-03T10:15:30+01:00", + str, offset)); + } + if (str.contains("Z")) { + return convertDatetimeStrToLong( + str.substring(0, str.indexOf('Z')) + "+00:00", offset, depth, timestampPrecision); + } else if (str.length() == 10) { + return convertDatetimeStrToLong(str + "T00:00:00", offset, depth, timestampPrecision); + } else if (str.length() - str.lastIndexOf('+') != 6 + && str.length() - str.lastIndexOf('-') != 6) { + return convertDatetimeStrToLong(str + offset, offset, depth + 1, timestampPrecision); + } else if (str.contains("[") || str.contains("]")) { + throw new DateTimeException( + String.format( + "%s with [time-region] at end is not supported now, " + + "please input like 2011-12-03T10:15:30 or 2011-12-03T10:15:30+01:00", + str)); + } + return getInstantWithPrecision(str, timestampPrecision); + } + + /** + * Convert duration string to time value. CurrentTime is used to calculate the days of natural + * month. If it's set as -1, which means a context free situation, then '1mo' will be thought as + * 30 days. + * + * @param duration represent duration string like: 12d8m9ns, 1y1mo, etc. + * @return time in milliseconds, microseconds, or nanoseconds depending on the profile + */ + public static long convertDurationStrToLong(String duration) { + return convertDurationStrToLong(-1, duration, false); + } + + public static long convertDurationStrToLong(String duration, boolean convertYearToMonth) { + return convertDurationStrToLong(-1, duration, convertYearToMonth); + } + + public static long convertDurationStrToLong( + String duration, String timestampPrecision, boolean convertYearToMonth) { + return convertDurationStrToLong(-1, duration, timestampPrecision, convertYearToMonth); + } + + public static long convertDurationStrToLong( + long currentTime, String duration, boolean convertYearToMonth) { + return convertDurationStrToLong( + currentTime, duration, PipeOptions.TIMESTAMP_PRECISION.value(), convertYearToMonth); + } + + /** + * convert duration string to time value. + * + * @param duration represent duration string like: 12d8m9ns, 1y1mo, etc. + * @param convertYearToMonth if we need convert year to month. eg: 1y -> 12mo + * @return time in milliseconds, microseconds, or nanoseconds depending on the profile + */ + public static long convertDurationStrToLong( + long currentTime, String duration, String timestampPrecision, boolean convertYearToMonth) { + long total = 0; + long temp = 0; + for (int i = 0; i < duration.length(); i++) { + char ch = duration.charAt(i); + if (Character.isDigit(ch)) { + temp *= 10; + temp += (ch - '0'); + } else { + String unit = String.valueOf(duration.charAt(i)); + // This is to identify units with two letters. + if (i + 1 < duration.length() && !Character.isDigit(duration.charAt(i + 1))) { + i++; + unit += duration.charAt(i); + } + unit = unit.toLowerCase(); + if (convertYearToMonth && unit.equals("y")) { + temp *= 12; + unit = "mo"; + } + total += + DateTimeUtils.convertDurationStrToLong( + currentTime == -1 ? -1 : currentTime + total, temp, unit, timestampPrecision); + temp = 0; + } + } + return total; + } + + @TestOnly + public static long convertDurationStrToLongForTest( + long value, String unit, String timestampPrecision) { + return convertDurationStrToLong(-1, value, unit, timestampPrecision); + } + + /** convert duration string to millisecond, microsecond or nanosecond. */ + public static long convertDurationStrToLong( + long currentTime, long value, String unit, String timestampPrecision) { + DurationUnit durationUnit = DurationUnit.valueOf(unit); + long res = value; + switch (durationUnit) { + case y: + case year: + res *= 365 * 86_400_000L; + break; + case mo: + case month: + if (currentTime == -1) { + res *= 30 * 86_400_000L; + } else { + Calendar calendar = Calendar.getInstance(); + // calendar.setTimeZone(SessionManager.getInstance().getSessionTimeZone()); + calendar.setTimeInMillis(currentTime); + calendar.add(Calendar.MONTH, (int) (value)); + res = calendar.getTimeInMillis() - currentTime; + } + break; + case w: + case week: + res *= 7 * 86_400_000L; + break; + case d: + case day: + res *= 86_400_000L; + break; + case h: + case hour: + res *= 3_600_000L; + break; + case m: + case minute: + res *= 60_000L; + break; + case s: + case second: + res *= 1_000L; + break; + default: + break; + } + + if ("us".equals(timestampPrecision) || "microsecond".equals(timestampPrecision)) { + if (unit.equals(DurationUnit.ns.toString()) + || unit.equals(DurationUnit.nanosecond.toString())) { + return value / 1000; + } else if (unit.equals(DurationUnit.us.toString()) + || unit.equals(DurationUnit.microsecond.toString())) { + return value; + } else { + return res * 1000; + } + } else if ("ns".equals(timestampPrecision) || "nanosecond".equals(timestampPrecision)) { + if (unit.equals(DurationUnit.ns.toString()) + || unit.equals(DurationUnit.nanosecond.toString())) { + return value; + } else if (unit.equals(DurationUnit.us.toString()) + || unit.equals(DurationUnit.microsecond.toString())) { + return value * 1000; + } else { + return res * 1000_000; + } + } else { + if (unit.equals(DurationUnit.ns.toString()) + || unit.equals(DurationUnit.nanosecond.toString())) { + return value / 1000_000; + } else if (unit.equals(DurationUnit.us.toString()) + || unit.equals(DurationUnit.microsecond.toString())) { + return value / 1000; + } else { + return res; + } + } + } + + public static TimeUnit timestampPrecisionStringToTimeUnit(String timestampPrecision) { + if ("us".equals(timestampPrecision) || "microsecond".equals(timestampPrecision)) { + return TimeUnit.MICROSECONDS; + } else if ("ns".equals(timestampPrecision) || "nanosecond".equals(timestampPrecision)) { + return TimeUnit.NANOSECONDS; + } else { + return TimeUnit.MILLISECONDS; + } + } + + public static String convertLongToDate(long timestamp) { + return convertLongToDate( + timestamp, PipeOptions.TIMESTAMP_PRECISION.value(), ZoneId.systemDefault()); + } + + public static String convertLongToDate(final long timestamp, final ZoneId zoneId) { + return convertLongToDate(timestamp, PipeOptions.TIMESTAMP_PRECISION.value(), zoneId); + } + + public static String convertLongToDate(final long timestamp, final String sourcePrecision) { + return convertLongToDate(timestamp, sourcePrecision, ZoneId.systemDefault()); + } + + public static String convertLongToDate( + long timestamp, final String sourcePrecision, final ZoneId zoneId) { + switch (sourcePrecision) { + case "ns": + case "nanosecond": + timestamp /= 1000_000; + break; + case "us": + case "microsecond": + timestamp /= 1000; + break; + default: + break; + } + return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zoneId).toString(); + } + + public static LocalDate convertToLocalDate(long timestamp, ZoneId zoneId) { + timestamp = CAST_TIMESTAMP_TO_MS.apply(timestamp); + return Instant.ofEpochMilli(timestamp).atZone(zoneId).toLocalDate(); + } + + public static ZonedDateTime convertToZonedDateTime(long timestamp, ZoneId zoneId) { + timestamp = CAST_TIMESTAMP_TO_MS.apply(timestamp); + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zoneId); + } + + public static ZoneOffset toZoneOffset(ZoneId zoneId) { + return zoneId.getRules().getOffset(Instant.now()); + } + + public static ZonedDateTime convertMillsecondToZonedDateTime(long millisecond) { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(millisecond), ZoneId.systemDefault()); + } + + public enum DurationUnit { + y, + year, + mo, + month, + w, + week, + d, + day, + h, + hour, + m, + minute, + s, + second, + ms, + millisecond, + us, + microsecond, + ns, + nanosecond + } + + public static TimeUnit toTimeUnit(String t) { + switch (t) { + case "h": + case "hour": + return TimeUnit.HOURS; + case "m": + case "minute": + return TimeUnit.MINUTES; + case "s": + case "second": + return TimeUnit.SECONDS; + case "ms": + case "millisecond": + return TimeUnit.MILLISECONDS; + case "u": + case "microsecond": + return TimeUnit.MICROSECONDS; + case "n": + case "nanosecond": + return TimeUnit.NANOSECONDS; + default: + throw new IllegalArgumentException("time precision must be one of: h,m,s,ms,u,n"); + } + } + + public static final long MS_TO_MONTH = 30 * 86400_000L; + + public static long calcPositiveIntervalByMonth( + long startTime, TimeDuration duration, ZoneId zoneId) { + return TimeDuration.calcPositiveIntervalByMonth( + startTime, duration, TimeZone.getTimeZone(zoneId), TimestampPrecisionUtils.currPrecision); + } + + /** + * Storage the duration into two parts: month part and non-month part, the non-month part's + * precision is depended on current time precision. e.g. ms precision: '1y1mo1ms' -> monthDuration + * = 13, nonMonthDuration = 1, ns precision: '1y1mo1ms' -> monthDuration = 13, nonMonthDuration = + * 1000_000. + * + * @param duration the input duration string + * @return the TimeDuration instance contains month part and non-month part + */ + public static TimeDuration constructTimeDuration(String duration) { + duration = duration.toLowerCase(); + String currTimePrecision = PipeOptions.TIMESTAMP_PRECISION.value(); + long temp = 0; + long monthDuration = 0; + long nonMonthDuration = 0; + int i = 0; + for (; i < duration.length(); i++) { + char ch = duration.charAt(i); + if (Character.isDigit(ch)) { + temp *= 10; + temp += (ch - '0'); + } else { + StringBuilder unit = new StringBuilder(String.valueOf(duration.charAt(i))); + i++; + // This is to identify units. + while (i < duration.length() && !Character.isDigit(duration.charAt(i))) { + unit.append(duration.charAt(i)); + i++; + } + i--; + if ("y".contentEquals(unit) || "year".contentEquals(unit)) { + monthDuration += temp * 12; + temp = 0; + continue; + } + if ("mo".contentEquals(unit) || "month".contentEquals(unit)) { + monthDuration += temp; + temp = 0; + continue; + } + nonMonthDuration += + DateTimeUtils.convertDurationStrToLong(-1, temp, unit.toString(), currTimePrecision); + temp = 0; + } + } + return new TimeDuration((int) monthDuration, nonMonthDuration); + } + + // public static Long parseDateTimeExpressionToLong(String dateExpression, ZoneId zoneId) { + // ASTVisitor astVisitor = new ASTVisitor(); + // astVisitor.setZoneId(zoneId); + // + // CharStream charStream1 = CharStreams.fromString(dateExpression); + // + // SqlLexer lexer1 = new SqlLexer(charStream1); + // lexer1.removeErrorListeners(); + // lexer1.addErrorListener(SqlParseError.INSTANCE); + // + // CommonTokenStream tokens1 = new CommonTokenStream(lexer1); + // + // IoTDBSqlParser parser1 = new IoTDBSqlParser(tokens1); + // parser1.getInterpreter().setPredictionMode(PredictionMode.SLL); + // parser1.removeErrorListeners(); + // parser1.addErrorListener(SqlParseError.INSTANCE); + // return astVisitor.parseDateExpression(parser1.dateExpression(), TIMESTAMP_PRECISION); + // } + + public static Integer parseDateExpressionToInt(String dateExpression) { + return DateUtils.parseDateExpressionToInt(dateExpression); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/FileUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/FileUtils.java new file mode 100644 index 00000000..8dc2ac56 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/FileUtils.java @@ -0,0 +1,430 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FilenameUtils; +// import org.apache.iotdb.commons.file.SystemFileFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Stack; + +public class FileUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(FileUtils.class); + + private static final int BUFFER_SIZE = 1024; + + private FileUtils() {} + + public static List listFilesRecursively(File dir, FileFilter fileFilter) { + List result = new ArrayList<>(); + Stack stack = new Stack<>(); + if (dir.exists()) { + stack.push(dir); + } + while (!stack.isEmpty()) { + File file = stack.pop(); + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File f : files) { + stack.push(f); + } + } + } + if (fileFilter.accept(file)) { + result.add(file); + } + } + return result; + } + + public static boolean deleteFileIfExist(File file) { + try { + Files.deleteIfExists(file.toPath()); + return true; + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + return false; + } + } + + public static void deleteFileOrDirectory(File file) { + deleteFileOrDirectory(file, false); + } + + public static void deleteFileOrDirectory(File file, boolean quietForNoSuchFile) { + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File subfile : files) { + deleteFileOrDirectory(subfile, quietForNoSuchFile); + } + } + } + try { + Files.delete(file.toPath()); + } catch (NoSuchFileException e) { + if (!quietForNoSuchFile) { + LOGGER.warn("{}: {}", e.getMessage(), Arrays.toString(file.list()), e); + } + } catch (DirectoryNotEmptyException e) { + LOGGER.warn("{}: {}", e.getMessage(), Arrays.toString(file.list()), e); + } catch (Exception e) { + LOGGER.warn("{}: {}", e.getMessage(), file.getName(), e); + } + } + + public static void deleteFileOrDirectoryWithRetry(File file) { + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File subfile : files) { + deleteFileOrDirectoryWithRetry(subfile); + } + } + } + try { + RetryUtils.retryOnException( + () -> { + Files.delete(file.toPath()); + return null; + }); + } catch (DirectoryNotEmptyException e) { + LOGGER.warn("{}: {}", e.getMessage(), Arrays.toString(file.list()), e); + } catch (Exception e) { + LOGGER.warn("{}: {}", e.getMessage(), file.getName(), e); + } + } + + public static void deleteDirectoryAndEmptyParent(File folder) { + deleteFileOrDirectory(folder); + final File parentFolder = folder.getParentFile(); + File[] files = parentFolder.listFiles(); + if (parentFolder.isDirectory() && (files == null || files.length == 0)) { + if (!parentFolder.delete()) { + LOGGER.warn("Delete folder failed: {}", parentFolder.getAbsolutePath()); + } + } + } + + public static boolean copyDir(File sourceDir, File targetDir) throws IOException { + if (!sourceDir.exists() || !sourceDir.isDirectory()) { + LOGGER.error( + "Failed to copy folder, because source folder [{}] doesn't exist.", + sourceDir.getAbsolutePath()); + return false; + } + if (!targetDir.exists() && !targetDir.mkdirs()) { + synchronized (FileUtils.class) { + if (!targetDir.exists() && !targetDir.mkdirs()) { + LOGGER.error( + "Failed to copy folder, because failed to create target folder[{}].", + targetDir.getAbsolutePath()); + return false; + } + } + } else if (!targetDir.isDirectory()) { + LOGGER.error( + "Failed to copy folder, because target folder [{}] already exist.", + targetDir.getAbsolutePath()); + return false; + } + File[] files = sourceDir.listFiles(); + if (files == null || files.length == 0) { + return true; + } + boolean result = true; + for (File file : files) { + if (!file.exists()) { + continue; + } + File targetFile = new File(targetDir, file.getName()); + if (file.isDirectory()) { + result &= copyDir(file.getAbsoluteFile(), targetFile); + } else { + // copy file + try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file)); + FileOutputStream fileOutputStream = new FileOutputStream(targetFile); + BufferedOutputStream out = new BufferedOutputStream(fileOutputStream)) { + byte[] bytes = new byte[BUFFER_SIZE]; + int size; + while ((size = in.read(bytes)) > 0) { + out.write(bytes, 0, size); + } + out.flush(); + fileOutputStream.getFD().sync(); // after try block, stream will call close automatically + } catch (IOException e) { + LOGGER.warn("get ioexception on file {}", file.getAbsolutePath(), e); + throw e; + } + } + } + return result; + } + +/* *//** + * Calculate the directory size including sub dir. + * + * @param path + * @return + *//* + public static long getDirSize(String path) { + long sum = 0; + File file = SystemFileFactory.INSTANCE.getFile(path); + if (file.isDirectory()) { + String[] list = file.list(); + for (String item : list) { + String subPath = path + File.separator + item; + sum += getDirSize(subPath); + } + } else { + // this is a file. + sum += file.length(); + } + return sum; + }*/ + + public static void recursivelyDeleteFolder(String path) throws IOException { + File file = new File(path); + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files == null || files.length == 0) { + org.apache.commons.io.FileUtils.deleteDirectory(file); + } else { + for (File f : files) { + recursivelyDeleteFolder(f.getAbsolutePath()); + } + org.apache.commons.io.FileUtils.deleteDirectory(file); + } + } else { + org.apache.commons.io.FileUtils.delete(file); + } + } + + /** Add a prefix to a relative path file */ + public static String addPrefix2FilePath(String prefix, String file) { + if (!new File(file).isAbsolute() && prefix != null && prefix.length() > 0) { + if (!prefix.endsWith(File.separator)) { + file = prefix + File.separatorChar + file; + } else { + file = prefix + file; + } + } + return file; + } + + /** + * Move source file to target file. The move will be divided into three steps: 1. Copy the source + * file to the "target.unfinished" location 2. Rename the "target.unfinished" to "target" 3. + * Delete the source file + * + * @param source source file, which can be a directory. + * @param target target file, which can be a directory. + * @return success or not + */ + public static boolean moveFileSafe(File source, File target) { + if (target.exists()) { + LOGGER.info( + "won't move file again because target file already exists: {}", target.getAbsolutePath()); + LOGGER.info("you may manually delete source file if necessary: {}", source.getAbsolutePath()); + return true; + } + + final String fromTo = + String.format("from %s to %s", source.getAbsolutePath(), target.getAbsolutePath()); + LOGGER.info("start to move file, {}", fromTo); + + // Prepare the xxx.unfinished File, delete it if it's already exist + File unfinishedTarget = new File(target.getAbsolutePath() + ".unfinished"); + try { + if (unfinishedTarget.exists()) { + if (unfinishedTarget.isFile()) { + org.apache.commons.io.FileUtils.delete(unfinishedTarget); + } else { + recursivelyDeleteFolder(unfinishedTarget.getAbsolutePath()); + } + } + } catch (IOException e) { + LOGGER.error( + "delete unfinished target file failed: {}", unfinishedTarget.getAbsolutePath(), e); + return false; + } + LOGGER.info( + "unfinished target file which was created last time has been deleted: {}", + unfinishedTarget.getAbsolutePath()); + + // Copy + try { + if (source.isDirectory()) { + if (!copyDir(source, unfinishedTarget)) { + LOGGER.error("file copy fail"); + return false; + } + } else { + org.apache.commons.io.FileUtils.copyFile(source, unfinishedTarget); + } + } catch (IOException e) { + LOGGER.error("file copy fail", e); + return false; + } + + // Rename + if (!unfinishedTarget.renameTo(target)) { + LOGGER.error("file rename fail"); + return false; + } + + // Delete old file + try { + if (source.isDirectory()) { + recursivelyDeleteFolder(source.getAbsolutePath()); + } else { + org.apache.commons.io.FileUtils.delete(source); + } + } catch (IOException e) { + LOGGER.error("delete source file fail: {}", source.getAbsolutePath(), e); + } + + LOGGER.info("move file success, {}", fromTo); + return true; + } + + public static File createHardLink(File sourceFile, File hardlink) throws IOException { + if (!hardlink.getParentFile().exists() && !hardlink.getParentFile().mkdirs()) { + synchronized (FileUtils.class) { + if (!hardlink.getParentFile().exists() && !hardlink.getParentFile().mkdirs()) { + throw new IOException( + String.format( + "failed to create hardlink %s for file %s: failed to create parent dir %s", + hardlink.getPath(), sourceFile.getPath(), hardlink.getParentFile().getPath())); + } + } + } + + final Path sourcePath = FileSystems.getDefault().getPath(sourceFile.getAbsolutePath()); + final Path linkPath = FileSystems.getDefault().getPath(hardlink.getAbsolutePath()); + Files.createLink(linkPath, sourcePath); + return hardlink; + } + + public static File copyFile(File sourceFile, File targetFile) throws IOException { + if (!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs()) { + synchronized (FileUtils.class) { + if (!targetFile.getParentFile().exists() && !targetFile.getParentFile().mkdirs()) { + throw new IOException( + String.format( + "failed to copy file %s to %s: failed to create parent dir %s", + sourceFile.getPath(), + targetFile.getPath(), + targetFile.getParentFile().getPath())); + } + } + } + + Files.copy(sourceFile.toPath(), targetFile.toPath()); + return targetFile; + } + + /** + * Transfer bytes to human-readable string. Copy from stackoverflow. + */ + public static String humanReadableByteCountSI(long bytes) { + if (-1000 < bytes && bytes < 1000) { + return bytes + " B"; + } + CharacterIterator ci = new StringCharacterIterator("KMGTPE"); + while (bytes <= -999_950 || bytes >= 999_950) { + bytes /= 1000; + ci.next(); + } + return String.format("%.2f %cB", bytes / 1000.0, ci.current()); + } + + public static void moveFileWithMD5Check(final File sourceFile, final File targetDir) + throws IOException { + final String sourceFileName = sourceFile.getName(); + final File targetFile = new File(targetDir, sourceFileName); + + if (targetFile.exists()) { + if (haveSameMD5(sourceFile, targetFile)) { + org.apache.commons.io.FileUtils.forceDelete(sourceFile); + LOGGER.info( + "Deleted the file {} because it already exists in the target directory: {}", + sourceFile.getName(), + targetDir.getAbsolutePath()); + } else { + renameWithMD5(sourceFile, targetDir); + LOGGER.info( + "Renamed file {} to {} because it already exists in the target directory: {}", + sourceFile.getName(), + targetFile.getName(), + targetDir.getAbsolutePath()); + } + } else { + org.apache.commons.io.FileUtils.moveFileToDirectory(sourceFile, targetDir, true); + } + } + + private static boolean haveSameMD5(final File file1, final File file2) { + try (final InputStream is1 = Files.newInputStream(file1.toPath()); + final InputStream is2 = Files.newInputStream(file2.toPath())) { + return DigestUtils.md5Hex(is1).equals(DigestUtils.md5Hex(is2)); + } catch (final Exception e) { + return false; + } + } + + private static void renameWithMD5(File sourceFile, File targetDir) throws IOException { + try (final InputStream is = Files.newInputStream(sourceFile.toPath())) { + final String sourceFileBaseName = FilenameUtils.getBaseName(sourceFile.getName()); + final String sourceFileExtension = FilenameUtils.getExtension(sourceFile.getName()); + final String sourceFileMD5 = DigestUtils.md5Hex(is); + + final String targetFileName = + sourceFileBaseName + "-" + sourceFileMD5.substring(0, 16) + "." + sourceFileExtension; + final File targetFile = new File(targetDir, targetFileName); + + org.apache.commons.io.FileUtils.moveFile( + sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING); + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/MemUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/MemUtils.java new file mode 100644 index 00000000..34af87cf --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/MemUtils.java @@ -0,0 +1,241 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils; + +import org.apache.iotdb.collector.plugin.builtin.sink.constant.IoTDBConstant; +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.rpc.TSStatusCode; +import org.apache.tsfile.common.conf.TSFileConfig; +import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.utils.Binary; +import org.apache.tsfile.utils.RamUsageEstimator; +import org.apache.tsfile.write.record.TSRecord; +import org.apache.tsfile.write.record.datapoint.BooleanDataPoint; +import org.apache.tsfile.write.record.datapoint.DataPoint; +import org.apache.tsfile.write.record.datapoint.DoubleDataPoint; +import org.apache.tsfile.write.record.datapoint.FloatDataPoint; +import org.apache.tsfile.write.record.datapoint.IntDataPoint; +import org.apache.tsfile.write.record.datapoint.LongDataPoint; +import org.apache.tsfile.write.record.datapoint.StringDataPoint; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +// Notice : methods in this class may not be accurate. +public class MemUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(MemUtils.class); + + private MemUtils() {} + + /** + * Function for obtaining the value size. For text values, there are two conditions: 1. During + * insertion, their size has already been added to memory. 2. During flushing, their size needs to + * be calculated. + */ + public static long getRecordSize(TSDataType dataType, Object value, boolean addingTextDataSize) { + if (dataType.isBinary()) { + return 8L + (addingTextDataSize ? getBinarySize((Binary) value) : 0); + } + return 8L + dataType.getDataTypeSize(); + } + + /** + * Function for obtaining the value size. For text values, their size has already been added to + * memory before insertion + */ + public static long getRowRecordSize(List dataTypes, Object[] value) { + int emptyRecordCount = 0; + long memSize = 0L; + for (int i = 0; i < value.length; i++) { + if (value[i] == null) { + emptyRecordCount++; + continue; + } + memSize += getRecordSize(dataTypes.get(i - emptyRecordCount), value[i], false); + } + return memSize; + } + + /** + * Function for obtaining the value size. For text values, their size has already been added to + * memory before insertion + */ + public static long getAlignedRowRecordSize( + List dataTypes, Object[] value, TsTableColumnCategory[] columnCategories) { + // time and index size + long memSize = 8L + 4L; + for (int i = 0; i < dataTypes.size(); i++) { + if (value[i] == null + || dataTypes.get(i).isBinary() + || columnCategories != null && columnCategories[i] != TsTableColumnCategory.FIELD) { + continue; + } + memSize += dataTypes.get(i).getDataTypeSize(); + } + return memSize; + } + + public static long getBinarySize(Binary value) { + return RamUsageEstimator.NUM_BYTES_OBJECT_HEADER + RamUsageEstimator.sizeOf(value.getValues()); + } + + public static long getBinaryColumnSize(Binary[] column, int start, int end, TSStatus[] results) { + long memSize = 0; + memSize += (long) (end - start) * RamUsageEstimator.NUM_BYTES_OBJECT_HEADER; + for (int i = start; i < end; i++) { + if (results == null + || results[i] == null + || results[i].code == TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + memSize += RamUsageEstimator.sizeOf(column[i].getValues()); + } + } + return memSize; + } + + // /** + // * Function for obtaining the value size. For text values, their size has already been added to + // * memory before insertion + // */ + // public static long getTabletSize(InsertTabletNode insertTabletNode, int start, int end) { + // if (start >= end) { + // return 0L; + // } + // long memSize = 0; + // for (int i = 0; i < insertTabletNode.getMeasurements().length; i++) { + // if (insertTabletNode.getMeasurements()[i] == null) { + // continue; + // } + // // Time column memSize + // memSize += (end - start) * 8L; + // memSize += (long) (end - start) * insertTabletNode.getDataTypes()[i].getDataTypeSize(); + // } + // return memSize; + // } + // + // public static long getAlignedTabletSize( + // InsertTabletNode insertTabletNode, int start, int end, TSStatus[] results) { + // if (start >= end) { + // return 0L; + // } + // long memSize = 0; + // for (int i = 0; i < insertTabletNode.getMeasurements().length; i++) { + // if (!insertTabletNode.isValidMeasurement(i)) { + // continue; + // } + // if (results == null) { + // memSize += (long) (end - start) * insertTabletNode.getDataTypes()[i].getDataTypeSize(); + // } else { + // for (int j = start; j < end; j++) { + // if (results[j] == null + // || results[j].code == TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + // memSize += insertTabletNode.getDataTypes()[i].getDataTypeSize(); + // } + // } + // } + // } + // // time and index column memSize for vector + // memSize += (end - start) * (8L + 4L); + // return memSize; + // } + + /** Calculate how much memory will be used if the given record is written to sequence file. */ + public static long getTsRecordMem(TSRecord tsRecord) { + long memUsed = 8; // time + memUsed += 8; // deviceId reference + memUsed += tsRecord.deviceId.ramBytesUsed(); + for (DataPoint dataPoint : tsRecord.dataPointList) { + memUsed += 8; // dataPoint reference + memUsed += getDataPointMem(dataPoint); + } + return memUsed; + } + + /** Function for getting the memory size of the given string. */ + public static long getStringMem(String str) { + // wide char (2 bytes each) and 64B String overhead + return str.length() * 2L + 64L; + } + + /** Function for getting the memory size of the given data point. */ + public static long getDataPointMem(DataPoint dataPoint) { + // type reference + long memUsed = 8; + // measurementId and its reference + memUsed += getStringMem(dataPoint.getMeasurementId()); + memUsed += 8; + + if (dataPoint instanceof FloatDataPoint) { + memUsed += 4; + } else if (dataPoint instanceof IntDataPoint) { + memUsed += 4; + } else if (dataPoint instanceof BooleanDataPoint) { + memUsed += 1; + } else if (dataPoint instanceof DoubleDataPoint) { + memUsed += 8; + } else if (dataPoint instanceof LongDataPoint) { + memUsed += 8; + } else if (dataPoint instanceof StringDataPoint) { + StringDataPoint stringDataPoint = (StringDataPoint) dataPoint; + memUsed += 8 + 20; // array reference and array overhead + memUsed += ((Binary) stringDataPoint.getValue()).getLength(); + // encoding string reference and its memory + memUsed += 8; + memUsed += getStringMem(TSFileConfig.STRING_ENCODING); + } else { + LOGGER.error("Unsupported data point type"); + } + + return memUsed; + } + + /** Function for converting the byte count result to readable string. */ + public static String bytesCntToStr(long inputCnt) { + long cnt = inputCnt; + long gbs = cnt / IoTDBConstant.GB; + cnt = cnt % IoTDBConstant.GB; + long mbs = cnt / IoTDBConstant.MB; + cnt = cnt % IoTDBConstant.MB; + long kbs = cnt / IoTDBConstant.KB; + cnt = cnt % IoTDBConstant.KB; + return gbs + " GB " + mbs + " MB " + kbs + " KB " + cnt + " B"; + } + + public static long strToBytesCnt(String str) { + if (str == null || str.isEmpty()) { + return 0; + } + str = str.toLowerCase(); + if (!str.endsWith("b")) { + str += "b"; + } + long unit = 1; + if (str.endsWith("kb")) { + unit = IoTDBConstant.KB; + } else if (str.endsWith("mb")) { + unit = IoTDBConstant.MB; + } else if (str.endsWith("gb")) { + unit = IoTDBConstant.GB; + } + str = str.replaceAll("\\D", ""); + return Long.parseLong(str) * unit; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/NodeUrlUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/NodeUrlUtils.java new file mode 100644 index 00000000..f286d4ad --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/NodeUrlUtils.java @@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +import org.apache.iotdb.common.rpc.thrift.TConfigNodeLocation; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; + +import org.apache.iotdb.rpc.UrlUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NodeUrlUtils { + + private static final Logger logger = LoggerFactory.getLogger(NodeUrlUtils.class); + + public static final String WILD_CARD_ADDRESS = "0.0.0.0"; + public static final String LOOPBACK_HOST_NAME = "localhost"; + + /** + * Convert TEndPoint to TEndPointUrl + * + * @param endPoint TEndPoint + * @return TEndPointUrl with format ip:port + */ + public static String convertTEndPointUrl(TEndPoint endPoint) { + StringJoiner url = new StringJoiner(":"); + url.add(endPoint.getIp()); + url.add(String.valueOf(endPoint.getPort())); + return url.toString(); + } + + /** + * Convert TEndPoints to TEndPointUrls + * + * @param endPoints List + * @return TEndPointUrls with format TEndPointUrl_0,TEndPointUrl_1,...,TEndPointUrl_n + */ + public static String convertTEndPointUrls(List endPoints) { + StringJoiner urls = new StringJoiner(","); + for (TEndPoint endPoint : endPoints) { + urls.add(convertTEndPointUrl(endPoint)); + } + return urls.toString(); + } + + /** + * Parse TEndPoint from a given TEndPointUrl + * + * @param endPointUrl ip:port + * @return TEndPoint + * @throws BadNodeUrlException Throw when unable to parse + */ + public static TEndPoint parseTEndPointUrl(String endPointUrl) throws BadNodeUrlException { + return UrlUtils.parseTEndPointIpv4AndIpv6Url(endPointUrl); + } + + /** + * Parse TEndPoints from given TEndPointUrls + * + * @param endPointUrls List + * @return List + */ + public static List parseTEndPointUrls(List endPointUrls) { + if (endPointUrls == null) { + throw new NumberFormatException("endPointUrls is null"); + } + List result = new ArrayList<>(); + for (String url : endPointUrls) { + result.add(UrlUtils.parseTEndPointIpv4AndIpv6Url(url)); + } + return result; + } + + /** + * Parse TEndPoints from given TEndPointUrls + * + * @param endPointUrls TEndPointUrls + * @return List + * @throws BadNodeUrlException Throw when unable to parse + */ + public static List parseTEndPointUrls(String endPointUrls) throws BadNodeUrlException { + return parseTEndPointUrls(Arrays.asList(endPointUrls.split(","))); + } + + /** + * Convert TConfigNodeLocation to TConfigNodeUrl + * + * @param configNodeLocation TConfigNodeLocation + * @return TConfigNodeUrl with format InternalEndPointUrl,ConsensusEndPointUrl + */ + public static String convertTConfigNodeUrl(TConfigNodeLocation configNodeLocation) { + StringJoiner url = new StringJoiner(","); + url.add(String.valueOf(configNodeLocation.getConfigNodeId())); + url.add(convertTEndPointUrl(configNodeLocation.getInternalEndPoint())); + url.add(convertTEndPointUrl(configNodeLocation.getConsensusEndPoint())); + return url.toString(); + } + + /** + * Convert TConfigNodeLocations to TConfigNodeUrls + * + * @param configNodeLocations List + * @return TConfigNodeUrls with format TConfigNodeUrl_0,TConfigNodeUrl_1,...,TConfigNodeUrl_n + */ + public static String convertTConfigNodeUrls(List configNodeLocations) { + StringJoiner urls = new StringJoiner(";"); + for (TConfigNodeLocation configNodeLocation : configNodeLocations) { + urls.add(convertTConfigNodeUrl(configNodeLocation)); + } + return urls.toString(); + } + + /** + * Parse TConfigNodeLocation from given TConfigNodeUrl + * + * @param configNodeUrl InternalEndPointUrl,ConsensusEndPointUrl + * @return TConfigNodeLocation + * @throws BadNodeUrlException Throw when unable to parse + */ + public static TConfigNodeLocation parseTConfigNodeUrl(String configNodeUrl) + throws BadNodeUrlException { + String[] split = configNodeUrl.split(","); + if (split.length != 3) { + logger.warn("Bad ConfigNode url: {}", configNodeUrl); + throw new BadNodeUrlException(String.format("Bad node url: %s", configNodeUrl)); + } + return new TConfigNodeLocation( + Integer.parseInt(split[0]), + UrlUtils.parseTEndPointIpv4AndIpv6Url(split[1]), + UrlUtils.parseTEndPointIpv4AndIpv6Url(split[2])); + } + + /** + * Parse TConfigNodeLocations from given TConfigNodeUrls + * + * @param configNodeUrls List + * @return List + * @throws BadNodeUrlException Throw when unable to parse + */ + public static List parseTConfigNodeUrls(List configNodeUrls) + throws BadNodeUrlException { + List result = new ArrayList<>(); + for (String url : configNodeUrls) { + result.add(parseTConfigNodeUrl(url)); + } + return result; + } + + /** + * Parse TConfigNodeLocations from given TConfigNodeUrls + * + * @param configNodeUrls TConfigNodeUrls + * @return List + * @throws BadNodeUrlException Throw when unable to parse + */ + public static List parseTConfigNodeUrls(String configNodeUrls) + throws BadNodeUrlException { + return parseTConfigNodeUrls(Arrays.asList(configNodeUrls.split(";"))); + } + + /** + * Detect whether the given addresses or host names(may contain both) point to the node itself. + * + * @param addressesOrHostNames List of the addresses or host name to check + * @return true if one of the given strings point to the node itself + * @throws UnknownHostException Throw when unable to parse the given addresses or host names + */ + public static boolean containsLocalAddress(List addressesOrHostNames) + throws UnknownHostException { + if (addressesOrHostNames == null) { + return false; + } + + Set selfAddresses = getAllLocalAddresses(); + + for (String addressOrHostName : addressesOrHostNames) { + if (addressOrHostName == null) { + continue; + } + // Unify address or hostName, converting them to addresses + Set translatedAddresses = + Arrays.stream(InetAddress.getAllByName(addressOrHostName)) + .map(InetAddress::getHostAddress) + .collect(Collectors.toCollection(HashSet::new)); + translatedAddresses.retainAll(selfAddresses); + + if (!translatedAddresses.isEmpty()) { + return true; + } + } + + return false; + } + + /** + * Return all the internal, external, IPv4, IPv6 and loopback addresses(without hostname) of the + * node + * + * @return the local addresses set + * @throws UnknownHostException Throw when unable to self addresses or host names (Normally not + * thrown) + */ + public static Set getAllLocalAddresses() throws UnknownHostException { + // Check internal and external, IPv4 and IPv6 network addresses + Set selfAddresses = + Arrays.stream(InetAddress.getAllByName(InetAddress.getLocalHost().getHostName())) + .map(InetAddress::getHostAddress) + .collect(Collectors.toCollection(HashSet::new)); + // Check IPv4 and IPv6 loopback addresses 127.0.0.1 and 0.0.0.0.0.0.0.1 + selfAddresses.addAll( + Arrays.stream(InetAddress.getAllByName(LOOPBACK_HOST_NAME)) + .map(InetAddress::getHostAddress) + .collect(Collectors.toCollection(HashSet::new))); + // Check general address 0.0.0.0 + selfAddresses.add(WILD_CARD_ADDRESS); + + return selfAddresses; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathDeserializeUtil.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathDeserializeUtil.java new file mode 100644 index 00000000..3de93e3e --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathDeserializeUtil.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils; + +import org.apache.iotdb.collector.plugin.builtin.sink.path.AlignedPath; +import org.apache.iotdb.collector.plugin.builtin.sink.path.MeasurementPath; +import org.apache.iotdb.collector.plugin.builtin.sink.path.PartialPath; +import org.apache.tsfile.read.common.Path; + +import java.nio.ByteBuffer; + +public class PathDeserializeUtil { + + public static Path deserialize(ByteBuffer buffer) { + byte pathType = buffer.get(); + switch (pathType) { + case 0: + return MeasurementPath.deserialize(buffer); + case 1: + return AlignedPath.deserialize(buffer); + case 2: + return PartialPath.deserialize(buffer); + case 3: + return Path.deserialize(buffer); + default: + throw new IllegalArgumentException("Invalid path type: " + pathType); + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathUtils.java new file mode 100644 index 00000000..dc676ec3 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathUtils.java @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.iotdb.collector.plugin.builtin.sink.utils; + +import org.apache.iotdb.collector.plugin.builtin.sink.constant.IoTDBConstant; +import org.apache.tsfile.common.constant.TsFileConstant; +import org.apache.tsfile.exception.PathParseException; +import org.apache.tsfile.file.metadata.IDeviceID; +import org.apache.tsfile.read.common.parser.PathNodesGenerator; +import org.apache.tsfile.read.common.parser.PathVisitor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PathUtils { + + /** + * @param path the path will split. ex, root.ln. + * @return string array. ex, [root, ln] + * @throws IllegalPathException if path isn't correct, the exception will throw + */ + public static String[] splitPathToDetachedNodes(String path) throws IllegalPathException { + if ("".equals(path)) { + return new String[] {}; + } + try { + return PathNodesGenerator.splitPathToNodes(path); + } catch (PathParseException e) { + throw new IllegalPathException(path); + } + } + + public static String[] isLegalPath(String path) throws IllegalPathException { + try { + return PathNodesGenerator.splitPathToNodes(path); + } catch (PathParseException e) { + throw new IllegalPathException(path); + } + } + + /** + * check whether measurement is legal according to syntax convention. Measurement can only be a + * single node name. The returned list is updated, could be different from the original list. + */ + public static List> checkIsLegalSingleMeasurementListsAndUpdate( + List> measurementLists) throws MetadataException { + if (measurementLists == null) { + return null; + } + // skip checking duplicated measurements + Map checkedMeasurements = new HashMap<>(); + List> res = new ArrayList<>(); + for (List measurements : measurementLists) { + res.add(checkLegalSingleMeasurementsAndSkipDuplicate(measurements, checkedMeasurements)); + } + return res; + } + + /** + * check whether measurement is legal according to syntax convention. Measurement can only be a + * single node name, use set to skip checking duplicated measurements + */ + public static List checkLegalSingleMeasurementsAndSkipDuplicate( + List measurements, Map checkedMeasurements) throws MetadataException { + if (measurements == null) { + return null; + } + List res = new ArrayList<>(); + for (String measurement : measurements) { + if (measurement == null) { + res.add(null); + continue; + } + String checked = checkedMeasurements.get(measurement); + if (checked == null) { + checked = checkAndReturnSingleMeasurement(measurement); + checkedMeasurements.put(measurement, checked); + } + res.add(checked); + } + return res; + } + + /** + * check whether measurement is legal according to syntax convention. Measurement can only be a + * single node name. + */ + public static List checkIsLegalSingleMeasurementsAndUpdate(List measurements) + throws MetadataException { + if (measurements == null) { + return null; + } + List res = new ArrayList<>(); + for (String measurement : measurements) { + if (measurement == null) { + continue; + } + res.add(checkAndReturnSingleMeasurement(measurement)); + } + return res; + } + + /** + * check whether measurement is legal according to syntax convention measurement could be like a.b + * (more than one node name), in template? + */ + public static List> checkIsLegalMeasurementListsAndUpdate( + List> measurementLists) throws IllegalPathException { + if (measurementLists == null) { + return null; + } + List> res = new ArrayList<>(); + for (List measurementList : measurementLists) { + res.add(checkIsLegalMeasurementsAndUpdate(measurementList)); + } + return res; + } + + /** + * check whether measurement is legal according to syntax convention measurement could be like a.b + * (more than one node name), in template? + */ + public static List checkIsLegalMeasurementsAndUpdate(List measurements) + throws IllegalPathException { + if (measurements == null) { + return null; + } + List res = new ArrayList<>(); + for (String measurement : measurements) { + if (measurement != null) { + res.add(PathUtils.isLegalPath(measurement)[0]); + } + } + return res; + } + + /** check a measurement and update it if needed to. for example: `sd` -> sd */ + public static String checkAndReturnSingleMeasurement(String measurement) + throws IllegalPathException { + if (measurement == null) { + return null; + } + if (measurement.startsWith(TsFileConstant.BACK_QUOTE_STRING) + && measurement.endsWith(TsFileConstant.BACK_QUOTE_STRING)) { + if (checkBackQuotes(measurement.substring(1, measurement.length() - 1))) { + return removeBackQuotesIfNecessary(measurement); + } else { + throw new IllegalPathException(measurement); + } + } + if (IoTDBConstant.reservedWords.contains(measurement.toUpperCase()) + || isRealNumber(measurement) + || !TsFileConstant.NODE_NAME_PATTERN.matcher(measurement).matches()) { + throw new IllegalPathException(measurement); + } + return measurement; + } + + /** Return true if the str is a real number. Examples: 1.0; +1.0; -1.0; 0011; 011e3; +23e-3 */ + public static boolean isRealNumber(final String str) { + return PathVisitor.isRealNumber(str); + } + + public static boolean isStartWith(final IDeviceID deviceID, final String storageGroup) { + return deviceID.segmentNum() > 0 && deviceID.matchDatabaseName(storageGroup); + } + + /** Remove the back quotes of a measurement if necessary */ + public static String removeBackQuotesIfNecessary(String measurement) { + String unWrapped = measurement.substring(1, measurement.length() - 1); + if (PathUtils.isRealNumber(unWrapped) + || !TsFileConstant.IDENTIFIER_PATTERN.matcher(unWrapped).matches()) { + return measurement; + } else { + return unWrapped; + } + } + + private static boolean checkBackQuotes(final String src) { + int num = src.length() - src.replace("`", "").length(); + if (num % 2 == 1) { + return false; + } + return src.length() == (src.replace("``", "").length() + num); + } + + // In tree model, the "root" shall not be a database name + // Thus we imply that "root" is a db name in table model + public static String qualifyDatabaseName(String databaseName) { + if (databaseName != null && !databaseName.startsWith("root.")) { + databaseName = "root." + databaseName; + } + return databaseName; + } + + public static String unQualifyDatabaseName(String databaseName) { + if (databaseName != null && databaseName.startsWith("root.")) { + databaseName = databaseName.substring(5).toLowerCase(); + } + return databaseName; + } + + public static boolean isTableModelDatabase(final String databaseName) { + return !databaseName.startsWith("root."); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PipeMemoryWeightUtil.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PipeMemoryWeightUtil.java new file mode 100644 index 00000000..9c42efe5 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PipeMemoryWeightUtil.java @@ -0,0 +1,358 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils; + +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.collector.plugin.builtin.sink.event.row.PipeRow; +import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.file.metadata.AbstractAlignedChunkMetadata; +import org.apache.tsfile.file.metadata.ChunkMetadata; +import org.apache.tsfile.file.metadata.IChunkMetadata; +import org.apache.tsfile.file.metadata.IDeviceID; +import org.apache.tsfile.read.common.BatchData; +import org.apache.tsfile.read.common.Chunk; +import org.apache.tsfile.read.common.Field; +import org.apache.tsfile.read.common.RowRecord; +import org.apache.tsfile.utils.Binary; +import org.apache.tsfile.utils.BitMap; +import org.apache.tsfile.utils.Pair; +import org.apache.tsfile.utils.TsPrimitiveType; +import org.apache.tsfile.write.record.Tablet; +import org.apache.tsfile.write.schema.IMeasurementSchema; + +import java.util.List; +import java.util.Map; + +public class PipeMemoryWeightUtil { + + /** Estimates memory usage of a {@link Map}<{@link IDeviceID}, {@link Boolean}>. */ + public static long memoryOfIDeviceId2Bool(Map map) { + long usageInBytes = 0L; + for (Map.Entry entry : map.entrySet()) { + usageInBytes = usageInBytes + entry.getKey().ramBytesUsed() + 1L; + } + return usageInBytes + 16L; // add the overhead of map + } + + /** Estimates memory usage of a {@link Map}<{@link String}, {@link TSDataType}>. */ + public static long memoryOfStr2TSDataType(Map map) { + long usageInBytes = 0L; + for (Map.Entry entry : map.entrySet()) { + usageInBytes = usageInBytes + MemUtils.getStringMem(entry.getKey()) + 4L; + } + return usageInBytes + 16L; // add the overhead of map + } + + /** Estimates memory usage of a {@link Map}<{@link IDeviceID}, {@link List}<{@link String}>>. */ + public static long memoryOfIDeviceID2StrList(Map> map) { + long usageInBytes = 0L; + for (Map.Entry> entry : map.entrySet()) { + usageInBytes += entry.getKey().ramBytesUsed(); + for (String str : entry.getValue()) { + usageInBytes += MemUtils.getStringMem(str); + } + } + return usageInBytes + 16L; // add the overhead of map + } + + /** + * Given a row of a tablet, calculate the row count and memory cost of the pipe tablet that will + * be constructed according to config. + * + * @return left is the row count of tablet, right is the memory cost of tablet in bytes + */ + public static Pair calculateTabletRowCountAndMemory(RowRecord row) { + int totalSizeInBytes = 0; + + // timestamp + totalSizeInBytes += 8L; + + // values + final List fields = row.getFields(); + int schemaCount = 0; + if (fields != null) { + schemaCount = fields.size(); + for (final Field field : fields) { + if (field == null) { + continue; + } + + final TSDataType tsDataType = field.getDataType(); + if (tsDataType == null) { + continue; + } + + if (tsDataType.isBinary()) { + final Binary binary = field.getBinaryV(); + totalSizeInBytes += binary == null ? 0 : binary.getLength(); + } else { + totalSizeInBytes += tsDataType.getDataTypeSize(); + } + } + } + + return calculateTabletRowCountAndMemoryBySize(totalSizeInBytes, schemaCount); + } + + /** + * Given a BatchData, calculate the row count and memory cost of the pipe tablet that will be + * constructed according to config. + * + * @return left is the row count of tablet, right is the memory cost of tablet in bytes + */ + public static Pair calculateTabletRowCountAndMemory(BatchData batchData) { + int totalSizeInBytes = 0; + int schemaCount = 0; + + // timestamp + totalSizeInBytes += 8L; + + // values + final TSDataType type = batchData.getDataType(); + if (type != null) { + if (type == TSDataType.VECTOR && batchData.getVector() != null) { + schemaCount = batchData.getVector().length; + for (int i = 0; i < schemaCount; ++i) { + final TsPrimitiveType primitiveType = batchData.getVector()[i]; + if (primitiveType == null || primitiveType.getDataType() == null) { + continue; + } + + if (primitiveType.getDataType().isBinary()) { + final Binary binary = primitiveType.getBinary(); + totalSizeInBytes += binary == null ? 0 : binary.getLength(); + } else { + totalSizeInBytes += primitiveType.getDataType().getDataTypeSize(); + } + } + } else { + schemaCount = 1; + if (type.isBinary()) { + final Binary binary = batchData.getBinary(); + totalSizeInBytes += binary == null ? 0 : binary.getLength(); + } else { + totalSizeInBytes += type.getDataTypeSize(); + } + } + } + + return calculateTabletRowCountAndMemoryBySize(totalSizeInBytes, schemaCount); + } + + /** + * Given a row of a tablet, calculate the row count and memory cost of the pipe tablet that will + * be constructed according to config. + * + * @return left is the row count of tablet, right is the memory cost of tablet in bytes + */ + public static Pair calculateTabletRowCountAndMemory(PipeRow row) { + return calculateTabletRowCountAndMemoryBySize(row.getCurrentRowSize(), row.size()); + } + + private static Pair calculateTabletRowCountAndMemoryBySize( + int rowSize, int schemaCount) { + if (rowSize <= 0) { + return new Pair<>(1, 0); + } + + // Calculate row number according to the max size of a pipe tablet. + // "-100" is the estimated size of other data structures in a pipe tablet. + // "*8" converts bytes to bits, because the bitmap size is 1 bit per schema. + int rowNumber = + 8 + * (PipeOptions.PIPE_DATA_STRUCTURE_TABLET_SIZE_IN_BYTES.value() - 100) + / (8 * rowSize + schemaCount); + rowNumber = Math.max(1, rowNumber); + + if ( // This means the row number is larger than the max row count of a pipe tablet + rowNumber > PipeOptions.PIPE_DATA_STRUCTURE_TABLET_ROW_SIZE.value()) { + // Bound the row number, the memory cost is rowSize * rowNumber + return new Pair<>( + PipeOptions.PIPE_DATA_STRUCTURE_TABLET_ROW_SIZE.value(), + rowSize * PipeOptions.PIPE_DATA_STRUCTURE_TABLET_ROW_SIZE.value()); + } else { + return new Pair<>(rowNumber, PipeOptions.PIPE_DATA_STRUCTURE_TABLET_SIZE_IN_BYTES.value()); + } + } + + public static long calculateTabletSizeInBytes(Tablet tablet) { + long totalSizeInBytes = 0; + + if (tablet == null) { + return totalSizeInBytes; + } + + long[] timestamps = tablet.getTimestamps(); + Object[] tabletValues = tablet.getValues(); + + // timestamps + if (timestamps != null) { + totalSizeInBytes += timestamps.length * 8L; + } + + // values + final List timeseries = tablet.getSchemas(); + if (timeseries != null) { + for (int column = 0; column < timeseries.size(); column++) { + final IMeasurementSchema measurementSchema = timeseries.get(column); + if (measurementSchema == null) { + continue; + } + + final TSDataType tsDataType = measurementSchema.getType(); + if (tsDataType == null) { + continue; + } + + if (tsDataType.isBinary()) { + if (tabletValues == null || tabletValues.length <= column) { + continue; + } + final Binary[] values = ((Binary[]) tabletValues[column]); + if (values == null) { + continue; + } + for (Binary value : values) { + totalSizeInBytes += + value == null ? 0 : (value.getLength() == -1 ? 0 : value.getLength()); + } + } else { + totalSizeInBytes += (long) timestamps.length * tsDataType.getDataTypeSize(); + } + } + } + + // bitMaps + BitMap[] bitMaps = tablet.getBitMaps(); + if (bitMaps != null) { + for (int i = 0; i < bitMaps.length; i++) { + totalSizeInBytes += bitMaps[i] == null ? 0 : bitMaps[i].getSize(); + } + } + + // estimate other dataStructures size + totalSizeInBytes += 100; + + return totalSizeInBytes; + } + + // public static long calculateTableSchemaBytesUsed(TableSchema tableSchema) { + // long totalSizeInBytes = 0; + // + // final String tableName = tableSchema.getTableName(); + // if (tableName != null) { + // totalSizeInBytes += tableName.length(); + // } + // + // final List measurementSchemas = tableSchema.getColumnSchemas(); + // if (measurementSchemas != null) { + // totalSizeInBytes += + // NUM_BYTES_ARRAY_HEADER + (long) NUM_BYTES_OBJECT_REF * measurementSchemas.size(); + // for (IMeasurementSchema measurementSchema : measurementSchemas) { + // InsertNodeMemoryEstimator.sizeOfMeasurementSchema((MeasurementSchema) measurementSchema); + // } + // } + // + // final List categories = tableSchema.getColumnTypes(); + // if (categories != null) { + // totalSizeInBytes += + // alignObjectSize( + // (long) NUM_BYTES_ARRAY_HEADER + // + (long) NUM_BYTES_OBJECT_REF * (long) categories.size()); + // } + // + // return totalSizeInBytes; + // } + + public static int calculateBatchDataRamBytesUsed(BatchData batchData) { + int totalSizeInBytes = 0; + + // timestamp + totalSizeInBytes += 8; + + // values + final TSDataType type = batchData.getDataType(); + if (type != null) { + if (type == TSDataType.VECTOR && batchData.getVector() != null) { + for (int i = 0; i < batchData.getVector().length; ++i) { + final TsPrimitiveType primitiveType = batchData.getVector()[i]; + if (primitiveType == null || primitiveType.getDataType() == null) { + continue; + } + // consider variable references (plus 8) and memory alignment (round up to 8) + totalSizeInBytes += roundUpToMultiple(primitiveType.getSize() + 8, 8); + } + } else { + if (type.isBinary()) { + final Binary binary = batchData.getBinary(); + // refer to org.apache.tsfile.utils.TsPrimitiveType.TsBinary.getSize + totalSizeInBytes += + roundUpToMultiple((binary == null ? 8 : binary.getLength() + 8) + 8, 8); + } else { + totalSizeInBytes += roundUpToMultiple(TsPrimitiveType.getByType(type).getSize() + 8, 8); + } + } + } + + return batchData.length() * totalSizeInBytes; + } + + public static long calculateChunkRamBytesUsed(Chunk chunk) { + return chunk != null ? chunk.getRetainedSizeInBytes() : 0L; + } + + public static long calculateAlignedChunkMetaBytesUsed( + AbstractAlignedChunkMetadata alignedChunkMetadata) { + if (alignedChunkMetadata == null) { + return 0L; + } + + final ChunkMetadata timeChunkMetadata = + (ChunkMetadata) alignedChunkMetadata.getTimeChunkMetadata(); + final List valueChunkMetadataList = + alignedChunkMetadata.getValueChunkMetadataList(); + + long size = timeChunkMetadata != null ? timeChunkMetadata.getRetainedSizeInBytes() : 0; + if (valueChunkMetadataList != null && !valueChunkMetadataList.isEmpty()) { + for (IChunkMetadata valueChunkMetadata : valueChunkMetadataList) { + if (valueChunkMetadata != null) { + size += ((ChunkMetadata) valueChunkMetadata).getRetainedSizeInBytes(); + } + } + } + + return size; + } + + /** + * Rounds up the given integer num to the nearest multiple of n. + * + * @param num The integer to be rounded up. + * @param n The specified multiple. + * @return The nearest multiple of n greater than or equal to num. + */ + private static int roundUpToMultiple(int num, int n) { + if (n == 0) { + throw new IllegalArgumentException("The multiple n must be greater than 0"); + } + // Calculate the rounded up value to the nearest multiple of n + return ((num + n - 1) / n) * n; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/RetryUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/RetryUtils.java new file mode 100644 index 00000000..e113a4c7 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/RetryUtils.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils; + +public class RetryUtils { + + public interface CallableWithException { + T call() throws E; + } + + public static final int MAX_RETRIES = 3; + + public static T retryOnException( + final CallableWithException callable) throws E { + int attempt = 0; + while (true) { + try { + return callable.call(); + } catch (Exception e) { + attempt++; + if (attempt >= MAX_RETRIES) { + throw e; + } + } + } + } + + private RetryUtils() { + // utility class + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TestOnly.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TestOnly.java new file mode 100644 index 00000000..17d55085 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TestOnly.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * TestOnly implies that the class or method should only be used in the tests, otherwise its + * functionality is not guaranteed and may interfere with the normal code. + */ +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE}) +@Retention(RetentionPolicy.CLASS) +public @interface TestOnly {} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TimestampPrecisionUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TimestampPrecisionUtils.java new file mode 100644 index 00000000..75a302a9 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TimestampPrecisionUtils.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.iotdb.collector.plugin.builtin.sink.utils; + +import java.util.concurrent.TimeUnit; + +import org.apache.iotdb.collector.config.PipeOptions; + +public class TimestampPrecisionUtils { + public static String TIMESTAMP_PRECISION = PipeOptions.TIMESTAMP_PRECISION.value(); + + @FunctionalInterface + private interface ConvertFunction { + R apply(T1 t1, T2 t2); + } + + private static final ConvertFunction convertToCurrPrecisionFunction; + public static final TimeUnit currPrecision; + + static { + switch (TIMESTAMP_PRECISION) { + case "ms": + convertToCurrPrecisionFunction = TimeUnit.MILLISECONDS::convert; + currPrecision = TimeUnit.MILLISECONDS; + break; + case "us": + convertToCurrPrecisionFunction = TimeUnit.MICROSECONDS::convert; + currPrecision = TimeUnit.MICROSECONDS; + break; + case "ns": + convertToCurrPrecisionFunction = TimeUnit.NANOSECONDS::convert; + currPrecision = TimeUnit.NANOSECONDS; + break; + // this case will never reach + default: + throw new UnsupportedOperationException( + "not supported time_precision: " + TIMESTAMP_PRECISION); + } + } + + /** convert specific precision timestamp to current precision timestamp */ + public static long convertToCurrPrecision(long sourceTime, TimeUnit sourceUnit) { + return convertToCurrPrecisionFunction.apply(sourceTime, sourceUnit); + } + + /** check whether the input timestamp match the current system timestamp precision. */ + public static void checkTimestampPrecision(long time) { + final boolean isTimestampPrecisionCheckEnabled = + PipeOptions.TIMESTAMP_PRECISION_CHECK_ENABLED.value(); + if (!isTimestampPrecisionCheckEnabled) { + return; + } + switch (TIMESTAMP_PRECISION) { + case "ms": + if (time > 10_000_000_000_000L || time < -10_000_000_000_000L) { + throw new SemanticException( + String.format( + "The timestamp is unexpectedly large, you may forget to set the timestamp precision." + + "Current system timestamp precision is %s, " + + "please check whether the timestamp %s is correct." + + "If you insist to insert this timestamp, please set timestamp_precision_check_enabled=false and restart the server.", + TIMESTAMP_PRECISION, time)); + } + break; + case "us": + if (time > 10_000_000_000_000_000L || time < -10_000_000_000_000L) { + throw new SemanticException( + String.format( + "The timestamp is unexpectedly large, you may forget to set the timestamp precision." + + "Current system timestamp precision is %s, " + + "please check whether the timestamp %s is correct." + + "If you insist to insert this timestamp, please set timestamp_precision_check_enabled=false and restart the server.", + TIMESTAMP_PRECISION, time)); + } + break; + // Long.MaxValue is 19 digits, therefore no problem when the precision is ns. + case "ns": + default: + break; + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TsTableColumnCategory.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TsTableColumnCategory.java new file mode 100644 index 00000000..512be69f --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TsTableColumnCategory.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils; + +import org.apache.tsfile.utils.ReadWriteIOUtils; +import org.apache.tsfile.write.record.Tablet.ColumnCategory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +public enum TsTableColumnCategory { + TAG((byte) 0), + ATTRIBUTE((byte) 1), + TIME((byte) 2), + FIELD((byte) 3); + + private final byte category; + + TsTableColumnCategory(byte category) { + this.category = category; + } + + byte getValue() { + return category; + } + + public void serialize(OutputStream stream) throws IOException { + ReadWriteIOUtils.write(category, stream); + } + + public void serialize(ByteBuffer byteBuffer) { + ReadWriteIOUtils.write(category, byteBuffer); + } + + public static TsTableColumnCategory deserialize(InputStream stream) throws IOException { + byte category = (byte) stream.read(); + return deserialize(category); + } + + public static TsTableColumnCategory deserialize(ByteBuffer stream) { + byte category = stream.get(); + return deserialize(category); + } + + public static TsTableColumnCategory deserialize(byte category) { + switch (category) { + case 0: + return TAG; + case 1: + return ATTRIBUTE; + case 2: + return TIME; + case 3: + return FIELD; + default: + throw new IllegalArgumentException(); + } + } + + public ColumnCategory toTsFileColumnType() { + switch (this) { + case TAG: + return ColumnCategory.TAG; + case ATTRIBUTE: + return ColumnCategory.ATTRIBUTE; + case FIELD: + return ColumnCategory.FIELD; + default: + throw new IllegalArgumentException("Unsupported column type in TsFile: " + this); + } + } + + public static TsTableColumnCategory fromTsFileColumnType(ColumnCategory columnType) { + switch (columnType) { + case FIELD: + return FIELD; + case TAG: + return TAG; + case ATTRIBUTE: + return ATTRIBUTE; + default: + throw new IllegalArgumentException("Unknown column type: " + columnType); + } + } + + public byte getCategory() { + return category; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/builder/PipeTableModeTsFileBuilder.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/builder/PipeTableModeTsFileBuilder.java new file mode 100644 index 00000000..4e2ba01e --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/builder/PipeTableModeTsFileBuilder.java @@ -0,0 +1,271 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils.builder; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; +import org.apache.commons.io.FileUtils; +import org.apache.iotdb.pipe.api.exception.PipeException; +import org.apache.tsfile.exception.write.WriteProcessException; +import org.apache.tsfile.file.metadata.IDeviceID; +import org.apache.tsfile.file.metadata.TableSchema; +import org.apache.tsfile.utils.Pair; +import org.apache.tsfile.utils.WriteUtils; +import org.apache.tsfile.write.record.Tablet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PipeTableModeTsFileBuilder extends PipeTsFileBuilder { + + private static final Logger LOGGER = LoggerFactory.getLogger(PipeTableModeTsFileBuilder.class); + + private final Map> dataBase2TabletList = new HashMap<>(); + + public PipeTableModeTsFileBuilder(AtomicLong currentBatchId, AtomicLong tsFileIdGenerator) { + super(currentBatchId, tsFileIdGenerator); + } + + @Override + public void bufferTableModelTablet(String dataBase, Tablet tablet) { + dataBase2TabletList.computeIfAbsent(dataBase, db -> new ArrayList<>()).add(tablet); + } + + @Override + public void bufferTreeModelTablet(Tablet tablet, Boolean isAligned) { + throw new UnsupportedOperationException( + "PipeTableModeTsFileBuilder does not support tree model tablet to build TSFile"); + } + + @Override + public List> convertTabletToTsFileWithDBInfo() throws IOException { + if (dataBase2TabletList.isEmpty()) { + return new ArrayList<>(0); + } + List> pairList = new ArrayList<>(); + for (Map.Entry> entry : dataBase2TabletList.entrySet()) { + final LinkedHashSet>>>> linkedHashSet = + new LinkedHashSet<>(); + pairList.addAll( + writeTableModelTabletsToTsFiles(entry.getValue(), entry.getKey(), linkedHashSet)); + } + return pairList; + } + + @Override + public boolean isEmpty() { + return dataBase2TabletList.isEmpty(); + } + + @Override + public synchronized void onSuccess() { + super.onSuccess(); + dataBase2TabletList.clear(); + } + + @Override + public synchronized void close() { + super.close(); + dataBase2TabletList.clear(); + } + + private >>> + List> writeTableModelTabletsToTsFiles( + final List tabletList, + final String dataBase, + LinkedHashSet> linkedHashSet) + throws IOException { + + final Map> tableName2Tablets = new HashMap<>(); + + // Sort the tablets by dataBaseName + for (final Tablet tablet : tabletList) { + tableName2Tablets + .computeIfAbsent(tablet.getTableName(), k -> new ArrayList<>()) + .add((T) new Pair<>(tablet, WriteUtils.splitTabletByDevice(tablet))); + } + + // Replace ArrayList with LinkedList to improve performance + final LinkedHashSet> table2Tablets = new LinkedHashSet<>(); + + // Sort the tablets by start time in first device + for (final List tablets : tableName2Tablets.values()) { + tablets.sort( + (o1, o2) -> { + final IDeviceID deviceID = o1.right.get(0).left; + final int result; + if ((result = deviceID.compareTo(o2.right.get(0).left)) == 0) { + return Long.compare(o1.left.getTimestamp(0), o2.left.getTimestamp(0)); + } + return result; + }); + } + + // Sort the tables by table name + tableName2Tablets.entrySet().stream() + .sorted(Map.Entry.comparingByKey(Comparator.naturalOrder())) + .forEach(entry -> linkedHashSet.add(new LinkedList<>(entry.getValue()))); + + // Help GC + tableName2Tablets.clear(); + + final List> sealedFiles = new ArrayList<>(); + + // Try making the tsfile size as large as possible + while (!linkedHashSet.isEmpty()) { + if (Objects.isNull(fileWriter)) { + createFileWriter(); + } + + try { + tryBestToWriteTabletsIntoOneFile(linkedHashSet); + } catch (final Exception e) { + LOGGER.warn( + "Batch id = {}: Failed to write tablets into tsfile, because {}", + currentBatchId.get(), + e.getMessage(), + e); + + try { + fileWriter.close(); + } catch (final Exception closeException) { + LOGGER.warn( + "Batch id = {}: Failed to close the tsfile {} after failed to write tablets into, because {}", + currentBatchId.get(), + fileWriter.getIOWriter().getFile().getPath(), + closeException.getMessage(), + closeException); + } finally { + // Add current writing file to the list and delete the file + sealedFiles.add(new Pair<>(dataBase, fileWriter.getIOWriter().getFile())); + } + + for (final Pair sealedFile : sealedFiles) { + final boolean deleteSuccess = FileUtils.deleteQuietly(sealedFile.right); + LOGGER.warn( + "Batch id = {}: {} delete the tsfile {} after failed to write tablets into {}. {}", + currentBatchId.get(), + deleteSuccess ? "Successfully" : "Failed to", + sealedFile.right.getPath(), + fileWriter.getIOWriter().getFile().getPath(), + deleteSuccess ? "" : "Maybe the tsfile needs to be deleted manually."); + } + sealedFiles.clear(); + + fileWriter = null; + + throw e; + } + + fileWriter.close(); + final File sealedFile = fileWriter.getIOWriter().getFile(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "Batch id = {}: Seal tsfile {} successfully.", + currentBatchId.get(), + sealedFile.getPath()); + } + sealedFiles.add(new Pair<>(dataBase, sealedFile)); + fileWriter = null; + } + + return sealedFiles; + } + + private >>> + void tryBestToWriteTabletsIntoOneFile( + final LinkedHashSet> device2TabletsLinkedList) throws IOException { + final Iterator> iterator = device2TabletsLinkedList.iterator(); + + while (iterator.hasNext()) { + final LinkedList tablets = iterator.next(); + + final List tabletsToWrite = new ArrayList<>(); + final Map deviceLastTimestampMap = new HashMap<>(); + while (!tablets.isEmpty()) { + final T pair = tablets.peekFirst(); + if (timestampsAreNonOverlapping( + (Pair>>) pair, deviceLastTimestampMap)) { + tabletsToWrite.add(pair); + tablets.pollFirst(); + continue; + } + break; + } + + if (tablets.isEmpty()) { + iterator.remove(); + } + boolean schemaNotRegistered = true; + for (final Pair>> pair : tabletsToWrite) { + final Tablet tablet = pair.left; + if (schemaNotRegistered) { + fileWriter.registerTableSchema( + new TableSchema(tablet.getTableName(), tablet.getSchemas(), tablet.getColumnTypes())); + schemaNotRegistered = false; + } + try { + fileWriter.writeTable(tablet, pair.right); + } catch (WriteProcessException e) { + LOGGER.warn( + "Batch id = {}: Failed to build the table model TSFile. Please check whether the written Tablet has time overlap and whether the Table Schema is correct.", + currentBatchId.get(), + e); + throw new PipeException( + "The written Tablet time may overlap or the Schema may be incorrect"); + } + } + } + } + + /** + * A Map is used to record the maximum time each {@link IDeviceID} is written. {@link Pair} + * records the Index+1 of the maximum timestamp of IDevice in each {@link Tablet}. + * + * @return If false, the tablet overlaps with the previous tablet; if true, there is no time + * overlap. + */ + private >>> + boolean timestampsAreNonOverlapping( + final T tabletPair, final Map deviceLastTimestampMap) { + int currentTimestampIndex = 0; + for (Pair deviceTimestampIndexPair : tabletPair.right) { + final Long lastDeviceTimestamp = deviceLastTimestampMap.get(deviceTimestampIndexPair.left); + if (lastDeviceTimestamp != null + && lastDeviceTimestamp >= tabletPair.left.getTimestamp(currentTimestampIndex)) { + return false; + } + currentTimestampIndex = deviceTimestampIndexPair.right; + deviceLastTimestampMap.put( + deviceTimestampIndexPair.left, tabletPair.left.getTimestamp(currentTimestampIndex - 1)); + } + + return true; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/builder/PipeTreeModelTsFileBuilder.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/builder/PipeTreeModelTsFileBuilder.java new file mode 100644 index 00000000..bd87d745 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/builder/PipeTreeModelTsFileBuilder.java @@ -0,0 +1,267 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils.builder; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; +import org.apache.commons.io.FileUtils; +import org.apache.tsfile.exception.write.WriteProcessException; +import org.apache.tsfile.file.metadata.IDeviceID; +import org.apache.tsfile.read.common.Path; +import org.apache.tsfile.utils.Pair; +import org.apache.tsfile.write.record.Tablet; +import org.apache.tsfile.write.schema.IMeasurementSchema; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PipeTreeModelTsFileBuilder extends PipeTsFileBuilder { + + private static final Logger LOGGER = LoggerFactory.getLogger(PipeTreeModelTsFileBuilder.class); + + private final List tabletList = new ArrayList<>(); + private final List isTabletAlignedList = new ArrayList<>(); + + public PipeTreeModelTsFileBuilder( + final AtomicLong currentBatchId, final AtomicLong tsFileIdGenerator) { + super(currentBatchId, tsFileIdGenerator); + } + + @Override + public void bufferTableModelTablet(final String dataBase, final Tablet tablet) { + throw new UnsupportedOperationException( + "PipeTreeModelTsFileBuilder does not support table model tablet to build TSFile"); + } + + @Override + public void bufferTreeModelTablet(final Tablet tablet, final Boolean isAligned) { + tabletList.add(tablet); + isTabletAlignedList.add(isAligned); + } + + @Override + public List> convertTabletToTsFileWithDBInfo() + throws IOException, WriteProcessException { + return writeTabletsToTsFiles(); + } + + @Override + public boolean isEmpty() { + return tabletList.isEmpty(); + } + + @Override + public void onSuccess() { + super.onSuccess(); + tabletList.clear(); + isTabletAlignedList.clear(); + } + + @Override + public synchronized void close() { + super.close(); + tabletList.clear(); + isTabletAlignedList.clear(); + } + + private List> writeTabletsToTsFiles() + throws IOException, WriteProcessException { + final Map> device2Tablets = new HashMap<>(); + final Map device2Aligned = new HashMap<>(); + + // Sort the tablets by device id + for (int i = 0, size = tabletList.size(); i < size; ++i) { + final Tablet tablet = tabletList.get(i); + final String deviceId = tablet.getDeviceId(); + device2Tablets.computeIfAbsent(deviceId, k -> new ArrayList<>()).add(tablet); + device2Aligned.put(deviceId, isTabletAlignedList.get(i)); + } + + // Sort the tablets by start time in each device + for (final List tablets : device2Tablets.values()) { + tablets.sort( + // Each tablet has at least one timestamp + Comparator.comparingLong(tablet -> tablet.getTimestamp(0))); + } + + // Sort the devices by device id + final List devices = new ArrayList<>(device2Tablets.keySet()); + devices.sort(Comparator.naturalOrder()); + + // Replace ArrayList with LinkedList to improve performance + final LinkedHashMap> device2TabletsLinkedList = + new LinkedHashMap<>(); + for (final String device : devices) { + device2TabletsLinkedList.put(device, new LinkedList<>(device2Tablets.get(device))); + } + + // Help GC + devices.clear(); + device2Tablets.clear(); + + // Write the tablets to the tsfile device by device, and the tablets + // in the same device are written in order of start time. Tablets in + // the same device should not be written if their time ranges overlap. + // If overlapped, we try to write the tablets whose device id is not + // the same as the previous one. For the tablets not written in the + // previous round, we write them in a new tsfile. + final List> sealedFiles = new ArrayList<>(); + + // Try making the tsfile size as large as possible + while (!device2TabletsLinkedList.isEmpty()) { + if (Objects.isNull(fileWriter)) { + createFileWriter(); + } + try { + tryBestToWriteTabletsIntoOneFile(device2TabletsLinkedList, device2Aligned); + } catch (final Exception e) { + LOGGER.warn( + "Batch id = {}: Failed to write tablets into tsfile, because {}", + currentBatchId.get(), + e.getMessage(), + e); + + try { + fileWriter.close(); + } catch (final Exception closeException) { + LOGGER.warn( + "Batch id = {}: Failed to close the tsfile {} after failed to write tablets into, because {}", + currentBatchId.get(), + fileWriter.getIOWriter().getFile().getPath(), + closeException.getMessage(), + closeException); + } finally { + // Add current writing file to the list and delete the file + sealedFiles.add(new Pair<>(null, fileWriter.getIOWriter().getFile())); + } + + for (final Pair sealedFile : sealedFiles) { + final boolean deleteSuccess = FileUtils.deleteQuietly(sealedFile.right); + LOGGER.warn( + "Batch id = {}: {} delete the tsfile {} after failed to write tablets into {}. {}", + currentBatchId.get(), + deleteSuccess ? "Successfully" : "Failed to", + sealedFile.right.getPath(), + fileWriter.getIOWriter().getFile().getPath(), + deleteSuccess ? "" : "Maybe the tsfile needs to be deleted manually."); + } + sealedFiles.clear(); + + fileWriter = null; + + throw e; + } + + fileWriter.close(); + final File sealedFile = fileWriter.getIOWriter().getFile(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "Batch id = {}: Seal tsfile {} successfully.", + currentBatchId.get(), + sealedFile.getPath()); + } + sealedFiles.add(new Pair<>(null, sealedFile)); + fileWriter = null; + } + + return sealedFiles; + } + + private void tryBestToWriteTabletsIntoOneFile( + final LinkedHashMap> device2TabletsLinkedList, + final Map device2Aligned) + throws IOException, WriteProcessException { + final Iterator>> iterator = + device2TabletsLinkedList.entrySet().iterator(); + + while (iterator.hasNext()) { + final Map.Entry> entry = iterator.next(); + final String deviceId = entry.getKey(); + final LinkedList tablets = entry.getValue(); + + final List tabletsToWrite = new ArrayList<>(); + + Tablet lastTablet = null; + while (!tablets.isEmpty()) { + final Tablet tablet = tablets.peekFirst(); + if (Objects.isNull(lastTablet) + // lastTablet.rowSize is not 0 + || lastTablet.getTimestamp(lastTablet.getRowSize() - 1) < tablet.getTimestamp(0)) { + tabletsToWrite.add(tablet); + lastTablet = tablet; + tablets.pollFirst(); + } else { + break; + } + } + + if (tablets.isEmpty()) { + iterator.remove(); + } + + final boolean isAligned = device2Aligned.get(deviceId); + if (isAligned) { + final Map> deviceId2MeasurementSchemas = new HashMap<>(); + tabletsToWrite.forEach( + tablet -> + deviceId2MeasurementSchemas.compute( + tablet.getDeviceId(), + (k, v) -> { + if (Objects.isNull(v)) { + return new ArrayList<>(tablet.getSchemas()); + } + v.addAll(tablet.getSchemas()); + return v; + })); + for (final Map.Entry> deviceIdWithMeasurementSchemas : + deviceId2MeasurementSchemas.entrySet()) { + fileWriter.registerAlignedTimeseries( + new Path(deviceIdWithMeasurementSchemas.getKey()), + deviceIdWithMeasurementSchemas.getValue()); + } + for (final Tablet tablet : tabletsToWrite) { + fileWriter.writeAligned(tablet); + } + } else { + for (final Tablet tablet : tabletsToWrite) { + for (final IMeasurementSchema schema : tablet.getSchemas()) { + try { + fileWriter.registerTimeseries( + IDeviceID.Factory.DEFAULT_FACTORY.create(tablet.getDeviceId()), schema); + } catch (final WriteProcessException ignore) { + // Do nothing if the timeSeries has been registered + } + } + + fileWriter.writeTree(tablet); + } + } + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/builder/PipeTsFileBuilder.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/builder/PipeTsFileBuilder.java new file mode 100644 index 00000000..ced0a2b7 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/builder/PipeTsFileBuilder.java @@ -0,0 +1,155 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils.builder; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.commons.io.FileUtils; +import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.pipe.api.exception.PipeException; +import org.apache.tsfile.common.constant.TsFileConstant; +import org.apache.tsfile.exception.write.WriteProcessException; +import org.apache.tsfile.utils.Pair; +import org.apache.tsfile.write.TsFileWriter; +import org.apache.tsfile.write.record.Tablet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class PipeTsFileBuilder { + + private static final Logger LOGGER = LoggerFactory.getLogger(PipeTsFileBuilder.class); + + // private static final AtomicReference FOLDER_MANAGER = new AtomicReference<>(); + protected final AtomicLong currentBatchId; + // private final File batchFileBaseDir; + + private static final String TS_FILE_PREFIX = "tb"; // tb means tablet batch + private final AtomicLong tsFileIdGenerator; + + @SuppressWarnings("java:S3077") + protected volatile TsFileWriter fileWriter; + + public PipeTsFileBuilder(final AtomicLong currentBatchId, final AtomicLong tsFileIdGenerator) { + this.currentBatchId = currentBatchId; + this.tsFileIdGenerator = tsFileIdGenerator; + try { + // this.batchFileBaseDir = getNextBaseDir(); + } catch (final Exception e) { + throw new PipeException( + String.format("Failed to create file dir for batch: %s", e.getMessage())); + } + } + + // private File getNextBaseDir() throws DiskSpaceInsufficientException { + // if (FOLDER_MANAGER.get() == null) { + // synchronized (FOLDER_MANAGER) { + // if (FOLDER_MANAGER.get() == null) { + // FOLDER_MANAGER.set( + // new FolderManager( + // Arrays.stream(PipeOptions.PIPE_RECEIVER_FILE_DIRS.value()) + // .map(fileDir -> fileDir + File.separator + ".batch") + // .collect(Collectors.toList()), + // DirectoryStrategyType.SEQUENCE_STRATEGY)); + // } + // } + // } + // + // final File baseDir = + // new File(FOLDER_MANAGER.get().getNextFolder(), Long.toString(currentBatchId.get())); + // if (baseDir.exists()) { + // FileUtils.deleteQuietly(baseDir); + // } + // if (!baseDir.exists() && !baseDir.mkdirs()) { + // LOGGER.warn( + // "Batch id = {}: Failed to create batch file dir {}.", + // currentBatchId.get(), + // baseDir.getPath()); + // throw new PipeException( + // String.format( + // "Failed to create batch file dir %s. (Batch id = %s)", + // baseDir.getPath(), currentBatchId.get())); + // } + // LOGGER.info( + // "Batch id = {}: Create batch dir successfully, batch file dir = {}.", + // currentBatchId.get(), + // baseDir.getPath()); + // return baseDir; + // } + + public abstract void bufferTableModelTablet(String dataBase, Tablet tablet); + + public abstract void bufferTreeModelTablet(Tablet tablet, Boolean isAligned); + + public abstract List> convertTabletToTsFileWithDBInfo() + throws IOException, WriteProcessException; + + public abstract boolean isEmpty(); + + public synchronized void onSuccess() { + fileWriter = null; + } + + public synchronized void close() { + if (Objects.nonNull(fileWriter)) { + try { + fileWriter.close(); + } catch (final Exception e) { + LOGGER.info( + "Batch id = {}: Failed to close the tsfile {} when trying to close batch, because {}", + currentBatchId.get(), + fileWriter.getIOWriter().getFile().getPath(), + e.getMessage(), + e); + } + + try { + FileUtils.delete(fileWriter.getIOWriter().getFile()); + } catch (final Exception e) { + LOGGER.info( + "Batch id = {}: Failed to delete the tsfile {} when trying to close batch, because {}", + currentBatchId.get(), + fileWriter.getIOWriter().getFile().getPath(), + e.getMessage(), + e); + } + + fileWriter = null; + } + } + + protected void createFileWriter() throws IOException { + fileWriter = + new TsFileWriter( + new File( + // batchFileBaseDir, + TS_FILE_PREFIX + + "_" + + PipeOptions.DATA_NODE_ID.value() + + "_" + + currentBatchId.get() + + "_" + + tsFileIdGenerator.getAndIncrement() + + TsFileConstant.TSFILE_SUFFIX)); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/cacher/LeaderCacheUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/cacher/LeaderCacheUtils.java new file mode 100644 index 00000000..a3072933 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/cacher/LeaderCacheUtils.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils.cacher; + +import java.util.ArrayList; +import java.util.List; +import org.apache.iotdb.common.rpc.thrift.TEndPoint; +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.rpc.TSStatusCode; +import org.apache.tsfile.utils.Pair; + +public class LeaderCacheUtils { + + private LeaderCacheUtils() { + // Do nothing + } + + /** + * Get all redirection recommends after transferring a batch event to update leader cache. + * + * @param status is the returned status after transferring a batch event. + * @return a list of pairs, each pair contains a device path and its redirect endpoint. + */ + public static List> parseRecommendedRedirections(TSStatus status) { + // If there is no exception, there should be 2 sub-statuses, one for InsertRowsStatement and one + // for InsertMultiTabletsStatement (see IoTDBDataNodeReceiver#handleTransferTabletBatch). + final List> redirectList = new ArrayList<>(); + + if (status.getSubStatusSize() != 2) { + return redirectList; + } + + for (final TSStatus subStatus : status.getSubStatus()) { + if (subStatus.getCode() != TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode()) { + continue; + } + + for (final TSStatus innerSubStatus : subStatus.getSubStatus()) { + if (innerSubStatus.isSetRedirectNode()) { + // We assume that innerSubStatus.getMessage() is a device path. + // The message field should be a device path. + redirectList.add( + new Pair<>(innerSubStatus.getMessage(), innerSubStatus.getRedirectNode())); + } + } + } + + return redirectList; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/sorter/PipeTableModelTabletEventSorter.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/sorter/PipeTableModelTabletEventSorter.java new file mode 100644 index 00000000..7052af16 --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/sorter/PipeTableModelTabletEventSorter.java @@ -0,0 +1,272 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils.sorter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.file.metadata.IDeviceID; +import org.apache.tsfile.utils.Pair; +import org.apache.tsfile.write.record.Tablet; +import org.apache.tsfile.write.schema.IMeasurementSchema; + +public class PipeTableModelTabletEventSorter { + + private final Tablet tablet; + + private Integer[] index; + private boolean isUnSorted = false; + private boolean hasDuplicates = false; + private int deduplicatedSize; + private int initIndexSize; + + public PipeTableModelTabletEventSorter(final Tablet tablet) { + this.tablet = tablet; + deduplicatedSize = tablet == null ? 0 : tablet.getRowSize(); + } + + /** + * For the sorting and deduplication needs of the table model tablet, it is done according to the + * {@link IDeviceID}. For sorting, it is necessary to sort the {@link IDeviceID} first, and then + * sort by time. Deduplication is to remove the same timestamp in the same {@link IDeviceID}, and + * the same timestamp in different {@link IDeviceID} will not be processed. + */ + public void sortAndDeduplicateByDevIdTimestamp() { + if (tablet == null || tablet.getRowSize() < 1) { + return; + } + + HashMap>> deviceIDToIndexMap = new HashMap<>(); + final long[] timestamps = tablet.getTimestamps(); + + IDeviceID lastDevice = tablet.getDeviceID(0); + long previousTimestamp = tablet.getTimestamp(0); + int lasIndex = 0; + for (int i = 1, size = tablet.getRowSize(); i < size; ++i) { + final IDeviceID deviceID = tablet.getDeviceID(i); + final long currentTimestamp = timestamps[i]; + final int deviceComparison = deviceID.compareTo(lastDevice); + if (deviceComparison == 0) { + if (previousTimestamp == currentTimestamp) { + hasDuplicates = true; + continue; + } + if (previousTimestamp > currentTimestamp) { + isUnSorted = true; + } + previousTimestamp = currentTimestamp; + continue; + } + if (deviceComparison < 0) { + isUnSorted = true; + } + + List> list = + deviceIDToIndexMap.computeIfAbsent(lastDevice, k -> new ArrayList<>()); + + if (!list.isEmpty()) { + isUnSorted = true; + } + list.add(new Pair<>(lasIndex, i)); + lastDevice = deviceID; + lasIndex = i; + previousTimestamp = currentTimestamp; + } + + List> list = + deviceIDToIndexMap.computeIfAbsent(lastDevice, k -> new ArrayList<>()); + if (!list.isEmpty()) { + isUnSorted = true; + } + list.add(new Pair<>(lasIndex, tablet.getRowSize())); + + if (!isUnSorted && !hasDuplicates) { + return; + } + + initIndexSize = 0; + deduplicatedSize = 0; + index = new Integer[tablet.getRowSize()]; + deviceIDToIndexMap.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .forEach( + entry -> { + final int start = initIndexSize; + int i = initIndexSize; + for (Pair pair : entry.getValue()) { + for (int j = pair.left; j < pair.right; j++) { + index[i++] = j; + } + } + if (isUnSorted) { + sortTimestamps(start, i); + deduplicateTimestamps(start, i); + initIndexSize = i; + return; + } + + if (hasDuplicates) { + deduplicateTimestamps(start, i); + } + initIndexSize = i; + }); + + sortAndDeduplicateValuesAndBitMaps(); + } + + private void sortAndDeduplicateValuesAndBitMaps() { + int columnIndex = 0; + tablet.setTimestamps( + (long[]) + PipeTabletEventSorter.reorderValueList( + deduplicatedSize, tablet.getTimestamps(), TSDataType.TIMESTAMP, index)); + for (int i = 0, size = tablet.getSchemas().size(); i < size; i++) { + final IMeasurementSchema schema = tablet.getSchemas().get(i); + if (schema != null) { + tablet.getValues()[columnIndex] = + PipeTabletEventSorter.reorderValueList( + deduplicatedSize, tablet.getValues()[columnIndex], schema.getType(), index); + if (tablet.getBitMaps() != null && tablet.getBitMaps()[columnIndex] != null) { + tablet.getBitMaps()[columnIndex] = + PipeTabletEventSorter.reorderBitMap( + deduplicatedSize, tablet.getBitMaps()[columnIndex], index); + } + columnIndex++; + } + } + + tablet.setRowSize(deduplicatedSize); + } + + private void sortTimestamps(final int startIndex, final int endIndex) { + Arrays.sort(this.index, startIndex, endIndex, Comparator.comparingLong(tablet::getTimestamp)); + } + + private void deduplicateTimestamps(final int startIndex, final int endIndex) { + long[] timestamps = tablet.getTimestamps(); + long lastTime = timestamps[index[startIndex]]; + index[deduplicatedSize++] = index[startIndex]; + for (int i = startIndex + 1; i < endIndex; i++) { + if (lastTime != (lastTime = timestamps[index[i]])) { + index[deduplicatedSize++] = index[i]; + } + } + } + + /** Sort by time only, and remove only rows with the same DeviceID and time. */ + public void sortAndDeduplicateByTimestampIfNecessary() { + if (tablet == null || tablet.getRowSize() == 0) { + return; + } + + long[] timestamps = tablet.getTimestamps(); + for (int i = 1, size = tablet.getRowSize(); i < size; ++i) { + final long currentTimestamp = timestamps[i]; + final long previousTimestamp = timestamps[i - 1]; + + if (currentTimestamp < previousTimestamp) { + isUnSorted = true; + break; + } + if (currentTimestamp == previousTimestamp) { + hasDuplicates = true; + } + } + + if (!isUnSorted && !hasDuplicates) { + return; + } + + index = new Integer[tablet.getRowSize()]; + for (int i = 0, size = tablet.getRowSize(); i < size; i++) { + index[i] = i; + } + + if (isUnSorted) { + sortTimestamps(); + + // Do deduplicate anyway. + // isDeduplicated may be false positive when isUnSorted is true. + deduplicateTimestamps(); + hasDuplicates = false; + } + + if (hasDuplicates) { + deduplicateTimestamps(); + } + + sortAndDeduplicateValuesAndBitMapsIgnoreTimestamp(); + } + + private void sortTimestamps() { + Arrays.sort(this.index, Comparator.comparingLong(tablet::getTimestamp)); + Arrays.sort(tablet.getTimestamps(), 0, tablet.getRowSize()); + } + + private void deduplicateTimestamps() { + deduplicatedSize = 1; + long[] timestamps = tablet.getTimestamps(); + long lastTime = timestamps[0]; + IDeviceID deviceID = tablet.getDeviceID(index[0]); + final Set deviceIDSet = new HashSet<>(); + deviceIDSet.add(deviceID); + for (int i = 1, size = tablet.getRowSize(); i < size; i++) { + deviceID = tablet.getDeviceID(index[i]); + if ((lastTime == (lastTime = timestamps[i]))) { + if (!deviceIDSet.contains(deviceID)) { + timestamps[deduplicatedSize] = lastTime; + index[deduplicatedSize++] = index[i]; + deviceIDSet.add(deviceID); + } + } else { + timestamps[deduplicatedSize] = lastTime; + index[deduplicatedSize++] = index[i]; + deviceIDSet.clear(); + deviceIDSet.add(deviceID); + } + } + } + + private void sortAndDeduplicateValuesAndBitMapsIgnoreTimestamp() { + int columnIndex = 0; + for (int i = 0, size = tablet.getSchemas().size(); i < size; i++) { + final IMeasurementSchema schema = tablet.getSchemas().get(i); + if (schema != null) { + tablet.getValues()[columnIndex] = + PipeTabletEventSorter.reorderValueList( + deduplicatedSize, tablet.getValues()[columnIndex], schema.getType(), index); + if (tablet.getBitMaps() != null && tablet.getBitMaps()[columnIndex] != null) { + tablet.getBitMaps()[columnIndex] = + PipeTabletEventSorter.reorderBitMap( + deduplicatedSize, tablet.getBitMaps()[columnIndex], index); + } + columnIndex++; + } + } + + tablet.setRowSize(deduplicatedSize); + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/sorter/PipeTabletEventSorter.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/sorter/PipeTabletEventSorter.java new file mode 100644 index 00000000..39a2effa --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/sorter/PipeTabletEventSorter.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils.sorter; + +import java.time.LocalDate; +import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.utils.Binary; +import org.apache.tsfile.utils.BitMap; +import org.apache.tsfile.write.UnSupportedDataTypeException; + +public class PipeTabletEventSorter { + + public static Object reorderValueList( + final int deduplicatedSize, + final Object valueList, + final TSDataType dataType, + final Integer[] index) { + switch (dataType) { + case BOOLEAN: + final boolean[] boolValues = (boolean[]) valueList; + final boolean[] deduplicatedBoolValues = new boolean[boolValues.length]; + for (int i = 0; i < deduplicatedSize; i++) { + deduplicatedBoolValues[i] = boolValues[index[i]]; + } + return deduplicatedBoolValues; + case INT32: + final int[] intValues = (int[]) valueList; + final int[] deduplicatedIntValues = new int[intValues.length]; + for (int i = 0; i < deduplicatedSize; i++) { + deduplicatedIntValues[i] = intValues[index[i]]; + } + return deduplicatedIntValues; + case DATE: + final LocalDate[] dateValues = (LocalDate[]) valueList; + final LocalDate[] deduplicatedDateValues = new LocalDate[dateValues.length]; + for (int i = 0; i < deduplicatedSize; i++) { + deduplicatedDateValues[i] = dateValues[index[i]]; + } + return deduplicatedDateValues; + case INT64: + case TIMESTAMP: + final long[] longValues = (long[]) valueList; + final long[] deduplicatedLongValues = new long[longValues.length]; + for (int i = 0; i < deduplicatedSize; i++) { + deduplicatedLongValues[i] = longValues[index[i]]; + } + return deduplicatedLongValues; + case FLOAT: + final float[] floatValues = (float[]) valueList; + final float[] deduplicatedFloatValues = new float[floatValues.length]; + for (int i = 0; i < deduplicatedSize; i++) { + deduplicatedFloatValues[i] = floatValues[index[i]]; + } + return deduplicatedFloatValues; + case DOUBLE: + final double[] doubleValues = (double[]) valueList; + final double[] deduplicatedDoubleValues = new double[doubleValues.length]; + for (int i = 0; i < deduplicatedSize; i++) { + deduplicatedDoubleValues[i] = doubleValues[index[i]]; + } + return deduplicatedDoubleValues; + case TEXT: + case BLOB: + case STRING: + final Binary[] binaryValues = (Binary[]) valueList; + final Binary[] deduplicatedBinaryValues = new Binary[binaryValues.length]; + for (int i = 0; i < deduplicatedSize; i++) { + deduplicatedBinaryValues[i] = binaryValues[index[i]]; + } + return deduplicatedBinaryValues; + default: + throw new UnSupportedDataTypeException( + String.format("Data type %s is not supported.", dataType)); + } + } + + public static BitMap reorderBitMap( + final int deduplicatedSize, final BitMap bitMap, final Integer[] index) { + final BitMap deduplicatedBitMap = new BitMap(bitMap.getSize()); + for (int i = 0; i < deduplicatedSize; i++) { + if (bitMap.isMarked(index[i])) { + deduplicatedBitMap.mark(i); + } + } + return deduplicatedBitMap; + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/sorter/PipeTreeModelTabletEventSorter.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/sorter/PipeTreeModelTabletEventSorter.java new file mode 100644 index 00000000..86dbff6f --- /dev/null +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/sorter/PipeTreeModelTabletEventSorter.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.iotdb.collector.plugin.builtin.sink.utils.sorter; + +import java.util.Arrays; +import java.util.Comparator; +import org.apache.tsfile.write.record.Tablet; +import org.apache.tsfile.write.schema.IMeasurementSchema; + +public class PipeTreeModelTabletEventSorter { + + private final Tablet tablet; + + private boolean isSorted = true; + private boolean isDeduplicated = true; + + private Integer[] index; + private int deduplicatedSize; + + public PipeTreeModelTabletEventSorter(final Tablet tablet) { + this.tablet = tablet; + deduplicatedSize = tablet == null ? 0 : tablet.getRowSize(); + } + + public void deduplicateAndSortTimestampsIfNecessary() { + if (tablet == null || tablet.getRowSize() == 0) { + return; + } + + long[] timestamps = tablet.getTimestamps(); + for (int i = 1, size = tablet.getRowSize(); i < size; ++i) { + final long currentTimestamp = timestamps[i]; + final long previousTimestamp = timestamps[i - 1]; + + if (currentTimestamp < previousTimestamp) { + isSorted = false; + break; + } + if (currentTimestamp == previousTimestamp) { + isDeduplicated = false; + } + } + + if (isSorted && isDeduplicated) { + return; + } + + index = new Integer[tablet.getRowSize()]; + for (int i = 0, size = tablet.getRowSize(); i < size; i++) { + index[i] = i; + } + + if (!isSorted) { + sortTimestamps(); + + // Do deduplicate anyway. + // isDeduplicated may be false positive when isSorted is false. + deduplicateTimestamps(); + isDeduplicated = true; + } + + if (!isDeduplicated) { + deduplicateTimestamps(); + } + + sortAndDeduplicateValuesAndBitMaps(); + } + + private void sortTimestamps() { + Arrays.sort(index, Comparator.comparingLong(tablet::getTimestamp)); + Arrays.sort(tablet.getTimestamps(), 0, tablet.getRowSize()); + } + + private void deduplicateTimestamps() { + deduplicatedSize = 1; + long[] timestamps = tablet.getTimestamps(); + for (int i = 1, size = tablet.getRowSize(); i < size; i++) { + if (timestamps[i] != timestamps[i - 1]) { + index[deduplicatedSize] = index[i]; + timestamps[deduplicatedSize] = timestamps[i]; + + ++deduplicatedSize; + } + } + tablet.setRowSize(deduplicatedSize); + } + + private void sortAndDeduplicateValuesAndBitMaps() { + int columnIndex = 0; + for (int i = 0, size = tablet.getSchemas().size(); i < size; i++) { + final IMeasurementSchema schema = tablet.getSchemas().get(i); + if (schema != null) { + tablet.getValues()[columnIndex] = + PipeTabletEventSorter.reorderValueList( + deduplicatedSize, tablet.getValues()[columnIndex], schema.getType(), index); + if (tablet.getBitMaps() != null && tablet.getBitMaps()[columnIndex] != null) { + tablet.getBitMaps()[columnIndex] = + PipeTabletEventSorter.reorderBitMap( + deduplicatedSize, tablet.getBitMaps()[columnIndex], index); + } + columnIndex++; + } + } + } +} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/task/processor/ProcessorTask.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/task/processor/ProcessorTask.java index e671ea00..dad94046 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/task/processor/ProcessorTask.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/task/processor/ProcessorTask.java @@ -25,8 +25,6 @@ import org.apache.iotdb.collector.runtime.task.event.EventCollector; import org.apache.iotdb.collector.runtime.task.event.EventContainer; import org.apache.iotdb.collector.service.RuntimeService; -import org.apache.iotdb.commons.concurrent.IoTThreadFactory; -import org.apache.iotdb.commons.concurrent.threadpool.WrappedThreadPoolExecutor; import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator; import com.lmax.disruptor.BlockingWaitStrategy; @@ -39,6 +37,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import static org.apache.iotdb.collector.config.TaskRuntimeOptions.TASK_PROCESSOR_RING_BUFFER_SIZE; @@ -67,14 +66,12 @@ public ProcessorTask( REGISTERED_EXECUTOR_SERVICES.putIfAbsent( taskId, - new WrappedThreadPoolExecutor( + new ThreadPoolExecutor( parallelism, parallelism, 0L, TimeUnit.SECONDS, - new LinkedBlockingQueue<>(parallelism), - new IoTThreadFactory(taskId), // TODO: thread name - taskId)); + new LinkedBlockingQueue<>(parallelism))); disruptor = new Disruptor<>( diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/task/sink/SinkTask.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/task/sink/SinkTask.java index e3e66db9..7e66d9e0 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/task/sink/SinkTask.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/task/sink/SinkTask.java @@ -25,8 +25,6 @@ import org.apache.iotdb.collector.runtime.task.event.EventCollector; import org.apache.iotdb.collector.runtime.task.event.EventContainer; import org.apache.iotdb.collector.service.RuntimeService; -import org.apache.iotdb.commons.concurrent.IoTThreadFactory; -import org.apache.iotdb.commons.concurrent.threadpool.WrappedThreadPoolExecutor; import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator; import com.lmax.disruptor.BlockingWaitStrategy; @@ -39,6 +37,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import static org.apache.iotdb.collector.config.TaskRuntimeOptions.TASK_SINK_PARALLELISM_NUM; @@ -59,14 +58,12 @@ public SinkTask(final String taskId, final Map attributes) { REGISTERED_EXECUTOR_SERVICES.putIfAbsent( taskId, - new WrappedThreadPoolExecutor( + new ThreadPoolExecutor( parallelism, parallelism, 0L, TimeUnit.SECONDS, - new LinkedBlockingQueue<>(parallelism), - new IoTThreadFactory(taskId), // TODO: thread name - taskId)); + new LinkedBlockingQueue<>(parallelism))); disruptor = new Disruptor<>( diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/task/source/pull/PullSourceTask.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/task/source/pull/PullSourceTask.java index b47054a9..cbcdb143 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/task/source/pull/PullSourceTask.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/task/source/pull/PullSourceTask.java @@ -25,8 +25,6 @@ import org.apache.iotdb.collector.runtime.task.event.EventCollector; import org.apache.iotdb.collector.runtime.task.source.SourceTask; import org.apache.iotdb.collector.service.RuntimeService; -import org.apache.iotdb.commons.concurrent.IoTThreadFactory; -import org.apache.iotdb.commons.concurrent.threadpool.WrappedThreadPoolExecutor; import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator; import org.slf4j.Logger; @@ -36,6 +34,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class PullSourceTask extends SourceTask { @@ -64,14 +63,9 @@ public void createInternal() throws Exception { REGISTERED_EXECUTOR_SERVICES.putIfAbsent( taskId, - new WrappedThreadPoolExecutor( - parallelism, - parallelism, - 0L, - TimeUnit.SECONDS, - new LinkedBlockingQueue<>(parallelism), - new IoTThreadFactory(taskId), // TODO: thread name - taskId)); + new ThreadPoolExecutor( + parallelism,parallelism,0L,TimeUnit.SECONDS,new LinkedBlockingQueue<>(parallelism) + )); final long creationTime = System.currentTimeMillis(); consumers = new PullSourceConsumer[parallelism]; diff --git a/pom.xml b/pom.xml index fca72e4f..7868c387 100644 --- a/pom.xml +++ b/pom.xml @@ -182,6 +182,7 @@ 0.14.1 9.0.86 + 2.1.0-250219-SNAPSHOT 1.9 0.11.1 1.5.5-5 @@ -713,6 +714,33 @@ + + org.eclipse.milo + stack-core + ${milo.version} + + + com.sun.activation + jakarta.activation + + + + + org.eclipse.milo + sdk-core + ${milo.version} + + + com.sun.activation + jakarta.activation + + + + + org.eclipse.milo + stack-server + ${milo.version} + org.eclipse.milo sdk-server @@ -749,6 +777,11 @@ logback-classic ${logback.version} + + ch.qos.logback + logback-core + ${logback.version} + com.rabbitmq amqp-client @@ -850,6 +883,56 @@ disruptor ${disruptor.version} + + org.apache.commons + commons-collections4 + ${commons.collections4.version} + + + org.apache.commons + commons-pool2 + ${commons-pool2.version} + + + org.apache.tsfile + tsfile + ${tsfile.version} + + + org.apache.tsfile + common + ${tsfile.version} + + + net.java.dev.jna + jna + ${jna.version} + + + net.java.dev.jna + jna-platform + ${jna.version} + + + commons-io + commons-io + ${commons-io.version} + + + com.github.luben + zstd-jni + ${zstd-jni.version} + + + com.github.ben-manes.caffeine + caffeine + ${caffeine.version} + + + io.moquette + moquette-broker + ${moquette.version} + tsFile flushed --> (isEmpty() <--> tsFile is empty) - return !resource.isEmpty(); - } - - public File getTsFile() { - return tsFile; - } - - public File getModFile() { - return modFile; - } - - public File getSharedModFile() { - return sharedModFile; - } - - public boolean isWithMod() { - return isWithMod; - } - - // If the previous "isWithMod" is false, the modFile has been set to "null", then the isWithMod - // can't be set to true - public void disableMod4NonTransferPipes(final boolean isWithMod) { - this.isWithMod = isWithMod && this.isWithMod; - } - - public boolean isLoaded() { - return isLoaded; - } - - public long getFileStartTime() { - return resource.getFileStartTime(); - } - - /** - * Only used for metrics on PipeConsensus' receiver side. If the event is recovered after data - * node's restart, the flushPointCount can be not set. It's totally fine for the PipeConsensus' - * receiver side. The receiver side will count the actual point count from the TsFile. - * - *

If you want to get the actual point count with no risk, you can call {@link - * #count(boolean)}. - */ - // public long getFlushPointCount() { - // return flushPointCount; - // } - - public long getTimePartitionId() { - return resource.getTimePartition(); - } - - /////////////////////////// PipeRawTabletInsertionEvent /////////////////////////// - - @Override - public boolean internallyIncreaseResourceReferenceCount(final String holderMessage) { - try { - // tsFile = PipeDataNodeResourceManager.tsfile().increaseFileReference(tsFile, true, - // resource); - // if (isWithMod) { - // modFile = PipeDataNodeResourceManager.tsfile().increaseFileReference(modFile, false, - // null); - // } - return true; - } catch (final Exception e) { - LOGGER.warn( - String.format( - "Increase reference count for TsFile %s or modFile %s error. Holder Message: %s", - tsFile, modFile, holderMessage), - e); - return false; - } - } - - @Override - public boolean internallyDecreaseResourceReferenceCount(final String holderMessage) { - try { - // PipeDataNodeResourceManager.tsfile().decreaseFileReference(tsFile); - // if (isWithMod) { - // PipeDataNodeResourceManager.tsfile().decreaseFileReference(modFile); - // } - close(); - return true; - } catch (final Exception e) { - LOGGER.warn( - String.format( - "Decrease reference count for TsFile %s error. Holder Message: %s", - tsFile.getPath(), holderMessage), - e); - return false; - } - } - - @Override - public PipeTsFileInsertionEvent shallowCopySelfAndBindPipeTaskMetaForProgressReport( - final String pipeName, - final long creationTime, - final TreePattern treePattern, - final TablePattern tablePattern, - final long startTime, - final long endTime) { - return new PipeTsFileInsertionEvent( - getRawIsTableModelEvent(), - getSourceDatabaseNameFromDataRegion(), - resource, - isWithMod, - isLoaded, - isGeneratedByPipe, - isGeneratedByHistoricalExtractor, - pipeName, - creationTime, - treePattern, - tablePattern, - startTime, - endTime); - } - - @Override - public boolean isGeneratedByPipe() { - return isGeneratedByPipe; - } - - @Override - public boolean mayEventTimeOverlappedWithTimeRange() { - // If the tsFile is not closed the resource.getFileEndTime() will be Long.MIN_VALUE - // In that case we only judge the resource.getFileStartTime() to avoid losing data - return isClosed.get() - ? startTime <= resource.getFileEndTime() && resource.getFileStartTime() <= endTime - : resource.getFileStartTime() <= endTime; - } - - @Override - public boolean mayEventPathsOverlappedWithPattern() { - if (!resource.isClosed()) { - return true; - } - - try { - final Map deviceIsAlignedMap = null; - // PipeDataNodeResourceManager.tsfile() - // .getDeviceIsAlignedMapFromCache( - // PipeTsFileResourceManager.getHardlinkOrCopiedFileInPipeDir(resource.getTsFile()), - // false); - final Set deviceSet = - Objects.nonNull(deviceIsAlignedMap) ? deviceIsAlignedMap.keySet() : resource.getDevices(); - return deviceSet.stream() - .anyMatch( - deviceID -> { - // Tree model - if (Boolean.FALSE.equals(getRawIsTableModelEvent()) - || deviceID instanceof PlainDeviceID - || deviceID.getTableName().startsWith(TREE_MODEL_EVENT_TABLE_NAME_PREFIX) - || deviceID.getTableName().equals(PATH_ROOT)) { - markAsTreeModelEvent(); - return treePattern.mayOverlapWithDevice(deviceID); - } - - // Table model - markAsTableModelEvent(); - return true; - }); - } catch (final Exception e) { - LOGGER.warn( - "Pipe {}: failed to get devices from TsFile {}, extract it anyway", - pipeName, - resource.getTsFilePath(), - e); - return true; - } - } - - /////////////////////////// PipeInsertionEvent /////////////////////////// - - @Override - public boolean isTableModelEvent() { - if (getRawIsTableModelEvent() == null) { - if (getSourceDatabaseNameFromDataRegion() != null) { - return super.isTableModelEvent(); - } - - try { - final Map deviceIsAlignedMap = null; - // PipeDataNodeResourceManager.tsfile() - // .getDeviceIsAlignedMapFromCache( - // PipeTsFileResourceManager.getHardlinkOrCopiedFileInPipeDir( - // resource.getTsFile()), - // false); - final Set deviceSet = - Objects.nonNull(deviceIsAlignedMap) - ? deviceIsAlignedMap.keySet() - : resource.getDevices(); - for (final IDeviceID deviceID : deviceSet) { - if (deviceID instanceof PlainDeviceID - || deviceID.getTableName().startsWith(TREE_MODEL_EVENT_TABLE_NAME_PREFIX) - || deviceID.getTableName().equals(PATH_ROOT)) { - markAsTreeModelEvent(); - } else { - markAsTableModelEvent(); - } - break; - } - } catch (final Exception e) { - throw new PipeException( - String.format( - "Pipe %s: failed to judge whether TsFile %s is table model or tree model", - pipeName, resource.getTsFilePath()), - e); - } - } - - return getRawIsTableModelEvent(); - } - - /////////////////////////// TsFileInsertionEvent /////////////////////////// - - @Override - public Iterable toTabletInsertionEvents() throws PipeException { - return toTabletInsertionEvents(Long.MAX_VALUE); - } - - public Iterable toTabletInsertionEvents(final long timeoutMs) - throws PipeException { - try { - if (!waitForTsFileClose()) { - LOGGER.warn( - "Pipe skipping temporary TsFile's parsing which shouldn't be transferred: {}", tsFile); - return Collections.emptyList(); - } - waitForResourceEnough4Parsing(timeoutMs); - return initEventParser().toTabletInsertionEvents(); - } catch (final Exception e) { - close(); - - // close() should be called before re-interrupting the thread - if (e instanceof InterruptedException) { - Thread.currentThread().interrupt(); - } - - final String errorMsg = - e instanceof InterruptedException - ? String.format( - "Interrupted when waiting for closing TsFile %s.", resource.getTsFilePath()) - : String.format( - "Parse TsFile %s error. Because: %s", resource.getTsFilePath(), e.getMessage()); - LOGGER.warn(errorMsg, e); - throw new PipeException(errorMsg); - } - } - - private void waitForResourceEnough4Parsing(final long timeoutMs) throws InterruptedException { - // final PipeMemoryManager memoryManager = PipeDataNodeResourceManager.memory(); - // if (memoryManager.isEnough4TabletParsing()) { - // return; - // } - - final long startTime = System.currentTimeMillis(); - // long lastRecordTime = startTime; - // - // final long memoryCheckIntervalMs = PipeOptions.PIPE_CHECK_MEMORY_ENOUGH_INTERVAL_MS.value(); - // while (!memoryManager.isEnough4TabletParsing()) { - // Thread.sleep(memoryCheckIntervalMs); - // - // final long currentTime = System.currentTimeMillis(); - // final double elapsedRecordTimeSeconds = (currentTime - lastRecordTime) / 1000.0; - // final double waitTimeSeconds = (currentTime - startTime) / 1000.0; - // if (elapsedRecordTimeSeconds > 10.0) { - // LOGGER.info( - // "Wait for resource enough for parsing {} for {} seconds.", - // resource != null ? resource.getTsFilePath() : "tsfile", - // waitTimeSeconds); - // lastRecordTime = currentTime; - // } else if (LOGGER.isDebugEnabled()) { - // LOGGER.debug( - // "Wait for resource enough for parsing {} for {} seconds.", - // resource != null ? resource.getTsFilePath() : "tsfile", - // waitTimeSeconds); - // } - // - // if (waitTimeSeconds * 1000 > timeoutMs) { - // // should contain 'TimeoutException' in exception message - // throw new PipeException( - // String.format("TimeoutException: Waited %s seconds", waitTimeSeconds)); - // } - // } - - final long currentTime = System.currentTimeMillis(); - final double waitTimeSeconds = (currentTime - startTime) / 1000.0; - LOGGER.info( - "Wait for resource enough for parsing {} for {} seconds.", - resource != null ? resource.getTsFilePath() : "tsfile", - waitTimeSeconds); - } - - /** The method is used to prevent circular replication in PipeConsensus */ - public boolean isGeneratedByPipeConsensus() { - return isGeneratedByPipeConsensus; - } - - public boolean isGeneratedByHistoricalExtractor() { - return isGeneratedByHistoricalExtractor; - } - - private TsFileInsertionEventParser initEventParser() { - try { - eventParser.compareAndSet( - null, - new TsFileInsertionEventParserProvider( - tsFile, treePattern, tablePattern, startTime, endTime, this) - .provide()); - return eventParser.get(); - } catch (final IOException e) { - close(); - - final String errorMsg = String.format("Read TsFile %s error.", resource.getTsFilePath()); - LOGGER.warn(errorMsg, e); - throw new PipeException(errorMsg); - } - } - - public long count(final boolean skipReportOnCommit) throws IOException { - long count = 0; - - if (shouldParseTime()) { - try { - for (final TabletInsertionEvent event : toTabletInsertionEvents()) { - final PipeRawTabletInsertionEvent rawEvent = ((PipeRawTabletInsertionEvent) event); - count += rawEvent.count(); - // if (skipReportOnCommit) { - // rawEvent.skipReportOnCommit(); - // } - } - return count; - } finally { - close(); - } - } - - try (final TsFileInsertionPointCounter counter = - new TsFileInsertionPointCounter(tsFile, treePattern)) { - return counter.count(); - } - } - - /** Release the resource of {@link TsFileInsertionEventParser}. */ - @Override - public void close() { - eventParser.getAndUpdate( - parser -> { - if (Objects.nonNull(parser)) { - parser.close(); - } - return null; - }); - } - - /////////////////////////// Object /////////////////////////// - - @Override - public String toString() { - return String.format( - "PipeTsFileInsertionEvent{resource=%s, tsFile=%s, isLoaded=%s, isGeneratedByPipe=%s, isClosed=%s, eventParser=%s}", - resource, tsFile, isLoaded, isGeneratedByPipe, isClosed.get(), eventParser) - + " - " - + super.toString(); - } - - @Override - public String coreReportMessage() { - return String.format( - "PipeTsFileInsertionEvent{resource=%s, tsFile=%s, isLoaded=%s, isGeneratedByPipe=%s, isClosed=%s}", - resource, tsFile, isLoaded, isGeneratedByPipe, isClosed.get()) - + " - " - + super.coreReportMessage(); - } - - /////////////////////////// ReferenceTrackableEvent /////////////////////////// - - @Override - public void trackResource() { - // PipeDataNodeResourceManager.ref().trackPipeEventResource(this, eventResourceBuilder()); - } - - @Override - public PipeEventResource eventResourceBuilder() { - return new PipeTsFileInsertionEventResource( - this.isReleased, - this.referenceCount, - this.tsFile, - this.isWithMod, - this.modFile, - this.sharedModFile, - this.eventParser); - } - - private static class PipeTsFileInsertionEventResource extends PipeEventResource { - - private final File tsFile; - private final boolean isWithMod; - private final File modFile; - private final File sharedModFile; // unused now - private final AtomicReference eventParser; - - private PipeTsFileInsertionEventResource( - final AtomicBoolean isReleased, - final AtomicInteger referenceCount, - final File tsFile, - final boolean isWithMod, - final File modFile, - final File sharedModFile, - final AtomicReference eventParser) { - super(isReleased, referenceCount); - this.tsFile = tsFile; - this.isWithMod = isWithMod; - this.modFile = modFile; - this.sharedModFile = sharedModFile; - this.eventParser = eventParser; - } - - @Override - protected void finalizeResource() { - try { - // decrease reference count - // PipeDataNodeResourceManager.tsfile().decreaseFileReference(tsFile); - // if (isWithMod) { - // PipeDataNodeResourceManager.tsfile().decreaseFileReference(modFile); - // } - - // close event parser - eventParser.getAndUpdate( - parser -> { - if (Objects.nonNull(parser)) { - parser.close(); - } - return null; - }); - } catch (final Exception e) { - LOGGER.warn("Decrease reference count for TsFile {} error.", tsFile.getPath(), e); - } - } - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeConnectorCriticalException.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeConnectorCriticalException.java deleted file mode 100644 index f84a263e..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeConnectorCriticalException.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.exception.pipe; - -import org.apache.tsfile.utils.ReadWriteIOUtils; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Objects; - -public class PipeRuntimeConnectorCriticalException extends PipeRuntimeCriticalException { - - public PipeRuntimeConnectorCriticalException(final String message) { - super(message); - } - - public PipeRuntimeConnectorCriticalException(final String message, final long timeStamp) { - super(message, timeStamp); - } - - @Override - public boolean equals(final Object obj) { - return obj instanceof PipeRuntimeConnectorCriticalException - && Objects.equals(getMessage(), ((PipeRuntimeConnectorCriticalException) obj).getMessage()) - && Objects.equals(getTimeStamp(), ((PipeRuntimeException) obj).getTimeStamp()); - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public void serialize(final ByteBuffer byteBuffer) { - PipeRuntimeExceptionType.CONNECTOR_CRITICAL_EXCEPTION.serialize(byteBuffer); - ReadWriteIOUtils.write(getMessage(), byteBuffer); - ReadWriteIOUtils.write(getTimeStamp(), byteBuffer); - } - - @Override - public void serialize(final OutputStream stream) throws IOException { - PipeRuntimeExceptionType.CONNECTOR_CRITICAL_EXCEPTION.serialize(stream); - ReadWriteIOUtils.write(getMessage(), stream); - ReadWriteIOUtils.write(getTimeStamp(), stream); - } - - @Override - public String toString() { - return "PipeRuntimeConnectorCriticalException{" - + "message='" - + getMessage() - + "', timeStamp=" - + getTimeStamp() - + "}"; - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeCriticalException.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeCriticalException.java deleted file mode 100644 index 4fe301aa..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeCriticalException.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.exception.pipe; - -import org.apache.tsfile.utils.ReadWriteIOUtils; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Objects; - -public class PipeRuntimeCriticalException extends PipeRuntimeException { - - public PipeRuntimeCriticalException(final String message) { - super(message); - } - - public PipeRuntimeCriticalException(final String message, final long timeStamp) { - super(message, timeStamp); - } - - @Override - public boolean equals(final Object obj) { - return obj instanceof PipeRuntimeCriticalException - && Objects.equals(getMessage(), ((PipeRuntimeCriticalException) obj).getMessage()) - && Objects.equals(getTimeStamp(), ((PipeRuntimeException) obj).getTimeStamp()); - } - - @Override - public void serialize(final ByteBuffer byteBuffer) { - PipeRuntimeExceptionType.CRITICAL_EXCEPTION.serialize(byteBuffer); - ReadWriteIOUtils.write(getMessage(), byteBuffer); - ReadWriteIOUtils.write(getTimeStamp(), byteBuffer); - } - - @Override - public void serialize(final OutputStream stream) throws IOException { - PipeRuntimeExceptionType.CRITICAL_EXCEPTION.serialize(stream); - ReadWriteIOUtils.write(getMessage(), stream); - ReadWriteIOUtils.write(getTimeStamp(), stream); - } - - @Override - public String toString() { - return "PipeRuntimeCriticalException{" - + "message='" - + getMessage() - + "', timeStamp=" - + getTimeStamp() - + "}"; - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeException.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeException.java deleted file mode 100644 index 8e85ce86..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeException.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.exception.pipe; - -import org.apache.iotdb.pipe.api.exception.PipeException; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Objects; - -public abstract class PipeRuntimeException extends PipeException { - - protected PipeRuntimeException(final String message) { - super(message); - } - - protected PipeRuntimeException(final String message, final long timeStamp) { - super(message, timeStamp); - } - - @Override - public boolean equals(final Object obj) { - return obj instanceof PipeRuntimeException - && Objects.equals(getMessage(), ((PipeRuntimeException) obj).getMessage()) - && Objects.equals(getTimeStamp(), ((PipeRuntimeException) obj).getTimeStamp()); - } - - @Override - public int hashCode() { - return Objects.hash(getMessage(), getTimeStamp()); - } - - public abstract void serialize(final ByteBuffer byteBuffer); - - public abstract void serialize(final OutputStream stream) throws IOException; -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeExceptionType.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeExceptionType.java deleted file mode 100644 index fdc38c8e..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeExceptionType.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.exception.pipe; - -import org.apache.tsfile.utils.ReadWriteIOUtils; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -public enum PipeRuntimeExceptionType { - NON_CRITICAL_EXCEPTION((short) 1), - CRITICAL_EXCEPTION((short) 2), - CONNECTOR_CRITICAL_EXCEPTION((short) 3), - OUT_OF_MEMORY_CRITICAL_EXCEPTION((short) 4), - ; - - private final short type; - - PipeRuntimeExceptionType(short type) { - this.type = type; - } - - public short getType() { - return type; - } - - public void serialize(ByteBuffer byteBuffer) { - ReadWriteIOUtils.write(type, byteBuffer); - } - - public void serialize(OutputStream stream) throws IOException { - ReadWriteIOUtils.write(type, stream); - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeNonCriticalException.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeNonCriticalException.java deleted file mode 100644 index 92ebff1f..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeNonCriticalException.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.exception.pipe; - -import org.apache.tsfile.utils.ReadWriteIOUtils; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Objects; - -public class PipeRuntimeNonCriticalException extends PipeRuntimeException { - - public PipeRuntimeNonCriticalException(String message) { - super(message); - } - - public PipeRuntimeNonCriticalException(String message, long timeStamp) { - super(message, timeStamp); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof PipeRuntimeNonCriticalException - && Objects.equals(getMessage(), ((PipeRuntimeNonCriticalException) obj).getMessage()) - && Objects.equals(getTimeStamp(), ((PipeRuntimeException) obj).getTimeStamp()); - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public void serialize(ByteBuffer byteBuffer) { - PipeRuntimeExceptionType.NON_CRITICAL_EXCEPTION.serialize(byteBuffer); - ReadWriteIOUtils.write(getMessage(), byteBuffer); - ReadWriteIOUtils.write(getTimeStamp(), byteBuffer); - } - - @Override - public void serialize(OutputStream stream) throws IOException { - PipeRuntimeExceptionType.NON_CRITICAL_EXCEPTION.serialize(stream); - ReadWriteIOUtils.write(getMessage(), stream); - ReadWriteIOUtils.write(getTimeStamp(), stream); - } - - @Override - public String toString() { - return "PipeRuntimeNonCriticalException{" - + "message='" - + getMessage() - + "', timeStamp=" - + getTimeStamp() - + "}"; - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeOutOfMemoryCriticalException.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeOutOfMemoryCriticalException.java deleted file mode 100644 index bca4676c..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/exception/pipe/PipeRuntimeOutOfMemoryCriticalException.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.exception.pipe; - -import org.apache.tsfile.utils.ReadWriteIOUtils; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Objects; - -public class PipeRuntimeOutOfMemoryCriticalException extends PipeRuntimeCriticalException { - - public PipeRuntimeOutOfMemoryCriticalException(String message) { - super(message); - } - - public PipeRuntimeOutOfMemoryCriticalException(String message, long timeStamp) { - super(message, timeStamp); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof PipeRuntimeOutOfMemoryCriticalException - && Objects.equals( - getMessage(), ((PipeRuntimeOutOfMemoryCriticalException) obj).getMessage()) - && Objects.equals(getTimeStamp(), ((PipeRuntimeException) obj).getTimeStamp()); - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public void serialize(ByteBuffer byteBuffer) { - PipeRuntimeExceptionType.OUT_OF_MEMORY_CRITICAL_EXCEPTION.serialize(byteBuffer); - ReadWriteIOUtils.write(getMessage(), byteBuffer); - ReadWriteIOUtils.write(getTimeStamp(), byteBuffer); - } - - @Override - public void serialize(OutputStream stream) throws IOException { - PipeRuntimeExceptionType.OUT_OF_MEMORY_CRITICAL_EXCEPTION.serialize(stream); - ReadWriteIOUtils.write(getMessage(), stream); - ReadWriteIOUtils.write(getTimeStamp(), stream); - } - - @Override - public String toString() { - return "PipeRuntimeOutOfMemoryException{" - + "message='" - + getMessage() - + "', timeStamp=" - + getTimeStamp() - + "}"; - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventBatch.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventBatch.java index 3d8d477b..859969e2 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventBatch.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventBatch.java @@ -19,7 +19,7 @@ package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch; -import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.PipeRawTabletInsertionEvent; import org.apache.iotdb.pipe.api.event.Event; import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent; diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventPlainBatch.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventPlainBatch.java index ad9a1ad5..8525f571 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventPlainBatch.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventPlainBatch.java @@ -19,7 +19,7 @@ package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch; -import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.PipeRawTabletInsertionEvent; import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTabletBatchReqV2; import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent; diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventTsFileBatch.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventTsFileBatch.java index fcb577da..903008b3 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventTsFileBatch.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTabletEventTsFileBatch.java @@ -19,8 +19,7 @@ package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch; -import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; -import org.apache.iotdb.collector.plugin.builtin.sink.utils.PipeMemoryWeightUtil; +import org.apache.iotdb.collector.plugin.builtin.sink.event.PipeRawTabletInsertionEvent; import org.apache.iotdb.collector.plugin.builtin.sink.utils.builder.PipeTableModeTsFileBuilder; import org.apache.iotdb.collector.plugin.builtin.sink.utils.builder.PipeTreeModelTsFileBuilder; import org.apache.iotdb.collector.plugin.builtin.sink.utils.builder.PipeTsFileBuilder; diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTransferBatchReqBuilder.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTransferBatchReqBuilder.java index f9539293..4bc87897 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTransferBatchReqBuilder.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/payload/evolvable/batch/PipeTransferBatchReqBuilder.java @@ -19,7 +19,7 @@ package org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch; -import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.PipeRawTabletInsertionEvent; import org.apache.iotdb.common.rpc.thrift.TEndPoint; import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters; import org.apache.iotdb.pipe.api.event.Event; diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ClientPoolProperty.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ClientPoolProperty.java deleted file mode 100644 index 359bfe33..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/ClientPoolProperty.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.property; - -import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; - -import java.time.Duration; -import java.util.concurrent.TimeUnit; - -public class ClientPoolProperty { - - private final GenericKeyedObjectPoolConfig config; - - private ClientPoolProperty(GenericKeyedObjectPoolConfig config) { - this.config = config; - } - - public GenericKeyedObjectPoolConfig getConfig() { - return config; - } - - public static class Builder { - - /** - * when the number of the client to a single node exceeds maxClientNumForEachNode, the thread - * for applying for a client will be blocked for waitClientTimeoutMs, then ClientManager will - * throw ClientManagerException if there are no clients after the block time. - */ - private long waitClientTimeoutMs = DefaultProperty.WAIT_CLIENT_TIMEOUT_MS; - - /** - * the maximum number of clients that can be allocated for a node. When some clients are idle - * for more than {@code maxIdleTimeForClient}, they will be cleaned up. - */ - private int maxClientNumForEachNode = DefaultProperty.MAX_CLIENT_NUM_FOR_EACH_NODE; - - /** - * the minimum amount of time a client may sit idle in the pool before it is eligible for - * eviction by the idle object evictor. - */ - private long minIdleTimeForClient = DefaultProperty.MIN_IDLE_TIME_FOR_CLIENT_MS; - - /** - * the duration to sleep between runs of the idle object evictor thread. When non-positive, no - * idle object evictor thread will be run, which means clients that are idle for more than - * {@code minIdleTimeForClient} will never be cleaned up. - */ - private long timeBetweenEvictionRuns = DefaultProperty.TIME_BETWEEN_EVICTION_RUNS_MS; - - public Builder setWaitClientTimeoutMs(long waitClientTimeoutMs) { - this.waitClientTimeoutMs = waitClientTimeoutMs; - return this; - } - - public Builder setMaxClientNumForEachNode(int maxClientNumForEachNode) { - this.maxClientNumForEachNode = maxClientNumForEachNode; - return this; - } - - public Builder setMinIdleTimeForClient(long minIdleTimeForClient) { - this.minIdleTimeForClient = minIdleTimeForClient; - return this; - } - - public Builder setTimeBetweenEvictionRuns(long timeBetweenEvictionRuns) { - this.timeBetweenEvictionRuns = timeBetweenEvictionRuns; - return this; - } - - public ClientPoolProperty build() { - GenericKeyedObjectPoolConfig poolConfig = new GenericKeyedObjectPoolConfig<>(); - poolConfig.setMaxTotalPerKey(maxClientNumForEachNode); - poolConfig.setMaxIdlePerKey(maxClientNumForEachNode); - poolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(timeBetweenEvictionRuns)); - poolConfig.setMinEvictableIdleTime(Duration.ofMillis(minIdleTimeForClient)); - poolConfig.setMaxWait(Duration.ofMillis(waitClientTimeoutMs)); - poolConfig.setTestOnReturn(true); - poolConfig.setTestOnBorrow(true); - return new ClientPoolProperty<>(poolConfig); - } - } - - public static class DefaultProperty { - - private DefaultProperty() {} - - public static final long WAIT_CLIENT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30); - public static final long MIN_IDLE_TIME_FOR_CLIENT_MS = TimeUnit.MINUTES.toMillis(1); - public static final long TIME_BETWEEN_EVICTION_RUNS_MS = TimeUnit.MINUTES.toMillis(1); - public static final int MAX_CLIENT_NUM_FOR_EACH_NODE = 1000; - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/PipeConsensusClientProperty.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/PipeConsensusClientProperty.java deleted file mode 100644 index e3cfe5bb..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/property/PipeConsensusClientProperty.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.property; - -/** This class defines the configurations used by the PipeConsensus Client. */ -public class PipeConsensusClientProperty { - private final boolean isRpcThriftCompressionEnabled; - private final int selectorNumOfClientManager; - private final boolean printLogWhenThriftClientEncounterException; - private final int maxClientNumForEachNode; - - public PipeConsensusClientProperty( - boolean isRpcThriftCompressionEnabled, - int selectorNumOfClientManager, - boolean printLogWhenThriftClientEncounterException, - int maxClientNumForEachNode) { - this.isRpcThriftCompressionEnabled = isRpcThriftCompressionEnabled; - this.selectorNumOfClientManager = selectorNumOfClientManager; - this.printLogWhenThriftClientEncounterException = printLogWhenThriftClientEncounterException; - this.maxClientNumForEachNode = maxClientNumForEachNode; - } - - public boolean isRpcThriftCompressionEnabled() { - return isRpcThriftCompressionEnabled; - } - - public int getSelectorNumOfClientManager() { - return selectorNumOfClientManager; - } - - public boolean isPrintLogWhenThriftClientEncounterException() { - return printLogWhenThriftClientEncounterException; - } - - public int getMaxClientNumForEachNode() { - return maxClientNumForEachNode; - } - - public static Builder newBuilder() { - return new Builder(); - } - - public static class Builder { - private boolean isRpcThriftCompressionEnabled = false; - private int selectorNumOfClientManager = 1; - private boolean printLogWhenThriftClientEncounterException = true; - private int maxClientNumForEachNode = - ClientPoolProperty.DefaultProperty.MAX_CLIENT_NUM_FOR_EACH_NODE; - - public Builder setIsRpcThriftCompressionEnabled(boolean isRpcThriftCompressionEnabled) { - this.isRpcThriftCompressionEnabled = isRpcThriftCompressionEnabled; - return this; - } - - public Builder setSelectorNumOfClientManager(int selectorNumOfClientManager) { - this.selectorNumOfClientManager = selectorNumOfClientManager; - return this; - } - - public Builder setPrintLogWhenThriftClientEncounterException( - boolean printLogWhenThriftClientEncounterException) { - this.printLogWhenThriftClientEncounterException = printLogWhenThriftClientEncounterException; - return this; - } - - public Builder setMaxClientNumForEachNode(int maxClientNumForEachNode) { - this.maxClientNumForEachNode = maxClientNumForEachNode; - return this; - } - - public PipeConsensusClientProperty build() { - return new PipeConsensusClientProperty( - isRpcThriftCompressionEnabled, - selectorNumOfClientManager, - printLogWhenThriftClientEncounterException, - maxClientNumForEachNode); - } - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/demo/DemoSink.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/DemoSink.java similarity index 97% rename from iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/demo/DemoSink.java rename to iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/DemoSink.java index 9205cc47..d5b8becf 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/demo/DemoSink.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/DemoSink.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.iotdb.collector.plugin.builtin.sink.protocol.demo; +package org.apache.iotdb.collector.plugin.builtin.sink.protocol; import org.apache.iotdb.pipe.api.PipeSink; import org.apache.iotdb.pipe.api.customizer.configuration.PipeConnectorRuntimeConfiguration; diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/IoTDBConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBConnector.java similarity index 99% rename from iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/IoTDBConnector.java rename to iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBConnector.java index df12205c..141bd643 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/IoTDBConnector.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBConnector.java @@ -17,13 +17,12 @@ * under the License. */ -package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift; +package org.apache.iotdb.collector.plugin.builtin.sink.protocol; import org.apache.iotdb.collector.plugin.builtin.sink.compressor.PipeCompressor; import org.apache.iotdb.collector.plugin.builtin.sink.compressor.PipeCompressorConfig; import org.apache.iotdb.collector.plugin.builtin.sink.compressor.PipeCompressorFactory; import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferCompressedReq; -import org.apache.iotdb.collector.plugin.builtin.sink.utils.NodeUrlUtils; import org.apache.iotdb.common.rpc.thrift.TEndPoint; import org.apache.iotdb.pipe.api.PipeConnector; import org.apache.iotdb.pipe.api.customizer.configuration.PipeConnectorRuntimeConfiguration; diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/IoTDBDataNodeSyncConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataNodeSyncConnector.java similarity index 93% rename from iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/IoTDBDataNodeSyncConnector.java rename to iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataNodeSyncConnector.java index 1d33bf58..d7c0a777 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/IoTDBDataNodeSyncConnector.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataNodeSyncConnector.java @@ -17,10 +17,11 @@ * under the License. */ -package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift; +package org.apache.iotdb.collector.plugin.builtin.sink.protocol; import org.apache.iotdb.collector.config.PipeOptions; -import org.apache.iotdb.collector.plugin.builtin.sink.utils.NodeUrlUtils; +import org.apache.iotdb.collector.plugin.builtin.sink.client.IoTDBDataNodeSyncClientManager; +import org.apache.iotdb.collector.plugin.builtin.sink.client.IoTDBSyncClientManager; import org.apache.iotdb.common.rpc.thrift.TEndPoint; import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator; diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/IoTDBDataRegionSyncConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataRegionSyncConnector.java similarity index 92% rename from iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/IoTDBDataRegionSyncConnector.java rename to iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataRegionSyncConnector.java index 57319b7f..9380757a 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/IoTDBDataRegionSyncConnector.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataRegionSyncConnector.java @@ -17,10 +17,11 @@ * under the License. */ -package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift; +package org.apache.iotdb.collector.plugin.builtin.sink.protocol; -import org.apache.iotdb.collector.plugin.builtin.sink.event.tablet.PipeRawTabletInsertionEvent; -import org.apache.iotdb.collector.plugin.builtin.sink.event.tsfile.PipeTsFileInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.client.IoTDBSyncClient; +import org.apache.iotdb.collector.plugin.builtin.sink.event.PipeRawTabletInsertionEvent; +import org.apache.iotdb.collector.plugin.builtin.sink.event.PipeTsFileInsertionEvent; import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch.PipeTabletEventBatch; import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch.PipeTabletEventPlainBatch; import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch.PipeTabletEventTsFileBatch; @@ -30,7 +31,6 @@ import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTsFilePieceWithModReq; import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.request.PipeTransferTsFileSealWithModReq; import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferFilePieceReq; -import org.apache.iotdb.collector.plugin.builtin.sink.utils.RetryUtils; import org.apache.iotdb.collector.plugin.builtin.sink.utils.cacher.LeaderCacheUtils; import org.apache.iotdb.common.rpc.thrift.TEndPoint; import org.apache.iotdb.common.rpc.thrift.TSStatus; @@ -282,27 +282,16 @@ private void doTransfer(final PipeRawTabletInsertionEvent pipeRawTabletInsertion private void doTransferWrapper(final PipeTsFileInsertionEvent pipeTsFileInsertionEvent) throws PipeException, IOException { - // We increase the reference count for this event to determine if the event may be released. - if (!pipeTsFileInsertionEvent.increaseReferenceCount( - IoTDBDataRegionSyncConnector.class.getName())) { - return; - } - try { - doTransfer( - Collections.singletonMap( - new Pair<>( - pipeTsFileInsertionEvent.getPipeName(), - pipeTsFileInsertionEvent.getCreationTime()), - 1.0), - pipeTsFileInsertionEvent.getTsFile(), - pipeTsFileInsertionEvent.isWithMod() ? pipeTsFileInsertionEvent.getModFile() : null, - pipeTsFileInsertionEvent.isTableModelEvent() - ? pipeTsFileInsertionEvent.getTableModelDatabaseName() - : null); - } finally { - pipeTsFileInsertionEvent.decreaseReferenceCount( - IoTDBDataRegionSyncConnector.class.getName(), false); - } + doTransfer( + Collections.singletonMap( + new Pair<>( + pipeTsFileInsertionEvent.getPipeName(), pipeTsFileInsertionEvent.getCreationTime()), + 1.0), + pipeTsFileInsertionEvent.getTsFile(), + pipeTsFileInsertionEvent.isWithMod() ? pipeTsFileInsertionEvent.getModFile() : null, + pipeTsFileInsertionEvent.isTableModelEvent() + ? pipeTsFileInsertionEvent.getTableModelDatabaseName() + : null); } private void doTransfer( diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/IoTDBSslSyncConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBSslSyncConnector.java similarity index 97% rename from iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/IoTDBSslSyncConnector.java rename to iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBSslSyncConnector.java index 1b93fb11..b1920d6c 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/IoTDBSslSyncConnector.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBSslSyncConnector.java @@ -17,9 +17,11 @@ * under the License. */ -package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift; +package org.apache.iotdb.collector.plugin.builtin.sink.protocol; import org.apache.iotdb.collector.config.PipeOptions; +import org.apache.iotdb.collector.plugin.builtin.sink.client.IoTDBSyncClient; +import org.apache.iotdb.collector.plugin.builtin.sink.client.IoTDBSyncClientManager; import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.request.PipeTransferFilePieceReq; import org.apache.iotdb.collector.plugin.builtin.sink.payload.thrift.response.PipeTransferFilePieceResp; import org.apache.iotdb.common.rpc.thrift.TEndPoint; diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/PipeReceiverStatusHandler.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/PipeReceiverStatusHandler.java similarity index 99% rename from iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/PipeReceiverStatusHandler.java rename to iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/PipeReceiverStatusHandler.java index ac9f8891..477dc641 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/thrift/PipeReceiverStatusHandler.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/PipeReceiverStatusHandler.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.iotdb.collector.plugin.builtin.sink.protocol.thrift; +package org.apache.iotdb.collector.plugin.builtin.sink.protocol; import org.apache.iotdb.common.rpc.thrift.TSStatus; import org.apache.iotdb.pipe.api.event.Event; diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/DateTimeUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/DateTimeUtils.java deleted file mode 100644 index 103e09c6..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/DateTimeUtils.java +++ /dev/null @@ -1,895 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.iotdb.collector.plugin.builtin.sink.utils; - -import org.apache.iotdb.collector.config.PipeOptions; - -import org.apache.tsfile.utils.DateUtils; -import org.apache.tsfile.utils.TimeDuration; - -import java.time.DateTimeException; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.format.DateTimeParseException; -import java.time.format.ResolverStyle; -import java.time.format.SignStyle; -import java.time.temporal.ChronoField; -import java.util.Calendar; -import java.util.TimeZone; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; - -public class DateTimeUtils { - - private DateTimeUtils() { - // forbidding instantiation - } - - public static final String TIMESTAMP_PRECISION = PipeOptions.TIMESTAMP_PRECISION.value(); - - public static long correctPrecision(long millis) { - try { - switch (TIMESTAMP_PRECISION) { - case "us": - case "microsecond": - return Math.multiplyExact(millis, 1_000L); - case "ns": - case "nanosecond": - return Math.multiplyExact(millis, 1_000_000L); - case "ms": - case "millisecond": - default: - return millis; - } - } catch (ArithmeticException e) { - throw new RuntimeException( - String.format( - "Timestamp overflow, Millisecond: %s , Timestamp precision: %s", - millis, TIMESTAMP_PRECISION)); - } - } - - private static Function CAST_TIMESTAMP_TO_MS; - - static { - switch (PipeOptions.TIMESTAMP_PRECISION.value()) { - case "us": - case "microsecond": - CAST_TIMESTAMP_TO_MS = timestamp -> timestamp / 1000; - break; - case "ns": - case "nanosecond": - CAST_TIMESTAMP_TO_MS = timestamp -> timestamp / 1000000; - break; - case "ms": - case "millisecond": - default: - CAST_TIMESTAMP_TO_MS = timestamp -> timestamp; - break; - } - } - - private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - public static final DateTimeFormatter ISO_LOCAL_DATE_WIDTH_1_2; - - static { - ISO_LOCAL_DATE_WIDTH_1_2 = - new DateTimeFormatterBuilder() - .appendValue(ChronoField.YEAR, 4, 19, SignStyle.NEVER) - .appendLiteral('-') - .appendValue(ChronoField.MONTH_OF_YEAR, 1, 2, SignStyle.NEVER) - .appendLiteral('-') - .appendValue(ChronoField.DAY_OF_MONTH, 1, 2, SignStyle.NEVER) - .toFormatter(); - } - - /** such as '2011/12/03'. */ - public static final DateTimeFormatter ISO_LOCAL_DATE_WITH_SLASH; - - static { - ISO_LOCAL_DATE_WITH_SLASH = - new DateTimeFormatterBuilder() - .appendValue(ChronoField.YEAR, 4, 19, SignStyle.NEVER) - .appendLiteral('/') - .appendValue(ChronoField.MONTH_OF_YEAR, 1, 2, SignStyle.NEVER) - .appendLiteral('/') - .appendValue(ChronoField.DAY_OF_MONTH, 1, 2, SignStyle.NEVER) - .toFormatter(); - } - - /** such as '2011.12.03'. */ - public static final DateTimeFormatter ISO_LOCAL_DATE_WITH_DOT; - - static { - ISO_LOCAL_DATE_WITH_DOT = - new DateTimeFormatterBuilder() - .appendValue(ChronoField.YEAR, 4, 19, SignStyle.NEVER) - .appendLiteral('.') - .appendValue(ChronoField.MONTH_OF_YEAR, 1, 2, SignStyle.NEVER) - .appendLiteral('.') - .appendValue(ChronoField.DAY_OF_MONTH, 1, 2, SignStyle.NEVER) - .toFormatter(); - } - - /** such as '10:15:30' or '10:15:30.123'. */ - public static final DateTimeFormatter ISO_LOCAL_TIME_WITH_MS; - - static { - ISO_LOCAL_TIME_WITH_MS = - new DateTimeFormatterBuilder() - .appendValue(ChronoField.HOUR_OF_DAY, 2) - .appendLiteral(':') - .appendValue(ChronoField.MINUTE_OF_HOUR, 2) - .appendLiteral(':') - .appendValue(ChronoField.SECOND_OF_MINUTE, 2) - .optionalStart() - .appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true) - .toFormatter(); - } - - /** such as '10:15:30' or '10:15:30.123456'. */ - public static final DateTimeFormatter ISO_LOCAL_TIME_WITH_US; - - static { - ISO_LOCAL_TIME_WITH_US = - new DateTimeFormatterBuilder() - .appendValue(ChronoField.HOUR_OF_DAY, 2) - .appendLiteral(':') - .appendValue(ChronoField.MINUTE_OF_HOUR, 2) - .appendLiteral(':') - .appendValue(ChronoField.SECOND_OF_MINUTE, 2) - .optionalStart() - .appendFraction(ChronoField.MICRO_OF_SECOND, 0, 6, true) - .toFormatter(); - } - - /** such as '10:15:30' or '10:15:30.123456789'. */ - public static final DateTimeFormatter ISO_LOCAL_TIME_WITH_NS; - - static { - ISO_LOCAL_TIME_WITH_NS = - new DateTimeFormatterBuilder() - .appendValue(ChronoField.HOUR_OF_DAY, 2) - .appendLiteral(':') - .appendValue(ChronoField.MINUTE_OF_HOUR, 2) - .appendLiteral(':') - .appendValue(ChronoField.SECOND_OF_MINUTE, 2) - .optionalStart() - .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true) - .optionalEnd() - .toFormatter(); - } - - /** such as '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30.123+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_MS; - - static { - ISO_OFFSET_DATE_TIME_WITH_MS = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WIDTH_1_2) - .appendLiteral('T') - .append(ISO_LOCAL_TIME_WITH_MS) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30.123456+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_US; - - static { - ISO_OFFSET_DATE_TIME_WITH_US = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WIDTH_1_2) - .appendLiteral('T') - .append(ISO_LOCAL_TIME_WITH_US) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30.123456789+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_NS; - - static { - ISO_OFFSET_DATE_TIME_WITH_NS = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WIDTH_1_2) - .appendLiteral('T') - .append(ISO_LOCAL_TIME_WITH_NS) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011/12/03T10:15:30+01:00' or '2011/12/03T10:15:30.123+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SLASH; - - static { - ISO_OFFSET_DATE_TIME_WITH_SLASH = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WITH_SLASH) - .appendLiteral('T') - .append(ISO_LOCAL_TIME_WITH_MS) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011/12/03T10:15:30+01:00' or '2011/12/03T10:15:30.123456+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SLASH_US; - - static { - ISO_OFFSET_DATE_TIME_WITH_SLASH_US = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WITH_SLASH) - .appendLiteral('T') - .append(ISO_LOCAL_TIME_WITH_US) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011/12/03T10:15:30+01:00' or '2011/12/03T10:15:30.123456789+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SLASH_NS; - - static { - ISO_OFFSET_DATE_TIME_WITH_SLASH_NS = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WITH_SLASH) - .appendLiteral('T') - .append(ISO_LOCAL_TIME_WITH_NS) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011.12.03T10:15:30+01:00' or '2011.12.03T10:15:30.123+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_DOT; - - static { - ISO_OFFSET_DATE_TIME_WITH_DOT = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WITH_DOT) - .appendLiteral('T') - .append(ISO_LOCAL_TIME_WITH_MS) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011.12.03T10:15:30+01:00' or '2011.12.03T10:15:30.123456+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_DOT_US; - - static { - ISO_OFFSET_DATE_TIME_WITH_DOT_US = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WITH_DOT) - .appendLiteral('T') - .append(ISO_LOCAL_TIME_WITH_US) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011.12.03T10:15:30+01:00' or '2011.12.03T10:15:30.123456789+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_DOT_NS; - - static { - ISO_OFFSET_DATE_TIME_WITH_DOT_NS = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WITH_DOT) - .appendLiteral('T') - .append(ISO_LOCAL_TIME_WITH_NS) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011-12-03 10:15:30+01:00' or '2011-12-03 10:15:30.123+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SPACE; - - static { - ISO_OFFSET_DATE_TIME_WITH_SPACE = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(DateTimeFormatter.ISO_LOCAL_DATE) - .appendLiteral(' ') - .append(ISO_LOCAL_TIME_WITH_MS) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011-12-03 10:15:30+01:00' or '2011-12-03 10:15:30.123456+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SPACE_US; - - static { - ISO_OFFSET_DATE_TIME_WITH_SPACE_US = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(DateTimeFormatter.ISO_LOCAL_DATE) - .appendLiteral(' ') - .append(ISO_LOCAL_TIME_WITH_US) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011-12-03 10:15:30+01:00' or '2011-12-03 10:15:30.123456789+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SPACE_NS; - - static { - ISO_OFFSET_DATE_TIME_WITH_SPACE_NS = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(DateTimeFormatter.ISO_LOCAL_DATE) - .appendLiteral(' ') - .append(ISO_LOCAL_TIME_WITH_NS) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011/12/03 10:15:30+01:00' or '2011/12/03 10:15:30.123+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE; - - static { - ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WITH_SLASH) - .appendLiteral(' ') - .append(ISO_LOCAL_TIME_WITH_MS) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011/12/03 10:15:30+01:00' or '2011/12/03 10:15:30.123456+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE_US; - - static { - ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE_US = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WITH_SLASH) - .appendLiteral(' ') - .append(ISO_LOCAL_TIME_WITH_US) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011/12/03 10:15:30+01:00' or '2011/12/03 10:15:30.123456789+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE_NS; - - static { - ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE_NS = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WITH_SLASH) - .appendLiteral(' ') - .append(ISO_LOCAL_TIME_WITH_NS) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011.12.03 10:15:30+01:00' or '2011.12.03 10:15:30.123+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE; - - static { - ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WITH_DOT) - .appendLiteral(' ') - .append(ISO_LOCAL_TIME_WITH_MS) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011.12.03 10:15:30+01:00' or '2011.12.03 10:15:30.123456+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE_US; - - static { - ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE_US = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WITH_DOT) - .appendLiteral(' ') - .append(ISO_LOCAL_TIME_WITH_US) - .appendOffsetId() - .toFormatter(); - } - - /** such as '2011.12.03 10:15:30+01:00' or '2011.12.03 10:15:30.123456789+01:00'. */ - public static final DateTimeFormatter ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE_NS; - - static { - ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE_NS = - new DateTimeFormatterBuilder() - .parseCaseInsensitive() - .append(ISO_LOCAL_DATE_WITH_DOT) - .appendLiteral(' ') - .append(ISO_LOCAL_TIME_WITH_NS) - .appendOffsetId() - .toFormatter(); - } - - public static final DateTimeFormatter formatter = - new DateTimeFormatterBuilder() - /** - * The ISO date-time formatter that formats or parses a date-time with an offset, such as - * '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30.123+01:00'. - */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_MS) - - /** such as '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30.123456+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_US) - - /** such as '2011-12-03T10:15:30+01:00' or '2011-12-03T10:15:30.123456789+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_NS) - - /** such as '2011/12/03T10:15:30+01:00' or '2011/12/03T10:15:30.123+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SLASH) - - /** such as '2011/12/03T10:15:30+01:00' or '2011/12/03T10:15:30.123456+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SLASH_US) - - /** such as '2011/12/03T10:15:30+01:00' or '2011/12/03T10:15:30.123456789+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SLASH_NS) - - /** such as '2011.12.03T10:15:30+01:00' or '2011.12.03T10:15:30.123+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_DOT) - - /** such as '2011.12.03T10:15:30+01:00' or '2011.12.03T10:15:30.123456+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_DOT_US) - - /** such as '2011.12.03T10:15:30+01:00' or '2011.12.03T10:15:30.123456789+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_DOT_NS) - - /** such as '2011-12-03 10:15:30+01:00' or '2011-12-03 10:15:30.123+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SPACE) - - /** such as '2011-12-03 10:15:30+01:00' or '2011-12-03 10:15:30.123456+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SPACE_US) - - /** such as '2011-12-03 10:15:30+01:00' or '2011-12-03 10:15:30.123456789+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SPACE_NS) - - /** such as '2011/12/03 10:15:30+01:00' or '2011/12/03 10:15:30.123+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE) - - /** such as '2011/12/03 10:15:30+01:00' or '2011/12/03 10:15:30.123456+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE_US) - - /** such as '2011/12/03 10:15:30+01:00' or '2011/12/03 10:15:30.123456789+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_SLASH_WITH_SPACE_NS) - - /** such as '2011.12.03 10:15:30+01:00' or '2011.12.03 10:15:30.123+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE) - - /** such as '2011.12.03 10:15:30+01:00' or '2011.12.03 10:15:30.123456+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE_US) - - /** such as '2011.12.03 10:15:30+01:00' or '2011.12.03 10:15:30.123456789+01:00'. */ - .appendOptional(ISO_OFFSET_DATE_TIME_WITH_DOT_WITH_SPACE_NS) - .toFormatter() - .withResolverStyle(ResolverStyle.STRICT); - - public static long convertTimestampOrDatetimeStrToLongWithDefaultZone(String timeStr) { - try { - return Long.parseLong(timeStr); - } catch (NumberFormatException e) { - return DateTimeUtils.convertDatetimeStrToLong(timeStr, ZoneId.systemDefault()); - } - } - - public static long convertDatetimeStrToLong(String str, ZoneId zoneId) { - return convertDatetimeStrToLong( - str, toZoneOffset(zoneId), 0, PipeOptions.TIMESTAMP_PRECISION.value()); - } - - public static long convertDatetimeStrToLong( - String str, ZoneId zoneId, String timestampPrecision) { - return convertDatetimeStrToLong(str, toZoneOffset(zoneId), 0, timestampPrecision); - } - - public static long getInstantWithPrecision(String str, String timestampPrecision) { - try { - ZonedDateTime zonedDateTime = ZonedDateTime.parse(str, formatter); - Instant instant = zonedDateTime.toInstant(); - if ("us".equals(timestampPrecision) || "microsecond".equals(timestampPrecision)) { - if (instant.getEpochSecond() < 0 && instant.getNano() > 0) { - // adjustment can reduce the loss of the division - long millis = Math.multiplyExact(instant.getEpochSecond() + 1, 1000_000L); - long adjustment = instant.getNano() / 1000 - 1L; - return Math.addExact(millis, adjustment); - } else { - long millis = Math.multiplyExact(instant.getEpochSecond(), 1000_000L); - return Math.addExact(millis, instant.getNano() / 1000); - } - } else if ("ns".equals(timestampPrecision) || "nanosecond".equals(timestampPrecision)) { - long millis = Math.multiplyExact(instant.getEpochSecond(), 1000_000_000L); - return Math.addExact(millis, instant.getNano()); - } - return instant.toEpochMilli(); - } catch (DateTimeParseException e) { - throw new RuntimeException(e.getMessage()); - } - } - - /** convert date time string to millisecond, microsecond or nanosecond. */ - public static long convertDatetimeStrToLong( - String str, ZoneOffset offset, int depth, String timestampPrecision) { - if (depth >= 2) { - throw new DateTimeException( - String.format( - "Failed to convert %s to millisecond, zone offset is %s, " - + "please input like 2011-12-03T10:15:30 or 2011-12-03T10:15:30+01:00", - str, offset)); - } - if (str.contains("Z")) { - return convertDatetimeStrToLong( - str.substring(0, str.indexOf('Z')) + "+00:00", offset, depth, timestampPrecision); - } else if (str.length() == 10) { - return convertDatetimeStrToLong(str + "T00:00:00", offset, depth, timestampPrecision); - } else if (str.length() - str.lastIndexOf('+') != 6 - && str.length() - str.lastIndexOf('-') != 6) { - return convertDatetimeStrToLong(str + offset, offset, depth + 1, timestampPrecision); - } else if (str.contains("[") || str.contains("]")) { - throw new DateTimeException( - String.format( - "%s with [time-region] at end is not supported now, " - + "please input like 2011-12-03T10:15:30 or 2011-12-03T10:15:30+01:00", - str)); - } - return getInstantWithPrecision(str, timestampPrecision); - } - - /** - * Convert duration string to time value. CurrentTime is used to calculate the days of natural - * month. If it's set as -1, which means a context free situation, then '1mo' will be thought as - * 30 days. - * - * @param duration represent duration string like: 12d8m9ns, 1y1mo, etc. - * @return time in milliseconds, microseconds, or nanoseconds depending on the profile - */ - public static long convertDurationStrToLong(String duration) { - return convertDurationStrToLong(-1, duration, false); - } - - public static long convertDurationStrToLong(String duration, boolean convertYearToMonth) { - return convertDurationStrToLong(-1, duration, convertYearToMonth); - } - - public static long convertDurationStrToLong( - String duration, String timestampPrecision, boolean convertYearToMonth) { - return convertDurationStrToLong(-1, duration, timestampPrecision, convertYearToMonth); - } - - public static long convertDurationStrToLong( - long currentTime, String duration, boolean convertYearToMonth) { - return convertDurationStrToLong( - currentTime, duration, PipeOptions.TIMESTAMP_PRECISION.value(), convertYearToMonth); - } - - /** - * convert duration string to time value. - * - * @param duration represent duration string like: 12d8m9ns, 1y1mo, etc. - * @param convertYearToMonth if we need convert year to month. eg: 1y -> 12mo - * @return time in milliseconds, microseconds, or nanoseconds depending on the profile - */ - public static long convertDurationStrToLong( - long currentTime, String duration, String timestampPrecision, boolean convertYearToMonth) { - long total = 0; - long temp = 0; - for (int i = 0; i < duration.length(); i++) { - char ch = duration.charAt(i); - if (Character.isDigit(ch)) { - temp *= 10; - temp += (ch - '0'); - } else { - String unit = String.valueOf(duration.charAt(i)); - // This is to identify units with two letters. - if (i + 1 < duration.length() && !Character.isDigit(duration.charAt(i + 1))) { - i++; - unit += duration.charAt(i); - } - unit = unit.toLowerCase(); - if (convertYearToMonth && unit.equals("y")) { - temp *= 12; - unit = "mo"; - } - total += - DateTimeUtils.convertDurationStrToLong( - currentTime == -1 ? -1 : currentTime + total, temp, unit, timestampPrecision); - temp = 0; - } - } - return total; - } - - /** convert duration string to millisecond, microsecond or nanosecond. */ - public static long convertDurationStrToLong( - long currentTime, long value, String unit, String timestampPrecision) { - DurationUnit durationUnit = DurationUnit.valueOf(unit); - long res = value; - switch (durationUnit) { - case y: - case year: - res *= 365 * 86_400_000L; - break; - case mo: - case month: - if (currentTime == -1) { - res *= 30 * 86_400_000L; - } else { - Calendar calendar = Calendar.getInstance(); - // calendar.setTimeZone(SessionManager.getInstance().getSessionTimeZone()); - calendar.setTimeInMillis(currentTime); - calendar.add(Calendar.MONTH, (int) (value)); - res = calendar.getTimeInMillis() - currentTime; - } - break; - case w: - case week: - res *= 7 * 86_400_000L; - break; - case d: - case day: - res *= 86_400_000L; - break; - case h: - case hour: - res *= 3_600_000L; - break; - case m: - case minute: - res *= 60_000L; - break; - case s: - case second: - res *= 1_000L; - break; - default: - break; - } - - if ("us".equals(timestampPrecision) || "microsecond".equals(timestampPrecision)) { - if (unit.equals(DurationUnit.ns.toString()) - || unit.equals(DurationUnit.nanosecond.toString())) { - return value / 1000; - } else if (unit.equals(DurationUnit.us.toString()) - || unit.equals(DurationUnit.microsecond.toString())) { - return value; - } else { - return res * 1000; - } - } else if ("ns".equals(timestampPrecision) || "nanosecond".equals(timestampPrecision)) { - if (unit.equals(DurationUnit.ns.toString()) - || unit.equals(DurationUnit.nanosecond.toString())) { - return value; - } else if (unit.equals(DurationUnit.us.toString()) - || unit.equals(DurationUnit.microsecond.toString())) { - return value * 1000; - } else { - return res * 1000_000; - } - } else { - if (unit.equals(DurationUnit.ns.toString()) - || unit.equals(DurationUnit.nanosecond.toString())) { - return value / 1000_000; - } else if (unit.equals(DurationUnit.us.toString()) - || unit.equals(DurationUnit.microsecond.toString())) { - return value / 1000; - } else { - return res; - } - } - } - - public static TimeUnit timestampPrecisionStringToTimeUnit(String timestampPrecision) { - if ("us".equals(timestampPrecision) || "microsecond".equals(timestampPrecision)) { - return TimeUnit.MICROSECONDS; - } else if ("ns".equals(timestampPrecision) || "nanosecond".equals(timestampPrecision)) { - return TimeUnit.NANOSECONDS; - } else { - return TimeUnit.MILLISECONDS; - } - } - - public static String convertLongToDate(long timestamp) { - return convertLongToDate( - timestamp, PipeOptions.TIMESTAMP_PRECISION.value(), ZoneId.systemDefault()); - } - - public static String convertLongToDate(final long timestamp, final ZoneId zoneId) { - return convertLongToDate(timestamp, PipeOptions.TIMESTAMP_PRECISION.value(), zoneId); - } - - public static String convertLongToDate(final long timestamp, final String sourcePrecision) { - return convertLongToDate(timestamp, sourcePrecision, ZoneId.systemDefault()); - } - - public static String convertLongToDate( - long timestamp, final String sourcePrecision, final ZoneId zoneId) { - switch (sourcePrecision) { - case "ns": - case "nanosecond": - timestamp /= 1000_000; - break; - case "us": - case "microsecond": - timestamp /= 1000; - break; - default: - break; - } - return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zoneId).toString(); - } - - public static LocalDate convertToLocalDate(long timestamp, ZoneId zoneId) { - timestamp = CAST_TIMESTAMP_TO_MS.apply(timestamp); - return Instant.ofEpochMilli(timestamp).atZone(zoneId).toLocalDate(); - } - - public static ZonedDateTime convertToZonedDateTime(long timestamp, ZoneId zoneId) { - timestamp = CAST_TIMESTAMP_TO_MS.apply(timestamp); - return ZonedDateTime.ofInstant(Instant.ofEpochMilli(timestamp), zoneId); - } - - public static ZoneOffset toZoneOffset(ZoneId zoneId) { - return zoneId.getRules().getOffset(Instant.now()); - } - - public static ZonedDateTime convertMillsecondToZonedDateTime(long millisecond) { - return ZonedDateTime.ofInstant(Instant.ofEpochMilli(millisecond), ZoneId.systemDefault()); - } - - public enum DurationUnit { - y, - year, - mo, - month, - w, - week, - d, - day, - h, - hour, - m, - minute, - s, - second, - ms, - millisecond, - us, - microsecond, - ns, - nanosecond - } - - public static TimeUnit toTimeUnit(String t) { - switch (t) { - case "h": - case "hour": - return TimeUnit.HOURS; - case "m": - case "minute": - return TimeUnit.MINUTES; - case "s": - case "second": - return TimeUnit.SECONDS; - case "ms": - case "millisecond": - return TimeUnit.MILLISECONDS; - case "u": - case "microsecond": - return TimeUnit.MICROSECONDS; - case "n": - case "nanosecond": - return TimeUnit.NANOSECONDS; - default: - throw new IllegalArgumentException("time precision must be one of: h,m,s,ms,u,n"); - } - } - - public static final long MS_TO_MONTH = 30 * 86400_000L; - - public static long calcPositiveIntervalByMonth( - long startTime, TimeDuration duration, ZoneId zoneId) { - return TimeDuration.calcPositiveIntervalByMonth( - startTime, duration, TimeZone.getTimeZone(zoneId), TimestampPrecisionUtils.currPrecision); - } - - /** - * Storage the duration into two parts: month part and non-month part, the non-month part's - * precision is depended on current time precision. e.g. ms precision: '1y1mo1ms' -> monthDuration - * = 13, nonMonthDuration = 1, ns precision: '1y1mo1ms' -> monthDuration = 13, nonMonthDuration = - * 1000_000. - * - * @param duration the input duration string - * @return the TimeDuration instance contains month part and non-month part - */ - public static TimeDuration constructTimeDuration(String duration) { - duration = duration.toLowerCase(); - String currTimePrecision = PipeOptions.TIMESTAMP_PRECISION.value(); - long temp = 0; - long monthDuration = 0; - long nonMonthDuration = 0; - int i = 0; - for (; i < duration.length(); i++) { - char ch = duration.charAt(i); - if (Character.isDigit(ch)) { - temp *= 10; - temp += (ch - '0'); - } else { - StringBuilder unit = new StringBuilder(String.valueOf(duration.charAt(i))); - i++; - // This is to identify units. - while (i < duration.length() && !Character.isDigit(duration.charAt(i))) { - unit.append(duration.charAt(i)); - i++; - } - i--; - if ("y".contentEquals(unit) || "year".contentEquals(unit)) { - monthDuration += temp * 12; - temp = 0; - continue; - } - if ("mo".contentEquals(unit) || "month".contentEquals(unit)) { - monthDuration += temp; - temp = 0; - continue; - } - nonMonthDuration += - DateTimeUtils.convertDurationStrToLong(-1, temp, unit.toString(), currTimePrecision); - temp = 0; - } - } - return new TimeDuration((int) monthDuration, nonMonthDuration); - } - - // public static Long parseDateTimeExpressionToLong(String dateExpression, ZoneId zoneId) { - // ASTVisitor astVisitor = new ASTVisitor(); - // astVisitor.setZoneId(zoneId); - // - // CharStream charStream1 = CharStreams.fromString(dateExpression); - // - // SqlLexer lexer1 = new SqlLexer(charStream1); - // lexer1.removeErrorListeners(); - // lexer1.addErrorListener(SqlParseError.INSTANCE); - // - // CommonTokenStream tokens1 = new CommonTokenStream(lexer1); - // - // IoTDBSqlParser parser1 = new IoTDBSqlParser(tokens1); - // parser1.getInterpreter().setPredictionMode(PredictionMode.SLL); - // parser1.removeErrorListeners(); - // parser1.addErrorListener(SqlParseError.INSTANCE); - // return astVisitor.parseDateExpression(parser1.dateExpression(), TIMESTAMP_PRECISION); - // } - - public static Integer parseDateExpressionToInt(String dateExpression) { - return DateUtils.parseDateExpressionToInt(dateExpression); - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/MemUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/MemUtils.java deleted file mode 100644 index 6850f781..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/MemUtils.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.utils; - -import org.apache.iotdb.collector.plugin.builtin.sink.constant.IoTDBConstant; -import org.apache.iotdb.common.rpc.thrift.TSStatus; -import org.apache.iotdb.rpc.TSStatusCode; - -import org.apache.tsfile.common.conf.TSFileConfig; -import org.apache.tsfile.enums.TSDataType; -import org.apache.tsfile.utils.Binary; -import org.apache.tsfile.utils.RamUsageEstimator; -import org.apache.tsfile.write.record.TSRecord; -import org.apache.tsfile.write.record.datapoint.BooleanDataPoint; -import org.apache.tsfile.write.record.datapoint.DataPoint; -import org.apache.tsfile.write.record.datapoint.DoubleDataPoint; -import org.apache.tsfile.write.record.datapoint.FloatDataPoint; -import org.apache.tsfile.write.record.datapoint.IntDataPoint; -import org.apache.tsfile.write.record.datapoint.LongDataPoint; -import org.apache.tsfile.write.record.datapoint.StringDataPoint; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.List; - -// Notice : methods in this class may not be accurate. -public class MemUtils { - - private static final Logger LOGGER = LoggerFactory.getLogger(MemUtils.class); - - private MemUtils() {} - - /** - * Function for obtaining the value size. For text values, there are two conditions: 1. During - * insertion, their size has already been added to memory. 2. During flushing, their size needs to - * be calculated. - */ - public static long getRecordSize(TSDataType dataType, Object value, boolean addingTextDataSize) { - if (dataType.isBinary()) { - return 8L + (addingTextDataSize ? getBinarySize((Binary) value) : 0); - } - return 8L + dataType.getDataTypeSize(); - } - - /** - * Function for obtaining the value size. For text values, their size has already been added to - * memory before insertion - */ - public static long getRowRecordSize(List dataTypes, Object[] value) { - int emptyRecordCount = 0; - long memSize = 0L; - for (int i = 0; i < value.length; i++) { - if (value[i] == null) { - emptyRecordCount++; - continue; - } - memSize += getRecordSize(dataTypes.get(i - emptyRecordCount), value[i], false); - } - return memSize; - } - - /** - * Function for obtaining the value size. For text values, their size has already been added to - * memory before insertion - */ - public static long getAlignedRowRecordSize( - List dataTypes, Object[] value, TsTableColumnCategory[] columnCategories) { - // time and index size - long memSize = 8L + 4L; - for (int i = 0; i < dataTypes.size(); i++) { - if (value[i] == null - || dataTypes.get(i).isBinary() - || columnCategories != null && columnCategories[i] != TsTableColumnCategory.FIELD) { - continue; - } - memSize += dataTypes.get(i).getDataTypeSize(); - } - return memSize; - } - - public static long getBinarySize(Binary value) { - return RamUsageEstimator.NUM_BYTES_OBJECT_HEADER + RamUsageEstimator.sizeOf(value.getValues()); - } - - public static long getBinaryColumnSize(Binary[] column, int start, int end, TSStatus[] results) { - long memSize = 0; - memSize += (long) (end - start) * RamUsageEstimator.NUM_BYTES_OBJECT_HEADER; - for (int i = start; i < end; i++) { - if (results == null - || results[i] == null - || results[i].code == TSStatusCode.SUCCESS_STATUS.getStatusCode()) { - memSize += RamUsageEstimator.sizeOf(column[i].getValues()); - } - } - return memSize; - } - - // /** - // * Function for obtaining the value size. For text values, their size has already been added to - // * memory before insertion - // */ - // public static long getTabletSize(InsertTabletNode insertTabletNode, int start, int end) { - // if (start >= end) { - // return 0L; - // } - // long memSize = 0; - // for (int i = 0; i < insertTabletNode.getMeasurements().length; i++) { - // if (insertTabletNode.getMeasurements()[i] == null) { - // continue; - // } - // // Time column memSize - // memSize += (end - start) * 8L; - // memSize += (long) (end - start) * insertTabletNode.getDataTypes()[i].getDataTypeSize(); - // } - // return memSize; - // } - // - // public static long getAlignedTabletSize( - // InsertTabletNode insertTabletNode, int start, int end, TSStatus[] results) { - // if (start >= end) { - // return 0L; - // } - // long memSize = 0; - // for (int i = 0; i < insertTabletNode.getMeasurements().length; i++) { - // if (!insertTabletNode.isValidMeasurement(i)) { - // continue; - // } - // if (results == null) { - // memSize += (long) (end - start) * insertTabletNode.getDataTypes()[i].getDataTypeSize(); - // } else { - // for (int j = start; j < end; j++) { - // if (results[j] == null - // || results[j].code == TSStatusCode.SUCCESS_STATUS.getStatusCode()) { - // memSize += insertTabletNode.getDataTypes()[i].getDataTypeSize(); - // } - // } - // } - // } - // // time and index column memSize for vector - // memSize += (end - start) * (8L + 4L); - // return memSize; - // } - - /** Calculate how much memory will be used if the given record is written to sequence file. */ - public static long getTsRecordMem(TSRecord tsRecord) { - long memUsed = 8; // time - memUsed += 8; // deviceId reference - memUsed += tsRecord.deviceId.ramBytesUsed(); - for (DataPoint dataPoint : tsRecord.dataPointList) { - memUsed += 8; // dataPoint reference - memUsed += getDataPointMem(dataPoint); - } - return memUsed; - } - - /** Function for getting the memory size of the given string. */ - public static long getStringMem(String str) { - // wide char (2 bytes each) and 64B String overhead - return str.length() * 2L + 64L; - } - - /** Function for getting the memory size of the given data point. */ - public static long getDataPointMem(DataPoint dataPoint) { - // type reference - long memUsed = 8; - // measurementId and its reference - memUsed += getStringMem(dataPoint.getMeasurementId()); - memUsed += 8; - - if (dataPoint instanceof FloatDataPoint) { - memUsed += 4; - } else if (dataPoint instanceof IntDataPoint) { - memUsed += 4; - } else if (dataPoint instanceof BooleanDataPoint) { - memUsed += 1; - } else if (dataPoint instanceof DoubleDataPoint) { - memUsed += 8; - } else if (dataPoint instanceof LongDataPoint) { - memUsed += 8; - } else if (dataPoint instanceof StringDataPoint) { - StringDataPoint stringDataPoint = (StringDataPoint) dataPoint; - memUsed += 8 + 20; // array reference and array overhead - memUsed += ((Binary) stringDataPoint.getValue()).getLength(); - // encoding string reference and its memory - memUsed += 8; - memUsed += getStringMem(TSFileConfig.STRING_ENCODING); - } else { - LOGGER.error("Unsupported data point type"); - } - - return memUsed; - } - - /** Function for converting the byte count result to readable string. */ - public static String bytesCntToStr(long inputCnt) { - long cnt = inputCnt; - long gbs = cnt / IoTDBConstant.GB; - cnt = cnt % IoTDBConstant.GB; - long mbs = cnt / IoTDBConstant.MB; - cnt = cnt % IoTDBConstant.MB; - long kbs = cnt / IoTDBConstant.KB; - cnt = cnt % IoTDBConstant.KB; - return gbs + " GB " + mbs + " MB " + kbs + " KB " + cnt + " B"; - } - - public static long strToBytesCnt(String str) { - if (str == null || str.isEmpty()) { - return 0; - } - str = str.toLowerCase(); - if (!str.endsWith("b")) { - str += "b"; - } - long unit = 1; - if (str.endsWith("kb")) { - unit = IoTDBConstant.KB; - } else if (str.endsWith("mb")) { - unit = IoTDBConstant.MB; - } else if (str.endsWith("gb")) { - unit = IoTDBConstant.GB; - } - str = str.replaceAll("\\D", ""); - return Long.parseLong(str) * unit; - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/NodeUrlUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/NodeUrlUtils.java deleted file mode 100644 index 5b11103d..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/NodeUrlUtils.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.utils; - -import org.apache.iotdb.common.rpc.thrift.TConfigNodeLocation; -import org.apache.iotdb.common.rpc.thrift.TEndPoint; -import org.apache.iotdb.rpc.UrlUtils; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.StringJoiner; -import java.util.stream.Collectors; - -public class NodeUrlUtils { - - private static final Logger logger = LoggerFactory.getLogger(NodeUrlUtils.class); - - public static final String WILD_CARD_ADDRESS = "0.0.0.0"; - public static final String LOOPBACK_HOST_NAME = "localhost"; - - /** - * Convert TEndPoint to TEndPointUrl - * - * @param endPoint TEndPoint - * @return TEndPointUrl with format ip:port - */ - public static String convertTEndPointUrl(TEndPoint endPoint) { - StringJoiner url = new StringJoiner(":"); - url.add(endPoint.getIp()); - url.add(String.valueOf(endPoint.getPort())); - return url.toString(); - } - - /** - * Convert TEndPoints to TEndPointUrls - * - * @param endPoints List - * @return TEndPointUrls with format TEndPointUrl_0,TEndPointUrl_1,...,TEndPointUrl_n - */ - public static String convertTEndPointUrls(List endPoints) { - StringJoiner urls = new StringJoiner(","); - for (TEndPoint endPoint : endPoints) { - urls.add(convertTEndPointUrl(endPoint)); - } - return urls.toString(); - } - - /** - * Parse TEndPoint from a given TEndPointUrl - * - * @param endPointUrl ip:port - * @return TEndPoint - * @throws BadNodeUrlException Throw when unable to parse - */ - public static TEndPoint parseTEndPointUrl(String endPointUrl) throws BadNodeUrlException { - return UrlUtils.parseTEndPointIpv4AndIpv6Url(endPointUrl); - } - - /** - * Parse TEndPoints from given TEndPointUrls - * - * @param endPointUrls List - * @return List - */ - public static List parseTEndPointUrls(List endPointUrls) { - if (endPointUrls == null) { - throw new NumberFormatException("endPointUrls is null"); - } - List result = new ArrayList<>(); - for (String url : endPointUrls) { - result.add(UrlUtils.parseTEndPointIpv4AndIpv6Url(url)); - } - return result; - } - - /** - * Parse TEndPoints from given TEndPointUrls - * - * @param endPointUrls TEndPointUrls - * @return List - * @throws BadNodeUrlException Throw when unable to parse - */ - public static List parseTEndPointUrls(String endPointUrls) throws BadNodeUrlException { - return parseTEndPointUrls(Arrays.asList(endPointUrls.split(","))); - } - - /** - * Convert TConfigNodeLocation to TConfigNodeUrl - * - * @param configNodeLocation TConfigNodeLocation - * @return TConfigNodeUrl with format InternalEndPointUrl,ConsensusEndPointUrl - */ - public static String convertTConfigNodeUrl(TConfigNodeLocation configNodeLocation) { - StringJoiner url = new StringJoiner(","); - url.add(String.valueOf(configNodeLocation.getConfigNodeId())); - url.add(convertTEndPointUrl(configNodeLocation.getInternalEndPoint())); - url.add(convertTEndPointUrl(configNodeLocation.getConsensusEndPoint())); - return url.toString(); - } - - /** - * Convert TConfigNodeLocations to TConfigNodeUrls - * - * @param configNodeLocations List - * @return TConfigNodeUrls with format TConfigNodeUrl_0,TConfigNodeUrl_1,...,TConfigNodeUrl_n - */ - public static String convertTConfigNodeUrls(List configNodeLocations) { - StringJoiner urls = new StringJoiner(";"); - for (TConfigNodeLocation configNodeLocation : configNodeLocations) { - urls.add(convertTConfigNodeUrl(configNodeLocation)); - } - return urls.toString(); - } - - /** - * Parse TConfigNodeLocation from given TConfigNodeUrl - * - * @param configNodeUrl InternalEndPointUrl,ConsensusEndPointUrl - * @return TConfigNodeLocation - * @throws BadNodeUrlException Throw when unable to parse - */ - public static TConfigNodeLocation parseTConfigNodeUrl(String configNodeUrl) - throws BadNodeUrlException { - String[] split = configNodeUrl.split(","); - if (split.length != 3) { - logger.warn("Bad ConfigNode url: {}", configNodeUrl); - throw new BadNodeUrlException(String.format("Bad node url: %s", configNodeUrl)); - } - return new TConfigNodeLocation( - Integer.parseInt(split[0]), - UrlUtils.parseTEndPointIpv4AndIpv6Url(split[1]), - UrlUtils.parseTEndPointIpv4AndIpv6Url(split[2])); - } - - /** - * Parse TConfigNodeLocations from given TConfigNodeUrls - * - * @param configNodeUrls List - * @return List - * @throws BadNodeUrlException Throw when unable to parse - */ - public static List parseTConfigNodeUrls(List configNodeUrls) - throws BadNodeUrlException { - List result = new ArrayList<>(); - for (String url : configNodeUrls) { - result.add(parseTConfigNodeUrl(url)); - } - return result; - } - - /** - * Parse TConfigNodeLocations from given TConfigNodeUrls - * - * @param configNodeUrls TConfigNodeUrls - * @return List - * @throws BadNodeUrlException Throw when unable to parse - */ - public static List parseTConfigNodeUrls(String configNodeUrls) - throws BadNodeUrlException { - return parseTConfigNodeUrls(Arrays.asList(configNodeUrls.split(";"))); - } - - /** - * Detect whether the given addresses or host names(may contain both) point to the node itself. - * - * @param addressesOrHostNames List of the addresses or host name to check - * @return true if one of the given strings point to the node itself - * @throws UnknownHostException Throw when unable to parse the given addresses or host names - */ - public static boolean containsLocalAddress(List addressesOrHostNames) - throws UnknownHostException { - if (addressesOrHostNames == null) { - return false; - } - - Set selfAddresses = getAllLocalAddresses(); - - for (String addressOrHostName : addressesOrHostNames) { - if (addressOrHostName == null) { - continue; - } - // Unify address or hostName, converting them to addresses - Set translatedAddresses = - Arrays.stream(InetAddress.getAllByName(addressOrHostName)) - .map(InetAddress::getHostAddress) - .collect(Collectors.toCollection(HashSet::new)); - translatedAddresses.retainAll(selfAddresses); - - if (!translatedAddresses.isEmpty()) { - return true; - } - } - - return false; - } - - /** - * Return all the internal, external, IPv4, IPv6 and loopback addresses(without hostname) of the - * node - * - * @return the local addresses set - * @throws UnknownHostException Throw when unable to self addresses or host names (Normally not - * thrown) - */ - public static Set getAllLocalAddresses() throws UnknownHostException { - // Check internal and external, IPv4 and IPv6 network addresses - Set selfAddresses = - Arrays.stream(InetAddress.getAllByName(InetAddress.getLocalHost().getHostName())) - .map(InetAddress::getHostAddress) - .collect(Collectors.toCollection(HashSet::new)); - // Check IPv4 and IPv6 loopback addresses 127.0.0.1 and 0.0.0.0.0.0.0.1 - selfAddresses.addAll( - Arrays.stream(InetAddress.getAllByName(LOOPBACK_HOST_NAME)) - .map(InetAddress::getHostAddress) - .collect(Collectors.toCollection(HashSet::new))); - // Check general address 0.0.0.0 - selfAddresses.add(WILD_CARD_ADDRESS); - - return selfAddresses; - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathDeserializeUtil.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathDeserializeUtil.java deleted file mode 100644 index f6c69056..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathDeserializeUtil.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.utils; - -import org.apache.iotdb.collector.plugin.builtin.sink.path.AlignedPath; -import org.apache.iotdb.collector.plugin.builtin.sink.path.MeasurementPath; -import org.apache.iotdb.collector.plugin.builtin.sink.path.PartialPath; - -import org.apache.tsfile.read.common.Path; - -import java.nio.ByteBuffer; - -public class PathDeserializeUtil { - - public static Path deserialize(ByteBuffer buffer) { - byte pathType = buffer.get(); - switch (pathType) { - case 0: - return MeasurementPath.deserialize(buffer); - case 1: - return AlignedPath.deserialize(buffer); - case 2: - return PartialPath.deserialize(buffer); - case 3: - return Path.deserialize(buffer); - default: - throw new IllegalArgumentException("Invalid path type: " + pathType); - } - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathUtils.java deleted file mode 100644 index b9513bdd..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PathUtils.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.iotdb.collector.plugin.builtin.sink.utils; - -import org.apache.iotdb.collector.plugin.builtin.sink.constant.IoTDBConstant; - -import org.apache.tsfile.common.constant.TsFileConstant; -import org.apache.tsfile.exception.PathParseException; -import org.apache.tsfile.file.metadata.IDeviceID; -import org.apache.tsfile.read.common.parser.PathNodesGenerator; -import org.apache.tsfile.read.common.parser.PathVisitor; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class PathUtils { - - /** - * @param path the path will split. ex, root.ln. - * @return string array. ex, [root, ln] - * @throws IllegalPathException if path isn't correct, the exception will throw - */ - public static String[] splitPathToDetachedNodes(String path) throws IllegalPathException { - if ("".equals(path)) { - return new String[] {}; - } - try { - return PathNodesGenerator.splitPathToNodes(path); - } catch (PathParseException e) { - throw new IllegalPathException(path); - } - } - - public static String[] isLegalPath(String path) throws IllegalPathException { - try { - return PathNodesGenerator.splitPathToNodes(path); - } catch (PathParseException e) { - throw new IllegalPathException(path); - } - } - - /** - * check whether measurement is legal according to syntax convention. Measurement can only be a - * single node name. The returned list is updated, could be different from the original list. - */ - public static List> checkIsLegalSingleMeasurementListsAndUpdate( - List> measurementLists) throws MetadataException { - if (measurementLists == null) { - return null; - } - // skip checking duplicated measurements - Map checkedMeasurements = new HashMap<>(); - List> res = new ArrayList<>(); - for (List measurements : measurementLists) { - res.add(checkLegalSingleMeasurementsAndSkipDuplicate(measurements, checkedMeasurements)); - } - return res; - } - - /** - * check whether measurement is legal according to syntax convention. Measurement can only be a - * single node name, use set to skip checking duplicated measurements - */ - public static List checkLegalSingleMeasurementsAndSkipDuplicate( - List measurements, Map checkedMeasurements) throws MetadataException { - if (measurements == null) { - return null; - } - List res = new ArrayList<>(); - for (String measurement : measurements) { - if (measurement == null) { - res.add(null); - continue; - } - String checked = checkedMeasurements.get(measurement); - if (checked == null) { - checked = checkAndReturnSingleMeasurement(measurement); - checkedMeasurements.put(measurement, checked); - } - res.add(checked); - } - return res; - } - - /** - * check whether measurement is legal according to syntax convention. Measurement can only be a - * single node name. - */ - public static List checkIsLegalSingleMeasurementsAndUpdate(List measurements) - throws MetadataException { - if (measurements == null) { - return null; - } - List res = new ArrayList<>(); - for (String measurement : measurements) { - if (measurement == null) { - continue; - } - res.add(checkAndReturnSingleMeasurement(measurement)); - } - return res; - } - - /** - * check whether measurement is legal according to syntax convention measurement could be like a.b - * (more than one node name), in template? - */ - public static List> checkIsLegalMeasurementListsAndUpdate( - List> measurementLists) throws IllegalPathException { - if (measurementLists == null) { - return null; - } - List> res = new ArrayList<>(); - for (List measurementList : measurementLists) { - res.add(checkIsLegalMeasurementsAndUpdate(measurementList)); - } - return res; - } - - /** - * check whether measurement is legal according to syntax convention measurement could be like a.b - * (more than one node name), in template? - */ - public static List checkIsLegalMeasurementsAndUpdate(List measurements) - throws IllegalPathException { - if (measurements == null) { - return null; - } - List res = new ArrayList<>(); - for (String measurement : measurements) { - if (measurement != null) { - res.add(PathUtils.isLegalPath(measurement)[0]); - } - } - return res; - } - - /** check a measurement and update it if needed to. for example: `sd` -> sd */ - public static String checkAndReturnSingleMeasurement(String measurement) - throws IllegalPathException { - if (measurement == null) { - return null; - } - if (measurement.startsWith(TsFileConstant.BACK_QUOTE_STRING) - && measurement.endsWith(TsFileConstant.BACK_QUOTE_STRING)) { - if (checkBackQuotes(measurement.substring(1, measurement.length() - 1))) { - return removeBackQuotesIfNecessary(measurement); - } else { - throw new IllegalPathException(measurement); - } - } - if (IoTDBConstant.reservedWords.contains(measurement.toUpperCase()) - || isRealNumber(measurement) - || !TsFileConstant.NODE_NAME_PATTERN.matcher(measurement).matches()) { - throw new IllegalPathException(measurement); - } - return measurement; - } - - /** Return true if the str is a real number. Examples: 1.0; +1.0; -1.0; 0011; 011e3; +23e-3 */ - public static boolean isRealNumber(final String str) { - return PathVisitor.isRealNumber(str); - } - - public static boolean isStartWith(final IDeviceID deviceID, final String storageGroup) { - return deviceID.segmentNum() > 0 && deviceID.matchDatabaseName(storageGroup); - } - - /** Remove the back quotes of a measurement if necessary */ - public static String removeBackQuotesIfNecessary(String measurement) { - String unWrapped = measurement.substring(1, measurement.length() - 1); - if (PathUtils.isRealNumber(unWrapped) - || !TsFileConstant.IDENTIFIER_PATTERN.matcher(unWrapped).matches()) { - return measurement; - } else { - return unWrapped; - } - } - - private static boolean checkBackQuotes(final String src) { - int num = src.length() - src.replace("`", "").length(); - if (num % 2 == 1) { - return false; - } - return src.length() == (src.replace("``", "").length() + num); - } - - // In tree model, the "root" shall not be a database name - // Thus we imply that "root" is a db name in table model - public static String qualifyDatabaseName(String databaseName) { - if (databaseName != null && !databaseName.startsWith("root.")) { - databaseName = "root." + databaseName; - } - return databaseName; - } - - public static String unQualifyDatabaseName(String databaseName) { - if (databaseName != null && databaseName.startsWith("root.")) { - databaseName = databaseName.substring(5).toLowerCase(); - } - return databaseName; - } - - public static boolean isTableModelDatabase(final String databaseName) { - return !databaseName.startsWith("root."); - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PipeMemoryWeightUtil.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PipeMemoryWeightUtil.java deleted file mode 100644 index eb75e5e3..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/PipeMemoryWeightUtil.java +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.utils; - -import org.apache.iotdb.collector.config.PipeOptions; -import org.apache.iotdb.collector.plugin.builtin.sink.event.row.PipeRow; - -import org.apache.tsfile.enums.TSDataType; -import org.apache.tsfile.file.metadata.AbstractAlignedChunkMetadata; -import org.apache.tsfile.file.metadata.ChunkMetadata; -import org.apache.tsfile.file.metadata.IChunkMetadata; -import org.apache.tsfile.file.metadata.IDeviceID; -import org.apache.tsfile.read.common.BatchData; -import org.apache.tsfile.read.common.Chunk; -import org.apache.tsfile.read.common.Field; -import org.apache.tsfile.read.common.RowRecord; -import org.apache.tsfile.utils.Binary; -import org.apache.tsfile.utils.BitMap; -import org.apache.tsfile.utils.Pair; -import org.apache.tsfile.utils.TsPrimitiveType; -import org.apache.tsfile.write.record.Tablet; -import org.apache.tsfile.write.schema.IMeasurementSchema; - -import java.util.List; -import java.util.Map; - -public class PipeMemoryWeightUtil { - - /** Estimates memory usage of a {@link Map}<{@link IDeviceID}, {@link Boolean}>. */ - public static long memoryOfIDeviceId2Bool(Map map) { - long usageInBytes = 0L; - for (Map.Entry entry : map.entrySet()) { - usageInBytes = usageInBytes + entry.getKey().ramBytesUsed() + 1L; - } - return usageInBytes + 16L; // add the overhead of map - } - - /** Estimates memory usage of a {@link Map}<{@link String}, {@link TSDataType}>. */ - public static long memoryOfStr2TSDataType(Map map) { - long usageInBytes = 0L; - for (Map.Entry entry : map.entrySet()) { - usageInBytes = usageInBytes + MemUtils.getStringMem(entry.getKey()) + 4L; - } - return usageInBytes + 16L; // add the overhead of map - } - - /** Estimates memory usage of a {@link Map}<{@link IDeviceID}, {@link List}<{@link String}>>. */ - public static long memoryOfIDeviceID2StrList(Map> map) { - long usageInBytes = 0L; - for (Map.Entry> entry : map.entrySet()) { - usageInBytes += entry.getKey().ramBytesUsed(); - for (String str : entry.getValue()) { - usageInBytes += MemUtils.getStringMem(str); - } - } - return usageInBytes + 16L; // add the overhead of map - } - - /** - * Given a row of a tablet, calculate the row count and memory cost of the pipe tablet that will - * be constructed according to config. - * - * @return left is the row count of tablet, right is the memory cost of tablet in bytes - */ - public static Pair calculateTabletRowCountAndMemory(RowRecord row) { - int totalSizeInBytes = 0; - - // timestamp - totalSizeInBytes += 8L; - - // values - final List fields = row.getFields(); - int schemaCount = 0; - if (fields != null) { - schemaCount = fields.size(); - for (final Field field : fields) { - if (field == null) { - continue; - } - - final TSDataType tsDataType = field.getDataType(); - if (tsDataType == null) { - continue; - } - - if (tsDataType.isBinary()) { - final Binary binary = field.getBinaryV(); - totalSizeInBytes += binary == null ? 0 : binary.getLength(); - } else { - totalSizeInBytes += tsDataType.getDataTypeSize(); - } - } - } - - return calculateTabletRowCountAndMemoryBySize(totalSizeInBytes, schemaCount); - } - - /** - * Given a BatchData, calculate the row count and memory cost of the pipe tablet that will be - * constructed according to config. - * - * @return left is the row count of tablet, right is the memory cost of tablet in bytes - */ - public static Pair calculateTabletRowCountAndMemory(BatchData batchData) { - int totalSizeInBytes = 0; - int schemaCount = 0; - - // timestamp - totalSizeInBytes += 8L; - - // values - final TSDataType type = batchData.getDataType(); - if (type != null) { - if (type == TSDataType.VECTOR && batchData.getVector() != null) { - schemaCount = batchData.getVector().length; - for (int i = 0; i < schemaCount; ++i) { - final TsPrimitiveType primitiveType = batchData.getVector()[i]; - if (primitiveType == null || primitiveType.getDataType() == null) { - continue; - } - - if (primitiveType.getDataType().isBinary()) { - final Binary binary = primitiveType.getBinary(); - totalSizeInBytes += binary == null ? 0 : binary.getLength(); - } else { - totalSizeInBytes += primitiveType.getDataType().getDataTypeSize(); - } - } - } else { - schemaCount = 1; - if (type.isBinary()) { - final Binary binary = batchData.getBinary(); - totalSizeInBytes += binary == null ? 0 : binary.getLength(); - } else { - totalSizeInBytes += type.getDataTypeSize(); - } - } - } - - return calculateTabletRowCountAndMemoryBySize(totalSizeInBytes, schemaCount); - } - - /** - * Given a row of a tablet, calculate the row count and memory cost of the pipe tablet that will - * be constructed according to config. - * - * @return left is the row count of tablet, right is the memory cost of tablet in bytes - */ - public static Pair calculateTabletRowCountAndMemory(PipeRow row) { - return calculateTabletRowCountAndMemoryBySize(row.getCurrentRowSize(), row.size()); - } - - private static Pair calculateTabletRowCountAndMemoryBySize( - int rowSize, int schemaCount) { - if (rowSize <= 0) { - return new Pair<>(1, 0); - } - - // Calculate row number according to the max size of a pipe tablet. - // "-100" is the estimated size of other data structures in a pipe tablet. - // "*8" converts bytes to bits, because the bitmap size is 1 bit per schema. - int rowNumber = - 8 - * (PipeOptions.PIPE_DATA_STRUCTURE_TABLET_SIZE_IN_BYTES.value() - 100) - / (8 * rowSize + schemaCount); - rowNumber = Math.max(1, rowNumber); - - if ( // This means the row number is larger than the max row count of a pipe tablet - rowNumber > PipeOptions.PIPE_DATA_STRUCTURE_TABLET_ROW_SIZE.value()) { - // Bound the row number, the memory cost is rowSize * rowNumber - return new Pair<>( - PipeOptions.PIPE_DATA_STRUCTURE_TABLET_ROW_SIZE.value(), - rowSize * PipeOptions.PIPE_DATA_STRUCTURE_TABLET_ROW_SIZE.value()); - } else { - return new Pair<>(rowNumber, PipeOptions.PIPE_DATA_STRUCTURE_TABLET_SIZE_IN_BYTES.value()); - } - } - - public static long calculateTabletSizeInBytes(Tablet tablet) { - long totalSizeInBytes = 0; - - if (tablet == null) { - return totalSizeInBytes; - } - - long[] timestamps = tablet.getTimestamps(); - Object[] tabletValues = tablet.getValues(); - - // timestamps - if (timestamps != null) { - totalSizeInBytes += timestamps.length * 8L; - } - - // values - final List timeseries = tablet.getSchemas(); - if (timeseries != null) { - for (int column = 0; column < timeseries.size(); column++) { - final IMeasurementSchema measurementSchema = timeseries.get(column); - if (measurementSchema == null) { - continue; - } - - final TSDataType tsDataType = measurementSchema.getType(); - if (tsDataType == null) { - continue; - } - - if (tsDataType.isBinary()) { - if (tabletValues == null || tabletValues.length <= column) { - continue; - } - final Binary[] values = ((Binary[]) tabletValues[column]); - if (values == null) { - continue; - } - for (Binary value : values) { - totalSizeInBytes += - value == null ? 0 : (value.getLength() == -1 ? 0 : value.getLength()); - } - } else { - totalSizeInBytes += (long) timestamps.length * tsDataType.getDataTypeSize(); - } - } - } - - // bitMaps - BitMap[] bitMaps = tablet.getBitMaps(); - if (bitMaps != null) { - for (int i = 0; i < bitMaps.length; i++) { - totalSizeInBytes += bitMaps[i] == null ? 0 : bitMaps[i].getSize(); - } - } - - // estimate other dataStructures size - totalSizeInBytes += 100; - - return totalSizeInBytes; - } - - // public static long calculateTableSchemaBytesUsed(TableSchema tableSchema) { - // long totalSizeInBytes = 0; - // - // final String tableName = tableSchema.getTableName(); - // if (tableName != null) { - // totalSizeInBytes += tableName.length(); - // } - // - // final List measurementSchemas = tableSchema.getColumnSchemas(); - // if (measurementSchemas != null) { - // totalSizeInBytes += - // NUM_BYTES_ARRAY_HEADER + (long) NUM_BYTES_OBJECT_REF * measurementSchemas.size(); - // for (IMeasurementSchema measurementSchema : measurementSchemas) { - // InsertNodeMemoryEstimator.sizeOfMeasurementSchema((MeasurementSchema) measurementSchema); - // } - // } - // - // final List categories = tableSchema.getColumnTypes(); - // if (categories != null) { - // totalSizeInBytes += - // alignObjectSize( - // (long) NUM_BYTES_ARRAY_HEADER - // + (long) NUM_BYTES_OBJECT_REF * (long) categories.size()); - // } - // - // return totalSizeInBytes; - // } - - public static int calculateBatchDataRamBytesUsed(BatchData batchData) { - int totalSizeInBytes = 0; - - // timestamp - totalSizeInBytes += 8; - - // values - final TSDataType type = batchData.getDataType(); - if (type != null) { - if (type == TSDataType.VECTOR && batchData.getVector() != null) { - for (int i = 0; i < batchData.getVector().length; ++i) { - final TsPrimitiveType primitiveType = batchData.getVector()[i]; - if (primitiveType == null || primitiveType.getDataType() == null) { - continue; - } - // consider variable references (plus 8) and memory alignment (round up to 8) - totalSizeInBytes += roundUpToMultiple(primitiveType.getSize() + 8, 8); - } - } else { - if (type.isBinary()) { - final Binary binary = batchData.getBinary(); - // refer to org.apache.tsfile.utils.TsPrimitiveType.TsBinary.getSize - totalSizeInBytes += - roundUpToMultiple((binary == null ? 8 : binary.getLength() + 8) + 8, 8); - } else { - totalSizeInBytes += roundUpToMultiple(TsPrimitiveType.getByType(type).getSize() + 8, 8); - } - } - } - - return batchData.length() * totalSizeInBytes; - } - - public static long calculateChunkRamBytesUsed(Chunk chunk) { - return chunk != null ? chunk.getRetainedSizeInBytes() : 0L; - } - - public static long calculateAlignedChunkMetaBytesUsed( - AbstractAlignedChunkMetadata alignedChunkMetadata) { - if (alignedChunkMetadata == null) { - return 0L; - } - - final ChunkMetadata timeChunkMetadata = - (ChunkMetadata) alignedChunkMetadata.getTimeChunkMetadata(); - final List valueChunkMetadataList = - alignedChunkMetadata.getValueChunkMetadataList(); - - long size = timeChunkMetadata != null ? timeChunkMetadata.getRetainedSizeInBytes() : 0; - if (valueChunkMetadataList != null && !valueChunkMetadataList.isEmpty()) { - for (IChunkMetadata valueChunkMetadata : valueChunkMetadataList) { - if (valueChunkMetadata != null) { - size += ((ChunkMetadata) valueChunkMetadata).getRetainedSizeInBytes(); - } - } - } - - return size; - } - - /** - * Rounds up the given integer num to the nearest multiple of n. - * - * @param num The integer to be rounded up. - * @param n The specified multiple. - * @return The nearest multiple of n greater than or equal to num. - */ - private static int roundUpToMultiple(int num, int n) { - if (n == 0) { - throw new IllegalArgumentException("The multiple n must be greater than 0"); - } - // Calculate the rounded up value to the nearest multiple of n - return ((num + n - 1) / n) * n; - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/RetryUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/RetryUtils.java deleted file mode 100644 index e113a4c7..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/RetryUtils.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.utils; - -public class RetryUtils { - - public interface CallableWithException { - T call() throws E; - } - - public static final int MAX_RETRIES = 3; - - public static T retryOnException( - final CallableWithException callable) throws E { - int attempt = 0; - while (true) { - try { - return callable.call(); - } catch (Exception e) { - attempt++; - if (attempt >= MAX_RETRIES) { - throw e; - } - } - } - } - - private RetryUtils() { - // utility class - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TestOnly.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TestOnly.java deleted file mode 100644 index 17d55085..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TestOnly.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.utils; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * TestOnly implies that the class or method should only be used in the tests, otherwise its - * functionality is not guaranteed and may interfere with the normal code. - */ -@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE}) -@Retention(RetentionPolicy.CLASS) -public @interface TestOnly {} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TimestampPrecisionUtils.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TimestampPrecisionUtils.java deleted file mode 100644 index 7bbc6ab7..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TimestampPrecisionUtils.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.iotdb.collector.plugin.builtin.sink.utils; - -import org.apache.iotdb.collector.config.PipeOptions; - -import java.util.concurrent.TimeUnit; - -public class TimestampPrecisionUtils { - public static String TIMESTAMP_PRECISION = PipeOptions.TIMESTAMP_PRECISION.value(); - - @FunctionalInterface - private interface ConvertFunction { - R apply(T1 t1, T2 t2); - } - - private static final ConvertFunction convertToCurrPrecisionFunction; - public static final TimeUnit currPrecision; - - static { - switch (TIMESTAMP_PRECISION) { - case "ms": - convertToCurrPrecisionFunction = TimeUnit.MILLISECONDS::convert; - currPrecision = TimeUnit.MILLISECONDS; - break; - case "us": - convertToCurrPrecisionFunction = TimeUnit.MICROSECONDS::convert; - currPrecision = TimeUnit.MICROSECONDS; - break; - case "ns": - convertToCurrPrecisionFunction = TimeUnit.NANOSECONDS::convert; - currPrecision = TimeUnit.NANOSECONDS; - break; - // this case will never reach - default: - throw new UnsupportedOperationException( - "not supported time_precision: " + TIMESTAMP_PRECISION); - } - } - - /** convert specific precision timestamp to current precision timestamp */ - public static long convertToCurrPrecision(long sourceTime, TimeUnit sourceUnit) { - return convertToCurrPrecisionFunction.apply(sourceTime, sourceUnit); - } - - /** check whether the input timestamp match the current system timestamp precision. */ - public static void checkTimestampPrecision(long time) { - final boolean isTimestampPrecisionCheckEnabled = - PipeOptions.TIMESTAMP_PRECISION_CHECK_ENABLED.value(); - if (!isTimestampPrecisionCheckEnabled) { - return; - } - switch (TIMESTAMP_PRECISION) { - case "ms": - if (time > 10_000_000_000_000L || time < -10_000_000_000_000L) { - throw new SemanticException( - String.format( - "The timestamp is unexpectedly large, you may forget to set the timestamp precision." - + "Current system timestamp precision is %s, " - + "please check whether the timestamp %s is correct." - + "If you insist to insert this timestamp, please set timestamp_precision_check_enabled=false and restart the server.", - TIMESTAMP_PRECISION, time)); - } - break; - case "us": - if (time > 10_000_000_000_000_000L || time < -10_000_000_000_000L) { - throw new SemanticException( - String.format( - "The timestamp is unexpectedly large, you may forget to set the timestamp precision." - + "Current system timestamp precision is %s, " - + "please check whether the timestamp %s is correct." - + "If you insist to insert this timestamp, please set timestamp_precision_check_enabled=false and restart the server.", - TIMESTAMP_PRECISION, time)); - } - break; - // Long.MaxValue is 19 digits, therefore no problem when the precision is ns. - case "ns": - default: - break; - } - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TsTableColumnCategory.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TsTableColumnCategory.java deleted file mode 100644 index 512be69f..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/utils/TsTableColumnCategory.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.utils; - -import org.apache.tsfile.utils.ReadWriteIOUtils; -import org.apache.tsfile.write.record.Tablet.ColumnCategory; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -public enum TsTableColumnCategory { - TAG((byte) 0), - ATTRIBUTE((byte) 1), - TIME((byte) 2), - FIELD((byte) 3); - - private final byte category; - - TsTableColumnCategory(byte category) { - this.category = category; - } - - byte getValue() { - return category; - } - - public void serialize(OutputStream stream) throws IOException { - ReadWriteIOUtils.write(category, stream); - } - - public void serialize(ByteBuffer byteBuffer) { - ReadWriteIOUtils.write(category, byteBuffer); - } - - public static TsTableColumnCategory deserialize(InputStream stream) throws IOException { - byte category = (byte) stream.read(); - return deserialize(category); - } - - public static TsTableColumnCategory deserialize(ByteBuffer stream) { - byte category = stream.get(); - return deserialize(category); - } - - public static TsTableColumnCategory deserialize(byte category) { - switch (category) { - case 0: - return TAG; - case 1: - return ATTRIBUTE; - case 2: - return TIME; - case 3: - return FIELD; - default: - throw new IllegalArgumentException(); - } - } - - public ColumnCategory toTsFileColumnType() { - switch (this) { - case TAG: - return ColumnCategory.TAG; - case ATTRIBUTE: - return ColumnCategory.ATTRIBUTE; - case FIELD: - return ColumnCategory.FIELD; - default: - throw new IllegalArgumentException("Unsupported column type in TsFile: " + this); - } - } - - public static TsTableColumnCategory fromTsFileColumnType(ColumnCategory columnType) { - switch (columnType) { - case FIELD: - return FIELD; - case TAG: - return TAG; - case ATTRIBUTE: - return ATTRIBUTE; - default: - throw new IllegalArgumentException("Unknown column type: " + columnType); - } - } - - public byte getCategory() { - return category; - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/constructor/SinkConstructor.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/constructor/SinkConstructor.java index 7f223b08..5e24cfd5 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/constructor/SinkConstructor.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/runtime/plugin/constructor/SinkConstructor.java @@ -20,7 +20,7 @@ package org.apache.iotdb.collector.runtime.plugin.constructor; import org.apache.iotdb.collector.plugin.builtin.BuiltinPlugin; -import org.apache.iotdb.collector.plugin.builtin.sink.protocol.demo.DemoSink; +import org.apache.iotdb.collector.plugin.builtin.sink.protocol.DemoSink; import org.apache.iotdb.collector.runtime.plugin.meta.PluginMetaKeeper; import org.apache.iotdb.pipe.api.PipeSink; import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters; From feeb55c13d815a7920a81ef2420ad9ed50bc25c4 Mon Sep 17 00:00:00 2001 From: Steve Yurong Su Date: Mon, 7 Apr 2025 10:20:00 +0800 Subject: [PATCH 5/5] refactor --- .../event/PipeRawTabletInsertionEvent.java | 8 ++ .../sink/event/PipeTsFileInsertionEvent.java | 4 + .../builtin/sink/protocol/IoTDBConnector.java | 30 ++--- .../protocol/IoTDBDataNodeSyncConnector.java | 104 --------------- .../IoTDBDataRegionSyncConnector.java | 124 +++++++++--------- .../sink/protocol/IoTDBSslSyncConnector.java | 5 +- .../protocol/PipeReceiverStatusHandler.java | 55 +------- 7 files changed, 93 insertions(+), 237 deletions(-) delete mode 100644 iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataNodeSyncConnector.java diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/PipeRawTabletInsertionEvent.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/PipeRawTabletInsertionEvent.java index c6a9e278..3ea3452d 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/PipeRawTabletInsertionEvent.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/PipeRawTabletInsertionEvent.java @@ -45,6 +45,10 @@ public Iterable processTablet( throw new UnsupportedOperationException(); } + public Tablet getTablet() { + return tablet; + } + public boolean isAligned() { return isAligned; } @@ -53,6 +57,10 @@ public boolean isTableModelEvent() { return false; } + public String getDeviceId() { + return null; + } + public String getTableModelDatabaseName() { return null; } diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/PipeTsFileInsertionEvent.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/PipeTsFileInsertionEvent.java index a1342080..2fc5e9a1 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/PipeTsFileInsertionEvent.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/event/PipeTsFileInsertionEvent.java @@ -39,6 +39,10 @@ public boolean isTableModelEvent() { throw new PipeException(""); } + public File getTsFile() { + return tsFile; + } + @Override public Iterable toTabletInsertionEvents() throws PipeException { return toTabletInsertionEvents(Long.MAX_VALUE); diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBConnector.java index 141bd643..70923143 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBConnector.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBConnector.java @@ -29,6 +29,7 @@ import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator; import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters; import org.apache.iotdb.pipe.api.exception.PipeParameterNotValidException; +import org.apache.iotdb.rpc.UrlUtils; import org.apache.iotdb.service.rpc.thrift.TPipeTransferReq; import org.slf4j.Logger; @@ -368,7 +369,7 @@ protected LinkedHashSet parseNodeUrls(final PipeParameters parameters if (parameters.hasAttribute(CONNECTOR_IOTDB_NODE_URLS_KEY)) { givenNodeUrls.addAll( - NodeUrlUtils.parseTEndPointUrls( + parseTEndPointUrls( Arrays.asList( parameters .getStringByKeys(CONNECTOR_IOTDB_NODE_URLS_KEY) @@ -378,7 +379,7 @@ protected LinkedHashSet parseNodeUrls(final PipeParameters parameters if (parameters.hasAttribute(SINK_IOTDB_NODE_URLS_KEY)) { givenNodeUrls.addAll( - NodeUrlUtils.parseTEndPointUrls( + parseTEndPointUrls( Arrays.asList( parameters .getStringByKeys(SINK_IOTDB_NODE_URLS_KEY) @@ -394,8 +395,19 @@ protected LinkedHashSet parseNodeUrls(final PipeParameters parameters return givenNodeUrls; } + + private static List parseTEndPointUrls(List endPointUrls) { + if (endPointUrls == null) { + throw new NumberFormatException("endPointUrls is null"); + } + List result = new ArrayList<>(); + for (String url : endPointUrls) { + result.add(UrlUtils.parseTEndPointIpv4AndIpv6Url(url)); + } + return result; + } - private void checkNodeUrls(final Set nodeUrls) throws PipeParameterNotValidException { + private static void checkNodeUrls(final Set nodeUrls) throws PipeParameterNotValidException { for (final TEndPoint nodeUrl : nodeUrls) { if (Objects.isNull(nodeUrl.ip) || nodeUrl.ip.isEmpty()) { LOGGER.warn(PARSE_URL_ERROR_FORMATTER, "host cannot be empty"); @@ -413,16 +425,4 @@ protected TPipeTransferReq compressIfNeeded(final TPipeTransferReq req) throws I ? PipeTransferCompressedReq.toTPipeTransferReq(req, compressors) : req; } - - public boolean isRpcCompressionEnabled() { - return isRpcCompressionEnabled; - } - - public List getCompressors() { - return compressors; - } - - public PipeReceiverStatusHandler statusHandler() { - return receiverStatusHandler; - } } diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataNodeSyncConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataNodeSyncConnector.java deleted file mode 100644 index d7c0a777..00000000 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataNodeSyncConnector.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.iotdb.collector.plugin.builtin.sink.protocol; - -import org.apache.iotdb.collector.config.PipeOptions; -import org.apache.iotdb.collector.plugin.builtin.sink.client.IoTDBDataNodeSyncClientManager; -import org.apache.iotdb.collector.plugin.builtin.sink.client.IoTDBSyncClientManager; -import org.apache.iotdb.common.rpc.thrift.TEndPoint; -import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.UnknownHostException; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -public abstract class IoTDBDataNodeSyncConnector extends IoTDBSslSyncConnector { - - private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBDataNodeSyncConnector.class); - - protected IoTDBDataNodeSyncClientManager clientManager; - - @Override - public void validate(final PipeParameterValidator validator) throws Exception { - super.validate(validator); - - final Set givenNodeUrls = parseNodeUrls(validator.getParameters()); - - validator.validate( - empty -> { - try { - // Ensure the sink doesn't point to the thrift receiver on DataNode itself - return !NodeUrlUtils.containsLocalAddress( - givenNodeUrls.stream() - .filter(tEndPoint -> tEndPoint.getPort() == PipeOptions.RPC_PORT.value()) - .map(TEndPoint::getIp) - .collect(Collectors.toList())); - } catch (final UnknownHostException e) { - LOGGER.warn("Unknown host when checking pipe sink IP.", e); - return false; - } - }, - String.format( - "One of the endpoints %s of the receivers is pointing back to the thrift receiver %s on sender itself, " - + "or unknown host when checking pipe sink IP.", - givenNodeUrls, - new TEndPoint(PipeOptions.RPC_ADDRESS.value(), PipeOptions.RPC_PORT.value()))); - } - - @Override - protected IoTDBSyncClientManager constructClient( - final List nodeUrls, - final boolean useSSL, - final String trustStorePath, - final String trustStorePwd, - /* The following parameters are used locally. */ - final boolean useLeaderCache, - final String loadBalanceStrategy, - /* The following parameters are used to handshake with the receiver. */ - final String username, - final String password, - final boolean shouldReceiverConvertOnTypeMismatch, - final String loadTsFileStrategy, - final boolean validateTsFile, - final boolean shouldMarkAsPipeRequest) { - clientManager = - new IoTDBDataNodeSyncClientManager( - nodeUrls, - useSSL, - Objects.nonNull(trustStorePath) - ? /*IoTDBConfig.addDataHomeDir(trustStorePath)*/ "" - : null, - trustStorePwd, - useLeaderCache, - loadBalanceStrategy, - username, - password, - shouldReceiverConvertOnTypeMismatch, - loadTsFileStrategy, - validateTsFile, - shouldMarkAsPipeRequest); - return clientManager; - } -} diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataRegionSyncConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataRegionSyncConnector.java index 9380757a..04b9844b 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataRegionSyncConnector.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBDataRegionSyncConnector.java @@ -19,7 +19,9 @@ package org.apache.iotdb.collector.plugin.builtin.sink.protocol; +import org.apache.iotdb.collector.plugin.builtin.sink.client.IoTDBDataNodeSyncClientManager; import org.apache.iotdb.collector.plugin.builtin.sink.client.IoTDBSyncClient; +import org.apache.iotdb.collector.plugin.builtin.sink.client.IoTDBSyncClientManager; import org.apache.iotdb.collector.plugin.builtin.sink.event.PipeRawTabletInsertionEvent; import org.apache.iotdb.collector.plugin.builtin.sink.event.PipeTsFileInsertionEvent; import org.apache.iotdb.collector.plugin.builtin.sink.payload.evolvable.batch.PipeTabletEventBatch; @@ -59,12 +61,49 @@ import java.util.Map; import java.util.Objects; -public class IoTDBDataRegionSyncConnector extends IoTDBDataNodeSyncConnector { +public class IoTDBDataRegionSyncConnector extends IoTDBSslSyncConnector { private static final Logger LOGGER = LoggerFactory.getLogger(IoTDBDataRegionSyncConnector.class); private PipeTransferBatchReqBuilder tabletBatchBuilder; + protected IoTDBDataNodeSyncClientManager clientManager; + + @Override + protected IoTDBSyncClientManager constructClient( + final List nodeUrls, + final boolean useSSL, + final String trustStorePath, + final String trustStorePwd, + /* The following parameters are used locally. */ + final boolean useLeaderCache, + final String loadBalanceStrategy, + /* The following parameters are used to handshake with the receiver. */ + final String username, + final String password, + final boolean shouldReceiverConvertOnTypeMismatch, + final String loadTsFileStrategy, + final boolean validateTsFile, + final boolean shouldMarkAsPipeRequest) { + clientManager = + new IoTDBDataNodeSyncClientManager( + nodeUrls, + useSSL, + Objects.nonNull(trustStorePath) + ? /*IoTDBConfig.addDataHomeDir(trustStorePath)*/ "" + : null, + trustStorePwd, + useLeaderCache, + loadBalanceStrategy, + username, + password, + shouldReceiverConvertOnTypeMismatch, + loadTsFileStrategy, + validateTsFile, + shouldMarkAsPipeRequest); + return clientManager; + } + @Override public void customize( final PipeParameters parameters, final PipeConnectorRuntimeConfiguration configuration) @@ -91,59 +130,25 @@ protected PipeTransferFilePieceReq getTransferMultiFilePieceReq( @Override public void transfer(final TabletInsertionEvent tabletInsertionEvent) throws Exception { - // PipeProcessor can change the type of TabletInsertionEvent - if (!(tabletInsertionEvent instanceof PipeRawTabletInsertionEvent)) { - LOGGER.warn( - "IoTDBThriftSyncConnector only support " + "PipeRawTabletInsertionEvent. " + "Ignore {}.", - tabletInsertionEvent); - return; - } - - try { - if (isTabletBatchModeEnabled) { - final Pair endPointAndBatch = - tabletBatchBuilder.onEvent(tabletInsertionEvent); - if (Objects.nonNull(endPointAndBatch)) { - doTransferWrapper(endPointAndBatch); - } - } else { - doTransferWrapper((PipeRawTabletInsertionEvent) tabletInsertionEvent); + if (isTabletBatchModeEnabled) { + final Pair endPointAndBatch = + tabletBatchBuilder.onEvent(tabletInsertionEvent); + if (Objects.nonNull(endPointAndBatch)) { + doTransferWrapper(endPointAndBatch); } - } catch (final Exception e) { - throw new PipeConnectionException( - String.format( - "Failed to transfer tablet insertion event %s, because %s.", - ((PipeRawTabletInsertionEvent) tabletInsertionEvent).coreReportMessage(), - e.getMessage()), - e); + } else { + doTransferWrapper((PipeRawTabletInsertionEvent) tabletInsertionEvent); } } @Override public void transfer(final TsFileInsertionEvent tsFileInsertionEvent) throws Exception { - // PipeProcessor can change the type of tsFileInsertionEvent - if (!(tsFileInsertionEvent instanceof PipeTsFileInsertionEvent)) { - LOGGER.warn( - "IoTDBThriftSyncConnector only support PipeTsFileInsertionEvent. Ignore {}.", - tsFileInsertionEvent); - return; + // In order to commit in order + if (isTabletBatchModeEnabled && !tabletBatchBuilder.isEmpty()) { + doTransferWrapper(); } - try { - // In order to commit in order - if (isTabletBatchModeEnabled && !tabletBatchBuilder.isEmpty()) { - doTransferWrapper(); - } - - doTransferWrapper((PipeTsFileInsertionEvent) tsFileInsertionEvent); - } catch (final Exception e) { - throw new PipeConnectionException( - String.format( - "Failed to transfer tsfile insertion event %s, because %s.", - ((PipeTsFileInsertionEvent) tsFileInsertionEvent).coreReportMessage(), - e.getMessage()), - e); - } + doTransferWrapper((PipeTsFileInsertionEvent) tsFileInsertionEvent); } @Override @@ -217,13 +222,9 @@ private void doTransfer(final PipeTabletEventTsFileBatch batchToTransfer) final Map, Double> pipe2WeightMap = batchToTransfer.deepCopyPipe2WeightMap(); for (final Pair dbTsFile : dbTsFilePairs) { - doTransfer(pipe2WeightMap, dbTsFile.right, null, dbTsFile.left); + doTransfer(dbTsFile.right, null, dbTsFile.left); try { - RetryUtils.retryOnException( - () -> { - FileUtils.delete(dbTsFile.right); - return null; - }); + FileUtils.delete(dbTsFile.right); } catch (final NoSuchFileException e) { LOGGER.info("The file {} is not found, may already be deleted.", dbTsFile); } catch (final Exception e) { @@ -248,7 +249,7 @@ private void doTransfer(final PipeRawTabletInsertionEvent pipeRawTabletInsertion final TPipeTransferReq req = compressIfNeeded( PipeTransferTabletRawReqV2.toTPipeTransferReq( - pipeRawTabletInsertionEvent.convertToTablet(), + pipeRawTabletInsertionEvent.getTablet(), pipeRawTabletInsertionEvent.isAligned(), pipeRawTabletInsertionEvent.isTableModelEvent() ? pipeRawTabletInsertionEvent.getTableModelDatabaseName() @@ -271,7 +272,7 @@ private void doTransfer(final PipeRawTabletInsertionEvent pipeRawTabletInsertion status, String.format( "Transfer PipeRawTabletInsertionEvent %s error, result status %s", - pipeRawTabletInsertionEvent.coreReportMessage(), status), + pipeRawTabletInsertionEvent, status), pipeRawTabletInsertionEvent.toString()); } if (status.isSetRedirectNode()) { @@ -283,19 +284,14 @@ private void doTransfer(final PipeRawTabletInsertionEvent pipeRawTabletInsertion private void doTransferWrapper(final PipeTsFileInsertionEvent pipeTsFileInsertionEvent) throws PipeException, IOException { doTransfer( - Collections.singletonMap( - new Pair<>( - pipeTsFileInsertionEvent.getPipeName(), pipeTsFileInsertionEvent.getCreationTime()), - 1.0), pipeTsFileInsertionEvent.getTsFile(), - pipeTsFileInsertionEvent.isWithMod() ? pipeTsFileInsertionEvent.getModFile() : null, + null, pipeTsFileInsertionEvent.isTableModelEvent() ? pipeTsFileInsertionEvent.getTableModelDatabaseName() : null); } private void doTransfer( - final Map, Double> pipeName2WeightMap, final File tsFile, final File modFile, final String dataBaseName) @@ -306,8 +302,8 @@ private void doTransfer( // 1. Transfer tsFile, and mod file if exists and receiver's version >= 2 if (Objects.nonNull(modFile) && clientManager.supportModsIfIsDataNodeReceiver()) { - transferFilePieces(pipeName2WeightMap, modFile, clientAndStatus, true); - transferFilePieces(pipeName2WeightMap, tsFile, clientAndStatus, true); + transferFilePieces(modFile, clientAndStatus, true); + transferFilePieces(tsFile, clientAndStatus, true); // 2. Transfer file seal signal with mod, which means the file is transferred completely try { @@ -329,7 +325,7 @@ private void doTransfer( e); } } else { - transferFilePieces(pipeName2WeightMap, tsFile, clientAndStatus, false); + transferFilePieces(tsFile, clientAndStatus, false); // 2. Transfer file seal signal without mod, which means the file is transferred completely try { @@ -362,7 +358,7 @@ private void doTransfer( } @Override - public void close() { + public void close() throws Exception { if (tabletBatchBuilder != null) { tabletBatchBuilder.close(); } diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBSslSyncConnector.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBSslSyncConnector.java index b1920d6c..c49cc2c6 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBSslSyncConnector.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/IoTDBSslSyncConnector.java @@ -163,7 +163,6 @@ public void heartbeat() { } protected void transferFilePieces( - final Map, Double> pipe2WeightMap, final File file, final Pair clientAndStatus, final boolean isMultiFile) @@ -237,11 +236,9 @@ protected abstract PipeTransferFilePieceReq getTransferMultiFilePieceReq( final String fileName, final long position, final byte[] payLoad) throws IOException; @Override - public void close() { + public void close() throws Exception { if (clientManager != null) { clientManager.close(); } - - super.close(); } } diff --git a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/PipeReceiverStatusHandler.java b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/PipeReceiverStatusHandler.java index 477dc641..12f8e090 100644 --- a/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/PipeReceiverStatusHandler.java +++ b/iotdb-collector/collector-core/src/main/java/org/apache/iotdb/collector/plugin/builtin/sink/protocol/PipeReceiverStatusHandler.java @@ -103,7 +103,7 @@ public void handle( case 1808: // PIPE_RECEIVER_TEMPORARY_UNAVAILABLE_EXCEPTION { LOGGER.info("Temporary unavailable exception: will retry forever. status: {}", status); - throw new PipeRuntimeConnectorCriticalException(exceptionMessage); + throw new PipeException(exceptionMessage); } case 1810: // PIPE_RECEIVER_USER_CONFLICT_EXCEPTION @@ -141,11 +141,11 @@ public void handle( + " seconds", status); exceptionEventHasBeenRetried.set(true); - throw new PipeRuntimeConnectorRetryTimesConfigurableException( + throw new PipeException( exceptionMessage, (int) Math.max( - PipeSubtask.MAX_RETRY_TIMES, + 5, Math.min(CONFLICT_RETRY_MAX_TIMES, retryMaxMillisWhenConflictOccurs * 1.1))); } @@ -176,11 +176,11 @@ public void handle( + " seconds", status); exceptionEventHasBeenRetried.set(true); - throw new PipeRuntimeConnectorRetryTimesConfigurableException( + throw new PipeException( exceptionMessage, (int) Math.max( - PipeSubtask.MAX_RETRY_TIMES, + 5, Math.min( CONFLICT_RETRY_MAX_TIMES, retryMaxMillisWhenOtherExceptionsOccur * 1.1))); } @@ -200,49 +200,4 @@ private void resetExceptionStatus() { exceptionEventHasBeenRetried.set(false); exceptionRecordedMessage.set(""); } - - /////////////////////////////// Prior status specifier /////////////////////////////// - - private static final List STATUS_PRIORITY = - Collections.unmodifiableList( - Arrays.asList( - TSStatusCode.SUCCESS_STATUS.getStatusCode(), - TSStatusCode.PIPE_RECEIVER_IDEMPOTENT_CONFLICT_EXCEPTION.getStatusCode(), - TSStatusCode.REDIRECTION_RECOMMEND.getStatusCode(), - TSStatusCode.PIPE_RECEIVER_USER_CONFLICT_EXCEPTION.getStatusCode(), - TSStatusCode.PIPE_RECEIVER_TEMPORARY_UNAVAILABLE_EXCEPTION.getStatusCode())); - - /** - * This method is used to get the highest priority {@link TSStatus} from a list of {@link - * TSStatus}. The priority of each status is determined by its {@link TSStatusCode}, and the - * priority sequence is defined in the {@link #STATUS_PRIORITY} list. - * - *

Specifically, it iterates through the input {@link TSStatus} list. For each {@link - * TSStatus}, if its {@link TSStatusCode} is not in the {@link #STATUS_PRIORITY} list, it directly - * returns this {@link TSStatus}. Otherwise, it compares the current {@link TSStatus} with the - * highest priority {@link TSStatus} found so far (initially set to the {@link - * TSStatusCode#SUCCESS_STATUS}). If the current {@link TSStatus} has a higher priority, it - * updates the highest priority {@link TSStatus} to the current {@link TSStatus}. - * - *

Finally, the method returns the highest priority {@link TSStatus}. - * - * @param givenStatusList a list of {@link TSStatus} from which the highest priority {@link - * TSStatus} is to be found - * @return the highest priority {@link TSStatus} from the input list - */ - public static TSStatus getPriorStatus(final List givenStatusList) { - final TSStatus resultStatus = new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode()); - for (final TSStatus givenStatus : givenStatusList) { - if (!STATUS_PRIORITY.contains(givenStatus.getCode())) { - return givenStatus; - } - - if (STATUS_PRIORITY.indexOf(givenStatus.getCode()) - > STATUS_PRIORITY.indexOf(resultStatus.getCode())) { - resultStatus.setCode(givenStatus.getCode()); - } - } - resultStatus.setSubStatus(givenStatusList); - return resultStatus; - } }