routes: support resumable pull

Some embedded clients use the "Range" header for blob pulls in order to
resume downloads.

Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>
This commit is contained in:
Ramkumar Chinchani
2022-08-30 22:12:10 +00:00
committed by Ramkumar Chinchani
parent 7912b6a3fb
commit 90c8390c29
10 changed files with 723 additions and 27 deletions
+161
View File
@@ -5463,6 +5463,167 @@ func TestManifestImageIndex(t *testing.T) {
})
}
func TestPullRange(t *testing.T) {
Convey("Make a new controller", t, func() {
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
ctlr := api.NewController(conf)
dir := t.TempDir()
ctlr.Config.Storage.RootDirectory = dir
go startServer(ctlr)
defer stopServer(ctlr)
test.WaitTillServerReady(baseURL)
// create a blob/layer
resp, err := resty.R().Post(baseURL + "/v2/index/blobs/uploads/")
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
loc := test.Location(baseURL, resp)
So(loc, ShouldNotBeEmpty)
// since we are not specifying any prefix i.e provided in config while starting server,
// so it should store index1 to global root dir
_, err = os.Stat(path.Join(dir, "index"))
So(err, ShouldBeNil)
resp, err = resty.R().Get(loc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
content := []byte("0123456789")
digest := godigest.FromBytes(content)
So(digest, ShouldNotBeNil)
// monolithic blob upload: success
resp, err = resty.R().SetQueryParam("digest", digest.String()).
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
blobLoc := resp.Header().Get("Location")
So(blobLoc, ShouldNotBeEmpty)
So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
So(resp.Header().Get(constants.DistContentDigestKey), ShouldNotBeEmpty)
blobLoc = baseURL + blobLoc
Convey("Range is supported using 'bytes'", func() {
resp, err = resty.R().Head(blobLoc)
So(err, ShouldBeNil)
So(resp.Header().Get("Accept-Ranges"), ShouldEqual, "bytes")
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
})
Convey("Get a range of bytes", func() {
resp, err = resty.R().SetHeader("Range", "bytes=0-").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusPartialContent)
So(resp.Header().Get("Content-Length"), ShouldEqual, fmt.Sprintf("%d", len(content)))
So(resp.Body(), ShouldResemble, content)
resp, err = resty.R().SetHeader("Range", "bytes=0-100").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusPartialContent)
So(resp.Header().Get("Content-Length"), ShouldEqual, fmt.Sprintf("%d", len(content)))
So(resp.Body(), ShouldResemble, content)
resp, err = resty.R().SetHeader("Range", "bytes=0-10").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusPartialContent)
So(resp.Header().Get("Content-Length"), ShouldEqual, fmt.Sprintf("%d", len(content)))
So(resp.Body(), ShouldResemble, content)
resp, err = resty.R().SetHeader("Range", "bytes=0-0").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusPartialContent)
So(resp.Header().Get("Content-Length"), ShouldEqual, "1")
So(resp.Body(), ShouldResemble, content[0:1])
resp, err = resty.R().SetHeader("Range", "bytes=0-1").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusPartialContent)
So(resp.Header().Get("Content-Length"), ShouldEqual, "2")
So(resp.Body(), ShouldResemble, content[0:2])
resp, err = resty.R().SetHeader("Range", "bytes=2-3").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusPartialContent)
So(resp.Header().Get("Content-Length"), ShouldEqual, "2")
So(resp.Body(), ShouldResemble, content[2:4])
})
Convey("Negative cases", func() {
resp, err = resty.R().SetHeader("Range", "=0").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "=a").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "=").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "byte=").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "bytes=").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "byte=-0").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "byte=0").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "octet=-0").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "bytes=-0").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "bytes=1-0").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "bytes=-1-0").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "bytes=-1--0").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "bytes=1--2").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "bytes=0-a").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "bytes=a-10").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
resp, err = resty.R().SetHeader("Range", "bytes=a-b").Get(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusRequestedRangeNotSatisfiable)
})
})
}
func TestInjectInterruptedImageManifest(t *testing.T) {
Convey("Make a new controller", t, func() {
port := test.GetFreePort()