Correct internal behavior to allow running when already mounted
This commit is contained in:
parent
a6a610fd82
commit
f8a25cb040
|
@ -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...")
|
||||||
|
|
|
@ -9,11 +9,13 @@ 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")
|
||||||
|
ErrMountPathNotDirectory = errors.Sentinel("vhd: mount point is not a directory")
|
||||||
ErrFilesystemMounted = errors.Sentinel("vhd: filesystem is already mounted")
|
ErrFilesystemMounted = errors.Sentinel("vhd: filesystem is already mounted")
|
||||||
ErrFilesystemExists = errors.Sentinel("vhd: filesystem already exists on disk")
|
ErrFilesystemExists = errors.Sentinel("vhd: filesystem already exists on disk")
|
||||||
)
|
)
|
||||||
|
@ -46,7 +48,10 @@ type CfgOption func(d *Disk) *Disk
|
||||||
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
|
||||||
|
// The path where the disk image should be created.
|
||||||
diskPath string
|
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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user