Add (unchecked) code to do an in-situ replacement of build settings

This commit is contained in:
Dane Everitt
2019-11-24 15:08:38 -08:00
parent 7f4c29580a
commit 9f4518fc58
9 changed files with 163 additions and 41 deletions

View File

@@ -14,6 +14,11 @@ type Environment interface {
// for this specific server instance.
IsRunning() (bool, error)
// Performs an update of server resource limits without actually stopping the server
// process. This only executes if the environment supports it, otherwise it is
// a no-op.
InSituUpdate() error
// Runs before the environment is started. If an error is returned starting will
// not occur, otherwise proceeds as normal.
OnBeforeStart() error

View File

@@ -114,6 +114,35 @@ func (d *DockerEnvironment) IsRunning() (bool, error) {
return c.State.Running, nil
}
// Performs an in-place update of the Docker container's resource limits without actually
// making any changes to the operational state of the container. This allows memory, cpu,
// and IO limitations to be adjusted on the fly for individual instances.
func (d *DockerEnvironment) InSituUpdate() error {
if _, err := d.Client.ContainerInspect(context.Background(), d.Server.Uuid); err != nil {
// If the container doesn't exist for some reason there really isn't anything
// we can do to fix that in this process (it doesn't make sense at least). In those
// cases just return without doing anything since we still want to save the configuration
// to the disk.
//
// We'll let a boot process make modifications to the container if needed at this point.
if client.IsErrNotFound(err) {
return nil
}
return errors.WithStack(err)
}
u := container.UpdateConfig{
Resources: d.getResourcesForServer(),
}
if _, err := d.Client.ContainerUpdate(context.Background(), d.Server.Uuid, u); err != nil {
return errors.WithStack(err)
}
return nil
}
// Run before the container starts and get the process configuration from the Panel.
// This is important since we use this to check configuration files as well as ensure
// we always have the latest version of an egg available for server processes.
@@ -412,8 +441,6 @@ func (d *DockerEnvironment) Create() error {
return errors.WithStack(err)
}
var oomDisabled = true
// Ensure the data directory exists before getting too far through this process.
if err := d.Server.Filesystem.EnsureDataDirectory(); err != nil {
return errors.WithStack(err)
@@ -477,19 +504,7 @@ func (d *DockerEnvironment) Create() error {
// Define resource limits for the container based on the data passed through
// from the Panel.
Resources: container.Resources{
// @todo memory limit should be slightly higher than the reservation
Memory: d.Server.Build.MemoryLimit * 1000000,
MemoryReservation: d.Server.Build.MemoryLimit * 1000000,
MemorySwap: d.Server.Build.ConvertedSwap(),
CPUQuota: d.Server.Build.ConvertedCpuLimit(),
CPUPeriod: 100000,
CPUShares: 1024,
BlkioWeight: d.Server.Build.IoWeight,
OomKillDisable: &oomDisabled,
},
Resources: d.getResourcesForServer(),
// @todo make this configurable again
DNS: []string{"1.1.1.1", "8.8.8.8"},
@@ -677,3 +692,26 @@ func (d *DockerEnvironment) exposedPorts() nat.PortSet {
return out
}
// Formats the resources available to a server instance in such as way that Docker will
// generate a matching environment in the container.
func (d *DockerEnvironment) getResourcesForServer() container.Resources {
b := true
oomDisabled := d.Server.Container.OomDisabled
if oomDisabled == nil {
oomDisabled = &b
}
return container.Resources{
// @todo memory limit should be slightly higher than the reservation
Memory: d.Server.Build.MemoryLimit * 1000000,
MemoryReservation: d.Server.Build.MemoryLimit * 1000000,
MemorySwap: d.Server.Build.ConvertedSwap(),
CPUQuota: d.Server.Build.ConvertedCpuLimit(),
CPUPeriod: 100000,
CPUShares: 1024,
BlkioWeight: d.Server.Build.IoWeight,
OomKillDisable: oomDisabled,
}
}

View File

@@ -13,6 +13,7 @@ import (
"os"
"path"
"strings"
"sync"
"time"
)
@@ -43,6 +44,11 @@ type Server struct {
Container struct {
// Defines the Docker image that will be used for this server
Image string `json:"image,omitempty"`
// If set to true, OOM killer will be disabled on the server's Docker container.
// If not present (nil) we will default to disabling it.
OomDisabled *bool `json:"oom_disabled,omitempty"`
// Defines if the container needs to be rebuilt on the next boot.
RebuildRequired bool `json:"rebuild_required,omitempty"`
} `json:"container,omitempty"`
Environment Environment `json:"-" yaml:"-"`
@@ -62,6 +68,10 @@ type Server struct {
// fetched from the Pterodactyl Server instance each time the server process is
// started, and then cached here.
processConfiguration *api.ServerConfiguration
// Internal mutex used to block actions that need to occur sequentially, such as
// writing the configuration to the disk.
mutex *sync.Mutex
}
// The build settings for a given server that impact docker container creation and
@@ -179,11 +189,18 @@ func LoadDirectory(dir string, cfg *config.SystemConfiguration) ([]*Server, erro
return servers, nil
}
// Initializes the default required internal struct components for a Server.
func (s *Server) Init() {
s.listeners = make(map[string][]EventListenerFunction)
s.mutex = &sync.Mutex{}
}
// Initalizes 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) {
s := &Server{}
s.Init()
if err := yaml.Unmarshal(data, s); err != nil {
return nil, err
@@ -277,4 +294,4 @@ func (s *Server) SetState(state string) error {
// Gets the process configuration data for the server.
func (s *Server) GetProcessConfiguration() (*api.ServerConfiguration, error) {
return api.NewRequester().GetServerConfiguration(s.Uuid)
}
}

View File

@@ -0,0 +1,31 @@
package server
import (
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
"os"
)
// Writes the server configuration to the disk. The saved configuration will be returned
// back to the calling function to use if desired.
func (s *Server) WriteConfigurationToDisk() ([]byte, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
f, err := os.Create("data/servers/" + s.Uuid + ".yml")
if err != nil {
return nil, errors.WithStack(err)
}
defer f.Close()
b, err := yaml.Marshal(&s)
if err != nil {
return nil, errors.WithStack(err)
}
if _, err := f.Write(b); err != nil {
return nil, errors.WithStack(err)
}
return b, nil
}

34
server/server_update.go Normal file
View File

@@ -0,0 +1,34 @@
package server
import (
"encoding/json"
"github.com/imdario/mergo"
"github.com/pkg/errors"
)
// Merges data passed through in JSON form into the existing server object.
// Any changes to the build settings will apply immediately in the environment
// if the environment supports it.
//
// The server will be marked as requiring a rebuild on the next boot sequence,
// it is up to the specific environment to determine what needs to happen when
// that is the case.
func (s *Server) UpdateDataStructure(data []byte) error {
src := Server{}
if err := json.Unmarshal(data, &src); err != nil {
return errors.WithStack(err)
}
// Merge the new data object that we have received with the existing server data object
// and then save it to the disk so it is persistent.
if err := mergo.Merge(&s, src); err != nil {
return errors.WithStack(err)
}
s.Container.RebuildRequired = true
if _, err := s.WriteConfigurationToDisk(); err != nil {
return errors.WithStack(err)
}
return s.Environment.InSituUpdate()
}