Skip to content

Commit 9da654a

Browse files
committed
[CI] Add stage-feature tests to ci-demo CI
INFINIOPS-296 What: Add job_matrix_stages_k8.yaml, run multiple matrix files via CI_K8_FILES, assert stage ordering from the Jenkins console log, and document the stage property. How: Extend local_gha_ci.sh to iterate multiple confs and compare declared stage order against Starting stage logs; wire the stages matrix into the GHA workflow. Why: Provide real CI coverage that stages run sequentially in the declared order (build, test, publish). Signed-off-by: Benny Itkin <bitkin@nvidia.com>
1 parent 6857fca commit 9da654a

6 files changed

Lines changed: 186 additions & 8 deletions

File tree

.ci/examples/job_matrix_stages.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ runs_on_dockers:
88
- {name: 'ubuntu2204', url: 'ubuntu:22.04', arch: 'x86_64'}
99
- {name: 'rhel9', url: 'redhat/ubi9', arch: 'x86_64'}
1010

11-
# Steps with the same 'stage' value are grouped and run in parallel
12-
# under a single Jenkins stage block. Steps without 'stage' run
13-
# sequentially as usual.
11+
# Steps are assigned to Jenkins stages based on the 'stage' field.
12+
# All steps with the same 'stage' value are grouped together and executed sequentially within that stage block.
13+
# Steps without a 'stage' field are assigned to a default stage and executed in order.
1414

1515
steps:
1616
- name: Build lib

