Skip to content

Commit ddba1b7

Browse files
authored
fix(scrub): hold locks per image not per repo while executing scrub (#2180)
Signed-off-by: Andreea-Lupu <[email protected]>
1 parent 1785688 commit ddba1b7

File tree

3 files changed

+268
-120
lines changed

3 files changed

+268
-120
lines changed

pkg/extensions/scrub/scrub_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,9 @@ func TestScrubExtension(t *testing.T) {
111111
err = WriteImageToFileSystem(image, repoName, "0.0.1", srcStorageCtlr)
112112
So(err, ShouldBeNil)
113113

114-
manifestDigest := image.ManifestDescriptor.Digest
114+
layerDigest := image.Manifest.Layers[0].Digest
115115

116-
err = os.Remove(path.Join(dir, repoName, "blobs/sha256", manifestDigest.Encoded()))
116+
err = os.Remove(path.Join(dir, repoName, "blobs/sha256", layerDigest.Encoded()))
117117
if err != nil {
118118
panic(err)
119119
}
@@ -240,9 +240,9 @@ func TestRunScrubRepo(t *testing.T) {
240240
err = WriteImageToFileSystem(image, repoName, "0.0.1", srcStorageCtlr)
241241
So(err, ShouldBeNil)
242242

243-
manifestDigest := image.ManifestDescriptor.Digest
243+
layerDigest := image.Manifest.Layers[0].Digest
244244

245-
err = os.Remove(path.Join(dir, repoName, "blobs/sha256", manifestDigest.Encoded()))
245+
err = os.Remove(path.Join(dir, repoName, "blobs/sha256", layerDigest.Encoded()))
246246
if err != nil {
247247
panic(err)
248248
}

pkg/storage/scrub.go

+143-93
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package storage
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"io"
89
"strings"
@@ -12,7 +13,7 @@ import (
1213
godigest "github.com/opencontainers/go-digest"
1314
ispec "github.com/opencontainers/image-spec/specs-go/v1"
1415

15-
"zotregistry.io/zot/errors"
16+
zerr "zotregistry.io/zot/errors"
1617
"zotregistry.io/zot/pkg/common"
1718
storageTypes "zotregistry.io/zot/pkg/storage/types"
1819
)
@@ -85,33 +86,20 @@ func CheckImageStoreBlobsIntegrity(ctx context.Context, imgStore storageTypes.Im
8586
return results, nil
8687
}
8788

89+
// CheckRepo is the main entry point for the scrub task
90+
// We aim for eventual consistency (locks, etc) since this task contends with data path.
8891
func CheckRepo(ctx context.Context, imageName string, imgStore storageTypes.ImageStore) ([]ScrubImageResult, error) {
8992
results := []ScrubImageResult{}
9093

91-
var lockLatency time.Time
92-
93-
imgStore.RLock(&lockLatency)
94-
defer imgStore.RUnlock(&lockLatency)
95-
96-
// check image structure / layout
97-
ok, err := imgStore.ValidateRepo(imageName)
98-
if err != nil {
99-
return results, err
100-
}
101-
102-
if !ok {
103-
return results, errors.ErrRepoBadLayout
104-
}
105-
106-
// check "index.json" content
107-
indexContent, err := imgStore.GetIndexContent(imageName)
94+
// getIndex holds the lock
95+
indexContent, err := getIndex(imageName, imgStore)
10896
if err != nil {
10997
return results, err
11098
}
11199

112100
var index ispec.Index
113101
if err := json.Unmarshal(indexContent, &index); err != nil {
114-
return results, errors.ErrRepoNotFound
102+
return results, zerr.ErrRepoNotFound
115103
}
116104

117105
scrubbedManifests := make(map[godigest.Digest]ScrubImageResult)
@@ -122,46 +110,107 @@ func CheckRepo(ctx context.Context, imageName string, imgStore storageTypes.Imag
122110
}
123111

124112
tag := manifest.Annotations[ispec.AnnotationRefName]
125-
scrubManifest(ctx, manifest, imgStore, imageName, tag, scrubbedManifests)
126-
results = append(results, scrubbedManifests[manifest.Digest])
113+
114+
// checkImage holds the lock
115+
layers, err := checkImage(manifest, imgStore, imageName, tag, scrubbedManifests)
116+
if err == nil && len(layers) > 0 {
117+
// CheckLayers doesn't use locks
118+
imgRes := CheckLayers(imageName, tag, layers, imgStore)
119+
scrubbedManifests[manifest.Digest] = imgRes
120+
}
121+
122+
// ignore the manifest if it isn't found
123+
if !errors.Is(err, zerr.ErrManifestNotFound) {
124+
results = append(results, scrubbedManifests[manifest.Digest])
125+
}
127126
}
128127

129128
return results, nil
130129
}
131130

132-
func scrubManifest(
133-
ctx context.Context, manifest ispec.Descriptor, imgStore storageTypes.ImageStore, imageName, tag string,
131+
func checkImage(
132+
manifest ispec.Descriptor, imgStore storageTypes.ImageStore, imageName, tag string,
134133
scrubbedManifests map[godigest.Digest]ScrubImageResult,
135-
) {
134+
) ([]ispec.Descriptor, error) {
135+
var lockLatency time.Time
136+
137+
imgStore.RLock(&lockLatency)
138+
defer imgStore.RUnlock(&lockLatency)
139+
140+
manifestContent, err := imgStore.GetBlobContent(imageName, manifest.Digest)
141+
if err != nil {
142+
// ignore if the manifest is not found(probably it was deleted after we got the list of manifests)
143+
return []ispec.Descriptor{}, zerr.ErrManifestNotFound
144+
}
145+
146+
return scrubManifest(manifest, imgStore, imageName, tag, manifestContent, scrubbedManifests)
147+
}
148+
149+
func getIndex(imageName string, imgStore storageTypes.ImageStore) ([]byte, error) {
150+
var lockLatency time.Time
151+
152+
imgStore.RLock(&lockLatency)
153+
defer imgStore.RUnlock(&lockLatency)
154+
155+
// check image structure / layout
156+
ok, err := imgStore.ValidateRepo(imageName)
157+
if err != nil {
158+
return []byte{}, err
159+
}
160+
161+
if !ok {
162+
return []byte{}, zerr.ErrRepoBadLayout
163+
}
164+
165+
// check "index.json" content
166+
indexContent, err := imgStore.GetIndexContent(imageName)
167+
if err != nil {
168+
return []byte{}, err
169+
}
170+
171+
return indexContent, nil
172+
}
173+
174+
func scrubManifest(
175+
manifest ispec.Descriptor, imgStore storageTypes.ImageStore, imageName, tag string,
176+
manifestContent []byte, scrubbedManifests map[godigest.Digest]ScrubImageResult,
177+
) ([]ispec.Descriptor, error) {
178+
layers := []ispec.Descriptor{}
179+
136180
res, ok := scrubbedManifests[manifest.Digest]
137181
if ok {
138182
scrubbedManifests[manifest.Digest] = newScrubImageResult(imageName, tag, res.Status,
139183
res.AffectedBlob, res.Error)
140184

141-
return
185+
return layers, nil
142186
}
143187

144188
switch manifest.MediaType {
145189
case ispec.MediaTypeImageIndex:
146-
buf, err := imgStore.GetBlobContent(imageName, manifest.Digest)
147-
if err != nil {
148-
imgRes := getResult(imageName, tag, manifest.Digest, errors.ErrBadBlobDigest)
149-
scrubbedManifests[manifest.Digest] = imgRes
150-
151-
return
152-
}
153-
154190
var idx ispec.Index
155-
if err := json.Unmarshal(buf, &idx); err != nil {
156-
imgRes := getResult(imageName, tag, manifest.Digest, errors.ErrBadBlobDigest)
191+
if err := json.Unmarshal(manifestContent, &idx); err != nil {
192+
imgRes := getResult(imageName, tag, manifest.Digest, zerr.ErrBadBlobDigest)
157193
scrubbedManifests[manifest.Digest] = imgRes
158194

159-
return
195+
return layers, err
160196
}
161197

162198
// check all manifests
163199
for _, man := range idx.Manifests {
164-
scrubManifest(ctx, man, imgStore, imageName, tag, scrubbedManifests)
200+
buf, err := imgStore.GetBlobContent(imageName, man.Digest)
201+
if err != nil {
202+
imgRes := getResult(imageName, tag, man.Digest, zerr.ErrBadBlobDigest)
203+
scrubbedManifests[man.Digest] = imgRes
204+
scrubbedManifests[manifest.Digest] = imgRes
205+
206+
return layers, err
207+
}
208+
209+
layersToScrub, err := scrubManifest(man, imgStore, imageName, tag, buf, scrubbedManifests)
210+
211+
if err == nil {
212+
layers = append(layers, layersToScrub...)
213+
}
165214

166215
// if the manifest is affected then this index is also affected
167216
if scrubbedManifests[man.Digest].Error != "" {
@@ -170,115 +219,116 @@ func scrubManifest(
170219
scrubbedManifests[manifest.Digest] = newScrubImageResult(imageName, tag, mRes.Status,
171220
mRes.AffectedBlob, mRes.Error)
172221

173-
return
222+
return layers, err
174223
}
175224
}
176225

177-
// at this point, before starting to check de subject we can consider the index is ok
226+
// at this point, before starting to check the subject we can consider the index is ok
178227
scrubbedManifests[manifest.Digest] = getResult(imageName, tag, "", nil)
179228

180229
// check subject if exists
181230
if idx.Subject != nil {
182-
scrubManifest(ctx, *idx.Subject, imgStore, imageName, tag, scrubbedManifests)
231+
buf, err := imgStore.GetBlobContent(imageName, idx.Subject.Digest)
232+
if err != nil {
233+
imgRes := getResult(imageName, tag, idx.Subject.Digest, zerr.ErrBadBlobDigest)
234+
scrubbedManifests[idx.Subject.Digest] = imgRes
235+
scrubbedManifests[manifest.Digest] = imgRes
236+
237+
return layers, err
238+
}
239+
240+
layersToScrub, err := scrubManifest(*idx.Subject, imgStore, imageName, tag, buf, scrubbedManifests)
241+
242+
if err == nil {
243+
layers = append(layers, layersToScrub...)
244+
}
183245

184246
subjectRes := scrubbedManifests[idx.Subject.Digest]
185247

186248
scrubbedManifests[manifest.Digest] = newScrubImageResult(imageName, tag, subjectRes.Status,
187249
subjectRes.AffectedBlob, subjectRes.Error)
250+
251+
return layers, err
188252
}
253+
254+
return layers, nil
189255
case ispec.MediaTypeImageManifest:
190-
imgRes := CheckIntegrity(ctx, imageName, tag, manifest, imgStore)
191-
scrubbedManifests[manifest.Digest] = imgRes
256+
affectedBlob, man, err := CheckManifestAndConfig(imageName, manifest, manifestContent, imgStore)
257+
if err == nil {
258+
layers = append(layers, man.Layers...)
259+
}
260+
261+
scrubbedManifests[manifest.Digest] = getResult(imageName, tag, affectedBlob, err)
192262

193263
// if integrity ok then check subject if exists
194-
if imgRes.Error == "" {
195-
manifestContent, _ := imgStore.GetBlobContent(imageName, manifest.Digest)
264+
if err == nil && man.Subject != nil {
265+
buf, err := imgStore.GetBlobContent(imageName, man.Subject.Digest)
266+
if err != nil {
267+
imgRes := getResult(imageName, tag, man.Subject.Digest, zerr.ErrBadBlobDigest)
268+
scrubbedManifests[man.Subject.Digest] = imgRes
269+
scrubbedManifests[manifest.Digest] = imgRes
270+
271+
return layers, err
272+
}
196273

197-
var man ispec.Manifest
274+
layersToScrub, err := scrubManifest(*man.Subject, imgStore, imageName, tag, buf, scrubbedManifests)
198275

199-
_ = json.Unmarshal(manifestContent, &man)
276+
if err == nil {
277+
layers = append(layers, layersToScrub...)
278+
}
200279

201-
if man.Subject != nil {
202-
scrubManifest(ctx, *man.Subject, imgStore, imageName, tag, scrubbedManifests)
280+
subjectRes := scrubbedManifests[man.Subject.Digest]
203281

204-
subjectRes := scrubbedManifests[man.Subject.Digest]
282+
scrubbedManifests[manifest.Digest] = newScrubImageResult(imageName, tag, subjectRes.Status,
283+
subjectRes.AffectedBlob, subjectRes.Error)
205284

206-
scrubbedManifests[manifest.Digest] = newScrubImageResult(imageName, tag, subjectRes.Status,
207-
subjectRes.AffectedBlob, subjectRes.Error)
208-
}
285+
return layers, err
209286
}
287+
288+
return layers, err
210289
default:
211-
scrubbedManifests[manifest.Digest] = getResult(imageName, tag, manifest.Digest, errors.ErrBadManifest)
212-
}
213-
}
290+
scrubbedManifests[manifest.Digest] = getResult(imageName, tag, manifest.Digest, zerr.ErrBadManifest)
214291

215-
func CheckIntegrity(
216-
ctx context.Context, imageName, tagName string, manifest ispec.Descriptor, imgStore storageTypes.ImageStore,
217-
) ScrubImageResult {
218-
// check manifest and config
219-
if affectedBlob, err := CheckManifestAndConfig(imageName, manifest, imgStore); err != nil {
220-
return getResult(imageName, tagName, affectedBlob, err)
292+
return layers, zerr.ErrBadManifest
221293
}
222-
223-
// check layers
224-
return CheckLayers(ctx, imageName, tagName, manifest, imgStore)
225294
}
226295

227296
func CheckManifestAndConfig(
228-
imageName string, manifestDesc ispec.Descriptor, imgStore storageTypes.ImageStore,
229-
) (godigest.Digest, error) {
297+
imageName string, manifestDesc ispec.Descriptor, manifestContent []byte, imgStore storageTypes.ImageStore,
298+
) (godigest.Digest, ispec.Manifest, error) {
230299
// Q oras artifacts?
231300
if manifestDesc.MediaType != ispec.MediaTypeImageManifest {
232-
return manifestDesc.Digest, errors.ErrBadManifest
233-
}
234-
235-
manifestContent, err := imgStore.GetBlobContent(imageName, manifestDesc.Digest)
236-
if err != nil {
237-
return manifestDesc.Digest, err
301+
return manifestDesc.Digest, ispec.Manifest{}, zerr.ErrBadManifest
238302
}
239303

240304
var manifest ispec.Manifest
241305

242-
err = json.Unmarshal(manifestContent, &manifest)
306+
err := json.Unmarshal(manifestContent, &manifest)
243307
if err != nil {
244-
return manifestDesc.Digest, errors.ErrBadManifest
308+
return manifestDesc.Digest, ispec.Manifest{}, zerr.ErrBadManifest
245309
}
246310

247311
configContent, err := imgStore.GetBlobContent(imageName, manifest.Config.Digest)
248312
if err != nil {
249-
return manifest.Config.Digest, err
313+
return manifest.Config.Digest, ispec.Manifest{}, err
250314
}
251315

252316
var config ispec.Image
253317

254318
err = json.Unmarshal(configContent, &config)
255319
if err != nil {
256-
return manifest.Config.Digest, errors.ErrBadConfig
320+
return manifest.Config.Digest, ispec.Manifest{}, zerr.ErrBadConfig
257321
}
258322

259-
return "", nil
323+
return "", manifest, nil
260324
}
261325

262326
func CheckLayers(
263-
ctx context.Context, imageName, tagName string, manifest ispec.Descriptor, imgStore storageTypes.ImageStore,
327+
imageName, tagName string, layers []ispec.Descriptor, imgStore storageTypes.ImageStore,
264328
) ScrubImageResult {
265329
imageRes := ScrubImageResult{}
266330

267-
buf, err := imgStore.GetBlobContent(imageName, manifest.Digest)
268-
if err != nil {
269-
imageRes = getResult(imageName, tagName, manifest.Digest, err)
270-
271-
return imageRes
272-
}
273-
274-
var man ispec.Manifest
275-
if err := json.Unmarshal(buf, &man); err != nil {
276-
imageRes = getResult(imageName, tagName, manifest.Digest, errors.ErrBadManifest)
277-
278-
return imageRes
279-
}
280-
281-
for _, layer := range man.Layers {
331+
for _, layer := range layers {
282332
if err := imgStore.VerifyBlobDigestValue(imageName, layer.Digest); err != nil {
283333
imageRes = getResult(imageName, tagName, layer.Digest, err)
284334

0 commit comments

Comments
 (0)