| Status: Draft | Authors: Puneetha Ramachandra, Ludovic Henry |
This document outlines the phased design for integrating GitHub-based kernel builds with the RISE Board Farm (targeting the Zhihe A210 boards hosted at OSU-OSL). It details the transition from the Phase 1 (Labgrid Pilot) to Phase 2 (OpenStack/OpenBMC Production).
To ensure a smooth transition, the boot requirements (specifically UEFI) are aligned across both phases.
+--------------------------------------------------------------------------+
| Phase 1: Labgrid Pilot (Targeted Testing) |
| GitHub Actions -> Labgrid (Serial/PDU) -> U-Boot (UEFI) -> TFTP |
| -> SSH (Test Execution) |
+--------------------------------------------------------------------------+
|
v (Transition: OpenBMC + Ironic)
+--------------------------------------------------------------------------+
| Phase 2: OpenStack Production (Cloud Bare-Metal) |
| GitHub Actions -> Glance -> Ironic -> External BMC (Pi) -> UEFI PXE |
| -> IPA -> eMMC -> SSH (User Access) |
+--------------------------------------------------------------------------+
In Phase 1, we use a lightweight setup where GitHub Actions orchestrates the workflow and Labgrid manages the physical board interaction.
There are two kinds of runners necessary for that setup:
ubuntu-latest, ubuntu-24.04, ubuntu-24.04-arm, etc.). They are hosted in a datacenter somewhere we do not have direct access to, and they are the fastest machine we have access to. The RISE RISC-V Runners are in this category as we can’t assume where they are hostedboard-farm-controller runner hosted at OSU-OSL. We are provisioning it to run on the same machine as the TFTP/DHCP and Labgrid Controller. This runner can’t be doing heavy compute work (build, test) and it should be used exclusively to download/upload artifacts and interact with Labgrid. We may (TBD) want to support multiple runners on this single machine to allow for multiple GHA workflows to use the cluster at the same time; Concurrency is controlled with concurrency| Steps | Runner | Description | |
|---|---|---|---|
| 1 | Build | ubuntu-24.04 or ubuntu-24.04-riscv |
Compile the kernel/FIT image on a hosted runner (e.g. ubuntu-24.04, ubuntu-24.04-arm, ubuntu-24.04-riscv) and upload the image to GHA artifacts (actions/upload-artifacts) |
| 2 | Stage | [self-hosted, board-farm-controller]All steps running sequentially in a single GHA job; Concurrency controlled by concurrency |
Download the kernel/FIT image (actions/download-artifacts) and store it locally; If that runner is running on the TFTP/DHCP server, then it can simply store it on the local filesystem |
| 3 | Acquire | The runner uses pytest-labgrid to lock the target A210 board; The runner must have network access to the Labgrid Controller, or even be hosted all on the same machine |
|
| 4 | Boot (serial) | Labgrid: - Power-cycles the board via the PDU. - Intercepts the U-Boot prompt over serial. - Dynamically configures U-Boot env vars (IP, TFTP server). - Executes tftpboot and boots the image. |
|
| 5 | Test (SSH) | - The runner waits for the board's SSH daemon to become active. - The test script connects to the board via SSH to run the test suite. - Fallback: If SSH fails to connect, the runner dumps the serial console log to diagnose |
|
| 6 | Release: | The runner releases the board lock. |
A sample GHA workflow would look like the following:
name: Kernel testing
on:
workflow_dispatch:
inputs:
machine:
description: Target machine to test on
required: true
type: string
jobs:
build:
name: Build Kernel
runs-on: ubuntu-24.04
steps:
- name: build kernel
run: ... # compile the kernel/FIT image
- name: upload artifacts
uses: actions/upload-artifact@v4
with:
name: kernel-image
path: ... # path to the built kernel/FIT image
test:
name: Boot & Test Kernel
needs: build
runs-on: [self-hosted, board-farm-controller]
# Concurrency is scoped per-machine so multiple workflows can't fight over the same board.
concurrency:
group: board-farm-$
cancel-in-progress: false
steps:
- name: download artifacts
uses: actions/download-artifact@v4
with:
name: kernel-image
path: /path/to/tftp-root
- name: acquire
run: |
# acquire labgrid machine $
- name: dhcp-setup
run: |
# does the right file mangling with the artifacts for the DHCP/TFTP server to pick up the files
- name: boot
run: |
# do power cycling of machine $, read console until it's booted, wait up to XX minutes
- name: test
run: |
# connect via SSH to machine $
- name: release
run: |
# release labgrid machine $
In Phase 2, the boards are managed as standard bare-metal compute instances within an OpenStack cloud.
To ensure boards can transition from Phase 1 to Phase 2 without hardware or firmware modifications:
CONFIG_EFI_LOADER=y).grubriscv64.efi) over the network.riscv64 IPA ramdisk image (using diskimage-builder or Buildroot) to enable Ironic to flash the eMMC on the RISC-V target.This section describes the end-to-end workflows for developers running tests on the A210 hardware during Phase 1, covering both OS-level and application-level testing.
This workflow is for developers modifying the kernel or OS image, requiring a fresh boot from the built artifacts on every run. This flow is highly board-specific.
fitImage.fitImage to /srv/tftp/a210/ and locks an available board via Labgrid.tftpboot of the new image.tests/test_kernel.py)import pytest
def test_boot_and_uname(target):
"""Verify the board boots the new kernel and reports the correct arch."""
ssh = target.get_driver("SSHDriver")
stdout, stderr, exit_code = ssh.run("uname -a")
assert exit_code == 0
assert "riscv64" in "".join(stdout)
lab_env.yaml)targets:
a210-board-01:
resources:
RemoteSerialPort:
speed: 115200
host: "192.168.1.50"
port: 2001
PDUPort:
host: "192.168.1.60"
port: 3
NetworkService:
address: "192.168.1.100"
username: "root"
drivers:
SerialDriver:
bindings:
port: RemoteSerialPort
ShellDriver:
bindings:
io: SerialDriver
prompt: "root@a210:~#"
login_prompt: "a210 login:"
username: "root"
SSHDriver:
bindings:
target: NetworkService
.github/workflows/a210_kernel_test.yml)name: A210 Kernel Test
on:
workflow_dispatch:
issue_comment:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Kernel
run: |
make a210_defconfig
make -j$(nproc) fitImage
- name: Upload FIT Image
uses: actions/upload-artifact@v4
with:
name: fitImage
path: arch/riscv/boot/fitImage
test-hw:
if: |
github.event_name == 'workflow_dispatch' ||
(github.event.comment && startsWith(github.event.comment.body, '/run-kernel-tests'))
needs: build
runs-on: [self-hosted, osu-osl-lab]
steps:
- name: Download FIT Image
uses: actions/download-artifact@v4
with:
name: fitImage
path: /srv/tftp/a210/
- name: Run Tests
run: |
pytest --lg-env=lab_env.yaml --lg-role=a210 --junitxml=results.xml tests/test_kernel.py
This workflow is for repositories that do not modify the OS but need to run application-level tests (e.g., OpenJDK, Go, Rust) on a stable RISC-V RVA23 Debian/Ubuntu environment. This flow is capability-generic (agnostic to the specific SoC/board).
To balance speed with benchmarking accuracy, we use a hybrid model:
rva23 profile and a specific OS like debian13 or ubuntu2404), rather than a specific board model like a210.tests/test_app.py)import pytest
def test_app_in_container(target):
"""Run application tests inside a sandboxed container on the target."""
ssh = target.get_driver("SSHDriver")
# 1. The runner/script has staged the files at /tmp/workspace.
# 2. Run the test suite inside a clean Debian/Ubuntu container
cmd = (
"docker run --rm "
"-v /tmp/workspace:/workspace "
"-w /workspace "
"riscv64/debian:latest ./run-tests.sh"
)
stdout, stderr, exit_code = ssh.run(cmd)
assert exit_code == 0
lab_env.yaml)To support capability-based targeting, we map generic roles (separating Debian and Ubuntu) to specific board targets in the Labgrid configuration:
targets:
a210-board-01:
resources:
RemoteSerialPort:
host: "192.168.1.50"
port: 2001
NetworkService:
address: "192.168.1.100"
username: "root"
spacemit-k1-01:
resources:
RemoteSerialPort:
host: "192.168.1.50"
port: 2002
NetworkService:
address: "192.168.1.101"
username: "root"
# Map generic capability roles (separated by OS) to physical boards
roles:
rva23_debian13:
- a210-board-01
rva23_ubuntu2404:
- spacemit-k1-01
.github/workflows/riscv_app_test.yml)name: RISC-V RVA23 Application Test
on:
workflow_dispatch:
issue_comment:
types: [created]
jobs:
test-hw:
if: |
github.event_name == 'workflow_dispatch' ||
(github.event.comment && startsWith(github.event.comment.body, '/run-app-tests'))
runs-on: [self-hosted, osu-osl-lab]
steps:
- uses: actions/checkout@v4 # Checks out the repository on the runner
- name: Run Labgrid App Tests (Debian 13)
run: |
# Request a board by its specific Debian capability role
pytest --lg-env=lab_env.yaml --lg-role=rva23_debian13 --junitxml=results.xml tests/test_app.py
# Alternatively, to run on Ubuntu 24.04:
# - name: Run Labgrid App Tests (Ubuntu 24.04)
# run: |
# pytest --lg-env=lab_env.yaml --lg-role=rva23_ubuntu2404 --junitxml=results.xml tests/test_app.py
- name: Publish Test Results
uses: EnricoMi/publish-unit-test-results-action@v2
if: always()
with:
files: "results.xml"
For both workflows, results are captured from the self-hosted runner and uploaded to GitHub:
pytest --junitxml=results.xml and published using the publish-unit-test-results-action to display a rich summary on the PR.Running self-hosted runners on public or shared repositories poses a significant security risk if untrusted code is allowed to run.
workflow_dispatch (manual) or via a ChatOps command (/run-tests) after a maintainer has reviewed the PR code. This prevents external contributors from executing arbitrary code within the OSU-OSL lab network.gha-runner), not root.The runner needs to copy the built image to the TFTP directory (e.g., /srv/tftp/a210/).
root, the /srv/tftp/a210/ directory should be owned by a shared group (e.g., tftpusers) to which the gha-runner user belongs, with write permissions enabled for the group (g+w).riscv64.riscv64 IPA image using diskimage-builder (DIB) with custom elements, or compile a minimal ramdisk via Buildroot/Yocto that runs the ironic-python-agent service. This is tracked as a Phase 2 prerequisite.This section addresses the workflow for repositories that do not modify the Kernel/OS, but require running application or library tests (e.g., OpenJDK, Go, Rust toolchains) on a stable RISC-V RVA23 Debian/Ubuntu environment.
We evaluate two models for this use case:
To balance execution speed with the resource isolation required for accurate testing (especially benchmarking), we implement an Exclusive Container Pool model.
+-------------------------------------------------------------------------+
| GitHub Actions -> Lock Board (Labgrid/Ironic) -> SSH to Host |
| -> Start RISC-V Debian/Ubuntu Container -> Run Tests |
| -> Destroy Container -> Unlock Board |
+-------------------------------------------------------------------------+
|
v (Periodic / On-Failure)
+-------------------------------------------------------------------------+
| Hard Reset: Trigger TFTP Reflash to restore Host OS to Golden Image |
+-------------------------------------------------------------------------+
docker rm -f). The runner executes a quick host-level cleanup script (cleaning /tmp, verifying no rogue processes are running).Instead of TFTP, modern BMCs (via OpenBMC) support mounting a remote disk image over HTTPS.