.ci/job_matrix_stages_k8.yaml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
job: ci-k8-stages
3+
4+
kubernetes:
5+
cloud: swx-k8s
6+
namespace: default
7+
serviceAccount: jenkins
8+
hostNetwork: false
9+
privileged: false
10+
limits: "{memory: 2Gi, cpu: 1000m}"
11+
requests: "{memory: 512Mi, cpu: 250m}"
12+
caps_add: "[]"
13+
tolerations: "[]"
14+
imagePullSecrets: "[]"
15+
16+
arch_table:
17+
x86_64:
18+
nodeSelector: "kubernetes.io/os=linux,kubernetes.io/arch=amd64"
19+
jnlpImage: "jenkins/inbound-agent:latest"
20+
aarch64:
21+
nodeSelector: "kubernetes.io/os=linux,kubernetes.io/arch=arm64"
22+
jnlpImage: "jenkins/inbound-agent:latest"
23+
24+
empty_volumes:
25+
- {mountPath: /tmp/workspace, memory: false}
26+
27+
runs_on_dockers:
28+
- {name: "ubuntu-x86_64", url: "ubuntu:22.04", arch: "x86_64"}
29+
- {name: "ubuntu-aarch64", url: "ubuntu:22.04", arch: "aarch64"}
30+
31+
matrix:
32+
axes:
33+
arch:
34+
- x86_64
35+
- aarch64
36+
include:
37+
- {arch: '${TARGET_ARCH}'}
38+
39+
# Steps are grouped into Jenkins stages via the 'stage' field.
40+
# Stages run sequentially in the order they first appear (build -> test -> publish),
41+
# and steps within a stage run together in that stage block.
42+
steps:
43+
- name: Build lib
44+
stage: build
45+
run: echo "building lib for ${arch}"
46+
47+
- name: Build tools
48+
stage: build
49+
run: echo "building tools for ${arch}"
50+
51+
- name: Unit tests
52+
stage: test
53+
run: echo "running unit tests for ${arch}"
54+
55+
- name: Integration tests
56+
stage: test
57+
run: echo "running integration tests for ${arch}"
58+
59+
- name: Publish
60+
stage: publish
61+
run: echo "publishing artifacts for ${arch}"
62+
63+
pipeline_start:
64+
run: echo "pipeline_start for ${job}"
65+
66+
pipeline_stop:
67+
run: echo "pipeline_stop status=${pipeline_status}"
68+
69+
failFast: false

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
BRANCH: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }}
3131
REPO_URL: "https://github.com/${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name || github.repository }}.git"
3232
TARGET_ARCHES: ${{ env.TARGET_ARCH }}
33-
CI_K8_FILE: .ci/job_matrix_gha_k8.yaml
33+
CI_K8_FILES: ".ci/job_matrix_gha_k8.yaml .ci/job_matrix_stages_k8.yaml"
3434
KEEP_JENKINS: "false"
3535
KEEP_K8S: "false"
3636
run: |

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ TARGET_ARCHES=aarch64 KEEP_JENKINS=false KEEP_K8S=false make -C .ci local-gha-ci
134134
CI_K8_FILE=.ci/job_matrix_debug.yaml make -C .ci local-gha-ci
135135
```
136136

137+
Run several matrix files in a single Jenkins/k3s setup:
138+
139+
```bash
140+
CI_K8_FILES=".ci/job_matrix_gha_k8.yaml .ci/job_matrix_stages_k8.yaml" make -C .ci local-gha-ci
141+
```
142+
137143
## Matrix YAML Essentials
138144

139145
A matrix config must include:

USERGUIDE.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ All are optional in schema.
153153
| `credentialsId` | `str/list(str)` |
154154
| `args` | `list/map/str` |
155155
| `env` | `map` |
156+
| `stage` | `str` |
156157

157158
## Minimal Valid Example
158159

@@ -194,6 +195,44 @@ steps:
194195
run: uname -m
195196
```
196197
198+
## Stages
199+
200+
Steps can be grouped into named stages with the optional `stage` key in `step_conf`.
201+
This lets you define sequential phases (for example build, then test, then deploy)
202+
while running the work inside each phase in parallel.
203+
204+
Behavior:
205+
206+
- Stage names are collected from the `stage` field of each step, in the order they
207+
first appear. Steps without a `stage` fall into a stage named `default`.
208+
- Stages execute sequentially in that first-seen order; every task in a stage
209+
finishes before the next stage starts (each is rendered as a Jenkins stage block).
210+
- Within a stage, tasks for all matching images/agents run in parallel (subject to
211+
`batchSize` and `failFast`). Multiple steps of the same stage on a single image run
212+
in their defined order unless the step sets `parallel: true`.
213+
- Stage grouping only takes effect when more than one distinct stage is present. If
214+
every step resolves to the same stage (for example when none define `stage`), all
215+
tasks run together in parallel, as before.
216+
217+
```yaml
218+
steps:
219+
- name: Build lib
220+
run: echo build lib
221+
stage: build
222+
- name: Build tools
223+
run: echo build tools
224+
stage: build
225+
- name: Unit tests
226+
run: echo unit
227+
stage: test
228+
- name: Publish
229+
run: echo publish
230+
stage: publish
231+
```
232+
233+
Here `build` runs first (both build steps in parallel across images), then `test`,
234+
then `publish`. See `.ci/examples/job_matrix_stages.yaml` for a complete example.
235+
197236
## Validation
198237

199238
Validate a file with:

