Add basic logic needed to correctly mount the VHD when initializing a server.

This commit is contained in:
Dane Everitt 2021-07-04 12:12:32 -07:00 committed by DaneEveritt
parent 7fed6a68cb
commit 265f8a6b39
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
8 changed files with 63 additions and 32 deletions

View File

@ -4,6 +4,9 @@ build:
GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -gcflags "all=-trimpath=$(pwd)" -o build/wings_linux_amd64 -v wings.go GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -gcflags "all=-trimpath=$(pwd)" -o build/wings_linux_amd64 -v wings.go
GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -gcflags "all=-trimpath=$(pwd)" -o build/wings_linux_arm64 -v wings.go GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -gcflags "all=-trimpath=$(pwd)" -o build/wings_linux_arm64 -v wings.go
race:
go build -ldflags="-X github.com/pterodactyl/wings/system.Version=$(GIT_HEAD)" -race
debug: debug:
go build -ldflags="-X github.com/pterodactyl/wings/system.Version=$(GIT_HEAD)" go build -ldflags="-X github.com/pterodactyl/wings/system.Version=$(GIT_HEAD)"
sudo ./wings --debug --ignore-certificate-errors --config config.yml --pprof --pprof-block-rate 1 sudo ./wings --debug --ignore-certificate-errors --config config.yml --pprof --pprof-block-rate 1

View File

@ -14,6 +14,7 @@ import (
"github.com/pterodactyl/wings/loggers/cli" "github.com/pterodactyl/wings/loggers/cli"
"github.com/pterodactyl/wings/remote" "github.com/pterodactyl/wings/remote"
"github.com/pterodactyl/wings/server" "github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/server/filesystem"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -55,7 +56,7 @@ func (m *MigrateVHDCommand) Run(ctx context.Context) error {
for _, s := range m.manager.All() { for _, s := range m.manager.All() {
s.Log().Debug("starting migration of server contents to virtual disk...") s.Log().Debug("starting migration of server contents to virtual disk...")
v := s.Filesystem().NewVHD() v := vhd.New(s.DiskSpace(), filesystem.VirtualDiskPath(s.Id()), s.Filesystem().Path())
if err := v.Allocate(ctx); err != nil { if err := v.Allocate(ctx); err != nil {
return errors.WithStackIf(err) return errors.WithStackIf(err)
} }

View File

@ -38,7 +38,7 @@ func New(ctx context.Context, manager *server.Manager, details ServerDetails) (*
// Create a new server instance using the configuration we wrote to the disk // Create a new server instance using the configuration we wrote to the disk
// so that everything gets instantiated correctly on the struct. // so that everything gets instantiated correctly on the struct.
s, err := manager.InitServer(c) s, err := manager.InitServer(ctx, c)
if err != nil { if err != nil {
return nil, errors.WrapIf(err, "installer: could not init server instance") return nil, errors.WrapIf(err, "installer: could not init server instance")
} }

View File

@ -43,6 +43,7 @@ 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.
size int64 size int64
diskPath string diskPath string
mountAt string mountAt string
@ -51,8 +52,8 @@ type Disk struct {
} }
// New returns a new Disk instance. The "size" parameter should be provided in // New returns a new Disk instance. The "size" parameter should be provided in
// megabytes of space allowed for the disk. An additional slice of option // bytes of space allowed for the disk. An additional slice of option callbacks
// callbacks can be provided to programatically swap out the underlying filesystem // can be provided to programatically swap out the underlying filesystem
// implementation or the underlying command exection engine. // implementation or the underlying command exection engine.
func New(size int64, diskPath string, mountAt string, opts ...func(*Disk)) *Disk { func New(size int64, diskPath string, mountAt string, opts ...func(*Disk)) *Disk {
if diskPath == "" || mountAt == "" { if diskPath == "" || mountAt == "" {
@ -181,7 +182,10 @@ func (d *Disk) Allocate(ctx context.Context) error {
} else if err != nil { } else if err != nil {
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")
} }
cmd := d.commander(ctx, "fallocate", "-l", fmt.Sprintf("%dM", d.size), d.diskPath) // 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)
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

@ -62,14 +62,14 @@ func newMockDisk(c CommanderProvider) *Disk {
if c != nil { if c != nil {
w = c w = c
} }
return New(100, "/foo", "/bar", WithFs(afero.NewMemMapFs()), WithCommander(w)) return New(100 * 1024 * 1024, "/foo", "/bar", 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, "/foo", "/bar") d := New(100 * 1024 * 1024, "/foo", "/bar")
assert.NotNil(t, d) assert.NotNil(t, d)
assert.Equal(t, int64(100), d.size) assert.Equal(t, int64(100 * 1024 * 1024), d.size)
assert.Equal(t, "/foo", d.diskPath) assert.Equal(t, "/foo", d.diskPath)
assert.Equal(t, "/bar", d.mountAt) assert.Equal(t, "/bar", d.mountAt)
@ -360,7 +360,7 @@ 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", "100M", "/foo"}, args) assert.Equal(t, []string{"-l", "102400K", "/foo"}, args)
return nil, nil return nil, nil
}, },
} }

View File

@ -2,6 +2,7 @@ package filesystem
import ( import (
"bufio" "bufio"
"context"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -20,6 +21,7 @@ import (
ignore "github.com/sabhiram/go-gitignore" ignore "github.com/sabhiram/go-gitignore"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/internal/vhd"
"github.com/pterodactyl/wings/system" "github.com/pterodactyl/wings/system"
) )
@ -30,6 +32,7 @@ type Filesystem struct {
diskUsed int64 diskUsed int64
diskCheckInterval time.Duration diskCheckInterval time.Duration
denylist *ignore.GitIgnore denylist *ignore.GitIgnore
vhd *vhd.Disk
// The maximum amount of disk space (in bytes) that this Filesystem instance can use. // The maximum amount of disk space (in bytes) that this Filesystem instance can use.
diskLimit int64 diskLimit int64
@ -41,8 +44,9 @@ type Filesystem struct {
} }
// New creates a new Filesystem instance for a given server. // New creates a new Filesystem instance for a given server.
func New(root string, size int64, denylist []string) *Filesystem { func New(uuid string, size int64, denylist []string) *Filesystem {
return &Filesystem{ root := filepath.Join(config.Get().System.Data, uuid)
fs := Filesystem{
root: root, root: root,
diskLimit: size, diskLimit: size,
diskCheckInterval: time.Duration(config.Get().System.DiskCheckInterval), diskCheckInterval: time.Duration(config.Get().System.DiskCheckInterval),
@ -50,6 +54,16 @@ func New(root string, size int64, denylist []string) *Filesystem {
lookupInProgress: system.NewAtomicBool(false), lookupInProgress: system.NewAtomicBool(false),
denylist: ignore.CompileIgnoreLines(denylist...), denylist: ignore.CompileIgnoreLines(denylist...),
} }
if config.Get().System.UseVirtualDisks {
fs.vhd = vhd.New(size, VirtualDiskPath(uuid), fs.root)
}
return &fs
}
func VirtualDiskPath(uuid string) string {
return filepath.Join(config.Get().System.Data, ".vhd/", uuid+".img")
} }
// Path returns the root path for the Filesystem instance. // Path returns the root path for the Filesystem instance.
@ -57,6 +71,25 @@ func (fs *Filesystem) Path() string {
return fs.root return fs.root
} }
// IsVirtual returns true if the filesystem is currently using a virtual disk.
func (fs *Filesystem) IsVirtual() bool {
return fs.vhd != nil
}
// MountDisk will attempt to mount the underlying virtual disk for the server.
// If the disk is already mounted this is a no-op function. If the filesystem is
// not configured for virtual disks this function will panic.
func (fs *Filesystem) MountDisk(ctx context.Context) error {
if !fs.IsVirtual() {
panic(errors.New("filesystem: cannot call MountDisk on Filesystem instance without VHD present"))
}
err := fs.vhd.Mount(ctx)
if errors.Is(err, vhd.ErrFilesystemMounted) {
return nil
}
return errors.WrapIf(err, "filesystem: failed to mount VHD")
}
// File returns a reader for a file instance as well as the stat information. // File returns a reader for a file instance as well as the stat information.
func (fs *Filesystem) File(p string) (*os.File, Stat, error) { func (fs *Filesystem) File(p string) (*os.File, Stat, error) {
cleaned, err := fs.SafePath(p) cleaned, err := fs.SafePath(p)

View File

@ -1,17 +0,0 @@
package filesystem
import (
"path/filepath"
"strings"
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/internal/vhd"
)
func (fs *Filesystem) NewVHD() *vhd.Disk {
parts := strings.Split(fs.root, "/")
disk := filepath.Join(config.Get().System.Data, ".disks/", parts[len(parts)-1]+".img")
return vhd.New(250, disk, fs.root)
// return vhd.New(fs.diskLimit/1024/1024, disk, fs.root)
}

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath"
"runtime" "runtime"
"sync" "sync"
"time" "time"
@ -184,7 +183,7 @@ func (m *Manager) ReadStates() (map[string]string, error) {
// InitServer initializes a server using a data byte array. This will be // InitServer initializes a server using a data byte array. This will be
// marshaled into the given struct using a YAML marshaler. This will also // marshaled into the given struct using a YAML marshaler. This will also
// configure the given environment for a server. // configure the given environment for a server.
func (m *Manager) InitServer(data remote.ServerConfigurationResponse) (*Server, error) { func (m *Manager) InitServer(ctx context.Context, data remote.ServerConfigurationResponse) (*Server, error) {
s, err := New(m.client) s, err := New(m.client)
if err != nil { if err != nil {
return nil, err return nil, err
@ -196,7 +195,15 @@ func (m *Manager) InitServer(data remote.ServerConfigurationResponse) (*Server,
return nil, errors.WithStackIf(err) return nil, errors.WithStackIf(err)
} }
s.fs = filesystem.New(filepath.Join(config.Get().System.Data, s.ID()), s.DiskSpace(), s.Config().Egg.FileDenylist) s.fs = filesystem.New(s.Id(), s.DiskSpace(), s.Config().Egg.FileDenylist)
// If this is a virtuakl filesystem we need to go ahead and mount the disk
// so that everything is accessible.
if s.fs.IsVirtual() {
log.WithField("server", s.Id()).Info("mounting virtual disk for server")
if err := s.fs.MountDisk(ctx); err != nil {
return nil, err
}
}
// Right now we only support a Docker based environment, so I'm going to hard code // Right now we only support a Docker based environment, so I'm going to hard code
// this logic in. When we're ready to support other environment we'll need to make // this logic in. When we're ready to support other environment we'll need to make
@ -258,7 +265,7 @@ func (m *Manager) init(ctx context.Context) error {
log.WithField("server", data.Uuid).WithField("error", err).Error("failed to parse server configuration from API response, skipping...") log.WithField("server", data.Uuid).WithField("error", err).Error("failed to parse server configuration from API response, skipping...")
return return
} }
s, err := m.InitServer(d) s, err := m.InitServer(ctx, d)
if err != nil { if err != nil {
log.WithField("server", data.Uuid).WithField("error", err).Error("failed to load server, skipping...") log.WithField("server", data.Uuid).WithField("error", err).Error("failed to load server, skipping...")
return return