Merge re/r1.10-rename-tags: RenameTagsAsync via History StartJob
# Conflicts: # docs/plans/hcal-capability-matrix.md # docs/plans/hcal-roadmap.md # src/AVEVA.Historian.Client/Wcf/HistorianWcfTagWriteOrchestrator.cs # tests/AVEVA.Historian.Client.Tests/HistorianClientIntegrationTests.cs # tools/AVEVA.Historian.NativeTraceHarness/Program.cs
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# Tag rename over 2020 WCF — StartJob (StJb) rename job (HCAL R1.10)
|
||||
|
||||
**Status: ✅ DONE + live-verified (2026-06-21).** `HistorianClient.RenameTagsAsync` /
|
||||
`RenameTagAsync` renames tags by submitting an asynchronous rename **job** to the Historian. Decoded
|
||||
from an instrumented native `RenameTags` capture and verified end-to-end from the pure-managed .NET 10
|
||||
client against the local 2020 Historian (sandbox tag created → renamed → new name visible → cleaned up).
|
||||
|
||||
## The op — rename rides the generic job framework
|
||||
|
||||
There is **no dedicated rename WCF operation**. The native `RenameTags(Tuple<string,string>[] pairs,
|
||||
ref HistorianTagRenameStatus, out error)` packs the batch into the generic History **`StartJob`**
|
||||
(`StJb`) buffer; the server returns a job id and applies the renames in the background. The native
|
||||
client then polls `GetJobStatus` (`GtJb`) until the job reports done.
|
||||
|
||||
```
|
||||
bool StartJob(string handle, byte[] jobBuffer, out string jobId, out byte[] errorBuffer) // StJb
|
||||
bool GetJobStatus(string handle, string jobId, out byte[] jobStatus, out byte[] errorBuffer) // GtJb
|
||||
```
|
||||
|
||||
Both already existed in `IHistoryServiceContract2`. `StartJob` takes a **string handle** = the Open2
|
||||
storage-session GUID formatted `storageSessionId.ToString("D").ToUpperInvariant()` (the same uppercase
|
||||
handle used by the other string-handle ops). The connection must be **write-enabled** (Open2 mode
|
||||
`0x401`); the SDK reuses the write orchestrator's open + priming chain.
|
||||
|
||||
## The rename jobBuffer (decoded + server-validated)
|
||||
|
||||
```
|
||||
byte[7] reserved / job-descriptor prefix (all zero in every capture)
|
||||
uint32 pairCount
|
||||
repeated pairCount times:
|
||||
uint32 oldNameCharCount + UTF-16LE oldName (the tag being renamed)
|
||||
uint32 newNameCharCount + UTF-16LE newName (its new name)
|
||||
```
|
||||
|
||||
Char counts are UTF-16 code-unit counts. Pair order is **(old, new)**. ⚠️ A **raw** instrument
|
||||
capture mangles the buffer's final byte with MDAS chunk markers (`9E`/`9F`) — the same hazard noted
|
||||
for R1.1. So the golden fixture is the **clean** byte[] the SDK hands the WCF channel, dumped via the
|
||||
`AVEVA_HISTORIAN_RENAME_DUMP` env hook on `HistorianWcfTagWriteOrchestrator`. That exact buffer was
|
||||
accepted by the live server and the tag was renamed, so it is server-validated, not hand-stitched.
|
||||
|
||||
## Server gate — `AllowRenameTags`
|
||||
|
||||
Rename is gated by the **`AllowRenameTags`** system parameter (default **0/disabled**). When disabled,
|
||||
the **native** client library rejects the call *before the wire* (`error 132 OperationNotEnabled`,
|
||||
component `aahClientCommon::CClientCommon::RenameTags`); the managed SDK has no such pre-check, so a
|
||||
disabled gate surfaces as `StartJob` returning false (reported as `Accepted = false`).
|
||||
|
||||
To enable for testing: `EXEC Runtime.dbo.aaSystemParameterUpdate @name='AllowRenameTags', @value=1`
|
||||
**and reload the Historian config** — the running services cache system parameters, so the value only
|
||||
takes effect after the Historian reloads (a Historian restart; a storage-engine-only restart is **not**
|
||||
enough — the value is served from the `InSQLConfiguration` cache). Restore to `0` when done.
|
||||
|
||||
## Async completion
|
||||
|
||||
`RenameTagsAsync` submits the job and returns `HistorianTagRenameResult { Accepted, JobId, PairCount,
|
||||
Error }`. The renames apply asynchronously server-side (observed: the native `GetTagRenameStatus` went
|
||||
`Pending=true` → `false` within ~1.5 s for a single rename on the local box). The SDK does **not** poll
|
||||
`GtJb` for completion: only the *pending* `jobstatus` buffer (6 zero bytes) was captured — the
|
||||
done-state encoding was not, so polling is intentionally left out rather than guessing it. The gated
|
||||
live test confirms completion by polling the new name's metadata after submission.
|
||||
|
||||
## Shipped surface
|
||||
|
||||
- `HistorianClient.RenameTagAsync(old, new)` / `RenameTagsAsync(IReadOnlyList<(string,string)>)`
|
||||
→ `HistorianTagRenameResult`.
|
||||
- `HistorianTagRenameProtocol.SerializeRenameJob` (the jobBuffer serializer);
|
||||
`HistorianWcfTagWriteOrchestrator.RenameTags`/`SendStartJobRename` (open → write-priming → StJb).
|
||||
- Golden `WcfTagRenameProtocolTests` (pins the server-accepted buffer + layout); gated live test
|
||||
`RenameTagsAsync_AgainstLocalHistorian_RenamesSandboxTag` (needs `HISTORIAN_HOST=localhost`,
|
||||
`HISTORIAN_RENAME_SANDBOX=RetestSdkWrite…`, and `AllowRenameTags` enabled).
|
||||
|
||||
## Capture / decode tooling
|
||||
|
||||
`scripts/Capture-RenameTags.ps1` (native-harness `rename` scenario + instrument-wcf-{write,read}message;
|
||||
sandbox-guarded create→rename→cleanup) and `scripts/decode-rename-capture.py`.
|
||||
|
||||
## Scope notes
|
||||
|
||||
- String-valued, original tag renames only. The multi-pair batch framing is captured (count-prefixed)
|
||||
and unit-tested; the live test exercises a single pair.
|
||||
- `RenameSourceTags` (replication/source-server rename) is **not** shipped — different op signature
|
||||
(adds a source-server string), not captured.
|
||||
Reference in New Issue
Block a user