zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/cmd/zb/helper.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "log" 9 "math/rand" 10 "net/http" 11 "net/url" 12 "os" 13 "path" 14 "sync" 15 "time" 16 17 "github.com/google/uuid" 18 godigest "github.com/opencontainers/go-digest" 19 imeta "github.com/opencontainers/image-spec/specs-go" 20 ispec "github.com/opencontainers/image-spec/specs-go/v1" 21 "gopkg.in/resty.v1" 22 23 zerr "zotregistry.io/zot/errors" 24 "zotregistry.io/zot/pkg/common" 25 ) 26 27 func makeHTTPGetRequest(url string, resultPtr interface{}, client *resty.Client) error { 28 resp, err := client.R().Get(url) 29 if err != nil { 30 return err 31 } 32 33 if resp.StatusCode() != http.StatusOK { 34 log.Printf("unable to make GET request on %s, response status code: %d", url, resp.StatusCode()) 35 36 return fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusOK, 37 resp.StatusCode(), string(resp.Body())) 38 } 39 40 err = json.Unmarshal(resp.Body(), resultPtr) 41 if err != nil { 42 return err 43 } 44 45 return nil 46 } 47 48 func makeHTTPDeleteRequest(url string, client *resty.Client) error { 49 resp, err := client.R().Delete(url) 50 if err != nil { 51 return err 52 } 53 54 if resp.StatusCode() != http.StatusAccepted { 55 log.Printf("unable to make DELETE request on %s, response status code: %d", url, resp.StatusCode()) 56 57 return fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusAccepted, 58 resp.StatusCode(), string(resp.Body())) 59 } 60 61 return nil 62 } 63 64 func deleteTestRepo(repos []string, url string, client *resty.Client) error { 65 for _, repo := range repos { 66 var tags common.ImageTags 67 68 // get tags 69 err := makeHTTPGetRequest(fmt.Sprintf("%s/v2/%s/tags/list", url, repo), &tags, client) 70 if err != nil { 71 return err 72 } 73 74 for _, tag := range tags.Tags { 75 var manifest ispec.Manifest 76 77 // first get tag manifest to get containing blobs 78 err := makeHTTPGetRequest(fmt.Sprintf("%s/v2/%s/manifests/%s", url, repo, tag), &manifest, client) 79 if err != nil { 80 return err 81 } 82 83 // delete manifest so that we don't trigger BlobInUse error 84 err = makeHTTPDeleteRequest(fmt.Sprintf("%s/v2/%s/manifests/%s", url, repo, tag), client) 85 if err != nil { 86 return err 87 } 88 89 // delete blobs 90 for _, blob := range manifest.Layers { 91 err := makeHTTPDeleteRequest(fmt.Sprintf("%s/v2/%s/blobs/%s", url, repo, blob.Digest.String()), client) 92 if err != nil { 93 return err 94 } 95 } 96 97 // delete config blob 98 err = makeHTTPDeleteRequest(fmt.Sprintf("%s/v2/%s/blobs/%s", url, repo, manifest.Config.Digest.String()), client) 99 if err != nil { 100 return err 101 } 102 } 103 } 104 105 return nil 106 } 107 108 func pullAndCollect(url string, repos []string, manifestItem manifestStruct, 109 config testConfig, client *resty.Client, statsCh chan statsRecord, 110 ) []string { 111 manifestHash := manifestItem.manifestHash 112 manifestBySizeHash := manifestItem.manifestBySizeHash 113 114 func() { 115 start := time.Now() 116 117 var isConnFail, isErr bool 118 119 var statusCode int 120 121 var latency time.Duration 122 123 defer func() { 124 // send a stats record 125 statsCh <- statsRecord{ 126 latency: latency, 127 statusCode: statusCode, 128 isConnFail: isConnFail, 129 isErr: isErr, 130 } 131 }() 132 133 if config.mixedSize { 134 _, idx := getRandomSize(config.probabilityRange) 135 136 manifestHash = manifestBySizeHash[idx] 137 } 138 139 for repo, manifestTag := range manifestHash { 140 manifestLoc := fmt.Sprintf("%s/v2/%s/manifests/%s", url, repo, manifestTag) 141 142 // check manifest 143 resp, err := client.R(). 144 SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 145 Head(manifestLoc) 146 147 latency = time.Since(start) 148 149 if err != nil { 150 isConnFail = true 151 152 return 153 } 154 155 // request specific check 156 statusCode = resp.StatusCode() 157 if statusCode != http.StatusOK { 158 isErr = true 159 160 return 161 } 162 163 // send request and get the manifest 164 resp, err = client.R(). 165 SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 166 Get(manifestLoc) 167 168 latency = time.Since(start) 169 170 if err != nil { 171 isConnFail = true 172 173 return 174 } 175 176 // request specific check 177 statusCode = resp.StatusCode() 178 if statusCode != http.StatusOK { 179 isErr = true 180 181 return 182 } 183 184 manifestBody := resp.Body() 185 186 // file copy simulation 187 _, err = io.Copy(io.Discard, bytes.NewReader(manifestBody)) 188 189 latency = time.Since(start) 190 191 if err != nil { 192 log.Fatal(err) 193 } 194 195 var pulledManifest ispec.Manifest 196 197 err = json.Unmarshal(manifestBody, &pulledManifest) 198 if err != nil { 199 log.Fatal(err) 200 } 201 202 // check config 203 configDigest := pulledManifest.Config.Digest 204 configLoc := fmt.Sprintf("%s/v2/%s/blobs/%s", url, repo, configDigest) 205 resp, err = client.R().Head(configLoc) 206 207 latency = time.Since(start) 208 209 if err != nil { 210 isConnFail = true 211 212 return 213 } 214 215 // request specific check 216 statusCode = resp.StatusCode() 217 if statusCode != http.StatusOK { 218 isErr = true 219 220 return 221 } 222 223 // send request and get the config 224 resp, err = client.R().Get(configLoc) 225 226 latency = time.Since(start) 227 228 if err != nil { 229 isConnFail = true 230 231 return 232 } 233 234 // request specific check 235 statusCode = resp.StatusCode() 236 if statusCode != http.StatusOK { 237 isErr = true 238 239 return 240 } 241 242 configBody := resp.Body() 243 244 // file copy simulation 245 _, err = io.Copy(io.Discard, bytes.NewReader(configBody)) 246 247 latency = time.Since(start) 248 249 if err != nil { 250 log.Fatal(err) 251 } 252 253 // download blobs 254 for _, layer := range pulledManifest.Layers { 255 blobDigest := layer.Digest 256 blobLoc := fmt.Sprintf("%s/v2/%s/blobs/%s", url, repo, blobDigest) 257 258 // check blob 259 resp, err := client.R().Head(blobLoc) 260 261 latency = time.Since(start) 262 263 if err != nil { 264 isConnFail = true 265 266 return 267 } 268 269 // request specific check 270 statusCode = resp.StatusCode() 271 if statusCode != http.StatusOK { 272 isErr = true 273 274 return 275 } 276 277 // send request and get response the blob 278 resp, err = client.R().Get(blobLoc) 279 280 latency = time.Since(start) 281 282 if err != nil { 283 isConnFail = true 284 285 return 286 } 287 288 // request specific check 289 statusCode = resp.StatusCode() 290 if statusCode != http.StatusOK { 291 isErr = true 292 293 return 294 } 295 296 blobBody := resp.Body() 297 298 // file copy simulation 299 _, err = io.Copy(io.Discard, bytes.NewReader(blobBody)) 300 if err != nil { 301 log.Fatal(err) 302 } 303 } 304 } 305 }() 306 307 return repos 308 } 309 310 func pushMonolithImage(workdir, url, trepo string, repos []string, config testConfig, 311 client *resty.Client, 312 ) (map[string]string, []string, error) { 313 var statusCode int 314 315 // key: repository name. value: manifest name 316 manifestHash := make(map[string]string) 317 318 ruid, err := uuid.NewUUID() 319 if err != nil { 320 return nil, repos, err 321 } 322 323 var repo string 324 325 if trepo != "" { 326 repo = trepo + "/" + ruid.String() 327 } else { 328 repo = ruid.String() 329 } 330 331 repos = append(repos, repo) 332 333 // upload blob 334 resp, err := client.R().Post(fmt.Sprintf("%s/v2/%s/blobs/uploads/", url, repo)) 335 if err != nil { 336 return nil, repos, err 337 } 338 339 // request specific check 340 statusCode = resp.StatusCode() 341 if statusCode != http.StatusAccepted { 342 return nil, repos, fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusAccepted, 343 resp.StatusCode(), string(resp.Body())) //nolint: goerr113 344 } 345 346 loc := getLocation(url, resp) 347 348 var size int 349 350 if config.size == 0 { 351 size, _ = getRandomSize(config.probabilityRange) 352 } else { 353 size = config.size 354 } 355 356 blob := path.Join(workdir, fmt.Sprintf("%d.blob", size)) 357 358 fhandle, err := os.OpenFile(blob, os.O_RDONLY, defaultFilePerms) 359 if err != nil { 360 return nil, repos, err 361 } 362 363 defer fhandle.Close() 364 365 // stream the entire blob 366 digest := blobHash[blob] 367 368 resp, err = client.R(). 369 SetContentLength(true). 370 SetQueryParam("digest", digest.String()). 371 SetHeader("Content-Length", fmt.Sprintf("%d", size)). 372 SetHeader("Content-Type", "application/octet-stream").SetBody(fhandle).Put(loc) 373 374 if err != nil { 375 return nil, repos, err 376 } 377 378 // request specific check 379 statusCode = resp.StatusCode() 380 if statusCode != http.StatusCreated { 381 return nil, repos, fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusCreated, 382 resp.StatusCode(), string(resp.Body())) 383 } 384 385 // upload image config blob 386 resp, err = client.R(). 387 Post(fmt.Sprintf("%s/v2/%s/blobs/uploads/", url, repo)) 388 389 if err != nil { 390 return nil, repos, err 391 } 392 393 // request specific check 394 statusCode = resp.StatusCode() 395 if statusCode != http.StatusAccepted { 396 return nil, repos, fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusAccepted, 397 resp.StatusCode(), string(resp.Body())) 398 } 399 400 loc = getLocation(url, resp) 401 cblob, cdigest := getImageConfig() 402 resp, err = client.R(). 403 SetContentLength(true). 404 SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))). 405 SetHeader("Content-Type", "application/octet-stream"). 406 SetQueryParam("digest", cdigest.String()). 407 SetBody(cblob). 408 Put(loc) 409 410 if err != nil { 411 return nil, repos, err 412 } 413 414 // request specific check 415 statusCode = resp.StatusCode() 416 if statusCode != http.StatusCreated { 417 return nil, repos, fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusCreated, 418 resp.StatusCode(), string(resp.Body())) 419 } 420 421 // create a manifest 422 manifest := ispec.Manifest{ 423 Versioned: imeta.Versioned{ 424 SchemaVersion: defaultSchemaVersion, 425 }, 426 Config: ispec.Descriptor{ 427 MediaType: "application/vnd.oci.image.config.v1+json", 428 Digest: cdigest, 429 Size: int64(len(cblob)), 430 }, 431 Layers: []ispec.Descriptor{ 432 { 433 MediaType: "application/vnd.oci.image.layer.v1.tar", 434 Digest: digest, 435 Size: int64(size), 436 }, 437 }, 438 } 439 440 content, err := json.MarshalIndent(&manifest, "", "\t") 441 if err != nil { 442 return nil, repos, err 443 } 444 445 manifestTag := fmt.Sprintf("tag%d", size) 446 447 // finish upload 448 resp, err = client.R(). 449 SetContentLength(true). 450 SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 451 SetBody(content). 452 Put(fmt.Sprintf("%s/v2/%s/manifests/%s", url, repo, manifestTag)) 453 454 if err != nil { 455 return nil, repos, err 456 } 457 458 // request specific check 459 statusCode = resp.StatusCode() 460 if statusCode != http.StatusCreated { 461 return nil, repos, fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusCreated, 462 resp.StatusCode(), string(resp.Body())) 463 } 464 465 manifestHash[repo] = manifestTag 466 467 return manifestHash, repos, nil 468 } 469 470 func pushMonolithAndCollect(workdir, url, trepo string, count int, 471 repos []string, config testConfig, client *resty.Client, 472 statsCh chan statsRecord, 473 ) []string { 474 func() { 475 start := time.Now() 476 477 var isConnFail, isErr bool 478 479 var statusCode int 480 481 var latency time.Duration 482 483 defer func() { 484 // send a stats record 485 statsCh <- statsRecord{ 486 latency: latency, 487 statusCode: statusCode, 488 isConnFail: isConnFail, 489 isErr: isErr, 490 } 491 }() 492 493 ruid, err := uuid.NewUUID() 494 if err != nil { 495 log.Fatal(err) 496 } 497 498 var repo string 499 500 if trepo != "" { 501 repo = trepo + "/" + ruid.String() 502 } else { 503 repo = ruid.String() 504 } 505 506 repos = append(repos, repo) 507 508 // create a new upload 509 resp, err := client.R(). 510 Post(fmt.Sprintf("%s/v2/%s/blobs/uploads/", url, repo)) 511 512 latency = time.Since(start) 513 514 if err != nil { 515 isConnFail = true 516 517 return 518 } 519 520 // request specific check 521 statusCode = resp.StatusCode() 522 if statusCode != http.StatusAccepted { 523 isErr = true 524 525 return 526 } 527 528 loc := getLocation(url, resp) 529 530 var size int 531 532 if config.mixedSize { 533 size, _ = getRandomSize(config.probabilityRange) 534 } else { 535 size = config.size 536 } 537 538 blob := path.Join(workdir, fmt.Sprintf("%d.blob", size)) 539 540 fhandle, err := os.OpenFile(blob, os.O_RDONLY, defaultFilePerms) 541 if err != nil { 542 isConnFail = true 543 544 return 545 } 546 547 defer fhandle.Close() 548 549 // stream the entire blob 550 digest := blobHash[blob] 551 552 resp, err = client.R(). 553 SetContentLength(true). 554 SetHeader("Content-Length", fmt.Sprintf("%d", size)). 555 SetHeader("Content-Type", "application/octet-stream"). 556 SetQueryParam("digest", digest.String()). 557 SetBody(fhandle). 558 Put(loc) 559 560 latency = time.Since(start) 561 562 if err != nil { 563 isConnFail = true 564 565 return 566 } 567 568 // request specific check 569 statusCode = resp.StatusCode() 570 if statusCode != http.StatusCreated { 571 isErr = true 572 573 return 574 } 575 576 // upload image config blob 577 resp, err = client.R(). 578 Post(fmt.Sprintf("%s/v2/%s/blobs/uploads/", url, repo)) 579 580 latency = time.Since(start) 581 582 if err != nil { 583 isConnFail = true 584 585 return 586 } 587 588 // request specific check 589 statusCode = resp.StatusCode() 590 if statusCode != http.StatusAccepted { 591 isErr = true 592 593 return 594 } 595 596 loc = getLocation(url, resp) 597 cblob, cdigest := getImageConfig() 598 resp, err = client.R(). 599 SetContentLength(true). 600 SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))). 601 SetHeader("Content-Type", "application/octet-stream"). 602 SetQueryParam("digest", cdigest.String()). 603 SetBody(cblob). 604 Put(loc) 605 606 latency = time.Since(start) 607 608 if err != nil { 609 isConnFail = true 610 611 return 612 } 613 614 // request specific check 615 statusCode = resp.StatusCode() 616 if statusCode != http.StatusCreated { 617 isErr = true 618 619 return 620 } 621 622 // create a manifest 623 manifest := ispec.Manifest{ 624 Versioned: imeta.Versioned{ 625 SchemaVersion: defaultSchemaVersion, 626 }, 627 Config: ispec.Descriptor{ 628 MediaType: "application/vnd.oci.image.config.v1+json", 629 Digest: cdigest, 630 Size: int64(len(cblob)), 631 }, 632 Layers: []ispec.Descriptor{ 633 { 634 MediaType: "application/vnd.oci.image.layer.v1.tar", 635 Digest: digest, 636 Size: int64(size), 637 }, 638 }, 639 } 640 641 content, err := json.MarshalIndent(&manifest, "", "\t") 642 if err != nil { 643 log.Fatal(err) 644 } 645 646 manifestTag := fmt.Sprintf("tag%d", count) 647 648 resp, err = client.R(). 649 SetContentLength(true). 650 SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 651 SetBody(content). 652 Put(fmt.Sprintf("%s/v2/%s/manifests/%s", url, repo, manifestTag)) 653 654 latency = time.Since(start) 655 656 if err != nil { 657 isConnFail = true 658 659 return 660 } 661 662 // request specific check 663 statusCode = resp.StatusCode() 664 if statusCode != http.StatusCreated { 665 isErr = true 666 667 return 668 } 669 }() 670 671 return repos 672 } 673 674 func pushChunkAndCollect(workdir, url, trepo string, count int, 675 repos []string, config testConfig, client *resty.Client, 676 statsCh chan statsRecord, 677 ) []string { 678 func() { 679 start := time.Now() 680 681 var isConnFail, isErr bool 682 683 var statusCode int 684 685 var latency time.Duration 686 687 defer func() { 688 // send a stats record 689 statsCh <- statsRecord{ 690 latency: latency, 691 statusCode: statusCode, 692 isConnFail: isConnFail, 693 isErr: isErr, 694 } 695 }() 696 697 ruid, err := uuid.NewUUID() 698 if err != nil { 699 log.Fatal(err) 700 } 701 702 var repo string 703 704 if trepo != "" { 705 repo = trepo + "/" + ruid.String() 706 } else { 707 repo = ruid.String() 708 } 709 710 repos = append(repos, repo) 711 712 // create a new upload 713 resp, err := client.R(). 714 Post(fmt.Sprintf("%s/v2/%s/blobs/uploads/", url, repo)) 715 716 latency = time.Since(start) 717 718 if err != nil { 719 isConnFail = true 720 721 return 722 } 723 724 // request specific check 725 statusCode = resp.StatusCode() 726 if statusCode != http.StatusAccepted { 727 isErr = true 728 729 return 730 } 731 732 loc := getLocation(url, resp) 733 734 var size int 735 736 if config.mixedSize { 737 size, _ = getRandomSize(config.probabilityRange) 738 } else { 739 size = config.size 740 } 741 742 blob := path.Join(workdir, fmt.Sprintf("%d.blob", size)) 743 744 fhandle, err := os.OpenFile(blob, os.O_RDONLY, defaultFilePerms) 745 if err != nil { 746 isConnFail = true 747 748 return 749 } 750 751 defer fhandle.Close() 752 753 digest := blobHash[blob] 754 755 // upload blob 756 resp, err = client.R(). 757 SetContentLength(true). 758 SetHeader("Content-Type", "application/octet-stream"). 759 SetBody(fhandle). 760 Patch(loc) 761 762 latency = time.Since(start) 763 764 if err != nil { 765 isConnFail = true 766 767 return 768 } 769 770 loc = getLocation(url, resp) 771 772 // request specific check 773 statusCode = resp.StatusCode() 774 if statusCode != http.StatusAccepted { 775 isErr = true 776 777 return 778 } 779 780 // finish upload 781 resp, err = client.R(). 782 SetContentLength(true). 783 SetHeader("Content-Length", fmt.Sprintf("%d", size)). 784 SetHeader("Content-Type", "application/octet-stream"). 785 SetQueryParam("digest", digest.String()). 786 Put(loc) 787 788 latency = time.Since(start) 789 790 if err != nil { 791 isConnFail = true 792 793 return 794 } 795 796 // request specific check 797 statusCode = resp.StatusCode() 798 if statusCode != http.StatusCreated { 799 isErr = true 800 801 return 802 } 803 804 // upload image config blob 805 resp, err = client.R(). 806 Post(fmt.Sprintf("%s/v2/%s/blobs/uploads/", url, repo)) 807 808 latency = time.Since(start) 809 810 if err != nil { 811 isConnFail = true 812 813 return 814 } 815 816 // request specific check 817 statusCode = resp.StatusCode() 818 if statusCode != http.StatusAccepted { 819 isErr = true 820 821 return 822 } 823 824 loc = getLocation(url, resp) 825 cblob, cdigest := getImageConfig() 826 resp, err = client.R(). 827 SetContentLength(true). 828 SetHeader("Content-Type", "application/octet-stream"). 829 SetBody(fhandle). 830 Patch(loc) 831 832 if err != nil { 833 isConnFail = true 834 835 return 836 } 837 838 // request specific check 839 statusCode = resp.StatusCode() 840 if statusCode != http.StatusAccepted { 841 isErr = true 842 843 return 844 } 845 846 // upload blob 847 resp, err = client.R(). 848 SetContentLength(true). 849 SetHeader("Content-Type", "application/octet-stream"). 850 SetBody(cblob). 851 Patch(loc) 852 853 latency = time.Since(start) 854 855 if err != nil { 856 isConnFail = true 857 858 return 859 } 860 861 loc = getLocation(url, resp) 862 863 // request specific check 864 statusCode = resp.StatusCode() 865 if statusCode != http.StatusAccepted { 866 isErr = true 867 868 return 869 } 870 871 // finish upload 872 resp, err = client.R(). 873 SetContentLength(true). 874 SetHeader("Content-Length", fmt.Sprintf("%d", len(cblob))). 875 SetHeader("Content-Type", "application/octet-stream"). 876 SetQueryParam("digest", cdigest.String()). 877 Put(loc) 878 879 latency = time.Since(start) 880 881 if err != nil { 882 isConnFail = true 883 884 return 885 } 886 887 // request specific check 888 statusCode = resp.StatusCode() 889 if statusCode != http.StatusCreated { 890 isErr = true 891 892 return 893 } 894 895 // create a manifest 896 manifest := ispec.Manifest{ 897 Versioned: imeta.Versioned{ 898 SchemaVersion: defaultSchemaVersion, 899 }, 900 Config: ispec.Descriptor{ 901 MediaType: "application/vnd.oci.image.config.v1+json", 902 Digest: cdigest, 903 Size: int64(len(cblob)), 904 }, 905 Layers: []ispec.Descriptor{ 906 { 907 MediaType: "application/vnd.oci.image.layer.v1.tar", 908 Digest: digest, 909 Size: int64(size), 910 }, 911 }, 912 } 913 914 content, err := json.Marshal(manifest) 915 if err != nil { 916 log.Fatal(err) 917 } 918 919 manifestTag := fmt.Sprintf("tag%d", count) 920 921 // finish upload 922 resp, err = client.R(). 923 SetContentLength(true). 924 SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 925 SetBody(content). 926 Put(fmt.Sprintf("%s/v2/%s/manifests/%s", url, repo, manifestTag)) 927 928 latency = time.Since(start) 929 930 if err != nil { 931 isConnFail = true 932 933 return 934 } 935 936 // request specific check 937 statusCode = resp.StatusCode() 938 if statusCode != http.StatusCreated { 939 isErr = true 940 941 return 942 } 943 }() 944 945 return repos 946 } 947 948 func getRandomSize(probabilityRange []float64) (int, int) { 949 var size int 950 951 idx := flipFunc(probabilityRange) 952 smallSizeIdx := 0 953 mediumSizeIdx := 1 954 largeSizeIdx := 2 955 956 switch idx { 957 case smallSizeIdx: 958 size = smallBlob 959 current := loadOrStore(&statusRequests, "1MB", 0) 960 statusRequests.Store("1MB", current+1) 961 case mediumSizeIdx: 962 size = mediumBlob 963 current := loadOrStore(&statusRequests, "10MB", 0) 964 statusRequests.Store("10MB", current+1) 965 case largeSizeIdx: 966 size = largeBlob 967 current := loadOrStore(&statusRequests, "100MB", 0) 968 statusRequests.Store("100MB", current+1) 969 default: 970 size = 0 971 } 972 973 return size, idx 974 } 975 976 //nolint:gosec 977 func flipFunc(probabilityRange []float64) int { 978 seed := time.Now().UTC().UnixNano() 979 mrand := rand.New(rand.NewSource(seed)) 980 toss := mrand.Float64() 981 982 for idx, r := range probabilityRange { 983 if toss < r { 984 return idx 985 } 986 } 987 988 return len(probabilityRange) - 1 989 } 990 991 // pbty - probabilities. 992 func normalizeProbabilityRange(pbty []float64) []float64 { 993 dim := len(pbty) 994 995 // npd - normalized probability density 996 npd := make([]float64, dim) 997 998 for idx := range pbty { 999 npd[idx] = 0.0 1000 } 1001 1002 // [0.2, 0.7, 0.1] -> [0.2, 0.9, 1] 1003 npd[0] = pbty[0] 1004 for i := 1; i < dim; i++ { 1005 npd[i] = npd[i-1] + pbty[i] 1006 } 1007 1008 return npd 1009 } 1010 1011 func loadOrStore(statusRequests *sync.Map, key string, value int) int { //nolint:unparam 1012 val, _ := statusRequests.LoadOrStore(key, value) 1013 1014 intValue, ok := val.(int) 1015 if !ok { 1016 log.Fatalf("invalid type: %#v, should be int", val) 1017 } 1018 1019 return intValue 1020 } 1021 1022 func getImageConfig() ([]byte, godigest.Digest) { 1023 createdTime := time.Date(2011, time.Month(1), 1, 1, 1, 1, 0, time.UTC) 1024 1025 config := ispec.Image{ 1026 Created: &createdTime, 1027 Author: "ZotUser", 1028 Platform: ispec.Platform{ 1029 OS: "linux", 1030 Architecture: "amd64", 1031 }, 1032 RootFS: ispec.RootFS{ 1033 Type: "layers", 1034 DiffIDs: []godigest.Digest{}, 1035 }, 1036 } 1037 1038 configBlobContent, err := json.MarshalIndent(&config, "", "\t") 1039 if err != nil { 1040 log.Fatal(err) 1041 } 1042 1043 configBlobDigestRaw := godigest.FromBytes(configBlobContent) 1044 1045 return configBlobContent, configBlobDigestRaw 1046 } 1047 1048 func getLocation(baseURL string, resp *resty.Response) string { 1049 // For some API responses, the Location header is set and is supposed to 1050 // indicate an opaque value. However, it is not clear if this value is an 1051 // absolute URL (https://server:port/v2/...) or just a path (/v2/...) 1052 // zot implements the latter as per the spec, but some registries appear to 1053 // return the former - this needs to be clarified 1054 loc := resp.Header().Get("Location") 1055 1056 uloc, err := url.Parse(loc) 1057 if err != nil { 1058 return "" 1059 } 1060 1061 path := uloc.Path 1062 1063 return baseURL + path 1064 }