Add (unchecked) code to do an in-situ replacement of build settings
This commit is contained in:
parent
7f4c29580a
commit
9f4518fc58
1
go.mod
1
go.mod
|
@ -19,6 +19,7 @@ require (
|
||||||
github.com/google/uuid v1.1.1
|
github.com/google/uuid v1.1.1
|
||||||
github.com/gorilla/websocket v1.4.0
|
github.com/gorilla/websocket v1.4.0
|
||||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
|
github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
|
||||||
|
github.com/imdario/mergo v0.3.8 // indirect
|
||||||
github.com/julienschmidt/httprouter v1.2.0
|
github.com/julienschmidt/httprouter v1.2.0
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
github.com/olebedev/emitter v0.0.0-20190110104742-e8d1457e6aee
|
github.com/olebedev/emitter v0.0.0-20190110104742-e8d1457e6aee
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -39,6 +39,8 @@ github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQ
|
||||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
||||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
||||||
|
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
|
|
16
http.go
16
http.go
|
@ -407,6 +407,21 @@ func (rt *Router) routeServerSendCommand(w http.ResponseWriter, r *http.Request,
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rt *Router) routeServerUpdate(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
s := rt.Servers.Get(ps.ByName("server"))
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
data := rt.ReaderToBytes(r.Body)
|
||||||
|
if err := s.UpdateDataStructure(data); err != nil {
|
||||||
|
zap.S().Errorw("failed to update a server's data structure", zap.String("server", s.Uuid), zap.Error(err))
|
||||||
|
|
||||||
|
http.Error(w, "failed to update data structure", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
func (rt *Router) routeCreateServer(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
func (rt *Router) routeCreateServer(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
@ -457,6 +472,7 @@ func (rt *Router) ConfigureRouter() *httprouter.Router {
|
||||||
router.POST("/api/servers/:server/files/delete", rt.AuthenticateRequest(rt.routeServerDeleteFile))
|
router.POST("/api/servers/:server/files/delete", rt.AuthenticateRequest(rt.routeServerDeleteFile))
|
||||||
router.POST("/api/servers/:server/power", rt.AuthenticateRequest(rt.routeServerPower))
|
router.POST("/api/servers/:server/power", rt.AuthenticateRequest(rt.routeServerPower))
|
||||||
router.POST("/api/servers/:server/commands", rt.AuthenticateRequest(rt.routeServerSendCommand))
|
router.POST("/api/servers/:server/commands", rt.AuthenticateRequest(rt.routeServerSendCommand))
|
||||||
|
router.PATCH("/api/servers/:server", rt.AuthenticateRequest(rt.routeServerUpdate))
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,6 @@ import (
|
||||||
"github.com/pterodactyl/wings/config"
|
"github.com/pterodactyl/wings/config"
|
||||||
"github.com/pterodactyl/wings/server"
|
"github.com/pterodactyl/wings/server"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Installer struct {
|
type Installer struct {
|
||||||
|
@ -46,6 +44,8 @@ func New(data []byte) (error, *Installer) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.Init()
|
||||||
|
|
||||||
s.Allocations.DefaultMapping.Ip = getString(data, "allocations", "default", "ip")
|
s.Allocations.DefaultMapping.Ip = getString(data, "allocations", "default", "ip")
|
||||||
s.Allocations.DefaultMapping.Port = int(getInt(data, "allocations", "default", "port"))
|
s.Allocations.DefaultMapping.Port = int(getInt(data, "allocations", "default", "port"))
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ func New(data []byte) (error, *Installer) {
|
||||||
|
|
||||||
s.Container.Image = getString(data, "container", "image")
|
s.Container.Image = getString(data, "container", "image")
|
||||||
|
|
||||||
b, err := WriteConfigurationToDisk(s)
|
b, err := s.WriteConfigurationToDisk()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err, nil
|
return err, nil
|
||||||
}
|
}
|
||||||
|
@ -108,28 +108,6 @@ func (i *Installer) Execute() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Writes the server configuration to the disk and return the byte representation
|
|
||||||
// of the configuration object. This allows us to pass it directly into the
|
|
||||||
// servers.FromConfiguration() function.
|
|
||||||
func WriteConfigurationToDisk(s *server.Server) ([]byte, error) {
|
|
||||||
f, err := os.Create("data/servers/" + s.Uuid + ".yml")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
b, err := yaml.Marshal(&s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := f.Write(b); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns a string value from the JSON data provided.
|
// Returns a string value from the JSON data provided.
|
||||||
func getString(data []byte, key ...string) string {
|
func getString(data []byte, key ...string) string {
|
||||||
value, _ := jsonparser.GetString(data, key...)
|
value, _ := jsonparser.GetString(data, key...)
|
||||||
|
|
|
@ -14,6 +14,11 @@ type Environment interface {
|
||||||
// for this specific server instance.
|
// for this specific server instance.
|
||||||
IsRunning() (bool, error)
|
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
|
// Runs before the environment is started. If an error is returned starting will
|
||||||
// not occur, otherwise proceeds as normal.
|
// not occur, otherwise proceeds as normal.
|
||||||
OnBeforeStart() error
|
OnBeforeStart() error
|
||||||
|
|
|
@ -114,6 +114,35 @@ func (d *DockerEnvironment) IsRunning() (bool, error) {
|
||||||
return c.State.Running, nil
|
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.
|
// 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
|
// 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.
|
// 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)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var oomDisabled = true
|
|
||||||
|
|
||||||
// Ensure the data directory exists before getting too far through this process.
|
// Ensure the data directory exists before getting too far through this process.
|
||||||
if err := d.Server.Filesystem.EnsureDataDirectory(); err != nil {
|
if err := d.Server.Filesystem.EnsureDataDirectory(); err != nil {
|
||||||
return errors.WithStack(err)
|
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
|
// Define resource limits for the container based on the data passed through
|
||||||
// from the Panel.
|
// from the Panel.
|
||||||
Resources: container.Resources{
|
Resources: d.getResourcesForServer(),
|
||||||
// @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,
|
|
||||||
},
|
|
||||||
|
|
||||||
// @todo make this configurable again
|
// @todo make this configurable again
|
||||||
DNS: []string{"1.1.1.1", "8.8.8.8"},
|
DNS: []string{"1.1.1.1", "8.8.8.8"},
|
||||||
|
@ -677,3 +692,26 @@ func (d *DockerEnvironment) exposedPorts() nat.PortSet {
|
||||||
|
|
||||||
return out
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,6 +44,11 @@ type Server struct {
|
||||||
Container struct {
|
Container struct {
|
||||||
// Defines the Docker image that will be used for this server
|
// Defines the Docker image that will be used for this server
|
||||||
Image string `json:"image,omitempty"`
|
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"`
|
} `json:"container,omitempty"`
|
||||||
|
|
||||||
Environment Environment `json:"-" yaml:"-"`
|
Environment Environment `json:"-" yaml:"-"`
|
||||||
|
@ -62,6 +68,10 @@ type Server struct {
|
||||||
// fetched from the Pterodactyl Server instance each time the server process is
|
// fetched from the Pterodactyl Server instance each time the server process is
|
||||||
// started, and then cached here.
|
// started, and then cached here.
|
||||||
processConfiguration *api.ServerConfiguration
|
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
|
// 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
|
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
|
// 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
|
// given struct using a YAML marshaler. This will also configure the given environment
|
||||||
// for a server.
|
// for a server.
|
||||||
func FromConfiguration(data []byte, cfg *config.SystemConfiguration) (*Server, error) {
|
func FromConfiguration(data []byte, cfg *config.SystemConfiguration) (*Server, error) {
|
||||||
s := &Server{}
|
s := &Server{}
|
||||||
|
s.Init()
|
||||||
|
|
||||||
if err := yaml.Unmarshal(data, s); err != nil {
|
if err := yaml.Unmarshal(data, s); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
31
server/server_configuration.go
Normal file
31
server/server_configuration.go
Normal 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
34
server/server_update.go
Normal 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()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user