forked from oras-project/oras-go
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[RD] Add Salad's resumable download patches (#3)
* [RD] Annotations key declarations (internal/spec/artifact.go) Constants for the Annotations map used by resumable downloads Signed-off-by: Dean Troyer <[email protected]> * [RD] Copy hashVerifier from go-digest hashVerifier is non-public in go-digest and we need to supply a pre-initialized Hash Signed-off-by: Dean Troyer <[email protected]> * [RD] Add resume to VerifyReader and add hashVerifier (content/reader.go) * Add resume field to VerifyReader to track resume mode * Skip setting UnexpectedEOF error in Read() in resume mode * Add hashVerifier from digest * Recover serialized Hash object in NewVerifyReader and create a content.hashVerifier if in resume mode * Duplicate tests for resume mode Signed-off-by: Dean Troyer <[email protected]> * [RD] Add Store.IngestFile() and Storage.IngestFile() (content/oci/storage.go) * injest(): calculate hash on existing ingest file contents Signed-off-by: Dean Troyer <[email protected]> * [RD] Add Repository.FetchHead() and friends (registry/remote/repository.go) * BlobStoreHead.FetchHead() * Repository.FetchHead() * blobStore.Fetch() checks for Range header support and sets it if so Signed-off-by: Dean Troyer <[email protected]> * [RD] Updates to CopyBuffer (internal/ioutil/io.go) * Handle partial copies in CopyBuffer() * Add tests (upstream TestUnwrapNopCloser(0 is failing, not fixed here) Signed-off-by: Dean Troyer <[email protected]> * [RD] Set up resume in doCopyNode() (copy.go) * Look for a partial downloaded file * Get headers from src to verify Range: support * Set up resume arguments in desc.Annotations Signed-off-by: Dean Troyer <[email protected]> --------- Signed-off-by: Dean Troyer <[email protected]>
- Loading branch information
1 parent
f11378b
commit 239949c
Showing
14 changed files
with
1,127 additions
and
28 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
# Salad Extension to ORAS Go Library | ||
|
||
## TL;DR | ||
|
||
The cost of failed downloads being restarted by deleting partially downloaded layers | ||
is a high one to pay in the Salad network especially when some of these layers may exceed | ||
10GB. Resuming partial downloads is an important part of a robust and resilient and | ||
performant distributed compute node with limited bandwidth. | ||
|
||
This repository is a fork of https://github.com/oras-project/oras-go/ just after the v2.5.0 | ||
tag. The [`oras-main`](https://github.com/saladtechnologies/oras-go/tree/oras-main) contains | ||
the upstream [`main`](https://github.com/oras-project/oras-go/tree/main) branch while | ||
[`main`](https://github.com/saladtechnologies/oras-go/tree/main) contains | ||
Salad's download changes. The only changes required to build the ORAS CLI (`oras`) | ||
(https://github.com/oras-project/oras) are to use this replacement | ||
for `oras-go`. | ||
|
||
Typically the only change required is a `replace` line in `go.mod`, setting the | ||
psuedo-version to the desired commit: | ||
|
||
``` | ||
replace oras.land/oras-go/v2 v2.5.0 => github.com/saladtechnologies/oras-go/v2 v2.0.0-20240409062726-11d464f8432e | ||
``` | ||
|
||
## Summary | ||
|
||
### Resumable Downloads | ||
|
||
This resumable download implementation is contained entirely within `oras-go` and the code path | ||
below `oras.doCopyNode()`. Attempts have been made to not alter the existing external interfaces | ||
although some new ones have been added. Resume download is always enabled but conditions are | ||
carefully evaluated and falls back to the original code path when not possible. This | ||
implementation does not include any way to force resume enabled (fail if not possible) or | ||
disabled (do not attempt even when possible). | ||
|
||
Resumable downloads are limited to remote registry source targets and local storage destination | ||
targets. This is the use case for prepping containerd images. | ||
|
||
### Changes | ||
|
||
* `Annotations` key constants (`internal/spec/artifact.go`) | ||
* AnnotationResume* - the keys used in the Annotations[] map | ||
* The Annotations field of the Descriptor is used to pass state around during the request handling. This avoids changing the public API via interfaces or structs. | ||
* Salad-specific keys are defined in `internal/spec/artifact.go` using constants with names beginning with `AnnotationResume`. | ||
|
||
* `oras.doCopyNode()` (`copy.go`) | ||
* Look for files in the ingest directory that match the current `Descriptor` being downloaded | ||
* if found: save full filename and file size to the `Annotations` map for the `Descriptor` | ||
* if not found: nothing to see here, proceed as normal | ||
|
||
* `remote.FetcherHead` (`registry/remote/repository.go`) | ||
* interface defining `FetchHead()` | ||
|
||
* `remote.BlobStoreHead` (`registry/remote/repository.go`) | ||
* interface combining `registry.BlobStore` with `FetcherHead` | ||
|
||
* `remote.Repository.FetchHead()` (new) (`registry/remote/repository.go`) | ||
* call `FetchHead()` when `BlobStoreHead` is implemented | ||
|
||
* `remote.blobStore` (`registry/remote/repository.go`) | ||
* `blobStore.Fetch()` | ||
* call `FetchHead()` to check for the `Range` header support from the server | ||
* FALSE: | ||
* reset resume flag and proceed as usual | ||
* TRUE: | ||
* Set `Range` header | ||
* after GET request to remote repository if in resume | ||
* `StatusPartialContent`: | ||
* check response `ContentLength` against `target.Size - ingestSize` | ||
* `StatusOK`: | ||
* check response `ContentLength` against `target.Size` | ||
* `blobStore.FetchHead()` (new) | ||
* do HEAD call to src | ||
* `StatusOK`: | ||
* check response `ContentLength` against `target.Size` | ||
* check response header `Accept-Ranges` has value `bytes` | ||
* TRUE: | ||
* Set resume flag | ||
|
||
* `content.Storage.Push()` (`content/oci/storage.go`) | ||
* call `Storage.ingest()` as usual | ||
|
||
* `content.Storage.ingest()` (`content/oci/storage.go`) | ||
* if resume conditions are all met | ||
* TRUE: | ||
* open existing ingest file | ||
* seek to 0 in ingest file | ||
* create a new Hash to contain the current hash of the ingest file | ||
* save encoded Hash to `Annotations[hash]` | ||
* FALSE: | ||
* if not found: `CreateTemp()` a new ingest file as usual | ||
* if `0 <= ingest size < content-length` | ||
* TRUE: | ||
* call `ioutil.CopyBuffer()` as usual | ||
|
||
* `ioutil.CopyBuffer()` (`internal/ioutil.io.go`) | ||
* call `content.NewVerifyReader()` as usual | ||
* handle `io.ErrUnexpectedEOF`: check `bytes read == desc.Size - ingestSize` | ||
|
||
* `content.NewVerifyReader()` (`content/reader.go`) | ||
* Add `resume` field to `VerifyReader` struct | ||
* if `Annotations[offset]` > 0 | ||
* TRUE: | ||
* decode `Annotations[Hash]` | ||
* create a new `content.hashVerifier` with the new `Hash` and the original `desc.Digest` | ||
* FALSE: | ||
* create a new `digest.hashVerifier` from `desc.Digest` | ||
|
||
* `content.hashVerifier` (new) (`content/verifiers.go`) | ||
* `digest.hashVerifier` is copied here from `opencontainers/go-digest/blob/master/verifiers.go` | ||
because it is private and we need to construct a verifier with our new `Hash` and the original `Digest`. | ||
|
||
* `content.Resumer` (new) (`content.storage.go`) | ||
* Interface to get ingest filenames, also used to determine support for resumable downloads | ||
|
||
* `content.Store.IngestFile()` (new) (`content/oci/storage.go`) | ||
* Provide access to `content.Store.storage.IngestFile()` | ||
|
||
* `content.Storage.IngestFile()` (new) (`content/oci/storage.go`) | ||
* Locate and return the first matching ingest file, if any |
This file contains 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
This file contains 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
Oops, something went wrong.