2019-11-24 23:08:38 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2019-11-25 03:57:20 +00:00
|
|
|
"github.com/buger/jsonparser"
|
2019-12-22 05:02:02 +00:00
|
|
|
"github.com/creasty/defaults"
|
2019-11-24 23:08:38 +00:00
|
|
|
"github.com/imdario/mergo"
|
|
|
|
"github.com/pkg/errors"
|
2019-11-30 23:19:08 +00:00
|
|
|
"go.uber.org/zap"
|
|
|
|
"os"
|
2019-11-24 23:08:38 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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.
|
2019-12-22 21:21:21 +00:00
|
|
|
func (s *Server) UpdateDataStructure(data []byte, background bool) error {
|
|
|
|
src := new(Server)
|
|
|
|
if err := json.Unmarshal(data, src); err != nil {
|
2019-11-24 23:08:38 +00:00
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
2019-11-25 03:57:20 +00:00
|
|
|
// 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.
|
2019-11-25 04:01:38 +00:00
|
|
|
if src.Uuid != "" && src.Uuid != s.Uuid {
|
2019-11-25 03:57:20 +00:00
|
|
|
return errors.New("attempting to merge a data stack with an invalid UUID")
|
|
|
|
}
|
|
|
|
|
2019-12-22 05:02:02 +00:00
|
|
|
// Set the default values in the interface that we unmarshaled into.
|
2019-12-22 21:21:21 +00:00
|
|
|
if err := defaults.Set(src); err != nil {
|
|
|
|
return errors.WithStack(err)
|
2019-12-22 05:02:02 +00:00
|
|
|
}
|
|
|
|
|
2019-11-24 23:08:38 +00:00
|
|
|
// 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.
|
2019-11-25 03:57:20 +00:00
|
|
|
if err := mergo.Merge(s, src, mergo.WithOverride); err != nil {
|
2019-11-24 23:08:38 +00:00
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
2019-11-25 03:57:20 +00:00
|
|
|
// Mergo can't quite handle this boolean value correctly, so for now we'll just
|
|
|
|
// handle this edge case manually since none of the other data passed through in this
|
|
|
|
// request is going to be boolean. Allegedly.
|
|
|
|
if v, err := jsonparser.GetBoolean(data, "container", "oom_disabled"); err != nil {
|
|
|
|
if err != jsonparser.KeyPathNotFoundError {
|
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
s.Container.OomDisabled = v
|
|
|
|
}
|
|
|
|
|
2019-11-30 23:46:28 +00:00
|
|
|
// Mergo also cannot handle this boolean value.
|
|
|
|
if v, err := jsonparser.GetBoolean(data, "suspended"); err != nil {
|
|
|
|
if err != jsonparser.KeyPathNotFoundError {
|
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
s.Suspended = v
|
|
|
|
}
|
|
|
|
|
2019-11-25 04:54:03 +00:00
|
|
|
// Environment and Mappings should be treated as a full update at all times, never a
|
|
|
|
// true patch, otherwise we can't know what we're passing along.
|
2019-11-30 23:46:28 +00:00
|
|
|
if src.EnvVars != nil && len(src.EnvVars) > 0 {
|
2019-11-25 04:54:03 +00:00
|
|
|
s.EnvVars = src.EnvVars
|
|
|
|
}
|
|
|
|
|
2019-12-01 00:37:11 +00:00
|
|
|
if src.Allocations.Mappings != nil && len(src.Allocations.Mappings) > 0 {
|
2019-11-25 04:54:03 +00:00
|
|
|
s.Allocations.Mappings = src.Allocations.Mappings
|
|
|
|
}
|
|
|
|
|
2019-11-24 23:08:38 +00:00
|
|
|
if _, err := s.WriteConfigurationToDisk(); err != nil {
|
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
2019-12-22 21:21:21 +00:00
|
|
|
if background {
|
|
|
|
s.runBackgroundActions()
|
|
|
|
}
|
2019-11-30 23:19:08 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Runs through different actions once a server's configuration has been persisted
|
|
|
|
// to the disk. This function does not return anything as any failures should be logged
|
|
|
|
// but have no effect on actually updating the server itself.
|
|
|
|
//
|
|
|
|
// These tasks run in independent threads where relevant to speed up any updates
|
|
|
|
// that need to happen.
|
|
|
|
func (s *Server) runBackgroundActions() {
|
|
|
|
// Update the environment in place, allowing memory and CPU usage to be adjusted
|
|
|
|
// on the fly without the user needing to reboot (theoretically).
|
|
|
|
go func(server *Server) {
|
|
|
|
if err := server.Environment.InSituUpdate(); err != nil {
|
|
|
|
zap.S().Warnw(
|
|
|
|
"failed to perform in-situ update of server environment",
|
|
|
|
zap.String("server", server.Uuid),
|
|
|
|
zap.Error(err),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}(s)
|
|
|
|
|
|
|
|
// Check if the server is now suspended, and if so and the process is not terminated
|
|
|
|
// yet, do it immediately.
|
|
|
|
go func(server *Server) {
|
|
|
|
if server.Suspended && server.State != ProcessOfflineState {
|
2019-11-30 23:46:28 +00:00
|
|
|
zap.S().Infow("server suspended with running process state, terminating now", zap.String("server", server.Uuid))
|
|
|
|
|
2019-11-30 23:19:08 +00:00
|
|
|
if err := server.Environment.Terminate(os.Kill); err != nil {
|
|
|
|
zap.S().Warnw(
|
|
|
|
"failed to terminate server environment after seeing suspension",
|
|
|
|
zap.String("server", server.Uuid),
|
|
|
|
zap.Error(err),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}(s)
|
2019-11-25 04:54:03 +00:00
|
|
|
}
|