Merge pull request #5610 from alakesh/xfs-support-devmapper

add xfs support to devicemapper snapshotter
This commit is contained in:
Phil Estes 2021-09-13 15:20:29 -04:00 committed by GitHub
commit 8493cd1a50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 117 additions and 26 deletions

View File

@ -47,6 +47,9 @@ type Config struct {
// Whether to discard blocks when removing a thin device. // Whether to discard blocks when removing a thin device.
DiscardBlocks bool `toml:"discard_blocks"` DiscardBlocks bool `toml:"discard_blocks"`
// Defines file system to use for snapshout device mount. Defaults to "ext4"
FileSystemType fsType `toml:"fs_type"`
} }
// LoadConfig reads devmapper configuration file from disk in TOML format // LoadConfig reads devmapper configuration file from disk in TOML format
@ -86,6 +89,10 @@ func (c *Config) parse() error {
return errors.Wrapf(err, "failed to parse base image size: '%s'", c.BaseImageSize) return errors.Wrapf(err, "failed to parse base image size: '%s'", c.BaseImageSize)
} }
if c.FileSystemType == "" {
c.FileSystemType = fsTypeExt4
}
c.BaseImageSizeBytes = uint64(baseImageSize) c.BaseImageSizeBytes = uint64(baseImageSize)
return nil return nil
} }
@ -106,5 +113,15 @@ func (c *Config) Validate() error {
result = multierror.Append(result, fmt.Errorf("base_image_size is required")) result = multierror.Append(result, fmt.Errorf("base_image_size is required"))
} }
if c.FileSystemType != "" {
switch c.FileSystemType {
case fsTypeExt4, fsTypeXFS:
default:
result = multierror.Append(result, fmt.Errorf("unsupported Filesystem Type: %q", c.FileSystemType))
}
} else {
result = multierror.Append(result, fmt.Errorf("filesystem type cannot be empty"))
}
return result.ErrorOrNil() return result.ErrorOrNil()
} }

View File

