-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Check for existence of layers in cache before returning cached base image #3767
Changes from 9 commits
3caa0ab
c1d1d4d
ecb4575
0d70c29
c9a51ec
755dcfc
b231d79
6ee16e0
ac8a2c7
cd2070f
7e89ea7
3445f77
1456e95
099c0ce
ab53ec2
05097fb
d891281
4971ee9
4ecb92f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -445,8 +445,8 @@ List<Image> getCachedBaseImages() | |
LayerCountMismatchException, UnlistedPlatformInManifestListException, | ||
PlatformNotFoundInBaseImageException { | ||
ImageReference baseImage = buildContext.getBaseImageConfiguration().getImage(); | ||
Optional<ImageMetadataTemplate> metadata = | ||
buildContext.getBaseImageLayersCache().retrieveMetadata(baseImage); | ||
Cache baseImageLayersCache = buildContext.getBaseImageLayersCache(); | ||
Optional<ImageMetadataTemplate> metadata = baseImageLayersCache.retrieveMetadata(baseImage); | ||
if (!metadata.isPresent()) { | ||
return Collections.emptyList(); | ||
} | ||
|
@@ -457,6 +457,12 @@ List<Image> getCachedBaseImages() | |
if (manifestList == null) { | ||
Verify.verify(manifestsAndConfigs.size() == 1); | ||
ManifestTemplate manifest = manifestsAndConfigs.get(0).getManifest(); | ||
|
||
// Verify all layers described in manifest are present in cache | ||
if (!baseImageLayersCache.allLayersCached(Verify.verifyNotNull(manifest))) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
if (manifest instanceof V21ManifestTemplate) { | ||
return Collections.singletonList( | ||
JsonToImageTranslator.toImage((V21ManifestTemplate) manifest)); | ||
|
@@ -486,6 +492,11 @@ List<Image> getCachedBaseImages() | |
} | ||
|
||
ManifestTemplate manifest = Verify.verifyNotNull(manifestAndConfigFound.get().getManifest()); | ||
// Verify all layers described in manifest are present in cache | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (optional) there was quite a bit of code duplication in this method even in the past between the section that deals with general and platform-based manifest processing. Could there be an opportunity for refactoring common logic out into a helper method? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah this is a great suggestion - will refactor this and see if I can make the methods cleaner to read. |
||
if (!baseImageLayersCache.allLayersCached(manifest)) { | ||
return Collections.emptyList(); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to combine There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It gets messier here to combine them I think? (Since grabbing the manifest is conditional on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see! thanks for trying this out. Hm thinking of a way in which we could remove the number of if statements since they are all testing similar things but just at different levels of granularity. Is it possible to put them in a helper method with a descriptive name? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, the many if statements did get more cumbersome with the changes added in this PR. I think the refactoring suggestion from @elefeint will help with this here! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Excellent refactoring! |
||
ContainerConfigurationTemplate containerConfig = | ||
Verify.verifyNotNull(manifestAndConfigFound.get().getConfig()); | ||
images.add( | ||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -81,6 +81,38 @@ static void verifyImageMetadata(ImageMetadataTemplate metadata, Path metadataCac | |||
this.cacheStorageFiles = cacheStorageFiles; | ||||
} | ||||
|
||||
/** | ||||
* Returns {@code true} if all image layers described in a manifest have a corresponding file | ||||
* entry in the cache. | ||||
* | ||||
* @param manifest the image manifest | ||||
* @return a boolean | ||||
*/ | ||||
boolean allLayersCached(ManifestTemplate manifest) { | ||||
emmileaf marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it possible to move this method directly to PullBaseImageStep? Since that is the only class where this is used and it could help us avoid having to go through an extra layer of classes to reach this method. Or does this method require any variables that are specific to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I was thinking about this too - this method goes through a few layers because it depends on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it, thanks! Hm it looks like Cache takes in
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah you’re right - I missed that we could just add I went ahead and tried to make this change, but noticed that the I am tempted to leave this logic in |
||||
|
||||
List<DescriptorDigest> layerDigests; | ||||
|
||||
if (manifest instanceof V21ManifestTemplate) { | ||||
layerDigests = ((V21ManifestTemplate) manifest).getLayerDigests(); | ||||
} else if (manifest instanceof BuildableManifestTemplate) { | ||||
layerDigests = | ||||
((BuildableManifestTemplate) manifest) | ||||
.getLayers().stream() | ||||
.map(BuildableManifestTemplate.ContentDescriptorTemplate::getDigest) | ||||
.collect(Collectors.toList()); | ||||
} else { | ||||
throw new IllegalArgumentException("Unknown manifest type: " + manifest); | ||||
} | ||||
Comment on lines
+103
to
+105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this would break current behavior for those doing multi-platform image building? For context #2730, we also do caching for manifest lists in addition to single manifests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That’s a good callout, and please let me know if I'm misunderstanding anything here! Right now the added logic in But, do you think there is value here to have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah thanks for the detailed explanation! You're right, it is called for the individual manifests in the manifest list.
Hm that's a good question. I was initially thinking about this too but looking at the code for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good! |
||||
|
||||
for (DescriptorDigest layerDigest : layerDigests) { | ||||
Path layerDirectory = cacheStorageFiles.getLayerDirectory(layerDigest); | ||||
if (!Files.exists(layerDirectory)) { | ||||
return false; | ||||
} | ||||
} | ||||
return true; | ||||
} | ||||
|
||||
/** | ||||
* Retrieves the cached image metadata (a manifest list and a list of manifest/container | ||||
* configuration pairs) for an image reference. | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -638,4 +638,52 @@ public void testVerifyImageMetadata_validOciImageIndex() throws CacheCorruptedEx | |
CacheStorageReader.verifyImageMetadata(metadata, Paths.get("/cache/dir")); | ||
// should pass without CacheCorruptedException | ||
} | ||
|
||
@Test | ||
public void testAllLayersCached_v21SingleManifest() | ||
throws IOException, CacheCorruptedException, DigestException, URISyntaxException { | ||
|
||
emmileaf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
setupCachedMetadataV21(cacheDirectory); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor suggestion: If possible, let's format the test into three blocks: arrange, act and assert, with a space between each of these blocks. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the suggestion, this makes a lot of sense! Tried to follow this idea though with the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about we store these calls in variables? For example:
This is just an example so you can pick a name you think works better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ohh apologies I completely misunderstood earlier! Thank you for the explanation, I see what is meant by having separate |
||
ImageMetadataTemplate metadata = | ||
cacheStorageReader.retrieveMetadata(ImageReference.of("test", "image", "tag")).get(); | ||
|
||
V21ManifestTemplate manifest = | ||
(V21ManifestTemplate) metadata.getManifestsAndConfigs().get(0).getManifest(); | ||
Assert.assertEquals(2, manifest.getLayerDigests().size()); | ||
emmileaf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ManifestAndConfigTemplate manifestAndConfig = | ||
new ManifestAndConfigTemplate(manifest, new ContainerConfigurationTemplate()); | ||
|
||
// Create only one of the layer directories. | ||
DescriptorDigest firstLayerDigest = | ||
DescriptorDigest.fromHash(manifest.getLayerDigests().get(0).getHash()); | ||
Files.createDirectories(cacheStorageFiles.getLayerDirectory(firstLayerDigest)); | ||
Assert.assertFalse(cacheStorageReader.allLayersCached(manifestAndConfig.getManifest())); | ||
|
||
// Create the other layer directory. | ||
DescriptorDigest secondLayerDigest = | ||
DescriptorDigest.fromHash(manifest.getLayerDigests().get(1).getHash()); | ||
Files.createDirectories(cacheStorageFiles.getLayerDirectory(secondLayerDigest)); | ||
Assert.assertTrue(cacheStorageReader.allLayersCached(manifestAndConfig.getManifest())); | ||
} | ||
|
||
@Test | ||
public void testAllLayersCached_v22SingleManifest() | ||
throws IOException, CacheCorruptedException, DigestException, URISyntaxException { | ||
|
||
setupCachedMetadataV22(cacheDirectory); | ||
ImageMetadataTemplate metadata = | ||
cacheStorageReader.retrieveMetadata(ImageReference.of("test", "image", "tag")).get(); | ||
|
||
V22ManifestTemplate manifest = | ||
(V22ManifestTemplate) metadata.getManifestsAndConfigs().get(0).getManifest(); | ||
Assert.assertEquals(1, manifest.getLayers().size()); | ||
ManifestAndConfigTemplate manifestAndConfig = | ||
new ManifestAndConfigTemplate(manifest, new ContainerConfigurationTemplate()); | ||
|
||
Assert.assertFalse(cacheStorageReader.allLayersCached(manifestAndConfig.getManifest())); | ||
// Create the layer directory. | ||
DescriptorDigest layerDigest = manifest.getLayers().get(0).getDigest(); | ||
Files.createDirectories(cacheStorageFiles.getLayerDirectory(layerDigest)); | ||
Assert.assertTrue(cacheStorageReader.allLayersCached(manifestAndConfig.getManifest())); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about we move the
Verify.verifyNotNull
condition toallLayersCached
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved this to a line above, since this condition is applied again to the manifest in
toImage()
a few lines down