diff --git a/samples/PumpDeviceIntegrationServer/Dockerfile b/samples/PumpDeviceIntegrationServer/Dockerfile index becd489807..90e3dd1bf6 100644 --- a/samples/PumpDeviceIntegrationServer/Dockerfile +++ b/samples/PumpDeviceIntegrationServer/Dockerfile @@ -78,10 +78,14 @@ COPY --from=build /app/publish . # The .NET base images ship a predefined non-root user `app` (UID 1654, # exposed via the APP_UID env var). The server creates its -# certificate/PKI store under /app at runtime, so make it writable by -# that user. -ENV HOME=/app -RUN mkdir -p /app/pki && chown -R $APP_UID:$APP_UID /app +# certificate/PKI store under /app at runtime. The shared /diag +# directory carries the diagnostic socket and collected artifacts for +# the companion Kubernetes diagnostics container. +ENV HOME=/app \ + DOTNET_DiagnosticPorts=/diag/dotnet-diagnostic.sock,suspend=n,listen +RUN mkdir -p /app/pki /diag \ + && chown -R $APP_UID:0 /app /diag \ + && chmod -R g=u /app /diag USER $APP_UID diff --git a/src/Opc.Ua.Server/NodeManager/MasterNodeManager.cs b/src/Opc.Ua.Server/NodeManager/MasterNodeManager.cs index 13b3fe683e..943f70d8ab 100644 --- a/src/Opc.Ua.Server/NodeManager/MasterNodeManager.cs +++ b/src/Opc.Ua.Server/NodeManager/MasterNodeManager.cs @@ -1897,7 +1897,11 @@ await GetManagerHandleAsync(view.ViewId, cancellationToken) BrowseDescription nodeToBrowse = nodesToBrowse[ii]; // initialize result. - var result = new BrowseResult { StatusCode = StatusCodes.Good }; + var result = new BrowseResult + { + StatusCode = StatusCodes.Good, + ContinuationPoint = default + }; results.Add(result); ServiceResult error; @@ -2065,7 +2069,11 @@ private static void PrepareValidationCache( .ConfigureAwait(false); if (ServiceResult.IsBad(validationResult)) { - var badResult = new BrowseResult { StatusCode = validationResult.Code }; + var badResult = new BrowseResult + { + StatusCode = validationResult.Code, + ContinuationPoint = default + }; results.Add(badResult); // put placeholder for diagnostics @@ -2075,7 +2083,11 @@ private static void PrepareValidationCache( } // initialize result. - var result = new BrowseResult { StatusCode = StatusCodes.Good }; + var result = new BrowseResult + { + StatusCode = StatusCodes.Good, + ContinuationPoint = default + }; results.Add(result); // check if simply releasing the continuation point. diff --git a/tests/Opc.Ua.InformationModel.Tests/BrowseContinuationPointTests.cs b/tests/Opc.Ua.InformationModel.Tests/BrowseContinuationPointTests.cs index d81d0d8c4f..b28908bdc6 100644 --- a/tests/Opc.Ua.InformationModel.Tests/BrowseContinuationPointTests.cs +++ b/tests/Opc.Ua.InformationModel.Tests/BrowseContinuationPointTests.cs @@ -118,8 +118,8 @@ public async Task BrowseNextUntilDoneCollectsAllReferencesAsync() cp = nextResp.Results[0].ContinuationPoint; } - Assert.That(cp.IsEmpty, Is.True, - "Continuation point should be empty after full traversal."); + Assert.That(cp.IsNull, Is.True, + "Continuation point should be null after full traversal."); Assert.That(totalRefs, Is.GreaterThan(1), "Server node should have more than one reference."); } @@ -391,7 +391,7 @@ public async Task BrowseNodeWithFewReferencesNoContinuationNeededAsync() Assert.That(response.Results.Count, Is.EqualTo(1)); Assert.That( StatusCode.IsGood(response.Results[0].StatusCode), Is.True); - Assert.That(response.Results[0].ContinuationPoint.IsEmpty, Is.True, + Assert.That(response.Results[0].ContinuationPoint.IsNull, Is.True, "Views folder has few children; no continuation point " + "should be returned with MaxRefs=100."); } @@ -432,7 +432,7 @@ public async Task BrowseNextWithReleaseTrueReturnsNoReferencesAsync() Is.True, "Release should return Good status."); Assert.That( - releaseResp.Results[0].ContinuationPoint.IsEmpty, Is.True, + releaseResp.Results[0].ContinuationPoint.IsNull, Is.True, "No continuation point should remain after release."); } @@ -631,7 +631,7 @@ public async Task BrowseWithMaxRefsZeroReturnsAllAsync() Assert.That(response.Results[0].References.Count, Is.GreaterThan(0), "Server node should have at least one reference."); - Assert.That(response.Results[0].ContinuationPoint.IsEmpty, Is.True, + Assert.That(response.Results[0].ContinuationPoint.IsNull, Is.True, "MaxReferencesPerNode=0 should return all references " + "without a continuation point."); } diff --git a/tests/Opc.Ua.InformationModel.Tests/BrowseTests.cs b/tests/Opc.Ua.InformationModel.Tests/BrowseTests.cs index 505d8e9c26..57f989b10a 100644 --- a/tests/Opc.Ua.InformationModel.Tests/BrowseTests.cs +++ b/tests/Opc.Ua.InformationModel.Tests/BrowseTests.cs @@ -417,7 +417,7 @@ public async Task Browse010BrowseNextReleaseContinuationPointAsync() Assert.That(releaseResponse.Results.Count, Is.EqualTo(1)); Assert.That(StatusCode.IsGood(releaseResponse.Results[0].StatusCode), Is.True, "Releasing a continuation point should return Good."); - Assert.That(releaseResponse.Results[0].ContinuationPoint.IsEmpty, Is.True, + Assert.That(releaseResponse.Results[0].ContinuationPoint.IsNull, Is.True, "No continuation point should remain after release."); } diff --git a/tests/Opc.Ua.Server.Tests/NodeManager/MasterNodeManagerDeterministicTests.cs b/tests/Opc.Ua.Server.Tests/NodeManager/MasterNodeManagerDeterministicTests.cs index 66adc086cf..1924b8a279 100644 --- a/tests/Opc.Ua.Server.Tests/NodeManager/MasterNodeManagerDeterministicTests.cs +++ b/tests/Opc.Ua.Server.Tests/NodeManager/MasterNodeManagerDeterministicTests.cs @@ -200,6 +200,34 @@ public async Task BrowseAsync_UnknownNode_ReturnsBadNodeIdUnknownAsync() Assert.That(results.Count, Is.EqualTo(1)); Assert.That(results[0].StatusCode, Is.EqualTo(StatusCodes.BadNodeIdUnknown)); + Assert.That(results[0].ContinuationPoint.IsNull, Is.True); + } + + [Test] + public async Task BrowseAsyncCompletedBrowseReturnsNullContinuationPointAsync() + { + IMasterNodeManager sut = m_server.CurrentInstance.NodeManager; + OperationContext ctx = CreateContext(); + + var nodeToBrowse = new BrowseDescription + { + NodeId = ObjectIds.ViewsFolder, + BrowseDirection = BrowseDirection.Forward, + ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences, + IncludeSubtypes = true, + ResultMask = (uint)BrowseResultMask.All + }; + + (ArrayOf results, _) = await sut.BrowseAsync( + ctx, + new ViewDescription(), + 0u, + new BrowseDescription[] { nodeToBrowse }.ToArrayOf(), + CancellationToken.None).ConfigureAwait(false); + + Assert.That(results.Count, Is.EqualTo(1)); + Assert.That(results[0].StatusCode, Is.EqualTo(StatusCodes.Good)); + Assert.That(results[0].ContinuationPoint.IsNull, Is.True); } [Test] @@ -330,6 +358,7 @@ public async Task BrowseNextAsync_InvalidContinuationPoint_ReturnsBadContinuatio Assert.That(results.Count, Is.EqualTo(1)); Assert.That(results[0].StatusCode, Is.EqualTo(StatusCodes.BadContinuationPointInvalid)); + Assert.That(results[0].ContinuationPoint.IsNull, Is.True); } [Test] @@ -351,6 +380,7 @@ public async Task BrowseNextAsync_ReleaseInvalidContinuationPoint_ReturnsGoodAsy Assert.That(results.Count, Is.EqualTo(1)); Assert.That(results[0].StatusCode, Is.EqualTo(StatusCodes.Good)); + Assert.That(results[0].ContinuationPoint.IsNull, Is.True); } [Test]