store: add support to split ImageStore

Allow storage users to split the filesystem of containers vs image
store, `imagestore` if configured will pull images in image storage
instead of the `graphRoot` while keeping the other parts still in the
originally configured `graphRoot`.

overlay: set workdir and upperdir according to splitstore
If splitstore is set `workdir` and `upperdir` must go into the
splitstore i.e `graphRoot`.

Signed-off-by: Aditya R <arajan@redhat.com>
This commit is contained in:
Aditya R 2023-04-03 12:58:16 +05:30
parent 21aca2978d
commit 30775d4b2b
No known key found for this signature in database
GPG Key ID: 8E5A8A19DF7C8673
9 changed files with 480 additions and 24 deletions

View File

@ -43,6 +43,7 @@ func main() {
flags := mflag.NewFlagSet(command, eh)
flags.StringVar(&options.RunRoot, []string{"-run", "R"}, options.RunRoot, "Root of the runtime state tree")
flags.StringVar(&options.GraphRoot, []string{"-graph", "g"}, options.GraphRoot, "Root of the storage tree")
flags.StringVar(&options.ImageStore, []string{"-image-store"}, options.ImageStore, "Root of the separate image store")
flags.BoolVar(&options.TransientStore, []string{"-transient-store"}, options.TransientStore, "Transient store")
flags.StringVar(&options.GraphDriverName, []string{"-storage-driver", "s"}, options.GraphDriverName, "Storage driver to use ($STORAGE_DRIVER)")
flags.Var(opts.NewListOptsRef(&options.GraphDriverOptions, nil), []string{"-storage-opt"}, "Set storage driver options ($STORAGE_OPTS)")

View File

@ -55,6 +55,11 @@ $ restorecon -R -v /NEWSTORAGEPATH
A common use case for this field is to provide a local storage directory when user home directories are NFS-mounted (podman does not support container storage over NFS).
**imagestore**=""
Path of imagestore different from `graphroot`, by default storage library stores all images in `graphroot` but if `imagestore` is provided it will store newly pulled images in provided `imagestore` but will keep using `graphroot` for everything else. If user is using `overlay` driver then images which were already part of `graphroot` will still be accessible ( Internally storage library will mount `graphroot` as an `additionalImageStore` to allow this behaviour ).
A common use case for this field is for the users who want to split the file-system in different parts i.e disk which stores images vs disk used by the container created by the image.
**runroot**=""
container storage run dir (default: "/run/containers/storage")
Default directory to store all temporary writable content created by container storage programs. The rootless runroot path supports environment variable substitutions (ie. `$HOME/containers/storage`)

View File

@ -322,6 +322,7 @@ func getBuiltinDriver(name, home string, options Options) (Driver, error) {
type Options struct {
Root string
RunRoot string
ImageStore string
DriverPriority []string
DriverOptions []string
UIDMaps []idtools.IDMap

View File

@ -112,6 +112,7 @@ type Driver struct {
name string
home string
runhome string
imageStore string
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap
ctr *graphdriver.RefCounter
@ -304,6 +305,16 @@ func isNetworkFileSystem(fsMagic graphdriver.FsMagic) bool {
// If overlay filesystem is not supported on the host, a wrapped graphdriver.ErrNotSupported is returned as error.
// If an overlay filesystem is not supported over an existing filesystem then a wrapped graphdriver.ErrIncompatibleFS is returned.
func Init(home string, options graphdriver.Options) (graphdriver.Driver, error) {
// If custom --imagestore is selected never
// ditch the original graphRoot, instead add it as
// additionalImageStore so its images can still be
// read and used.
if options.ImageStore != "" {
graphRootAsAdditionalStore := fmt.Sprintf("AdditionalImageStore=%s", options.ImageStore)
options.DriverOptions = append(options.DriverOptions, graphRootAsAdditionalStore)
// complete base name with driver name included
options.ImageStore = filepath.Join(options.ImageStore, "overlay")
}
opts, err := parseOptions(options.DriverOptions)
if err != nil {
return nil, err
@ -330,6 +341,12 @@ func Init(home string, options graphdriver.Options) (graphdriver.Driver, error)
return nil, err
}
if options.ImageStore != "" {
if err := idtools.MkdirAllAs(path.Join(options.ImageStore, linkDir), 0755, 0, 0); err != nil {
return nil, err
}
}
if err := idtools.MkdirAllAs(runhome, 0700, rootUID, rootGID); err != nil {
return nil, err
}
@ -421,6 +438,7 @@ func Init(home string, options graphdriver.Options) (graphdriver.Driver, error)
d := &Driver{
name: "overlay",
home: home,
imageStore: options.ImageStore,
runhome: runhome,
uidMaps: options.UIDMaps,
gidMaps: options.GIDMaps,
@ -807,15 +825,22 @@ func (d *Driver) Status() [][2]string {
// Metadata returns meta data about the overlay driver such as
// LowerDir, UpperDir, WorkDir and MergeDir used to store data.
func (d *Driver) Metadata(id string) (map[string]string, error) {
dir := d.dir(id)
dir, imagestore, _ := d.dir2(id)
if _, err := os.Stat(dir); err != nil {
return nil, err
}
workDirBase := dir
if imagestore != "" {
if _, err := os.Stat(dir); err != nil {
return nil, err
}
workDirBase = imagestore
}
metadata := map[string]string{
"WorkDir": path.Join(dir, "work"),
"MergedDir": path.Join(dir, "merged"),
"UpperDir": path.Join(dir, "diff"),
"WorkDir": path.Join(workDirBase, "work"),
"MergedDir": path.Join(workDirBase, "merged"),
"UpperDir": path.Join(workDirBase, "diff"),
}
lowerDirs, err := d.getLowerDirs(id)
@ -930,7 +955,7 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr
}
func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, disableQuota bool) (retErr error) {
dir := d.dir(id)
dir, imageStore, _ := d.dir2(id)
uidMaps := d.uidMaps
gidMaps := d.gidMaps
@ -958,8 +983,19 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, disable
if err := idtools.MkdirAllAndChownNew(path.Dir(dir), 0755, idPair); err != nil {
return err
}
workDirBase := dir
if imageStore != "" {
workDirBase = imageStore
if err := idtools.MkdirAllAndChownNew(path.Dir(imageStore), 0755, idPair); err != nil {
return err
}
}
if parent != "" {
st, err := system.Stat(filepath.Join(d.dir(parent), "diff"))
parentBase, parentImageStore, _ := d.dir2(parent)
if parentImageStore != "" {
parentBase = parentImageStore
}
st, err := system.Stat(filepath.Join(parentBase, "diff"))
if err != nil {
return err
}
@ -979,6 +1015,11 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, disable
if err := idtools.MkdirAllAndChownNew(dir, 0700, idPair); err != nil {
return err
}
if imageStore != "" {
if err := idtools.MkdirAllAndChownNew(imageStore, 0700, idPair); err != nil {
return err
}
}
defer func() {
// Clean up on failure
@ -986,6 +1027,11 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, disable
if err2 := os.RemoveAll(dir); err2 != nil {
logrus.Errorf("While recovering from a failure creating a layer, error deleting %#v: %v", dir, err2)
}
if imageStore != "" {
if err2 := os.RemoveAll(workDirBase); err2 != nil {
logrus.Errorf("While recovering from a failure creating a layer, error deleting %#v: %v", dir, err2)
}
}
}
}()
@ -1014,20 +1060,31 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, disable
if d.options.forceMask != nil {
perms = *d.options.forceMask
}
if parent != "" {
st, err := system.Stat(filepath.Join(d.dir(parent), "diff"))
parentDir, parentImageStore, _ := d.dir2(parent)
base := parentDir
if parentImageStore != "" {
base = parentImageStore
}
st, err := system.Stat(filepath.Join(base, "diff"))
if err != nil {
return err
}
perms = os.FileMode(st.Mode())
}
if err := idtools.MkdirAs(path.Join(dir, "diff"), perms, rootUID, rootGID); err != nil {
if err := idtools.MkdirAs(path.Join(workDirBase, "diff"), perms, rootUID, rootGID); err != nil {
return err
}
lid := generateID(idLength)
if err := os.Symlink(path.Join("..", id, "diff"), path.Join(d.home, linkDir, lid)); err != nil {
linkBase := path.Join("..", id, "diff")
if imageStore != "" {
linkBase = path.Join(imageStore, "diff")
}
if err := os.Symlink(linkBase, path.Join(d.home, linkDir, lid)); err != nil {
return err
}
@ -1036,7 +1093,7 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, disable
return err
}
if err := idtools.MkdirAs(path.Join(dir, "work"), 0700, rootUID, rootGID); err != nil {
if err := idtools.MkdirAs(path.Join(workDirBase, "work"), 0700, rootUID, rootGID); err != nil {
return err
}
if err := idtools.MkdirAs(path.Join(dir, "merged"), 0700, rootUID, rootGID); err != nil {
@ -1121,22 +1178,26 @@ func (d *Driver) getLower(parent string) (string, error) {
}
func (d *Driver) dir(id string) string {
p, _ := d.dir2(id)
p, _, _ := d.dir2(id)
return p
}
func (d *Driver) dir2(id string) (string, bool) {
func (d *Driver) dir2(id string) (string, string, bool) {
newpath := path.Join(d.home, id)
imageStore := ""
if d.imageStore != "" {
imageStore = path.Join(d.imageStore, id)
}
if _, err := os.Stat(newpath); err != nil {
for _, p := range d.AdditionalImageStores() {
l := path.Join(p, d.name, id)
_, err = os.Stat(l)
if err == nil {
return l, true
return l, imageStore, true
}
}
}
return newpath, false
return newpath, imageStore, false
}
func (d *Driver) getLowerDirs(id string) ([]string, error) {
@ -1343,10 +1404,14 @@ func (d *Driver) Get(id string, options graphdriver.MountOpts) (string, error) {
}
func (d *Driver) get(id string, disableShifting bool, options graphdriver.MountOpts) (_ string, retErr error) {
dir, inAdditionalStore := d.dir2(id)
dir, imageStore, inAdditionalStore := d.dir2(id)
if _, err := os.Stat(dir); err != nil {
return "", err
}
workDirBase := dir
if imageStore != "" {
workDirBase = imageStore
}
readWrite := !inAdditionalStore
if !d.SupportsShifting() || options.DisableShifting {
@ -1483,7 +1548,7 @@ func (d *Driver) get(id string, disableShifting bool, options graphdriver.MountO
if err != nil {
return "", err
}
diffDir := path.Join(dir, "diff")
diffDir := path.Join(workDirBase, "diff")
if err := idtools.MkdirAllAs(diffDir, perms, rootUID, rootGID); err != nil {
return "", err
}
@ -1506,7 +1571,7 @@ func (d *Driver) get(id string, disableShifting bool, options graphdriver.MountO
}
}()
workdir := path.Join(dir, "work")
workdir := path.Join(workDirBase, "work")
if d.options.mountProgram == "" && unshare.IsRootless() {
optsList = append(optsList, "userxattr")
@ -1926,8 +1991,12 @@ func (d *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts)
}
func (d *Driver) getDiffPath(id string) (string, error) {
dir := d.dir(id)
return redirectDiffIfAdditionalLayer(path.Join(dir, "diff"))
dir, imagestore, _ := d.dir2(id)
base := dir
if imagestore != "" {
base = imagestore
}
return redirectDiffIfAdditionalLayer(path.Join(base, "diff"))
}
func (d *Driver) getLowerDiffPaths(id string) ([]string, error) {
@ -2018,8 +2087,12 @@ func (d *Driver) AdditionalImageStores() []string {
// by toContainer to those specified by toHost.
func (d *Driver) UpdateLayerIDMap(id string, toContainer, toHost *idtools.IDMappings, mountLabel string) error {
var err error
dir := d.dir(id)
diffDir := filepath.Join(dir, "diff")
dir, imagestore, _ := d.dir2(id)
base := dir
if imagestore != "" {
base = imagestore
}
diffDir := filepath.Join(base, "diff")
rootUID, rootGID := 0, 0
if toHost != nil {

View File

@ -124,6 +124,11 @@ type OptionsConfig struct {
// for shared image content
AdditionalImageStores []string `toml:"additionalimagestores,omitempty"`
// ImageStore is the location of image store which is separated from the
// container store. Usually this is not recommended unless users wants
// separate store for image and containers.
ImageStore string `toml:"imagestore,omitempty"`
// AdditionalLayerStores is the location of additional read/only
// Layer stores. Usually used to access Networked File System
// for shared image content

View File

@ -661,6 +661,7 @@ type store struct {
usernsLock *lockfile.LockFile
graphRoot string
graphOptions []string
imageStoreDir string
pullOptions map[string]string
uidMap []idtools.IDMap
gidMap []idtools.IDMap
@ -755,9 +756,19 @@ func GetStore(options types.StoreOptions) (Store, error) {
if err := os.MkdirAll(options.GraphRoot, 0700); err != nil {
return nil, err
}
if options.ImageStore != "" {
if err := os.MkdirAll(options.ImageStore, 0700); err != nil {
return nil, err
}
}
if err := os.MkdirAll(filepath.Join(options.GraphRoot, options.GraphDriverName), 0700); err != nil {
return nil, err
}
if options.ImageStore != "" {
if err := os.MkdirAll(filepath.Join(options.ImageStore, options.GraphDriverName), 0700); err != nil {
return nil, err
}
}
graphLock, err := lockfile.GetLockFile(filepath.Join(options.GraphRoot, "storage.lock"))
if err != nil {
@ -785,6 +796,7 @@ func GetStore(options types.StoreOptions) (Store, error) {
usernsLock: usernsLock,
graphRoot: options.GraphRoot,
graphOptions: options.GraphDriverOptions,
imageStoreDir: options.ImageStore,
pullOptions: options.PullOptions,
uidMap: copyIDMap(options.UIDMap),
gidMap: copyIDMap(options.GIDMap),
@ -889,7 +901,11 @@ func (s *store) load() error {
}
driverPrefix := s.graphDriverName + "-"
gipath := filepath.Join(s.graphRoot, driverPrefix+"images")
imgStoreRoot := s.imageStoreDir
if imgStoreRoot == "" {
imgStoreRoot = s.graphRoot
}
gipath := filepath.Join(imgStoreRoot, driverPrefix+"images")
if err := os.MkdirAll(gipath, 0700); err != nil {
return err
}
@ -989,8 +1005,15 @@ func (s *store) stopUsingGraphDriver() {
// Almost all users should use startUsingGraphDriver instead.
// The caller must hold s.graphLock.
func (s *store) createGraphDriverLocked() (drivers.Driver, error) {
driverRoot := s.imageStoreDir
imageStoreBase := s.graphRoot
if driverRoot == "" {
driverRoot = s.graphRoot
imageStoreBase = ""
}
config := drivers.Options{
Root: s.graphRoot,
Root: driverRoot,
ImageStore: imageStoreBase,
RunRoot: s.runRoot,
DriverPriority: s.graphDriverPriority,
DriverOptions: s.graphOptions,
@ -1020,7 +1043,11 @@ func (s *store) getLayerStoreLocked() (rwLayerStore, error) {
if err := os.MkdirAll(rlpath, 0700); err != nil {
return nil, err
}
glpath := filepath.Join(s.graphRoot, driverPrefix+"layers")
imgStoreRoot := s.imageStoreDir
if imgStoreRoot == "" {
imgStoreRoot = s.graphRoot
}
glpath := filepath.Join(imgStoreRoot, driverPrefix+"layers")
if err := os.MkdirAll(glpath, 0700); err != nil {
return nil, err
}

View File

@ -247,3 +247,241 @@ func TestStore(t *testing.T) {
store.Free()
store.Free()
}
func TestWithSplitStore(t *testing.T) {
wd := t.TempDir()
pullOpts := map[string]string{"Test1": "test1", "Test2": "test2"}
store, err := GetStore(StoreOptions{
RunRoot: filepath.Join(wd, "run"),
GraphRoot: filepath.Join(wd, "root"),
ImageStore: filepath.Join(wd, "imgstore"),
GraphDriverName: "vfs",
GraphDriverOptions: []string{},
UIDMap: []idtools.IDMap{{
ContainerID: 0,
HostID: os.Getuid(),
Size: 1,
}},
GIDMap: []idtools.IDMap{{
ContainerID: 0,
HostID: os.Getgid(),
Size: 1,
}},
PullOptions: pullOpts,
})
require.NoError(t, err)
root := store.RunRoot()
require.NotNil(t, root)
root = store.GraphRoot()
require.NotNil(t, root)
root = store.GraphDriverName()
require.NotNil(t, root)
gopts := store.GraphOptions()
assert.Equal(t, []string{}, gopts)
store.UIDMap()
store.GIDMap()
opts := store.PullOptions()
assert.Equal(t, pullOpts, opts)
_, err = store.GraphDriver()
require.Nil(t, err)
_, err = store.CreateLayer("foo", "bar", nil, "", false, nil)
require.Error(t, err)
_, _, err = store.PutLayer("foo", "bar", nil, "", true, nil, nil)
require.Error(t, err)
_, err = store.CreateImage("foo", nil, "bar", "", nil)
require.Error(t, err)
_, err = store.CreateContainer("foo", nil, "bar", "layer", "", nil)
require.Error(t, err)
_, err = store.Metadata("foobar")
require.Error(t, err)
err = store.SetMetadata("foo", "bar")
require.Error(t, err)
exists := store.Exists("foobar")
require.False(t, exists)
_, err = store.Status()
require.Nil(t, err)
err = store.Delete("foobar")
require.Error(t, err)
err = store.DeleteLayer("foobar")
require.Error(t, err)
_, err = store.DeleteImage("foobar", true)
require.Error(t, err)
_, err = store.DeleteImage("foobar", false)
require.Error(t, err)
err = store.DeleteContainer("foobar")
require.Error(t, err)
err = store.DeleteContainer("foobar")
require.Error(t, err)
err = store.Wipe()
require.Nil(t, err)
_, err = store.Mount("foobar", "")
require.Error(t, err)
_, err = store.Unmount("foobar", true)
require.Error(t, err)
_, err = store.Unmount("foobar", false)
require.Error(t, err)
_, err = store.Mounted("foobar")
require.Error(t, err)
_, err = store.Changes("foobar", "foobar")
require.Error(t, err)
_, err = store.DiffSize("foobar", "foobar")
require.Error(t, err)
_, err = store.Diff("foobar", "foobar", nil)
require.Error(t, err)
_, err = store.ApplyDiff("foobar", nil)
require.Error(t, err)
var d digest.Digest
_, err = store.LayersByCompressedDigest(d)
require.Error(t, err)
_, err = store.LayersByUncompressedDigest(d)
require.Error(t, err)
_, err = store.LayerSize("foobar")
require.Error(t, err)
_, _, err = store.LayerParentOwners("foobar")
require.Error(t, err)
_, err = store.Layers()
require.Nil(t, err)
_, err = store.Images()
require.Nil(t, err)
_, err = store.Containers()
require.Nil(t, err)
_, err = store.Names("foobar")
require.Error(t, err)
err = store.SetNames("foobar", nil)
require.Error(t, err)
_, err = store.ListImageBigData("foobar")
require.Error(t, err)
_, err = store.ImageBigData("foo", "bar")
require.Error(t, err)
_, err = store.ImageBigDataSize("foo", "bar")
require.Error(t, err)
_, err = store.ImageBigDataDigest("foo", "bar")
require.Error(t, err)
err = store.SetImageBigData("foo", "bar", nil, nil)
require.Error(t, err)
_, err = store.ImageSize("foobar")
require.Error(t, err)
_, err = store.ListContainerBigData("foobar")
require.Error(t, err)
_, err = store.ContainerBigData("foo", "bar")
require.Error(t, err)
_, err = store.ContainerBigDataSize("foo", "bar")
require.Error(t, err)
_, err = store.ContainerBigDataDigest("foo", "bar")
require.Error(t, err)
err = store.SetContainerBigData("foo", "bar", nil)
require.Error(t, err)
_, err = store.ContainerSize("foobar")
require.Error(t, err)
_, err = store.Layer("foobar")
require.Error(t, err)
_, err = store.Image("foobar")
require.Error(t, err)
_, err = store.ImagesByTopLayer("foobar")
require.Error(t, err)
images, err := store.ImagesByDigest("foobar")
require.NoError(t, err)
assert.Equal(t, len(images), 0)
_, err = store.Container("foobar")
require.Error(t, err)
_, err = store.ContainerByLayer("foobar")
require.Error(t, err)
_, err = store.ContainerDirectory("foobar")
require.Error(t, err)
err = store.SetContainerDirectoryFile("foo", "bar", nil)
require.Error(t, err)
_, err = store.FromContainerDirectory("foo", "bar")
require.Error(t, err)
_, err = store.ContainerRunDirectory("foobar")
require.Error(t, err)
err = store.SetContainerRunDirectoryFile("foo", "bar", nil)
require.Error(t, err)
_, err = store.FromContainerRunDirectory("foo", "bar")
require.Error(t, err)
_, _, err = store.ContainerParentOwners("foobar")
require.Error(t, err)
_, err = store.Lookup("foobar")
require.Error(t, err)
_, err = store.Shutdown(false)
require.Nil(t, err)
_, err = store.Shutdown(true)
require.Nil(t, err)
_, err = store.Version()
require.Nil(t, err)
// GetDigestLock returns digest-specific Locker.
_, err = store.GetDigestLock(d)
require.Error(t, err)
store.Free()
store.Free()
}

98
tests/split-store.bats Normal file
View File

@ -0,0 +1,98 @@
#!/usr/bin/env bats
load helpers
@test "split-store" {
# Create and populate three interesting layers.
populate
# Create an image using to top layer.
name=wonderful-image
run mkdir -p ${TESTDIR}/imagestore
run mkdir -p ${TESTDIR}/emptyimagestore
run storage --graph ${TESTDIR}/graph/ --image-store ${TESTDIR}/imagestore/ --run ${TESTDIR}/runroot/ --debug=false create-image --name $name
[ "$status" -eq 0 ]
[ "$output" != "" ]
image=${lines[0]}
# Add a couple of big data items.
createrandom ${TESTDIR}/random1
createrandom ${TESTDIR}/random2
storage --graph ${TESTDIR}/graph/ --image-store ${TESTDIR}/imagestore/ --run ${TESTDIR}/runroot/ set-image-data -f ${TESTDIR}/random1 $image random1
storage --graph ${TESTDIR}/graph/ --image-store ${TESTDIR}/imagestore/ --run ${TESTDIR}/runroot/ set-image-data -f ${TESTDIR}/random2 $image random2
# Get information about the image, and make sure the ID, name, and data names were preserved.
run storage --graph ${TESTDIR}/graph/ --image-store ${TESTDIR}/imagestore/ --run ${TESTDIR}/runroot/ image $image
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ "ID: $image" ]]
[[ "$output" =~ "Name: $name" ]]
[[ "$output" =~ "Data: random1" ]]
[[ "$output" =~ "Data: random2" ]]
# shutdown store
run storage --graph ${TESTDIR}/graph/ --image-store ${TESTDIR}/imagestore/ --run ${TESTDIR}/runroot/ shutdown
# Similar data must not be shown when image-store is switched to empty store
run storage --graph ${TESTDIR}/graph/ --image-store ${TESTDIR}/emptyimagestore/ --run ${TESTDIR}/runroot/ image $image
echo "$output"
[[ "$output" != "ID: $image" ]]
[[ "$output" != "Name: $name" ]]
[[ "$output" != "Data: random1" ]]
[[ "$output" != "Data: random2" ]]
# shutdown store
run storage --graph ${TESTDIR}/graph/ --image-store ${TESTDIR}/emptyimagestore/ --run ${TESTDIR}/runroot/ shutdown
}
@test "split-store - use graphRoot as an additional store by default" {
case "$STORAGE_DRIVER" in
overlay*)
;;
*)
skip "additional store not supported by driver $STORAGE_DRIVER"
;;
esac
# Create and populate three interesting layers.
populate
# Create an image using to top layer.
name=wonderful-image
run mkdir -p ${TESTDIR}/imagestore
run storage --graph ${TESTDIR}/graph --debug=false create-image --name $name
[ "$status" -eq 0 ]
[ "$output" != "" ]
image=${lines[0]}
# Add a couple of big data items.
createrandom ${TESTDIR}/random1
createrandom ${TESTDIR}/random2
storage --graph ${TESTDIR}/graph set-image-data -f ${TESTDIR}/random1 $image random1
storage --graph ${TESTDIR}/graph set-image-data -f ${TESTDIR}/random2 $image random2
# Get information about the image, and make sure the ID, name, and data names were preserved.
run storage --graph ${TESTDIR}/graph image $image
echo "$output"
[ "$status" -eq 0 ]
[[ "$output" =~ "ID: $image" ]]
[[ "$output" =~ "Name: $name" ]]
[[ "$output" =~ "Data: random1" ]]
[[ "$output" =~ "Data: random2" ]]
# shutdown store
run storage --graph ${TESTDIR}/graph shutdown
# Similar data must not be shown when image-store is switched to empty store
run storage --graph ${TESTDIR}/graph --image-store ${TESTDIR}/imagestore/ --run ${TESTDIR}/runroot/ image $image
echo "$output"
[[ "$output" =~ "ID: $image" ]]
[[ "$output" =~ "Name: $name" ]]
[[ "$output" =~ "Data: random1" ]]
[[ "$output" =~ "Data: random2" ]]
# Since this image is being read from the readonly graph root
# so it must show that
[[ "$output" =~ "Read Only: true" ]]
# shutdown store
run storage --graph ${TESTDIR}/graph --image-store ${TESTDIR}/imagestore/ --run ${TESTDIR}/runroot/ shutdown
}

View File

@ -21,6 +21,7 @@ type TomlConfig struct {
Driver string `toml:"driver,omitempty"`
DriverPriority []string `toml:"driver_priority,omitempty"`
RunRoot string `toml:"runroot,omitempty"`
ImageStore string `toml:"imagestore,omitempty"`
GraphRoot string `toml:"graphroot,omitempty"`
RootlessStoragePath string `toml:"rootless_storage_path,omitempty"`
TransientStore bool `toml:"transient_store,omitempty"`
@ -215,6 +216,10 @@ type StoreOptions struct {
// GraphRoot is the filesystem path under which we will store the
// contents of layers, images, and containers.
GraphRoot string `json:"root,omitempty"`
// Image Store is the location of image store which is seperated from the
// container store. Usually this is not recommended unless users wants
// seperate store for image and containers.
ImageStore string `json:"imagestore,omitempty"`
// RootlessStoragePath is the storage path for rootless users
// default $HOME/.local/share/containers/storage
RootlessStoragePath string `toml:"rootless_storage_path"`
@ -405,6 +410,9 @@ func ReloadConfigurationFile(configFile string, storeOptions *StoreOptions) erro
if config.Storage.GraphRoot != "" {
storeOptions.GraphRoot = config.Storage.GraphRoot
}
if config.Storage.ImageStore != "" {
storeOptions.ImageStore = config.Storage.ImageStore
}
if config.Storage.RootlessStoragePath != "" {
storeOptions.RootlessStoragePath = config.Storage.RootlessStoragePath
}