Skip to content

oci: multipart image upload, part timeout, and flexible shape sizing - #1792

Merged
eyberg merged 3 commits into
nanovms:masterfrom
AngeloRubens:oci-multipart-image-upload
Sep 9, 2026
Merged

eyberg merged 3 commits into
nanovms:masterfrom
AngeloRubens:oci-multipart-image-upload

Conversation

@AngeloRubens

Copy link
Copy Markdown
Contributor

Three fixes to the OCI provider, found while deploying an arm64 image to
eu-milan-1. They are independent and each stands on its own commit.

Uploading the image never finished. CreateImage sent the whole qcow2 in a
single PutObject, and the SDK gave up while the request was still in flight:

Put ".../o/<image>": context deadline exceeded (Client.Timeout exceeded while awaiting headers)

The first commit switches to the transfer package's upload manager, which
splits the object into parts, sends them in parallel and retries one part
rather than the whole transfer. That is what the OCI CLI does for the same
reason. The upload manager wants the concrete ObjectStorageClient while the
provider deliberately hides Object Storage behind an interface, so the upload
moves behind a small seam: the real client gets the multipart uploader, and a
provider built with injected clients gets a single PutObject over the injected
interface, which keeps the existing tests exercising that path unchanged.

Splitting it was not enough on its own. The SDK applies its client timeout to
the whole request, body included, and defaults it to 60 seconds, which no part
of a useful size meets on an ordinary uplink. The second commit gives the
uploader its own client with a timeout sized for a part and leaves every other
call at the default; an explicit OCI_CUSTOM_CLIENT_TIMEOUT still wins. Parts
are 16 MiB so a retry is cheap. It remains a bound: a part that stops making
progress still fails rather than hanging.

The third commit is unrelated to the upload. CreateInstance hardcoded one ocpu
and one gigabyte for every flexible shape, with a comment saying as much, and
nothing in the configuration could change it, so anything needing more memory
than that could not be launched at all. Ocpus and MemoryInGBs are now read from
the cloud configuration, keeping the previous values as defaults so a
configuration that does not set them behaves exactly as before.

Verified end to end: a 120 MB image uploads in about ten minutes with no
timeout, imports, and launches on VM.Standard.A1.Flex at 1 OCPU / 1 GB and at
2 OCPU / 4 GB. go test ./provider/oci/ passes.

Importing an image on OCI failed for any image of real size. CreateImage
sent the whole qcow2 in a single PutObject, and the SDK's HTTP client
gave up on its own timeout while still awaiting the response headers:

  Put ".../o/<image>": context deadline exceeded
  (Client.Timeout exceeded while awaiting headers)

The transfer package's upload manager splits the object into parts,
uploads them in parallel and retries a single failed part instead of the
whole transfer, which is what the OCI CLI does for the same reason.

The upload manager wants the concrete ObjectStorageClient, while the
provider deliberately hides Object Storage behind the StorageService
interface so it can be mocked. So the upload is now behind an
ImageUploader seam: Initialize wires the multipart uploader over the real
client, and NewProviderWithClients wires a single PutObject over the
injected StorageService, which keeps the existing tests exercising that
seam unchanged.

qemu-img now writes the qcow2 compressed (-c). The image is mostly empty
space, so this is the difference between minutes and tens of minutes of
upload on an ordinary connection, and Object Storage imports a compressed
qcow2 exactly like an uncompressed one.
Splitting the upload into parts was not enough on its own: the SDK's HTTP
client applies its timeout to the whole request, sending the body
included, and defaults it to 60 seconds. No part of a useful size meets
that on an ordinary uplink, all the more so with several parts sharing
it, so the upload still died, one part further along:

  Put ".../u/<image>?uploadId=...&uploadPartNum=3": context deadline
  exceeded (Client.Timeout exceeded while awaiting headers)

The uploader now gets its own Object Storage client whose timeout is
sized for a part, leaving the client every other call uses at the SDK
default, and an explicit OCI_CUSTOM_CLIENT_TIMEOUT still wins. Parts drop
to 16 MiB, which makes a retry cheaper.

It stays a bound rather than an absence of one: a part that stops making
progress fails instead of hanging forever.
A flexible shape does not carry its cpu and memory in its name, and
CreateInstance hardcoded one ocpu and one gigabyte for every *.Flex, a
"hack as we don't have a system for 'flex' today", as the comment put it.
On OCI's Ampere shapes that is the smallest slice the shape allows, and
nothing in the configuration could change it, so anything that needs more
than a gigabyte of memory could not be launched at all.

Ocpus and MemoryInGBs are now read from the cloud configuration, keeping
the previous 1/1 as the default so nothing changes for a configuration
that does not set them.
@eyberg
eyberg merged commit fc3195f into nanovms:master Sep 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants