Skip to content

Commit 2c8ec1a

Browse files
authored
Merge pull request #391 from sudo-bmitch/pr-chunk-min-size
Add OCI-Chunk-Min-Length header
2 parents 66bd6f5 + 851ea52 commit 2c8ec1a

File tree

3 files changed

+67
-13
lines changed

3 files changed

+67
-13
lines changed

conformance/02_push_test.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"fmt"
55
"net/http"
66

7+
"strconv"
8+
79
"github.com/bloodorangeio/reggie"
810
g "github.com/onsi/ginkgo"
911
. "github.com/onsi/gomega"
@@ -157,6 +159,16 @@ var test02Push = func() {
157159
location := resp.Header().Get("Location")
158160
Expect(location).ToNot(BeEmpty())
159161

162+
// rebuild chunked blob if min size is above our chunk size
163+
minSizeStr := resp.Header().Get("OCI-Chunk-Min-Length")
164+
if minSizeStr != "" {
165+
minSize, err := strconv.Atoi(minSizeStr)
166+
Expect(err).To(BeNil())
167+
if minSize > len(testBlobBChunk1) {
168+
setupChunkedBlob(minSize*2 - 2)
169+
}
170+
}
171+
160172
req = client.NewRequest(reggie.PATCH, resp.GetRelativeLocation()).
161173
SetHeader("Content-Type", "application/octet-stream").
162174
SetHeader("Content-Length", testBlobBChunk2Length).
@@ -187,18 +199,31 @@ var test02Push = func() {
187199
lastResponse = resp
188200
})
189201

190-
g.Specify("PUT request with final chunk should return 201", func() {
202+
g.Specify("PATCH request with second chunk should return 202", func() {
191203
SkipIfDisabled(push)
192-
req := client.NewRequest(reggie.PUT, lastResponse.GetRelativeLocation()).
204+
req := client.NewRequest(reggie.PATCH, lastResponse.GetRelativeLocation()).
193205
SetHeader("Content-Length", testBlobBChunk2Length).
194206
SetHeader("Content-Range", testBlobBChunk2Range).
195207
SetHeader("Content-Type", "application/octet-stream").
196-
SetQueryParam("digest", testBlobBDigest).
197208
SetBody(testBlobBChunk2)
198209
resp, err := client.Do(req)
199210
Expect(err).To(BeNil())
200211
location := resp.Header().Get("Location")
201212
Expect(location).ToNot(BeEmpty())
213+
Expect(resp.StatusCode()).To(Equal(http.StatusAccepted))
214+
lastResponse = resp
215+
})
216+
217+
g.Specify("PUT request with digest should return 201", func() {
218+
SkipIfDisabled(push)
219+
req := client.NewRequest(reggie.PUT, lastResponse.GetRelativeLocation()).
220+
SetHeader("Content-Length", "0").
221+
SetHeader("Content-Type", "application/octet-stream").
222+
SetQueryParam("digest", testBlobBDigest)
223+
resp, err := client.Do(req)
224+
Expect(err).To(BeNil())
225+
location := resp.Header().Get("Location")
226+
Expect(location).ToNot(BeEmpty())
202227
Expect(resp.StatusCode()).To(Equal(http.StatusCreated))
203228
})
204229
})

conformance/setup.go

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"log"
1010
"math/big"
11+
mathrand "math/rand"
1112
"os"
1213
"strconv"
1314

@@ -133,12 +134,14 @@ var (
133134
automaticCrossmountEnabled bool
134135
configs []TestBlob
135136
manifests []TestBlob
137+
seed int64
136138
Version = "unknown"
137139
)
138140

139141
func init() {
140142
var err error
141143

144+
seed = g.GinkgoRandomSeed()
142145
hostname := os.Getenv(envVarRootURL)
143146
namespace := os.Getenv(envVarNamespace)
144147
username := os.Getenv(envVarUsername)
@@ -274,18 +277,12 @@ func init() {
274277
nonexistentManifest = ".INVALID_MANIFEST_NAME"
275278
invalidManifestContent = []byte("blablabla")
276279

277-
testBlobA = []byte("NBA Jam on my NBA toast")
280+
dig, blob := randomBlob(42, seed+1)
281+
testBlobA = blob
278282
testBlobALength = strconv.Itoa(len(testBlobA))
279-
testBlobADigest = godigest.FromBytes(testBlobA).String()
283+
testBlobADigest = dig.String()
280284

281-
testBlobB = []byte("Hello, how are you today?")
282-
testBlobBDigest = godigest.FromBytes(testBlobB).String()
283-
testBlobBChunk1 = testBlobB[:3]
284-
testBlobBChunk1Length = strconv.Itoa(len(testBlobBChunk1))
285-
testBlobBChunk1Range = fmt.Sprintf("0-%d", len(testBlobBChunk1)-1)
286-
testBlobBChunk2 = testBlobB[3:]
287-
testBlobBChunk2Length = strconv.Itoa(len(testBlobBChunk2))
288-
testBlobBChunk2Range = fmt.Sprintf("%d-%d", len(testBlobBChunk1), len(testBlobB)-1)
285+
setupChunkedBlob(42)
289286

290287
dummyDigest = godigest.FromString("hello world").String()
291288

@@ -404,3 +401,27 @@ func randomString(n int) string {
404401
}
405402
return string(ret)
406403
}
404+
405+
// randomBlob outputs a reproducible random blob (based on the seed) for testing
406+
func randomBlob(size int, seed int64) (godigest.Digest, []byte) {
407+
r := mathrand.New(mathrand.NewSource(seed))
408+
b := make([]byte, size)
409+
if n, err := r.Read(b); err != nil {
410+
panic(err)
411+
} else if n != size {
412+
panic("unable to read enough bytes")
413+
}
414+
return godigest.FromBytes(b), b
415+
}
416+
417+
func setupChunkedBlob(size int) {
418+
dig, blob := randomBlob(size, seed+2)
419+
testBlobB = blob
420+
testBlobBDigest = dig.String()
421+
testBlobBChunk1 = testBlobB[:size/2+1]
422+
testBlobBChunk1Length = strconv.Itoa(len(testBlobBChunk1))
423+
testBlobBChunk1Range = fmt.Sprintf("0-%d", len(testBlobBChunk1)-1)
424+
testBlobBChunk2 = testBlobB[size/2+1:]
425+
testBlobBChunk2Length = strconv.Itoa(len(testBlobBChunk2))
426+
testBlobBChunk2Range = fmt.Sprintf("%d-%d", len(testBlobBChunk1), len(testBlobB)-1)
427+
}

spec.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,12 @@ The process remains unchanged for chunked upload, except that the post request M
321321
Content-Length: 0
322322
```
323323

324+
If the registry has a minimum chunk size, the response SHOULD include the following header, where `<size>` is the size in bytes (see the blob `PATCH` definition for usage):
325+
326+
```
327+
OCI-Chunk-Min-Length: <size>
328+
```
329+
324330
Please reference the above section for restrictions on the `<location>`.
325331

326332
---
@@ -347,6 +353,8 @@ It MUST match the following regular expression:
347353
```
348354

349355
The `<length>` is the content-length, in bytes, of the current chunk.
356+
If the registry provides a `OCI-Chunk-Min-Length` header in the `PUT` response, the size of each chunk, except for the final chunk, SHOULD be greater or equal to that value.
357+
The final chunk MAY have any length.
350358

351359
Each successful chunk upload MUST have a `202 Accepted` response code, and MUST have the following header:
352360

0 commit comments

Comments
 (0)