// Delete deletes the blob from the storage backend. func (bh *blobHandler) Delete(w http.ResponseWriter, req *http.Request) { defer req.Body.Close() if len(bh.Digest) == 0 { bh.Errors = append(bh.Errors, v2.ErrorCodeBlobUnknown) return } vacuum := storage.NewVacuum(bh.Context, dockerStorageDriver) err := vacuum.RemoveBlob(bh.Digest.String()) if err != nil { // ignore not found error switch t := err.(type) { case storagedriver.PathNotFoundError: case errcode.Error: if t.Code != v2.ErrorCodeBlobUnknown { bh.Errors = append(bh.Errors, err) return } default: if err != distribution.ErrBlobUnknown { detail := fmt.Sprintf("error deleting blob %q: %v", bh.Digest, err) err = errcode.ErrorCodeUnknown.WithDetail(detail) bh.Errors = append(bh.Errors, err) return } } context.GetLogger(bh).Infof("blobHandler: ignoring %T error: %v", err, err) } w.WriteHeader(http.StatusNoContent) }
// NewRegistryPullThroughCache creates a registry acting as a pull through cache func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Namespace, driver driver.StorageDriver, config configuration.Proxy) (distribution.Namespace, error) { _, err := url.Parse(config.RemoteURL) if err != nil { return nil, err } v := storage.NewVacuum(ctx, driver) s := scheduler.New(ctx, driver, "/scheduler-state.json") s.OnBlobExpire(func(digest string) error { return v.RemoveBlob(digest) }) s.OnManifestExpire(func(repoName string) error { return v.RemoveRepository(repoName) }) err = s.Start() if err != nil { return nil, err } challengeManager := auth.NewSimpleChallengeManager() cs, err := ConfigureAuth(config.RemoteURL, config.Username, config.Password, challengeManager) if err != nil { return nil, err } return &proxyingRegistry{ embedded: registry, scheduler: s, challengeManager: challengeManager, credentialStore: cs, remoteURL: config.RemoteURL, }, nil }
// NewRegistryPullThroughCache creates a registry acting as a pull through cache func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Namespace, driver driver.StorageDriver, config configuration.Proxy) (distribution.Namespace, error) { remoteURL, err := url.Parse(config.RemoteURL) if err != nil { return nil, err } v := storage.NewVacuum(ctx, driver) s := scheduler.New(ctx, driver, "/scheduler-state.json") s.OnBlobExpire(func(ref reference.Reference) error { var r reference.Canonical var ok bool if r, ok = ref.(reference.Canonical); !ok { return fmt.Errorf("unexpected reference type : %T", ref) } repo, err := registry.Repository(ctx, r) if err != nil { return err } blobs := repo.Blobs(ctx) // Clear the repository reference and descriptor caches err = blobs.Delete(ctx, r.Digest()) if err != nil { return err } err = v.RemoveBlob(r.Digest().String()) if err != nil { return err } return nil }) s.OnManifestExpire(func(ref reference.Reference) error { var r reference.Canonical var ok bool if r, ok = ref.(reference.Canonical); !ok { return fmt.Errorf("unexpected reference type : %T", ref) } repo, err := registry.Repository(ctx, r) if err != nil { return err } manifests, err := repo.Manifests(ctx) if err != nil { return err } err = manifests.Delete(ctx, r.Digest()) if err != nil { return err } return nil }) err = s.Start() if err != nil { return nil, err } cs, err := configureAuth(config.Username, config.Password, config.RemoteURL) if err != nil { return nil, err } return &proxyingRegistry{ embedded: registry, scheduler: s, remoteURL: *remoteURL, authChallenger: &remoteAuthChallenger{ remoteURL: *remoteURL, cm: auth.NewSimpleChallengeManager(), cs: cs, }, }, nil }
func TestRepositoryBlobStatCacheEviction(t *testing.T) { const blobRepoCacheTTL = time.Millisecond * 500 quotaEnforcing = "aEnforcingConfig{} ctx := WithAuthPerformed(context.Background()) // this driver holds all the testing blobs in memory during the whole test run driver := inmemory.New() // generate two images and store their blobs in the driver testImages, err := populateTestStorage(t, driver, true, 1, map[string]int{"nm/is:latest": 1}, nil) if err != nil { t.Fatal(err) } testImage := testImages["nm/is:latest"][0] testImageStream := registrytest.TestNewImageStreamObject("nm", "is", "latest", testImage.Name, "") blob1Desc := testNewDescriptorForLayer(testImage.DockerImageLayers[0]) blob1Dgst := blob1Desc.Digest blob2Desc := testNewDescriptorForLayer(testImage.DockerImageLayers[1]) blob2Dgst := blob2Desc.Digest // remove repo layer repo link of the image's second blob alg, hex := blob2Dgst.Algorithm(), blob2Dgst.Hex() err = driver.Delete(ctx, fmt.Sprintf("/docker/registry/v2/repositories/%s/_layers/%s/%s", "nm/is", alg, hex)) cachedLayers, err = newDigestToRepositoryCache(defaultDigestToRepositoryCacheSize) if err != nil { t.Fatal(err) } client := &testclient.Fake{} client.AddReactor("get", "imagestreams", imagetest.GetFakeImageStreamGetHandler(t, *testImageStream)) client.AddReactor("get", "images", registrytest.GetFakeImageGetHandler(t, *testImage)) reg, err := newTestRegistry(ctx, client, driver, blobRepoCacheTTL, false, false) if err != nil { t.Fatalf("unexpected error: %v", err) } ref, err := reference.ParseNamed("nm/is") if err != nil { t.Errorf("failed to parse blob reference %q: %v", "nm/is", err) } repo, err := reg.Repository(ctx, ref) if err != nil { t.Fatalf("unexpected error: %v", err) } // hit the layer repo link - cache the result desc, err := repo.Blobs(ctx).Stat(ctx, blob1Dgst) if err != nil { t.Fatalf("got unexpected stat error: %v", err) } if !reflect.DeepEqual(desc, blob1Desc) { t.Fatalf("got unexpected descriptor: %#+v != %#+v", desc, blob1Desc) } compareActions(t, "no actions expected", client.Actions(), []clientAction{}) // remove layer repo link, delete the association from cache as well err = repo.Blobs(ctx).Delete(ctx, blob1Dgst) if err != nil { t.Fatalf("got unexpected error: %v", err) } // query etcd desc, err = repo.Blobs(ctx).Stat(ctx, blob1Dgst) if err != nil { t.Fatalf("got unexpected stat error: %v", err) } if !reflect.DeepEqual(desc, blob1Desc) { t.Fatalf("got unexpected descriptor: %#+v != %#+v", desc, blob1Desc) } expectedActions := []clientAction{{"get", "imagestreams"}, {"get", "images"}} compareActions(t, "1st roundtrip to etcd", client.Actions(), expectedActions) // remove the underlying blob vacuum := storage.NewVacuum(ctx, driver) err = vacuum.RemoveBlob(blob1Dgst.String()) if err != nil { t.Fatalf("got unexpected error: %v", err) } // fail because the blob isn't stored locally desc, err = repo.Blobs(ctx).Stat(ctx, blob1Dgst) if err == nil { t.Fatalf("got unexpected non error: %v", err) } if err != distribution.ErrBlobUnknown { t.Fatalf("got unexpected error: %#+v", err) } // cache hit - don't query etcd desc, err = repo.Blobs(ctx).Stat(ctx, blob2Dgst) if err != nil { t.Fatalf("got unexpected stat error: %v", err) } if !reflect.DeepEqual(desc, blob2Desc) { t.Fatalf("got unexpected descriptor: %#+v != %#+v", desc, blob2Desc) } compareActions(t, "no etcd query", client.Actions(), expectedActions) lastStatTimestamp := time.Now() // hit the cache desc, err = repo.Blobs(ctx).Stat(ctx, blob2Dgst) if err != nil { t.Fatalf("got unexpected stat error: %v", err) } if !reflect.DeepEqual(desc, blob2Desc) { t.Fatalf("got unexpected descriptor: %#+v != %#+v", desc, blob2Desc) } // cache hit - no additional etcd query compareActions(t, "no roundrip to etcd", client.Actions(), expectedActions) t.Logf("sleeping %s while waiting for eviction of blob %q from cache", blobRepoCacheTTL.String(), blob2Dgst.String()) time.Sleep(blobRepoCacheTTL - (time.Now().Sub(lastStatTimestamp))) desc, err = repo.Blobs(ctx).Stat(ctx, blob2Dgst) if err != nil { t.Fatalf("got unexpected stat error: %v", err) } if !reflect.DeepEqual(desc, blob2Desc) { t.Fatalf("got unexpected descriptor: %#+v != %#+v", desc, blob2Desc) } expectedActions = append(expectedActions, []clientAction{{"get", "imagestreams"}, {"get", "images"}}...) compareActions(t, "2nd roundtrip to etcd", client.Actions(), expectedActions) err = vacuum.RemoveBlob(blob2Dgst.String()) if err != nil { t.Fatalf("got unexpected error: %v", err) } // fail because the blob isn't stored locally desc, err = repo.Blobs(ctx).Stat(ctx, blob2Dgst) if err == nil { t.Fatalf("got unexpected non error: %v", err) } if err != distribution.ErrBlobUnknown { t.Fatalf("got unexpected error: %#+v", err) } }
func markAndSweep(ctx context.Context, storageDriver driver.StorageDriver, registry distribution.Namespace) error { repositoryEnumerator, ok := registry.(distribution.RepositoryEnumerator) if !ok { return fmt.Errorf("unable to convert Namespace to RepositoryEnumerator") } // mark markSet := make(map[digest.Digest]struct{}) err := repositoryEnumerator.Enumerate(ctx, func(repoName string) error { emit(repoName) var err error named, err := reference.ParseNamed(repoName) if err != nil { return fmt.Errorf("failed to parse repo name %s: %v", repoName, err) } repository, err := registry.Repository(ctx, named) if err != nil { return fmt.Errorf("failed to construct repository: %v", err) } manifestService, err := repository.Manifests(ctx) if err != nil { return fmt.Errorf("failed to construct manifest service: %v", err) } manifestEnumerator, ok := manifestService.(distribution.ManifestEnumerator) if !ok { return fmt.Errorf("unable to convert ManifestService into ManifestEnumerator") } err = manifestEnumerator.Enumerate(ctx, func(dgst digest.Digest) error { // Mark the manifest's blob emit("%s: marking manifest %s ", repoName, dgst) markSet[dgst] = struct{}{} manifest, err := manifestService.Get(ctx, dgst) if err != nil { return fmt.Errorf("failed to retrieve manifest for digest %v: %v", dgst, err) } descriptors := manifest.References() for _, descriptor := range descriptors { markSet[descriptor.Digest] = struct{}{} emit("%s: marking blob %s", repoName, descriptor.Digest) } switch manifest.(type) { case *schema1.SignedManifest: signaturesGetter, ok := manifestService.(distribution.SignaturesGetter) if !ok { return fmt.Errorf("unable to convert ManifestService into SignaturesGetter") } signatures, err := signaturesGetter.GetSignatures(ctx, dgst) if err != nil { return fmt.Errorf("failed to get signatures for signed manifest: %v", err) } for _, signatureDigest := range signatures { emit("%s: marking signature %s", repoName, signatureDigest) markSet[signatureDigest] = struct{}{} } break case *schema2.DeserializedManifest: config := manifest.(*schema2.DeserializedManifest).Config emit("%s: marking configuration %s", repoName, config.Digest) markSet[config.Digest] = struct{}{} break } return nil }) return err }) if err != nil { return fmt.Errorf("failed to mark: %v\n", err) } // sweep blobService := registry.Blobs() deleteSet := make(map[digest.Digest]struct{}) err = blobService.Enumerate(ctx, func(dgst digest.Digest) error { // check if digest is in markSet. If not, delete it! if _, ok := markSet[dgst]; !ok { deleteSet[dgst] = struct{}{} } return nil }) if err != nil { return fmt.Errorf("error enumerating blobs: %v", err) } emit("\n%d blobs marked, %d blobs eligible for deletion", len(markSet), len(deleteSet)) // Construct vacuum vacuum := storage.NewVacuum(ctx, storageDriver) for dgst := range deleteSet { emit("blob eligible for deletion: %s", dgst) if dryRun { continue } err = vacuum.RemoveBlob(string(dgst)) if err != nil { return fmt.Errorf("failed to delete blob %s: %v\n", dgst, err) } } return err }
func markAndSweep(storageDriver driver.StorageDriver) error { ctx := context.Background() // Construct a registry registry, err := storage.NewRegistry(ctx, storageDriver) if err != nil { return fmt.Errorf("failed to construct registry: %v", err) } repositoryEnumerator, ok := registry.(distribution.RepositoryEnumerator) if !ok { return fmt.Errorf("coercion error: unable to convert Namespace to RepositoryEnumerator") } // mark markSet := make(map[digest.Digest]struct{}) err = repositoryEnumerator.Enumerate(ctx, func(repoName string) error { var err error named, err := reference.ParseNamed(repoName) if err != nil { return fmt.Errorf("failed to parse repo name %s: %v", repoName, err) } repository, err := registry.Repository(ctx, named) if err != nil { return fmt.Errorf("failed to construct repository: %v", err) } manifestService, err := repository.Manifests(ctx) if err != nil { return fmt.Errorf("failed to construct manifest service: %v", err) } manifestEnumerator, ok := manifestService.(distribution.ManifestEnumerator) if !ok { return fmt.Errorf("coercion error: unable to convert ManifestService into ManifestEnumerator") } err = manifestEnumerator.Enumerate(ctx, func(dgst digest.Digest) error { // Mark the manifest's blob markSet[dgst] = struct{}{} manifest, err := manifestService.Get(ctx, dgst) if err != nil { return fmt.Errorf("failed to retrieve manifest for digest %v: %v", dgst, err) } descriptors := manifest.References() for _, descriptor := range descriptors { markSet[descriptor.Digest] = struct{}{} } switch manifest.(type) { case *schema1.SignedManifest: signaturesGetter, ok := manifestService.(distribution.SignaturesGetter) if !ok { return fmt.Errorf("coercion error: unable to convert ManifestSErvice into SignaturesGetter") } signatures, err := signaturesGetter.GetSignatures(ctx, dgst) if err != nil { return fmt.Errorf("failed to get signatures for signed manifest: %v", err) } for _, signatureDigest := range signatures { markSet[signatureDigest] = struct{}{} } break case *schema2.DeserializedManifest: config := manifest.(*schema2.DeserializedManifest).Config markSet[config.Digest] = struct{}{} break } return nil }) return err }) if err != nil { return fmt.Errorf("failed to mark: %v\n", err) } // sweep blobService := registry.Blobs() deleteSet := make(map[digest.Digest]struct{}) err = blobService.Enumerate(ctx, func(dgst digest.Digest) error { // check if digest is in markSet. If not, delete it! if _, ok := markSet[dgst]; !ok { deleteSet[dgst] = struct{}{} } return nil }) // Construct vacuum vacuum := storage.NewVacuum(ctx, storageDriver) for dgst := range deleteSet { err = vacuum.RemoveBlob(string(dgst)) if err != nil { return fmt.Errorf("failed to delete blob %s: %v\n", dgst, err) } } return err }