using System;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Opc.Ua.Server.TestFramework;
using Opc.Ua.Tests;
using Quickstarts.ReferenceServer;
namespace Opc.Ua.Server.Tests
{
///
/// Test
///
[TestFixture]
[Category("CustomNodeManager")]
[SetCulture("en-us")]
[SetUICulture("en-us")]
[Parallelizable]
public class CustomNodeManagerTests
{
///
/// Tests the componentCache methods with multiple threads
///
[Test]
public async Task TestComponentCacheAsync()
{
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var fixture = new ServerFixture(t => new ReferenceServer(t));
try
{
// Arrange
const string ns = "http://test.org/UA/Data/";
StandardServer server = await fixture.StartAsync()
.ConfigureAwait(false);
using var nodeManager = new TestableCustomNodeManger2(server.CurrentInstance, ns);
var baseObject = new BaseObjectState(null);
var nodeHandle = new NodeHandle(
CommonTestWorkers.NodeIdTestSetStatic[0]
.WithNamespaceIndex(0)
.InnerNodeId,
baseObject);
//Act
await RunTaskInParallelAsync(
() => UseComponentCacheAsync(nodeManager, baseObject, nodeHandle),
100)
.ConfigureAwait(false);
//Assert, that entry was deleted from cache after parallel operations on the same node
NodeState handleFromCache = nodeManager.LookupNodeInComponentCache(
nodeManager.SystemContext,
nodeHandle);
Assert.That(handleFromCache, Is.Null);
}
finally
{
await fixture.StopAsync().ConfigureAwait(false);
}
}
///
/// Part 4 ยง5.12.2.2: a Call whose methodId is the Method declaration of the
/// ObjectType resolves to the Method of the Object, so the RolePermissions
/// of the Object's Method are verified and that Method is invoked.
///
[Test]
public async Task FindMethodStateResolvesTypeDeclarationToObjectMethodAsync()
{
var fixture = new ServerFixture(t => new ReferenceServer(t));
try
{
const string ns = "http://test.org/UA/MethodResolution/";
StandardServer server = await fixture.StartAsync()
.ConfigureAwait(false);
using var nodeManager = new TestableCustomNodeManger2(server.CurrentInstance, ns);
ushort index = (ushort)server.CurrentInstance.NamespaceUris.GetIndex(ns);
ServerSystemContext context = nodeManager.SystemContext;
var objectType = new BaseObjectTypeState
{
NodeId = new NodeId("ResolveType", index),
BrowseName = new QualifiedName("ResolveType", index),
SuperTypeId = ObjectTypeIds.BaseObjectType
};
var declaration = new MethodState(objectType)
{
NodeId = new NodeId("ResolveType.Reset", index),
BrowseName = new QualifiedName("Reset", index)
};
objectType.AddChild(declaration);
var instance = new BaseObjectState(null)
{
NodeId = new NodeId("ResolveInstance", index),
BrowseName = new QualifiedName("ResolveInstance", index),
TypeDefinitionId = objectType.NodeId
};
var instanceMethod = new MethodState(instance)
{
NodeId = new NodeId("ResolveInstance.Reset", index),
BrowseName = new QualifiedName("Reset", index)
};
instance.AddChild(instanceMethod);
nodeManager.AddPredefinedNode(context, objectType);
nodeManager.AddPredefinedNode(context, instance);
server.CurrentInstance.TypeTree.AddSubtype(
objectType.NodeId,
ObjectTypeIds.BaseObjectType);
var operationContext = new OperationContext(
new RequestHeader(),
null,
RequestType.Call,
RequestLifetime.None);
MethodState resolved = nodeManager.FindMethodState(
operationContext,
new CallMethodRequest
{
ObjectId = instance.NodeId,
MethodId = declaration.NodeId
});
Assert.That(resolved, Is.SameAs(instanceMethod));
}
finally
{
await fixture.StopAsync().ConfigureAwait(false);
}
}
///
/// Tests the Predefined Nodes methods with multiple threads
///
[Test]
public async Task TestPredefinedNodesAsync()
{
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
var fixture = new ServerFixture(t => new ReferenceServer(t));
try
{
// Arrange
const string ns = "http://test.org/UA/Data/";
StandardServer server = await fixture.StartAsync()
.ConfigureAwait(false);
using var nodeManager = new TestableCustomNodeManger2(server.CurrentInstance, ns);
int index = server.CurrentInstance.NamespaceUris.GetIndex(ns);
var baseObject = new DataItemState(null);
NodeId nodeId =
CommonTestWorkers.NodeIdTestSetStatic[0]
.WithNamespaceIndex((ushort)index)
.InnerNodeId;
baseObject.NodeId = nodeId;
//single threaded test
nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject);
Assert.That(nodeManager.PredefinedNodes.ContainsKey(nodeId), Is.True);
NodeState nodeState = nodeManager.Find(nodeId)!;
Assert.That(nodeState, Is.Not.Null);
var handle = nodeManager.GetManagerHandle(nodeId) as NodeHandle;
Assert.That(handle, Is.Not.Null);
nodeManager.DeleteNode(nodeManager.SystemContext, nodeId);
Assert.That(nodeManager.PredefinedNodes, Is.Empty);
nodeState = nodeManager.Find(nodeId)!;
Assert.That(nodeState, Is.Null);
handle = nodeManager.GetManagerHandle(nodeId) as NodeHandle;
Assert.That(handle, Is.Null);
nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject);
nodeManager.DeleteAddressSpace();
Assert.That(nodeManager.PredefinedNodes, Is.Empty);
//Act
await RunTaskInParallelAsync(
() => UsePredefinedNodesAsync(nodeManager, baseObject, nodeId),
100)
.ConfigureAwait(false);
//last operation added the Node back into the dictionary
Assert.That(nodeManager.PredefinedNodes.ContainsKey(nodeId), Is.True);
//delete full adress space
nodeManager.DeleteAddressSpace();
Assert.That(nodeManager.PredefinedNodes, Is.Empty);
}
finally
{
await fixture.StopAsync().ConfigureAwait(false);
}
}
private static Task UsePredefinedNodesAsync(
TestableCustomNodeManger2 nodeManager,
DataItemState baseObject,
NodeId nodeId)
{
nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject);
_ = nodeManager.Find(nodeId);
_ = nodeManager.GetManagerHandle(nodeId) as NodeHandle;
nodeManager.DeleteNode(nodeManager.SystemContext, nodeId);
_ = nodeManager.Find(nodeId);
_ = nodeManager.GetManagerHandle(nodeId) as NodeHandle;
nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject);
return Task.CompletedTask;
}
///
/// Test Methods AddNodeToComponentCache, RemoveNodeFromComponentCache & LookupNodeInComponentCache & verify the node is added to the cache
///
private static async Task UseComponentCacheAsync(
TestableCustomNodeManger2 nodeManager,
BaseObjectState baseObject,
NodeHandle nodeHandle)
{
//-- Act
nodeManager.AddNodeToComponentCache(nodeManager.SystemContext, nodeHandle, baseObject);
nodeManager.AddNodeToComponentCache(nodeManager.SystemContext, nodeHandle, baseObject);
NodeState handleFromCache = nodeManager.LookupNodeInComponentCache(
nodeManager.SystemContext,
nodeHandle);
//-- Assert
Assert.That(handleFromCache, Is.Not.Null);
nodeManager.RemoveNodeFromComponentCache(nodeManager.SystemContext, nodeHandle);
handleFromCache = nodeManager.LookupNodeInComponentCache(
nodeManager.SystemContext,
nodeHandle);
Assert.That(handleFromCache, Is.Not.Null);
nodeManager.RemoveNodeFromComponentCache(nodeManager.SystemContext, nodeHandle);
await Task.CompletedTask.ConfigureAwait(false);
}
private static async Task<(bool IsSuccess, Exception? Error)> RunTaskInParallelAsync(
Func task,
int iterations)
{
using var cancellationTokenSource = new CancellationTokenSource();
Exception? error = null;
int tasksCompletedCount = 0;
ParallelLoopResult result = Parallel.For(
0,
iterations,
new ParallelOptions(),
async _ =>
{
try
{
await task().ConfigureAwait(false);
}
catch (Exception ex)
{
error = ex;
cancellationTokenSource.Cancel();
}
finally
{
tasksCompletedCount++;
}
});
int spinWaitCount = 0;
const int maxSpinWaitCount = 100;
while (iterations > tasksCompletedCount &&
error is null &&
spinWaitCount < maxSpinWaitCount)
{
await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false);
spinWaitCount++;
}
return (error == null, error);
}
}
public class TestableCustomNodeManger2 : CustomNodeManager2
{
public TestableCustomNodeManger2(IServerInternal server, params string[] namespaceUris)
: base(server, namespaceUris)
{
}
public new NodeState AddNodeToComponentCache(
ISystemContext context,
NodeHandle handle,
NodeState node)
{
return base.AddNodeToComponentCache(context, handle, node);
}
public new void RemoveNodeFromComponentCache(ISystemContext context, NodeHandle handle)
{
base.RemoveNodeFromComponentCache(context, handle);
}
public new NodeState LookupNodeInComponentCache(ISystemContext context, NodeHandle handle)
{
return base.LookupNodeInComponentCache(context, handle)!;
}
public new NodeIdDictionary PredefinedNodes => base.PredefinedNodes;
public new virtual void AddPredefinedNode(ISystemContext context, NodeState node)
{
base.AddPredefinedNode(context, node);
}
}
}