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.
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 {
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...")

View File

@ -9,13 +9,15 @@ import (
"strings"
"emperror.dev/errors"
"github.com/pterodactyl/wings/config"
"github.com/spf13/afero"
)
var (
ErrPathNotDirectory = errors.Sentinel("vhd: filesystem path is not a directory")
ErrFilesystemMounted = errors.Sentinel("vhd: filesystem is already mounted")
ErrFilesystemExists = errors.Sentinel("vhd: filesystem already exists on disk")
ErrInvalidDiskPathTarget = errors.Sentinel("vhd: disk path is a directory or symlink")
ErrMountPathNotDirectory = errors.Sentinel("vhd: mount point is not a directory")
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
@ -45,8 +47,11 @@ type CfgOption func(d *Disk) *Disk
// Disk represents the underlying virtual disk for the instance.
type Disk struct {
// The total size of the disk allowed in bytes.
size int64
diskPath string
size int64
// 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
fs afero.Fs
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
// 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) {
st, err := d.fs.Stat(d.diskPath)
if err != nil && os.IsNotExist(err) {
@ -100,10 +105,10 @@ func (d *Disk) Exists() (bool, error) {
} else if err != nil {
return false, errors.WithStack(err)
}
if st.IsDir() {
if !st.IsDir() && st.Mode()&os.ModeSymlink == 0 {
return true, nil
}
return false, errors.WithStack(ErrPathNotDirectory)
return false, errors.WithStack(ErrInvalidDiskPathTarget)
}
// 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
// error is returned to the caller.
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) {
return errors.Wrap(err, "vhd: failed to stat mount path")
} 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")
}
} else if !st.IsDir() {
return errors.WithStack(ErrPathNotDirectory)
return errors.WithStack(ErrMountPathNotDirectory)
}
if isMounted, err := d.IsMounted(ctx); err != nil {
return errors.WithStackIf(err)
} else if isMounted {
return ErrFilesystemMounted
u := config.Get().System.User
if err := d.fs.Chown(d.mountAt, u.Uid, u.Gid); err != nil {
return errors.Wrap(err, "vhd: failed to chown mount point")
}
cmd := d.commander(ctx, "mount", "-t", "auto", "-o", "loop", d.diskPath, d.mountAt)
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")
}
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")
}
// 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
// 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 {
msg := "vhd: failed to execute fallocate command"
if v, ok := err.(*exec.ExitError); ok {

View File

@ -8,11 +8,24 @@ import (
"os/exec"
"testing"
"github.com/pterodactyl/wings/config"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"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 {
run func() error
output func() ([]byte, error)
@ -62,16 +75,16 @@ func newMockDisk(c CommanderProvider) *Disk {
if c != nil {
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) {
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.Equal(t, int64(100 * 1024 * 1024), d.size)
assert.Equal(t, "/foo", d.diskPath)
assert.Equal(t, "/bar", d.mountAt)
assert.Equal(t, "/disk.img", d.diskPath)
assert.Equal(t, "/mnt", d.mountAt)
// Ensure by default we get a commander interface returned and that it
// returns an *exec.Cmd.
@ -93,7 +106,7 @@ func Test_New(t *testing.T) {
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.Same(t, fs, d.fs)
assert.Same(t, &cprov, d.commander(context.TODO(), ""))
@ -113,8 +126,9 @@ func Test_New(t *testing.T) {
func TestDisk_Exists(t *testing.T) {
t.Run("it exists", func(t *testing.T) {
d := newMockDisk(nil)
err := d.fs.Mkdir("/foo", 0600)
f, err := d.fs.Create("/disk.img")
require.NoError(t, err)
_ = f.Close()
exists, err := d.Exists()
assert.NoError(t, err)
@ -130,13 +144,13 @@ func TestDisk_Exists(t *testing.T) {
t.Run("it reports errors", func(t *testing.T) {
d := newMockDisk(nil)
_, err := d.fs.Create("/foo")
err := d.fs.Mkdir("/disk.img", 0600)
require.NoError(t, err)
exists, err := d.Exists()
assert.Error(t, err)
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.Equal("grep", name)
is.Len(args, 3)
is.Equal([]string{"-qs", "/bar ext4", "/proc/mounts"}, args)
is.Equal([]string{"-qs", "/mnt ext4", "/proc/mounts"}, args)
return &mockCmd{}
}
@ -198,18 +212,24 @@ func TestDisk_IsMounted(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) {
disk := newMockDisk(nil)
_, err := disk.fs.Create("/bar")
disk := newMockDisk(failedCmd)
_, err := disk.fs.Create("/mnt")
require.NoError(t, err)
err = disk.Mount(context.TODO())
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) {
disk := newMockDisk(nil)
disk := newMockDisk(failedCmd)
disk.fs = afero.NewReadOnlyFs(disk.fs)
err := disk.Mount(context.TODO())
@ -235,7 +255,7 @@ func TestDisk_Mount(t *testing.T) {
called = true
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{
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) {
var cmd CommanderProvider = func(ctx context.Context, name string, args ...string) Commander {
return &mockCmd{
run: func() error {
return &mockedExitCode{code: 1}
},
}
}
disk := newMockDisk(cmd)
require.NoError(t, disk.fs.Mkdir("/bar", 0600))
disk := newMockDisk(failedCmd)
require.NoError(t, disk.fs.Mkdir("/mnt", 0600))
err := disk.Mount(context.TODO())
assert.NoError(t, err)
})
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 {
return &mockCmd{
run: func() error {
return &mockedExitCode{code: 1}
},
}
}
disk := newMockDisk(cmd)
disk := newMockDisk(failedCmd)
err := disk.Mount(context.TODO())
assert.NoError(t, err)
st, err := disk.fs.Stat("/bar")
st, err := disk.fs.Stat("/mnt")
assert.NoError(t, err)
assert.True(t, st.IsDir())
})
@ -298,7 +302,7 @@ func TestDisk_Unmount(t *testing.T) {
is.Same(pctx, ctx)
is.Equal("umount", name)
is.Equal([]string{"/bar"}, args)
is.Equal([]string{"/mnt"}, args)
return &mockCmd{}
}
@ -360,14 +364,14 @@ func TestDisk_Allocate(t *testing.T) {
output: func() ([]byte, error) {
called = true
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
},
}
}
disk := newMockDisk(cmd)
err := disk.fs.Mkdir("/foo", 0600)
err := disk.fs.Mkdir("/mnt", 0600)
require.NoError(t, err)
err = disk.Allocate(context.TODO())
@ -394,7 +398,7 @@ func TestDisk_Allocate(t *testing.T) {
}
disk := newMockDisk(cmd)
err := disk.fs.Mkdir("/foo", 0600)
_, err := disk.fs.Create("/disk.img")
require.NoError(t, err)
err = disk.Allocate(context.TODO())
@ -416,7 +420,7 @@ func TestDisk_MakeFilesystem(t *testing.T) {
}
called = true
assert.Equal(t, "mkfs", name)
assert.Equal(t, []string{"-t", "ext4", "/foo"}, args)
assert.Equal(t, []string{"-t", "ext4", "/disk.img"}, args)
return nil
},
output: func() ([]byte, error) {
@ -443,7 +447,7 @@ func TestDisk_MakeFilesystem(t *testing.T) {
}
called = true
assert.Equal(t, "mkfs", name)
assert.Equal(t, []string{"-t", "ext4", "/foo"}, args)
assert.Equal(t, []string{"-t", "ext4", "/disk.img"}, args)
return nil
},
output: func() ([]byte, error) {