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 }