@ -85,11 +85,12 @@ func TestFieldValidation(t *testing.T) {
assert.Assert(t, err != nil) assert.Assert(t, err != nil)
multErr := (err).(*multierror.Error) multErr := (err).(*multierror.Error)
assert.Assert(t, is.Len(multErr.Errors, 3)) assert.Assert(t, is.Len(multErr.Errors, 4))
assert.Assert(t, multErr.Errors[0] != nil, "pool_name is empty") assert.Assert(t, multErr.Errors[0] != nil, "pool_name is empty")
assert.Assert(t, multErr.Errors[1] != nil, "root_path is empty") assert.Assert(t, multErr.Errors[1] != nil, "root_path is empty")
assert.Assert(t, multErr.Errors[2] != nil, "base_image_size is empty") assert.Assert(t, multErr.Errors[2] != nil, "base_image_size is empty")
assert.Assert(t, multErr.Errors[3] != nil, "filesystem type cannot be empty")
} }
func TestExistingPoolFieldValidation(t *testing.T) { func TestExistingPoolFieldValidation(t *testing.T) {
@ -97,6 +98,7 @@ func TestExistingPoolFieldValidation(t *testing.T) {
PoolName: "test", PoolName: "test",
RootPath: "test", RootPath: "test",
BaseImageSize: "10mb", BaseImageSize: "10mb",
FileSystemType: "ext4",
} }
err := config.Validate() err := config.Validate()

View File

@ -39,9 +39,13 @@ import (
exec "golang.org/x/sys/execabs" exec "golang.org/x/sys/execabs"
) )
type fsType string
const ( const (
metadataFileName = "metadata.db" metadataFileName = "metadata.db"
fsTypeExt4 = "ext4" fsTypeExt4 fsType = "ext4"
fsTypeXFS fsType = "xfs"
devmapperSnapshotFsType = "containerd.io/snapshot/devmapper/fstype"
) )
type closeFunc func() error type closeFunc func() error
@ -183,7 +187,13 @@ func (s *Snapshotter) Mounts(ctx context.Context, key string) ([]mount.Mount, er
return err return err
}) })
return s.buildMounts(snap), nil snapInfo, err := s.Stat(ctx, key)
if err != nil {
log.G(ctx).WithError(err).Errorf("cannot retrieve snapshot info for key %s", key)
return nil, err
}
return s.buildMounts(ctx, snap, fsType(snapInfo.Labels[devmapperSnapshotFsType])), nil
} }
// Prepare creates thin device for an active snapshot identified by key // 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
log.G(ctx).WithFields(logrus.Fields{"name": name, "key": key}).Debug("commit") log.G(ctx).WithFields(logrus.Fields{"name": name, "key": key}).Debug("commit")
return s.withTransaction(ctx, true, func(ctx context.Context) error { return s.withTransaction(ctx, true, func(ctx context.Context) error {
id, _, _, err := storage.GetInfo(ctx, key) id, snapInfo, _, err := storage.GetInfo(ctx, key)
if err != nil { if err != nil {
return err return err
} }
@ -242,6 +252,15 @@ func (s *Snapshotter) Commit(ctx context.Context, name, key string, opts ...snap
Size: size, Size: size,
} }
// Add file system type label if present. In case more than one file system
// type is supported file system type from parent will be used for creating
// snapshot.
fsTypeActive := snapInfo.Labels[devmapperSnapshotFsType]
if fsTypeActive != "" {
fsLabel := make(map[string]string)
fsLabel[devmapperSnapshotFsType] = fsTypeActive
opts = append(opts, snapshots.WithLabels(fsLabel))
}
_, err = storage.CommitActive(ctx, key, name, usage, opts...) _, err = storage.CommitActive(ctx, key, name, usage, opts...)
if err != nil { if err != nil {
return err return err
@ -351,6 +370,33 @@ func (s *Snapshotter) Close() error {
} }
func (s *Snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) { func (s *Snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, key, parent string, opts ...snapshots.Opt) ([]mount.Mount, error) {
var fileSystemType fsType
// For snapshots with no parents, we use file system type as configured in config.
// For snapshots with parents, we inherit the file system type. We use the same
// file system type derived here for building mount points later.
fsLabel := make(map[string]string)
if len(parent) == 0 {
fileSystemType = s.config.FileSystemType
} else {
_, snapInfo, _, err := storage.GetInfo(ctx, parent)
if err != nil {
log.G(ctx).Errorf("failed to read snapshotInfo for %s", parent)
return nil, err
}
fileSystemType = fsType(snapInfo.Labels[devmapperSnapshotFsType])
if fileSystemType == "" {
// For parent snapshots created without label support, we can assume that
// they are ext4 type. Children of parents with no label for fsType will
// now have correct label and committed snapshots from them will carry fs type
// label. TODO: find out if it is better to update the parent's label with
// fsType as ext4.
fileSystemType = fsTypeExt4
}
}
fsLabel[devmapperSnapshotFsType] = string(fileSystemType)
opts = append(opts, snapshots.WithLabels(fsLabel))
snap, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...) snap, err := storage.CreateSnapshot(ctx, kind, key, parent, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -366,7 +412,7 @@ func (s *Snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
return nil, err return nil, err
} }
if err := mkfs(ctx, dmsetup.GetFullDevicePath(deviceName)); err != nil { if err := mkfs(ctx, s.config.FileSystemType, dmsetup.GetFullDevicePath(deviceName)); err != nil {
status, sErr := dmsetup.Status(s.pool.poolName) status, sErr := dmsetup.Status(s.pool.poolName)
if sErr != nil { if sErr != nil {
multierror.Append(err, sErr) multierror.Append(err, sErr)
@ -380,16 +426,17 @@ func (s *Snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
} else { } else {
parentDeviceName := s.getDeviceName(snap.ParentIDs[0]) parentDeviceName := s.getDeviceName(snap.ParentIDs[0])
snapDeviceName := s.getDeviceName(snap.ID) snapDeviceName := s.getDeviceName(snap.ID)
log.G(ctx).Debugf("creating snapshot device '%s' from '%s'", snapDeviceName, parentDeviceName)
err := s.pool.CreateSnapshotDevice(ctx, parentDeviceName, snapDeviceName, s.config.BaseImageSizeBytes) log.G(ctx).Debugf("creating snapshot device '%s' from '%s' with fsType: '%s'", snapDeviceName, parentDeviceName, fileSystemType)
err = s.pool.CreateSnapshotDevice(ctx, parentDeviceName, snapDeviceName, s.config.BaseImageSizeBytes)
if err != nil { if err != nil {
log.G(ctx).WithError(err).Errorf("failed to create snapshot device from parent %s", parentDeviceName) log.G(ctx).WithError(err).Errorf("failed to create snapshot device from parent %s", parentDeviceName)
return nil, err return nil, err
} }
} }
mounts := s.buildMounts(snap) mounts := s.buildMounts(ctx, snap, fileSystemType)
// Remove default directories not expected by the container image // Remove default directories not expected by the container image
_ = mount.WithTempMount(ctx, mounts, func(root string) error { _ = mount.WithTempMount(ctx, mounts, func(root string) error {
@ -399,20 +446,35 @@ func (s *Snapshotter) createSnapshot(ctx context.Context, kind snapshots.Kind, k
return mounts, nil return mounts, nil
} }
// mkfs creates ext4 filesystem on the given devmapper device // mkfs creates filesystem on the given devmapper device based on type
func mkfs(ctx context.Context, path string) error { // specified in config.
args := []string{ func mkfs(ctx context.Context, fs fsType, path string) error {
mkfsCommand := ""
var args []string
switch fs {
case fsTypeExt4:
mkfsCommand = "mkfs.ext4"
args = []string{
"-E", "-E",
// We don't want any zeroing in advance when running mkfs on thin devices (see "man mkfs.ext4") // We don't want any zeroing in advance when running mkfs on thin devices (see "man mkfs.ext4")
"nodiscard,lazy_itable_init=0,lazy_journal_init=0", "nodiscard,lazy_itable_init=0,lazy_journal_init=0",
path, path,
} }
case fsTypeXFS:
mkfsCommand = "mkfs.xfs"
args = []string{
path,
}
default:
return errors.New("file system not supported")
}
log.G(ctx).Debugf("mkfs.ext4 %s", strings.Join(args, " ")) log.G(ctx).Debugf("%s %s", mkfsCommand, strings.Join(args, " "))
b, err := exec.Command("mkfs.ext4", args...).CombinedOutput() b, err := exec.Command(mkfsCommand, args...).CombinedOutput()
out := string(b) out := string(b)
if err != nil { if err != nil {
return errors.Wrapf(err, "mkfs.ext4 couldn't initialize %q: %s", path, out) return errors.Wrapf(err, "%s couldn't initialize %q: %s", mkfsCommand, path, out)
} }
log.G(ctx).Debugf("mkfs:\n%s", out) log.G(ctx).Debugf("mkfs:\n%s", out)
@ -429,9 +491,13 @@ func (s *Snapshotter) getDevicePath(snap storage.Snapshot) string {
return dmsetup.GetFullDevicePath(name) return dmsetup.GetFullDevicePath(name)
} }
func (s *Snapshotter) buildMounts(snap storage.Snapshot) []mount.Mount { func (s *Snapshotter) buildMounts(ctx context.Context, snap storage.Snapshot, fileSystemType fsType) []mount.Mount {
var options []string var options []string
if fileSystemType == "" {
log.G(ctx).Error("File system type cannot be empty")
return nil
}
if snap.Kind != snapshots.KindActive { if snap.Kind != snapshots.KindActive {
options = append(options, "ro") options = append(options, "ro")
} }
@ -439,7 +505,7 @@ func (s *Snapshotter) buildMounts(snap storage.Snapshot) []mount.Mount {
mounts := []mount.Mount{ mounts := []mount.Mount{
{ {
Source: s.getDevicePath(snap), Source: s.getDevicePath(snap),
Type: fsTypeExt4, Type: string(fileSystemType),
Options: options, Options: options,
}, },
} }

View File

@ -141,8 +141,14 @@ func testUsage(t *testing.T, snapshotter snapshots.Snapshotter) {
"%d > %d", layer2Usage.Size, sizeBytes) "%d > %d", layer2Usage.Size, sizeBytes)
} }
func TestMkfs(t *testing.T) { func TestMkfsExt4(t *testing.T) {
ctx := context.Background() ctx := context.Background()
err := mkfs(ctx, "") err := mkfs(ctx, "ext4", "")
assert.ErrorContains(t, err, `mkfs.ext4 couldn't initialize ""`) assert.ErrorContains(t, err, `mkfs.ext4 couldn't initialize ""`)
} }
func TestMkfsXfs(t *testing.T) {
ctx := context.Background()
err := mkfs(ctx, "xfs", "")
assert.ErrorContains(t, err, `mkfs.xfs couldn't initialize ""`)
}