github.com/lusis/distribution@v2.0.1+incompatible/registry/handlers/api_test.go (about)

     1  package handlers
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"net/http/httputil"
    12  	"net/url"
    13  	"os"
    14  	"path"
    15  	"reflect"
    16  	"strings"
    17  	"testing"
    18  
    19  	"github.com/docker/distribution/configuration"
    20  	"github.com/docker/distribution/digest"
    21  	"github.com/docker/distribution/manifest"
    22  	"github.com/docker/distribution/registry/api/v2"
    23  	_ "github.com/docker/distribution/registry/storage/driver/inmemory"
    24  	"github.com/docker/distribution/testutil"
    25  	"github.com/docker/libtrust"
    26  	"github.com/gorilla/handlers"
    27  	"golang.org/x/net/context"
    28  )
    29  
    30  // TestCheckAPI hits the base endpoint (/v2/) ensures we return the specified
    31  // 200 OK response.
    32  func TestCheckAPI(t *testing.T) {
    33  	env := newTestEnv(t)
    34  
    35  	baseURL, err := env.builder.BuildBaseURL()
    36  	if err != nil {
    37  		t.Fatalf("unexpected error building base url: %v", err)
    38  	}
    39  
    40  	resp, err := http.Get(baseURL)
    41  	if err != nil {
    42  		t.Fatalf("unexpected error issuing request: %v", err)
    43  	}
    44  	defer resp.Body.Close()
    45  
    46  	checkResponse(t, "issuing api base check", resp, http.StatusOK)
    47  	checkHeaders(t, resp, http.Header{
    48  		"Content-Type":   []string{"application/json; charset=utf-8"},
    49  		"Content-Length": []string{"2"},
    50  	})
    51  
    52  	p, err := ioutil.ReadAll(resp.Body)
    53  	if err != nil {
    54  		t.Fatalf("unexpected error reading response body: %v", err)
    55  	}
    56  
    57  	if string(p) != "{}" {
    58  		t.Fatalf("unexpected response body: %v", string(p))
    59  	}
    60  }
    61  
    62  func TestURLPrefix(t *testing.T) {
    63  	config := configuration.Configuration{
    64  		Storage: configuration.Storage{
    65  			"inmemory": configuration.Parameters{},
    66  		},
    67  	}
    68  	config.HTTP.Prefix = "/test/"
    69  
    70  	env := newTestEnvWithConfig(t, &config)
    71  
    72  	baseURL, err := env.builder.BuildBaseURL()
    73  	if err != nil {
    74  		t.Fatalf("unexpected error building base url: %v", err)
    75  	}
    76  
    77  	parsed, _ := url.Parse(baseURL)
    78  	if !strings.HasPrefix(parsed.Path, config.HTTP.Prefix) {
    79  		t.Fatalf("Prefix %v not included in test url %v", config.HTTP.Prefix, baseURL)
    80  	}
    81  
    82  	resp, err := http.Get(baseURL)
    83  	if err != nil {
    84  		t.Fatalf("unexpected error issuing request: %v", err)
    85  	}
    86  	defer resp.Body.Close()
    87  
    88  	checkResponse(t, "issuing api base check", resp, http.StatusOK)
    89  	checkHeaders(t, resp, http.Header{
    90  		"Content-Type":   []string{"application/json; charset=utf-8"},
    91  		"Content-Length": []string{"2"},
    92  	})
    93  
    94  }
    95  
    96  // TestLayerAPI conducts a full test of the of the layer api.
    97  func TestLayerAPI(t *testing.T) {
    98  	// TODO(stevvooe): This test code is complete junk but it should cover the
    99  	// complete flow. This must be broken down and checked against the
   100  	// specification *before* we submit the final to docker core.
   101  	env := newTestEnv(t)
   102  
   103  	imageName := "foo/bar"
   104  	// "build" our layer file
   105  	layerFile, tarSumStr, err := testutil.CreateRandomTarFile()
   106  	if err != nil {
   107  		t.Fatalf("error creating random layer file: %v", err)
   108  	}
   109  
   110  	layerDigest := digest.Digest(tarSumStr)
   111  
   112  	// -----------------------------------
   113  	// Test fetch for non-existent content
   114  	layerURL, err := env.builder.BuildBlobURL(imageName, layerDigest)
   115  	if err != nil {
   116  		t.Fatalf("error building url: %v", err)
   117  	}
   118  
   119  	resp, err := http.Get(layerURL)
   120  	if err != nil {
   121  		t.Fatalf("unexpected error fetching non-existent layer: %v", err)
   122  	}
   123  
   124  	checkResponse(t, "fetching non-existent content", resp, http.StatusNotFound)
   125  
   126  	// ------------------------------------------
   127  	// Test head request for non-existent content
   128  	resp, err = http.Head(layerURL)
   129  	if err != nil {
   130  		t.Fatalf("unexpected error checking head on non-existent layer: %v", err)
   131  	}
   132  
   133  	checkResponse(t, "checking head on non-existent layer", resp, http.StatusNotFound)
   134  
   135  	// ------------------------------------------
   136  	// Start an upload, check the status then cancel
   137  	uploadURLBase, uploadUUID := startPushLayer(t, env.builder, imageName)
   138  
   139  	// A status check should work
   140  	resp, err = http.Get(uploadURLBase)
   141  	if err != nil {
   142  		t.Fatalf("unexpected error getting upload status: %v", err)
   143  	}
   144  	checkResponse(t, "status of deleted upload", resp, http.StatusNoContent)
   145  	checkHeaders(t, resp, http.Header{
   146  		"Location":           []string{"*"},
   147  		"Range":              []string{"0-0"},
   148  		"Docker-Upload-UUID": []string{uploadUUID},
   149  	})
   150  
   151  	req, err := http.NewRequest("DELETE", uploadURLBase, nil)
   152  	if err != nil {
   153  		t.Fatalf("unexpected error creating delete request: %v", err)
   154  	}
   155  
   156  	resp, err = http.DefaultClient.Do(req)
   157  	if err != nil {
   158  		t.Fatalf("unexpected error sending delete request: %v", err)
   159  	}
   160  
   161  	checkResponse(t, "deleting upload", resp, http.StatusNoContent)
   162  
   163  	// A status check should result in 404
   164  	resp, err = http.Get(uploadURLBase)
   165  	if err != nil {
   166  		t.Fatalf("unexpected error getting upload status: %v", err)
   167  	}
   168  	checkResponse(t, "status of deleted upload", resp, http.StatusNotFound)
   169  
   170  	// -----------------------------------------
   171  	// Do layer push with an empty body and different digest
   172  	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
   173  	resp, err = doPushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, bytes.NewReader([]byte{}))
   174  	if err != nil {
   175  		t.Fatalf("unexpected error doing bad layer push: %v", err)
   176  	}
   177  
   178  	checkResponse(t, "bad layer push", resp, http.StatusBadRequest)
   179  	checkBodyHasErrorCodes(t, "bad layer push", resp, v2.ErrorCodeDigestInvalid)
   180  
   181  	// -----------------------------------------
   182  	// Do layer push with an empty body and correct digest
   183  	zeroDigest, err := digest.FromTarArchive(bytes.NewReader([]byte{}))
   184  	if err != nil {
   185  		t.Fatalf("unexpected error digesting empty buffer: %v", err)
   186  	}
   187  
   188  	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
   189  	pushLayer(t, env.builder, imageName, zeroDigest, uploadURLBase, bytes.NewReader([]byte{}))
   190  
   191  	// -----------------------------------------
   192  	// Do layer push with an empty body and correct digest
   193  
   194  	// This is a valid but empty tarfile!
   195  	emptyTar := bytes.Repeat([]byte("\x00"), 1024)
   196  	emptyDigest, err := digest.FromTarArchive(bytes.NewReader(emptyTar))
   197  	if err != nil {
   198  		t.Fatalf("unexpected error digesting empty tar: %v", err)
   199  	}
   200  
   201  	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
   202  	pushLayer(t, env.builder, imageName, emptyDigest, uploadURLBase, bytes.NewReader(emptyTar))
   203  
   204  	// ------------------------------------------
   205  	// Now, actually do successful upload.
   206  	layerLength, _ := layerFile.Seek(0, os.SEEK_END)
   207  	layerFile.Seek(0, os.SEEK_SET)
   208  
   209  	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
   210  	pushLayer(t, env.builder, imageName, layerDigest, uploadURLBase, layerFile)
   211  
   212  	// ------------------------------------------
   213  	// Now, push just a chunk
   214  	layerFile.Seek(0, 0)
   215  
   216  	uploadURLBase, uploadUUID = startPushLayer(t, env.builder, imageName)
   217  	uploadURLBase, dgst := pushChunk(t, env.builder, imageName, uploadURLBase, layerFile, layerLength)
   218  	finishUpload(t, env.builder, imageName, uploadURLBase, dgst)
   219  	// ------------------------
   220  	// Use a head request to see if the layer exists.
   221  	resp, err = http.Head(layerURL)
   222  	if err != nil {
   223  		t.Fatalf("unexpected error checking head on existing layer: %v", err)
   224  	}
   225  
   226  	checkResponse(t, "checking head on existing layer", resp, http.StatusOK)
   227  	checkHeaders(t, resp, http.Header{
   228  		"Content-Length":        []string{fmt.Sprint(layerLength)},
   229  		"Docker-Content-Digest": []string{layerDigest.String()},
   230  	})
   231  
   232  	// ----------------
   233  	// Fetch the layer!
   234  	resp, err = http.Get(layerURL)
   235  	if err != nil {
   236  		t.Fatalf("unexpected error fetching layer: %v", err)
   237  	}
   238  
   239  	checkResponse(t, "fetching layer", resp, http.StatusOK)
   240  	checkHeaders(t, resp, http.Header{
   241  		"Content-Length":        []string{fmt.Sprint(layerLength)},
   242  		"Docker-Content-Digest": []string{layerDigest.String()},
   243  	})
   244  
   245  	// Verify the body
   246  	verifier, err := digest.NewDigestVerifier(layerDigest)
   247  	if err != nil {
   248  		t.Fatalf("unexpected error getting digest verifier: %s", err)
   249  	}
   250  	io.Copy(verifier, resp.Body)
   251  
   252  	if !verifier.Verified() {
   253  		t.Fatalf("response body did not pass verification")
   254  	}
   255  
   256  	// ----------------
   257  	// Fetch the layer with an invalid digest
   258  	badURL := strings.Replace(layerURL, "tarsum", "trsum", 1)
   259  	resp, err = http.Get(badURL)
   260  	if err != nil {
   261  		t.Fatalf("unexpected error fetching layer: %v", err)
   262  	}
   263  
   264  	checkResponse(t, "fetching layer bad digest", resp, http.StatusBadRequest)
   265  
   266  	// Missing tests:
   267  	// 	- Upload the same tarsum file under and different repository and
   268  	//       ensure the content remains uncorrupted.
   269  }
   270  
   271  func TestManifestAPI(t *testing.T) {
   272  	env := newTestEnv(t)
   273  
   274  	imageName := "foo/bar"
   275  	tag := "thetag"
   276  
   277  	manifestURL, err := env.builder.BuildManifestURL(imageName, tag)
   278  	if err != nil {
   279  		t.Fatalf("unexpected error getting manifest url: %v", err)
   280  	}
   281  
   282  	// -----------------------------
   283  	// Attempt to fetch the manifest
   284  	resp, err := http.Get(manifestURL)
   285  	if err != nil {
   286  		t.Fatalf("unexpected error getting manifest: %v", err)
   287  	}
   288  	defer resp.Body.Close()
   289  
   290  	checkResponse(t, "getting non-existent manifest", resp, http.StatusNotFound)
   291  	checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, v2.ErrorCodeManifestUnknown)
   292  
   293  	tagsURL, err := env.builder.BuildTagsURL(imageName)
   294  	if err != nil {
   295  		t.Fatalf("unexpected error building tags url: %v", err)
   296  	}
   297  
   298  	resp, err = http.Get(tagsURL)
   299  	if err != nil {
   300  		t.Fatalf("unexpected error getting unknown tags: %v", err)
   301  	}
   302  	defer resp.Body.Close()
   303  
   304  	// Check that we get an unknown repository error when asking for tags
   305  	checkResponse(t, "getting unknown manifest tags", resp, http.StatusNotFound)
   306  	checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeNameUnknown)
   307  
   308  	// --------------------------------
   309  	// Attempt to push unsigned manifest with missing layers
   310  	unsignedManifest := &manifest.Manifest{
   311  		Versioned: manifest.Versioned{
   312  			SchemaVersion: 1,
   313  		},
   314  		Name: imageName,
   315  		Tag:  tag,
   316  		FSLayers: []manifest.FSLayer{
   317  			{
   318  				BlobSum: "asdf",
   319  			},
   320  			{
   321  				BlobSum: "qwer",
   322  			},
   323  		},
   324  	}
   325  
   326  	resp = putManifest(t, "putting unsigned manifest", manifestURL, unsignedManifest)
   327  	defer resp.Body.Close()
   328  	checkResponse(t, "posting unsigned manifest", resp, http.StatusBadRequest)
   329  	_, p, counts := checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp,
   330  		v2.ErrorCodeManifestUnverified, v2.ErrorCodeBlobUnknown, v2.ErrorCodeDigestInvalid)
   331  
   332  	expectedCounts := map[v2.ErrorCode]int{
   333  		v2.ErrorCodeManifestUnverified: 1,
   334  		v2.ErrorCodeBlobUnknown:        2,
   335  		v2.ErrorCodeDigestInvalid:      2,
   336  	}
   337  
   338  	if !reflect.DeepEqual(counts, expectedCounts) {
   339  		t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
   340  	}
   341  
   342  	// TODO(stevvooe): Add a test case where we take a mostly valid registry,
   343  	// tamper with the content and ensure that we get a unverified manifest
   344  	// error.
   345  
   346  	// Push 2 random layers
   347  	expectedLayers := make(map[digest.Digest]io.ReadSeeker)
   348  
   349  	for i := range unsignedManifest.FSLayers {
   350  		rs, dgstStr, err := testutil.CreateRandomTarFile()
   351  
   352  		if err != nil {
   353  			t.Fatalf("error creating random layer %d: %v", i, err)
   354  		}
   355  		dgst := digest.Digest(dgstStr)
   356  
   357  		expectedLayers[dgst] = rs
   358  		unsignedManifest.FSLayers[i].BlobSum = dgst
   359  
   360  		uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
   361  		pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs)
   362  	}
   363  
   364  	// -------------------
   365  	// Push the signed manifest with all layers pushed.
   366  	signedManifest, err := manifest.Sign(unsignedManifest, env.pk)
   367  	if err != nil {
   368  		t.Fatalf("unexpected error signing manifest: %v", err)
   369  	}
   370  
   371  	payload, err := signedManifest.Payload()
   372  	checkErr(t, err, "getting manifest payload")
   373  
   374  	dgst, err := digest.FromBytes(payload)
   375  	checkErr(t, err, "digesting manifest")
   376  
   377  	manifestDigestURL, err := env.builder.BuildManifestURL(imageName, dgst.String())
   378  	checkErr(t, err, "building manifest url")
   379  
   380  	resp = putManifest(t, "putting signed manifest", manifestURL, signedManifest)
   381  	checkResponse(t, "putting signed manifest", resp, http.StatusAccepted)
   382  	checkHeaders(t, resp, http.Header{
   383  		"Location":              []string{manifestDigestURL},
   384  		"Docker-Content-Digest": []string{dgst.String()},
   385  	})
   386  
   387  	// --------------------
   388  	// Push by digest -- should get same result
   389  	resp = putManifest(t, "putting signed manifest", manifestDigestURL, signedManifest)
   390  	checkResponse(t, "putting signed manifest", resp, http.StatusAccepted)
   391  	checkHeaders(t, resp, http.Header{
   392  		"Location":              []string{manifestDigestURL},
   393  		"Docker-Content-Digest": []string{dgst.String()},
   394  	})
   395  
   396  	// ------------------
   397  	// Fetch by tag name
   398  	resp, err = http.Get(manifestURL)
   399  	if err != nil {
   400  		t.Fatalf("unexpected error fetching manifest: %v", err)
   401  	}
   402  	defer resp.Body.Close()
   403  
   404  	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
   405  	checkHeaders(t, resp, http.Header{
   406  		"Docker-Content-Digest": []string{dgst.String()},
   407  	})
   408  
   409  	var fetchedManifest manifest.SignedManifest
   410  	dec := json.NewDecoder(resp.Body)
   411  	if err := dec.Decode(&fetchedManifest); err != nil {
   412  		t.Fatalf("error decoding fetched manifest: %v", err)
   413  	}
   414  
   415  	if !bytes.Equal(fetchedManifest.Raw, signedManifest.Raw) {
   416  		t.Fatalf("manifests do not match")
   417  	}
   418  
   419  	// ---------------
   420  	// Fetch by digest
   421  	resp, err = http.Get(manifestDigestURL)
   422  	checkErr(t, err, "fetching manifest by digest")
   423  	defer resp.Body.Close()
   424  
   425  	checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
   426  	checkHeaders(t, resp, http.Header{
   427  		"Docker-Content-Digest": []string{dgst.String()},
   428  	})
   429  
   430  	var fetchedManifestByDigest manifest.SignedManifest
   431  	dec = json.NewDecoder(resp.Body)
   432  	if err := dec.Decode(&fetchedManifestByDigest); err != nil {
   433  		t.Fatalf("error decoding fetched manifest: %v", err)
   434  	}
   435  
   436  	if !bytes.Equal(fetchedManifestByDigest.Raw, signedManifest.Raw) {
   437  		t.Fatalf("manifests do not match")
   438  	}
   439  
   440  	// Ensure that the tag is listed.
   441  	resp, err = http.Get(tagsURL)
   442  	if err != nil {
   443  		t.Fatalf("unexpected error getting unknown tags: %v", err)
   444  	}
   445  	defer resp.Body.Close()
   446  
   447  	// Check that we get an unknown repository error when asking for tags
   448  	checkResponse(t, "getting unknown manifest tags", resp, http.StatusOK)
   449  	dec = json.NewDecoder(resp.Body)
   450  
   451  	var tagsResponse tagsAPIResponse
   452  
   453  	if err := dec.Decode(&tagsResponse); err != nil {
   454  		t.Fatalf("unexpected error decoding error response: %v", err)
   455  	}
   456  
   457  	if tagsResponse.Name != imageName {
   458  		t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
   459  	}
   460  
   461  	if len(tagsResponse.Tags) != 1 {
   462  		t.Fatalf("expected some tags in response: %v", tagsResponse.Tags)
   463  	}
   464  
   465  	if tagsResponse.Tags[0] != tag {
   466  		t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
   467  	}
   468  }
   469  
   470  type testEnv struct {
   471  	pk      libtrust.PrivateKey
   472  	ctx     context.Context
   473  	config  configuration.Configuration
   474  	app     *App
   475  	server  *httptest.Server
   476  	builder *v2.URLBuilder
   477  }
   478  
   479  func newTestEnv(t *testing.T) *testEnv {
   480  	config := configuration.Configuration{
   481  		Storage: configuration.Storage{
   482  			"inmemory": configuration.Parameters{},
   483  		},
   484  	}
   485  
   486  	return newTestEnvWithConfig(t, &config)
   487  }
   488  
   489  func newTestEnvWithConfig(t *testing.T, config *configuration.Configuration) *testEnv {
   490  	ctx := context.Background()
   491  
   492  	app := NewApp(ctx, *config)
   493  	server := httptest.NewServer(handlers.CombinedLoggingHandler(os.Stderr, app))
   494  	builder, err := v2.NewURLBuilderFromString(server.URL + config.HTTP.Prefix)
   495  
   496  	if err != nil {
   497  		t.Fatalf("error creating url builder: %v", err)
   498  	}
   499  
   500  	pk, err := libtrust.GenerateECP256PrivateKey()
   501  	if err != nil {
   502  		t.Fatalf("unexpected error generating private key: %v", err)
   503  	}
   504  
   505  	return &testEnv{
   506  		pk:      pk,
   507  		ctx:     ctx,
   508  		config:  *config,
   509  		app:     app,
   510  		server:  server,
   511  		builder: builder,
   512  	}
   513  }
   514  
   515  func putManifest(t *testing.T, msg, url string, v interface{}) *http.Response {
   516  	var body []byte
   517  	if sm, ok := v.(*manifest.SignedManifest); ok {
   518  		body = sm.Raw
   519  	} else {
   520  		var err error
   521  		body, err = json.MarshalIndent(v, "", "   ")
   522  		if err != nil {
   523  			t.Fatalf("unexpected error marshaling %v: %v", v, err)
   524  		}
   525  	}
   526  
   527  	req, err := http.NewRequest("PUT", url, bytes.NewReader(body))
   528  	if err != nil {
   529  		t.Fatalf("error creating request for %s: %v", msg, err)
   530  	}
   531  
   532  	resp, err := http.DefaultClient.Do(req)
   533  	if err != nil {
   534  		t.Fatalf("error doing put request while %s: %v", msg, err)
   535  	}
   536  
   537  	return resp
   538  }
   539  
   540  func startPushLayer(t *testing.T, ub *v2.URLBuilder, name string) (location string, uuid string) {
   541  	layerUploadURL, err := ub.BuildBlobUploadURL(name)
   542  	if err != nil {
   543  		t.Fatalf("unexpected error building layer upload url: %v", err)
   544  	}
   545  
   546  	resp, err := http.Post(layerUploadURL, "", nil)
   547  	if err != nil {
   548  		t.Fatalf("unexpected error starting layer push: %v", err)
   549  	}
   550  	defer resp.Body.Close()
   551  
   552  	checkResponse(t, fmt.Sprintf("pushing starting layer push %v", name), resp, http.StatusAccepted)
   553  
   554  	u, err := url.Parse(resp.Header.Get("Location"))
   555  	if err != nil {
   556  		t.Fatalf("error parsing location header: %v", err)
   557  	}
   558  
   559  	uuid = path.Base(u.Path)
   560  	checkHeaders(t, resp, http.Header{
   561  		"Location":           []string{"*"},
   562  		"Content-Length":     []string{"0"},
   563  		"Docker-Upload-UUID": []string{uuid},
   564  	})
   565  
   566  	return resp.Header.Get("Location"), uuid
   567  }
   568  
   569  // doPushLayer pushes the layer content returning the url on success returning
   570  // the response. If you're only expecting a successful response, use pushLayer.
   571  func doPushLayer(t *testing.T, ub *v2.URLBuilder, name string, dgst digest.Digest, uploadURLBase string, body io.Reader) (*http.Response, error) {
   572  	u, err := url.Parse(uploadURLBase)
   573  	if err != nil {
   574  		t.Fatalf("unexpected error parsing pushLayer url: %v", err)
   575  	}
   576  
   577  	u.RawQuery = url.Values{
   578  		"_state": u.Query()["_state"],
   579  
   580  		"digest": []string{dgst.String()},
   581  	}.Encode()
   582  
   583  	uploadURL := u.String()
   584  
   585  	// Just do a monolithic upload
   586  	req, err := http.NewRequest("PUT", uploadURL, body)
   587  	if err != nil {
   588  		t.Fatalf("unexpected error creating new request: %v", err)
   589  	}
   590  
   591  	return http.DefaultClient.Do(req)
   592  }
   593  
   594  // pushLayer pushes the layer content returning the url on success.
   595  func pushLayer(t *testing.T, ub *v2.URLBuilder, name string, dgst digest.Digest, uploadURLBase string, body io.Reader) string {
   596  	digester := digest.NewCanonicalDigester()
   597  
   598  	resp, err := doPushLayer(t, ub, name, dgst, uploadURLBase, io.TeeReader(body, &digester))
   599  	if err != nil {
   600  		t.Fatalf("unexpected error doing push layer request: %v", err)
   601  	}
   602  	defer resp.Body.Close()
   603  
   604  	checkResponse(t, "putting monolithic chunk", resp, http.StatusCreated)
   605  
   606  	if err != nil {
   607  		t.Fatalf("error generating sha256 digest of body")
   608  	}
   609  
   610  	sha256Dgst := digester.Digest()
   611  
   612  	expectedLayerURL, err := ub.BuildBlobURL(name, sha256Dgst)
   613  	if err != nil {
   614  		t.Fatalf("error building expected layer url: %v", err)
   615  	}
   616  
   617  	checkHeaders(t, resp, http.Header{
   618  		"Location":              []string{expectedLayerURL},
   619  		"Content-Length":        []string{"0"},
   620  		"Docker-Content-Digest": []string{sha256Dgst.String()},
   621  	})
   622  
   623  	return resp.Header.Get("Location")
   624  }
   625  
   626  func finishUpload(t *testing.T, ub *v2.URLBuilder, name string, uploadURLBase string, dgst digest.Digest) string {
   627  	resp, err := doPushLayer(t, ub, name, dgst, uploadURLBase, nil)
   628  	if err != nil {
   629  		t.Fatalf("unexpected error doing push layer request: %v", err)
   630  	}
   631  	defer resp.Body.Close()
   632  
   633  	checkResponse(t, "putting monolithic chunk", resp, http.StatusCreated)
   634  
   635  	expectedLayerURL, err := ub.BuildBlobURL(name, dgst)
   636  	if err != nil {
   637  		t.Fatalf("error building expected layer url: %v", err)
   638  	}
   639  
   640  	checkHeaders(t, resp, http.Header{
   641  		"Location":              []string{expectedLayerURL},
   642  		"Content-Length":        []string{"0"},
   643  		"Docker-Content-Digest": []string{dgst.String()},
   644  	})
   645  
   646  	return resp.Header.Get("Location")
   647  }
   648  
   649  func doPushChunk(t *testing.T, uploadURLBase string, body io.Reader) (*http.Response, digest.Digest, error) {
   650  	u, err := url.Parse(uploadURLBase)
   651  	if err != nil {
   652  		t.Fatalf("unexpected error parsing pushLayer url: %v", err)
   653  	}
   654  
   655  	u.RawQuery = url.Values{
   656  		"_state": u.Query()["_state"],
   657  	}.Encode()
   658  
   659  	uploadURL := u.String()
   660  
   661  	digester := digest.NewCanonicalDigester()
   662  
   663  	req, err := http.NewRequest("PATCH", uploadURL, io.TeeReader(body, digester))
   664  	if err != nil {
   665  		t.Fatalf("unexpected error creating new request: %v", err)
   666  	}
   667  	req.Header.Set("Content-Type", "application/octet-stream")
   668  
   669  	resp, err := http.DefaultClient.Do(req)
   670  
   671  	return resp, digester.Digest(), err
   672  }
   673  
   674  func pushChunk(t *testing.T, ub *v2.URLBuilder, name string, uploadURLBase string, body io.Reader, length int64) (string, digest.Digest) {
   675  	resp, dgst, err := doPushChunk(t, uploadURLBase, body)
   676  	if err != nil {
   677  		t.Fatalf("unexpected error doing push layer request: %v", err)
   678  	}
   679  	defer resp.Body.Close()
   680  
   681  	checkResponse(t, "putting chunk", resp, http.StatusAccepted)
   682  
   683  	if err != nil {
   684  		t.Fatalf("error generating sha256 digest of body")
   685  	}
   686  
   687  	checkHeaders(t, resp, http.Header{
   688  		"Range":          []string{fmt.Sprintf("0-%d", length-1)},
   689  		"Content-Length": []string{"0"},
   690  	})
   691  
   692  	return resp.Header.Get("Location"), dgst
   693  }
   694  
   695  func checkResponse(t *testing.T, msg string, resp *http.Response, expectedStatus int) {
   696  	if resp.StatusCode != expectedStatus {
   697  		t.Logf("unexpected status %s: %v != %v", msg, resp.StatusCode, expectedStatus)
   698  		maybeDumpResponse(t, resp)
   699  
   700  		t.FailNow()
   701  	}
   702  }
   703  
   704  // checkBodyHasErrorCodes ensures the body is an error body and has the
   705  // expected error codes, returning the error structure, the json slice and a
   706  // count of the errors by code.
   707  func checkBodyHasErrorCodes(t *testing.T, msg string, resp *http.Response, errorCodes ...v2.ErrorCode) (v2.Errors, []byte, map[v2.ErrorCode]int) {
   708  	p, err := ioutil.ReadAll(resp.Body)
   709  	if err != nil {
   710  		t.Fatalf("unexpected error reading body %s: %v", msg, err)
   711  	}
   712  
   713  	var errs v2.Errors
   714  	if err := json.Unmarshal(p, &errs); err != nil {
   715  		t.Fatalf("unexpected error decoding error response: %v", err)
   716  	}
   717  
   718  	if len(errs.Errors) == 0 {
   719  		t.Fatalf("expected errors in response")
   720  	}
   721  
   722  	// TODO(stevvooe): Shoot. The error setup is not working out. The content-
   723  	// type headers are being set after writing the status code.
   724  	// if resp.Header.Get("Content-Type") != "application/json; charset=utf-8" {
   725  	// 	t.Fatalf("unexpected content type: %v != 'application/json'",
   726  	// 		resp.Header.Get("Content-Type"))
   727  	// }
   728  
   729  	expected := map[v2.ErrorCode]struct{}{}
   730  	counts := map[v2.ErrorCode]int{}
   731  
   732  	// Initialize map with zeros for expected
   733  	for _, code := range errorCodes {
   734  		expected[code] = struct{}{}
   735  		counts[code] = 0
   736  	}
   737  
   738  	for _, err := range errs.Errors {
   739  		if _, ok := expected[err.Code]; !ok {
   740  			t.Fatalf("unexpected error code %v encountered during %s: %s ", err.Code, msg, string(p))
   741  		}
   742  		counts[err.Code]++
   743  	}
   744  
   745  	// Ensure that counts of expected errors were all non-zero
   746  	for code := range expected {
   747  		if counts[code] == 0 {
   748  			t.Fatalf("expected error code %v not encounterd during %s: %s", code, msg, string(p))
   749  		}
   750  	}
   751  
   752  	return errs, p, counts
   753  }
   754  
   755  func maybeDumpResponse(t *testing.T, resp *http.Response) {
   756  	if d, err := httputil.DumpResponse(resp, true); err != nil {
   757  		t.Logf("error dumping response: %v", err)
   758  	} else {
   759  		t.Logf("response:\n%s", string(d))
   760  	}
   761  }
   762  
   763  // matchHeaders checks that the response has at least the headers. If not, the
   764  // test will fail. If a passed in header value is "*", any non-zero value will
   765  // suffice as a match.
   766  func checkHeaders(t *testing.T, resp *http.Response, headers http.Header) {
   767  	for k, vs := range headers {
   768  		if resp.Header.Get(k) == "" {
   769  			t.Fatalf("response missing header %q", k)
   770  		}
   771  
   772  		for _, v := range vs {
   773  			if v == "*" {
   774  				// Just ensure there is some value.
   775  				if len(resp.Header[k]) > 0 {
   776  					continue
   777  				}
   778  			}
   779  
   780  			for _, hv := range resp.Header[k] {
   781  				if hv != v {
   782  					t.Fatalf("%v header value not matched in response: %q != %q", k, hv, v)
   783  				}
   784  			}
   785  		}
   786  	}
   787  }
   788  
   789  func checkErr(t *testing.T, err error, msg string) {
   790  	if err != nil {
   791  		t.Fatalf("unexpected error %s: %v", msg, err)
   792  	}
   793  }