Correct internal behavior to allow running when already mounted

This commit is contained in:
Dane Everitt 2021-07-04 13:23:45 -07:00 committed by DaneEveritt
parent a6a610fd82
commit f8a25cb040
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
3 changed files with 74 additions and 57 deletions

View File

@ -89,9 +89,13 @@ func (m *MigrateVHDCommand) Run(ctx context.Context) error {
} }
// Copy over the files from the backup for this server. // Copy over the files from the backup for this server.
cmd := exec.CommandContext(ctx, "cp", "-a", bak + "/.", s.Filesystem().Path()) cmd := exec.CommandContext(ctx, "mv", bak + "/*", s.Filesystem().Path())
s.Log().Debug(cmd.String())
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return errors.WithStack(err) return errors.Wrap(err, "migrate: failed to move old server files into new direcotry")
}
if err := os.Remove(bak); err != nil {
s.Log().WithField("directory", bak).Warn("failed to remove backup directory")
} }
s.Log().Info("finished migration to virtual disk...") s.Log().Info("finished migration to virtual disk...")

View File

@ -9,13 +9,15 @@ import (
"strings" "strings"
"emperror.dev/errors" "emperror.dev/errors"
"github.com/pterodactyl/wings/config"
"github.com/spf13/afero" "github.com/spf13/afero"
) )
var ( var (
ErrPathNotDirectory = errors.Sentinel("vhd: filesystem path is not a directory") ErrInvalidDiskPathTarget = errors.Sentinel("vhd: disk path is a directory or symlink")
ErrFilesystemMounted = errors.Sentinel("vhd: filesystem is already mounted") ErrMountPathNotDirectory = errors.Sentinel("vhd: mount point is not a directory")
ErrFilesystemExists = errors.Sentinel("vhd: filesystem already exists on disk") ErrFilesystemMounted = errors.Sentinel("vhd: filesystem is already mounted")
ErrFilesystemExists = errors.Sentinel("vhd: filesystem already exists on disk")
) )
// hasExitCode allows this code to test the response error to see if there is // hasExitCode allows this code to test the response error to see if there is
@ -45,8 +47,11 @@ type CfgOption func(d *Disk) *Disk
// Disk represents the underlying virtual disk for the instance. // Disk represents the underlying virtual disk for the instance.
type Disk struct { type Disk struct {
// The total size of the disk allowed in bytes. // The total size of the disk allowed in bytes.
size int64 size int64
diskPath string // The path where the disk image should be created.
diskPath string
// The point at which this disk should be made available on the system. This
// is where files can be read/written to.
mountAt string mountAt string
fs afero.Fs fs afero.Fs
commander CommanderProvider commander CommanderProvider
@ -92,7 +97,7 @@ func WithCommander(c CommanderProvider) func(*Disk) {
// Exists reports if the disk exists on the system yet or not. This only verifies // Exists reports if the disk exists on the system yet or not. This only verifies
// the presence of the disk image, not the validity of it. An error is returned // the presence of the disk image, not the validity of it. An error is returned
// if the provided disk path exists but is not a directory. // if the path exists but the destination is not a file or is a symlink.
func (d *Disk) Exists() (bool, error) { func (d *Disk) Exists() (bool, error) {
st, err := d.fs.Stat(d.diskPath) st, err := d.fs.Stat(d.diskPath)
if err != nil && os.IsNotExist(err) { if err != nil && os.IsNotExist(err) {
@ -100,10 +105,10 @@ func (d *Disk) Exists() (bool, error) {
} else if err != nil { } else if err != nil {
return false, errors.WithStack(err) return false, errors.WithStack(err)
} }
if st.IsDir() { if !st.IsDir() && st.Mode()&os.ModeSymlink == 0 {
return true, nil return true, nil
} }
return false, errors.WithStack(ErrPathNotDirectory) return false, errors.WithStack(ErrInvalidDiskPathTarget)
} }
// IsMounted checks to see if the given disk is currently mounted. // IsMounted checks to see if the given disk is currently mounted.
@ -129,6 +134,11 @@ func (d *Disk) IsMounted(ctx context.Context) (bool, error) {
// returned to the caller. If the disk is already mounted an ErrFilesystemMounted // returned to the caller. If the disk is already mounted an ErrFilesystemMounted
// error is returned to the caller. // error is returned to the caller.
func (d *Disk) Mount(ctx context.Context) error { func (d *Disk) Mount(ctx context.Context) error {
if isMounted, err := d.IsMounted(ctx); err != nil {
return errors.WithStackIf(err)
} else if isMounted {
return ErrFilesystemMounted
}
if st, err := d.fs.Stat(d.mountAt); err != nil && !os.IsNotExist(err) { if st, err := d.fs.Stat(d.mountAt); err != nil && !os.IsNotExist(err) {
return errors.Wrap(err, "vhd: failed to stat mount path") return errors.Wrap(err, "vhd: failed to stat mount path")
} else if os.IsNotExist(err) { } else if os.IsNotExist(err) {
@ -136,12 +146,11 @@ func (d *Disk) Mount(ctx context.Context) error {
return errors.Wrap(err, "vhd: failed to create mount path") return errors.Wrap(err, "vhd: failed to create mount path")
} }
} else if !st.IsDir() { } else if !st.IsDir() {
return errors.WithStack(ErrPathNotDirectory) return errors.WithStack(ErrMountPathNotDirectory)
} }
if isMounted, err := d.IsMounted(ctx); err != nil { u := config.Get().System.User
return errors.WithStackIf(err) if err := d.fs.Chown(d.mountAt, u.Uid, u.Gid); err != nil {
} else if isMounted { return errors.Wrap(err, "vhd: failed to chown mount point")
return ErrFilesystemMounted
} }
cmd := d.commander(ctx, "mount", "-t", "auto", "-o", "loop", d.diskPath, d.mountAt) cmd := d.commander(ctx, "mount", "-t", "auto", "-o", "loop", d.diskPath, d.mountAt)
if _, err := cmd.Output(); err != nil { if _, err := cmd.Output(); err != nil {
@ -184,13 +193,13 @@ func (d *Disk) Allocate(ctx context.Context) error {
return errors.Wrap(err, "vhd: failed to check for existence of root disk") return errors.Wrap(err, "vhd: failed to check for existence of root disk")
} }
trim := path.Base(d.diskPath) trim := path.Base(d.diskPath)
if err := os.MkdirAll(strings.TrimSuffix(d.diskPath, trim), 0600); err != nil { if err := d.fs.MkdirAll(strings.TrimSuffix(d.diskPath, trim), 0600); err != nil {
return errors.Wrap(err, "vhd: failed to create base vhd disk directory") return errors.Wrap(err, "vhd: failed to create base vhd disk directory")
} }
// We use 1024 as the multiplier for all of the disk space logic within the // We use 1024 as the multiplier for all of the disk space logic within the
// application. Passing "K" (/1024) is the same as "KiB" for fallocate, but // application. Passing "K" (/1024) is the same as "KiB" for fallocate, but
// is different than "KB" (/1000). // is different than "KB" (/1000).
cmd := d.commander(ctx, "fallocate", "-l", fmt.Sprintf("%dK", d.size / 1024), d.diskPath) cmd := d.commander(ctx, "fallocate", "-l", fmt.Sprintf("%dK", d.size/1024), d.diskPath)
if _, err := cmd.Output(); err != nil { if _, err := cmd.Output(); err != nil {
msg := "vhd: failed to execute fallocate command" msg := "vhd: failed to execute fallocate command"
if v, ok := err.(*exec.ExitError); ok { if v, ok := err.(*exec.ExitError); ok {

View File

@ -8,11 +8,24 @@ import (
"os/exec" "os/exec"
"testing" "testing"
"github.com/pterodactyl/wings/config"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func init() {
config.Set(&config.Configuration{
AuthenticationToken: "token123",
System: config.SystemConfiguration{
User: struct {
Uid int
Gid int
}{Uid: 10, Gid: 10},
},
})
}
type mockCmd struct { type mockCmd struct {
run func() error run func() error
output func() ([]byte, error) output func() ([]byte, error)
@ -62,16 +75,16 @@ func newMockDisk(c CommanderProvider) *Disk {
if c != nil { if c != nil {
w = c w = c
} }
return New(100 * 1024 * 1024, "/foo", "/bar", WithFs(afero.NewMemMapFs()), WithCommander(w)) return New(100 * 1024 * 1024, "/disk.img", "/mnt", WithFs(afero.NewMemMapFs()), WithCommander(w))
} }
func Test_New(t *testing.T) { func Test_New(t *testing.T) {
t.Run("creates expected struct", func(t *testing.T) { t.Run("creates expected struct", func(t *testing.T) {
d := New(100 * 1024 * 1024, "/foo", "/bar") d := New(100 * 1024 * 1024, "/disk.img", "/mnt")
assert.NotNil(t, d) assert.NotNil(t, d)
assert.Equal(t, int64(100 * 1024 * 1024), d.size) assert.Equal(t, int64(100 * 1024 * 1024), d.size)
assert.Equal(t, "/foo", d.diskPath) assert.Equal(t, "/disk.img", d.diskPath)
assert.Equal(t, "/bar", d.mountAt) assert.Equal(t, "/mnt", d.mountAt)
// Ensure by default we get a commander interface returned and that it // Ensure by default we get a commander interface returned and that it
// returns an *exec.Cmd. // returns an *exec.Cmd.
@ -93,7 +106,7 @@ func Test_New(t *testing.T) {
return &cprov return &cprov
} }
d := New(100, "/foo", "/bar", WithFs(fs), WithCommander(c)) d := New(100, "/disk.img", "/mnt", WithFs(fs), WithCommander(c))
assert.NotNil(t, d) assert.NotNil(t, d)
assert.Same(t, fs, d.fs) assert.Same(t, fs, d.fs)
assert.Same(t, &cprov, d.commander(context.TODO(), "")) assert.Same(t, &cprov, d.commander(context.TODO(), ""))
@ -113,8 +126,9 @@ func Test_New(t *testing.T) {
func TestDisk_Exists(t *testing.T) { func TestDisk_Exists(t *testing.T) {
t.Run("it exists", func(t *testing.T) { t.Run("it exists", func(t *testing.T) {
d := newMockDisk(nil) d := newMockDisk(nil)
err := d.fs.Mkdir("/foo", 0600) f, err := d.fs.Create("/disk.img")
require.NoError(t, err) require.NoError(t, err)
_ = f.Close()
exists, err := d.Exists() exists, err := d.Exists()
assert.NoError(t, err) assert.NoError(t, err)
@ -130,13 +144,13 @@ func TestDisk_Exists(t *testing.T) {
t.Run("it reports errors", func(t *testing.T) { t.Run("it reports errors", func(t *testing.T) {
d := newMockDisk(nil) d := newMockDisk(nil)
_, err := d.fs.Create("/foo") err := d.fs.Mkdir("/disk.img", 0600)
require.NoError(t, err) require.NoError(t, err)
exists, err := d.Exists() exists, err := d.Exists()
assert.Error(t, err) assert.Error(t, err)
assert.False(t, exists) assert.False(t, exists)
assert.EqualError(t, err, ErrPathNotDirectory.Error()) assert.EqualError(t, err, ErrInvalidDiskPathTarget.Error())
}) })
} }
@ -151,7 +165,7 @@ func TestDisk_IsMounted(t *testing.T) {
is.Same(pctx, ctx) is.Same(pctx, ctx)
is.Equal("grep", name) is.Equal("grep", name)
is.Len(args, 3) is.Len(args, 3)
is.Equal([]string{"-qs", "/bar ext4", "/proc/mounts"}, args) is.Equal([]string{"-qs", "/mnt ext4", "/proc/mounts"}, args)
return &mockCmd{} return &mockCmd{}
} }
@ -198,18 +212,24 @@ func TestDisk_IsMounted(t *testing.T) {
} }
func TestDisk_Mount(t *testing.T) { func TestDisk_Mount(t *testing.T) {
failedCmd := func(ctx context.Context, name string, args ...string) Commander {
return &mockCmd{run: func() error {
return &mockedExitCode{code: 1}
}}
}
t.Run("error is returned if mount point is not a directory", func(t *testing.T) { t.Run("error is returned if mount point is not a directory", func(t *testing.T) {
disk := newMockDisk(nil) disk := newMockDisk(failedCmd)
_, err := disk.fs.Create("/bar") _, err := disk.fs.Create("/mnt")
require.NoError(t, err) require.NoError(t, err)
err = disk.Mount(context.TODO()) err = disk.Mount(context.TODO())
assert.Error(t, err) assert.Error(t, err)
assert.EqualError(t, err, ErrPathNotDirectory.Error()) assert.EqualError(t, err, ErrMountPathNotDirectory.Error())
}) })
t.Run("error is returned if mount point cannot be created", func(t *testing.T) { t.Run("error is returned if mount point cannot be created", func(t *testing.T) {
disk := newMockDisk(nil) disk := newMockDisk(failedCmd)
disk.fs = afero.NewReadOnlyFs(disk.fs) disk.fs = afero.NewReadOnlyFs(disk.fs)
err := disk.Mount(context.TODO()) err := disk.Mount(context.TODO())
@ -235,7 +255,7 @@ func TestDisk_Mount(t *testing.T) {
called = true called = true
assert.Equal(t, "mount", name) assert.Equal(t, "mount", name)
assert.Equal(t, []string{"-t", "auto", "-o", "loop", "/foo", "/bar"}, args) assert.Equal(t, []string{"-t", "auto", "-o", "loop", "/disk.img", "/mnt"}, args)
return nil, &exec.ExitError{ return nil, &exec.ExitError{
ProcessState: &os.ProcessState{}, ProcessState: &os.ProcessState{},
@ -253,35 +273,19 @@ func TestDisk_Mount(t *testing.T) {
}) })
t.Run("disk can be mounted at existing path", func(t *testing.T) { t.Run("disk can be mounted at existing path", func(t *testing.T) {
var cmd CommanderProvider = func(ctx context.Context, name string, args ...string) Commander { disk := newMockDisk(failedCmd)
return &mockCmd{ require.NoError(t, disk.fs.Mkdir("/mnt", 0600))
run: func() error {
return &mockedExitCode{code: 1}
},
}
}
disk := newMockDisk(cmd)
require.NoError(t, disk.fs.Mkdir("/bar", 0600))
err := disk.Mount(context.TODO()) err := disk.Mount(context.TODO())
assert.NoError(t, err) assert.NoError(t, err)
}) })
t.Run("disk can be mounted at non-existing path", func(t *testing.T) { t.Run("disk can be mounted at non-existing path", func(t *testing.T) {
var cmd CommanderProvider = func(ctx context.Context, name string, args ...string) Commander { disk := newMockDisk(failedCmd)
return &mockCmd{
run: func() error {
return &mockedExitCode{code: 1}
},
}
}
disk := newMockDisk(cmd)
err := disk.Mount(context.TODO()) err := disk.Mount(context.TODO())
assert.NoError(t, err) assert.NoError(t, err)
st, err := disk.fs.Stat("/bar") st, err := disk.fs.Stat("/mnt")
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, st.IsDir()) assert.True(t, st.IsDir())
}) })
@ -298,7 +302,7 @@ func TestDisk_Unmount(t *testing.T) {
is.Same(pctx, ctx) is.Same(pctx, ctx)
is.Equal("umount", name) is.Equal("umount", name)
is.Equal([]string{"/bar"}, args) is.Equal([]string{"/mnt"}, args)
return &mockCmd{} return &mockCmd{}
} }
@ -360,14 +364,14 @@ func TestDisk_Allocate(t *testing.T) {
output: func() ([]byte, error) { output: func() ([]byte, error) {
called = true called = true
assert.Equal(t, "fallocate", name) assert.Equal(t, "fallocate", name)
assert.Equal(t, []string{"-l", "102400K", "/foo"}, args) assert.Equal(t, []string{"-l", "102400K", "/disk.img"}, args)
return nil, nil return nil, nil
}, },
} }
} }
disk := newMockDisk(cmd) disk := newMockDisk(cmd)
err := disk.fs.Mkdir("/foo", 0600) err := disk.fs.Mkdir("/mnt", 0600)
require.NoError(t, err) require.NoError(t, err)
err = disk.Allocate(context.TODO()) err = disk.Allocate(context.TODO())
@ -394,7 +398,7 @@ func TestDisk_Allocate(t *testing.T) {
} }
disk := newMockDisk(cmd) disk := newMockDisk(cmd)
err := disk.fs.Mkdir("/foo", 0600) _, err := disk.fs.Create("/disk.img")
require.NoError(t, err) require.NoError(t, err)
err = disk.Allocate(context.TODO()) err = disk.Allocate(context.TODO())
@ -416,7 +420,7 @@ func TestDisk_MakeFilesystem(t *testing.T) {
} }
called = true called = true
assert.Equal(t, "mkfs", name) assert.Equal(t, "mkfs", name)
assert.Equal(t, []string{"-t", "ext4", "/foo"}, args) assert.Equal(t, []string{"-t", "ext4", "/disk.img"}, args)
return nil return nil
}, },
output: func() ([]byte, error) { output: func() ([]byte, error) {
@ -443,7 +447,7 @@ func TestDisk_MakeFilesystem(t *testing.T) {
} }
called = true called = true
assert.Equal(t, "mkfs", name) assert.Equal(t, "mkfs", name)
assert.Equal(t, []string{"-t", "ext4", "/foo"}, args) assert.Equal(t, []string{"-t", "ext4", "/disk.img"}, args)
return nil return nil
}, },
output: func() ([]byte, error) { output: func() ([]byte, error) {