oci: multipart image upload, part timeout, and flexible shape sizing - #1792
Merged
Merged
Conversation
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
approved these changes
Sep 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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.