This is an automated email from the ASF dual-hosted git repository.
szaszm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new 4a3c96967 MINIFICPP-2920 - PutOPCProcessor creates writable node
(#2279)
4a3c96967 is described below
commit 4a3c969677b46d2206b884810926e70e3dacf0b9
Author: Adam Debreceni <[email protected]>
AuthorDate: Fri Oct 9 16:33:55 2026 +0200
MINIFICPP-2920 - PutOPCProcessor creates writable node (#2279)
Co-authored-by: Copilot Autofix powered by AI
<[email protected]>
---
extensions/opc/src/OPCCommon.cpp | 2 ++
extensions/opc/tests/PutOPCProcessorTests.cpp | 31 ++++++++++++++++++++++++
extensions/opc/tests/features/opcua.feature | 34 +++++++++++++++++----------
3 files changed, 55 insertions(+), 12 deletions(-)
diff --git a/extensions/opc/src/OPCCommon.cpp b/extensions/opc/src/OPCCommon.cpp
index 8bf836a4a..6c1b8b568 100644
--- a/extensions/opc/src/OPCCommon.cpp
+++ b/extensions/opc/src/OPCCommon.cpp
@@ -430,6 +430,8 @@ UA_StatusCode
Client::translateBrowsePathsToNodeIdsRequest(const std::string& pa
template<typename T>
UA_StatusCode Client::add_node(const UA_NodeId parent_node_id, const UA_NodeId
target_node_id, const UA_UInt32 ref_type_id, std::string_view browse_name, T
value, UA_NodeId *received_node_id) {
UA_VariableAttributes attr = UA_VariableAttributes_default;
+ attr.accessLevel |= UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
+ attr.userAccessLevel |= UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
add_value_to_variant(&attr.value, value);
char local[6] = "en-US"; // NOLINT(cppcoreguidelines-avoid-c-arrays)
attr.displayName = UA_LOCALIZEDTEXT(local,
const_cast<char*>(browse_name.data()));
diff --git a/extensions/opc/tests/PutOPCProcessorTests.cpp
b/extensions/opc/tests/PutOPCProcessorTests.cpp
index 7197d05c7..c53d672c2 100644
--- a/extensions/opc/tests/PutOPCProcessorTests.cpp
+++ b/extensions/opc/tests/PutOPCProcessorTests.cpp
@@ -105,6 +105,37 @@ TEST_CASE("Test creating a new node with path node id",
"[putopcprocessor]") {
verifyCreatedNode(expected_node, controller);
}
+TEST_CASE("Test updating a node previously created by the processor",
"[putopcprocessor]") {
+ OpcUaTestServer server;
+ server.start();
+ SingleProcessorTestController
controller{minifi::test::utils::make_processor<processors::PutOPCProcessor>("PutOPCProcessor")};
+ auto put_opc_processor = controller.getProcessor();
+
+ NodeData expected_node{42, server.getNamespaceIndex(), 9999, "everything",
"Simulator/Default/Device1", {}};
+
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::OPCServerEndPoint.name,
"opc.tcp://127.0.0.1:4840/"));
+
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::ParentNodeIDType.name,
"Path"));
+
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::ParentNodeID.name,
expected_node.path));
+
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::ParentNameSpaceIndex.name,
std::to_string(expected_node.namespace_index)));
+
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::ValueType.name,
"Int32"));
+
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::TargetNodeIDType.name,
"Int"));
+
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::TargetNodeID.name,
std::to_string(expected_node.node_id)));
+
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::TargetNodeNameSpaceIndex.name,
std::to_string(expected_node.namespace_index)));
+
REQUIRE(put_opc_processor->setProperty(processors::PutOPCProcessor::TargetNodeBrowseName.name,
expected_node.browse_name));
+
+ // The first flow file creates the node.
+ const auto create_results =
controller.trigger(std::to_string(expected_node.data));
+ REQUIRE(create_results.at(processors::PutOPCProcessor::Failure).empty());
+ REQUIRE(create_results.at(processors::PutOPCProcessor::Success).size() == 1);
+ verifyCreatedNode(expected_node, controller);
+
+ // A second flow file to the same target node must update it, not fail.
+ expected_node.data = 43;
+ const auto update_results =
controller.trigger(std::to_string(expected_node.data));
+ REQUIRE(update_results.at(processors::PutOPCProcessor::Failure).empty());
+ REQUIRE(update_results.at(processors::PutOPCProcessor::Success).size() == 1);
+ verifyCreatedNode(expected_node, controller);
+}
+
TEST_CASE("Test fetching using custom reference type id path",
"[putopcprocessor]") {
OpcUaTestServer server;
server.start();
diff --git a/extensions/opc/tests/features/opcua.feature
b/extensions/opc/tests/features/opcua.feature
index 0b6c8966d..d86ae5c59 100644
--- a/extensions/opc/tests/features/opcua.feature
+++ b/extensions/opc/tests/features/opcua.feature
@@ -61,9 +61,14 @@ Feature: Putting and fetching data to OPC UA server
| Double | 123.321 |
| Boolean | False |
+ # The target node does not exist on the server, so the first flow file
creates it and the second
+ # one - dropped into the input directory only after the first value has been
read back - takes the
+ # update path. Sequencing the two writes through separate steps is what
makes this deterministic:
+ # if both files were present at startup, GetFile's listing order would
decide which value lands
+ # last and the final assertion would be a coin flip.
Scenario Outline: Update and fetch data from an OPC UA node
Given a GetFile processor with the "Input Directory" property set to
"/tmp/input" in the "update-opc-ua-node" flow
- And a directory at "/tmp/input" has a file with the content "<Value>" in
the "update-opc-ua-node" flow
+ And a directory at "/tmp/input" has a file with the content "<Initial
Value>" in the "update-opc-ua-node" flow
And a PutOPCProcessor processor in the "update-opc-ua-node" flow
And PutOPCProcessor is EVENT_DRIVEN in the "update-opc-ua-node" flow
And a FetchOPCProcessor processor in the "fetch-opc-ua-node" flow
@@ -79,7 +84,7 @@ Feature: Putting and fetching data to OPC UA server
| PutOPCProcessor | Target node namespace index | 1
|
| PutOPCProcessor | Value type | <Value Type>
|
| PutOPCProcessor | OPC server endpoint |
opc.tcp://opcua-server-${scenario_id}:4840/ |
- | PutOPCProcessor | Target node browse name | testnodename
|
+ | PutOPCProcessor | Target node browse name | updatetestnode
|
And these processor properties are set in the "fetch-opc-ua-node" flow
| processor name | property name | property value
|
| FetchOPCProcessor | Node ID | <Node ID>
|
@@ -94,17 +99,22 @@ Feature: Putting and fetching data to OPC UA server
And an OPC UA server is set up
When all instances start up
- Then in the "fetch-opc-ua-node" container at least one file with the
content "<Value>" is placed in the "/tmp/output" directory in less than 60
seconds
+ # The node did not exist, so this first value proves the node was created.
+ Then in the "fetch-opc-ua-node" container at least one file with the
content "<Initial Value>" is placed in the "/tmp/output" directory in less than
60 seconds
- # Node ids starting from 51000 are pre-defined demo node ids in the test
server application (server_ctt) of the open62541 docker image. There is one
nodeid defined
- # for each type supported by OPC UA. These demo nodes can be used for
testing purposes. "the.answer" is also a pre-defined string id for the same
testing purposes.
- Examples: Topic names and formats to test
- | Node ID Type | Node ID | Value Type | Value |
- | Int | 51034 | String | minifi-test |
- | Int | 51001 | Boolean | True |
- | String | the.answer | Int32 | 54 |
- | Int | 51019 | UInt32 | 123 |
- | Int | 51031 | Double | 66.6 |
+ # Only now, with the node known to exist, write a second value to the same
node. Reading this
+ # one back is only possible if PutOPCProcessor can update a node it
created itself.
+ When a file with the content "<Updated Value>" is placed in "/tmp/input"
in the "update-opc-ua-node" flow
+ Then in the "fetch-opc-ua-node" container at least one file with the
content "<Updated Value>" is placed in the "/tmp/output" directory in less than
60 seconds
+ And the logs of the "update-opc-ua-node" container do not contain the
following message: "Failed to update node" after 1 second
+
+ Examples: Node id types and value types to test
+ | Node ID Type | Node ID | Value Type | Initial Value | Updated
Value |
+ | Int | 9001 | String | minifi-test |
minifi-update |
+ | Int | 9002 | Boolean | False | True
|
+ | String | updatetest | Int32 | 54 | 55
|
+ | Int | 9003 | UInt32 | 123 | 456
|
+ | Int | 9004 | Double | 66.6 | 77.7
|
Scenario: Create and fetch data from an OPC UA node through secure connection
Given a GetFile processor with the "Input Directory" property set to
"/tmp/input" in the "create-opc-ua-node" flow