ci(minicloud): run minicloud on a local KVM Jenkins agent - #15668
Conversation
d097e1c to
bfa880e
Compare
b3bdb92 to
31783cd
Compare
|
Carrying over a security finding from the review of #15618, since
Worth addressing before this merges — an allowlist of approved labels plus rejecting expression syntax ( |
31783cd to
1e20477
Compare
✅ Test Summary: PASSED✅ Precommit: PASSED
✅ Tests: PASSED
|
bfa880e to
73d7103
Compare
1e20477 to
85502f7
Compare
73d7103 to
86896f6
Compare
3dd1aab to
e77c908
Compare
86896f6 to
9468276
Compare
e77c908 to
a2c4d00
Compare
There was a problem hiding this comment.
Pull request overview
Adds local KVM Jenkins-agent support for minicloud while retaining cloud runner topology.
Changes:
- Adds agent selection and local topology controls.
- Adds preflight, reclaim, and teardown helpers.
- Documents local-agent requirements and loads KVM modules on runners.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
vars/stopMinicloud.groovy |
Archives logs and removes minicloud. |
vars/rollingUpgradePipeline.groovy |
Adds local-agent upgrade topology. |
vars/minicloudReclaim.groovy |
Cleans persistent-agent state. |
vars/minicloudPreflight.groovy |
Validates local-agent prerequisites. |
vars/longevityPipeline.groovy |
Adds local-agent longevity topology. |
vars/getJenkinsLabels.groovy |
Supports pinned and KVM labels. |
vars/artifactsPipeline.groovy |
Adds selectable minicloud topology. |
sdcm/utils/lint/env_builder.py |
Excludes orchestration parameter from SCT environment. |
sct.py |
Loads KVM modules on minicloud runners. |
docs/sct-pipelines.md |
Documents local pipeline execution. |
docs/minicloud.md |
Documents topology and prerequisites. |
Suppressed comments (1)
docs/sct-pipelines.md:63
- The artifacts pipeline no longer mandates local topology:
local_agentdefaults to false anduseRunnerthen provisions the nested-virtualization runner. This sentence would send users to expect the opposite behavior.
For minicloud specifically - which needs a KVM-capable agent and mandates the local topology
on artifacts - see [minicloud](./minicloud.md). For the cloud sct-runner mechanics, see
[sct-runners](./sct-runners.md).
9468276 to
2c09f43
Compare
a2c4d00 to
88f61f3
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.
Suppressed comments (12)
vars/longevityPipeline.groovy:25
localAgentis honored even whenminicloudEnabledis false, although the new parameter says it is ignored then. Ticking it on an ordinary longevity build therefore skips runner creation and cleanup and silently runs on the normal regional builder. Either gate the effective value withminicloudEnabled, or explicitly require a suitablejenkins_labeland update the parameter contract.
def localAgent = (params.local_agent != null ? params.local_agent.toString().toBoolean()
: pipelineParams.get('local_agent', false))
vars/getJenkinsLabels.groovy:130
- This also assigns AWS
eu-west-1to local minicloud runs whose GCE/Azure/OCI region parameter is empty. Sincebuilder.regionis forwarded to provisioning and cleanup, resolve the fallback from the provider-specific defaults already represented by the generic labels above.
return [ "label": minicloudLabel, "region": effectiveRegion ?: 'eu-west-1' ]
vars/rollingUpgradePipeline.groovy:22
localAgentis honored even whenminicloudEnabledis false, despite the parameter being documented as ignored. On an ordinary rolling-upgrade build this suppresses runner creation and both cleanup paths, causing the test to execute on the regional builder. Gate local mode on minicloud, or validate that a deliberatejenkins_labelwas supplied and document that generic mode.
def localAgent = (params.local_agent != null ? params.local_agent.toString().toBoolean()
: pipelineParams.get('local_agent', false))
docs/sct-pipelines.md:50
local_agentis now also a build parameter in all three pipelines, so calling it only a jenkinsfile literal and omitting artifacts is outdated.
- **`local_agent: true` (jenkinsfile literal, longevity / rolling-upgrade)** - skip the
vars/longevityPipeline.groovy:409
- This preflight runs only after Checkout/docker login, Argus registration, timeout discovery, and the potentially 240-minute BYO stage. That contradicts the PR/docs guarantee that a bad agent fails before Argus and expensive work. Move reclaim/preflight ahead of Create Argus Test Run and BYO for local minicloud builds.
stage('Minicloud Agent Preflight') {
when { expression { minicloudEnabled && localAgent } }
vars/rollingUpgradePipeline.groovy:337
- In each rolling-upgrade branch, Argus is registered before this preflight, and the top-level supported-version/BYO stages have already run. Thus a misconfigured local agent does not fail before Argus and expensive work as promised. Run a local-agent preflight before those stages (and before per-version Argus registration).
} else if (minicloudEnabled) {
stage("Minicloud Agent Preflight for ${base_version}") {
docs/sct-pipelines.md:59
- This rationale is no longer true: the implementation explicitly reads
params.local_agentfrom build #2 onward. Document the build-1 fallback instead of saying the option cannot be a job parameter.
Why a jenkinsfile literal and not a job parameter: the agent label is decided at
pipeline-definition time, before parameters exist on build #1 - the same reason
`functional_test` works the way it does.
docs/minicloud.md:238
- The preflight does not verify
numExecutors=1, exclusive mode, or any of the listed egress destinations, so saying it verifies all prerequisites can leave an agent incorrectly configured. Distinguish the host-local checks from the Jenkins and network settings that remain manual.
egress to docker.io, ghcr.io, github.com, amazonaws.com, argus.scylladb.com,
downloads.scylladb.com. The *Minicloud Preflight* stage verifies all of this in one pass.
vars/getJenkinsLabels.groovy:117
- When a pinned GCE/Azure/OCI build omits its backend-specific region, this fallback returns AWS
eu-west-1. The pipeline then passes that invalid region to runner creation and cleanup. Use the existing provider defaults (us-east1,eastus,us-phoenix-1) rather than an AWS-only fallback.
This issue also appears on line 130 of the same file.
println("Pinned to Jenkins agent label: " + pinnedLabel + ", region: " + (effectiveRegion ?: 'eu-west-1'))
return [ "label": pinnedLabel, "region": effectiveRegion ?: 'eu-west-1' ]
vars/artifactsPipeline.groovy:24
- This makes the cloud runner the default for minicloud artifact jobs, but
vars/startMinicloud.groovy:24-29and several minicloud jenkinsfile headers still document artifacts as local-agent-by-default and saylocal_agent: falseopts into a runner. Those instructions now select the opposite topology and should be updated with this behavior change.
def useRunner = minicloudEnabled && !localAgent
docs/sct-pipelines.md:63
- Artifacts no longer mandate local topology:
useRunner = minicloudEnabled && !localAgentmakes the cloud runner the default. This sentence currently tells readers the opposite.
For minicloud specifically - which needs a KVM-capable agent and mandates the local topology
on artifacts - see [minicloud](./minicloud.md). For the cloud sct-runner mechanics, see
[sct-runners](./sct-runners.md).
sct.py:2943
- A timeout or transport error from this new
modprobecall is caught by the existing outer handler, which reports “Unable to SSH” even though SSH verification already succeeded. Use an initialization-level error message (or catch this operation separately) so nested-virtualization failures are diagnosable.
remoter.sudo(
"modprobe kvm_intel || modprobe kvm_amd || true", timeout=60, ignore_status=True, verbose=False
2c09f43 to
e6f95c5
Compare
88f61f3 to
99fc6f2
Compare
11a0e7e to
3b3aa17
Compare
dd3a273 to
6eb8f86
Compare
3b3aa17 to
dbc0dab
Compare
6eb8f86 to
0046534
Compare
dbc0dab to
fb06265
Compare
0046534 to
a8059e4
Compare
The three minicloud PRs are a GitHub stack, and a stack invites merging the whole thing at once. Only #15617 is self-contained: nothing in its tree names the Jenkins vars, jenkinsfiles or per-backend overlays that arrive in #15618 and #15668, while #15618's own commits touch ten files #15617 also touches. So the order is fixed and the merges are separate. Records the rationale, the verified rebase --onto sequence for restacking each dependent onto master, and why jenkins/precommit fails on a stacked PR until its base is master.
fb06265 to
dea9510
Compare
a8059e4 to
247d18e
Compare
|
Restacked onto #15618's new head ( Merge order note: this is the bottom of a stack that merges one PR at a time — #15617 (core, targets Sequence and rationale: |
The three minicloud PRs are a GitHub stack, and a stack invites merging the whole thing at once. Only #15617 is self-contained: nothing in its tree names the Jenkins vars, jenkinsfiles or per-backend overlays that arrive in #15618 and #15668, while #15618's own commits touch ten files #15617 also touches. So the order is fixed and the merges are separate. Records the rationale, the verified rebase --onto sequence for restacking each dependent onto master, and why jenkins/precommit fails on a stacked PR until its base is master.
dea9510 to
f473d83
Compare
247d18e to
422b342
Compare
f473d83 to
33839c0
Compare
422b342 to
e1745f4
Compare
33839c0 to
ebf37fa
Compare
e1745f4 to
a9ed75b
Compare
ebf37fa to
1729aa5
Compare
a9ed75b to
1d8db31
Compare
1d8db31 to
4dbfb65
Compare
A `jenkins_label` job parameter pins a build to a named agent on any backend - a lab machine, a static builder - instead of the region builder the backend/region mapping would pick. Declaring the parameter is the whole integration: getJenkinsLabels already reads the global `params` binding, and it is empty on build #1, where the label is first evaluated, so the normal mapping applies there and nothing changes for the ~1100 jobs that never set it. The pinned return resolves the effective backend region first (datacenter/location/oci_region for GCE/Azure/OCI, and `random` from the per-provider supported list), because the pipelines hand that value straight to createSctRunner and the collect/clean stages: the label alone is never enough. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`local_agent` runs a minicloud job on a KVM-capable Jenkins agent with no
sct-runner at all: the runner stages are skipped and every stage takes
the builder-local branch, selected as everywhere else by the absence of
./sct_runner_ip. The cloud runner stays the default, and the only option
for anyone without access to a lab machine. Settable per job in the
jenkinsfile or per build as a parameter, in which case the KVM label is
resolved for that run - so moving an existing job to a lab machine needs
no new jenkinsfile.
Three helpers, all of them there because the agent outlives its build:
* minicloudPreflight - one pass, every problem at once, before Argus
registration and the hydra pull. Everything it probes is a property
of the AGENT, which is why it is not a call into
MinicloudManager.preflight_check(): that runs 20 minutes later from
inside a container, where it can see neither whether the agent user
can open /dev/kvm nor whether the docker daemon answers it. Where
the two would overlap - the RAM arithmetic, which needs the merged
node counts - Python keeps the check.
* minicloudReclaim - twice per build. At the start it sweeps what
earlier builds left and clears a stale ./sct_runner_ip before any
stage can act on it; at the end (`atEnd: true`) it drops this
build's own container and guest state, because a shared agent also
serves scylla builds and dtest and those jobs will never clean up
after minicloud. The image cache gets a long TTL of its own rather
than being kept forever: rebuilding an entry is tens of minutes, but
master images are rebuilt daily, so a cache that only grows fills
the disk on its own. Docker images are not pruned at all, not even
dangling ones - the host is shared and RelEng manage retention their
own way.
* stopMinicloud - local teardown, last, after log collection, because
`docker rm -f` kills every guest with the container. A no-op in
runner topology, where the container dies with the instance.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4dbfb65 to
6257019
Compare
|
@fruch new branch |
What
The local-agent half of minicloud CI, split out of #15618 per review — that PR now runs every
minicloud job on an sct-runner, and this one adds the alternative: a long-lived KVM-capable
Jenkins agent with no runner at all.
Stacked on #15618 (base is its branch); only the two commits below are in this diff.
1.
jenkins_label— pin any build to a named agentBackend-agnostic and independent of minicloud: declaring the parameter is the whole integration,
because
getJenkinsLabelsalready reads the globalparamsbinding and it is empty on build #1,where the label is first evaluated. The pinned return resolves the effective backend region first
(
datacenter/location/oci_regionfor GCE/Azure/OCI, andrandomfrom the per-providersupported list) — the pipelines hand that value straight to
createSctRunnerand thecollect/clean stages, so a label alone is never enough.
2.
local_agent— the local topology, per job or per buildSelectable two ways, so moving an existing job to a lab machine needs no new jenkinsfile:
local_agent: truealongsideminicloud: true, for a job that always runs there.local_agenton any minicloud job and the KVM label is resolved forthat run. Read off
paramsfrom build sdcm.tester: Introduce the clean_aws_resources decorator #2 onward (build AWS use spot instances #1 only loads parameters and thesepipelines abort it), falling back to the jenkinsfile default before that. Safe to read there
unlike
params.minicloud, sincegetJenkinsLabelsonly ever merges aminicloudkey into theglobal binding.
jenkins_labelremains the manual override for pinning a build to a named agent on any backend.Runner stages are skipped and every stage takes the builder-local branch, selected as everywhere
else by the absence of
./sct_runner_ip. The cloud runner stays the default, and remains the onlyoption for anyone without a lab machine.
Three helpers exist only because the agent outlives its build:
minicloudPreflightminicloud0, port 5000, disk,$USER/$HOME) — before Argus registration and the hydra pull, so a misconfigured agent costs seconds, not 20 minutesminicloudReclaim~/.cache/minicloud/amis(tens of GiB, tens of minutes to rebuild — the whole economic case for a static agent). Also the only thing clearing a stale./sct_runner_ipfrom a persistent workspacestopMiniclouddocker rm -fkills every guest with the container. A no-op in runner topology, where the container dies with the instanceAlso loads the KVM module at runner creation for the nested-virt case: the runner image does not
auto-load it and hydra cannot compensate from inside a container without the host's
/lib/modules. Guarded on minicloud activation so no existing job's runner runs a new command.Kept here per review — QATOOLS-374 tracks baking it into the sct-runner image instead, after
which this can go.
Not verified end to end
No Jenkins node serves
minicloud-kvm-builders-v1yet, so this path has never run; theprerequisites are documented in
docs/minicloud.md. The seam itself is provable today by pinningany job with
jenkins_label. Everything in #15618 is verified — AWS and GCE artifact jobspass on the runner topology.
Open question from review
@fruch asked whether the preflight logic should be a
hydracommand reusingMinicloudManager.preflight_check()instead of bash. Partly yes —/dev/kvm, docker and memoryare duplicated. But the value of this stage is failing in ~10 s before the hydra image pull, and
a hydra command would need that image first; the agent-specific checks (
minicloud0, port 5000,group membership, disk) have no Python equivalent either. Happy to go either way — flagging the
trade-off rather than picking silently.
🤖 Generated with Claude Code
Note
Bottom of a stack that merges one PR at a time, in order: #15617 (core, targets
master, self-contained) → #15618 (pipelines, sct-runner topology) → this. Each is rebased ontomasterand reviewed on its own after the one below it lands; none of them should be merged together.jenkins/precommitfails here only because of the stacking — Jenkins derivesbaseHashfrom a merge ref it cannot reach for a stacked PR — and clears once the base ismaster.