-
Notifications
You must be signed in to change notification settings - Fork 0
test: boot & OTA integration test (dummy server + QEMU harness) + release gate #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
35bb48a
docs(test): spec for boot & OTA integration test harness
peterus c823a84
docs(test): fold pipeline rework into boot/OTA test spec
peterus c737094
docs(test): design Target interface CM4-ready; add bench mechanics
peterus 6202803
docs(test): finalize CM4 flash-backend cost tiers + eMMC/Lite fidelity
peterus e463069
docs(test): implementation plan for boot & OTA integration test
peterus 54a37eb
test(ota-it): scaffold + dummy OTA server + image/seed helpers (unit-…
peterus 956faee
test(ota-it): Target interface, L0a UUID lint, compute-version (unit-…
peterus 5f66c18
test(ota-it): L0b build assertion + T1/T2 qemu tests (+T3 skipped fol…
peterus a03136f
ci: L0a on PRs, boot-critical PR test, reusable gate, one-button release
peterus aeacdb9
test(ota-it): install grub2-common in CI + tolerate missing grub-editenv
peterus 1de0a98
ci: shellcheck only the new scripts, not the whole scripts/ dir
peterus c33a6fb
fix(ci): L0a scans only git-tracked files, not kas-fetched layers
peterus 6ea2f66
ci: run T2 (cross-build OTA) on boot-critical PRs too
peterus fcf7579
ci: gate boot-ota-pr on non-draft PRs
peterus ed616b5
Address Copilot review (round 1)
peterus 4599cce
Address Copilot review (round 2)
peterus bd4c544
Address Copilot review (round 3)
peterus b2a0f2c
Address Copilot review (round 4)
peterus 01f81b7
Address Copilot review (round 5)
peterus c4ae9c4
Address Copilot review (round 6)
peterus 60eff50
fix(ota-it): banner regex captures only the tag charset, not trailing…
peterus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| name: Compute release version | ||
| description: > | ||
| Compute the next OE5XRX linux-image release version (YYYY.MM.DD-HH[a-z], UTC | ||
| hour, letter suffix on collision) from existing git tags. Mirrors the FW | ||
| one-button-release pattern. | ||
|
|
||
| outputs: | ||
| version: | ||
| description: The computed release version. | ||
| value: ${{ steps.compute.outputs.version }} | ||
|
|
||
| runs: | ||
| using: composite | ||
| steps: | ||
| - name: Fetch tags | ||
| shell: bash | ||
| run: git fetch --tags --quiet | ||
|
|
||
| - name: Compute | ||
| id: compute | ||
| shell: bash | ||
| run: | | ||
| version="$(scripts/compute-release-version.sh)" | ||
| echo "Computed release version: ${version}" | ||
| echo "version=${version}" >> "$GITHUB_OUTPUT" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| name: Boot/OTA (boot-critical PRs) | ||
|
|
||
| # Shift-left: on PRs that touch boot/OTA-critical paths, build the x86 image and | ||
| # run BOTH the boot smoke (T1) and the cross-build OTA cycle (T2) — slot A = the | ||
| # latest published release, slot B = this PR's build. The release gate runs the | ||
| # same test again as the publish barrier. If no release exists yet (bootstrap), | ||
| # T2 skips gracefully and only T1 runs. | ||
|
|
||
| # Draft PRs do NOT trigger the (expensive) Hetzner build + QEMU test — so | ||
| # Copilot-loop / WIP pushes on a draft don't re-run everything. Mark the PR | ||
| # ready for review to run it (that also fires `ready_for_review`). | ||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, ready_for_review] | ||
| paths: | ||
| - 'meta-oe5xrx-remotestation/wic/**' | ||
| - 'meta-oe5xrx-remotestation/recipes-bsp/**' | ||
| - 'meta-oe5xrx-remotestation/recipes-core/ab-layout/**' | ||
| - 'meta-oe5xrx-remotestation/recipes-core/images/**' | ||
| - 'meta-oe5xrx-remotestation/recipes-core/station-agent/**' | ||
| - 'tests/ota-integration/**' | ||
| - '.github/workflows/boot-ota-test.yml' | ||
| - '.github/workflows/boot-ota-pr.yml' | ||
|
|
||
| permissions: | ||
| contents: read | ||
| actions: read # download-artifact from the build job in the same run | ||
|
|
||
| concurrency: | ||
| group: boot-ota-pr-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| resolve-slot-a: | ||
| name: Resolve slot A (last release) | ||
| if: ${{ !github.event.pull_request.draft }} | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| tag: ${{ steps.last.outputs.tag }} | ||
| steps: | ||
| - name: Latest published release | ||
| id: last | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| REPO: ${{ github.repository }} | ||
| run: | | ||
| set -euo pipefail | ||
| tag=$(gh release list -R "$REPO" -L 1 --exclude-drafts --exclude-pre-releases --json tagName -q '.[0].tagName' || true) | ||
| echo "Slot A (last release): ${tag:-<none — T2 will skip>}" | ||
| echo "tag=$tag" >> "$GITHUB_OUTPUT" | ||
|
|
||
| build-x64: | ||
| name: Build qemux86-64 (dev) | ||
| if: ${{ !github.event.pull_request.draft }} | ||
| uses: ./.github/workflows/build.yml | ||
| with: | ||
| machine: qemux86-64 | ||
| release_tag: "" | ||
| secrets: inherit | ||
|
|
||
| test: | ||
| name: Boot + cross-build OTA (T1+T2) | ||
| if: ${{ !github.event.pull_request.draft }} | ||
| needs: [build-x64, resolve-slot-a] | ||
| uses: ./.github/workflows/boot-ota-test.yml | ||
| with: | ||
| machine: qemux86-64 | ||
| new_artifact: yocto-image-qemux86-64 | ||
| expected_tag: dev | ||
| last_release_tag: ${{ needs.resolve-slot-a.outputs.tag }} | ||
| secrets: inherit | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| name: Boot/OTA Feasibility Spike | ||
|
|
||
| # One-off decision aid: does a GH-hosted ubuntu-latest runner boot the image | ||
| # under TCG (no KVM) in acceptable wall-clock and disk? Boots the latest (or a | ||
| # given) release via the T1 harness and reports timing + RAM + disk headroom. | ||
| # If this is too slow / disk-tight, flip boot-ota-test.yml's runner_label to a | ||
| # self-hosted Hetzner label. | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| release_tag: | ||
| description: Release to boot (empty → latest). | ||
| type: string | ||
| default: "" | ||
|
|
||
| permissions: | ||
| contents: read # gh release download | ||
|
|
||
| jobs: | ||
| spike: | ||
| name: TCG boot timing | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 45 | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
||
| - name: Host capacity (before) | ||
| run: | | ||
| echo "== nproc =="; nproc | ||
| echo "== free -m =="; free -m | ||
| echo "== df -h / =="; df -h / | ||
| echo "== /dev/kvm present? =="; ls -l /dev/kvm || echo "no KVM (expected → TCG)" | ||
|
|
||
| - name: Install QEMU + OVMF + deps | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y --no-install-recommends qemu-system-x86 ovmf grub-common grub2-common python3-pip | ||
| sudo python3 -m pip install --break-system-packages -r tests/ota-integration/requirements.txt | ||
|
|
||
| - name: Resolve release tag | ||
| id: tag | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| REPO: ${{ github.repository }} | ||
| REQ_TAG: ${{ inputs.release_tag }} | ||
| run: | | ||
| set -euo pipefail | ||
| tag="$REQ_TAG" | ||
| if [ -z "$tag" ]; then | ||
| tag=$(gh release list -R "$REPO" -L 1 --exclude-drafts --exclude-pre-releases --json tagName -q '.[0].tagName') | ||
| fi | ||
| echo "tag=$tag" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Download release wic | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| REPO: ${{ github.repository }} | ||
| TAG: ${{ steps.tag.outputs.tag }} | ||
| run: | | ||
| set -euo pipefail | ||
| gh release download "$TAG" -R "$REPO" \ | ||
| --pattern "*qemux86-64*.wic.bz2" \ | ||
| --pattern "*qemux86-64*.wic.xz" \ | ||
| --pattern "*qemux86-64*.wic.gz" \ | ||
| --pattern "*qemux86-64*.wic" \ | ||
| -D spike | ||
|
|
||
| - name: Timed TCG boot (T1) | ||
| env: | ||
| OTA_IT_WIC: spike | ||
| OTA_IT_EXPECTED_TAG: ${{ steps.tag.outputs.tag }} | ||
| run: | | ||
| set -euo pipefail | ||
| shopt -s nullglob | ||
| cands=(spike/*.wic.bz2 spike/*.wic.xz spike/*.wic.gz spike/*.wic) | ||
| OTA_IT_WIC="${cands[0]:-}" | ||
| [ -n "$OTA_IT_WIC" ] || { echo "no wic asset downloaded" >&2; exit 1; } | ||
| export OTA_IT_WIC | ||
| start=$(date +%s) | ||
| sudo -E env "PATH=$PATH" \ | ||
| python3 -m pytest tests/ota-integration/test_boot.py -m qemu -v | ||
| end=$(date +%s) | ||
| echo "T1 boot wall-clock: $((end - start))s" | ||
|
|
||
| - name: Host capacity (after) | ||
| run: | | ||
| echo "== free -m =="; free -m | ||
| echo "== df -h / =="; df -h / |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| name: Boot & OTA Test | ||
|
|
||
| # Reusable boot + cross-build OTA integration test. Boots the built image in | ||
| # QEMU (TCG), drives the real station-agent against a dummy OTA server, and | ||
| # asserts a serial banner+login AND an agent check-in/commit. Called by the | ||
| # boot-critical PR workflow (T1) and by the release gate (T1+T2). | ||
| # | ||
| # Runner is a parameter: default GH-hosted ubuntu-latest (TCG). If a feasibility | ||
| # spike shows GH-hosted is insufficient, pass a self-hosted Hetzner label. | ||
|
|
||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| machine: | ||
| type: string | ||
| default: qemux86-64 | ||
| runner_label: | ||
| type: string | ||
| default: ubuntu-latest | ||
| new_artifact: | ||
| description: Artifact name of the build-under-test wic (same run). | ||
| type: string | ||
| required: true | ||
| expected_tag: | ||
| description: Version stamped into the build-under-test (banner + os-release). | ||
| type: string | ||
| required: true | ||
| last_release_tag: | ||
| description: Previous release tag = slot A for T2 (empty → T1 only). | ||
| type: string | ||
| default: "" | ||
|
|
||
| jobs: | ||
| test: | ||
| name: Boot & OTA (${{ inputs.machine }}) | ||
| runs-on: ${{ inputs.runner_label }} | ||
| timeout-minutes: 60 | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
|
|
||
| - name: Install QEMU + OVMF + deps | ||
| run: | | ||
| sudo apt-get update | ||
| sudo apt-get install -y --no-install-recommends qemu-system-x86 ovmf grub-common grub2-common python3-pip | ||
| sudo python3 -m pip install --break-system-packages -r tests/ota-integration/requirements.txt | ||
|
|
||
| - name: Download build-under-test artifact | ||
| uses: actions/download-artifact@v8 | ||
| with: | ||
| name: ${{ inputs.new_artifact }} | ||
| path: it-artifacts | ||
|
|
||
| - name: Locate build-under-test wic | ||
| id: wic | ||
| run: | | ||
| set -euo pipefail | ||
| shopt -s nullglob | ||
| # Multiple patterns via an array so a non-matching glob can't fail the | ||
| # step under `set -e` when another pattern does match. | ||
| cands=(it-artifacts/*.wic.bz2 it-artifacts/*.wic.xz it-artifacts/*.wic.gz it-artifacts/*.rootfs.wic it-artifacts/*.wic) | ||
| wic="${cands[0]:-}" | ||
| [ -n "$wic" ] || { echo "no wic artifact found" >&2; exit 1; } | ||
| echo "wic=$wic" >> "$GITHUB_OUTPUT" | ||
|
peterus marked this conversation as resolved.
|
||
|
|
||
| - name: Fetch last release (slot A) | ||
| id: slota | ||
| if: inputs.last_release_tag != '' | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| LAST_RELEASE_TAG: ${{ inputs.last_release_tag }} | ||
| MACHINE: ${{ inputs.machine }} | ||
| REPO: ${{ github.repository }} | ||
| run: | | ||
| set -euo pipefail | ||
| # Accept the same compression set release.yml may publish, in the same | ||
| # extension order; sidecars (.sha256/.bundle) don't match these globs. | ||
| gh release download "$LAST_RELEASE_TAG" -R "$REPO" \ | ||
| --pattern "*${MACHINE}*.wic.bz2" \ | ||
| --pattern "*${MACHINE}*.wic.xz" \ | ||
| --pattern "*${MACHINE}*.wic.gz" \ | ||
| --pattern "*${MACHINE}*.wic" \ | ||
| -D slota | ||
| shopt -s nullglob | ||
| cands=(slota/*.wic.bz2 slota/*.wic.xz slota/*.wic.gz slota/*.wic) | ||
| wic="${cands[0]:-}" | ||
| # Fail fast: last_release_tag was set, so a missing slot-A asset must | ||
| # NOT silently empty the output and make T2 skip (a false-green gate). | ||
| [ -n "$wic" ] || { echo "last_release_tag='$LAST_RELEASE_TAG' set but no slot-A wic asset was downloaded" >&2; exit 1; } | ||
| echo "wic=$wic" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Run boot + OTA integration tests | ||
| env: | ||
| OTA_IT_WIC: ${{ steps.wic.outputs.wic }} | ||
| OTA_IT_LAST_RELEASE_WIC: ${{ steps.slota.outputs.wic }} | ||
| OTA_IT_EXPECTED_TAG: ${{ inputs.expected_tag }} | ||
| run: | | ||
| sudo -E env "PATH=$PATH" \ | ||
| python3 -m pytest tests/ota-integration -m qemu -v | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.