Skip to content

ci(minicloud): run minicloud on a local KVM Jenkins agent - #15668

Open
fruch wants to merge 2 commits into
masterfrom
feature/minicloud-local-agent
Open

ci(minicloud): run minicloud on a local KVM Jenkins agent#15668
fruch wants to merge 2 commits into
masterfrom
feature/minicloud-local-agent

Conversation

@fruch

@fruch fruch commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

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 agent

Backend-agnostic and independent of minicloud: declaring the parameter is the whole integration,
because getJenkinsLabels already reads the global params binding 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_region for GCE/Azure/OCI, and random from the per-provider
supported list) — the pipelines hand that value straight to createSctRunner and the
collect/clean stages, so a label alone is never enough.

2. local_agent — the local topology, per job or per build

Selectable two ways, so moving an existing job to a lab machine needs no new jenkinsfile:

  • jenkinsfile: local_agent: true alongside minicloud: true, for a job that always runs there.
  • build parameter: tick local_agent on any minicloud job and the KVM label is resolved for
    that run. Read off params from build sdcm.tester: Introduce the clean_aws_resources decorator #2 onward (build AWS use spot instances #1 only loads parameters and these
    pipelines abort it), falling back to the jenkinsfile default before that. Safe to read there
    unlike params.minicloud, since getJenkinsLabels only ever merges a minicloud key into the
    global binding.

jenkins_label remains 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 only
option for anyone without a lab machine.

Three helpers exist only because the agent outlives its build:

helper why
minicloudPreflight one pass, every problem at once (KVM writability, docker daemon, minicloud0, port 5000, disk, $USER/$HOME) — before Argus registration and the hydra pull, so a misconfigured agent costs seconds, not 20 minutes
minicloudReclaim sweeps at build start, so the previous failure's logs survive for post-mortem; never touches ~/.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_ip from a persistent workspace
stopMinicloud local teardown, last, after log collection — docker rm -f kills every guest with the container. A no-op in runner topology, where the container dies with the instance

Also 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-v1 yet, so this path has never run; the
prerequisites are documented in docs/minicloud.md. The seam itself is provable today by pinning
any job with jenkins_label. Everything in #15618 is verified — AWS and GCE artifact jobs
pass on the runner topology.

Open question from review

@fruch asked whether the preflight logic should be a hydra command reusing
MinicloudManager.preflight_check() instead of bash. Partly yes — /dev/kvm, docker and memory
are 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 onto master and reviewed on its own after the one below it lands; none of them should be merged together. jenkins/precommit fails here only because of the stacking — Jenkins derives baseHash from a merge ref it cannot reach for a stacked PR — and clears once the base is master.

@fruch
fruch force-pushed the feature/minicloud-pipelines branch from d097e1c to bfa880e Compare August 4, 2026 14:07
@fruch
fruch force-pushed the feature/minicloud-local-agent branch from b3bdb92 to 31783cd Compare August 4, 2026 14:07
@fruch

fruch commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Carrying over a security finding from the review of #15618, since jenkins_label moved here:

jenkins_label is a free-form build parameter returned directly as the Pipeline node label. Jenkins treats this value as a label expression, so a build user can select arbitrary agents (including sensitive labels or expressions using ||) for jobs that bind QA credentials.

Worth addressing before this merges — an allowlist of approved labels plus rejecting expression syntax (||, &&, parentheses), or making the pin jenkinsfile-controlled rather than a build parameter. The same class of issue on the minicloud image reference is already fixed in #15618 with a repository allowlist validated before export.

@fruch
fruch force-pushed the feature/minicloud-local-agent branch from 31783cd to 1e20477 Compare August 4, 2026 14:10
@scylladb-promoter

scylladb-promoter commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

✅ Test Summary: PASSED

✅ Precommit: PASSED

Total Passed Failed Skipped
15 15 0 0

✅ Tests: PASSED

Total Passed Failed Errors Skipped
4782 4751 0 0 31

Full build log

@fruch
fruch force-pushed the feature/minicloud-pipelines branch from bfa880e to 73d7103 Compare August 4, 2026 14:18
@fruch
fruch force-pushed the feature/minicloud-local-agent branch from 1e20477 to 85502f7 Compare August 4, 2026 14:18
@fruch fruch added backport/none Backport is not required minicloud PRs related to minicloud labels Aug 4, 2026
@fruch
fruch force-pushed the feature/minicloud-pipelines branch from 73d7103 to 86896f6 Compare August 4, 2026 14:54
@fruch
fruch force-pushed the feature/minicloud-local-agent branch 2 times, most recently from 3dd1aab to e77c908 Compare August 4, 2026 15:44
@fruch
fruch force-pushed the feature/minicloud-pipelines branch from 86896f6 to 9468276 Compare August 4, 2026 18:59
Copilot AI balanced review requested due to automatic review settings August 4, 2026 18:59
@fruch
fruch force-pushed the feature/minicloud-local-agent branch from e77c908 to a2c4d00 Compare August 4, 2026 18:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_agent defaults to false and useRunner then 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).

