Skip to content

Commit 79afcb3

Browse files
committed
Refactor hard prune
Signed-off-by: Gladkov Alexey <[email protected]>
1 parent 337ee95 commit 79afcb3

File tree

2 files changed

+151
-41
lines changed

2 files changed

+151
-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: 142 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,133 @@ 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+
logger := context.GetLogger(ctx)
62+
vacuum := storage.NewVacuum(ctx, p.StorageDriver)
63+
64+
// Log message will be generated by RemoveRepository with loglevel=info.
65+
if err := vacuum.RemoveRepository(reponame); err != nil {
66+
return fmt.Errorf("unable to remove the repository %s: %v", reponame, err)
67+
}
68+
69+
return nil
70+
}
71+
72+
// DeleteManifestLink removes a manifest link from the storage
73+
func (p *RegistryPruner) DeleteManifestLink(ctx context.Context, svc distribution.ManifestService, reponame string, dgst digest.Digest) error {
74+
logger := context.GetLogger(ctx)
75+
76+
logger.Printf("Deleting manifest link: %s@%s", reponame, dgst)
77+
if err := svc.Delete(ctx, dgst); err != nil {
78+
return fmt.Errorf("failed to delete the manifest link %s@%s: %v", reponame, dgst, err)
79+
}
80+
81+
return nil
82+
}
83+
84+
// DeleteBlob removes a blob from the storage
85+
func (p *RegistryPruner) DeleteBlob(ctx context.Context, dgst digest.Digest) error {
86+
vacuum := storage.NewVacuum(ctx, p.StorageDriver)
87+
88+
// Log message will be generated by RemoveBlob with loglevel=info.
89+
if err := vacuum.RemoveBlob(string(dgst)); err != nil {
90+
return fmt.Errorf("failed to delete the blob %s: %v", dgst, err)
91+
}
92+
93+
return nil
94+
}
95+
96+
// garbageCollector holds objects for later deletion. If the object is replaced,
97+
// then the previous one will be deleted.
98+
type garbageCollector struct {
99+
Pruner Pruner
100+
Ctx context.Context
101+
102+
repoName string
103+
104+
manifestService distribution.ManifestService
105+
manifestRepo string
106+
manifestLink digest.Digest
107+
}
108+
109+
func (gc *garbageCollector) AddRepository(repoName string) error {
110+
// If the place is occupied, then it is necessary to clean it.
111+
if err := gc.Collect(); err != nil {
112+
return err
113+
}
114+
115+
gc.repoName = repoName
116+
117+
return nil
118+
}
119+
120+
func (gc *garbageCollector) AddManifestLink(svc distribution.ManifestService, repoName string, dgst digest.Digest) error {
121+
// If the place is occupied, then it is necessary to clean it.
122+
if err := gc.Collect(); err != nil {
123+
return err
124+
}
125+
126+
gc.manifestService = svc
127+
gc.manifestRepo = repoName
128+
gc.manifestLink = dgst
129+
130+
return nil
131+
}
132+
133+
func (gc *garbageCollector) Collect() error {
134+
if len(gc.manifestLink) > 0 {
135+
if err := gc.Pruner.DeleteManifestLink(gc.Ctx, gc.manifestService, gc.manifestRepo, gc.manifestLink); err != nil {
136+
return err
137+
}
138+
gc.manifestLink = ""
139+
}
140+
if len(gc.repoName) > 0 {
141+
if err := gc.Pruner.DeleteRepository(gc.Ctx, gc.repoName); err != nil {
142+
return err
143+
}
144+
gc.repoName = ""
145+
}
146+
return nil
147+
}
148+
22149
func imageStreamHasManifestDigest(is *imageapiv1.ImageStream, dgst digest.Digest) bool {
23150
for _, tagEventList := range is.Status.Tags {
24151
for _, tagEvent := range tagEventList.Items {
@@ -42,7 +169,7 @@ type Summary struct {
42169
//
43170
// TODO(dmage): remove layer links to a blob if the blob is removed or it doesn't belong to the ImageStream.
44171
// 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) {
172+
func Prune(ctx context.Context, registry distribution.Namespace, registryClient client.RegistryClient, pruner Pruner) (Summary, error) {
46173
logger := context.GetLogger(ctx)
47174

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

85212
var stats Summary
86213

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

@@ -101,11 +236,7 @@ func Prune(ctx context.Context, storageDriver driver.StorageDriver, registry dis
101236
is, err := oc.ImageStreams(ref.Namespace).Get(ref.Name, metav1.GetOptions{})
102237
if kerrors.IsNotFound(err) {
103238
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
239+
return gc.AddRepository(repoName)
109240
} else if err != nil {
110241
return fmt.Errorf("failed to get the image stream %s: %v", repoName, err)
111242
}
@@ -131,17 +262,7 @@ func Prune(ctx context.Context, storageDriver driver.StorageDriver, registry dis
131262
return nil
132263
}
133264

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
265+
return gc.AddManifestLink(manifestService, repoName, dgst)
145266
})
146267
if e, ok := err.(driver.PathNotFoundError); ok {
147268
logger.Printf("Skipped manifest link pruning for the repository %s: %v", repoName, e)
@@ -158,18 +279,8 @@ func Prune(ctx context.Context, storageDriver driver.StorageDriver, registry dis
158279
return stats, err
159280
}
160281

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-
}
282+
if err := gc.Collect(); err != nil {
283+
return stats, err
173284
}
174285

175286
logger.Debugln("Processing blobs")
@@ -188,16 +299,7 @@ func Prune(ctx context.Context, storageDriver driver.StorageDriver, registry dis
188299
stats.Blobs++
189300
stats.DiskSpace += desc.Size
190301

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
302+
return pruner.DeleteBlob(ctx, dgst)
201303
})
202304
return stats, err
203305
}

0 commit comments

Comments
 (0)