Merge pull request #20 from matthewpi/issues/1899

Improved server loading
This commit is contained in:
Dane Everitt
2020-04-10 17:37:45 -07:00
committed by GitHub
15 changed files with 228 additions and 124 deletions

View File

@@ -9,10 +9,7 @@ import (
"github.com/pterodactyl/wings/config"
"github.com/remeh/sizedwaitgroup"
"go.uber.org/zap"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"path"
"strings"
"sync"
"time"
@@ -142,6 +139,11 @@ type Allocations struct {
Mappings map[string][]int `json:"mappings"`
}
// Initializes the default required internal struct components for a Server.
func (s *Server) Init() {
s.mutex = &sync.Mutex{}
}
// Iterates over a given directory and loads all of the servers listed before returning
// them to the calling function.
func LoadDirectory(dir string, cfg *config.SystemConfiguration) error {
@@ -155,43 +157,41 @@ func LoadDirectory(dir string, cfg *config.SystemConfiguration) error {
// the road to help big instances scale better.
wg := sizedwaitgroup.New(10)
f, err := ioutil.ReadDir(dir)
configs, rerr, err := api.NewRequester().GetAllServerConfigurations()
if err != nil || rerr != nil {
if err != nil {
return errors.WithStack(err)
}
return errors.New(rerr.String())
}
states, err := FetchServerStates()
if err != nil {
return err
return errors.WithStack(err)
}
servers = NewCollection(nil)
for _, file := range f {
if !strings.HasSuffix(file.Name(), ".yml") || file.IsDir() {
continue
}
for uuid, data := range configs {
wg.Add()
// For each of the YAML files we find, parse it and create a new server
// configuration object that can then be returned to the caller.
go func(file os.FileInfo) {
go func(uuid string, data *api.ServerConfigurationResponse) {
defer wg.Done()
b, err := ioutil.ReadFile(path.Join(dir, file.Name()))
s, err := FromConfiguration(data, cfg)
if err != nil {
zap.S().Errorw("failed to read server configuration file, skipping...", zap.String("server", file.Name()), zap.Error(err))
zap.S().Errorw("failed to load server, skipping...", zap.String("server", uuid), zap.Error(err))
return
}
s, err := FromConfiguration(b, cfg)
if err != nil {
if IsServerDoesNotExistError(err) {
zap.S().Infow("server does not exist on remote system", zap.String("server", file.Name()))
} else {
zap.S().Errorw("failed to parse server configuration, skipping...", zap.String("server", file.Name()), zap.Error(err))
}
return
if state, exists := states[s.Uuid]; exists {
s.State = state
zap.S().Debugw("loaded server state from cache", zap.String("server", s.Uuid), zap.String("state", s.State))
}
servers.Add(s)
}(file)
}(uuid, data)
}
// Wait until we've processed all of the configuration files in the directory
@@ -201,15 +201,10 @@ func LoadDirectory(dir string, cfg *config.SystemConfiguration) error {
return nil
}
// Initializes the default required internal struct components for a Server.
func (s *Server) Init() {
s.mutex = &sync.Mutex{}
}
// Initializes a server using a data byte array. This will be marshaled into the
// given struct using a YAML marshaler. This will also configure the given environment
// for a server.
func FromConfiguration(data []byte, cfg *config.SystemConfiguration) (*Server, error) {
func FromConfiguration(data *api.ServerConfigurationResponse, cfg *config.SystemConfiguration) (*Server, error) {
s := new(Server)
if err := defaults.Set(s); err != nil {
@@ -218,7 +213,7 @@ func FromConfiguration(data []byte, cfg *config.SystemConfiguration) (*Server, e
s.Init()
if err := yaml.Unmarshal(data, s); err != nil {
if err := s.UpdateDataStructure(data.Settings, false); err != nil {
return nil, err
}
@@ -241,13 +236,10 @@ func FromConfiguration(data []byte, cfg *config.SystemConfiguration) (*Server, e
}
s.Resources = ResourceUsage{}
// This is also done when the server is booted, however we need to account for instances
// where the server is already running and the Daemon reboots. In those cases this will
// allow us to you know, stop servers.
if cfg.SyncServersOnBoot {
if err := s.Sync(); err != nil {
return nil, err
}
// Forces the configuration to be synced with the panel.
zap.S().Debugw("syncing config with panel", zap.String("server", s.Uuid))
if err := s.SyncWithConfiguration(data); err != nil {
return nil, err
}
return s, nil
@@ -300,13 +292,16 @@ func (s *Server) Sync() error {
return errors.New(rerr.String())
}
return s.SyncWithConfiguration(cfg)
}
func (s *Server) SyncWithConfiguration(cfg *api.ServerConfigurationResponse) error {
// Update the data structure and persist it to the disk.
if err := s.UpdateDataStructure(cfg.Settings, false); err != nil {
return errors.WithStack(err)
}
s.processConfiguration = cfg.ProcessConfiguration
return nil
}
@@ -354,11 +349,15 @@ func (s *Server) SetState(state string) error {
//
// We also get the benefit of server status changes always propagating corrected configurations
// to the disk should we forget to do it elsewhere.
go func(server *Server) {
if _, err := server.WriteConfigurationToDisk(); err != nil {
go func() {
/*if _, err := server.WriteConfigurationToDisk(); err != nil {
zap.S().Warnw("failed to write server state change to disk", zap.String("server", server.Uuid), zap.Error(err))
}*/
if err := SaveServerStates(); err != nil {
zap.S().Warnw("failed to write server states to disk", zap.Error(err))
}
}(s)
}()
zap.S().Debugw("saw server status change event", zap.String("server", s.Uuid), zap.String("status", s.State))
@@ -414,4 +413,4 @@ func (s *Server) HandlePowerAction(action PowerAction) error {
default:
return errors.New("an invalid power action was provided")
}
}
}

88
server/state.go Normal file
View File

@@ -0,0 +1,88 @@
package server
import (
"encoding/json"
"github.com/pkg/errors"
"io/ioutil"
"os"
"sync"
)
var (
statesLock sync.Mutex
statesFile = "data/states.json"
)
// DoesStatesFileExist .
func DoesStatesFileExist() (bool, error) {
statesLock.Lock()
defer statesLock.Unlock()
if _, err := os.Stat(statesFile); err != nil {
if !os.IsNotExist(err) {
return false, errors.WithStack(err)
}
return false, nil
}
return true, nil
}
// FetchServerStates .
func FetchServerStates() (map[string]string, error) {
// Check if the states file exists.
exists, err := DoesStatesFileExist()
if err != nil {
return nil, errors.WithStack(err)
}
// Request a lock after we check if the file exists.
statesLock.Lock()
defer statesLock.Unlock()
// Return an empty map if the file does not exist.
if !exists {
return map[string]string{}, nil
}
// Open the states file.
f, err := os.Open(statesFile)
if err != nil {
return nil, errors.WithStack(err)
}
defer f.Close()
// Convert the json object to a map.
states := map[string]string{}
if err := json.NewDecoder(f).Decode(&states); err != nil {
return nil, errors.WithStack(err)
}
return states, nil
}
// SaveServerStates .
func SaveServerStates() error {
// Get the states of all servers on the daemon.
states := map[string]string{}
for _, s := range GetServers().All() {
states[s.Uuid] = s.State
}
// Convert the map to a json object.
data, err := json.Marshal(states)
if err != nil {
return errors.WithStack(err)
}
statesLock.Lock()
defer statesLock.Unlock()
// Write the data to the file
if err := ioutil.WriteFile(statesFile, data, 0644); err != nil {
return errors.WithStack(err)
}
return nil
}

View File

@@ -24,7 +24,7 @@ func (s *Server) UpdateDataStructure(data []byte, background bool) error {
// Don't allow obviously corrupted data to pass through into this function. If the UUID
// doesn't match something has gone wrong and the API is attempting to meld this server
// instance into a totally different one, which would be bad.
if src.Uuid != "" && src.Uuid != s.Uuid {
if src.Uuid != "" && s.Uuid != "" && src.Uuid != s.Uuid {
return errors.New("attempting to merge a data stack with an invalid UUID")
}
@@ -69,9 +69,9 @@ func (s *Server) UpdateDataStructure(data []byte, background bool) error {
s.Allocations.Mappings = src.Allocations.Mappings
}
if _, err := s.WriteConfigurationToDisk(); err != nil {
/*if _, err := s.WriteConfigurationToDisk(); err != nil {
return errors.WithStack(err)
}
}*/
if background {
s.runBackgroundActions()