Skip to content

Commit e0032c4

Browse files
Merge pull request #17020 from legionus/dockerregistry-hard-prune-fix-layer-links
Automatic merge from submit-queue (batch tested with PRs 17020, 17026, 17000, 17010). Dockerregistry hard prune fix layer links Fixes [BZ#1483930](https://bugzilla.redhat.com/show_bug.cgi?id=1483930)
2 parents 951a379 + 9421ecc commit e0032c4

File tree

2 files changed

+150
-41
lines changed

2 files changed

+150
-41
lines changed

pkg/cmd/dockerregistry/dockerregistry.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,15 @@ func ExecutePruner(configFile io.Reader, dryRun bool) {
104104
log.Fatalf("error creating registry: %s", err)
105105
}
106106

107-
stats, err := prune.Prune(ctx, storageDriver, registry, registryClient, dryRun)
107+
var pruner prune.Pruner
108+
109+
if dryRun {
110+
pruner = &prune.DryRunPruner{}
111+
} else {
112+
pruner = &prune.RegistryPruner{storageDriver}
113+
}
114+
115+
stats, err := prune.Prune(ctx, registry, registryClient, pruner)
108116
if err != nil {
109117
log.Error(err)
110118
}

pkg/dockerregistry/server/prune/prune.go

Lines changed: 141 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,132 @@ import (
1919
imageapiv1 "github.com/openshift/origin/pkg/image/apis/image/v1"
2020
)
2121

22+
// Pruner defines a common set of operations for pruning
23+
type Pruner interface {
24+
DeleteRepository(ctx context.Context, reponame string) error
25+
DeleteManifestLink(ctx context.Context, svc distribution.ManifestService, reponame string, dgst digest.Digest) error
26+
DeleteBlob(ctx context.Context, dgst digest.Digest) error
27+
}
28+
29+
// DryRunPruner prints information about each object that going to remove.
30+
type DryRunPruner struct{}
31+
32+
var _ Pruner = &DryRunPruner{}
33+
34+
func (p *DryRunPruner) DeleteRepository(ctx context.Context, reponame string) error {
35+
logger := context.GetLogger(ctx)
36+
logger.Printf("Would delete repository: %s", reponame)
37+
return nil
38+
}
39+
40+
func (p *DryRunPruner) DeleteManifestLink(ctx context.Context, svc distribution.ManifestService, reponame string, dgst digest.Digest) error {
41+
logger := context.GetLogger(ctx)
42+
logger.Printf("Would delete manifest link: %s@%s", reponame, dgst)
43+
return nil
44+
}
45+
46+
func (p *DryRunPruner) DeleteBlob(ctx context.Context, dgst digest.Digest) error {
47+
logger := context.GetLogger(ctx)
48+
logger.Printf("Would delete blob: %s", dgst)
49+
return nil
50+
}
51+
52+
// RegistryPruner deletes objects.
53+
type RegistryPruner struct {
54+
StorageDriver driver.StorageDriver
55+
}
56+
57+
var _ Pruner = &RegistryPruner{}
58+
59+
// DeleteRepository removes a repository directory from the storage
60+
func (p *RegistryPruner) DeleteRepository(ctx context.Context, reponame string) error {
61+
vacuum := storage.NewVacuum(ctx, p.StorageDriver)
62+
63+
// Log message will be generated by RemoveRepository with loglevel=info.
64+
if err := vacuum.RemoveRepository(reponame); err != nil {
65+
return fmt.Errorf("unable to remove the repository %s: %v", reponame, err)
66+
}
67+
68+
return nil
69+
}
70+
71+
// DeleteManifestLink removes a manifest link from the storage
72+
func (p *RegistryPruner) DeleteManifestLink(ctx context.Context, svc distribution.ManifestService, reponame string, dgst digest.Digest) error {
73+
logger := context.GetLogger(ctx)
74+
75+
logger.Printf("Deleting manifest link: %s@%s", reponame, dgst)
76+
if err := svc.Delete(ctx, dgst); err != nil {
77+
return fmt.Errorf("failed to delete the manifest link %s@%s: %v", reponame, dgst, err)
78+
}
79+
80+
return nil
81+
}
82+
83+
// DeleteBlob removes a blob from the storage
84+
func (p *RegistryPruner) DeleteBlob(ctx context.Context, dgst digest.Digest) error {
85+
vacuum := storage.NewVacuum(ctx, p.StorageDriver)
86+
87+
// Log message will be generated by RemoveBlob with loglevel=info.
88+
if err := vacuum.RemoveBlob(string(dgst)); err != nil {
89+
return fmt.Errorf("failed to delete the blob %s: %v", dgst, err)
90+
}
91+
92+
return nil
93+
}
94+
95+
// garbageCollector holds objects for later deletion. If the object is replaced,
96+
// then the previous one will be deleted.
97+
type garbageCollector struct {
98+
Pruner Pruner
99+
Ctx context.Context
100+
101+
repoName string
102+
103+
manifestService distribution.ManifestService
104+
manifestRepo string
105+
manifestLink digest.Digest
106+
}
107+
108+
func (gc *garbageCollector) AddRepository(repoName string) error {
109+
// If the place is occupied, then it is necessary to clean it.
110+
if err := gc.Collect(); err != nil {
111+
return err
112+
}
113+
114+
gc.repoName = repoName
115+
116+
return nil
117+
}
118+
119+
func (gc *garbageCollector) AddManifestLink(svc distribution.ManifestService, repoName string, dgst digest.Digest) error {
120+
// If the place is occupied, then it is necessary to clean it.
121+
if err := gc.Collect(); err != nil {
122+
return err
123+
}
124+
125+
gc.manifestService = svc
126+
gc.manifestRepo = repoName
127+
gc.manifestLink = dgst
128+
129+
return nil
130+
}
131+
132+
func (gc *garbageCollector) Collect() error {
133+
if len(gc.manifestLink) > 0 {
134+
if err := gc.Pruner.DeleteManifestLink(gc.Ctx, gc.manifestService, gc.manifestRepo, gc.manifestLink); err != nil {
135+
return err
136+
}
137+
gc.manifestLink = ""
138+
}
139+
if len(gc.repoName) > 0 {
140+
if err := gc.Pruner.DeleteRepository(gc.Ctx, gc.repoName); err != nil {
141+
return err
142+
}
143+
gc.repoName = ""
144+
}
145+
return nil
146+
}
147+
22148
func imageStreamHasManifestDigest(is *imageapiv1.ImageStream, dgst digest.Digest) bool {
23149
for _, tagEventList := range is.Status.Tags {
24150
for _, tagEvent := range tagEventList.Items {
@@ -42,7 +168,7 @@ type Summary struct {
42168
//
43169
// TODO(dmage): remove layer links to a blob if the blob is removed or it doesn't belong to the ImageStream.
44170
// TODO(dmage): keep young blobs (docker/distribution#2297).
45-
func Prune(ctx context.Context, storageDriver driver.StorageDriver, registry distribution.Namespace, registryClient client.RegistryClient, dryRun bool) (Summary, error) {
171+
func Prune(ctx context.Context, registry distribution.Namespace, registryClient client.RegistryClient, pruner Pruner) (Summary, error) {
46172
logger := context.GetLogger(ctx)
47173

48174
repositoryEnumerator, ok := registry.(distribution.RepositoryEnumerator)
@@ -84,7 +210,15 @@ func Prune(ctx context.Context, storageDriver driver.StorageDriver, registry dis
84210

85211
var stats Summary
86212

87-
var reposToDelete []string
213+
// The Enumerate calls a Stat() on each file or directory in the tree before call our handler.
214+
// Therefore, we can not delete subdirectories from the handler. On some types of storage (S3),
215+
// this can lead to an error in the Enumerate.
216+
// We are waiting for the completion of our handler and perform deferred deletion of objects.
217+
gc := &garbageCollector{
218+
Ctx: ctx,
219+
Pruner: pruner,
220+
}
221+
88222
err = repositoryEnumerator.Enumerate(ctx, func(repoName string) error {
89223
logger.Debugln("Processing repository", repoName)
90224

@@ -101,11 +235,7 @@ func Prune(ctx context.Context, storageDriver driver.StorageDriver, registry dis
101235
is, err := oc.ImageStreams(ref.Namespace).Get(ref.Name, metav1.GetOptions{})
102236
if kerrors.IsNotFound(err) {
103237
logger.Printf("The image stream %s/%s is not found, will remove the whole repository", ref.Namespace, ref.Name)
104-
105-
// We cannot delete the repository at this point, because it would break Enumerate.
106-
reposToDelete = append(reposToDelete, repoName)
107-
108-
return nil
238+
return gc.AddRepository(repoName)
109239
} else if err != nil {
110240
return fmt.Errorf("failed to get the image stream %s: %v", repoName, err)
111241
}
@@ -131,17 +261,7 @@ func Prune(ctx context.Context, storageDriver driver.StorageDriver, registry dis
131261
return nil
132262
}
133263

134-
if dryRun {
135-
logger.Printf("Would delete manifest link: %s@%s", repoName, dgst)
136-
return nil
137-
}
138-
139-
logger.Printf("Deleting manifest link: %s@%s", repoName, dgst)
140-
if err := manifestService.Delete(ctx, dgst); err != nil {
141-
return fmt.Errorf("failed to delete the manifest link %s@%s: %v", repoName, dgst, err)
142-
}
143-
144-
return nil
264+
return gc.AddManifestLink(manifestService, repoName, dgst)
145265
})
146266
if e, ok := err.(driver.PathNotFoundError); ok {
147267
logger.Printf("Skipped manifest link pruning for the repository %s: %v", repoName, e)
@@ -158,18 +278,8 @@ func Prune(ctx context.Context, storageDriver driver.StorageDriver, registry dis
158278
return stats, err
159279
}
160280

161-
vacuum := storage.NewVacuum(ctx, storageDriver)
162-
163-
logger.Debugln("Removing repositories")
164-
for _, repoName := range reposToDelete {
165-
if dryRun {
166-
logger.Printf("Would delete repository: %s", repoName)
167-
continue
168-
}
169-
170-
if err = vacuum.RemoveRepository(repoName); err != nil {
171-
return stats, fmt.Errorf("unable to remove the repository %s: %v", repoName, err)
172-
}
281+
if err := gc.Collect(); err != nil {
282+
return stats, err
173283
}
174284

175285
logger.Debugln("Processing blobs")
@@ -188,16 +298,7 @@ func Prune(ctx context.Context, storageDriver driver.StorageDriver, registry dis
188298
stats.Blobs++
189299
stats.DiskSpace += desc.Size
190300

191-
if dryRun {
192-
logger.Printf("Would delete blob: %s", dgst)
193-
return nil
194-
}
195-
196-
if err := vacuum.RemoveBlob(string(dgst)); err != nil {
197-
return fmt.Errorf("failed to delete the blob %s: %v", dgst, err)
198-
}
199-
200-
return nil
301+
return pruner.DeleteBlob(ctx, dgst)
201302
})
202303
return stats, err
203304
}

0 commit comments

Comments
 (0)