Skip to content

Commit

Permalink
Add batching functionality to delete_nodes
Browse files Browse the repository at this point in the history
When `delete_nodes` is given a large amount of nodes, or is instructed
to recursively delete a big structure, the connection between client and
server can time out.

To remedy this, allow the user to supply a `batch` parameter, which
controls how many nodes are deleted in one request.

closes FreeOpcUa#1148
  • Loading branch information
Nebukadneza committed Oct 21, 2020
1 parent 9e1dd48 commit 73db462
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 10 deletions.
4 changes: 2 additions & 2 deletions opcua/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,8 +535,8 @@ def get_namespace_index(self, uri):
uries = self.get_namespace_array()
return uries.index(uri)

def delete_nodes(self, nodes, recursive=False):
return delete_nodes(self.uaclient, nodes, recursive)
def delete_nodes(self, nodes, recursive=False, batch=False):
return delete_nodes(self.uaclient, nodes, recursive, batch=batch)

def import_xml(self, path=None, xmlstring=None):
"""
Expand Down
32 changes: 26 additions & 6 deletions opcua/common/manage_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,13 @@ def _guess_datatype(variant):
return ua.NodeId(getattr(ua.ObjectIds, variant.VariantType.name))


def delete_nodes(server, nodes, recursive=False, delete_target_references=True):
def delete_nodes(server, nodes, recursive=False, delete_target_references=True, batch=False):
"""
Delete specified nodes. Optionally delete recursively all nodes with a
downward hierachic references to the node
Delete specified nodes. Optionally recursively delete all nodes with a
downward hierachic references to the node.
`batch` can be False or an integer denoting the maximum amount of items per
request. If an integer is given, batches the discovered nodes into multiple
DeleteNodesRequests.
return the list of deleted node and the result
"""
nodestodelete = []
Expand All @@ -386,9 +389,26 @@ def delete_nodes(server, nodes, recursive=False, delete_target_references=True):
it.NodeId = mynode.nodeid
it.DeleteTargetReferences = delete_target_references
nodestodelete.append(it)
params = ua.DeleteNodesParameters()
params.NodesToDelete = nodestodelete
return nodes, server.delete_nodes(params)

if batch is True:
batch = 100

if batch and isinstance(batch, int):
chunks = [
nodestodelete[i : i + batch]
for i in range(0, len(nodestodelete), batch)
]

results = []
for chunk in chunks:
params = ua.DeleteNodesParameters()
params.NodesToDelete = chunk
results.extend(server.delete_nodes(params))
return nodes, results
else:
params = ua.DeleteNodesParameters()
params.NodesToDelete = nodestodelete
return nodes, server.delete_nodes(params)


def _add_childs(nodes):
Expand Down
4 changes: 2 additions & 2 deletions opcua/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,8 +539,8 @@ def export_xml_by_ns(self, path, namespaces=None):
nodes = get_nodes_of_namespace(self, namespaces)
self.export_xml(nodes, path)

def delete_nodes(self, nodes, recursive=False):
return delete_nodes(self.iserver.isession, nodes, recursive)
def delete_nodes(self, nodes, recursive=False, batch=False):
return delete_nodes(self.iserver.isession, nodes, recursive, batch=batch)

def historize_node_data_change(self, node, period=timedelta(days=7), count=0):
"""
Expand Down
24 changes: 24 additions & 0 deletions tests/tests_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,30 @@ def test_delete_nodes_recursive2(self):
with self.assertRaises(ua.UaStatusCodeError):
node.get_browse_name()

def test_delete_nodes_recursive_batching(self):
obj = self.opc.get_objects_node()
fold = obj.add_folder(2, "FolderToDeleteRoot")
nfold = fold
mynodes = []
for i in range(7):
nfold = fold.add_folder(2, "FolderToDeleteRoot")
var = fold.add_variable(2, "VarToDeleteR", 9.1)
var = fold.add_property(2, "ProToDeleteR", 9.1)
prop = fold.add_property(2, "ProToDeleteR", 9.1)
o = fold.add_object(3, "ObjToDeleteR")
o_var = o.add_variable(3, "VarToDeleteRR", 9.2)
o_prop = o.add_property(3, "PropToDeleteRR", 9.2)
mynodes.append(nfold)
mynodes.append(var)
mynodes.append(prop)
mynodes.append(o)
mynodes.append(o_var)
mynodes.append(o_prop)
self.opc.delete_nodes([fold], recursive=True, batch=2)
for node in mynodes:
with self.assertRaises(ua.UaStatusCodeError):
node.get_browse_name()

def test_delete_references(self):
newtype = self.opc.get_node(ua.ObjectIds.HierarchicalReferences).add_reference_type(0, "HasSuperSecretVariable")

Expand Down

0 comments on commit 73db462

Please sign in to comment.