From 341cb2dfc10cc242336b058ea384a86efab4be76 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:53:26 +0800 Subject: [PATCH 01/47] pj --- .../pipe/sink/protocol/opcua/OpcUaSink.java | 26 ++++++++++++++++ .../{ => server}/OpcUaKeyStoreLoader.java | 2 +- .../opcua/{ => server}/OpcUaNameSpace.java | 30 ++++++++++--------- .../{ => server}/OpcUaServerBuilder.java | 18 +++++------ 4 files changed, 52 insertions(+), 24 deletions(-) rename iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/{ => server}/OpcUaKeyStoreLoader.java (98%) rename iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/{ => server}/OpcUaNameSpace.java (94%) rename iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/{ => server}/OpcUaServerBuilder.java (95%) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 7404a8b03bcb..9a505ce90f0c 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -24,6 +24,8 @@ import org.apache.iotdb.db.conf.IoTDBConfig; import org.apache.iotdb.db.pipe.event.common.tablet.PipeInsertNodeTabletInsertionEvent; import org.apache.iotdb.db.pipe.event.common.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaNameSpace; +import org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaServerBuilder; import org.apache.iotdb.db.storageengine.StorageEngine; import org.apache.iotdb.db.storageengine.dataregion.DataRegion; import org.apache.iotdb.pipe.api.PipeConnector; @@ -356,4 +358,28 @@ public void close() throws Exception { } } } + + // Getter + + public boolean isClientServerModel() { + return isClientServerModel; + } + + public String getUnQualifiedDatabaseName() { + return unQualifiedDatabaseName; + } + + public String getPlaceHolder() { + return placeHolder; + } + + @Nullable + public String getValueName() { + return valueName; + } + + @Nullable + public String getQualityName() { + return qualityName; + } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaKeyStoreLoader.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaKeyStoreLoader.java similarity index 98% rename from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaKeyStoreLoader.java rename to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaKeyStoreLoader.java index b17f27532d7a..56b231fb4608 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaKeyStoreLoader.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaKeyStoreLoader.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.iotdb.db.pipe.sink.protocol.opcua; +package org.apache.iotdb.db.pipe.sink.protocol.opcua.server; import org.apache.iotdb.commons.utils.FileUtils; diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaNameSpace.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java similarity index 94% rename from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaNameSpace.java rename to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java index 6850fba8f20d..0a13fc34865a 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaNameSpace.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java @@ -17,10 +17,11 @@ * under the License. */ -package org.apache.iotdb.db.pipe.sink.protocol.opcua; +package org.apache.iotdb.db.pipe.sink.protocol.opcua.server; import org.apache.iotdb.commons.exception.pipe.PipeRuntimeCriticalException; import org.apache.iotdb.commons.exception.pipe.PipeRuntimeNonCriticalException; +import org.apache.iotdb.db.pipe.sink.protocol.opcua.OpcUaSink; import org.apache.iotdb.db.pipe.sink.util.sorter.PipeTableModelTabletEventSorter; import org.apache.iotdb.db.pipe.sink.util.sorter.PipeTreeModelTabletEventSorter; import org.apache.iotdb.db.utils.DateTimeUtils; @@ -72,7 +73,7 @@ public class OpcUaNameSpace extends ManagedNamespaceWithLifecycle { private final SubscriptionModel subscriptionModel; private final OpcUaServerBuilder builder; - OpcUaNameSpace(final OpcUaServer server, final OpcUaServerBuilder builder) { + public OpcUaNameSpace(final OpcUaServer server, final OpcUaServerBuilder builder) { super(server, NAMESPACE_URI); this.builder = builder; @@ -94,9 +95,9 @@ public void shutdown() { }); } - void transfer(final Tablet tablet, final boolean isTableModel, final OpcUaSink sink) + public void transfer(final Tablet tablet, final boolean isTableModel, final OpcUaSink sink) throws UaException { - if (sink.isClientServerModel) { + if (sink.isClientServerModel()) { transferTabletForClientServerModel(tablet, isTableModel, sink); } else { transferTabletForPubSubModel(tablet, isTableModel, sink); @@ -141,11 +142,11 @@ private void transferTabletForClientServerModel( for (int i = 0; i < tablet.getRowSize(); ++i) { final Object[] segments = tablet.getDeviceID(i).getSegments(); final String[] folderSegments = new String[segments.length + 1]; - folderSegments[0] = sink.unQualifiedDatabaseName; + folderSegments[0] = sink.getUnQualifiedDatabaseName(); for (int j = 0; j < segments.length; ++j) { folderSegments[j + 1] = - Objects.isNull(segments[j]) ? sink.placeHolder : (String) segments[j]; + Objects.isNull(segments[j]) ? sink.getPlaceHolder() : (String) segments[j]; } final int finalI = i; @@ -228,7 +229,7 @@ private void transferTabletRowForClientServerModel( final String currentFolder = currentStr.toString(); StatusCode currentQuality = - Objects.isNull(sink.valueName) ? StatusCode.GOOD : StatusCode.UNCERTAIN; + Objects.isNull(sink.getValueName()) ? StatusCode.GOOD : StatusCode.UNCERTAIN; UaVariableNode valueNode = null; Object value = null; long timestamp = 0; @@ -239,7 +240,7 @@ private void transferTabletRowForClientServerModel( } final String name = measurementSchemas.get(i).getMeasurementName(); final TSDataType type = measurementSchemas.get(i).getType(); - if (Objects.nonNull(sink.qualityName) && sink.qualityName.equals(name)) { + if (Objects.nonNull(sink.getQualityName()) && sink.getQualityName().equals(name)) { if (!type.equals(TSDataType.BOOLEAN)) { throw new UnsupportedOperationException( "The quality value only supports boolean type, while true == GOOD and false == BAD."); @@ -247,11 +248,12 @@ private void transferTabletRowForClientServerModel( currentQuality = values.get(i) == Boolean.TRUE ? StatusCode.GOOD : StatusCode.BAD; continue; } - if (Objects.nonNull(sink.valueName) && !sink.valueName.equals(name)) { + if (Objects.nonNull(sink.getValueName()) && !sink.getValueName().equals(name)) { throw new UnsupportedOperationException( "When the 'with-quality' mode is enabled, the measurement must be either \"value-name\" or \"quality-name\""); } - final String nodeName = Objects.isNull(sink.valueName) ? name : segments[segments.length - 1]; + final String nodeName = + Objects.isNull(sink.getValueName()) ? name : segments[segments.length - 1]; final NodeId nodeId = newNodeId(currentFolder + nodeName); final UaVariableNode measurementNode; if (!getNodeManager().containsNode(nodeId)) { @@ -288,7 +290,7 @@ private void transferTabletRowForClientServerModel( } final long utcTimestamp = timestampToUtc(timestamps.get(timestamps.size() > 1 ? i : 0)); - if (Objects.isNull(sink.valueName)) { + if (Objects.isNull(sink.getValueName())) { if (Objects.isNull(measurementNode.getValue()) || Objects.requireNonNull(measurementNode.getValue().getSourceTime()).getUtcTime() < utcTimestamp) { @@ -365,11 +367,11 @@ private void transferTabletForPubSubModel( if (isTableModel) { sourceNameList = new ArrayList<>(tablet.getRowSize()); for (int i = 0; i < tablet.getRowSize(); ++i) { - final StringBuilder idBuilder = new StringBuilder(sink.unQualifiedDatabaseName); + final StringBuilder idBuilder = new StringBuilder(sink.getUnQualifiedDatabaseName()); for (final Object segment : tablet.getDeviceID(i).getSegments()) { idBuilder .append(TsFileConstant.PATH_SEPARATOR) - .append(Objects.isNull(segment) ? sink.placeHolder : segment); + .append(Objects.isNull(segment) ? sink.getPlaceHolder() : segment); } sourceNameList.add(idBuilder.toString()); } @@ -521,7 +523,7 @@ public void onMonitoringModeChanged(final List monitoredItems) { /////////////////////////////// Conflict detection /////////////////////////////// - void checkEquals( + public void checkEquals( final String user, final String password, final String securityDir, diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaServerBuilder.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java similarity index 95% rename from iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaServerBuilder.java rename to iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java index bc2df4839e2b..1d4334828536 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaServerBuilder.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.iotdb.db.pipe.sink.protocol.opcua; +package org.apache.iotdb.db.pipe.sink.protocol.opcua.server; import org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant; import org.apache.iotdb.pipe.api.exception.PipeException; @@ -86,7 +86,7 @@ public class OpcUaServerBuilder implements Closeable { private boolean enableAnonymousAccess; private DefaultTrustListManager trustListManager; - OpcUaServerBuilder() { + public OpcUaServerBuilder() { tcpBindPort = PipeSinkConstant.CONNECTOR_OPC_UA_TCP_BIND_PORT_DEFAULT_VALUE; httpsBindPort = PipeSinkConstant.CONNECTOR_OPC_UA_HTTPS_BIND_PORT_DEFAULT_VALUE; user = PipeSinkConstant.CONNECTOR_IOTDB_USER_DEFAULT_VALUE; @@ -95,37 +95,37 @@ public class OpcUaServerBuilder implements Closeable { enableAnonymousAccess = PipeSinkConstant.CONNECTOR_OPC_UA_ENABLE_ANONYMOUS_ACCESS_DEFAULT_VALUE; } - OpcUaServerBuilder setTcpBindPort(final int tcpBindPort) { + public OpcUaServerBuilder setTcpBindPort(final int tcpBindPort) { this.tcpBindPort = tcpBindPort; return this; } - OpcUaServerBuilder setHttpsBindPort(final int httpsBindPort) { + public OpcUaServerBuilder setHttpsBindPort(final int httpsBindPort) { this.httpsBindPort = httpsBindPort; return this; } - OpcUaServerBuilder setUser(final String user) { + public OpcUaServerBuilder setUser(final String user) { this.user = user; return this; } - OpcUaServerBuilder setPassword(final String password) { + public OpcUaServerBuilder setPassword(final String password) { this.password = password; return this; } - OpcUaServerBuilder setSecurityDir(final String securityDir) { + public OpcUaServerBuilder setSecurityDir(final String securityDir) { this.securityDir = Paths.get(securityDir); return this; } - OpcUaServerBuilder setEnableAnonymousAccess(final boolean enableAnonymousAccess) { + public OpcUaServerBuilder setEnableAnonymousAccess(final boolean enableAnonymousAccess) { this.enableAnonymousAccess = enableAnonymousAccess; return this; } - OpcUaServer build() throws Exception { + public OpcUaServer build() throws Exception { Files.createDirectories(securityDir); if (!Files.exists(securityDir)) { throw new PipeException("Unable to create security dir: " + securityDir); From 109a3fc04b5cd199e3279650afc6d54d3c1eff8a Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:55:22 +0800 Subject: [PATCH 02/47] cj --- .../db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java index 0a13fc34865a..6a4581dec462 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java @@ -180,7 +180,7 @@ private void transferTabletRowForClientServerModel( UaNode folderNode = null; NodeId folderNodeId; for (int i = 0; - i < (Objects.isNull(sink.valueName) ? segments.length : segments.length - 1); + i < (Objects.isNull(sink.getValueName()) ? segments.length : segments.length - 1); ++i) { final String segment = segments[i]; final UaNode nextFolderNode; From efa0fe4077c4c3021eb1727dff95037f17acc385 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:20:30 +0800 Subject: [PATCH 03/47] bone --- iotdb-core/datanode/pom.xml | 5 + .../pipe/sink/protocol/opcua/OpcUaSink.java | 77 ++++++++- .../protocol/opcua/client/ClientRunner.java | 147 ++++++++++++++++ .../client/IoTDBKeyStoreLoaderClient.java | 124 ++++++++++++++ .../opcua/client/IoTDBOpcUaClient.java | 158 ++++++++++++++++++ .../config/constant/PipeSinkConstant.java | 17 ++ 6 files changed, 526 insertions(+), 2 deletions(-) create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java diff --git a/iotdb-core/datanode/pom.xml b/iotdb-core/datanode/pom.xml index 284eb6796727..d7d2628f36d0 100644 --- a/iotdb-core/datanode/pom.xml +++ b/iotdb-core/datanode/pom.xml @@ -191,6 +191,11 @@ org.eclipse.milo stack-server + + org.eclipse.milo + sdk-client + ${milo.version} + org.eclipse.jetty jetty-http diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 9a505ce90f0c..2c1ebf9a8340 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -24,6 +24,7 @@ import org.apache.iotdb.db.conf.IoTDBConfig; import org.apache.iotdb.db.pipe.event.common.tablet.PipeInsertNodeTabletInsertionEvent; import org.apache.iotdb.db.pipe.event.common.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.db.pipe.sink.protocol.opcua.client.IoTDBOpcUaClient; import org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaNameSpace; import org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaServerBuilder; import org.apache.iotdb.db.storageengine.StorageEngine; @@ -40,7 +41,11 @@ import org.apache.tsfile.utils.Pair; import org.apache.tsfile.write.record.Tablet; +import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider; +import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider; +import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider; import org.eclipse.milo.opcua.sdk.server.OpcUaServer; +import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,12 +71,20 @@ import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_MODEL_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_MODEL_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_MODEL_PUB_SUB_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_NODE_URL_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_PLACEHOLDER_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_PLACEHOLDER_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_NAME_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_NAME_KEY; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_128_RSA_15_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_NONE_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_DIR_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_DIR_KEY; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_POLICY_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_TCP_BIND_PORT_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_TCP_BIND_PORT_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_VALUE_NAME_DEFAULT_VALUE; @@ -84,9 +97,11 @@ import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_ENABLE_ANONYMOUS_ACCESS_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_HTTPS_BIND_PORT_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_MODEL_KEY; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_NODE_URL_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_PLACEHOLDER_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_QUALITY_NAME_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_SECURITY_DIR_KEY; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_SECURITY_POLICY_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_TCP_BIND_PORT_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_VALUE_NAME_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_WITH_QUALITY_KEY; @@ -114,7 +129,12 @@ public class OpcUaSink implements PipeConnector { String placeHolder; @Nullable String valueName; @Nullable String qualityName; - private OpcUaNameSpace nameSpace; + + // Inner server + private @Nullable OpcUaNameSpace nameSpace; + + // Outer server + private @Nullable IoTDBOpcUaClient client; @Override public void validate(final PipeParameterValidator validator) throws Exception { @@ -139,6 +159,17 @@ public void validate(final PipeParameterValidator validator) throws Exception { public void customize( final PipeParameters parameters, final PipeConnectorRuntimeConfiguration configuration) throws Exception { + final String nodeUrl = + parameters.getStringByKeys(CONNECTOR_OPC_UA_NODE_URL_KEY, SINK_OPC_UA_NODE_URL_KEY); + if (Objects.isNull(nodeUrl)) { + customizeServer(parameters, configuration); + } else { + customizeClient(nodeUrl, parameters); + } + } + + private void customizeServer( + final PipeParameters parameters, final PipeConnectorRuntimeConfiguration configuration) { final int tcpBindPort = parameters.getIntOrDefault( Arrays.asList(CONNECTOR_OPC_UA_TCP_BIND_PORT_KEY, SINK_OPC_UA_TCP_BIND_PORT_KEY), @@ -250,6 +281,48 @@ public void customize( } } + private void customizeClient(final String nodeUrl, final PipeParameters parameters) { + final SecurityPolicy policy; + switch (parameters + .getStringOrDefault( + Arrays.asList(CONNECTOR_OPC_UA_SECURITY_POLICY_KEY, SINK_OPC_UA_SECURITY_POLICY_KEY), + CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE) + .toUpperCase()) { + case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_NONE_VALUE: + policy = SecurityPolicy.None; + break; + case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_128_RSA_15_VALUE: + policy = SecurityPolicy.Basic128Rsa15; + break; + case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_VALUE: + policy = SecurityPolicy.Basic256; + break; + case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE: + policy = SecurityPolicy.Basic256Sha256; + break; + case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE: + policy = SecurityPolicy.Aes128_Sha256_RsaOaep; + break; + case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE: + policy = SecurityPolicy.Aes256_Sha256_RsaPss; + break; + default: + policy = null; + break; + } + + final IdentityProvider provider; + final String userName = + parameters.getStringByKeys(CONNECTOR_IOTDB_USER_KEY, SINK_IOTDB_USER_KEY); + provider = + Objects.nonNull(userName) + ? new UsernameProvider( + userName, + parameters.getStringByKeys(CONNECTOR_IOTDB_PASSWORD_KEY, SINK_IOTDB_PASSWORD_KEY)) + : new AnonymousProvider(); + client = new IoTDBOpcUaClient(nodeUrl, policy, provider); + } + @Override public void handshake() throws Exception { // Server side, do nothing @@ -359,7 +432,7 @@ public void close() throws Exception { } } - // Getter + /////////////////////////////// Getter /////////////////////////////// public boolean isClientServerModel() { return isClientServerModel; diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java new file mode 100644 index 000000000000..b0bc87090984 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java @@ -0,0 +1,147 @@ +/* + * 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.db.pipe.sink.protocol.opcua.client; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.eclipse.milo.opcua.sdk.client.OpcUaClient; +import org.eclipse.milo.opcua.stack.client.security.DefaultClientCertificateValidator; +import org.eclipse.milo.opcua.stack.core.Stack; +import org.eclipse.milo.opcua.stack.core.security.DefaultTrustListManager; +import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.Security; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint; + +public class ClientRunner { + + static { + // Required for SecurityPolicy.Aes256_Sha256_RsaPss + Security.addProvider(new BouncyCastleProvider()); + } + + private final CompletableFuture future = new CompletableFuture<>(); + + private final IoTDBOpcUaClient configurableUaClient; + + public ClientRunner(IoTDBOpcUaClient configurableUaClient) { + this.configurableUaClient = configurableUaClient; + } + + private OpcUaClient createClient() throws Exception { + final Path securityTempDir = + Paths.get(System.getProperty("java.io.tmpdir"), "client", "security"); + Files.createDirectories(securityTempDir); + if (!Files.exists(securityTempDir)) { + throw new Exception("unable to create security dir: " + securityTempDir); + } + + final File pkiDir = securityTempDir.resolve("pki").toFile(); + + System.out.println("security dir: " + securityTempDir.toAbsolutePath()); + LoggerFactory.getLogger(getClass()).info("security pki dir: {}", pkiDir.getAbsolutePath()); + + final IoTDBKeyStoreLoaderClient loader = new IoTDBKeyStoreLoaderClient().load(securityTempDir); + + final DefaultTrustListManager trustListManager = new DefaultTrustListManager(pkiDir); + + final DefaultClientCertificateValidator certificateValidator = + new DefaultClientCertificateValidator(trustListManager); + + return OpcUaClient.create( + configurableUaClient.getNodeUrl(), + endpoints -> endpoints.stream().filter(configurableUaClient.endpointFilter()).findFirst(), + configBuilder -> + configBuilder + .setApplicationName(LocalizedText.english("eclipse milo opc-ua client")) + .setApplicationUri("urn:eclipse:milo:examples:client") + .setKeyPair(loader.getClientKeyPair()) + .setCertificate(loader.getClientCertificate()) + .setCertificateChain(loader.getClientCertificateChain()) + .setCertificateValidator(certificateValidator) + .setIdentityProvider(configurableUaClient.getIdentityProvider()) + .setRequestTimeout(uint(5000)) + .build()); + } + + public void run() { + try { + final OpcUaClient client = createClient(); + + future.whenCompleteAsync( + (c, ex) -> { + if (ex != null) { + System.out.println("Error running example: " + ex.getMessage()); + } + + try { + client.disconnect().get(); + Stack.releaseSharedResources(); + } catch (InterruptedException | ExecutionException e) { + Thread.currentThread().interrupt(); + System.out.println("Error disconnecting: {}" + e.getMessage()); + } + + try { + Thread.sleep(1000); + System.exit(0); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + e.printStackTrace(); + } + }); + + try { + configurableUaClient.run(client); + future.get(100000, TimeUnit.SECONDS); + } catch (Throwable t) { + System.out.println("Error running client example: " + t.getMessage() + t); + future.completeExceptionally(t); + } + } catch (Throwable t) { + System.out.println("Error getting client: {}" + t.getMessage()); + + future.completeExceptionally(t); + + try { + Thread.sleep(1000); + System.exit(0); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + e.printStackTrace(); + } + } + + try { + Thread.sleep(999_999_999); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + e.printStackTrace(); + } + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java new file mode 100644 index 000000000000..fc524a60c716 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.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.db.pipe.sink.protocol.opcua.client; + +import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil; +import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder; +import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator; + +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Key; +import java.security.KeyPair; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.regex.Pattern; + +class IoTDBKeyStoreLoaderClient { + + private static final Pattern IP_ADDR_PATTERN = + Pattern.compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"); + + private static final String CLIENT_ALIAS = "client-ai"; + private static final char[] PASSWORD = "root".toCharArray(); + + private X509Certificate[] clientCertificateChain; + private X509Certificate clientCertificate; + private KeyPair clientKeyPair; + + IoTDBKeyStoreLoaderClient load(Path baseDir) throws Exception { + final KeyStore keyStore = KeyStore.getInstance("PKCS12"); + + final Path serverKeyStore = baseDir.resolve("example-client.pfx"); + + System.out.println("Loading KeyStore at " + serverKeyStore); + + if (!Files.exists(serverKeyStore)) { + keyStore.load(null, PASSWORD); + + final KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048); + + final SelfSignedCertificateBuilder builder = + new SelfSignedCertificateBuilder(keyPair) + .setCommonName("Eclipse Milo Example Client") + .setOrganization("digitalpetri") + .setOrganizationalUnit("dev") + .setLocalityName("Folsom") + .setStateName("CA") + .setCountryCode("US") + .setApplicationUri("urn:eclipse:milo:examples:client") + .addDnsName("localhost") + .addIpAddress("127.0.0.1"); + + // Get as many hostnames and IP addresses as we can listed in the certificate. + for (String hostname : HostnameUtil.getHostnames("0.0.0.0")) { + if (IP_ADDR_PATTERN.matcher(hostname).matches()) { + builder.addIpAddress(hostname); + } else { + builder.addDnsName(hostname); + } + } + + final X509Certificate certificate = builder.build(); + + keyStore.setKeyEntry( + CLIENT_ALIAS, keyPair.getPrivate(), PASSWORD, new X509Certificate[] {certificate}); + try (OutputStream out = Files.newOutputStream(serverKeyStore)) { + keyStore.store(out, PASSWORD); + } + } else { + try (InputStream in = Files.newInputStream(serverKeyStore)) { + keyStore.load(in, PASSWORD); + } + } + + final Key clientPrivateKey = keyStore.getKey(CLIENT_ALIAS, PASSWORD); + if (clientPrivateKey instanceof PrivateKey) { + clientCertificate = (X509Certificate) keyStore.getCertificate(CLIENT_ALIAS); + + clientCertificateChain = + Arrays.stream(keyStore.getCertificateChain(CLIENT_ALIAS)) + .map(X509Certificate.class::cast) + .toArray(X509Certificate[]::new); + + final PublicKey serverPublicKey = clientCertificate.getPublicKey(); + clientKeyPair = new KeyPair(serverPublicKey, (PrivateKey) clientPrivateKey); + } + + return this; + } + + X509Certificate getClientCertificate() { + return clientCertificate; + } + + public X509Certificate[] getClientCertificateChain() { + return clientCertificateChain; + } + + KeyPair getClientKeyPair() { + return clientKeyPair; + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java new file mode 100644 index 000000000000..a46f2153e95b --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -0,0 +1,158 @@ +/* + * 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.db.pipe.sink.protocol.opcua.client; + +import org.eclipse.milo.opcua.sdk.client.OpcUaClient; +import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider; +import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem; +import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription; +import org.eclipse.milo.opcua.stack.core.AttributeId; +import org.eclipse.milo.opcua.stack.core.Identifiers; +import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; +import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject; +import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName; +import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger; +import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode; +import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn; +import org.eclipse.milo.opcua.stack.core.types.structured.ContentFilter; +import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription; +import org.eclipse.milo.opcua.stack.core.types.structured.EventFilter; +import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest; +import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters; +import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId; +import org.eclipse.milo.opcua.stack.core.types.structured.SimpleAttributeOperand; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Predicate; + +import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint; + +public class IoTDBOpcUaClient { + + private final String nodeUrl; + + private final SecurityPolicy securityPolicy; + private final IdentityProvider identityProvider; + + private final AtomicLong clientHandles = new AtomicLong(1L); + + public IoTDBOpcUaClient( + final String nodeUrl, + final SecurityPolicy securityPolicy, + final IdentityProvider identityProvider) { + this.nodeUrl = nodeUrl; + this.securityPolicy = securityPolicy; + this.identityProvider = identityProvider; + } + + public void run(OpcUaClient client) throws Exception { + // synchronous connect + client.connect().get(); + + // create a subscription and a monitored item + final UaSubscription subscription = + client.getSubscriptionManager().createSubscription(200.0).get(); + + final ReadValueId readValueId = + new ReadValueId( + Identifiers.Server, AttributeId.EventNotifier.uid(), null, QualifiedName.NULL_VALUE); + + // client handle must be unique per item + final UInteger clientHandle = uint(clientHandles.getAndIncrement()); + + final EventFilter eventFilter = + new EventFilter( + new SimpleAttributeOperand[] { + new SimpleAttributeOperand( + Identifiers.BaseEventType, + new QualifiedName[] {new QualifiedName(0, "Time")}, + AttributeId.Value.uid(), + null), + new SimpleAttributeOperand( + Identifiers.BaseEventType, + new QualifiedName[] {new QualifiedName(0, "Message")}, + AttributeId.Value.uid(), + null), + new SimpleAttributeOperand( + Identifiers.BaseEventType, + new QualifiedName[] {new QualifiedName(0, "SourceName")}, + AttributeId.Value.uid(), + null), + new SimpleAttributeOperand( + Identifiers.BaseEventType, + new QualifiedName[] {new QualifiedName(0, "SourceNode")}, + AttributeId.Value.uid(), + null) + }, + new ContentFilter(null)); + + final MonitoringParameters parameters = + new MonitoringParameters( + clientHandle, + 0.0, + ExtensionObject.encode(client.getStaticSerializationContext(), eventFilter), + uint(10000), + true); + + final MonitoredItemCreateRequest request = + new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters); + + final List items = + subscription + .createMonitoredItems(TimestampsToReturn.Both, Collections.singletonList(request)) + .get(); + + // do something with the value updates + final UaMonitoredItem monitoredItem = items.get(0); + + final AtomicInteger eventCount = new AtomicInteger(0); + + monitoredItem.setEventConsumer( + (item, vs) -> { + eventCount.incrementAndGet(); + System.out.println("Event Received from " + item.getReadValueId().getNodeId()); + + for (int i = 0; i < vs.length; i++) { + System.out.println(("\tvariant[" + i + "]: " + vs[i].getValue())); + } + }); + } + + /////////////////////////////// Getter /////////////////////////////// + + String getNodeUrl() { + return nodeUrl; + } + + Predicate endpointFilter() { + return e -> getSecurityPolicy().getUri().equals(e.getSecurityPolicyUri()); + } + + SecurityPolicy getSecurityPolicy() { + return securityPolicy; + } + + IdentityProvider getIdentityProvider() { + return identityProvider; + } +} diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java index ecdc01237e91..6fd228db7ec9 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java @@ -194,6 +194,23 @@ public class PipeSinkConstant { public static final String SINK_OPC_UA_QUALITY_NAME_KEY = "sink.opcua.quality-name"; public static final String CONNECTOR_OPC_UA_QUALITY_NAME_DEFAULT_VALUE = "quality"; + public static final String CONNECTOR_OPC_UA_NODE_URL_KEY = "connector.opcua.node-url"; + public static final String SINK_OPC_UA_NODE_URL_KEY = "sink.opcua.node-url"; + + public static final String CONNECTOR_OPC_UA_SECURITY_POLICY_KEY = + "connector.opcua.security-policy"; + public static final String SINK_OPC_UA_SECURITY_POLICY_KEY = "sink.opcua.security-policy"; + public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_NONE_VALUE = "NONE"; + public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_128_RSA_15_VALUE = + "BASIC128RSA15"; + public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_VALUE = "BASIC256"; + public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE = + "BASIC256SHA256"; + public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE = + "AES128_SHA256_RSAOAEP"; + public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE = + "AES256_SHA256_RSAPSS"; + 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; From 8efcd63ceaa24b9cc5e18e0804bc59ce0096dc1b Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:30:09 +0800 Subject: [PATCH 04/47] fix --- .../apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 2c1ebf9a8340..2856d2d04334 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -307,8 +307,8 @@ private void customizeClient(final String nodeUrl, final PipeParameters paramete policy = SecurityPolicy.Aes256_Sha256_RsaPss; break; default: - policy = null; - break; + throw new PipeException( + "The security policy can only be 'None', 'Basic128Rsa15', 'Basic256', 'Basic256Sha256', 'Aes128_Sha256_RsaOaep' or 'Aes256_Sha256_RsaPss'."); } final IdentityProvider provider; From fd395599241f9e7bbd923f0e235a821655f5155d Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:34:18 +0800 Subject: [PATCH 05/47] fix --- .../pipe/sink/protocol/opcua/OpcUaSink.java | 2 + .../opcua/client/IoTDBOpcUaClient.java | 68 ------------------- 2 files changed, 2 insertions(+), 68 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 2856d2d04334..6928385eca9c 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -24,6 +24,7 @@ import org.apache.iotdb.db.conf.IoTDBConfig; import org.apache.iotdb.db.pipe.event.common.tablet.PipeInsertNodeTabletInsertionEvent; import org.apache.iotdb.db.pipe.event.common.tablet.PipeRawTabletInsertionEvent; +import org.apache.iotdb.db.pipe.sink.protocol.opcua.client.ClientRunner; import org.apache.iotdb.db.pipe.sink.protocol.opcua.client.IoTDBOpcUaClient; import org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaNameSpace; import org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaServerBuilder; @@ -321,6 +322,7 @@ private void customizeClient(final String nodeUrl, final PipeParameters paramete parameters.getStringByKeys(CONNECTOR_IOTDB_PASSWORD_KEY, SINK_IOTDB_PASSWORD_KEY)) : new AnonymousProvider(); client = new IoTDBOpcUaClient(nodeUrl, policy, provider); + new ClientRunner(client).run(); } @Override diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index a46f2153e95b..120039d56002 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -68,74 +68,6 @@ public IoTDBOpcUaClient( public void run(OpcUaClient client) throws Exception { // synchronous connect client.connect().get(); - - // create a subscription and a monitored item - final UaSubscription subscription = - client.getSubscriptionManager().createSubscription(200.0).get(); - - final ReadValueId readValueId = - new ReadValueId( - Identifiers.Server, AttributeId.EventNotifier.uid(), null, QualifiedName.NULL_VALUE); - - // client handle must be unique per item - final UInteger clientHandle = uint(clientHandles.getAndIncrement()); - - final EventFilter eventFilter = - new EventFilter( - new SimpleAttributeOperand[] { - new SimpleAttributeOperand( - Identifiers.BaseEventType, - new QualifiedName[] {new QualifiedName(0, "Time")}, - AttributeId.Value.uid(), - null), - new SimpleAttributeOperand( - Identifiers.BaseEventType, - new QualifiedName[] {new QualifiedName(0, "Message")}, - AttributeId.Value.uid(), - null), - new SimpleAttributeOperand( - Identifiers.BaseEventType, - new QualifiedName[] {new QualifiedName(0, "SourceName")}, - AttributeId.Value.uid(), - null), - new SimpleAttributeOperand( - Identifiers.BaseEventType, - new QualifiedName[] {new QualifiedName(0, "SourceNode")}, - AttributeId.Value.uid(), - null) - }, - new ContentFilter(null)); - - final MonitoringParameters parameters = - new MonitoringParameters( - clientHandle, - 0.0, - ExtensionObject.encode(client.getStaticSerializationContext(), eventFilter), - uint(10000), - true); - - final MonitoredItemCreateRequest request = - new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters); - - final List items = - subscription - .createMonitoredItems(TimestampsToReturn.Both, Collections.singletonList(request)) - .get(); - - // do something with the value updates - final UaMonitoredItem monitoredItem = items.get(0); - - final AtomicInteger eventCount = new AtomicInteger(0); - - monitoredItem.setEventConsumer( - (item, vs) -> { - eventCount.incrementAndGet(); - System.out.println("Event Received from " + item.getReadValueId().getNodeId()); - - for (int i = 0; i < vs.length; i++) { - System.out.println(("\tvariant[" + i + "]: " + vs[i].getValue())); - } - }); } /////////////////////////////// Getter /////////////////////////////// From 6be7b4dee6e545178a1a81e40ef9290a75a0ff1a Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:38:11 +0800 Subject: [PATCH 06/47] framework --- .../opcua/client/IoTDBOpcUaClient.java | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index 120039d56002..b760785684be 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -19,34 +19,18 @@ package org.apache.iotdb.db.pipe.sink.protocol.opcua.client; +import org.apache.iotdb.db.pipe.sink.protocol.opcua.OpcUaSink; + +import org.apache.tsfile.write.record.Tablet; import org.eclipse.milo.opcua.sdk.client.OpcUaClient; import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider; -import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem; -import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription; -import org.eclipse.milo.opcua.stack.core.AttributeId; -import org.eclipse.milo.opcua.stack.core.Identifiers; +import org.eclipse.milo.opcua.stack.core.UaException; import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; -import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject; -import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName; -import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger; -import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode; -import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn; -import org.eclipse.milo.opcua.stack.core.types.structured.ContentFilter; import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription; -import org.eclipse.milo.opcua.stack.core.types.structured.EventFilter; -import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest; -import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters; -import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId; -import org.eclipse.milo.opcua.stack.core.types.structured.SimpleAttributeOperand; - -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; + import java.util.concurrent.atomic.AtomicLong; import java.util.function.Predicate; -import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint; - public class IoTDBOpcUaClient { private final String nodeUrl; @@ -70,6 +54,8 @@ public void run(OpcUaClient client) throws Exception { client.connect().get(); } + public void transfer(final Tablet tablet, final OpcUaSink sink) throws UaException {} + /////////////////////////////// Getter /////////////////////////////// String getNodeUrl() { From f78e6d78b3908c9aa3dffd0c26f9c63d3417f1d9 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 16:56:33 +0800 Subject: [PATCH 07/47] fix --- .../pipe/sink/protocol/opcua/OpcUaSink.java | 16 ++ .../opcua/client/IoTDBOpcUaClient.java | 147 +++++++++++++++++- 2 files changed, 162 insertions(+), 1 deletion(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 6928385eca9c..c2ca03419201 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -154,6 +154,22 @@ public void validate(final PipeParameterValidator validator) throws Exception { Arrays.asList(CONNECTOR_IOTDB_USER_KEY, SINK_IOTDB_USER_KEY), Arrays.asList(CONNECTOR_IOTDB_USERNAME_KEY, SINK_IOTDB_USERNAME_KEY), false); + + final PipeParameters parameters = validator.getParameters(); + if (validator + .getParameters() + .hasAnyAttributes(CONNECTOR_OPC_UA_NODE_URL_KEY, SINK_OPC_UA_NODE_URL_KEY)) { + validator.validate( + CONNECTOR_OPC_UA_MODEL_CLIENT_SERVER_VALUE::equals, + String.format( + "When the OPC UA sink points to an outer server or specifies 'with-quality', the %s or %s must be %s.", + CONNECTOR_OPC_UA_MODEL_KEY, + SINK_OPC_UA_MODEL_KEY, + CONNECTOR_OPC_UA_MODEL_CLIENT_SERVER_VALUE), + parameters.getStringOrDefault( + Arrays.asList(CONNECTOR_OPC_UA_MODEL_KEY, SINK_OPC_UA_MODEL_KEY), + CONNECTOR_OPC_UA_MODEL_DEFAULT_VALUE)); + } } @Override diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index b760785684be..cc6651338da9 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -24,10 +24,31 @@ import org.apache.tsfile.write.record.Tablet; import org.eclipse.milo.opcua.sdk.client.OpcUaClient; import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider; +import org.eclipse.milo.opcua.sdk.core.AccessLevel; +import org.eclipse.milo.opcua.sdk.core.ValueRanks; +import org.eclipse.milo.opcua.stack.core.Identifiers; import org.eclipse.milo.opcua.stack.core.UaException; import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; +import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; +import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime; +import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject; +import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; +import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; +import org.eclipse.milo.opcua.stack.core.types.builtin.QualifiedName; +import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode; +import org.eclipse.milo.opcua.stack.core.types.builtin.Variant; +import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned; +import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass; +import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn; +import org.eclipse.milo.opcua.stack.core.types.structured.AddNodesItem; +import org.eclipse.milo.opcua.stack.core.types.structured.AddNodesResponse; +import org.eclipse.milo.opcua.stack.core.types.structured.DeleteNodesItem; import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription; +import org.eclipse.milo.opcua.stack.core.types.structured.ObjectAttributes; +import org.eclipse.milo.opcua.stack.core.types.structured.VariableAttributes; +import java.util.Arrays; +import java.util.Collections; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Predicate; @@ -54,7 +75,10 @@ public void run(OpcUaClient client) throws Exception { client.connect().get(); } - public void transfer(final Tablet tablet, final OpcUaSink sink) throws UaException {} + // Only support tree model & client-server + public void transfer(final Tablet tablet, final OpcUaSink sink) throws UaException { + + } /////////////////////////////// Getter /////////////////////////////// @@ -73,4 +97,125 @@ SecurityPolicy getSecurityPolicy() { IdentityProvider getIdentityProvider() { return identityProvider; } + + public void runA(OpcUaClient client) throws Exception { + // synchronous connect + client.connect().get(); + System.out.println("✅ 连接成功"); + + // 读取标签值c + NodeId nodeId = new NodeId(2, "root/sg/d1/s2"); + + // 1. 先读取当前值确认节点可访问 + DataValue readValue = client.readValue(0, TimestampsToReturn.Both, nodeId).get(); + System.out.println("读取当前值: " + readValue.getValue().getValue()); + System.out.println("读取状态: " + readValue.getStatusCode()); + + // 2. 尝试写入新值 + Variant newValue = new Variant(42.0f); + DataValue writeValue = new DataValue(newValue, StatusCode.GOOD, new DateTime(), new DateTime()); + + System.out.println("尝试写入值: " + newValue.getValue()); + + StatusCode writeStatus = client.writeValue(nodeId, writeValue).get(); + System.out.println("写入状态: " + writeStatus); + + client.deleteNodes(Collections.singletonList(new DeleteNodesItem(nodeId, true))); + + AddNodesResponse addStatus = + client + .addNodes( + Arrays.asList( + new AddNodesItem( + Identifiers.ObjectsFolder.expanded(), + Identifiers.Organizes, + new NodeId(2, "root").expanded(), + new QualifiedName(2, "root"), + NodeClass.Object, + ExtensionObject.encode( + client.getStaticSerializationContext(), createFolder0Attributes()), + Identifiers.FolderType.expanded()), + new AddNodesItem( + new NodeId(2, "root").expanded(), + Identifiers.Organizes, + new NodeId(2, "root/sg").expanded(), + new QualifiedName(2, "sg"), + NodeClass.Object, + ExtensionObject.encode( + client.getStaticSerializationContext(), createFolder1Attributes()), + Identifiers.FolderType.expanded()), + new AddNodesItem( + new NodeId(2, "root/sg").expanded(), + Identifiers.Organizes, + new NodeId(2, "root/sg/d1").expanded(), + new QualifiedName(2, "d2"), + NodeClass.Object, + ExtensionObject.encode( + client.getStaticSerializationContext(), createFolder2Attributes()), + Identifiers.FolderType.expanded()), + new AddNodesItem( + new NodeId(2, "root/sg/d1").expanded(), + Identifiers.Organizes, + new NodeId(2, "root/sg/d1/s2").expanded(), + new QualifiedName(2, "s2"), + NodeClass.Variable, + ExtensionObject.encode( + client.getStaticSerializationContext(), + createPressureSensorAttributes()), + Identifiers.BaseDataVariableType.expanded()))) + .get(); + System.out.println("新增节点状态: " + addStatus); + client.disconnect().get(); + } + + public static VariableAttributes createPressureSensorAttributes() { + return new VariableAttributes( + Unsigned.uint(0xFFFF), // specifiedAttributes + LocalizedText.english("s2"), + LocalizedText.english("反应釜压力传感器"), + Unsigned.uint(0), // writeMask + Unsigned.uint(0), // userWriteMask + new Variant(101.3f), // 初始压力值 101.3 kPa + Identifiers.Float, // 浮点数类型 + ValueRanks.Scalar, // 标量 + null, // arrayDimensions + AccessLevel.toValue(AccessLevel.READ_WRITE), + AccessLevel.toValue(AccessLevel.READ_WRITE), + 500.0, // 500ms 采样间隔 + false // 启用历史记录 + ); + } + + public static ObjectAttributes createFolder0Attributes() { + return new ObjectAttributes( + Unsigned.uint(0xFFFF), // specifiedAttributes + LocalizedText.english("root"), + LocalizedText.english("反应釜压力传感器"), + Unsigned.uint(0), // writeMask + Unsigned.uint(0), // userWriteMask + null // 启用历史记录 + ); + } + + public static ObjectAttributes createFolder1Attributes() { + return new ObjectAttributes( + Unsigned.uint(0xFFFF), // specifiedAttributes + LocalizedText.english("sg"), + LocalizedText.english("反应釜压力传感器"), + Unsigned.uint(0), // writeMask + Unsigned.uint(0), // userWriteMask + null // 启用历史记录 + ); + } + + public static ObjectAttributes createFolder2Attributes() { + return new ObjectAttributes( + Unsigned.uint(0xFFFF), // specifiedAttributes + LocalizedText.english("d1"), + LocalizedText.english("反应釜压力传感器"), + Unsigned.uint(0), // writeMask + Unsigned.uint(0), // userWriteMask + null // 启用历史记录 + ); + } } From ef67611077b77dba33111f61a5c27d338741d829 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 17:06:16 +0800 Subject: [PATCH 08/47] trilog --- .../opcua/client/IoTDBOpcUaClient.java | 4 +-- .../protocol/opcua/server/OpcUaNameSpace.java | 25 ++++++++++++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index cc6651338da9..d7f02d1ddd01 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -76,9 +76,7 @@ public void run(OpcUaClient client) throws Exception { } // Only support tree model & client-server - public void transfer(final Tablet tablet, final OpcUaSink sink) throws UaException { - - } + public void transfer(final Tablet tablet, final OpcUaSink sink) throws UaException {} /////////////////////////////// Getter /////////////////////////////// diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java index 6a4581dec462..c60680a1f1e0 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java @@ -98,14 +98,18 @@ public void shutdown() { public void transfer(final Tablet tablet, final boolean isTableModel, final OpcUaSink sink) throws UaException { if (sink.isClientServerModel()) { - transferTabletForClientServerModel(tablet, isTableModel, sink); + transferTabletForClientServerModel( + tablet, isTableModel, sink, this::transferTabletRowForClientServerModel); } else { transferTabletForPubSubModel(tablet, isTableModel, sink); } } - private void transferTabletForClientServerModel( - final Tablet tablet, final boolean isTableModel, final OpcUaSink sink) { + public static void transferTabletForClientServerModel( + final Tablet tablet, + final boolean isTableModel, + final OpcUaSink sink, + final TabletRowConsumer consumer) { final List schemas = tablet.getSchemas(); final List newSchemas = new ArrayList<>(); if (!isTableModel) { @@ -126,8 +130,7 @@ private void transferTabletForClientServerModel( } } - transferTabletRowForClientServerModel( - tablet.getDeviceId().split("\\."), newSchemas, timestamps, values, sink); + consumer.accept(tablet.getDeviceId().split("\\."), newSchemas, timestamps, values, sink); } else { new PipeTableModelTabletEventSorter(tablet).sortByTimestampIfNecessary(); @@ -150,7 +153,7 @@ private void transferTabletForClientServerModel( } final int finalI = i; - transferTabletRowForClientServerModel( + consumer.accept( folderSegments, newSchemas, Collections.singletonList(tablet.getTimestamp(i)), @@ -167,6 +170,16 @@ private void transferTabletForClientServerModel( } } + @FunctionalInterface + public interface TabletRowConsumer { + void accept( + final String[] segments, + final List measurementSchemas, + final List timestamps, + final List values, + final OpcUaSink sink); + } + private void transferTabletRowForClientServerModel( final String[] segments, final List measurementSchemas, From b8719251b4b10f1734bb64e94889c23b27804894 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 17:45:21 +0800 Subject: [PATCH 09/47] framework --- .../opcua/client/IoTDBOpcUaClient.java | 127 +++++++++++++++++- .../protocol/opcua/server/OpcUaNameSpace.java | 10 +- 2 files changed, 127 insertions(+), 10 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index d7f02d1ddd01..6d9c6a71a7f4 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -20,14 +20,18 @@ package org.apache.iotdb.db.pipe.sink.protocol.opcua.client; import org.apache.iotdb.db.pipe.sink.protocol.opcua.OpcUaSink; +import org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaNameSpace; +import org.apache.iotdb.pipe.api.exception.PipeException; +import org.apache.tsfile.enums.TSDataType; import org.apache.tsfile.write.record.Tablet; +import org.apache.tsfile.write.schema.IMeasurementSchema; import org.eclipse.milo.opcua.sdk.client.OpcUaClient; import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider; import org.eclipse.milo.opcua.sdk.core.AccessLevel; import org.eclipse.milo.opcua.sdk.core.ValueRanks; import org.eclipse.milo.opcua.stack.core.Identifiers; -import org.eclipse.milo.opcua.stack.core.UaException; +import org.eclipse.milo.opcua.stack.core.StatusCodes; import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime; @@ -42,6 +46,7 @@ import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn; import org.eclipse.milo.opcua.stack.core.types.structured.AddNodesItem; import org.eclipse.milo.opcua.stack.core.types.structured.AddNodesResponse; +import org.eclipse.milo.opcua.stack.core.types.structured.AddNodesResult; import org.eclipse.milo.opcua.stack.core.types.structured.DeleteNodesItem; import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription; import org.eclipse.milo.opcua.stack.core.types.structured.ObjectAttributes; @@ -49,17 +54,19 @@ import java.util.Arrays; import java.util.Collections; -import java.util.concurrent.atomic.AtomicLong; +import java.util.List; +import java.util.Objects; import java.util.function.Predicate; +import static org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaNameSpace.timestampToUtc; + public class IoTDBOpcUaClient { private final String nodeUrl; private final SecurityPolicy securityPolicy; private final IdentityProvider identityProvider; - - private final AtomicLong clientHandles = new AtomicLong(1L); + private OpcUaClient client; public IoTDBOpcUaClient( final String nodeUrl, @@ -70,13 +77,121 @@ public IoTDBOpcUaClient( this.identityProvider = identityProvider; } - public void run(OpcUaClient client) throws Exception { + public void run(final OpcUaClient client) throws Exception { // synchronous connect + this.client = client; client.connect().get(); } // Only support tree model & client-server - public void transfer(final Tablet tablet, final OpcUaSink sink) throws UaException {} + public void transfer(final Tablet tablet, final OpcUaSink sink) throws Exception { + OpcUaNameSpace.transferTabletForClientServerModel( + tablet, false, sink, this::transferTabletRowForClientServerModel); + } + + private void transferTabletRowForClientServerModel( + final String[] segments, + final List measurementSchemas, + final List timestamps, + final List values, + final OpcUaSink sink) + throws Exception { + StatusCode currentQuality = + Objects.isNull(sink.getValueName()) ? StatusCode.GOOD : StatusCode.UNCERTAIN; + Object value = null; + long timestamp = 0; + NodeId nodeId = null; + + for (int i = 0; i < measurementSchemas.size(); ++i) { + if (Objects.isNull(values.get(i))) { + continue; + } + final String name = measurementSchemas.get(i).getMeasurementName(); + final TSDataType type = measurementSchemas.get(i).getType(); + if (Objects.nonNull(sink.getQualityName()) && sink.getQualityName().equals(name)) { + if (!type.equals(TSDataType.BOOLEAN)) { + throw new UnsupportedOperationException( + "The quality value only supports boolean type, while true == GOOD and false == BAD."); + } + currentQuality = values.get(i) == Boolean.TRUE ? StatusCode.GOOD : StatusCode.BAD; + continue; + } + if (Objects.nonNull(sink.getValueName()) && !sink.getValueName().equals(name)) { + throw new UnsupportedOperationException( + "When the 'with-quality' mode is enabled, the measurement must be either \"value-name\" or \"quality-name\""); + } + nodeId = new NodeId(2, String.join("/", segments)); + + final long utcTimestamp = timestampToUtc(timestamps.get(timestamps.size() > 1 ? i : 0)); + value = values.get(i); + timestamp = utcTimestamp; + } + final DataValue dataValue = + new DataValue(new Variant(value), currentQuality, new DateTime(timestamp), new DateTime()); + StatusCode writeStatus = client.writeValue(nodeId, dataValue).get(); + + if (writeStatus.getValue() == StatusCodes.Bad_NodeIdUnknown) { + final AddNodesResponse addStatus = + client + .addNodes( + Arrays.asList( + new AddNodesItem( + Identifiers.ObjectsFolder.expanded(), + Identifiers.Organizes, + new NodeId(2, "root").expanded(), + new QualifiedName(2, "root"), + NodeClass.Object, + ExtensionObject.encode( + client.getStaticSerializationContext(), createFolder0Attributes()), + Identifiers.FolderType.expanded()), + new AddNodesItem( + new NodeId(2, "root").expanded(), + Identifiers.Organizes, + new NodeId(2, "root/sg").expanded(), + new QualifiedName(2, "sg"), + NodeClass.Object, + ExtensionObject.encode( + client.getStaticSerializationContext(), createFolder1Attributes()), + Identifiers.FolderType.expanded()), + new AddNodesItem( + new NodeId(2, "root/sg").expanded(), + Identifiers.Organizes, + new NodeId(2, "root/sg/d1").expanded(), + new QualifiedName(2, "d2"), + NodeClass.Object, + ExtensionObject.encode( + client.getStaticSerializationContext(), createFolder2Attributes()), + Identifiers.FolderType.expanded()), + new AddNodesItem( + new NodeId(2, "root/sg/d1").expanded(), + Identifiers.Organizes, + new NodeId(2, "root/sg/d1/s2").expanded(), + new QualifiedName(2, "s2"), + NodeClass.Variable, + ExtensionObject.encode( + client.getStaticSerializationContext(), + createPressureSensorAttributes()), + Identifiers.BaseDataVariableType.expanded()))) + .get(); + for (final AddNodesResult result : addStatus.getResults()) { + if (!result.getStatusCode().equals(StatusCode.GOOD) + && !(result.getStatusCode().getValue() == StatusCodes.Bad_NodeIdExists)) { + throw new PipeException( + "Failed to create nodes after transfer data value, write status: " + + writeStatus + + ", creation status: " + + addStatus); + } + } + writeStatus = client.writeValue(nodeId, dataValue).get(); + if (writeStatus.getValue() != StatusCode.GOOD.getValue()) { + throw new PipeException( + "Failed to transfer dataValue after successfully created nodes, error: " + writeStatus); + } + } else { + throw new PipeException("Failed to transfer dataValue, error: " + writeStatus); + } + } /////////////////////////////// Getter /////////////////////////////// diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java index c60680a1f1e0..ddd94f84c0a1 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java @@ -96,7 +96,7 @@ public void shutdown() { } public void transfer(final Tablet tablet, final boolean isTableModel, final OpcUaSink sink) - throws UaException { + throws Exception { if (sink.isClientServerModel()) { transferTabletForClientServerModel( tablet, isTableModel, sink, this::transferTabletRowForClientServerModel); @@ -109,7 +109,8 @@ public static void transferTabletForClientServerModel( final Tablet tablet, final boolean isTableModel, final OpcUaSink sink, - final TabletRowConsumer consumer) { + final TabletRowConsumer consumer) + throws Exception { final List schemas = tablet.getSchemas(); final List newSchemas = new ArrayList<>(); if (!isTableModel) { @@ -177,7 +178,8 @@ void accept( final List measurementSchemas, final List timestamps, final List values, - final OpcUaSink sink); + final OpcUaSink sink) + throws Exception; } private void transferTabletRowForClientServerModel( @@ -357,7 +359,7 @@ private static Object getTabletObjectValue4Opc( } } - private static long timestampToUtc(final long timeStamp) { + public static long timestampToUtc(final long timeStamp) { return TimestampPrecisionUtils.currPrecision.toNanos(timeStamp) / 100L + 116444736000000000L; } From 97897c39cf2a314094e115aba831eb5134419557 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:18:34 +0800 Subject: [PATCH 10/47] fix --- .../pipe/sink/protocol/opcua/OpcUaSink.java | 8 +- .../opcua/client/IoTDBOpcUaClient.java | 201 +++++++----------- .../protocol/opcua/server/OpcUaNameSpace.java | 2 +- 3 files changed, 81 insertions(+), 130 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index c2ca03419201..d3788f0d5056 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -361,7 +361,13 @@ public void transfer(final TabletInsertionEvent tabletInsertionEvent) throws Exc transferByTablet( tabletInsertionEvent, LOGGER, - (tablet, isTableModel) -> nameSpace.transfer(tablet, isTableModel, this)); + (tablet, isTableModel) -> { + if (Objects.nonNull(nameSpace)) { + nameSpace.transfer(tablet, isTableModel, this); + } else if (Objects.nonNull(client)) { + client.transfer(tablet, this); + } + }); } public static void transferByTablet( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index 6d9c6a71a7f4..db9fd171dad3 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -35,6 +35,7 @@ import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime; +import org.eclipse.milo.opcua.stack.core.types.builtin.ExpandedNodeId; import org.eclipse.milo.opcua.stack.core.types.builtin.ExtensionObject; import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; @@ -43,25 +44,23 @@ import org.eclipse.milo.opcua.stack.core.types.builtin.Variant; import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned; import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass; -import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn; import org.eclipse.milo.opcua.stack.core.types.structured.AddNodesItem; import org.eclipse.milo.opcua.stack.core.types.structured.AddNodesResponse; import org.eclipse.milo.opcua.stack.core.types.structured.AddNodesResult; -import org.eclipse.milo.opcua.stack.core.types.structured.DeleteNodesItem; import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription; import org.eclipse.milo.opcua.stack.core.types.structured.ObjectAttributes; import org.eclipse.milo.opcua.stack.core.types.structured.VariableAttributes; -import java.util.Arrays; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.Predicate; +import static org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaNameSpace.convertToOpcDataType; import static org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaNameSpace.timestampToUtc; public class IoTDBOpcUaClient { - + private static final int NAME_SPACE_INDEX = 2; private final String nodeUrl; private final SecurityPolicy securityPolicy; @@ -101,6 +100,7 @@ private void transferTabletRowForClientServerModel( Object value = null; long timestamp = 0; NodeId nodeId = null; + NodeId opcDataType = null; for (int i = 0; i < measurementSchemas.size(); ++i) { if (Objects.isNull(values.get(i))) { @@ -120,59 +120,21 @@ private void transferTabletRowForClientServerModel( throw new UnsupportedOperationException( "When the 'with-quality' mode is enabled, the measurement must be either \"value-name\" or \"quality-name\""); } - nodeId = new NodeId(2, String.join("/", segments)); + nodeId = new NodeId(NAME_SPACE_INDEX, String.join("/", segments)); final long utcTimestamp = timestampToUtc(timestamps.get(timestamps.size() > 1 ? i : 0)); value = values.get(i); timestamp = utcTimestamp; + opcDataType = convertToOpcDataType(type); } + final Variant variant = new Variant(value); final DataValue dataValue = - new DataValue(new Variant(value), currentQuality, new DateTime(timestamp), new DateTime()); + new DataValue(variant, currentQuality, new DateTime(timestamp), new DateTime()); StatusCode writeStatus = client.writeValue(nodeId, dataValue).get(); if (writeStatus.getValue() == StatusCodes.Bad_NodeIdUnknown) { final AddNodesResponse addStatus = - client - .addNodes( - Arrays.asList( - new AddNodesItem( - Identifiers.ObjectsFolder.expanded(), - Identifiers.Organizes, - new NodeId(2, "root").expanded(), - new QualifiedName(2, "root"), - NodeClass.Object, - ExtensionObject.encode( - client.getStaticSerializationContext(), createFolder0Attributes()), - Identifiers.FolderType.expanded()), - new AddNodesItem( - new NodeId(2, "root").expanded(), - Identifiers.Organizes, - new NodeId(2, "root/sg").expanded(), - new QualifiedName(2, "sg"), - NodeClass.Object, - ExtensionObject.encode( - client.getStaticSerializationContext(), createFolder1Attributes()), - Identifiers.FolderType.expanded()), - new AddNodesItem( - new NodeId(2, "root/sg").expanded(), - Identifiers.Organizes, - new NodeId(2, "root/sg/d1").expanded(), - new QualifiedName(2, "d2"), - NodeClass.Object, - ExtensionObject.encode( - client.getStaticSerializationContext(), createFolder2Attributes()), - Identifiers.FolderType.expanded()), - new AddNodesItem( - new NodeId(2, "root/sg/d1").expanded(), - Identifiers.Organizes, - new NodeId(2, "root/sg/d1/s2").expanded(), - new QualifiedName(2, "s2"), - NodeClass.Variable, - ExtensionObject.encode( - client.getStaticSerializationContext(), - createPressureSensorAttributes()), - Identifiers.BaseDataVariableType.expanded()))) - .get(); + client.addNodes(getNodesToAdd(segments, opcDataType, variant)).get(); for (final AddNodesResult result : addStatus.getResults()) { if (!result.getStatusCode().equals(StatusCode.GOOD) && !(result.getStatusCode().getValue() == StatusCodes.Bad_NodeIdExists)) { @@ -193,6 +155,56 @@ private void transferTabletRowForClientServerModel( } } + public List getNodesToAdd( + final String[] segments, final NodeId opcDataType, final Variant initialValue) { + final List addNodesItems = new ArrayList<>(); + final StringBuilder sb = new StringBuilder(segments[0]); + ExpandedNodeId curNodeId = new NodeId(NAME_SPACE_INDEX, segments[0]).expanded(); + addNodesItems.add( + new AddNodesItem( + Identifiers.ObjectsFolder.expanded(), + Identifiers.Organizes, + curNodeId, + new QualifiedName(NAME_SPACE_INDEX, segments[0]), + NodeClass.Object, + ExtensionObject.encode( + client.getStaticSerializationContext(), createFolderAttributes(segments[0])), + Identifiers.FolderType.expanded())); + + // segments.length >= 3 + for (int i = 1; i < segments.length - 1; ++i) { + sb.append("/").append(segments[i]); + final ExpandedNodeId nextId = new NodeId(NAME_SPACE_INDEX, sb.toString()).expanded(); + addNodesItems.add( + new AddNodesItem( + curNodeId, + Identifiers.Organizes, + nextId, + new QualifiedName(NAME_SPACE_INDEX, segments[i]), + NodeClass.Object, + ExtensionObject.encode( + client.getStaticSerializationContext(), createFolderAttributes(segments[i])), + Identifiers.FolderType.expanded())); + curNodeId = nextId; + } + + final String measurementName = segments[segments.length - 1]; + sb.append("/").append(measurementName); + addNodesItems.add( + new AddNodesItem( + curNodeId, + Identifiers.Organizes, + new NodeId(NAME_SPACE_INDEX, sb.toString()).expanded(), + new QualifiedName(NAME_SPACE_INDEX, measurementName), + NodeClass.Variable, + ExtensionObject.encode( + client.getStaticSerializationContext(), + createMeasurementAttributes(measurementName, opcDataType, initialValue)), + Identifiers.BaseDataVariableType.expanded())); + + return addNodesItems; + } + /////////////////////////////// Getter /////////////////////////////// String getNodeUrl() { @@ -211,99 +223,32 @@ IdentityProvider getIdentityProvider() { return identityProvider; } - public void runA(OpcUaClient client) throws Exception { - // synchronous connect - client.connect().get(); - System.out.println("✅ 连接成功"); - - // 读取标签值c - NodeId nodeId = new NodeId(2, "root/sg/d1/s2"); - - // 1. 先读取当前值确认节点可访问 - DataValue readValue = client.readValue(0, TimestampsToReturn.Both, nodeId).get(); - System.out.println("读取当前值: " + readValue.getValue().getValue()); - System.out.println("读取状态: " + readValue.getStatusCode()); + /////////////////////////////// Attribute creator /////////////////////////////// - // 2. 尝试写入新值 - Variant newValue = new Variant(42.0f); - DataValue writeValue = new DataValue(newValue, StatusCode.GOOD, new DateTime(), new DateTime()); - - System.out.println("尝试写入值: " + newValue.getValue()); - - StatusCode writeStatus = client.writeValue(nodeId, writeValue).get(); - System.out.println("写入状态: " + writeStatus); - - client.deleteNodes(Collections.singletonList(new DeleteNodesItem(nodeId, true))); - - AddNodesResponse addStatus = - client - .addNodes( - Arrays.asList( - new AddNodesItem( - Identifiers.ObjectsFolder.expanded(), - Identifiers.Organizes, - new NodeId(2, "root").expanded(), - new QualifiedName(2, "root"), - NodeClass.Object, - ExtensionObject.encode( - client.getStaticSerializationContext(), createFolder0Attributes()), - Identifiers.FolderType.expanded()), - new AddNodesItem( - new NodeId(2, "root").expanded(), - Identifiers.Organizes, - new NodeId(2, "root/sg").expanded(), - new QualifiedName(2, "sg"), - NodeClass.Object, - ExtensionObject.encode( - client.getStaticSerializationContext(), createFolder1Attributes()), - Identifiers.FolderType.expanded()), - new AddNodesItem( - new NodeId(2, "root/sg").expanded(), - Identifiers.Organizes, - new NodeId(2, "root/sg/d1").expanded(), - new QualifiedName(2, "d2"), - NodeClass.Object, - ExtensionObject.encode( - client.getStaticSerializationContext(), createFolder2Attributes()), - Identifiers.FolderType.expanded()), - new AddNodesItem( - new NodeId(2, "root/sg/d1").expanded(), - Identifiers.Organizes, - new NodeId(2, "root/sg/d1/s2").expanded(), - new QualifiedName(2, "s2"), - NodeClass.Variable, - ExtensionObject.encode( - client.getStaticSerializationContext(), - createPressureSensorAttributes()), - Identifiers.BaseDataVariableType.expanded()))) - .get(); - System.out.println("新增节点状态: " + addStatus); - client.disconnect().get(); - } - - public static VariableAttributes createPressureSensorAttributes() { + public static VariableAttributes createMeasurementAttributes( + final String name, final NodeId objectType, final Variant initialValue) { return new VariableAttributes( Unsigned.uint(0xFFFF), // specifiedAttributes - LocalizedText.english("s2"), - LocalizedText.english("反应釜压力传感器"), + LocalizedText.english(name), + LocalizedText.english(name), Unsigned.uint(0), // writeMask Unsigned.uint(0), // userWriteMask - new Variant(101.3f), // 初始压力值 101.3 kPa - Identifiers.Float, // 浮点数类型 - ValueRanks.Scalar, // 标量 + new Variant(initialValue), + objectType, + ValueRanks.Scalar, null, // arrayDimensions AccessLevel.toValue(AccessLevel.READ_WRITE), AccessLevel.toValue(AccessLevel.READ_WRITE), - 500.0, // 500ms 采样间隔 - false // 启用历史记录 + 500.0, // samplingInterval + false // historizing ); } - public static ObjectAttributes createFolder0Attributes() { + public static ObjectAttributes createFolderAttributes(final String name) { return new ObjectAttributes( Unsigned.uint(0xFFFF), // specifiedAttributes - LocalizedText.english("root"), - LocalizedText.english("反应釜压力传感器"), + LocalizedText.english(name), + LocalizedText.english(name), Unsigned.uint(0), // writeMask Unsigned.uint(0), // userWriteMask null // 启用历史记录 diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java index ddd94f84c0a1..ddb8e161d08c 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java @@ -490,7 +490,7 @@ private void transferTabletForPubSubModel( eventNode.delete(); } - private NodeId convertToOpcDataType(final TSDataType type) { + public static NodeId convertToOpcDataType(final TSDataType type) { switch (type) { case BOOLEAN: return Identifiers.Boolean; From 97db004cf7b5da427014f2850004e75dd8718d44 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:20:18 +0800 Subject: [PATCH 11/47] fix --- .../opcua/client/IoTDBOpcUaClient.java | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index db9fd171dad3..a952197c2923 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -251,29 +251,7 @@ public static ObjectAttributes createFolderAttributes(final String name) { LocalizedText.english(name), Unsigned.uint(0), // writeMask Unsigned.uint(0), // userWriteMask - null // 启用历史记录 - ); - } - - public static ObjectAttributes createFolder1Attributes() { - return new ObjectAttributes( - Unsigned.uint(0xFFFF), // specifiedAttributes - LocalizedText.english("sg"), - LocalizedText.english("反应釜压力传感器"), - Unsigned.uint(0), // writeMask - Unsigned.uint(0), // userWriteMask - null // 启用历史记录 - ); - } - - public static ObjectAttributes createFolder2Attributes() { - return new ObjectAttributes( - Unsigned.uint(0xFFFF), // specifiedAttributes - LocalizedText.english("d1"), - LocalizedText.english("反应釜压力传感器"), - Unsigned.uint(0), // writeMask - Unsigned.uint(0), // userWriteMask - null // 启用历史记录 + null // notifier ); } } From 2da0e40d284a309a42a3b005cbc1c90d005f044c Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:29:09 +0800 Subject: [PATCH 12/47] yl --- example/pipe-opc-ua-sink/pom.xml | 1 - iotdb-core/datanode/pom.xml | 1 - pom.xml | 5 +++++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/example/pipe-opc-ua-sink/pom.xml b/example/pipe-opc-ua-sink/pom.xml index fdb07367dc2c..945706366261 100644 --- a/example/pipe-opc-ua-sink/pom.xml +++ b/example/pipe-opc-ua-sink/pom.xml @@ -32,7 +32,6 @@ org.eclipse.milo sdk-client - ${milo.version} org.eclipse.milo diff --git a/iotdb-core/datanode/pom.xml b/iotdb-core/datanode/pom.xml index d7d2628f36d0..ef25fcacbf39 100644 --- a/iotdb-core/datanode/pom.xml +++ b/iotdb-core/datanode/pom.xml @@ -194,7 +194,6 @@ org.eclipse.milo sdk-client - ${milo.version} org.eclipse.jetty diff --git a/pom.xml b/pom.xml index fae2d82e526f..697c1eb1d7d5 100644 --- a/pom.xml +++ b/pom.xml @@ -409,6 +409,11 @@ stack-server ${milo.version} + + org.eclipse.milo + sdk-client + ${milo.version} + io.airlift From 667a148b84af6147c4aa50ebd9359f225774a195 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 19:14:17 +0800 Subject: [PATCH 13/47] stack-client --- iotdb-core/datanode/pom.xml | 4 ++++ pom.xml | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/iotdb-core/datanode/pom.xml b/iotdb-core/datanode/pom.xml index ef25fcacbf39..5b615d4dbea0 100644 --- a/iotdb-core/datanode/pom.xml +++ b/iotdb-core/datanode/pom.xml @@ -191,6 +191,10 @@ org.eclipse.milo stack-server + + org.eclipse.milo + stack-client + org.eclipse.milo sdk-client diff --git a/pom.xml b/pom.xml index 697c1eb1d7d5..8a4400f07389 100644 --- a/pom.xml +++ b/pom.xml @@ -409,6 +409,11 @@ stack-server ${milo.version} + + org.eclipse.milo + stack-client + ${milo.version} + org.eclipse.milo sdk-client From 205cd19d373f335b31263469b431d5f1a7a85829 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 22 Dec 2025 19:15:21 +0800 Subject: [PATCH 14/47] fix --- example/pipe-opc-ua-sink/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/example/pipe-opc-ua-sink/pom.xml b/example/pipe-opc-ua-sink/pom.xml index 945706366261..fdb07367dc2c 100644 --- a/example/pipe-opc-ua-sink/pom.xml +++ b/example/pipe-opc-ua-sink/pom.xml @@ -32,6 +32,7 @@ org.eclipse.milo sdk-client + ${milo.version} org.eclipse.milo From 4901a6f8f8eaf2612fa5d33f412c0d12d01e5385 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 09:55:51 +0800 Subject: [PATCH 15/47] might --- iotdb-core/datanode/pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/iotdb-core/datanode/pom.xml b/iotdb-core/datanode/pom.xml index 5b615d4dbea0..85c96595381e 100644 --- a/iotdb-core/datanode/pom.xml +++ b/iotdb-core/datanode/pom.xml @@ -199,6 +199,10 @@ org.eclipse.milo sdk-client + + org.bouncycastle + bcprov-jdk18on + org.eclipse.jetty jetty-http From f2735bbf91317c1f409f3f6a81ed0f9cf4968642 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 10:05:11 +0800 Subject: [PATCH 16/47] sleep-removal --- .../db/pipe/sink/protocol/opcua/client/ClientRunner.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java index b0bc87090984..c80098e45a96 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java @@ -136,12 +136,5 @@ public void run() { e.printStackTrace(); } } - - try { - Thread.sleep(999_999_999); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - e.printStackTrace(); - } } } From ed07970919885ce084904416261690b8b2ec5d28 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 10:13:53 +0800 Subject: [PATCH 17/47] cleaning --- .../protocol/opcua/client/ClientRunner.java | 35 ++++++++++--------- .../client/IoTDBKeyStoreLoaderClient.java | 12 +++---- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java index c80098e45a96..edcdbb6113eb 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java @@ -25,6 +25,7 @@ import org.eclipse.milo.opcua.stack.core.Stack; import org.eclipse.milo.opcua.stack.core.security.DefaultTrustListManager; import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; @@ -40,6 +41,8 @@ public class ClientRunner { + private static final Logger logger = LoggerFactory.getLogger(ClientRunner.class); + static { // Required for SecurityPolicy.Aes256_Sha256_RsaPss Security.addProvider(new BouncyCastleProvider()); @@ -63,8 +66,8 @@ private OpcUaClient createClient() throws Exception { final File pkiDir = securityTempDir.resolve("pki").toFile(); - System.out.println("security dir: " + securityTempDir.toAbsolutePath()); - LoggerFactory.getLogger(getClass()).info("security pki dir: {}", pkiDir.getAbsolutePath()); + logger.info("security dir: " + securityTempDir.toAbsolutePath()); + logger.info("security pki dir: {}", pkiDir.getAbsolutePath()); final IoTDBKeyStoreLoaderClient loader = new IoTDBKeyStoreLoaderClient().load(securityTempDir); @@ -78,8 +81,8 @@ private OpcUaClient createClient() throws Exception { endpoints -> endpoints.stream().filter(configurableUaClient.endpointFilter()).findFirst(), configBuilder -> configBuilder - .setApplicationName(LocalizedText.english("eclipse milo opc-ua client")) - .setApplicationUri("urn:eclipse:milo:examples:client") + .setApplicationName(LocalizedText.english("Apache IoTDB OPC UA client")) + .setApplicationUri("urn:apache:iotdb:opc-ua-client") .setKeyPair(loader.getClientKeyPair()) .setCertificate(loader.getClientCertificate()) .setCertificateChain(loader.getClientCertificateChain()) @@ -96,21 +99,21 @@ public void run() { future.whenCompleteAsync( (c, ex) -> { if (ex != null) { - System.out.println("Error running example: " + ex.getMessage()); + logger.warn("Error running opc client: ", ex); } try { client.disconnect().get(); Stack.releaseSharedResources(); - } catch (InterruptedException | ExecutionException e) { + } catch (final InterruptedException | ExecutionException e) { Thread.currentThread().interrupt(); - System.out.println("Error disconnecting: {}" + e.getMessage()); + logger.warn("Error disconnecting: ", e); } try { Thread.sleep(1000); System.exit(0); - } catch (InterruptedException e) { + } catch (final InterruptedException e) { Thread.currentThread().interrupt(); e.printStackTrace(); } @@ -119,21 +122,21 @@ public void run() { try { configurableUaClient.run(client); future.get(100000, TimeUnit.SECONDS); - } catch (Throwable t) { - System.out.println("Error running client example: " + t.getMessage() + t); - future.completeExceptionally(t); + } catch (final Exception e) { + logger.warn("Error running client example: ", e); + future.completeExceptionally(e); } - } catch (Throwable t) { - System.out.println("Error getting client: {}" + t.getMessage()); + } catch (final Exception e) { + logger.warn("Error getting client: ", e); - future.completeExceptionally(t); + future.completeExceptionally(e); try { Thread.sleep(1000); System.exit(0); - } catch (InterruptedException e) { + } catch (InterruptedException interruptedException) { Thread.currentThread().interrupt(); - e.printStackTrace(); + logger.warn("Interrupted when running client: ", e); } } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java index fc524a60c716..3250897504bd 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java @@ -62,13 +62,13 @@ IoTDBKeyStoreLoaderClient load(Path baseDir) throws Exception { final SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair) - .setCommonName("Eclipse Milo Example Client") - .setOrganization("digitalpetri") + .setCommonName("Apache IoTDB OPC UA client") + .setOrganization("Apache") .setOrganizationalUnit("dev") - .setLocalityName("Folsom") - .setStateName("CA") - .setCountryCode("US") - .setApplicationUri("urn:eclipse:milo:examples:client") + .setLocalityName("Beijing") + .setStateName("China") + .setCountryCode("UN") + .setApplicationUri("urn:apache:iotdb:opc-ua-client") .addDnsName("localhost") .addIpAddress("127.0.0.1"); From b1b7e2f101acb4e026c1cb4aebeec3f93571e81b Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 11:06:08 +0800 Subject: [PATCH 18/47] fix --- .../db/pipe/sink/protocol/opcua/OpcUaSink.java | 10 ++++++---- .../sink/protocol/opcua/client/ClientRunner.java | 9 ++++++--- .../opcua/client/IoTDBKeyStoreLoaderClient.java | 13 ++++++------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index d3788f0d5056..36a965659aee 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -331,14 +331,16 @@ private void customizeClient(final String nodeUrl, final PipeParameters paramete final IdentityProvider provider; final String userName = parameters.getStringByKeys(CONNECTOR_IOTDB_USER_KEY, SINK_IOTDB_USER_KEY); + final String password = + parameters.getStringOrDefault( + Arrays.asList(CONNECTOR_IOTDB_PASSWORD_KEY, SINK_IOTDB_PASSWORD_KEY), + CONNECTOR_IOTDB_PASSWORD_DEFAULT_VALUE); provider = Objects.nonNull(userName) - ? new UsernameProvider( - userName, - parameters.getStringByKeys(CONNECTOR_IOTDB_PASSWORD_KEY, SINK_IOTDB_PASSWORD_KEY)) + ? new UsernameProvider(userName, password) : new AnonymousProvider(); client = new IoTDBOpcUaClient(nodeUrl, policy, provider); - new ClientRunner(client).run(); + new ClientRunner(client, password).run(); } @Override diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java index edcdbb6113eb..4ae2193b55e5 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java @@ -51,9 +51,11 @@ public class ClientRunner { private final CompletableFuture future = new CompletableFuture<>(); private final IoTDBOpcUaClient configurableUaClient; + private final String password; - public ClientRunner(IoTDBOpcUaClient configurableUaClient) { + public ClientRunner(final IoTDBOpcUaClient configurableUaClient, final String password) { this.configurableUaClient = configurableUaClient; + this.password = password; } private OpcUaClient createClient() throws Exception { @@ -66,10 +68,11 @@ private OpcUaClient createClient() throws Exception { final File pkiDir = securityTempDir.resolve("pki").toFile(); - logger.info("security dir: " + securityTempDir.toAbsolutePath()); + logger.info("security dir: {}", securityTempDir.toAbsolutePath()); logger.info("security pki dir: {}", pkiDir.getAbsolutePath()); - final IoTDBKeyStoreLoaderClient loader = new IoTDBKeyStoreLoaderClient().load(securityTempDir); + final IoTDBKeyStoreLoaderClient loader = + new IoTDBKeyStoreLoaderClient().load(securityTempDir, password.toCharArray()); final DefaultTrustListManager trustListManager = new DefaultTrustListManager(pkiDir); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java index 3250897504bd..2d95f174cb7e 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java @@ -42,13 +42,12 @@ class IoTDBKeyStoreLoaderClient { Pattern.compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"); private static final String CLIENT_ALIAS = "client-ai"; - private static final char[] PASSWORD = "root".toCharArray(); private X509Certificate[] clientCertificateChain; private X509Certificate clientCertificate; private KeyPair clientKeyPair; - IoTDBKeyStoreLoaderClient load(Path baseDir) throws Exception { + IoTDBKeyStoreLoaderClient load(final Path baseDir, final char[] password) throws Exception { final KeyStore keyStore = KeyStore.getInstance("PKCS12"); final Path serverKeyStore = baseDir.resolve("example-client.pfx"); @@ -56,7 +55,7 @@ IoTDBKeyStoreLoaderClient load(Path baseDir) throws Exception { System.out.println("Loading KeyStore at " + serverKeyStore); if (!Files.exists(serverKeyStore)) { - keyStore.load(null, PASSWORD); + keyStore.load(null, password); final KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048); @@ -84,17 +83,17 @@ IoTDBKeyStoreLoaderClient load(Path baseDir) throws Exception { final X509Certificate certificate = builder.build(); keyStore.setKeyEntry( - CLIENT_ALIAS, keyPair.getPrivate(), PASSWORD, new X509Certificate[] {certificate}); + CLIENT_ALIAS, keyPair.getPrivate(), password, new X509Certificate[] {certificate}); try (OutputStream out = Files.newOutputStream(serverKeyStore)) { - keyStore.store(out, PASSWORD); + keyStore.store(out, password); } } else { try (InputStream in = Files.newInputStream(serverKeyStore)) { - keyStore.load(in, PASSWORD); + keyStore.load(in, password); } } - final Key clientPrivateKey = keyStore.getKey(CLIENT_ALIAS, PASSWORD); + final Key clientPrivateKey = keyStore.getKey(CLIENT_ALIAS, password); if (clientPrivateKey instanceof PrivateKey) { clientCertificate = (X509Certificate) keyStore.getCertificate(CLIENT_ALIAS); From d78b11d8a1522e1e1ddf22f541ad20503a8f4b20 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 11:16:37 +0800 Subject: [PATCH 19/47] sec-dir --- .../pipe/sink/protocol/opcua/OpcUaSink.java | 12 ++++++++++- .../protocol/opcua/client/ClientRunner.java | 21 +++++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 36a965659aee..8ed65dbbd339 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -56,6 +56,7 @@ import java.util.Arrays; import java.util.Map; import java.util.Objects; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -339,8 +340,17 @@ private void customizeClient(final String nodeUrl, final PipeParameters paramete Objects.nonNull(userName) ? new UsernameProvider(userName, password) : new AnonymousProvider(); + + final String securityDir = + IoTDBConfig.addDataHomeDir( + parameters.getStringOrDefault( + Arrays.asList(CONNECTOR_OPC_UA_SECURITY_DIR_KEY, SINK_OPC_UA_SECURITY_DIR_KEY), + CONNECTOR_OPC_UA_SECURITY_DIR_DEFAULT_VALUE + + File.separatorChar + + UUID.fromString(nodeUrl))); + client = new IoTDBOpcUaClient(nodeUrl, policy, provider); - new ClientRunner(client, password).run(); + new ClientRunner(client, securityDir, password).run(); } @Override diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java index 4ae2193b55e5..65e99078e056 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java @@ -51,28 +51,31 @@ public class ClientRunner { private final CompletableFuture future = new CompletableFuture<>(); private final IoTDBOpcUaClient configurableUaClient; + private final Path securityDir; private final String password; - public ClientRunner(final IoTDBOpcUaClient configurableUaClient, final String password) { + public ClientRunner( + final IoTDBOpcUaClient configurableUaClient, + final String securityDir, + final String password) { this.configurableUaClient = configurableUaClient; + this.securityDir = Paths.get(securityDir); this.password = password; } private OpcUaClient createClient() throws Exception { - final Path securityTempDir = - Paths.get(System.getProperty("java.io.tmpdir"), "client", "security"); - Files.createDirectories(securityTempDir); - if (!Files.exists(securityTempDir)) { - throw new Exception("unable to create security dir: " + securityTempDir); + Files.createDirectories(securityDir); + if (!Files.exists(securityDir)) { + throw new Exception("unable to create security dir: " + securityDir); } - final File pkiDir = securityTempDir.resolve("pki").toFile(); + final File pkiDir = securityDir.resolve("pki").toFile(); - logger.info("security dir: {}", securityTempDir.toAbsolutePath()); + logger.info("security dir: {}", securityDir.toAbsolutePath()); logger.info("security pki dir: {}", pkiDir.getAbsolutePath()); final IoTDBKeyStoreLoaderClient loader = - new IoTDBKeyStoreLoaderClient().load(securityTempDir, password.toCharArray()); + new IoTDBKeyStoreLoaderClient().load(securityDir, password.toCharArray()); final DefaultTrustListManager trustListManager = new DefaultTrustListManager(pkiDir); From 5edd5fd08d818e59fd170c337f6b7be614f4846f Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 11:22:01 +0800 Subject: [PATCH 20/47] cleaning --- .../protocol/opcua/client/ClientRunner.java | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java index 65e99078e056..a8f7d336a933 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java @@ -19,6 +19,9 @@ package org.apache.iotdb.db.pipe.sink.protocol.opcua.client; +import org.apache.iotdb.commons.pipe.config.PipeConfig; +import org.apache.iotdb.pipe.api.exception.PipeException; + import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.eclipse.milo.opcua.sdk.client.OpcUaClient; import org.eclipse.milo.opcua.stack.client.security.DefaultClientCertificateValidator; @@ -115,35 +118,17 @@ public void run() { Thread.currentThread().interrupt(); logger.warn("Error disconnecting: ", e); } - - try { - Thread.sleep(1000); - System.exit(0); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - e.printStackTrace(); - } }); try { configurableUaClient.run(client); - future.get(100000, TimeUnit.SECONDS); + future.get( + PipeConfig.getInstance().getPipeConnectorHandshakeTimeoutMs(), TimeUnit.MICROSECONDS); } catch (final Exception e) { - logger.warn("Error running client example: ", e); - future.completeExceptionally(e); + throw new PipeException("Error running client example.", e); } } catch (final Exception e) { - logger.warn("Error getting client: ", e); - - future.completeExceptionally(e); - - try { - Thread.sleep(1000); - System.exit(0); - } catch (InterruptedException interruptedException) { - Thread.currentThread().interrupt(); - logger.warn("Interrupted when running client: ", e); - } + throw new PipeException("Error getting client.", e); } } } From 2a3cdcf4177eb5897219bd5dee551e6b7670d60d Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 11:22:39 +0800 Subject: [PATCH 21/47] remove-poison --- .../iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java index a8f7d336a933..811af7a63e1b 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java @@ -25,7 +25,6 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.eclipse.milo.opcua.sdk.client.OpcUaClient; import org.eclipse.milo.opcua.stack.client.security.DefaultClientCertificateValidator; -import org.eclipse.milo.opcua.stack.core.Stack; import org.eclipse.milo.opcua.stack.core.security.DefaultTrustListManager; import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; import org.slf4j.Logger; @@ -113,7 +112,6 @@ public void run() { try { client.disconnect().get(); - Stack.releaseSharedResources(); } catch (final InterruptedException | ExecutionException e) { Thread.currentThread().interrupt(); logger.warn("Error disconnecting: ", e); From cacf80668bb96856cdbb5b22c336353014228073 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 12:02:21 +0800 Subject: [PATCH 22/47] f --- .../apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java | 3 ++- .../db/pipe/sink/protocol/opcua/client/ClientRunner.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 8ed65dbbd339..a108695183f5 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -40,6 +40,7 @@ import org.apache.iotdb.pipe.api.event.dml.insertion.TabletInsertionEvent; import org.apache.iotdb.pipe.api.exception.PipeException; +import org.apache.tsfile.common.conf.TSFileConfig; import org.apache.tsfile.utils.Pair; import org.apache.tsfile.write.record.Tablet; import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider; @@ -347,7 +348,7 @@ private void customizeClient(final String nodeUrl, final PipeParameters paramete Arrays.asList(CONNECTOR_OPC_UA_SECURITY_DIR_KEY, SINK_OPC_UA_SECURITY_DIR_KEY), CONNECTOR_OPC_UA_SECURITY_DIR_DEFAULT_VALUE + File.separatorChar - + UUID.fromString(nodeUrl))); + + UUID.nameUUIDFromBytes(nodeUrl.getBytes(TSFileConfig.STRING_CHARSET)))); client = new IoTDBOpcUaClient(nodeUrl, policy, provider); new ClientRunner(client, securityDir, password).run(); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java index 811af7a63e1b..007287ab19c5 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java @@ -123,10 +123,10 @@ public void run() { future.get( PipeConfig.getInstance().getPipeConnectorHandshakeTimeoutMs(), TimeUnit.MICROSECONDS); } catch (final Exception e) { - throw new PipeException("Error running client example.", e); + throw new PipeException("Error running client example: " + e.getMessage()); } } catch (final Exception e) { - throw new PipeException("Error getting client.", e); + throw new PipeException("Error getting client: " + e.getMessage()); } } } From 20095a124abcac9cc9adc7a49f07396e01563072 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 12:18:35 +0800 Subject: [PATCH 23/47] fix --- .../db/pipe/sink/protocol/opcua/client/ClientRunner.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java index 007287ab19c5..90027bab38ba 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java @@ -123,10 +123,10 @@ public void run() { future.get( PipeConfig.getInstance().getPipeConnectorHandshakeTimeoutMs(), TimeUnit.MICROSECONDS); } catch (final Exception e) { - throw new PipeException("Error running client example: " + e.getMessage()); + throw new PipeException("Error running opc client: " + e.getMessage()); } } catch (final Exception e) { - throw new PipeException("Error getting client: " + e.getMessage()); + throw new PipeException("Error getting opc client: " + e.getMessage()); } } } From 77c9cd3239e87aa0cc55d0b3d9aad2175732d6cd Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 14:28:39 +0800 Subject: [PATCH 24/47] clean-sit --- .../protocol/opcua/client/ClientRunner.java | 24 ++++--------------- .../client/IoTDBKeyStoreLoaderClient.java | 2 +- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java index 90027bab38ba..9a4ab45eaed6 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java @@ -50,8 +50,6 @@ public class ClientRunner { Security.addProvider(new BouncyCastleProvider()); } - private final CompletableFuture future = new CompletableFuture<>(); - private final IoTDBOpcUaClient configurableUaClient; private final Path securityDir; private final String password; @@ -104,29 +102,15 @@ public void run() { try { final OpcUaClient client = createClient(); - future.whenCompleteAsync( - (c, ex) -> { - if (ex != null) { - logger.warn("Error running opc client: ", ex); - } - - try { - client.disconnect().get(); - } catch (final InterruptedException | ExecutionException e) { - Thread.currentThread().interrupt(); - logger.warn("Error disconnecting: ", e); - } - }); - try { configurableUaClient.run(client); - future.get( - PipeConfig.getInstance().getPipeConnectorHandshakeTimeoutMs(), TimeUnit.MICROSECONDS); } catch (final Exception e) { - throw new PipeException("Error running opc client: " + e.getMessage()); + throw new PipeException( + "Error running opc client: " + e.getClass().getSimpleName() + ": " + e.getMessage()); } } catch (final Exception e) { - throw new PipeException("Error getting opc client: " + e.getMessage()); + throw new PipeException( + "Error getting opc client: " + e.getClass().getSimpleName() + ": " + e.getMessage()); } } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java index 2d95f174cb7e..b81bf1d7732d 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java @@ -66,7 +66,7 @@ IoTDBKeyStoreLoaderClient load(final Path baseDir, final char[] password) throws .setOrganizationalUnit("dev") .setLocalityName("Beijing") .setStateName("China") - .setCountryCode("UN") + .setCountryCode("CN") .setApplicationUri("urn:apache:iotdb:opc-ua-client") .addDnsName("localhost") .addIpAddress("127.0.0.1"); From 74a917c2beb47aba7b3a6b51cacb8a96930d3681 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 14:35:33 +0800 Subject: [PATCH 25/47] sit-comp --- .../db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index a952197c2923..0896cb087011 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -150,7 +150,7 @@ private void transferTabletRowForClientServerModel( throw new PipeException( "Failed to transfer dataValue after successfully created nodes, error: " + writeStatus); } - } else { + } else if (writeStatus.getValue() != StatusCode.GOOD.getValue()){ throw new PipeException("Failed to transfer dataValue, error: " + writeStatus); } } @@ -233,7 +233,7 @@ public static VariableAttributes createMeasurementAttributes( LocalizedText.english(name), Unsigned.uint(0), // writeMask Unsigned.uint(0), // userWriteMask - new Variant(initialValue), + initialValue, objectType, ValueRanks.Scalar, null, // arrayDimensions From 0abc6b27d2b473098f32bddcc26bfe3c7548a4b0 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 14:38:49 +0800 Subject: [PATCH 26/47] object --- .../iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java | 1 + 1 file changed, 1 insertion(+) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java index ddb8e161d08c..0ef020cf8191 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java @@ -510,6 +510,7 @@ public static NodeId convertToOpcDataType(final TSDataType type) { case STRING: return Identifiers.String; case VECTOR: + case OBJECT: case UNKNOWN: default: throw new PipeRuntimeNonCriticalException("Unsupported data type: " + type); From 1f07ae3c425e0262df0ffaa141cd846c1628f4f6 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 15:03:03 +0800 Subject: [PATCH 27/47] many-clean --- .../protocol/opcua/client/ClientRunner.java | 4 --- .../opcua/client/IoTDBOpcUaClient.java | 32 +++++++++++++++---- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java index 9a4ab45eaed6..725ecbbae98a 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/ClientRunner.java @@ -19,7 +19,6 @@ package org.apache.iotdb.db.pipe.sink.protocol.opcua.client; -import org.apache.iotdb.commons.pipe.config.PipeConfig; import org.apache.iotdb.pipe.api.exception.PipeException; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -35,9 +34,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.security.Security; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint; diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index 0896cb087011..ce54aaf52942 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -52,6 +52,7 @@ import org.eclipse.milo.opcua.stack.core.types.structured.VariableAttributes; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.function.Predicate; @@ -139,22 +140,39 @@ private void transferTabletRowForClientServerModel( if (!result.getStatusCode().equals(StatusCode.GOOD) && !(result.getStatusCode().getValue() == StatusCodes.Bad_NodeIdExists)) { throw new PipeException( - "Failed to create nodes after transfer data value, write status: " - + writeStatus - + ", creation status: " - + addStatus); + "Failed to create nodes after transfer data value, creation status: " + + addStatus + + getErrorString(segments, opcDataType, value, writeStatus)); } } writeStatus = client.writeValue(nodeId, dataValue).get(); if (writeStatus.getValue() != StatusCode.GOOD.getValue()) { throw new PipeException( - "Failed to transfer dataValue after successfully created nodes, error: " + writeStatus); + "Failed to transfer dataValue after successfully created nodes" + + getErrorString(segments, opcDataType, value, writeStatus)); } - } else if (writeStatus.getValue() != StatusCode.GOOD.getValue()){ - throw new PipeException("Failed to transfer dataValue, error: " + writeStatus); + } else if (writeStatus.getValue() != StatusCode.GOOD.getValue()) { + throw new PipeException( + "Failed to transfer dataValue" + + getErrorString(segments, opcDataType, value, writeStatus)); } } + private static String getErrorString( + final String[] segments, + final NodeId dataType, + final Object value, + final StatusCode writeStatus) { + return ", segments: " + + Arrays.toString(segments) + + ", dataType: " + + dataType + + ", value: " + + value + + ", error: " + + writeStatus; + } + public List getNodesToAdd( final String[] segments, final NodeId opcDataType, final Variant initialValue) { final List addNodesItems = new ArrayList<>(); From 4a1ad700b824f976434f54d135703219f445ac26 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 15:23:46 +0800 Subject: [PATCH 28/47] sit-sit --- .../pipe/sink/protocol/opcua/OpcUaSink.java | 77 ++++++++++--------- .../opcua/client/IoTDBOpcUaClient.java | 8 ++ 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index a108695183f5..c106da361d70 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -178,17 +178,50 @@ public void validate(final PipeParameterValidator validator) throws Exception { public void customize( final PipeParameters parameters, final PipeConnectorRuntimeConfiguration configuration) throws Exception { + final boolean withQuality = + parameters.getBooleanOrDefault( + Arrays.asList(CONNECTOR_OPC_UA_WITH_QUALITY_KEY, SINK_OPC_UA_WITH_QUALITY_KEY), + CONNECTOR_OPC_UA_WITH_QUALITY_DEFAULT_VALUE); + valueName = + withQuality + ? parameters.getStringOrDefault( + Arrays.asList(CONNECTOR_OPC_UA_VALUE_NAME_KEY, SINK_OPC_UA_VALUE_NAME_KEY), + CONNECTOR_OPC_UA_VALUE_NAME_DEFAULT_VALUE) + : null; + qualityName = + withQuality + ? parameters.getStringOrDefault( + Arrays.asList(CONNECTOR_OPC_UA_QUALITY_NAME_KEY, SINK_OPC_UA_QUALITY_NAME_KEY), + CONNECTOR_OPC_UA_QUALITY_NAME_DEFAULT_VALUE) + : null; + isClientServerModel = + parameters + .getStringOrDefault( + Arrays.asList(CONNECTOR_OPC_UA_MODEL_KEY, SINK_OPC_UA_MODEL_KEY), + CONNECTOR_OPC_UA_MODEL_DEFAULT_VALUE) + .equals(CONNECTOR_OPC_UA_MODEL_CLIENT_SERVER_VALUE); + placeHolder = + parameters.getStringOrDefault( + Arrays.asList(CONNECTOR_OPC_UA_PLACEHOLDER_KEY, SINK_OPC_UA_PLACEHOLDER_KEY), + CONNECTOR_OPC_UA_PLACEHOLDER_DEFAULT_VALUE); + final DataRegion region = + StorageEngine.getInstance() + .getDataRegion(new DataRegionId(configuration.getRuntimeEnvironment().getRegionId())); + unQualifiedDatabaseName = + Objects.nonNull(region) + ? PathUtils.unQualifyDatabaseName(region.getDatabaseName()) + : "__temp_db"; + final String nodeUrl = parameters.getStringByKeys(CONNECTOR_OPC_UA_NODE_URL_KEY, SINK_OPC_UA_NODE_URL_KEY); if (Objects.isNull(nodeUrl)) { - customizeServer(parameters, configuration); + customizeServer(parameters); } else { customizeClient(nodeUrl, parameters); } } - private void customizeServer( - final PipeParameters parameters, final PipeConnectorRuntimeConfiguration configuration) { + private void customizeServer(final PipeParameters parameters) { final int tcpBindPort = parameters.getIntOrDefault( Arrays.asList(CONNECTOR_OPC_UA_TCP_BIND_PORT_KEY, SINK_OPC_UA_TCP_BIND_PORT_KEY), @@ -225,40 +258,6 @@ private void customizeServer( CONNECTOR_OPC_UA_ENABLE_ANONYMOUS_ACCESS_KEY, SINK_OPC_UA_ENABLE_ANONYMOUS_ACCESS_KEY), CONNECTOR_OPC_UA_ENABLE_ANONYMOUS_ACCESS_DEFAULT_VALUE); - placeHolder = - parameters.getStringOrDefault( - Arrays.asList(CONNECTOR_OPC_UA_PLACEHOLDER_KEY, SINK_OPC_UA_PLACEHOLDER_KEY), - CONNECTOR_OPC_UA_PLACEHOLDER_DEFAULT_VALUE); - final boolean withQuality = - parameters.getBooleanOrDefault( - Arrays.asList(CONNECTOR_OPC_UA_WITH_QUALITY_KEY, SINK_OPC_UA_WITH_QUALITY_KEY), - CONNECTOR_OPC_UA_WITH_QUALITY_DEFAULT_VALUE); - valueName = - withQuality - ? parameters.getStringOrDefault( - Arrays.asList(CONNECTOR_OPC_UA_VALUE_NAME_KEY, SINK_OPC_UA_VALUE_NAME_KEY), - CONNECTOR_OPC_UA_VALUE_NAME_DEFAULT_VALUE) - : null; - qualityName = - withQuality - ? parameters.getStringOrDefault( - Arrays.asList(CONNECTOR_OPC_UA_QUALITY_NAME_KEY, SINK_OPC_UA_QUALITY_NAME_KEY), - CONNECTOR_OPC_UA_QUALITY_NAME_DEFAULT_VALUE) - : null; - isClientServerModel = - parameters - .getStringOrDefault( - Arrays.asList(CONNECTOR_OPC_UA_MODEL_KEY, SINK_OPC_UA_MODEL_KEY), - CONNECTOR_OPC_UA_MODEL_DEFAULT_VALUE) - .equals(CONNECTOR_OPC_UA_MODEL_CLIENT_SERVER_VALUE); - - final DataRegion region = - StorageEngine.getInstance() - .getDataRegion(new DataRegionId(configuration.getRuntimeEnvironment().getRegionId())); - unQualifiedDatabaseName = - Objects.nonNull(region) - ? PathUtils.unQualifyDatabaseName(region.getDatabaseName()) - : "__temp_db"; synchronized (SERVER_KEY_TO_REFERENCE_COUNT_AND_NAME_SPACE_MAP) { serverKey = httpsBindPort + ":" + tcpBindPort; @@ -448,6 +447,10 @@ public interface ThrowingBiConsumer { @Override public void close() throws Exception { + if (Objects.nonNull(client)) { + client.disconnect(); + } + if (serverKey == null) { return; } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index ce54aaf52942..4397c401d3f3 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -128,6 +128,10 @@ private void transferTabletRowForClientServerModel( timestamp = utcTimestamp; opcDataType = convertToOpcDataType(type); } + if (Objects.isNull(value)) { + return; + } + final Variant variant = new Variant(value); final DataValue dataValue = new DataValue(variant, currentQuality, new DateTime(timestamp), new DateTime()); @@ -223,6 +227,10 @@ public List getNodesToAdd( return addNodesItems; } + public void disconnect() throws Exception { + client.disconnect().get(); + } + /////////////////////////////// Getter /////////////////////////////// String getNodeUrl() { From 0c556ea28fd1f8cb7e03376dc185b370e9ea8ddf Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:04:50 +0800 Subject: [PATCH 29/47] fix --- .../pipe/sink/protocol/opcua/OpcUaSink.java | 29 ++++++++++++------- .../protocol/opcua/server/OpcUaNameSpace.java | 4 +-- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index c106da361d70..7d4814352621 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -128,7 +128,7 @@ public class OpcUaSink implements PipeConnector { private String serverKey; boolean isClientServerModel; - String unQualifiedDatabaseName; + String databaseName; String placeHolder; @Nullable String valueName; @Nullable String qualityName; @@ -159,12 +159,15 @@ public void validate(final PipeParameterValidator validator) throws Exception { final PipeParameters parameters = validator.getParameters(); if (validator - .getParameters() - .hasAnyAttributes(CONNECTOR_OPC_UA_NODE_URL_KEY, SINK_OPC_UA_NODE_URL_KEY)) { + .getParameters() + .hasAnyAttributes(CONNECTOR_OPC_UA_NODE_URL_KEY, SINK_OPC_UA_NODE_URL_KEY) + || parameters.getBooleanOrDefault( + Arrays.asList(CONNECTOR_OPC_UA_WITH_QUALITY_KEY, SINK_OPC_UA_WITH_QUALITY_KEY), + CONNECTOR_OPC_UA_WITH_QUALITY_DEFAULT_VALUE)) { validator.validate( CONNECTOR_OPC_UA_MODEL_CLIENT_SERVER_VALUE::equals, String.format( - "When the OPC UA sink points to an outer server or specifies 'with-quality', the %s or %s must be %s.", + "When the OPC UA sink points to an outer server or sets 'with-quality' to true, the %s or %s must be %s.", CONNECTOR_OPC_UA_MODEL_KEY, SINK_OPC_UA_MODEL_KEY, CONNECTOR_OPC_UA_MODEL_CLIENT_SERVER_VALUE), @@ -207,16 +210,22 @@ public void customize( final DataRegion region = StorageEngine.getInstance() .getDataRegion(new DataRegionId(configuration.getRuntimeEnvironment().getRegionId())); - unQualifiedDatabaseName = - Objects.nonNull(region) - ? PathUtils.unQualifyDatabaseName(region.getDatabaseName()) - : "__temp_db"; + databaseName = Objects.nonNull(region) ? region.getDatabaseName() : "__temp_db"; + + if (withQuality && PathUtils.isTableModelDatabase(databaseName)) { + throw new PipeException( + "When the OPC UA sink sets 'with-quality' to true, the table model data is not supported."); + } final String nodeUrl = parameters.getStringByKeys(CONNECTOR_OPC_UA_NODE_URL_KEY, SINK_OPC_UA_NODE_URL_KEY); if (Objects.isNull(nodeUrl)) { customizeServer(parameters); } else { + if (PathUtils.isTableModelDatabase(databaseName)) { + throw new PipeException( + "When the OPC UA sink points to an outer server, the table model data is not supported."); + } customizeClient(nodeUrl, parameters); } } @@ -478,8 +487,8 @@ public boolean isClientServerModel() { return isClientServerModel; } - public String getUnQualifiedDatabaseName() { - return unQualifiedDatabaseName; + public String getDatabaseName() { + return databaseName; } public String getPlaceHolder() { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java index 0ef020cf8191..93e078627532 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java @@ -146,7 +146,7 @@ public static void transferTabletForClientServerModel( for (int i = 0; i < tablet.getRowSize(); ++i) { final Object[] segments = tablet.getDeviceID(i).getSegments(); final String[] folderSegments = new String[segments.length + 1]; - folderSegments[0] = sink.getUnQualifiedDatabaseName(); + folderSegments[0] = sink.getDatabaseName(); for (int j = 0; j < segments.length; ++j) { folderSegments[j + 1] = @@ -382,7 +382,7 @@ private void transferTabletForPubSubModel( if (isTableModel) { sourceNameList = new ArrayList<>(tablet.getRowSize()); for (int i = 0; i < tablet.getRowSize(); ++i) { - final StringBuilder idBuilder = new StringBuilder(sink.getUnQualifiedDatabaseName()); + final StringBuilder idBuilder = new StringBuilder(sink.getDatabaseName()); for (final Object segment : tablet.getDeviceID(i).getSegments()) { idBuilder .append(TsFileConstant.PATH_SEPARATOR) From c6d1170421c81bc372231573edced68492bb1dc9 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:27:11 +0800 Subject: [PATCH 30/47] fix --- .../iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java | 12 +++++++++++- .../sink/protocol/opcua/client/IoTDBOpcUaClient.java | 12 +++++++----- .../pipe/config/constant/PipeSinkConstant.java | 4 ++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 7d4814352621..0e0ccc1b3ba7 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -68,6 +68,8 @@ import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_IOTDB_USER_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_ENABLE_ANONYMOUS_ACCESS_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_ENABLE_ANONYMOUS_ACCESS_KEY; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_HISTORIZING_DEFAULT_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_HISTORIZING_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_HTTPS_BIND_PORT_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_HTTPS_BIND_PORT_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_MODEL_CLIENT_SERVER_VALUE; @@ -98,6 +100,7 @@ import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_IOTDB_USERNAME_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_IOTDB_USER_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_ENABLE_ANONYMOUS_ACCESS_KEY; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_HISTORIZING_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_HTTPS_BIND_PORT_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_MODEL_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_NODE_URL_KEY; @@ -358,7 +361,14 @@ private void customizeClient(final String nodeUrl, final PipeParameters paramete + File.separatorChar + UUID.nameUUIDFromBytes(nodeUrl.getBytes(TSFileConfig.STRING_CHARSET)))); - client = new IoTDBOpcUaClient(nodeUrl, policy, provider); + client = + new IoTDBOpcUaClient( + nodeUrl, + policy, + provider, + parameters.getBooleanOrDefault( + Arrays.asList(CONNECTOR_OPC_UA_HISTORIZING_KEY, SINK_OPC_UA_HISTORIZING_KEY), + CONNECTOR_OPC_UA_HISTORIZING_DEFAULT_VALUE)); new ClientRunner(client, securityDir, password).run(); } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index 4397c401d3f3..e5edc3a24182 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -67,14 +67,17 @@ public class IoTDBOpcUaClient { private final SecurityPolicy securityPolicy; private final IdentityProvider identityProvider; private OpcUaClient client; + private boolean historizing; public IoTDBOpcUaClient( final String nodeUrl, final SecurityPolicy securityPolicy, - final IdentityProvider identityProvider) { + final IdentityProvider identityProvider, + final boolean historizing) { this.nodeUrl = nodeUrl; this.securityPolicy = securityPolicy; this.identityProvider = identityProvider; + this.historizing = historizing; } public void run(final OpcUaClient client) throws Exception { @@ -251,7 +254,7 @@ IdentityProvider getIdentityProvider() { /////////////////////////////// Attribute creator /////////////////////////////// - public static VariableAttributes createMeasurementAttributes( + private VariableAttributes createMeasurementAttributes( final String name, final NodeId objectType, final Variant initialValue) { return new VariableAttributes( Unsigned.uint(0xFFFF), // specifiedAttributes @@ -266,11 +269,10 @@ public static VariableAttributes createMeasurementAttributes( AccessLevel.toValue(AccessLevel.READ_WRITE), AccessLevel.toValue(AccessLevel.READ_WRITE), 500.0, // samplingInterval - false // historizing - ); + historizing); } - public static ObjectAttributes createFolderAttributes(final String name) { + private static ObjectAttributes createFolderAttributes(final String name) { return new ObjectAttributes( Unsigned.uint(0xFFFF), // specifiedAttributes LocalizedText.english(name), diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java index 6fd228db7ec9..383a29e05677 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java @@ -211,6 +211,10 @@ public class PipeSinkConstant { public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE = "AES256_SHA256_RSAPSS"; + public static final String CONNECTOR_OPC_UA_HISTORIZING_KEY = "connector.opcua.historizing"; + public static final String SINK_OPC_UA_HISTORIZING_KEY = "sink.opcua.historizing"; + public static final boolean CONNECTOR_OPC_UA_HISTORIZING_DEFAULT_VALUE = false; + 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; From 4d700882e1607638a787904ec151b3172a14b26c Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:35:18 +0800 Subject: [PATCH 31/47] fix --- .../sink/protocol/opcua/server/OpcUaServerBuilder.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java index 1d4334828536..5bc0a0e34b9d 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java @@ -19,7 +19,6 @@ package org.apache.iotdb.db.pipe.sink.protocol.opcua.server; -import org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant; import org.apache.iotdb.pipe.api.exception.PipeException; import org.eclipse.milo.opcua.sdk.server.OpcUaServer; @@ -86,15 +85,6 @@ public class OpcUaServerBuilder implements Closeable { private boolean enableAnonymousAccess; private DefaultTrustListManager trustListManager; - public OpcUaServerBuilder() { - tcpBindPort = PipeSinkConstant.CONNECTOR_OPC_UA_TCP_BIND_PORT_DEFAULT_VALUE; - httpsBindPort = PipeSinkConstant.CONNECTOR_OPC_UA_HTTPS_BIND_PORT_DEFAULT_VALUE; - user = PipeSinkConstant.CONNECTOR_IOTDB_USER_DEFAULT_VALUE; - password = PipeSinkConstant.CONNECTOR_IOTDB_PASSWORD_DEFAULT_VALUE; - securityDir = Paths.get(PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_DIR_DEFAULT_VALUE); - enableAnonymousAccess = PipeSinkConstant.CONNECTOR_OPC_UA_ENABLE_ANONYMOUS_ACCESS_DEFAULT_VALUE; - } - public OpcUaServerBuilder setTcpBindPort(final int tcpBindPort) { this.tcpBindPort = tcpBindPort; return this; From 6f25d12bbc32d0f0be5d1cfb38e40bd613bf3632 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:42:05 +0800 Subject: [PATCH 32/47] ref --- .../protocol/opcua/client/IoTDBKeyStoreLoaderClient.java | 6 +++++- .../pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java index b81bf1d7732d..2b7ba3502e3d 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java @@ -22,6 +22,8 @@ import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil; import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder; import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; @@ -38,6 +40,8 @@ class IoTDBKeyStoreLoaderClient { + private static final Logger logger = LoggerFactory.getLogger(ClientRunner.class); + private static final Pattern IP_ADDR_PATTERN = Pattern.compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"); @@ -52,7 +56,7 @@ IoTDBKeyStoreLoaderClient load(final Path baseDir, final char[] password) throws final Path serverKeyStore = baseDir.resolve("example-client.pfx"); - System.out.println("Loading KeyStore at " + serverKeyStore); + logger.info("Loading KeyStore at {}.", serverKeyStore); if (!Files.exists(serverKeyStore)) { keyStore.load(null, password); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index e5edc3a24182..aa6dcdb9bc5e 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -67,7 +67,7 @@ public class IoTDBOpcUaClient { private final SecurityPolicy securityPolicy; private final IdentityProvider identityProvider; private OpcUaClient client; - private boolean historizing; + private final boolean historizing; public IoTDBOpcUaClient( final String nodeUrl, From 727f007b1b461d2f9c9981ef8a43a447a36f410d Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 23 Dec 2025 18:29:48 +0800 Subject: [PATCH 33/47] sit --- .../org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 0e0ccc1b3ba7..5907e9899def 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -213,7 +213,7 @@ public void customize( final DataRegion region = StorageEngine.getInstance() .getDataRegion(new DataRegionId(configuration.getRuntimeEnvironment().getRegionId())); - databaseName = Objects.nonNull(region) ? region.getDatabaseName() : "__temp_db"; + databaseName = Objects.nonNull(region) ? region.getDatabaseName() : "root.__temp_db"; if (withQuality && PathUtils.isTableModelDatabase(databaseName)) { throw new PipeException( From 2645e83de8ebcd9a754d247f6582773201cee66d Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Wed, 24 Dec 2025 16:23:59 +0800 Subject: [PATCH 34/47] partial --- .../sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java index 2b7ba3502e3d..bfaf378822c3 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBKeyStoreLoaderClient.java @@ -54,7 +54,7 @@ class IoTDBKeyStoreLoaderClient { IoTDBKeyStoreLoaderClient load(final Path baseDir, final char[] password) throws Exception { final KeyStore keyStore = KeyStore.getInstance("PKCS12"); - final Path serverKeyStore = baseDir.resolve("example-client.pfx"); + final Path serverKeyStore = baseDir.resolve("iotdb-client.pfx"); logger.info("Loading KeyStore at {}.", serverKeyStore); From 48d02de3d03bad9b49b7dea297a4cb9a07a88e62 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:19:54 +0800 Subject: [PATCH 35/47] security-policies --- .../pipe/sink/protocol/opcua/OpcUaSink.java | 74 +++++++++++-------- .../opcua/server/OpcUaServerBuilder.java | 59 +++++++++------ .../config/constant/PipeSinkConstant.java | 7 ++ 3 files changed, 87 insertions(+), 53 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 5907e9899def..fafa68504b1a 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -57,9 +57,11 @@ import java.util.Arrays; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_IOTDB_PASSWORD_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_IOTDB_PASSWORD_KEY; @@ -90,6 +92,7 @@ import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_DIR_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_DIR_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_POLICY_KEY; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_POLICY_SERVER_DEFAULT_VALUES; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_TCP_BIND_PORT_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_TCP_BIND_PORT_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_VALUE_NAME_DEFAULT_VALUE; @@ -270,6 +273,18 @@ private void customizeServer(final PipeParameters parameters) { CONNECTOR_OPC_UA_ENABLE_ANONYMOUS_ACCESS_KEY, SINK_OPC_UA_ENABLE_ANONYMOUS_ACCESS_KEY), CONNECTOR_OPC_UA_ENABLE_ANONYMOUS_ACCESS_DEFAULT_VALUE); + final Set securityPolicies = + (parameters.hasAnyAttributes( + CONNECTOR_OPC_UA_SECURITY_POLICY_KEY, SINK_OPC_UA_SECURITY_POLICY_KEY) + ? Arrays.stream( + parameters + .getStringByKeys( + CONNECTOR_OPC_UA_SECURITY_POLICY_KEY, SINK_OPC_UA_SECURITY_POLICY_KEY) + .replace(" ", "") + .split(",")) + : CONNECTOR_OPC_UA_SECURITY_POLICY_SERVER_DEFAULT_VALUES.stream()) + .map(this::getSecurityPolicy) + .collect(Collectors.toSet()); synchronized (SERVER_KEY_TO_REFERENCE_COUNT_AND_NAME_SPACE_MAP) { serverKey = httpsBindPort + ":" + tcpBindPort; @@ -288,7 +303,8 @@ private void customizeServer(final PipeParameters parameters) { .setUser(user) .setPassword(password) .setSecurityDir(securityDir) - .setEnableAnonymousAccess(enableAnonymousAccess); + .setEnableAnonymousAccess(enableAnonymousAccess) + .setSecurityPolicies(securityPolicies); final OpcUaServer newServer = builder.build(); nameSpace = new OpcUaNameSpace(newServer, builder); nameSpace.startup(); @@ -312,34 +328,14 @@ private void customizeServer(final PipeParameters parameters) { } private void customizeClient(final String nodeUrl, final PipeParameters parameters) { - final SecurityPolicy policy; - switch (parameters - .getStringOrDefault( - Arrays.asList(CONNECTOR_OPC_UA_SECURITY_POLICY_KEY, SINK_OPC_UA_SECURITY_POLICY_KEY), - CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE) - .toUpperCase()) { - case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_NONE_VALUE: - policy = SecurityPolicy.None; - break; - case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_128_RSA_15_VALUE: - policy = SecurityPolicy.Basic128Rsa15; - break; - case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_VALUE: - policy = SecurityPolicy.Basic256; - break; - case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE: - policy = SecurityPolicy.Basic256Sha256; - break; - case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE: - policy = SecurityPolicy.Aes128_Sha256_RsaOaep; - break; - case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE: - policy = SecurityPolicy.Aes256_Sha256_RsaPss; - break; - default: - throw new PipeException( - "The security policy can only be 'None', 'Basic128Rsa15', 'Basic256', 'Basic256Sha256', 'Aes128_Sha256_RsaOaep' or 'Aes256_Sha256_RsaPss'."); - } + final SecurityPolicy policy = + getSecurityPolicy( + parameters + .getStringOrDefault( + Arrays.asList( + CONNECTOR_OPC_UA_SECURITY_POLICY_KEY, SINK_OPC_UA_SECURITY_POLICY_KEY), + CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE) + .toUpperCase()); final IdentityProvider provider; final String userName = @@ -372,6 +368,26 @@ private void customizeClient(final String nodeUrl, final PipeParameters paramete new ClientRunner(client, securityDir, password).run(); } + private SecurityPolicy getSecurityPolicy(final String securityPolicy) { + switch (securityPolicy.toUpperCase()) { + case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_NONE_VALUE: + return SecurityPolicy.None; + case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_128_RSA_15_VALUE: + return SecurityPolicy.Basic128Rsa15; + case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_VALUE: + return SecurityPolicy.Basic256; + case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE: + return SecurityPolicy.Basic256Sha256; + case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE: + return SecurityPolicy.Aes128_Sha256_RsaOaep; + case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE: + return SecurityPolicy.Aes256_Sha256_RsaPss; + default: + throw new PipeException( + "The security policy can only be 'None', 'Basic128Rsa15', 'Basic256', 'Basic256Sha256', 'Aes128_Sha256_RsaOaep' or 'Aes256_Sha256_RsaPss'."); + } + } + @Override public void handshake() throws Exception { // Server side, do nothing diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java index 5bc0a0e34b9d..6ed4b210c187 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java @@ -83,6 +83,7 @@ public class OpcUaServerBuilder implements Closeable { private String password; private Path securityDir; private boolean enableAnonymousAccess; + private Set securityPolicies; private DefaultTrustListManager trustListManager; public OpcUaServerBuilder setTcpBindPort(final int tcpBindPort) { @@ -115,6 +116,11 @@ public OpcUaServerBuilder setEnableAnonymousAccess(final boolean enableAnonymous return this; } + public OpcUaServerBuilder setSecurityPolicies(final Set securityPolicies) { + this.securityPolicies = securityPolicies; + return this; + } + public OpcUaServer build() throws Exception { Files.createDirectories(securityDir); if (!Files.exists(securityDir)) { @@ -237,30 +243,35 @@ private Set createEndpointConfigurations( USER_TOKEN_POLICY_USERNAME, USER_TOKEN_POLICY_X509); - final EndpointConfiguration.Builder noSecurityBuilder = - builder - .copy() - .setSecurityPolicy(SecurityPolicy.None) - .setSecurityMode(MessageSecurityMode.None); - - endpointConfigurations.add(buildTcpEndpoint(noSecurityBuilder, tcpBindPort)); - endpointConfigurations.add(buildHttpsEndpoint(noSecurityBuilder, httpsBindPort)); - - endpointConfigurations.add( - buildTcpEndpoint( - builder - .copy() - .setSecurityPolicy(SecurityPolicy.Basic256Sha256) - .setSecurityMode(MessageSecurityMode.SignAndEncrypt), - tcpBindPort)); - - endpointConfigurations.add( - buildHttpsEndpoint( - builder - .copy() - .setSecurityPolicy(SecurityPolicy.Basic256Sha256) - .setSecurityMode(MessageSecurityMode.Sign), - httpsBindPort)); + if (securityPolicies.contains(SecurityPolicy.None)) { + final EndpointConfiguration.Builder noSecurityBuilder = + builder + .copy() + .setSecurityPolicy(SecurityPolicy.None) + .setSecurityMode(MessageSecurityMode.None); + + endpointConfigurations.add(buildTcpEndpoint(noSecurityBuilder, tcpBindPort)); + endpointConfigurations.add(buildHttpsEndpoint(noSecurityBuilder, httpsBindPort)); + securityPolicies.remove(SecurityPolicy.None); + } + + for (final SecurityPolicy securityPolicy : securityPolicies) { + endpointConfigurations.add( + buildTcpEndpoint( + builder + .copy() + .setSecurityPolicy(securityPolicy) + .setSecurityMode(MessageSecurityMode.SignAndEncrypt), + tcpBindPort)); + + endpointConfigurations.add( + buildHttpsEndpoint( + builder + .copy() + .setSecurityPolicy(securityPolicy) + .setSecurityMode(MessageSecurityMode.Sign), + httpsBindPort)); + } final EndpointConfiguration.Builder discoveryBuilder = builder diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java index 383a29e05677..0f271c20cf44 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java @@ -28,6 +28,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import static org.apache.iotdb.commons.conf.IoTDBConstant.MB; @@ -211,6 +212,12 @@ public class PipeSinkConstant { public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE = "AES256_SHA256_RSAPSS"; + public static final List CONNECTOR_OPC_UA_SECURITY_POLICY_SERVER_DEFAULT_VALUES = + Arrays.asList( + CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE, + CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE, + CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE); + public static final String CONNECTOR_OPC_UA_HISTORIZING_KEY = "connector.opcua.historizing"; public static final String SINK_OPC_UA_HISTORIZING_KEY = "sink.opcua.historizing"; public static final boolean CONNECTOR_OPC_UA_HISTORIZING_DEFAULT_VALUE = false; From 3b89d41b5e392e3b992c1f8b83cda419dca63f11 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:33:18 +0800 Subject: [PATCH 36/47] check-equals --- .../iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java | 7 ++++++- .../pipe/sink/protocol/opcua/server/OpcUaNameSpace.java | 8 ++++++-- .../sink/protocol/opcua/server/OpcUaServerBuilder.java | 4 +++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index fafa68504b1a..7dfc0d70ad77 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -313,7 +313,12 @@ private void customizeServer(final PipeParameters parameters) { } else { oldValue .getRight() - .checkEquals(user, password, securityDir, enableAnonymousAccess); + .checkEquals( + user, + password, + securityDir, + enableAnonymousAccess, + securityPolicies); return oldValue; } } catch (final PipeException e) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java index 93e078627532..f29185be1cc3 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java @@ -49,6 +49,7 @@ import org.eclipse.milo.opcua.sdk.server.util.SubscriptionModel; import org.eclipse.milo.opcua.stack.core.Identifiers; import org.eclipse.milo.opcua.stack.core.UaException; +import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime; import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText; @@ -65,6 +66,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -543,7 +545,9 @@ public void checkEquals( final String user, final String password, final String securityDir, - final boolean enableAnonymousAccess) { - builder.checkEquals(user, password, Paths.get(securityDir), enableAnonymousAccess); + final boolean enableAnonymousAccess, + final Set securityPolicies) { + builder.checkEquals( + user, password, Paths.get(securityDir), enableAnonymousAccess, securityPolicies); } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java index 6ed4b210c187..05c6eeb33690 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java @@ -310,7 +310,8 @@ void checkEquals( final String user, final String password, final Path securityDir, - final boolean enableAnonymousAccess) { + final boolean enableAnonymousAccess, + final Set securityPolicies) { checkEquals("user", this.user, user); checkEquals("password", this.password, password); checkEquals( @@ -318,6 +319,7 @@ void checkEquals( FileSystems.getDefault().getPath(this.securityDir.toAbsolutePath().toString()), FileSystems.getDefault().getPath(securityDir.toAbsolutePath().toString())); checkEquals("enableAnonymousAccess option", this.enableAnonymousAccess, enableAnonymousAccess); + checkEquals("securityPolicies", this.securityPolicies, securityPolicies); } private void checkEquals(final String attrName, final Object thisAttr, final Object thatAttr) { From f5baef549a45796026204f64f0299612ed66d405 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Wed, 24 Dec 2025 18:51:47 +0800 Subject: [PATCH 37/47] check-err --- .../apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 7dfc0d70ad77..a88a1a13d76e 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -285,6 +285,9 @@ private void customizeServer(final PipeParameters parameters) { : CONNECTOR_OPC_UA_SECURITY_POLICY_SERVER_DEFAULT_VALUES.stream()) .map(this::getSecurityPolicy) .collect(Collectors.toSet()); + if (securityPolicies.isEmpty()) { + throw new PipeException("The security policy cannot be empty."); + } synchronized (SERVER_KEY_TO_REFERENCE_COUNT_AND_NAME_SPACE_MAP) { serverKey = httpsBindPort + ":" + tcpBindPort; From 32c0a61ec36a09066ba8d4319586a42f2a0bc164 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Wed, 24 Dec 2025 19:04:01 +0800 Subject: [PATCH 38/47] fix --- .../pipe/sink/protocol/opcua/OpcUaSink.java | 26 +++++++++---------- .../config/constant/PipeSinkConstant.java | 18 ++++++------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index a88a1a13d76e..6dd09db1c2a6 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -83,15 +83,15 @@ import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_PLACEHOLDER_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_NAME_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_NAME_KEY; -import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE; -import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE; -import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_128_RSA_15_VALUE; -import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE; -import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_VALUE; -import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_NONE_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_DIR_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_DIR_KEY; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_POLICY_BASIC_128_RSA_15_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_POLICY_BASIC_256_SHA_256_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_POLICY_BASIC_256_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_POLICY_KEY; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_POLICY_NONE_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_POLICY_SERVER_DEFAULT_VALUES; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_TCP_BIND_PORT_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_TCP_BIND_PORT_KEY; @@ -342,7 +342,7 @@ private void customizeClient(final String nodeUrl, final PipeParameters paramete .getStringOrDefault( Arrays.asList( CONNECTOR_OPC_UA_SECURITY_POLICY_KEY, SINK_OPC_UA_SECURITY_POLICY_KEY), - CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE) + CONNECTOR_OPC_UA_SECURITY_POLICY_BASIC_256_SHA_256_VALUE) .toUpperCase()); final IdentityProvider provider; @@ -378,17 +378,17 @@ private void customizeClient(final String nodeUrl, final PipeParameters paramete private SecurityPolicy getSecurityPolicy(final String securityPolicy) { switch (securityPolicy.toUpperCase()) { - case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_NONE_VALUE: + case CONNECTOR_OPC_UA_SECURITY_POLICY_NONE_VALUE: return SecurityPolicy.None; - case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_128_RSA_15_VALUE: + case CONNECTOR_OPC_UA_SECURITY_POLICY_BASIC_128_RSA_15_VALUE: return SecurityPolicy.Basic128Rsa15; - case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_VALUE: + case CONNECTOR_OPC_UA_SECURITY_POLICY_BASIC_256_VALUE: return SecurityPolicy.Basic256; - case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE: + case CONNECTOR_OPC_UA_SECURITY_POLICY_BASIC_256_SHA_256_VALUE: return SecurityPolicy.Basic256Sha256; - case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE: + case CONNECTOR_OPC_UA_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE: return SecurityPolicy.Aes128_Sha256_RsaOaep; - case CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE: + case CONNECTOR_OPC_UA_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE: return SecurityPolicy.Aes256_Sha256_RsaPss; default: throw new PipeException( diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java index 0f271c20cf44..22f01a188fa1 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java @@ -201,22 +201,22 @@ public class PipeSinkConstant { public static final String CONNECTOR_OPC_UA_SECURITY_POLICY_KEY = "connector.opcua.security-policy"; public static final String SINK_OPC_UA_SECURITY_POLICY_KEY = "sink.opcua.security-policy"; - public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_NONE_VALUE = "NONE"; - public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_128_RSA_15_VALUE = + public static final String CONNECTOR_OPC_UA_SECURITY_POLICY_NONE_VALUE = "NONE"; + public static final String CONNECTOR_OPC_UA_SECURITY_POLICY_BASIC_128_RSA_15_VALUE = "BASIC128RSA15"; - public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_VALUE = "BASIC256"; - public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE = + public static final String CONNECTOR_OPC_UA_SECURITY_POLICY_BASIC_256_VALUE = "BASIC256"; + public static final String CONNECTOR_OPC_UA_SECURITY_POLICY_BASIC_256_SHA_256_VALUE = "BASIC256SHA256"; - public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE = + public static final String CONNECTOR_OPC_UA_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE = "AES128_SHA256_RSAOAEP"; - public static final String CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE = + public static final String CONNECTOR_OPC_UA_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE = "AES256_SHA256_RSAPSS"; public static final List CONNECTOR_OPC_UA_SECURITY_POLICY_SERVER_DEFAULT_VALUES = Arrays.asList( - CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_BASIC_256_SHA_256_VALUE, - CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE, - CONNECTOR_OPC_UA_QUALITY_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE); + CONNECTOR_OPC_UA_SECURITY_POLICY_BASIC_256_SHA_256_VALUE, + CONNECTOR_OPC_UA_SECURITY_POLICY_AES128_SHA256_RSAOAEP_VALUE, + CONNECTOR_OPC_UA_SECURITY_POLICY_AES256_SHA256_RSAPSS_VALUE); public static final String CONNECTOR_OPC_UA_HISTORIZING_KEY = "connector.opcua.historizing"; public static final String SINK_OPC_UA_HISTORIZING_KEY = "sink.opcua.historizing"; From 4861ca3c2a42ab6a5ae8e171616b0ad766aa722a Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Thu, 25 Dec 2025 10:46:20 +0800 Subject: [PATCH 39/47] compile-fix --- .../db/queryengine/plan/relational/sql/ast/CreatePipe.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/CreatePipe.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/CreatePipe.java index 1543e339fc86..269978e87bde 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/CreatePipe.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/CreatePipe.java @@ -115,9 +115,9 @@ public long ramBytesUsed() { long size = INSTANCE_SIZE; size += AstMemoryEstimationHelper.getEstimatedSizeOfNodeLocation(getLocationInternal()); size += RamUsageEstimator.sizeOf(pipeName); - size += RamUsageEstimator.sizeOfMap(extractorAttributes); + size += RamUsageEstimator.sizeOfMap(sourceAttributes); size += RamUsageEstimator.sizeOfMap(processorAttributes); - size += RamUsageEstimator.sizeOfMap(connectorAttributes); + size += RamUsageEstimator.sizeOfMap(sinkAttributes); return size; } } From efd189b5aae8a6765a4a4488485ef4f68da36683 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Thu, 25 Dec 2025 11:28:53 +0800 Subject: [PATCH 40/47] adjust --- .../pipe/sink/protocol/opcua/OpcUaSink.java | 42 ++++++++++++++++--- .../opcua/client/IoTDBOpcUaClient.java | 11 +++-- .../protocol/opcua/server/OpcUaNameSpace.java | 11 +++-- .../config/constant/PipeSinkConstant.java | 7 ++++ 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 6dd09db1c2a6..60fc6c371f81 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -48,6 +48,7 @@ import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider; import org.eclipse.milo.opcua.sdk.server.OpcUaServer; import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; +import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,6 +69,10 @@ import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_IOTDB_USERNAME_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_IOTDB_USER_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_IOTDB_USER_KEY; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_DEFAULT_QUALITY_BAD_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_DEFAULT_QUALITY_GOOD_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_DEFAULT_QUALITY_KEY; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_DEFAULT_QUALITY_UNCERTAIN_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_ENABLE_ANONYMOUS_ACCESS_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_ENABLE_ANONYMOUS_ACCESS_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_HISTORIZING_DEFAULT_VALUE; @@ -102,6 +107,7 @@ import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_IOTDB_PASSWORD_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_IOTDB_USERNAME_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_IOTDB_USER_KEY; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_DEFAULT_QUALITY_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_ENABLE_ANONYMOUS_ACCESS_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_HISTORIZING_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.SINK_OPC_UA_HTTPS_BIND_PORT_KEY; @@ -133,11 +139,12 @@ public class OpcUaSink implements PipeConnector { SERVER_KEY_TO_REFERENCE_COUNT_AND_NAME_SPACE_MAP = new ConcurrentHashMap<>(); private String serverKey; - boolean isClientServerModel; - String databaseName; - String placeHolder; - @Nullable String valueName; - @Nullable String qualityName; + private boolean isClientServerModel; + private String databaseName; + private String placeHolder; + private @Nullable String valueName; + private @Nullable String qualityName; + private StatusCode defaultQuality; // Inner server private @Nullable OpcUaNameSpace nameSpace; @@ -203,6 +210,14 @@ public void customize( Arrays.asList(CONNECTOR_OPC_UA_QUALITY_NAME_KEY, SINK_OPC_UA_QUALITY_NAME_KEY), CONNECTOR_OPC_UA_QUALITY_NAME_DEFAULT_VALUE) : null; + defaultQuality = + getQuality( + withQuality + ? parameters.getStringOrDefault( + Arrays.asList( + CONNECTOR_OPC_UA_DEFAULT_QUALITY_KEY, SINK_OPC_UA_DEFAULT_QUALITY_KEY), + CONNECTOR_OPC_UA_DEFAULT_QUALITY_UNCERTAIN_VALUE) + : CONNECTOR_OPC_UA_DEFAULT_QUALITY_GOOD_VALUE); isClientServerModel = parameters .getStringOrDefault( @@ -396,6 +411,19 @@ private SecurityPolicy getSecurityPolicy(final String securityPolicy) { } } + private StatusCode getQuality(final String quality) { + switch (quality.toUpperCase()) { + case CONNECTOR_OPC_UA_DEFAULT_QUALITY_GOOD_VALUE: + return StatusCode.GOOD; + case CONNECTOR_OPC_UA_DEFAULT_QUALITY_BAD_VALUE: + return StatusCode.BAD; + case CONNECTOR_OPC_UA_DEFAULT_QUALITY_UNCERTAIN_VALUE: + return StatusCode.UNCERTAIN; + default: + throw new PipeException("The default quality can only be 'GOOD', 'BAD' or 'UNCERTAIN'."); + } + } + @Override public void handshake() throws Exception { // Server side, do nothing @@ -538,4 +566,8 @@ public String getValueName() { public String getQualityName() { return qualityName; } + + public StatusCode getDefaultQuality() { + return defaultQuality; + } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index aa6dcdb9bc5e..490f4e137a59 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -19,6 +19,7 @@ package org.apache.iotdb.db.pipe.sink.protocol.opcua.client; +import org.apache.iotdb.commons.pipe.resource.log.PipeLogger; import org.apache.iotdb.db.pipe.sink.protocol.opcua.OpcUaSink; import org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaNameSpace; import org.apache.iotdb.pipe.api.exception.PipeException; @@ -50,6 +51,8 @@ import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription; import org.eclipse.milo.opcua.stack.core.types.structured.ObjectAttributes; import org.eclipse.milo.opcua.stack.core.types.structured.VariableAttributes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; @@ -61,6 +64,7 @@ import static org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaNameSpace.timestampToUtc; public class IoTDBOpcUaClient { + private static final Logger LOGGER = LoggerFactory.getLogger(OpcUaNameSpace.class); private static final int NAME_SPACE_INDEX = 2; private final String nodeUrl; @@ -99,8 +103,7 @@ private void transferTabletRowForClientServerModel( final List values, final OpcUaSink sink) throws Exception { - StatusCode currentQuality = - Objects.isNull(sink.getValueName()) ? StatusCode.GOOD : StatusCode.UNCERTAIN; + StatusCode currentQuality = sink.getDefaultQuality(); Object value = null; long timestamp = 0; NodeId nodeId = null; @@ -121,8 +124,10 @@ private void transferTabletRowForClientServerModel( continue; } if (Objects.nonNull(sink.getValueName()) && !sink.getValueName().equals(name)) { - throw new UnsupportedOperationException( + PipeLogger.log( + LOGGER::warn, "When the 'with-quality' mode is enabled, the measurement must be either \"value-name\" or \"quality-name\""); + continue; } nodeId = new NodeId(NAME_SPACE_INDEX, String.join("/", segments)); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java index f29185be1cc3..491b43404abf 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java @@ -21,6 +21,7 @@ import org.apache.iotdb.commons.exception.pipe.PipeRuntimeCriticalException; import org.apache.iotdb.commons.exception.pipe.PipeRuntimeNonCriticalException; +import org.apache.iotdb.commons.pipe.resource.log.PipeLogger; import org.apache.iotdb.db.pipe.sink.protocol.opcua.OpcUaSink; import org.apache.iotdb.db.pipe.sink.util.sorter.PipeTableModelTabletEventSorter; import org.apache.iotdb.db.pipe.sink.util.sorter.PipeTreeModelTabletEventSorter; @@ -56,6 +57,8 @@ import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode; import org.eclipse.milo.opcua.stack.core.types.builtin.Variant; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.file.Paths; import java.sql.Date; @@ -71,6 +74,7 @@ import java.util.stream.Collectors; public class OpcUaNameSpace extends ManagedNamespaceWithLifecycle { + private static final Logger LOGGER = LoggerFactory.getLogger(OpcUaNameSpace.class); public static final String NAMESPACE_URI = "urn:apache:iotdb:opc-server"; private final SubscriptionModel subscriptionModel; private final OpcUaServerBuilder builder; @@ -245,8 +249,7 @@ private void transferTabletRowForClientServerModel( final String currentFolder = currentStr.toString(); - StatusCode currentQuality = - Objects.isNull(sink.getValueName()) ? StatusCode.GOOD : StatusCode.UNCERTAIN; + StatusCode currentQuality = sink.getDefaultQuality(); UaVariableNode valueNode = null; Object value = null; long timestamp = 0; @@ -266,8 +269,10 @@ private void transferTabletRowForClientServerModel( continue; } if (Objects.nonNull(sink.getValueName()) && !sink.getValueName().equals(name)) { - throw new UnsupportedOperationException( + PipeLogger.log( + LOGGER::warn, "When the 'with-quality' mode is enabled, the measurement must be either \"value-name\" or \"quality-name\""); + continue; } final String nodeName = Objects.isNull(sink.getValueName()) ? name : segments[segments.length - 1]; diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java index 22f01a188fa1..ab82b3c1a806 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java @@ -195,6 +195,13 @@ public class PipeSinkConstant { public static final String SINK_OPC_UA_QUALITY_NAME_KEY = "sink.opcua.quality-name"; public static final String CONNECTOR_OPC_UA_QUALITY_NAME_DEFAULT_VALUE = "quality"; + public static final String CONNECTOR_OPC_UA_DEFAULT_QUALITY_KEY = + "connector.opcua.default-quality"; + public static final String SINK_OPC_UA_DEFAULT_QUALITY_KEY = "sink.opcua.default-quality"; + public static final String CONNECTOR_OPC_UA_DEFAULT_QUALITY_GOOD_VALUE = "GOOD"; + public static final String CONNECTOR_OPC_UA_DEFAULT_QUALITY_BAD_VALUE = "BAD"; + public static final String CONNECTOR_OPC_UA_DEFAULT_QUALITY_UNCERTAIN_VALUE = "UNCERTAIN"; + public static final String CONNECTOR_OPC_UA_NODE_URL_KEY = "connector.opcua.node-url"; public static final String SINK_OPC_UA_NODE_URL_KEY = "sink.opcua.node-url"; From bf72c19a6bb17f8f2ec47bc71ebb0e9fbb0e19b4 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Thu, 25 Dec 2025 12:08:39 +0800 Subject: [PATCH 41/47] ut --- .../test/java/org/apache/iotdb/db/pipe/sink/PipeSinkTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/PipeSinkTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/PipeSinkTest.java index 26ff72cde240..ec2122b91757 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/PipeSinkTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/sink/PipeSinkTest.java @@ -172,7 +172,8 @@ public void testOpcUaSink() { false, "root.db", "db", "root.db", tablet, false, "pipe", 0L, null, null, false); event.increaseReferenceCount(""); normalOPC.transfer(event); - Assert.assertThrows(UnsupportedOperationException.class, () -> qualityOPC.transfer(event)); + // Shall not throw + qualityOPC.transfer(event); event.decreaseReferenceCount("", false); qualityOPC.transfer( From e19adb5edb051f14841e3d7a9a7ff9620c8b654d Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Thu, 25 Dec 2025 12:22:39 +0800 Subject: [PATCH 42/47] refactor --- .../iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java index ee9e5400aad4..ecd27ea53e7d 100644 --- a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java @@ -46,15 +46,15 @@ public void testOPCUASink() throws Exception { TestUtils.executeNonQuery(env, "insert into root.db.d1(time, s1) values (1, 1)", null); - final Map connectorAttributes = new HashMap<>(); - connectorAttributes.put("sink", "opc-ua-sink"); - connectorAttributes.put("opcua.model", "client-server"); + final Map sinkAttributes = new HashMap<>(); + sinkAttributes.put("sink", "opc-ua-sink"); + sinkAttributes.put("opcua.model", "client-server"); Assert.assertEquals( TSStatusCode.SUCCESS_STATUS.getStatusCode(), client .createPipe( - new TCreatePipeReq("testPipe", connectorAttributes) + new TCreatePipeReq("testPipe", sinkAttributes) .setExtractorAttributes(Collections.emptyMap()) .setProcessorAttributes(Collections.emptyMap())) .getCode()); @@ -62,23 +62,23 @@ public void testOPCUASink() throws Exception { TSStatusCode.SUCCESS_STATUS.getStatusCode(), client.dropPipe("testPipe").getCode()); // Test reconstruction - connectorAttributes.put("password123456", "test"); + sinkAttributes.put("password123456", "test"); Assert.assertEquals( TSStatusCode.SUCCESS_STATUS.getStatusCode(), client .createPipe( - new TCreatePipeReq("testPipe", connectorAttributes) + new TCreatePipeReq("testPipe", sinkAttributes) .setExtractorAttributes(Collections.emptyMap()) .setProcessorAttributes(Collections.emptyMap())) .getCode()); // Test conflict - connectorAttributes.put("password123456", "conflict"); + sinkAttributes.put("password123456", "conflict"); Assert.assertEquals( TSStatusCode.PIPE_ERROR.getStatusCode(), client .createPipe( - new TCreatePipeReq("testPipe", connectorAttributes) + new TCreatePipeReq("testPipe", sinkAttributes) .setExtractorAttributes(Collections.emptyMap()) .setProcessorAttributes(Collections.emptyMap())) .getCode()); From f8888a100cf85ea7a6c3cf3429b38be286fb7b68 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Thu, 25 Dec 2025 15:42:57 +0800 Subject: [PATCH 43/47] fix_and_IT --- integration-test/pom.xml | 1 - .../pipe/it/single/AbstractPipeSingleIT.java | 2 +- .../pipe/it/single/IoTDBPipeOPCUAIT.java | 154 +++++++++++++++--- .../opcua/client/IoTDBOpcUaClient.java | 8 + .../opcua/server/OpcUaServerBuilder.java | 8 +- 5 files changed, 145 insertions(+), 28 deletions(-) diff --git a/integration-test/pom.xml b/integration-test/pom.xml index 150419970a0c..f566c8e5995b 100644 --- a/integration-test/pom.xml +++ b/integration-test/pom.xml @@ -183,7 +183,6 @@ org.bouncycastle bcprov-jdk18on - test junit diff --git a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/AbstractPipeSingleIT.java b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/AbstractPipeSingleIT.java index 3bb3ecce21e5..3ade13c7209d 100644 --- a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/AbstractPipeSingleIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/AbstractPipeSingleIT.java @@ -31,7 +31,7 @@ abstract class AbstractPipeSingleIT { @Before public void setUp() { - MultiEnvFactory.createEnv(2); + MultiEnvFactory.createEnv(1); env = MultiEnvFactory.getEnv(0); env.getConfig() .getCommonConfig() diff --git a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java index ecd27ea53e7d..99b9cc74a2db 100644 --- a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java @@ -20,49 +20,123 @@ package org.apache.iotdb.pipe.it.single; import org.apache.iotdb.commons.client.sync.SyncConfigNodeIServiceClient; +import org.apache.iotdb.confignode.rpc.thrift.TAlterPipeReq; import org.apache.iotdb.confignode.rpc.thrift.TCreatePipeReq; import org.apache.iotdb.db.it.utils.TestUtils; +import org.apache.iotdb.db.pipe.sink.protocol.opcua.client.ClientRunner; +import org.apache.iotdb.db.pipe.sink.protocol.opcua.client.IoTDBOpcUaClient; import org.apache.iotdb.it.framework.IoTDBTestRunner; import org.apache.iotdb.itbase.category.MultiClusterIT1; +import org.apache.iotdb.pipe.api.exception.PipeException; import org.apache.iotdb.pipe.it.dual.tablemodel.TableModelUtils; import org.apache.iotdb.rpc.TSStatusCode; +import org.apache.tsfile.common.conf.TSFileConfig; +import org.eclipse.milo.opcua.sdk.client.OpcUaClient; +import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider; +import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider; +import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider; +import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy; +import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; +import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime; +import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; +import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode; +import org.eclipse.milo.opcua.stack.core.types.builtin.Variant; +import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn; import org.junit.Assert; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; +import java.io.File; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_SECURITY_DIR_DEFAULT_VALUE; +import static org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaNameSpace.timestampToUtc; @RunWith(IoTDBTestRunner.class) @Category({MultiClusterIT1.class}) public class IoTDBPipeOPCUAIT extends AbstractPipeSingleIT { @Test - public void testOPCUASink() throws Exception { + public void testOPCUAServerSink() throws Exception { try (final SyncConfigNodeIServiceClient client = (SyncConfigNodeIServiceClient) env.getLeaderConfigNodeConnection()) { - TestUtils.executeNonQuery(env, "insert into root.db.d1(time, s1) values (1, 1)", null); - final Map sinkAttributes = new HashMap<>(); + sinkAttributes.put("sink", "opc-ua-sink"); sinkAttributes.put("opcua.model", "client-server"); + sinkAttributes.put("security-policy", "None"); Assert.assertEquals( TSStatusCode.SUCCESS_STATUS.getStatusCode(), client .createPipe( new TCreatePipeReq("testPipe", sinkAttributes) - .setExtractorAttributes(Collections.emptyMap()) + .setExtractorAttributes(Collections.singletonMap("user", "root")) .setProcessorAttributes(Collections.emptyMap())) .getCode()); + + TestUtils.executeNonQuery(env, "insert into root.db.d1(time, s1) values (1, 1)", null); + + final OpcUaClient opcUaClient = + getOpcUaClient("opc.tcp://127.0.0.1:12686/iotdb", SecurityPolicy.None, "root", "root"); + DataValue value = + opcUaClient.readValue(0, TimestampsToReturn.Both, new NodeId(2, "root/db/d1/s1")).get(); + Assert.assertEquals(new Variant(1.0), value.getValue()); + Assert.assertEquals(new DateTime(timestampToUtc(1)), value.getSourceTime()); + + Assert.assertEquals( + TSStatusCode.SUCCESS_STATUS.getStatusCode(), + client + .alterPipe( + new TAlterPipeReq() + .setPipeName("testPipe") + .setIsReplaceAllConnectorAttributes(false) + .setConnectorAttributes(Collections.singletonMap("with-quality", "true")) + .setProcessorAttributes(Collections.emptyMap()) + .setExtractorAttributes(Collections.emptyMap())) + .getCode()); + + TestUtils.executeNonQuery( + env, + "insert into root.db.opc(time, value, quality, other) values (1, 1, false, 1)", + null); + value = opcUaClient.readValue(0, TimestampsToReturn.Both, new NodeId(2, "root/db/opc")).get(); + Assert.assertEquals(new Variant(1.0), value.getValue()); + Assert.assertEquals(StatusCode.BAD, value.getStatusCode()); + Assert.assertEquals(new DateTime(timestampToUtc(1)), value.getSourceTime()); + + TestUtils.executeNonQuery( + env, "insert into root.db.opc(time, quality) values (2, true)", null); + TestUtils.executeNonQuery(env, "insert into root.db.opc(time, value) values (2, 2)", null); + + final long startTime = System.currentTimeMillis(); + while (true) { + try { + value = opcUaClient.readValue(0, TimestampsToReturn.Both, new NodeId(2, "root/db/opc")).get(); + Assert.assertEquals(new DateTime(timestampToUtc(2)), value.getSourceTime()); + Assert.assertEquals(new Variant(2.0), value.getValue()); + Assert.assertEquals(StatusCode.UNCERTAIN, value.getStatusCode()); + break; + } catch (final Throwable t) { + if (System.currentTimeMillis() - startTime > 10_000L) { + throw t; + } + } + } + + opcUaClient.disconnect().get(); Assert.assertEquals( TSStatusCode.SUCCESS_STATUS.getStatusCode(), client.dropPipe("testPipe").getCode()); // Test reconstruction - sinkAttributes.put("password123456", "test"); + sinkAttributes.put("password", "test"); + sinkAttributes.put("security-policy", "basic256sha256"); Assert.assertEquals( TSStatusCode.SUCCESS_STATUS.getStatusCode(), client @@ -72,16 +146,24 @@ public void testOPCUASink() throws Exception { .setProcessorAttributes(Collections.emptyMap())) .getCode()); + // Banned none, only allows basic256sha256 + Assert.assertThrows( + PipeException.class, + () -> + getOpcUaClient( + "opc.tcp://127.0.0.1:12686/iotdb", SecurityPolicy.None, "root", "root")); + // Test conflict - sinkAttributes.put("password123456", "conflict"); - Assert.assertEquals( - TSStatusCode.PIPE_ERROR.getStatusCode(), - client - .createPipe( - new TCreatePipeReq("testPipe", sinkAttributes) - .setExtractorAttributes(Collections.emptyMap()) - .setProcessorAttributes(Collections.emptyMap())) - .getCode()); + sinkAttributes.put("password", "conflict"); + try { + TestUtils.executeNonQuery( + env, "create pipe test1 ('sink'='opc-ua-sink', 'password'='conflict')", null); + Assert.fail(); + } catch (final Exception e) { + Assert.assertEquals( + "org.apache.iotdb.jdbc.IoTDBSQLException: 1107: The existing server with tcp port 12686 and https port 8443's password test conflicts to the new password conflict, reject reusing.", + e.getMessage()); + } } } @@ -93,42 +175,68 @@ public void testOPCUASinkInTableModel() throws Exception { TableModelUtils.createDataBaseAndTable(env, "test", "test"); TableModelUtils.insertData("test", "test", 0, 10, env); - final Map connectorAttributes = new HashMap<>(); - connectorAttributes.put("sink", "opc-ua-sink"); - connectorAttributes.put("opcua.model", "client-server"); + final Map sourceAttributes = new HashMap<>(); + final Map sinkAttributes = new HashMap<>(); + sourceAttributes.put("capture.table", "true"); + sourceAttributes.put("user", "root"); + + sinkAttributes.put("sink", "opc-ua-sink"); + sinkAttributes.put("opcua.model", "client-server"); Assert.assertEquals( TSStatusCode.SUCCESS_STATUS.getStatusCode(), client .createPipe( - new TCreatePipeReq("testPipe", connectorAttributes) - .setExtractorAttributes(Collections.singletonMap("capture.table", "true")) + new TCreatePipeReq("testPipe", sinkAttributes) + .setExtractorAttributes(sourceAttributes) .setProcessorAttributes(Collections.emptyMap())) .getCode()); Assert.assertEquals( TSStatusCode.SUCCESS_STATUS.getStatusCode(), client.dropPipe("testPipe").getCode()); // Test reconstruction - connectorAttributes.put("password123456", "test"); + sinkAttributes.put("password123456", "test"); Assert.assertEquals( TSStatusCode.SUCCESS_STATUS.getStatusCode(), client .createPipe( - new TCreatePipeReq("testPipe", connectorAttributes) + new TCreatePipeReq("testPipe", sinkAttributes) .setExtractorAttributes(Collections.emptyMap()) .setProcessorAttributes(Collections.emptyMap())) .getCode()); // Test conflict - connectorAttributes.put("password123456", "conflict"); + sinkAttributes.put("password123456", "conflict"); Assert.assertEquals( TSStatusCode.PIPE_ERROR.getStatusCode(), client .createPipe( - new TCreatePipeReq("testPipe", connectorAttributes) + new TCreatePipeReq("testPipe", sinkAttributes) .setExtractorAttributes(Collections.emptyMap()) .setProcessorAttributes(Collections.emptyMap())) .getCode()); } } + + private static OpcUaClient getOpcUaClient( + final String nodeUrl, + final SecurityPolicy policy, + final String userName, + final String password) { + final IoTDBOpcUaClient client; + + final IdentityProvider provider = + Objects.nonNull(userName) + ? new UsernameProvider(userName, password) + : new AnonymousProvider(); + + final String securityDir = + CONNECTOR_OPC_UA_SECURITY_DIR_DEFAULT_VALUE + + File.separatorChar + + UUID.nameUUIDFromBytes(nodeUrl.getBytes(TSFileConfig.STRING_CHARSET)); + + client = new IoTDBOpcUaClient(nodeUrl, policy, provider, false); + new ClientRunner(client, securityDir, password).run(); + return client.getClient(); + } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index 490f4e137a59..dd5810edbfb7 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -20,6 +20,7 @@ package org.apache.iotdb.db.pipe.sink.protocol.opcua.client; import org.apache.iotdb.commons.pipe.resource.log.PipeLogger; +import org.apache.iotdb.commons.utils.TestOnly; import org.apache.iotdb.db.pipe.sink.protocol.opcua.OpcUaSink; import org.apache.iotdb.db.pipe.sink.protocol.opcua.server.OpcUaNameSpace; import org.apache.iotdb.pipe.api.exception.PipeException; @@ -65,6 +66,8 @@ public class IoTDBOpcUaClient { private static final Logger LOGGER = LoggerFactory.getLogger(OpcUaNameSpace.class); + + // Customized nodes private static final int NAME_SPACE_INDEX = 2; private final String nodeUrl; @@ -257,6 +260,11 @@ IdentityProvider getIdentityProvider() { return identityProvider; } + @TestOnly + public OpcUaClient getClient() { + return client; + } + /////////////////////////////// Attribute creator /////////////////////////////// private VariableAttributes createMeasurementAttributes( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java index 05c6eeb33690..2bc19b7167da 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaServerBuilder.java @@ -57,6 +57,7 @@ import java.nio.file.Paths; import java.security.KeyPair; import java.security.cert.X509Certificate; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; @@ -243,7 +244,8 @@ private Set createEndpointConfigurations( USER_TOKEN_POLICY_USERNAME, USER_TOKEN_POLICY_X509); - if (securityPolicies.contains(SecurityPolicy.None)) { + final Set securityPolicySet = new HashSet<>(securityPolicies); + if (securityPolicySet.contains(SecurityPolicy.None)) { final EndpointConfiguration.Builder noSecurityBuilder = builder .copy() @@ -252,10 +254,10 @@ private Set createEndpointConfigurations( endpointConfigurations.add(buildTcpEndpoint(noSecurityBuilder, tcpBindPort)); endpointConfigurations.add(buildHttpsEndpoint(noSecurityBuilder, httpsBindPort)); - securityPolicies.remove(SecurityPolicy.None); + securityPolicySet.remove(SecurityPolicy.None); } - for (final SecurityPolicy securityPolicy : securityPolicies) { + for (final SecurityPolicy securityPolicy : securityPolicySet) { endpointConfigurations.add( buildTcpEndpoint( builder From 2d0c7500da2bf4b3827e0f3689a871846cf13c24 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Thu, 25 Dec 2025 16:10:30 +0800 Subject: [PATCH 44/47] fix --- integration-test/pom.xml | 5 +++++ .../org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java | 6 +++++- .../apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/integration-test/pom.xml b/integration-test/pom.xml index f566c8e5995b..7af12cbbeb3a 100644 --- a/integration-test/pom.xml +++ b/integration-test/pom.xml @@ -195,6 +195,11 @@ junit-jupiter-api test + + io.github.Caideyipi + 0.0.1 + iotdb_opc_server + org.awaitility awaitility diff --git a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java index 99b9cc74a2db..8aaed547e80b 100644 --- a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java @@ -118,7 +118,8 @@ public void testOPCUAServerSink() throws Exception { final long startTime = System.currentTimeMillis(); while (true) { try { - value = opcUaClient.readValue(0, TimestampsToReturn.Both, new NodeId(2, "root/db/opc")).get(); + value = + opcUaClient.readValue(0, TimestampsToReturn.Both, new NodeId(2, "root/db/opc")).get(); Assert.assertEquals(new DateTime(timestampToUtc(2)), value.getSourceTime()); Assert.assertEquals(new Variant(2.0), value.getValue()); Assert.assertEquals(StatusCode.UNCERTAIN, value.getStatusCode()); @@ -167,6 +168,9 @@ public void testOPCUAServerSink() throws Exception { } } + @Test + public void testOPCUAClientSink() throws Exception {} + @Test public void testOPCUASinkInTableModel() throws Exception { try (final SyncConfigNodeIServiceClient client = diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 60fc6c371f81..878298a8b07a 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -449,6 +449,9 @@ public void transfer(final TabletInsertionEvent tabletInsertionEvent) throws Exc nameSpace.transfer(tablet, isTableModel, this); } else if (Objects.nonNull(client)) { client.transfer(tablet, this); + } else { + throw new PipeException( + "No OPC client or server is specified when transferring tablet"); } }); } From 39b13b128eb3284229ca9f9011301bce54dba869 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Thu, 25 Dec 2025 16:16:53 +0800 Subject: [PATCH 45/47] placeholder --- .../iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java | 12 ++++++------ .../sink/protocol/opcua/client/IoTDBOpcUaClient.java | 5 ++++- .../sink/protocol/opcua/server/OpcUaNameSpace.java | 4 ++-- .../pipe/config/constant/PipeSinkConstant.java | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java index 878298a8b07a..071db688dd8b 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/OpcUaSink.java @@ -84,7 +84,7 @@ import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_MODEL_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_MODEL_PUB_SUB_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_NODE_URL_KEY; -import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_PLACEHOLDER_DEFAULT_VALUE; +import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_PLACEHOLDER_4_NULL_TAG_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_PLACEHOLDER_KEY; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_NAME_DEFAULT_VALUE; import static org.apache.iotdb.commons.pipe.config.constant.PipeSinkConstant.CONNECTOR_OPC_UA_QUALITY_NAME_KEY; @@ -141,7 +141,7 @@ public class OpcUaSink implements PipeConnector { private String serverKey; private boolean isClientServerModel; private String databaseName; - private String placeHolder; + private String placeHolder4NullTag; private @Nullable String valueName; private @Nullable String qualityName; private StatusCode defaultQuality; @@ -224,10 +224,10 @@ public void customize( Arrays.asList(CONNECTOR_OPC_UA_MODEL_KEY, SINK_OPC_UA_MODEL_KEY), CONNECTOR_OPC_UA_MODEL_DEFAULT_VALUE) .equals(CONNECTOR_OPC_UA_MODEL_CLIENT_SERVER_VALUE); - placeHolder = + placeHolder4NullTag = parameters.getStringOrDefault( Arrays.asList(CONNECTOR_OPC_UA_PLACEHOLDER_KEY, SINK_OPC_UA_PLACEHOLDER_KEY), - CONNECTOR_OPC_UA_PLACEHOLDER_DEFAULT_VALUE); + CONNECTOR_OPC_UA_PLACEHOLDER_4_NULL_TAG_DEFAULT_VALUE); final DataRegion region = StorageEngine.getInstance() .getDataRegion(new DataRegionId(configuration.getRuntimeEnvironment().getRegionId())); @@ -556,8 +556,8 @@ public String getDatabaseName() { return databaseName; } - public String getPlaceHolder() { - return placeHolder; + public String getPlaceHolder4NullTag() { + return placeHolder4NullTag; } @Nullable diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java index dd5810edbfb7..bf96d9881807 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/client/IoTDBOpcUaClient.java @@ -69,6 +69,9 @@ public class IoTDBOpcUaClient { // Customized nodes private static final int NAME_SPACE_INDEX = 2; + + // Useless for a server only accept client writing + private static final double SAMPLING_INTERVAL_PLACEHOLDER = 500; private final String nodeUrl; private final SecurityPolicy securityPolicy; @@ -281,7 +284,7 @@ private VariableAttributes createMeasurementAttributes( null, // arrayDimensions AccessLevel.toValue(AccessLevel.READ_WRITE), AccessLevel.toValue(AccessLevel.READ_WRITE), - 500.0, // samplingInterval + SAMPLING_INTERVAL_PLACEHOLDER, historizing); } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java index 491b43404abf..56a906801855 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/protocol/opcua/server/OpcUaNameSpace.java @@ -156,7 +156,7 @@ public static void transferTabletForClientServerModel( for (int j = 0; j < segments.length; ++j) { folderSegments[j + 1] = - Objects.isNull(segments[j]) ? sink.getPlaceHolder() : (String) segments[j]; + Objects.isNull(segments[j]) ? sink.getPlaceHolder4NullTag() : (String) segments[j]; } final int finalI = i; @@ -393,7 +393,7 @@ private void transferTabletForPubSubModel( for (final Object segment : tablet.getDeviceID(i).getSegments()) { idBuilder .append(TsFileConstant.PATH_SEPARATOR) - .append(Objects.isNull(segment) ? sink.getPlaceHolder() : segment); + .append(Objects.isNull(segment) ? sink.getPlaceHolder4NullTag() : segment); } sourceNameList.add(idBuilder.toString()); } diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java index ab82b3c1a806..b27bb59224da 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/pipe/config/constant/PipeSinkConstant.java @@ -181,7 +181,7 @@ public class PipeSinkConstant { 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_OPC_UA_PLACEHOLDER_4_NULL_TAG_DEFAULT_VALUE = "null"; public static final String CONNECTOR_OPC_UA_WITH_QUALITY_KEY = "connector.opcua.with-quality"; public static final String SINK_OPC_UA_WITH_QUALITY_KEY = "sink.opcua.with-quality"; From ae331f4ea38fb1723428104228bee3ef53f80ec6 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Thu, 25 Dec 2025 16:28:40 +0800 Subject: [PATCH 46/47] rollback --- integration-test/pom.xml | 5 ----- .../org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java | 3 --- 2 files changed, 8 deletions(-) diff --git a/integration-test/pom.xml b/integration-test/pom.xml index 7af12cbbeb3a..f566c8e5995b 100644 --- a/integration-test/pom.xml +++ b/integration-test/pom.xml @@ -195,11 +195,6 @@ junit-jupiter-api test - - io.github.Caideyipi - 0.0.1 - iotdb_opc_server - org.awaitility awaitility diff --git a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java index 8aaed547e80b..b026d17a7af8 100644 --- a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java @@ -168,9 +168,6 @@ public void testOPCUAServerSink() throws Exception { } } - @Test - public void testOPCUAClientSink() throws Exception {} - @Test public void testOPCUASinkInTableModel() throws Exception { try (final SyncConfigNodeIServiceClient client = From 012e6666611bc766331cb08e3316e9965f038452 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Thu, 25 Dec 2025 17:33:02 +0800 Subject: [PATCH 47/47] eliminate-fault --- .../org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java index b026d17a7af8..a190f7c5182b 100644 --- a/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/pipe/it/single/IoTDBPipeOPCUAIT.java @@ -66,6 +66,8 @@ public void testOPCUAServerSink() throws Exception { try (final SyncConfigNodeIServiceClient client = (SyncConfigNodeIServiceClient) env.getLeaderConfigNodeConnection()) { + TestUtils.executeNonQuery(env, "insert into root.db.d1(time, s1) values (1, 1)", null); + final Map sinkAttributes = new HashMap<>(); sinkAttributes.put("sink", "opc-ua-sink"); @@ -81,8 +83,6 @@ public void testOPCUAServerSink() throws Exception { .setProcessorAttributes(Collections.emptyMap())) .getCode()); - TestUtils.executeNonQuery(env, "insert into root.db.d1(time, s1) values (1, 1)", null); - final OpcUaClient opcUaClient = getOpcUaClient("opc.tcp://127.0.0.1:12686/iotdb", SecurityPolicy.None, "root", "root"); DataValue value =