Comment thread vars/getJenkinsLabels.groovy
Comment thread vars/minicloudPreflight.groovy Outdated
Comment thread vars/longevityPipeline.groovy Outdated
Comment thread vars/rollingUpgradePipeline.groovy Outdated
Comment thread vars/longevityPipeline.groovy Outdated
Comment thread vars/artifactsPipeline.groovy
Comment thread docs/sct-pipelines.md Outdated
Comment thread docs/minicloud.md Outdated
Comment thread vars/minicloudPreflight.groovy Outdated
Comment thread vars/stopMinicloud.groovy
@fruch fruch added the test-provision-aws Run provision test on AWS label Aug 4, 2026
@fruch
fruch force-pushed the feature/minicloud-pipelines branch from 9468276 to 2c09f43 Compare August 4, 2026 22:27
Copilot AI review requested due to automatic review settings August 4, 2026 22:28
@fruch
fruch force-pushed the feature/minicloud-local-agent branch from a2c4d00 to 88f61f3 Compare August 4, 2026 22:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • localAgent is honored even when minicloudEnabled is 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 with minicloudEnabled, or explicitly require a suitable jenkins_label and 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-1 to local minicloud runs whose GCE/Azure/OCI region parameter is empty. Since builder.region is 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

  • localAgent is honored even when minicloudEnabled is 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 deliberate jenkins_label was 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_agent is 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_agent from 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-29 and several minicloud jenkinsfile headers still document artifacts as local-agent-by-default and say local_agent: false opts 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 && !localAgent makes 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 modprobe call 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

@fruch
fruch force-pushed the feature/minicloud-pipelines branch from 2c09f43 to e6f95c5 Compare August 5, 2026 09:47
Copilot AI review requested due to automatic review settings August 5, 2026 09:47
@fruch
fruch force-pushed the feature/minicloud-local-agent branch from 88f61f3 to 99fc6f2 Compare August 5, 2026 09:47
@fruch
fruch force-pushed the feature/minicloud-pipelines branch from 11a0e7e to 3b3aa17 Compare August 6, 2026 08:45
@fruch
fruch force-pushed the feature/minicloud-local-agent branch 2 times, most recently from dd3a273 to 6eb8f86 Compare August 6, 2026 09:06
@fruch
fruch force-pushed the feature/minicloud-pipelines branch from 3b3aa17 to dbc0dab Compare August 10, 2026 11:44
@fruch
fruch force-pushed the feature/minicloud-local-agent branch from 6eb8f86 to 0046534 Compare August 10, 2026 11:44
@fruch
fruch force-pushed the feature/minicloud-pipelines branch from dbc0dab to fb06265 Compare August 10, 2026 13:05
@fruch
fruch force-pushed the feature/minicloud-local-agent branch from 0046534 to a8059e4 Compare August 10, 2026 13:05
fruch added a commit that referenced this pull request Aug 10, 2026
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.
@fruch
fruch force-pushed the feature/minicloud-pipelines branch from fb06265 to dea9510 Compare August 10, 2026 15:57
@fruch
fruch force-pushed the feature/minicloud-local-agent branch from a8059e4 to 247d18e Compare August 10, 2026 15:58
@fruch

fruch commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Restacked onto #15618's new head (dea951045d). Still 2 commits / 10 files of its own.

Merge order note: this is the bottom of a stack that merges one PR at a time — #15617 (core, targets master, self-contained) → #15618 (pipelines, sct-runner topology) → this. Each is rebased onto master and reviewed on its own after the one below it lands; none of them get merged together. jenkins/precommit fails here purely because of the stacking and clears once the base is master.

Sequence and rationale: docs/plans/minicloud-stack-merge-runbook.md (on #15617).

fruch added a commit that referenced this pull request Aug 10, 2026
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.
@fruch
fruch force-pushed the feature/minicloud-pipelines branch from dea9510 to f473d83 Compare August 10, 2026 16:22
@fruch
fruch force-pushed the feature/minicloud-local-agent branch from 247d18e to 422b342 Compare August 10, 2026 16:23
@fruch
fruch force-pushed the feature/minicloud-pipelines branch from f473d83 to 33839c0 Compare August 10, 2026 16:31
@fruch
fruch force-pushed the feature/minicloud-local-agent branch from 422b342 to e1745f4 Compare August 10, 2026 16:31
@fruch
fruch requested a review from dimakr August 10, 2026 18:13
@fruch
fruch force-pushed the feature/minicloud-pipelines branch from 33839c0 to ebf37fa Compare August 11, 2026 05:49
@fruch
fruch force-pushed the feature/minicloud-local-agent branch from e1745f4 to a9ed75b Compare August 11, 2026 05:50
@fruch
fruch force-pushed the feature/minicloud-pipelines branch from ebf37fa to 1729aa5 Compare August 11, 2026 05:58
@fruch
fruch force-pushed the feature/minicloud-local-agent branch from a9ed75b to 1d8db31 Compare August 11, 2026 05:59
@dimakr
dimakr force-pushed the feature/minicloud-local-agent branch from 1d8db31 to 4dbfb65 Compare August 12, 2026 19:53
@dimakr
dimakr requested a review from abramche as a code owner August 12, 2026 19:53
@dimakr
dimakr changed the base branch from feature/minicloud-pipelines to master August 12, 2026 19:53
fruch and others added 2 commits August 13, 2026 10:16
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>
@dimakr
dimakr force-pushed the feature/minicloud-local-agent branch from 4dbfb65 to 6257019 Compare August 13, 2026 08:17
@scylladbbot

Copy link
Copy Markdown

@fruch new branch manager-3.12 was added, please add backport label if needed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-assisted backport/none Backport is not required minicloud PRs related to minicloud test-provision-aws Run provision test on AWS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants