From 73bde82a05c87de240486d9d72160e7baa14fdac Mon Sep 17 00:00:00 2001 From: Giuseppe Scrivano Date: Fri, 14 Feb 2025 16:19:33 +0100 Subject: [PATCH] layers: write read only layers to imagestore when an imagestore is used, a R/O layer must be written to the layers.json under the imagestore, not graphroot. The lock on the imagestore is already taken through the multipleLockFile{} mechanism in place. Closes: https://github.com/containers/storage/issues/2257 Signed-off-by: Giuseppe Scrivano --- layers.go | 18 ++++++++++++++++++ store.go | 5 +++++ tests/split-store.bats | 11 +++++++++++ 3 files changed, 34 insertions(+) diff --git a/layers.go b/layers.go index 69a4887bfb..15c32f3f9c 100644 --- a/layers.go +++ b/layers.go @@ -51,6 +51,7 @@ type layerLocations uint8 // unclean shutdown const ( stableLayerLocation layerLocations = 1 << iota + imageStoreLayerLocation volatileLayerLocation numLayerLocationIndex = iota @@ -167,6 +168,9 @@ type Layer struct { // volatileStore is true if the container is from the volatile json file volatileStore bool `json:"-"` + // imageStore is true if the layer is from the image store json file + imageStore bool `json:"-"` + // BigDataNames is a list of names of data items that we keep for the // convenience of the caller. They can be large, and are only in // memory when being read from or written to disk. @@ -435,6 +439,9 @@ func layerLocation(l *Layer) layerLocations { if l.volatileStore { return volatileLayerLocation } + if l.imageStore { + return imageStoreLayerLocation + } return stableLayerLocation } @@ -456,6 +463,7 @@ func copyLayer(l *Layer) *Layer { CompressionType: l.CompressionType, ReadOnly: l.ReadOnly, volatileStore: l.volatileStore, + imageStore: l.imageStore, BigDataNames: copySlicePreferringNil(l.BigDataNames), Flags: copyMapPreferringNil(l.Flags), UIDMap: copySlicePreferringNil(l.UIDMap), @@ -823,6 +831,9 @@ func (r *layerStore) load(lockedForWriting bool) (bool, error) { if location == volatileLayerLocation { layer.volatileStore = true } + if location == imageStoreLayerLocation { + layer.imageStore = true + } layers = append(layers, layer) ids[layer.ID] = layer } @@ -1144,6 +1155,7 @@ func (s *store) newLayerStore(rundir, layerdir, imagedir string, driver drivers. rundir: rundir, jsonPath: [numLayerLocationIndex]string{ filepath.Join(layerdir, "layers.json"), + filepath.Join(imagedir, "layers.json"), filepath.Join(volatileDir, "volatile-layers.json"), }, layerdir: layerdir, @@ -1180,6 +1192,7 @@ func newROLayerStore(rundir string, layerdir string, driver drivers.Driver) (roL mountsLockfile: nil, rundir: rundir, jsonPath: [numLayerLocationIndex]string{ + filepath.Join(layerdir, "layers.json"), filepath.Join(layerdir, "layers.json"), filepath.Join(layerdir, "volatile-layers.json"), }, @@ -1399,6 +1412,10 @@ func (r *layerStore) create(id string, parentLayer *Layer, names []string, mount selinux.ReserveLabel(mountLabel) } + if moreOptions.Volatile && moreOptions.ImageStore { + return nil, -1, errors.New("internal error: volatile and image store layers are mutually exclusive") + } + // Before actually creating the layer, make a persistent record of it // with the incomplete flag set, so that future processes have a chance // to clean up after it. @@ -1422,6 +1439,7 @@ func (r *layerStore) create(id string, parentLayer *Layer, names []string, mount GIDMap: copySlicePreferringNil(moreOptions.GIDMap), BigDataNames: []string{}, volatileStore: moreOptions.Volatile, + imageStore: moreOptions.ImageStore, } layer.Flags[incompleteFlag] = true diff --git a/store.go b/store.go index 053c31c426..88e7e088ea 100644 --- a/store.go +++ b/store.go @@ -667,6 +667,8 @@ type LayerOptions struct { UncompressedDigest digest.Digest // True is the layer info can be treated as volatile Volatile bool + // True is the layer info must be written to the image store + ImageStore bool // BigData is a set of items which should be stored with the layer. BigData []LayerBigDataOption // Flags is a set of named flags and their values to store with the layer. @@ -1545,6 +1547,9 @@ func (s *store) putLayer(rlstore rwLayerStore, rlstores []roLayerStore, id, pare GIDMap: copySlicePreferringNil(gidMap), } } + if !writeable && s.imageStoreDir != "" { + options.ImageStore = true + } return rlstore.create(id, parentLayer, names, mountLabel, nil, &options, writeable, diff, slo) } diff --git a/tests/split-store.bats b/tests/split-store.bats index dbd69dd65c..103ed41b4b 100644 --- a/tests/split-store.bats +++ b/tests/split-store.bats @@ -100,6 +100,17 @@ load helpers # shutdown store run storage --graph ${TESTDIR}/graph --image-store ${TESTDIR}/imagestore/ --run ${TESTDIR}/runroot/ shutdown + # A RO layer must be created in the image store and must be usable from there as a regular store. + run storage --graph ${TESTDIR}/graph --image-store ${TESTDIR}/imagestore/ --debug=false create-layer --readonly + [ "$status" -eq 0 ] + rolayer=$output + run storage --graph ${TESTDIR}/imagestore --debug=false mount $rolayer + [ "$status" -eq 0 ] + run storage --graph ${TESTDIR}/imagestore --debug=false unmount $rolayer + [ "$status" -eq 0 ] + run storage --graph ${TESTDIR}/imagestore shutdown + [ "$status" -eq 0 ] + # Now since image was deleted from graphRoot, we should # get false output while checking if image still exists run storage --graph ${TESTDIR}/graph exists -i $image