forked from TrueCloudLab/distribution
Initial implementation of Layer API
The http API has its first set of endpoints to implement the core aspects of fetching and uploading layers. Uploads can be started and completed in a single chunk and the content can be fetched via tarsum. Most proposed error conditions should be represented but edge cases likely remain. In this version, note that the layers are still called layers, even though the routes are pointing to blobs. This will change with backend refactoring over the next few weeks. The unit tests are a bit of a shamble but these need to be carefully written along with the core specification process. As the the client-server interaction solidifies, we can port this into a verification suite for registry providers.
This commit is contained in:
parent
195568017a
commit
e158e3cd65
6 changed files with 528 additions and 32 deletions
236
api_test.go
Normal file
236
api_test.go
Normal file
|
@ -0,0 +1,236 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
_ "github.com/docker/docker-registry/storagedriver/inmemory"
|
||||
|
||||
"github.com/gorilla/handlers"
|
||||
|
||||
"github.com/docker/docker-registry/common/testutil"
|
||||
"github.com/docker/docker-registry/configuration"
|
||||
"github.com/docker/docker-registry/digest"
|
||||
)
|
||||
|
||||
// TestLayerAPI conducts a full of the of the layer api.
|
||||
func TestLayerAPI(t *testing.T) {
|
||||
// TODO(stevvooe): This test code is complete junk but it should cover the
|
||||
// complete flow. This must be broken down and checked against the
|
||||
// specification *before* we submit the final to docker core.
|
||||
|
||||
config := configuration.Configuration{
|
||||
Storage: configuration.Storage{
|
||||
"inmemory": configuration.Parameters{},
|
||||
},
|
||||
}
|
||||
|
||||
app := NewApp(config)
|
||||
server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
|
||||
router := v2APIRouter()
|
||||
|
||||
u, err := url.Parse(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("error parsing server url: %v", err)
|
||||
}
|
||||
|
||||
imageName := "foo/bar"
|
||||
// "build" our layer file
|
||||
layerFile, tarSumStr, err := testutil.CreateRandomTarFile()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating random layer file: %v", err)
|
||||
}
|
||||
|
||||
layerDigest := digest.Digest(tarSumStr)
|
||||
|
||||
// -----------------------------------
|
||||
// Test fetch for non-existent content
|
||||
r, err := router.GetRoute(routeNameBlob).Host(u.Host).
|
||||
URL("name", imageName,
|
||||
"digest", tarSumStr)
|
||||
|
||||
resp, err := http.Get(r.String())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error fetching non-existent layer: %v", err)
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusNotFound:
|
||||
break // expected
|
||||
default:
|
||||
d, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected status fetching non-existent layer: %v, %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
t.Logf("response:\n%s", string(d))
|
||||
t.Fatalf("unexpected status fetching non-existent layer: %v, %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Test head request for non-existent content
|
||||
resp, err = http.Head(r.String())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error checking head on non-existent layer: %v", err)
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusNotFound:
|
||||
break // expected
|
||||
default:
|
||||
d, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected status checking head on non-existent layer: %v, %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
t.Logf("response:\n%s", string(d))
|
||||
t.Fatalf("unexpected status checking head on non-existent layer: %v, %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Upload a layer
|
||||
r, err = router.GetRoute(routeNameBlobUpload).Host(u.Host).
|
||||
URL("name", imageName)
|
||||
if err != nil {
|
||||
t.Fatalf("error starting layer upload: %v", err)
|
||||
}
|
||||
|
||||
resp, err = http.Post(r.String(), "", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error starting layer upload: %v", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
d, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected status starting layer upload: %v, %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
t.Logf("response:\n%s", string(d))
|
||||
t.Fatalf("unexpected status starting layer upload: %v, %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
if resp.Header.Get("Location") == "" { // TODO(stevvooe): Need better check here.
|
||||
t.Fatalf("unexpected Location: %q != %q", resp.Header.Get("Location"), "foo")
|
||||
}
|
||||
|
||||
if resp.Header.Get("Content-Length") != "0" {
|
||||
t.Fatalf("unexpected content-length: %q != %q", resp.Header.Get("Content-Length"), "0")
|
||||
}
|
||||
|
||||
layerLength, _ := layerFile.Seek(0, os.SEEK_END)
|
||||
layerFile.Seek(0, os.SEEK_SET)
|
||||
|
||||
uploadURLStr := resp.Header.Get("Location")
|
||||
|
||||
// TODO(sday): Cancel the layer upload here and restart.
|
||||
|
||||
query := url.Values{
|
||||
"digest": []string{layerDigest.String()},
|
||||
"length": []string{fmt.Sprint(layerLength)},
|
||||
}
|
||||
|
||||
uploadURL, err := url.Parse(uploadURLStr)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error parsing url: %v", err)
|
||||
}
|
||||
|
||||
uploadURL.RawQuery = query.Encode()
|
||||
|
||||
// Just do a monolithic upload
|
||||
req, err := http.NewRequest("PUT", uploadURL.String(), layerFile)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating new request: %v", err)
|
||||
}
|
||||
|
||||
resp, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error doing put: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusCreated:
|
||||
break // expected
|
||||
default:
|
||||
d, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected status putting chunk: %v, %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
t.Logf("response:\n%s", string(d))
|
||||
t.Fatalf("unexpected status putting chunk: %v, %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
if resp.Header.Get("Location") == "" {
|
||||
t.Fatalf("unexpected Location: %q", resp.Header.Get("Location"))
|
||||
}
|
||||
|
||||
if resp.Header.Get("Content-Length") != "0" {
|
||||
t.Fatalf("unexpected content-length: %q != %q", resp.Header.Get("Content-Length"), "0")
|
||||
}
|
||||
|
||||
layerURL := resp.Header.Get("Location")
|
||||
|
||||
// ------------------------
|
||||
// Use a head request to see if the layer exists.
|
||||
resp, err = http.Head(layerURL)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error checking head on non-existent layer: %v", err)
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
break // expected
|
||||
default:
|
||||
d, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected status checking head on layer: %v, %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
t.Logf("response:\n%s", string(d))
|
||||
t.Fatalf("unexpected status checking head on layer: %v, %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
logrus.Infof("fetch the layer")
|
||||
// ----------------
|
||||
// Fetch the layer!
|
||||
resp, err = http.Get(layerURL)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error fetching layer: %v", err)
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
break // expected
|
||||
default:
|
||||
d, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected status fetching layer: %v, %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
t.Logf("response:\n%s", string(d))
|
||||
t.Fatalf("unexpected status fetching layer: %v, %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
// Verify the body
|
||||
verifier := digest.NewDigestVerifier(layerDigest)
|
||||
io.Copy(verifier, resp.Body)
|
||||
|
||||
if !verifier.Verified() {
|
||||
d, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected status checking head on layer ayo!: %v, %v", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
t.Logf("response:\n%s", string(d))
|
||||
t.Fatalf("response body did not pass verification")
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue