Skip to content

Commit 91b64c5

Browse files
committed
add xfs support to devicemapper snapshotter
ext4 file system was supported before. This adds support for xfs as well. Containerd config file can have fs_type as an additional option with possible values as "xfs" and "ext4" for now. In future other fstype support can be added. A snapshot created from a committed snapshot inherits the file system type of the parent. Any new snapshots that has no parent is created with the file system type indicated in config. If there is no config for file system type is found, then ext4 is assumed. This allows users to use xfs as an optional file system type. Signed-off-by: Alakesh Haloi <[email protected]>
1 parent 3d734d0 commit 91b64c5

4 files changed

Lines changed: 117 additions & 26 deletions

File tree

snapshots/devmapper/config.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ type Config struct {
4747

4848
// Whether to discard blocks when removing a thin device.
4949
DiscardBlocks bool `toml:"discard_blocks"`
50+
51+
// Defines file system to use for snapshout device mount. Defaults to "ext4"
52+
FileSystemType fsType `toml:"fs_type"`
5053
}
5154

5255
// LoadConfig reads devmapper configuration file from disk in TOML format
@@ -86,6 +89,10 @@ func (c *Config) parse() error {
8689
return errors.Wrapf(err, "failed to parse base image size: '%s'", c.BaseImageSize)
8790
}
8891

92+
if c.FileSystemType == "" {
93+
c.FileSystemType = fsTypeExt4
94+
}
95+
8996
c.BaseImageSizeBytes = uint64(baseImageSize)
9097
return nil
9198
}
@@ -106,5 +113,15 @@ func (c *Config) Validate() error {
106113
result = multierror.Append(result, fmt.Errorf("base_image_size is required"))
107114
}
108115

116+
if c.FileSystemType != "" {
117+
switch c.FileSystemType {
118+
case fsTypeExt4, fsTypeXFS:
119+
default:
120+
result = multierror.Append(result, fmt.Errorf("unsupported Filesystem Type: %q", c.FileSystemType))
121+
}
122+
} else {
123+
result = multierror.Append(result, fmt.Errorf("filesystem type cannot be empty"))
124+
}
125+
109126
return result.ErrorOrNil()
110127
}

snapshots/devmapper/config_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,20 @@ func TestFieldValidation(t *testing.T) {
8585
assert.Assert(t, err != nil)
8686

8787
multErr := (err).(*multierror.Error)
88-
assert.Assert(t, is.Len(multErr.Errors, 3))
88+
assert.Assert(t, is.Len(multErr.Errors, 4))
8989

9090
assert.Assert(t, multErr.Errors[0] != nil, "pool_name is empty")
9191
assert.Assert(t, multErr.Errors[1] != nil, "root_path is empty")
9292
assert.Assert(t, multErr.Errors[2] != nil, "base_image_size is empty")
93+
assert.Assert(t, multErr.Errors[3] != nil, "filesystem type cannot be empty")
9394
}
9495

9596
func TestExistingPoolFieldValidation(t *testing.T) {
9697
config := &Config{
97-
PoolName: "test",
98-
RootPath: "test",
99-
BaseImageSize: "10mb",
98+
PoolName: "test",
99+
RootPath: "test",
100+
BaseImageSize: "10mb",
101+
FileSystemType: "ext4",
100102
}
101103

102104
err := config.Validate()

snapshots/devmapper/snapshotter.go

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,13 @@ import (
3939
exec "golang.org/x/sys/execabs"
4040
)
4141

42+
type fsType string
43+
4244
const (
43-
metadataFileName = "metadata.db"
44-
fsTypeExt4 = "ext4"
45+
metadataFileName = "metadata.db"
46+
fsTypeExt4 fsType = "ext4"
47+
fsTypeXFS fsType = "xfs"
48+
devmapperSnapshotFsType = "containerd.io/snapshot/devmapper/fstype"
4549
)
4650

4751
type closeFunc func() error
@@ -183,7 +187,13 @@ func (s *Snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, er
183187
return err
184188
})
185189

186-
return s.buildMounts(snap), nil
190+
snapInfo, err := s.Stat(ctx, key)
191+
if err != nil {
192+
log.G(ctx).WithError(err).Errorf("cannot retrieve snapshot info for key %s", key)
193+
return nil, err
194+
}
195+
196+
return s.buildMounts(ctx, snap, fsType(snapInfo.Labels[devmapperSnapshotFsType])), nil
187197
}
188198

189199
// Prepare creates thin device for an active snapshot identified by key
@@ -227,7 +237,7 @@ func (s *Snapshotter) Commit(ctx context.Context, name, key string, opts ...snap
227237
log.G(ctx).WithFields(logrus.Fields{"name": name, "key": key}).Debug("commit")
228238

229239
return s.withTransaction(ctx, true, func(ctx context.Context) error {
230-
id, _, _, err := storage.GetInfo(ctx, key)
240+
id, snapInfo, _, err := storage.GetInfo(ctx, key)
231241
if err != nil {
232242
return err
233243
}
@@ -242,6 +252,15 @@ func (s *Snapshotter) Commit(ctx context.Context, name, key string, opts ...snap
242252
Size: size,
243253
}
244254

255+
// Add file system type label if present. In case more than one file system
256+
// type is supported file system type from parent will be used for creating
257+
// snapshot.
258+
fsTypeActive := snapInfo.Labels[devmapperSnapshotFsType]
259+
if fsTypeActive != "" {
260+
fsLabel := make(map[string]string)
261+
fsLabel[devmapperSnapshotFsType] = fsTypeActive
262+
opts = append(opts, snapshots.WithLabels(fsLabel))
263+
}
245264
_, err = storage.CommitActive(ctx, key, name, usage, opts...)
246265
if err != nil {
247266
return err
@@ -351,6 +370,33 @@ func (s *Snapshotter) Close() error {
351370
}
352371

353372
func (s *Snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
373+
var fileSystemType fsType
374+
375+
// For snapshots with no parents, we use file system type as configured in config.
376+
// For snapshots with parents, we inherit the file system type. We use the same
377+
// file system type derived here for building mount points later.
378+
fsLabel := make(map[string]string)
379+
if len(parent) == 0 {
380+
fileSystemType = s.config.FileSystemType
381+
} else {
382+
_, snapInfo, _, err := storage.GetInfo(ctx, parent)
383+
if err != nil {
384+
log.G(ctx).Errorf("failed to read snapshotInfo for %s", parent)
385+
return nil, err
386+
}
387+
fileSystemType = fsType(snapInfo.Labels[devmapperSnapshotFsType])
388+
if fileSystemType == "" {
389+
// For parent snapshots created without label support, we can assume that
390+
// they are ext4 type. Children of parents with no label for fsType will
391+
// now have correct label and committed snapshots from them will carry fs type
392+
// label. TODO: find out if it is better to update the parent's label with
393+
// fsType as ext4.
394+
fileSystemType = fsTypeExt4
395+
}
396+
}
397+
fsLabel[devmapperSnapshotFsType] = string(fileSystemType)
398+
opts = append(opts, snapshots.WithLabels(fsLabel))
399+
354400
snap, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...)
355401
if err != nil {
356402
return nil, err
@@ -366,7 +412,7 @@ func (s *Snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
366412
return nil, err
367413
}
368414

369-
if err := mkfs(ctx, dmsetup.GetFullDevicePath(deviceName)); err != nil {
415+
if err := mkfs(ctx, s.config.FileSystemType, dmsetup.GetFullDevicePath(deviceName)); err != nil {
370416
status, sErr := dmsetup.Status(s.pool.poolName)
371417
if sErr != nil {
372418
multierror.Append(err, sErr)
@@ -380,16 +426,17 @@ func (s *Snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
380426
} else {
381427
parentDeviceName := s.getDeviceName(snap.ParentIDs[0])
382428
snapDeviceName := s.getDeviceName(snap.ID)
383-
log.G(ctx).Debugf("creating snapshot device '%s' from '%s'", snapDeviceName, parentDeviceName)
384429

385-
err := s.pool.CreateSnapshotDevice(ctx, parentDeviceName, snapDeviceName, s.config.BaseImageSizeBytes)
430+
log.G(ctx).Debugf("creating snapshot device '%s' from '%s' with fsType: '%s'", snapDeviceName, parentDeviceName, fileSystemType)
431+
432+
err = s.pool.CreateSnapshotDevice(ctx, parentDeviceName, snapDeviceName, s.config.BaseImageSizeBytes)
386433
if err != nil {
387434
log.G(ctx).WithError(err).Errorf("failed to create snapshot device from parent %s", parentDeviceName)
388435
return nil, err
389436
}
390437
}
391438

392-
mounts := s.buildMounts(snap)
439+
mounts := s.buildMounts(ctx, snap, fileSystemType)
393440

394441
// Remove default directories not expected by the container image
395442
_ = mount.WithTempMount(ctx, mounts, func(root string) error {
@@ -399,20 +446,35 @@ func (s *Snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
399446
return mounts, nil
400447
}
401448

402-
// mkfs creates ext4 filesystem on the given devmapper device
403-
func mkfs(ctx context.Context, path string) error {
404-
args := []string{
405-
"-E",
406-
// We don't want any zeroing in advance when running mkfs on thin devices (see "man mkfs.ext4")
407-
"nodiscard,lazy_itable_init=0,lazy_journal_init=0",
408-
path,
449+
// mkfs creates filesystem on the given devmapper device based on type
450+
// specified in config.
451+
func mkfs(ctx context.Context, fs fsType, path string) error {
452+
mkfsCommand := ""
453+
var args []string
454+
455+
switch fs {
456+
case fsTypeExt4:
457+
mkfsCommand = "mkfs.ext4"
458+
args = []string{
459+
"-E",
460+
// We don't want any zeroing in advance when running mkfs on thin devices (see "man mkfs.ext4")
461+
"nodiscard,lazy_itable_init=0,lazy_journal_init=0",
462+
path,
463+
}
464+
case fsTypeXFS:
465+
mkfsCommand = "mkfs.xfs"
466+
args = []string{
467+
path,
468+
}
469+
default:
470+
return errors.New("file system not supported")
409471
}
410472

411-
log.G(ctx).Debugf("mkfs.ext4 %s", strings.Join(args, " "))
412-
b, err := exec.Command("mkfs.ext4", args...).CombinedOutput()
473+
log.G(ctx).Debugf("%s %s", mkfsCommand, strings.Join(args, " "))
474+
b, err := exec.Command(mkfsCommand, args...).CombinedOutput()
413475
out := string(b)
414476
if err != nil {
415-
return errors.Wrapf(err, "mkfs.ext4 couldn't initialize %q: %s", path, out)
477+
return errors.Wrapf(err, "%s couldn't initialize %q: %s", mkfsCommand, path, out)
416478
}
417479

418480
log.G(ctx).Debugf("mkfs:\n%s", out)
@@ -429,17 +491,21 @@ func (s *Snapshotter) getDevicePath(snap storage.Snapshot) string {
429491
return dmsetup.GetFullDevicePath(name)
430492
}
431493

432-
func (s *Snapshotter) buildMounts(snap storage.Snapshot) []mount.Mount {
494+
func (s *Snapshotter) buildMounts(ctx context.Context, snap storage.Snapshot, fileSystemType fsType) []mount.Mount {
433495
var options []string
434496

497+
if fileSystemType == "" {
498+
log.G(ctx).Error("File system type cannot be empty")
499+
return nil
500+
}
435501
if snap.Kind != snapshots.KindActive {
436502
options = append(options, "ro")
437503
}
438504

439505
mounts := []mount.Mount{
440506
{
441507
Source: s.getDevicePath(snap),
442-
Type: fsTypeExt4,
508+
Type: string(fileSystemType),
443509
Options: options,
444510
},
445511
}

snapshots/devmapper/snapshotter_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,14 @@ func testUsage(t *testing.T, snapshotter snapshots.Snapshotter) {
141141
"%d > %d", layer2Usage.Size, sizeBytes)
142142
}
143143

144-
func TestMkfs(t *testing.T) {
144+
func TestMkfsExt4(t *testing.T) {
145145
ctx := context.Background()
146-
err := mkfs(ctx, "")
146+
err := mkfs(ctx, "ext4", "")
147147
assert.ErrorContains(t, err, `mkfs.ext4 couldn't initialize ""`)
148148
}
149+
150+
func TestMkfsXfs(t *testing.T) {
151+
ctx := context.Background()
152+
err := mkfs(ctx, "xfs", "")
153+
assert.ErrorContains(t, err, `mkfs.xfs couldn't initialize ""`)
154+
}

0 commit comments

Comments
 (0)