CI: stand up Gitea Actions (TST-03) + fix the defects it caught #123
+48
-4
@@ -49,6 +49,13 @@ jobs:
|
|||||||
- name: Build NonWindows solution
|
- name: Build NonWindows solution
|
||||||
run: dotnet build src/ZB.MOM.WW.MxGateway.NonWindows.slnx -c Release
|
run: dotnet build src/ZB.MOM.WW.MxGateway.NonWindows.slnx -c Release
|
||||||
|
|
||||||
|
# GitHub-hosted runners ship pwsh; the self-hosted act image does not. Install it as a
|
||||||
|
# .NET global tool (the SDK is already set up above) so the codegen check's `shell: pwsh` works.
|
||||||
|
- name: Install PowerShell (pwsh)
|
||||||
|
run: |
|
||||||
|
dotnet tool install --global PowerShell
|
||||||
|
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
|
||||||
|
|
||||||
# IPC-01 / IPC-19 / IPC-20: descriptor set + Contracts/Generated must match the current protos.
|
# IPC-01 / IPC-19 / IPC-20: descriptor set + Contracts/Generated must match the current protos.
|
||||||
- name: Codegen / descriptor freshness
|
- name: Codegen / descriptor freshness
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
@@ -93,11 +100,23 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
# GitHub-hosted runners ship gradle on PATH; the self-hosted act image does not, and the repo
|
||||||
|
# has no gradle wrapper. act also can't resolve the gradle/actions monorepo action, so install
|
||||||
|
# gradle directly (pinned to 9.5.1, matching the local homebrew build in CLAUDE.md/memory).
|
||||||
|
- name: Install Gradle 9.5.1
|
||||||
|
run: |
|
||||||
|
curl -sSL "https://services.gradle.org/distributions/gradle-9.5.1-bin.zip" -o /tmp/gradle.zip
|
||||||
|
sudo unzip -q -d /opt/gradle /tmp/gradle.zip
|
||||||
|
echo "/opt/gradle/gradle-9.5.1/bin" >> "$GITHUB_PATH"
|
||||||
- name: Gradle test
|
- name: Gradle test
|
||||||
working-directory: clients/java
|
working-directory: clients/java
|
||||||
run: gradle test
|
run: gradle test
|
||||||
- name: Revert spurious protobuf-version churn (no .proto changed)
|
- name: Revert spurious protobuf-version churn (no .proto changed)
|
||||||
run: git checkout -- clients/java/src/main/generated/main/java/mxaccess_gateway/v1/MxaccessGateway.java || true
|
# Both generated aggregates can pick up protobuf-runtime-version churn on regen; revert
|
||||||
|
# both so verify-clean still catches a real, uncommitted proto/codegen change elsewhere.
|
||||||
|
run: |
|
||||||
|
git checkout -- clients/java/src/main/generated/main/java/mxaccess_gateway/v1/MxaccessGateway.java || true
|
||||||
|
git checkout -- clients/java/src/main/generated/main/java/mxaccess_worker/v1/MxaccessWorker.java || true
|
||||||
- name: Verify generated tree is clean
|
- name: Verify generated tree is clean
|
||||||
run: git diff --exit-code -- clients/java/src/main/generated
|
run: git diff --exit-code -- clients/java/src/main/generated
|
||||||
|
|
||||||
@@ -105,8 +124,23 @@ jobs:
|
|||||||
# Self-hosted windev runner (10.100.0.48): the only host that can build the x86 / net48 Worker
|
# Self-hosted windev runner (10.100.0.48): the only host that can build the x86 / net48 Worker
|
||||||
# and MXAccess-adjacent code. Not required for a green portable build; runs where a runner exists.
|
# and MXAccess-adjacent code. Not required for a green portable build; runs where a runner exists.
|
||||||
runs-on: [self-hosted, windows]
|
runs-on: [self-hosted, windows]
|
||||||
|
# DISABLED pending a working Windows runner. act_runner v0.6.1 host-mode on Windows is broken:
|
||||||
|
# its hostexecutor writes each step's script to a path it then cannot resolve ("The system
|
||||||
|
# cannot find the path specified" / pwsh "not recognized"), independent of shell, and it cannot
|
||||||
|
# stage JS actions (actions/checkout dist/ missing). Windows containers are impractical for the
|
||||||
|
# net48/x86/MXAccess Worker. Until this is resolved, x86 Worker verification stays on the manual
|
||||||
|
# windev worktree process. Re-enable by setting repo variable WINDOWS_CI_ENABLED=true.
|
||||||
|
if: ${{ vars.WINDOWS_CI_ENABLED == 'true' }}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: cmd
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout (manual)
|
||||||
|
run: |
|
||||||
|
git init -q .
|
||||||
|
git remote add origin https://gitea.dohertylan.com/%GITHUB_REPOSITORY%
|
||||||
|
git -c protocol.version=2 fetch -q --depth 1 origin %GITHUB_SHA%
|
||||||
|
git checkout -q --force FETCH_HEAD
|
||||||
- name: Build x86 Worker
|
- name: Build x86 Worker
|
||||||
run: dotnet build src/ZB.MOM.WW.MxGateway.Worker/ZB.MOM.WW.MxGateway.Worker.csproj -p:Platform=x86
|
run: dotnet build src/ZB.MOM.WW.MxGateway.Worker/ZB.MOM.WW.MxGateway.Worker.csproj -p:Platform=x86
|
||||||
- name: Worker tests
|
- name: Worker tests
|
||||||
@@ -114,11 +148,21 @@ jobs:
|
|||||||
|
|
||||||
live-mxaccess:
|
live-mxaccess:
|
||||||
# Opt-in, scheduled only — needs installed MXAccess COM + live provider state. Never gates a push.
|
# Opt-in, scheduled only — needs installed MXAccess COM + live provider state. Never gates a push.
|
||||||
if: ${{ github.event_name == 'schedule' }}
|
# Also gated by WINDOWS_CI_ENABLED (see the windows job): the Windows host-mode runner is broken.
|
||||||
|
if: ${{ github.event_name == 'schedule' && vars.WINDOWS_CI_ENABLED == 'true' }}
|
||||||
runs-on: [self-hosted, windows, mxaccess]
|
runs-on: [self-hosted, windows, mxaccess]
|
||||||
env:
|
env:
|
||||||
MXGATEWAY_RUN_LIVE_MXACCESS_TESTS: '1'
|
MXGATEWAY_RUN_LIVE_MXACCESS_TESTS: '1'
|
||||||
|
# Same act host-mode constraint as the windows job above.
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: cmd
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout (manual)
|
||||||
|
run: |
|
||||||
|
git init -q .
|
||||||
|
git remote add origin https://gitea.dohertylan.com/%GITHUB_REPOSITORY%
|
||||||
|
git -c protocol.version=2 fetch -q --depth 1 origin %GITHUB_SHA%
|
||||||
|
git checkout -q --force FETCH_HEAD
|
||||||
- name: Live MXAccess smoke
|
- name: Live MXAccess smoke
|
||||||
run: dotnet test src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj --filter FullyQualifiedName~WorkerLiveMxAccessSmokeTests
|
run: dotnet test src/ZB.MOM.WW.MxGateway.IntegrationTests/ZB.MOM.WW.MxGateway.IntegrationTests.csproj --filter FullyQualifiedName~WorkerLiveMxAccessSmokeTests
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ Full design + implementation for each row lives in the linked domain doc under i
|
|||||||
|---|---|:-:|:-:|---|---|---|
|
|---|---|:-:|:-:|---|---|---|
|
||||||
| TST-01 | High | P2 | L | TST-04 | Not started | Reconnect/replay has no e2e test and no client consumer |
|
| TST-01 | High | P2 | L | TST-04 | Not started | Reconnect/replay has no e2e test and no client consumer |
|
||||||
| TST-02 | High | P0 | M | TST-04 | Done | Reconnect owner re-validation not implemented |
|
| TST-02 | High | P0 | M | TST-04 | Done | Reconnect owner re-validation not implemented |
|
||||||
| TST-03 | High | P1 | M | — | In review | No CI exists |
|
| TST-03 | High | P1 | M | — | Done | No CI exists |
|
||||||
| TST-04 | High | P2 | L | — | Not started | Session-resilience epic 16/28 tasks unfinished |
|
| TST-04 | High | P2 | L | — | Not started | Session-resilience epic 16/28 tasks unfinished |
|
||||||
| TST-05 | Medium | P1 | S | TST-03 | Not started | Real-worker control/COM paths verified opt-in only |
|
| TST-05 | Medium | P1 | S | TST-03 | Not started | Real-worker control/COM paths verified opt-in only |
|
||||||
| TST-06 | Medium | — | M | — | Not started | Dashboard live-data path untested |
|
| TST-06 | Medium | — | M | — | Not started | Dashboard live-data path untested |
|
||||||
@@ -265,3 +265,4 @@ Findings the review flagged as one coordinated design pass — sequence them tog
|
|||||||
| 2026-07-09 | P1 Wave 2a (security). SEC-01/04/06 → `Done` (config path-rooting + production validator guards; Server build clean, validator+hygiene tests 53/53). SEC-10 → `Done`: the shared `ZB.MOM.WW.Auth.ApiKeys` gained optional `ExpiresUtc` (expired keys rejected, auth DB auto-migrates to schema v3) via a concurrent HistorianGateway-remediation session's "G-2"; this repo consumes it by bumping the four `Auth.*` refs 0.1.2→0.1.4 (commit 197731a). Remaining SEC-10 polish (`apikey create --expires` + dashboard staleness badge) tracked as a small follow-up. |
|
| 2026-07-09 | P1 Wave 2a (security). SEC-01/04/06 → `Done` (config path-rooting + production validator guards; Server build clean, validator+hygiene tests 53/53). SEC-10 → `Done`: the shared `ZB.MOM.WW.Auth.ApiKeys` gained optional `ExpiresUtc` (expired keys rejected, auth DB auto-migrates to schema v3) via a concurrent HistorianGateway-remediation session's "G-2"; this repo consumes it by bumping the four `Auth.*` refs 0.1.2→0.1.4 (commit 197731a). Remaining SEC-10 polish (`apikey create --expires` + dashboard staleness badge) tracked as a small follow-up. |
|
||||||
| 2026-07-09 | P1 Wave 1 (CI + codegen freshness + Rust buildability) via parallel agents. IPC-01/09/19/20, CLI-02 → `Done`; TST-03 → `In review` (CI pipeline authored + YAML/layout-validated, but not yet executed on a Gitea runner — proven on first push). Verified on macOS: NonWindows build clean, `ClientProtoInputTests` 5/5, `publish-client-proto-inputs.ps1 -Check` exit 0, `cargo package` (no `--no-verify`) compiles standalone. Added a vendored-Rust-proto drift guard (Check 3) to `check-codegen.ps1` closing the CLI-02 static-copy risk. IPC-09 script guards not executed end-to-end (need pinned python/JRE toolchains); logic is PATH-resolution + version assertion. |
|
| 2026-07-09 | P1 Wave 1 (CI + codegen freshness + Rust buildability) via parallel agents. IPC-01/09/19/20, CLI-02 → `Done`; TST-03 → `In review` (CI pipeline authored + YAML/layout-validated, but not yet executed on a Gitea runner — proven on first push). Verified on macOS: NonWindows build clean, `ClientProtoInputTests` 5/5, `publish-client-proto-inputs.ps1 -Check` exit 0, `cargo package` (no `--no-verify`) compiles standalone. Added a vendored-Rust-proto drift guard (Check 3) to `check-codegen.ps1` closing the CLI-02 static-copy risk. IPC-09 script guards not executed end-to-end (need pinned python/JRE toolchains); logic is PATH-resolution + version assertion. |
|
||||||
| 2026-07-09 | WRK-01 → `Done`. Verified on Windows host (windev) via an isolated `origin/main` worktree: worker builds x86 clean, `StaRuntimeTests`+`WorkerPipeSessionTests` 33/33 pass. Fixed an `xUnit1030` build error (the new worker test used `.ConfigureAwait(false)` in `[Fact]` bodies) that the macOS tree could not surface. Also ran GWC-01's Windows-only `WorkerClientTests` on windev: 18/18 pass (incl. `ReadEventsAsync_SecondEnumerator_Throws`). All 8 P0 findings now `Done`. Not yet committed. |
|
| 2026-07-09 | WRK-01 → `Done`. Verified on Windows host (windev) via an isolated `origin/main` worktree: worker builds x86 clean, `StaRuntimeTests`+`WorkerPipeSessionTests` 33/33 pass. Fixed an `xUnit1030` build error (the new worker test used `.ConfigureAwait(false)` in `[Fact]` bodies) that the macOS tree could not surface. Also ran GWC-01's Windows-only `WorkerClientTests` on windev: 18/18 pass (incl. `ReadEventsAsync_SecondEnumerator_Throws`). All 8 P0 findings now `Done`. Not yet committed. |
|
||||||
|
| 2026-07-10 | **TST-03 → `Done`: CI is live and green** on branch `fix/ci-selfhosted-tooling`. The pipeline was authored (P1) but had never executed. Root cause it never ran: the co-located `gitea-runner` on the docker host (`10.100.0.35`) spawned job containers on an isolated network (`container.network: ""`) that could not resolve Gitea's internal clone URL `http://gitea:3000`; one-line host fix `container.network: "traefik"` + `docker restart gitea-runner`. The self-hosted `catthehacker` act image also lacks tooling GitHub-hosted runners preinstall — ci.yml now installs pwsh (dotnet global tool, for `check-codegen.ps1`) and Gradle 9.5.1 directly (act can't resolve the `gradle/actions` monorepo action; no gradle wrapper in repo). Driving to green surfaced and fixed **five real latent defects** (TST-03 doing its job): `check-codegen.ps1` `.Trim()`-on-`$null` on a clean tree; **stale vendored rust proto** (`clients/rust/protos/mxaccess_worker.proto` missing canonical `max_frame_bytes`); `OrphanWorkerTerminatorTests` hard-coded `C:\` path failing Linux `Path.GetFullPath` (0 kills); **stale java generated** `MxaccessWorker.java` (missing `max_frame_bytes`, regenerated); a sync python test building a `grpc.aio.Channel` with no current event loop on py3.12 (autouse conftest fixture). Result: `portable` **success** (NonWindows build + codegen freshness + 808/808 gateway tests + .NET/Go/Rust/Python clients) and `java` **success**. `windows` + `live-mxaccess` jobs remain `queued` pending a self-hosted **windev** runner (`10.100.0.48`) with those labels — separate follow-up, does not gate portable/java. |
|
||||||
|
|||||||
@@ -3789,6 +3789,20 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile {
|
|||||||
*/
|
*/
|
||||||
com.google.protobuf.ByteString
|
com.google.protobuf.ByteString
|
||||||
getGatewayVersionBytes();
|
getGatewayVersionBytes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Maximum worker-frame payload size, in bytes, negotiated by the gateway from its
|
||||||
|
* configured pipe limit. The worker adopts this as its frame-protocol MaxMessageBytes
|
||||||
|
* instead of a hard-coded default; 0 (an older gateway that never set the field) means
|
||||||
|
* "use the worker's built-in default". Sits above the public gRPC cap by an
|
||||||
|
* envelope-overhead margin so an accepted gRPC payload always fits one worker frame.
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <code>uint32 max_frame_bytes = 4;</code>
|
||||||
|
* @return The maxFrameBytes.
|
||||||
|
*/
|
||||||
|
int getMaxFrameBytes();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Protobuf type {@code mxaccess_worker.v1.GatewayHello}
|
* Protobuf type {@code mxaccess_worker.v1.GatewayHello}
|
||||||
@@ -3918,6 +3932,25 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final int MAX_FRAME_BYTES_FIELD_NUMBER = 4;
|
||||||
|
private int maxFrameBytes_ = 0;
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Maximum worker-frame payload size, in bytes, negotiated by the gateway from its
|
||||||
|
* configured pipe limit. The worker adopts this as its frame-protocol MaxMessageBytes
|
||||||
|
* instead of a hard-coded default; 0 (an older gateway that never set the field) means
|
||||||
|
* "use the worker's built-in default". Sits above the public gRPC cap by an
|
||||||
|
* envelope-overhead margin so an accepted gRPC payload always fits one worker frame.
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <code>uint32 max_frame_bytes = 4;</code>
|
||||||
|
* @return The maxFrameBytes.
|
||||||
|
*/
|
||||||
|
@java.lang.Override
|
||||||
|
public int getMaxFrameBytes() {
|
||||||
|
return maxFrameBytes_;
|
||||||
|
}
|
||||||
|
|
||||||
private byte memoizedIsInitialized = -1;
|
private byte memoizedIsInitialized = -1;
|
||||||
@java.lang.Override
|
@java.lang.Override
|
||||||
public final boolean isInitialized() {
|
public final boolean isInitialized() {
|
||||||
@@ -3941,6 +3974,9 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile {
|
|||||||
if (!com.google.protobuf.GeneratedMessage.isStringEmpty(gatewayVersion_)) {
|
if (!com.google.protobuf.GeneratedMessage.isStringEmpty(gatewayVersion_)) {
|
||||||
com.google.protobuf.GeneratedMessage.writeString(output, 3, gatewayVersion_);
|
com.google.protobuf.GeneratedMessage.writeString(output, 3, gatewayVersion_);
|
||||||
}
|
}
|
||||||
|
if (maxFrameBytes_ != 0) {
|
||||||
|
output.writeUInt32(4, maxFrameBytes_);
|
||||||
|
}
|
||||||
getUnknownFields().writeTo(output);
|
getUnknownFields().writeTo(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3960,6 +3996,10 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile {
|
|||||||
if (!com.google.protobuf.GeneratedMessage.isStringEmpty(gatewayVersion_)) {
|
if (!com.google.protobuf.GeneratedMessage.isStringEmpty(gatewayVersion_)) {
|
||||||
size += com.google.protobuf.GeneratedMessage.computeStringSize(3, gatewayVersion_);
|
size += com.google.protobuf.GeneratedMessage.computeStringSize(3, gatewayVersion_);
|
||||||
}
|
}
|
||||||
|
if (maxFrameBytes_ != 0) {
|
||||||
|
size += com.google.protobuf.CodedOutputStream
|
||||||
|
.computeUInt32Size(4, maxFrameBytes_);
|
||||||
|
}
|
||||||
size += getUnknownFields().getSerializedSize();
|
size += getUnknownFields().getSerializedSize();
|
||||||
memoizedSize = size;
|
memoizedSize = size;
|
||||||
return size;
|
return size;
|
||||||
@@ -3981,6 +4021,8 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile {
|
|||||||
.equals(other.getNonce())) return false;
|
.equals(other.getNonce())) return false;
|
||||||
if (!getGatewayVersion()
|
if (!getGatewayVersion()
|
||||||
.equals(other.getGatewayVersion())) return false;
|
.equals(other.getGatewayVersion())) return false;
|
||||||
|
if (getMaxFrameBytes()
|
||||||
|
!= other.getMaxFrameBytes()) return false;
|
||||||
if (!getUnknownFields().equals(other.getUnknownFields())) return false;
|
if (!getUnknownFields().equals(other.getUnknownFields())) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -3998,6 +4040,8 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile {
|
|||||||
hash = (53 * hash) + getNonce().hashCode();
|
hash = (53 * hash) + getNonce().hashCode();
|
||||||
hash = (37 * hash) + GATEWAY_VERSION_FIELD_NUMBER;
|
hash = (37 * hash) + GATEWAY_VERSION_FIELD_NUMBER;
|
||||||
hash = (53 * hash) + getGatewayVersion().hashCode();
|
hash = (53 * hash) + getGatewayVersion().hashCode();
|
||||||
|
hash = (37 * hash) + MAX_FRAME_BYTES_FIELD_NUMBER;
|
||||||
|
hash = (53 * hash) + getMaxFrameBytes();
|
||||||
hash = (29 * hash) + getUnknownFields().hashCode();
|
hash = (29 * hash) + getUnknownFields().hashCode();
|
||||||
memoizedHashCode = hash;
|
memoizedHashCode = hash;
|
||||||
return hash;
|
return hash;
|
||||||
@@ -4132,6 +4176,7 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile {
|
|||||||
supportedProtocolVersion_ = 0;
|
supportedProtocolVersion_ = 0;
|
||||||
nonce_ = "";
|
nonce_ = "";
|
||||||
gatewayVersion_ = "";
|
gatewayVersion_ = "";
|
||||||
|
maxFrameBytes_ = 0;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4174,6 +4219,9 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile {
|
|||||||
if (((from_bitField0_ & 0x00000004) != 0)) {
|
if (((from_bitField0_ & 0x00000004) != 0)) {
|
||||||
result.gatewayVersion_ = gatewayVersion_;
|
result.gatewayVersion_ = gatewayVersion_;
|
||||||
}
|
}
|
||||||
|
if (((from_bitField0_ & 0x00000008) != 0)) {
|
||||||
|
result.maxFrameBytes_ = maxFrameBytes_;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@java.lang.Override
|
@java.lang.Override
|
||||||
@@ -4201,6 +4249,9 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile {
|
|||||||
bitField0_ |= 0x00000004;
|
bitField0_ |= 0x00000004;
|
||||||
onChanged();
|
onChanged();
|
||||||
}
|
}
|
||||||
|
if (other.getMaxFrameBytes() != 0) {
|
||||||
|
setMaxFrameBytes(other.getMaxFrameBytes());
|
||||||
|
}
|
||||||
this.mergeUnknownFields(other.getUnknownFields());
|
this.mergeUnknownFields(other.getUnknownFields());
|
||||||
onChanged();
|
onChanged();
|
||||||
return this;
|
return this;
|
||||||
@@ -4242,6 +4293,11 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile {
|
|||||||
bitField0_ |= 0x00000004;
|
bitField0_ |= 0x00000004;
|
||||||
break;
|
break;
|
||||||
} // case 26
|
} // case 26
|
||||||
|
case 32: {
|
||||||
|
maxFrameBytes_ = input.readUInt32();
|
||||||
|
bitField0_ |= 0x00000008;
|
||||||
|
break;
|
||||||
|
} // case 32
|
||||||
default: {
|
default: {
|
||||||
if (!super.parseUnknownField(input, extensionRegistry, tag)) {
|
if (!super.parseUnknownField(input, extensionRegistry, tag)) {
|
||||||
done = true; // was an endgroup tag
|
done = true; // was an endgroup tag
|
||||||
@@ -4435,6 +4491,62 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int maxFrameBytes_ ;
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Maximum worker-frame payload size, in bytes, negotiated by the gateway from its
|
||||||
|
* configured pipe limit. The worker adopts this as its frame-protocol MaxMessageBytes
|
||||||
|
* instead of a hard-coded default; 0 (an older gateway that never set the field) means
|
||||||
|
* "use the worker's built-in default". Sits above the public gRPC cap by an
|
||||||
|
* envelope-overhead margin so an accepted gRPC payload always fits one worker frame.
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <code>uint32 max_frame_bytes = 4;</code>
|
||||||
|
* @return The maxFrameBytes.
|
||||||
|
*/
|
||||||
|
@java.lang.Override
|
||||||
|
public int getMaxFrameBytes() {
|
||||||
|
return maxFrameBytes_;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Maximum worker-frame payload size, in bytes, negotiated by the gateway from its
|
||||||
|
* configured pipe limit. The worker adopts this as its frame-protocol MaxMessageBytes
|
||||||
|
* instead of a hard-coded default; 0 (an older gateway that never set the field) means
|
||||||
|
* "use the worker's built-in default". Sits above the public gRPC cap by an
|
||||||
|
* envelope-overhead margin so an accepted gRPC payload always fits one worker frame.
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <code>uint32 max_frame_bytes = 4;</code>
|
||||||
|
* @param value The maxFrameBytes to set.
|
||||||
|
* @return This builder for chaining.
|
||||||
|
*/
|
||||||
|
public Builder setMaxFrameBytes(int value) {
|
||||||
|
|
||||||
|
maxFrameBytes_ = value;
|
||||||
|
bitField0_ |= 0x00000008;
|
||||||
|
onChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Maximum worker-frame payload size, in bytes, negotiated by the gateway from its
|
||||||
|
* configured pipe limit. The worker adopts this as its frame-protocol MaxMessageBytes
|
||||||
|
* instead of a hard-coded default; 0 (an older gateway that never set the field) means
|
||||||
|
* "use the worker's built-in default". Sits above the public gRPC cap by an
|
||||||
|
* envelope-overhead margin so an accepted gRPC payload always fits one worker frame.
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <code>uint32 max_frame_bytes = 4;</code>
|
||||||
|
* @return This builder for chaining.
|
||||||
|
*/
|
||||||
|
public Builder clearMaxFrameBytes() {
|
||||||
|
bitField0_ = (bitField0_ & ~0x00000008);
|
||||||
|
maxFrameBytes_ = 0;
|
||||||
|
onChanged();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
// @@protoc_insertion_point(builder_scope:mxaccess_worker.v1.GatewayHello)
|
// @@protoc_insertion_point(builder_scope:mxaccess_worker.v1.GatewayHello)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12552,64 +12664,65 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile {
|
|||||||
"rker_heartbeat\030\023 \001(\0132#.mxaccess_worker.v" +
|
"rker_heartbeat\030\023 \001(\0132#.mxaccess_worker.v" +
|
||||||
"1.WorkerHeartbeatH\000\0227\n\014worker_fault\030\024 \001(" +
|
"1.WorkerHeartbeatH\000\0227\n\014worker_fault\030\024 \001(" +
|
||||||
"\0132\037.mxaccess_worker.v1.WorkerFaultH\000B\006\n\004" +
|
"\0132\037.mxaccess_worker.v1.WorkerFaultH\000B\006\n\004" +
|
||||||
"body\"Z\n\014GatewayHello\022\"\n\032supported_protoc" +
|
"body\"s\n\014GatewayHello\022\"\n\032supported_protoc" +
|
||||||
"ol_version\030\001 \001(\r\022\r\n\005nonce\030\002 \001(\t\022\027\n\017gatew" +
|
"ol_version\030\001 \001(\r\022\r\n\005nonce\030\002 \001(\t\022\027\n\017gatew" +
|
||||||
"ay_version\030\003 \001(\t\"i\n\013WorkerHello\022\030\n\020proto" +
|
"ay_version\030\003 \001(\t\022\027\n\017max_frame_bytes\030\004 \001(" +
|
||||||
"col_version\030\001 \001(\r\022\r\n\005nonce\030\002 \001(\t\022\031\n\021work" +
|
"\r\"i\n\013WorkerHello\022\030\n\020protocol_version\030\001 \001" +
|
||||||
"er_process_id\030\003 \001(\005\022\026\n\016worker_version\030\004 " +
|
"(\r\022\r\n\005nonce\030\002 \001(\t\022\031\n\021worker_process_id\030\003" +
|
||||||
"\001(\t\"\216\001\n\013WorkerReady\022\031\n\021worker_process_id" +
|
" \001(\005\022\026\n\016worker_version\030\004 \001(\t\"\216\001\n\013WorkerR" +
|
||||||
"\030\001 \001(\005\022\027\n\017mxaccess_progid\030\002 \001(\t\022\026\n\016mxacc" +
|
"eady\022\031\n\021worker_process_id\030\001 \001(\005\022\027\n\017mxacc" +
|
||||||
"ess_clsid\030\003 \001(\t\0223\n\017ready_timestamp\030\004 \001(\013" +
|
"ess_progid\030\002 \001(\t\022\026\n\016mxaccess_clsid\030\003 \001(\t" +
|
||||||
"2\032.google.protobuf.Timestamp\"w\n\rWorkerCo" +
|
"\0223\n\017ready_timestamp\030\004 \001(\0132\032.google.proto" +
|
||||||
"mmand\022/\n\007command\030\001 \001(\0132\036.mxaccess_gatewa" +
|
"buf.Timestamp\"w\n\rWorkerCommand\022/\n\007comman" +
|
||||||
"y.v1.MxCommand\0225\n\021enqueue_timestamp\030\002 \001(" +
|
"d\030\001 \001(\0132\036.mxaccess_gateway.v1.MxCommand\022" +
|
||||||
"\0132\032.google.protobuf.Timestamp\"\201\001\n\022Worker" +
|
"5\n\021enqueue_timestamp\030\002 \001(\0132\032.google.prot" +
|
||||||
"CommandReply\0222\n\005reply\030\001 \001(\0132#.mxaccess_g" +
|
"obuf.Timestamp\"\201\001\n\022WorkerCommandReply\0222\n" +
|
||||||
"ateway.v1.MxCommandReply\0227\n\023completed_ti" +
|
"\005reply\030\001 \001(\0132#.mxaccess_gateway.v1.MxCom" +
|
||||||
"mestamp\030\002 \001(\0132\032.google.protobuf.Timestam" +
|
"mandReply\0227\n\023completed_timestamp\030\002 \001(\0132\032" +
|
||||||
"p\"\036\n\014WorkerCancel\022\016\n\006reason\030\001 \001(\t\"Q\n\016Wor" +
|
".google.protobuf.Timestamp\"\036\n\014WorkerCanc" +
|
||||||
"kerShutdown\022/\n\014grace_period\030\001 \001(\0132\031.goog" +
|
"el\022\016\n\006reason\030\001 \001(\t\"Q\n\016WorkerShutdown\022/\n\014" +
|
||||||
"le.protobuf.Duration\022\016\n\006reason\030\002 \001(\t\"H\n\021" +
|
"grace_period\030\001 \001(\0132\031.google.protobuf.Dur" +
|
||||||
"WorkerShutdownAck\0223\n\006status\030\001 \001(\0132#.mxac" +
|
"ation\022\016\n\006reason\030\002 \001(\t\"H\n\021WorkerShutdownA" +
|
||||||
"cess_gateway.v1.ProtocolStatus\":\n\013Worker" +
|
"ck\0223\n\006status\030\001 \001(\0132#.mxaccess_gateway.v1" +
|
||||||
"Event\022+\n\005event\030\001 \001(\0132\034.mxaccess_gateway." +
|
".ProtocolStatus\":\n\013WorkerEvent\022+\n\005event\030" +
|
||||||
"v1.MxEvent\"\245\002\n\017WorkerHeartbeat\022\031\n\021worker" +
|
"\001 \001(\0132\034.mxaccess_gateway.v1.MxEvent\"\245\002\n\017" +
|
||||||
"_process_id\030\001 \001(\005\022.\n\005state\030\002 \001(\0162\037.mxacc" +
|
"WorkerHeartbeat\022\031\n\021worker_process_id\030\001 \001" +
|
||||||
"ess_worker.v1.WorkerState\022?\n\033last_sta_ac" +
|
"(\005\022.\n\005state\030\002 \001(\0162\037.mxaccess_worker.v1.W" +
|
||||||
"tivity_timestamp\030\003 \001(\0132\032.google.protobuf" +
|
"orkerState\022?\n\033last_sta_activity_timestam" +
|
||||||
".Timestamp\022\035\n\025pending_command_count\030\004 \001(" +
|
"p\030\003 \001(\0132\032.google.protobuf.Timestamp\022\035\n\025p" +
|
||||||
"\r\022\"\n\032outbound_event_queue_depth\030\005 \001(\r\022\033\n" +
|
"ending_command_count\030\004 \001(\r\022\"\n\032outbound_e" +
|
||||||
"\023last_event_sequence\030\006 \001(\004\022&\n\036current_co" +
|
"vent_queue_depth\030\005 \001(\r\022\033\n\023last_event_seq" +
|
||||||
"mmand_correlation_id\030\007 \001(\t\"\364\001\n\013WorkerFau" +
|
"uence\030\006 \001(\004\022&\n\036current_command_correlati" +
|
||||||
"lt\0229\n\010category\030\001 \001(\0162\'.mxaccess_worker.v" +
|
"on_id\030\007 \001(\t\"\364\001\n\013WorkerFault\0229\n\010category\030" +
|
||||||
"1.WorkerFaultCategory\022\026\n\016command_method\030" +
|
"\001 \001(\0162\'.mxaccess_worker.v1.WorkerFaultCa" +
|
||||||
"\002 \001(\t\022\024\n\007hresult\030\003 \001(\005H\000\210\001\001\022\026\n\016exception" +
|
"tegory\022\026\n\016command_method\030\002 \001(\t\022\024\n\007hresul" +
|
||||||
"_type\030\004 \001(\t\022\032\n\022diagnostic_message\030\005 \001(\t\022" +
|
"t\030\003 \001(\005H\000\210\001\001\022\026\n\016exception_type\030\004 \001(\t\022\032\n\022" +
|
||||||
"<\n\017protocol_status\030\006 \001(\0132#.mxaccess_gate" +
|
"diagnostic_message\030\005 \001(\t\022<\n\017protocol_sta" +
|
||||||
"way.v1.ProtocolStatusB\n\n\010_hresult*\227\002\n\013Wo" +
|
"tus\030\006 \001(\0132#.mxaccess_gateway.v1.Protocol" +
|
||||||
"rkerState\022\034\n\030WORKER_STATE_UNSPECIFIED\020\000\022" +
|
"StatusB\n\n\010_hresult*\227\002\n\013WorkerState\022\034\n\030WO" +
|
||||||
"\031\n\025WORKER_STATE_STARTING\020\001\022\034\n\030WORKER_STA" +
|
"RKER_STATE_UNSPECIFIED\020\000\022\031\n\025WORKER_STATE" +
|
||||||
"TE_HANDSHAKING\020\002\022!\n\035WORKER_STATE_INITIAL" +
|
"_STARTING\020\001\022\034\n\030WORKER_STATE_HANDSHAKING\020" +
|
||||||
"IZING_STA\020\003\022\026\n\022WORKER_STATE_READY\020\004\022\"\n\036W" +
|
"\002\022!\n\035WORKER_STATE_INITIALIZING_STA\020\003\022\026\n\022" +
|
||||||
"ORKER_STATE_EXECUTING_COMMAND\020\005\022\036\n\032WORKE" +
|
"WORKER_STATE_READY\020\004\022\"\n\036WORKER_STATE_EXE" +
|
||||||
"R_STATE_SHUTTING_DOWN\020\006\022\030\n\024WORKER_STATE_" +
|
"CUTING_COMMAND\020\005\022\036\n\032WORKER_STATE_SHUTTIN" +
|
||||||
"STOPPED\020\007\022\030\n\024WORKER_STATE_FAULTED\020\010*\307\004\n\023" +
|
"G_DOWN\020\006\022\030\n\024WORKER_STATE_STOPPED\020\007\022\030\n\024WO" +
|
||||||
"WorkerFaultCategory\022%\n!WORKER_FAULT_CATE" +
|
"RKER_STATE_FAULTED\020\010*\307\004\n\023WorkerFaultCate" +
|
||||||
"GORY_UNSPECIFIED\020\000\022+\n\'WORKER_FAULT_CATEG" +
|
"gory\022%\n!WORKER_FAULT_CATEGORY_UNSPECIFIE" +
|
||||||
"ORY_INVALID_ARGUMENTS\020\001\0227\n3WORKER_FAULT_" +
|
"D\020\000\022+\n\'WORKER_FAULT_CATEGORY_INVALID_ARG" +
|
||||||
"CATEGORY_GATEWAY_AUTHENTICATION_FAILED\020\002" +
|
"UMENTS\020\001\0227\n3WORKER_FAULT_CATEGORY_GATEWA" +
|
||||||
"\022+\n\'WORKER_FAULT_CATEGORY_PROTOCOL_MISMA" +
|
"Y_AUTHENTICATION_FAILED\020\002\022+\n\'WORKER_FAUL" +
|
||||||
"TCH\020\003\022,\n(WORKER_FAULT_CATEGORY_PROTOCOL_" +
|
"T_CATEGORY_PROTOCOL_MISMATCH\020\003\022,\n(WORKER" +
|
||||||
"VIOLATION\020\004\022+\n\'WORKER_FAULT_CATEGORY_PIP" +
|
"_FAULT_CATEGORY_PROTOCOL_VIOLATION\020\004\022+\n\'" +
|
||||||
"E_DISCONNECTED\020\005\0222\n.WORKER_FAULT_CATEGOR" +
|
"WORKER_FAULT_CATEGORY_PIPE_DISCONNECTED\020" +
|
||||||
"Y_MXACCESS_CREATION_FAILED\020\006\0221\n-WORKER_F" +
|
"\005\0222\n.WORKER_FAULT_CATEGORY_MXACCESS_CREA" +
|
||||||
"AULT_CATEGORY_MXACCESS_COMMAND_FAILED\020\007\022" +
|
"TION_FAILED\020\006\0221\n-WORKER_FAULT_CATEGORY_M" +
|
||||||
":\n6WORKER_FAULT_CATEGORY_MXACCESS_EVENT_" +
|
"XACCESS_COMMAND_FAILED\020\007\022:\n6WORKER_FAULT" +
|
||||||
"CONVERSION_FAILED\020\010\022\"\n\036WORKER_FAULT_CATE" +
|
"_CATEGORY_MXACCESS_EVENT_CONVERSION_FAIL" +
|
||||||
"GORY_STA_HUNG\020\t\022(\n$WORKER_FAULT_CATEGORY" +
|
"ED\020\010\022\"\n\036WORKER_FAULT_CATEGORY_STA_HUNG\020\t" +
|
||||||
"_QUEUE_OVERFLOW\020\n\022*\n&WORKER_FAULT_CATEGO" +
|
"\022(\n$WORKER_FAULT_CATEGORY_QUEUE_OVERFLOW" +
|
||||||
"RY_SHUTDOWN_TIMEOUT\020\013B&\252\002#ZB.MOM.WW.MxGa" +
|
"\020\n\022*\n&WORKER_FAULT_CATEGORY_SHUTDOWN_TIM" +
|
||||||
"teway.Contracts.Protob\006proto3"
|
"EOUT\020\013B&\252\002#ZB.MOM.WW.MxGateway.Contracts" +
|
||||||
|
".Protob\006proto3"
|
||||||
};
|
};
|
||||||
descriptor = com.google.protobuf.Descriptors.FileDescriptor
|
descriptor = com.google.protobuf.Descriptors.FileDescriptor
|
||||||
.internalBuildGeneratedFileFrom(descriptorData,
|
.internalBuildGeneratedFileFrom(descriptorData,
|
||||||
@@ -12629,7 +12742,7 @@ public final class MxaccessWorker extends com.google.protobuf.GeneratedFile {
|
|||||||
internal_static_mxaccess_worker_v1_GatewayHello_fieldAccessorTable = new
|
internal_static_mxaccess_worker_v1_GatewayHello_fieldAccessorTable = new
|
||||||
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
||||||
internal_static_mxaccess_worker_v1_GatewayHello_descriptor,
|
internal_static_mxaccess_worker_v1_GatewayHello_descriptor,
|
||||||
new java.lang.String[] { "SupportedProtocolVersion", "Nonce", "GatewayVersion", });
|
new java.lang.String[] { "SupportedProtocolVersion", "Nonce", "GatewayVersion", "MaxFrameBytes", });
|
||||||
internal_static_mxaccess_worker_v1_WorkerHello_descriptor =
|
internal_static_mxaccess_worker_v1_WorkerHello_descriptor =
|
||||||
getDescriptor().getMessageType(2);
|
getDescriptor().getMessageType(2);
|
||||||
internal_static_mxaccess_worker_v1_WorkerHello_fieldAccessorTable = new
|
internal_static_mxaccess_worker_v1_WorkerHello_fieldAccessorTable = new
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
"""Shared pytest fixtures for the client test suite.
|
||||||
|
|
||||||
|
pytest-asyncio 1.x no longer installs a current event loop for *synchronous*
|
||||||
|
tests, and it clears the loop after each ``async`` test. Some synchronous tests
|
||||||
|
build a ``grpc.aio.Channel`` (via ``create_channel``), which calls
|
||||||
|
``asyncio.get_event_loop()`` and therefore raises
|
||||||
|
``RuntimeError: There is no current event loop`` on Python 3.12 when a prior
|
||||||
|
async test has left the policy with no current loop. Ensure every test starts
|
||||||
|
with a usable current loop so channel construction in a sync test is
|
||||||
|
order-independent.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from collections.abc import Iterator
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _ensure_current_event_loop() -> Iterator[None]:
|
||||||
|
try:
|
||||||
|
asyncio.get_event_loop()
|
||||||
|
except RuntimeError:
|
||||||
|
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||||
|
yield
|
||||||
@@ -42,6 +42,12 @@ message GatewayHello {
|
|||||||
uint32 supported_protocol_version = 1;
|
uint32 supported_protocol_version = 1;
|
||||||
string nonce = 2;
|
string nonce = 2;
|
||||||
string gateway_version = 3;
|
string gateway_version = 3;
|
||||||
|
// Maximum worker-frame payload size, in bytes, negotiated by the gateway from its
|
||||||
|
// configured pipe limit. The worker adopts this as its frame-protocol MaxMessageBytes
|
||||||
|
// instead of a hard-coded default; 0 (an older gateway that never set the field) means
|
||||||
|
// "use the worker's built-in default". Sits above the public gRPC cap by an
|
||||||
|
// envelope-overhead margin so an accepted gRPC payload always fits one worker frame.
|
||||||
|
uint32 max_frame_bytes = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message WorkerHello {
|
message WorkerHello {
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ try {
|
|||||||
$failures.Add('Contracts project failed to build during codegen check.')
|
$failures.Add('Contracts project failed to build during codegen check.')
|
||||||
}
|
}
|
||||||
|
|
||||||
$diff = (& git -C $repoRoot status --porcelain -- 'src/ZB.MOM.WW.MxGateway.Contracts/Generated').Trim()
|
# Pipe through Out-String so a clean tree (git emits nothing -> $null) does not throw
|
||||||
|
# "cannot call a method on a null-valued expression"; Out-String yields '' for no output.
|
||||||
|
$diff = (& git -C $repoRoot status --porcelain -- 'src/ZB.MOM.WW.MxGateway.Contracts/Generated' | Out-String).Trim()
|
||||||
if (-not [string]::IsNullOrEmpty($diff)) {
|
if (-not [string]::IsNullOrEmpty($diff)) {
|
||||||
Write-Host $diff
|
Write-Host $diff
|
||||||
$failures.Add('Contracts/Generated differs from a fresh regeneration. Run `dotnet build` on the contracts project and commit Generated/ (required for the net48 worker build).')
|
$failures.Add('Contracts/Generated differs from a fresh regeneration. Run `dotnet build` on the contracts project and commit Generated/ (required for the net48 worker build).')
|
||||||
|
|||||||
@@ -13,7 +13,19 @@ namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Workers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class OrphanWorkerTerminatorTests
|
public sealed class OrphanWorkerTerminatorTests
|
||||||
{
|
{
|
||||||
private const string WorkerExecutablePath = @"C:\app\src\ZB.MOM.WW.MxGateway.Worker\bin\x86\Release\ZB.MOM.WW.MxGateway.Worker.exe";
|
// An OS-appropriate, already-normalized absolute path so Path.GetFullPath (used by the
|
||||||
|
// terminator to resolve the configured path) is idempotent and the exact-match compare holds
|
||||||
|
// on every platform. A hard-coded Windows literal (C:\...) is not rooted on Linux, so
|
||||||
|
// Path.GetFullPath there prepends the cwd and the match fails — the terminator is a
|
||||||
|
// Windows-only feature in production, but its matching logic must still be testable on the
|
||||||
|
// Linux CI runner.
|
||||||
|
private static readonly string WorkerExecutablePath = Path.GetFullPath(Path.Combine(
|
||||||
|
Path.GetTempPath(),
|
||||||
|
"MxGatewayOrphanTests",
|
||||||
|
"bin",
|
||||||
|
"x86",
|
||||||
|
"Release",
|
||||||
|
"ZB.MOM.WW.MxGateway.Worker.exe"));
|
||||||
|
|
||||||
/// <summary>Verifies that orphan worker processes matching the configured executable path are killed.</summary>
|
/// <summary>Verifies that orphan worker processes matching the configured executable path are killed.</summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -59,7 +71,10 @@ public sealed class OrphanWorkerTerminatorTests
|
|||||||
// not our worker and must be left alone.
|
// not our worker and must be left alone.
|
||||||
FakeProcessInspector inspector = new(
|
FakeProcessInspector inspector = new(
|
||||||
[
|
[
|
||||||
new RunningProcessInfo(301, @"C:\other\place\ZB.MOM.WW.MxGateway.Worker.exe"),
|
new RunningProcessInfo(301, Path.GetFullPath(Path.Combine(
|
||||||
|
Path.GetTempPath(),
|
||||||
|
"MxGatewayOrphanTests-other",
|
||||||
|
"ZB.MOM.WW.MxGateway.Worker.exe"))),
|
||||||
]);
|
]);
|
||||||
OrphanWorkerTerminator terminator = CreateTerminator(inspector);
|
OrphanWorkerTerminator terminator = CreateTerminator(inspector);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user