scripts/local_gha_ci.sh

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ JENKINS_K8S_DNS_NAME=${JENKINS_K8S_DNS_NAME:-${JENKINS_NETWORK_ALIAS}.default.sv
2222
KEEP_JENKINS=${KEEP_JENKINS:-true}
2323

2424
CI_K8_FILE=${CI_K8_FILE:-.ci/job_matrix_gha_k8.yaml}
25+
# Accept one or more config files (space/comma separated). Falls back to CI_K8_FILE.
26+
CI_K8_FILES=${CI_K8_FILES:-${CI_K8_FILE}}
2527
TARGET_ARCHES=${TARGET_ARCHES:-${TARGET_ARCH:-}}
2628
SKIP_REGEX=${SKIP_REGEX:-}
2729
AGENT_EXECUTORS=${AGENT_EXECUTORS:-8}
@@ -119,12 +121,22 @@ fi
119121

120122
mkdir -p "${LOG_DIR}"
121123

122-
echo "[1/9] Using static workflow config ${CI_K8_FILE}"
123-
if [[ ! -f "${CI_K8_FILE}" ]]; then
124-
echo "ERROR: Config file not found: ${CI_K8_FILE}" >&2
124+
conf_files=()
125+
while IFS= read -r conf; do
126+
[[ -n "${conf}" ]] && conf_files+=("${conf}")
127+
done < <(echo "${CI_K8_FILES}" | tr ', ' '\n' | awk 'NF')
128+
if [[ "${#conf_files[@]}" -eq 0 ]]; then
129+
echo "ERROR: No config files specified (set CI_K8_FILES or CI_K8_FILE)" >&2
125130
exit 1
126131
fi
127-
conf_files=("${CI_K8_FILE}")
132+
133+
echo "[1/9] Using static workflow config(s): ${conf_files[*]}"
134+
for conf in "${conf_files[@]}"; do
135+
if [[ ! -f "${conf}" ]]; then
136+
echo "ERROR: Config file not found: ${conf}" >&2
137+
exit 1
138+
fi
139+
done
128140

129141
target_arch_list=()
130142
while IFS= read -r arch; do
@@ -297,6 +309,55 @@ save_jenkins_artifacts() {
297309
docker logs "${JENKINS_NAME}" > "${LOG_DIR}/${prefix}.jenkins-container.log" 2>&1 || true
298310
}
299311

312+
# Extract declared stage names from a matrix YAML in first-appearance order (deduped).
313+
extract_declared_stages() {
314+
local file="$1"
315+
awk '
316+
/^[[:space:]]*stage:[[:space:]]*/ {
317+
line=$0
318+
sub(/^[[:space:]]*stage:[[:space:]]*/, "", line)
319+
sub(/[[:space:]]*(#.*)?$/, "", line)
320+
gsub(/["'\'']/, "", line)
321+
if (line != "" && !(line in seen)) { seen[line]=1; order[++n]=line }
322+
}
323+
END { for (i=1;i<=n;i++) print order[i] }
324+
' "${file}"
325+
}
326+
327+
# Extract stage names in the order they were entered from a Jenkins console log (deduped).
328+
extract_console_stages() {
329+
local logf="$1"
330+
grep -a 'Starting stage:' "${logf}" 2>/dev/null \
331+
| sed -E 's/.*Starting stage:[[:space:]]*//' \
332+
| sed -E 's/[^A-Za-z0-9_.-].*$//' \
333+
| awk 'NF && !seen[$0]++'
334+
}
335+
336+
# Assert stages ran sequentially in declared order. Self-activating: no-op unless
337+
# the config declares >= 2 stages. Returns non-zero on mismatch.
338+
assert_stage_order() {
339+
local conf_file="$1" console_log="$2" label="$3"
340+
local expected actual nstages
341+
expected=$(extract_declared_stages "${conf_file}")
342+
nstages=$(printf '%s\n' "${expected}" | awk 'NF' | wc -l | tr -d ' ')
343+
if [[ "${nstages}" -lt 2 ]]; then
344+
return 0
345+
fi
346+
if [[ ! -f "${console_log}" ]]; then
347+
echo "STAGE-ORDER FAIL ${label}: console log not found (${console_log})"
348+
return 1
349+
fi
350+
actual=$(extract_console_stages "${console_log}")
351+
if [[ "${expected}" == "${actual}" ]]; then
352+
echo "STAGE-ORDER PASS ${label}: $(echo ${expected} | tr '\n' ' ')"
353+
return 0
354+
fi
355+
echo "STAGE-ORDER FAIL ${label}"
356+
echo " expected: $(echo ${expected} | tr '\n' ' ')"
357+
echo " actual: $(echo ${actual} | tr '\n' ' ')"
358+
return 1
359+
}
360+
300361
echo "Waiting for Jenkins CLI readiness"
301362
cli_ready=false
302363
for i in $(seq 1 90); do
@@ -575,6 +636,9 @@ for conf in "${conf_files[@]}"; do
575636
fail_count=$((fail_count + 1))
576637
else
577638
echo "PASS ${conf_rel} TARGET_ARCH=${target_arch}"
639+
if ! assert_stage_order "${conf}" "${LOG_DIR}/${output_prefix}.jenkins-console.log" "${conf_base} TARGET_ARCH=${target_arch}"; then
640+
fail_count=$((fail_count + 1))
641+
fi
578642
fi
579643
done
580644
done

0 commit comments

Comments
 (0)