Skip to content

Add aria2 prefetch + glance-direct import fallback#1252

Merged
berendt merged 4 commits into
mainfrom
prefetch-on-stuck
Jul 14, 2026
Merged

Add aria2 prefetch + glance-direct import fallback#1252
berendt merged 4 commits into
mainfrom
prefetch-on-stuck

Conversation

@ideaship

Copy link
Copy Markdown
Contributor

What

A robust alternative to Glance's web-download import for images whose source
download is slow or truncating: download the image locally with aria2c
(multi-connection range download + resume + retry), then import via the
glance-direct method — which runs the same decompress → qcow2→raw convert →
Ceph-store taskflow, so conversion and storage are unchanged.

Changes

  • Bound wait_for_image() against hangs: overall deadline, terminal-status
    handling, and SDK-error caps (also fixes a pre-existing forever-hang on the
    web-download path).
  • Wire the definition checksum through import so the download can be verified.
  • Add --prefetch {never,on-stuck,always} (default on-stuck) and
    --import-timeout; on on-stuck, fall back to aria2 + glance-direct after a
    failed web-download. Includes a free-space preflight (the download is new
    manager-side disk) and PREFETCH: telemetry lines.

--prefetch never reproduces today's behaviour byte-for-byte. 45 unit tests;
flake8/black/mypy clean.

Related

🤖 Generated with Claude Code

ideaship added 4 commits July 13, 2026 11:16
The wait_for_image() polling loop could block the caller forever. Any
status that was neither "active" nor "queued" -- including the terminal
"killed"/"deleted" states -- fell through to an unconditional sleep(10)
inside a "while True" with no overall deadline, and the except handler
set exit_with_error but kept looping, so repeated get_image() failures
spun indefinitely. A stalled import therefore had no way to fail fast.

Make the wait bounded:

- Accept an optional absolute deadline (a time.monotonic() timestamp).
  When omitted it is derived from self.CONF.import_timeout so any single
  caller stays bounded; import_image() will pass one shared deadline
  across all attempts. On the deadline elapsing, log and return None.
- Handle the terminal statuses "killed", "deleted" and "pending_delete"
  explicitly by returning None instead of looping.
- Bound consecutive SDK exceptions and return None once the limit is
  reached rather than spinning.

wait_for_image() no longer sets exit_with_error; returning None is now
its single "did not reach active" signal and the caller owns the error
decision. The existing-image wait in process_image() is updated to set
exit_with_error when the wait returns None, preserving today's failure
reporting for that path.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Roger Luethi <luethi@osism.tech>
The per-version "checksum" field defined in the image YAML (and rendered
by OSISM's octavia template as "sha256:<hex>") was dropped during
version normalization and never reached import_image(), so it could not
be used to verify a downloaded image. The upcoming aria2 prefetch path
needs it to let aria2c verify and retry on corruption.

Copy "checksum" through the normalization loop, add an optional
"checksum" parameter to import_image(), and pass it from process_image()
via versions[version].get("checksum"). Add a module-level helper
checksum_to_aria2() that converts a "sha256:<hex>" or bare hex digest to
the "<algo>=<hex>" form aria2c's --checksum expects.

No behavioural change yet: the checksum is only threaded through; the
prefetch path that consumes it is added separately. Definitions using
version "latest" fetch their digest dynamically and are not covered here.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Roger Luethi <luethi@osism.tech>
When Glance imports the octavia amphora image via the web-download
method, glance-api itself pulls the ~345 MB qcow2 from an external object
store over the public internet. When that fetch stalls, the import task
fails asynchronously, the image reverts to "queued", and the manager
eventually exits non-zero -- aborting the deploy before tempest runs.

Add an alternative import path that moves only the fragile internet
fetch off glance-api and onto aria2c running locally, then uploads the
downloaded file via the glance-direct import method. glance-direct runs
the same decompress/convert/store taskflow as web-download, so the
qcow2->raw conversion required by the Ceph/RBD store is preserved.

A new --prefetch option selects the behaviour, validated against the
allowed set:

- never    -- today's behaviour, byte-for-byte;
- on-stuck -- (default) web-download first, fall back to aria2 +
              glance-direct once if every attempt fails;
- always   -- skip web-download and use aria2 + glance-direct directly.

The fallback does not reuse the image left behind by the failed
web-download (which may be queued, killed, deleted or still importing);
it best-effort deletes that leftover and stages a fresh image. A
synchronous web-download create/import error is treated as a failed
attempt so the fallback is still reached, and the fallback's own
create_image() is inside its try so a failure (for example a fixed
image id not yet freed) fails cleanly rather than raising.

Since the download runs on the manager (unlike web-download, which pulls
directly into glance), a HEAD Content-Length vs free-space preflight
aborts before writing when the target filesystem is too small, so a
prefetch cannot silently fill the manager disk; an actual out-of-space
error mid-download is still caught via aria2c's non-zero exit.

import_image() computes one overall deadline from --import-timeout and
shares it across all attempts, reserving headroom for the fallback. The
aria2c download is bounded by the remaining budget (subprocess timeout)
and the deadline is re-checked before staging; the staging upload itself
is a synchronous SDK call and is not bounded, but the import wait after
it is. aria2c is a required runtime dependency; if absent, _download()
logs an error and returns False rather than skipping silently. Distinct
"PREFETCH:" log lines are emitted for monitoring how often the fallback
fires and its outcome.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Roger Luethi <luethi@osism.tech>
Describe the new --prefetch import path (never / on-stuck / always) and
the --import-timeout budget, including the aria2c runtime dependency, the
glance-direct requirement on the target cloud, and the manager-side
temporary disk space the prefetch download needs.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Roger Luethi <luethi@osism.tech>
@ideaship
ideaship marked this pull request as ready for review July 14, 2026 04:44
@ideaship
ideaship requested a review from berendt July 14, 2026 04:44
@ideaship ideaship moved this from New to In review in Human Board Jul 14, 2026
@berendt
berendt merged commit 129df0d into main Jul 14, 2026
3 checks passed
@berendt
berendt deleted the prefetch-on-stuck branch July 14, 2026 07:00
@github-project-automation github-project-automation Bot moved this from In review to Done in Human Board Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants