mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-21 09:06:37 +08:00
3690341c80
# Description
**Software update methods** (`fix(di): wire PrepareForUpdate
Abort/Resume and Installation Resume`)
- New internal `SoftwareUpdateTransitions` (one per `SoftwareUpdate`)
checks the current state and moves it under one lock, so concurrent
calls cannot both pass a check.
- `Prepare` (§8.4.8.3) is accepted only from `Idle`. The application's
`OnPrepare` callback gets a token that `Abort` cancels; the token source
is disposed only after both the Prepare call and a racing Abort are done
with it, so a handler still running after Abort can use its token.
- `Abort` (§8.4.8.4) moves `Preparing` to `Idle` (`PreparingToIdle`),
cancels the preparation in flight and reports `Failed` ("Aborted")
through `OnPrepareStateChanged`; the aborted `Prepare` call returns
`Bad_InvalidState`. Outside `Preparing` it returns `Bad_InvalidState`.
- `Resume` (§8.4.8.5) no longer runs the Prepare callback. It moves
`PreparedForUpdate` through `Resuming` to `Idle` and is rejected with
`Bad_InvalidState` outside `PreparedForUpdate` or while the installation
is `Installing`.
- `Installation.Resume` (§8.4.9) moves `Error` to `Idle`
(`ErrorToIdle`),
`Bad_InvalidState` otherwise.
- The dispatcher knows the `Resuming` state and the `ResumingToIdle`
transition, so `CurrentState` / `LastTransition` show their names.
**Advertising** (`fix(di): advertise only the DI facets whose mandatory
units are met`)
- `DI Offline`, `DI SU UpdateStatus`, `DI SU FileSystem Loading`,
`DI SU Installation for File System` and `DI SU Installation for Cached
Loading` are no longer claimed (see the issue for why each is unmet).
- The facets are derived from the advertised units with the mandatory
units of the DI 1.05 profile database, so a facet cannot be listed
without them. Today this leaves Locking and Software Update Base;
DeviceIntegrationHost, FileSystem, Direct and Cached Loading come back
by themselves once the missing pieces are built.
- `docs/DeviceIntegration.md` documents both tables and the missing
pieces.
Tests: `SoftwareUpdateMethodTransitionTests` (9 tests) and
`DiNodeManagerConformanceTests` (5 tests) are new;
`DiConformanceFacetTests`
and `PumpAddressSpaceComplianceTests` expected the old claims and are
updated. All 9 transition tests and 8 of the 10 conformance tests fail
against master; the other two are the unchanged locking test and a guard
that every advertised facet carries its mandatory units (master
over-claims
the units too, so the guard alone holds there).
Behaviour change for users: a plain DI server no longer lists the
DeviceIntegrationHost facet, and Direct/Cached/Package software updates
list only Software Update Base.
Fork CI (full matrix incl. macOS) and CodeQL: green — [CI run
36779552603](https://github.com/biancode/UA-.NETStandard/actions/runs/36779552603),
[CodeQL run
36779556125](https://github.com/biancode/UA-.NETStandard/actions/runs/36779556125).
## Related Issues
- Fixes #4616
## Checklist
- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.
---------
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
99 lines
3.9 KiB
C#
99 lines
3.9 KiB
C#
/* ========================================================================
|
|
* Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
|
|
*
|
|
* OPC Foundation MIT License 1.00
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* The complete license agreement can be found here:
|
|
* http://opcfoundation.org/License/MIT/1.00/
|
|
* ======================================================================*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using Opc.Ua.Di.Server;
|
|
using Opc.Ua.Server;
|
|
using Opc.Ua.Server.TestFramework;
|
|
|
|
namespace Opc.Ua.Di.Tests
|
|
{
|
|
/// <summary>
|
|
/// Test fixture that boots a minimal in-process OPC UA server with a
|
|
/// single <see cref="DiNodeManager"/> registered. Used by the device-
|
|
/// builder tests so they can exercise the full predefined-node
|
|
/// pipeline (lifecycle, NodeIdFactory, type tree) without managing
|
|
/// the boilerplate.
|
|
/// </summary>
|
|
internal sealed class DiServerFixture : IAsyncDisposable
|
|
{
|
|
/// <summary>
|
|
/// Creates a fixture that registers a plain <see cref="DiNodeManager"/>.
|
|
/// </summary>
|
|
public DiServerFixture()
|
|
: this((server, configuration) => new DiNodeManager(server, configuration))
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a fixture that registers the manager the factory creates,
|
|
/// such as a subclass a companion specification would add.
|
|
/// </summary>
|
|
public DiServerFixture(Func<IServerInternal, ApplicationConfiguration, DiNodeManager> createManager)
|
|
{
|
|
m_createManager = createManager;
|
|
}
|
|
|
|
public StandardServer Server { get; private set; } = null!;
|
|
public DiNodeManager Manager { get; private set; } = null!;
|
|
|
|
public async Task StartAsync()
|
|
{
|
|
m_fixture = new ServerFixture<StandardServer>(t => new StandardServer(t))
|
|
{
|
|
AutoAccept = true,
|
|
SecurityNone = true
|
|
};
|
|
|
|
Server = await m_fixture.StartAsync().ConfigureAwait(false);
|
|
|
|
// Construct the DiNodeManager against the running server and
|
|
// populate its address space by running the standard
|
|
// CreateAddressSpaceAsync pipeline.
|
|
Manager = m_createManager(Server.CurrentInstance, m_fixture.Config);
|
|
var externalReferences = new Dictionary<NodeId, IList<IReference>>();
|
|
await Manager.CreateAddressSpaceAsync(externalReferences).ConfigureAwait(false);
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
Manager?.Dispose();
|
|
if (m_fixture != null)
|
|
{
|
|
await m_fixture.StopAsync().ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
private readonly Func<IServerInternal, ApplicationConfiguration, DiNodeManager> m_createManager;
|
|
private ServerFixture<StandardServer>? m_fixture;
|
|
}
|
|
}
|