Emit server status events

This commit is contained in:
Dane Everitt 2019-04-21 12:02:28 -07:00
parent bed30d9229
commit 8795e7d739
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
4 changed files with 70 additions and 39 deletions

View File

@ -120,6 +120,7 @@ func (d *DockerEnvironment) Start() error {
// No reason to try starting a container that is already running. // No reason to try starting a container that is already running.
if c.State.Running { if c.State.Running {
d.Server.Emit(StatusEvent, ProcessRunningState)
if !d.attached { if !d.attached {
return d.Attach() return d.Attach()
} }
@ -129,7 +130,9 @@ func (d *DockerEnvironment) Start() error {
opts := types.ContainerStartOptions{} opts := types.ContainerStartOptions{}
d.Server.Emit(StatusEvent, ProcessStartingState)
if err := d.Client.ContainerStart(context.Background(), d.Server.Uuid, opts); err != nil { if err := d.Client.ContainerStart(context.Background(), d.Server.Uuid, opts); err != nil {
d.Server.Emit(StatusEvent, ProcessOfflineState)
return err return err
} }
@ -142,6 +145,7 @@ func (d *DockerEnvironment) Start() error {
func (d *DockerEnvironment) Stop() error { func (d *DockerEnvironment) Stop() error {
t := time.Second * 10 t := time.Second * 10
d.Server.Emit(StatusEvent, ProcessStoppingState)
return d.Client.ContainerStop(context.Background(), d.Server.Uuid, &t) return d.Client.ContainerStop(context.Background(), d.Server.Uuid, &t)
} }
@ -158,6 +162,7 @@ func (d *DockerEnvironment) Terminate(signal os.Signal) error {
return nil return nil
} }
d.Server.Emit(StatusEvent, ProcessStoppingState)
return d.Client.ContainerKill(ctx, d.Server.Uuid, signal.String()) return d.Client.ContainerKill(ctx, d.Server.Uuid, signal.String())
} }
@ -193,6 +198,7 @@ func (d *DockerEnvironment) Attach() error {
go func() { go func() {
defer d.stream.Close() defer d.stream.Close()
defer func() { defer func() {
d.Server.Emit(StatusEvent, ProcessOfflineState)
d.attached = false d.attached = false
}() }()
@ -232,7 +238,7 @@ func (d *DockerEnvironment) FollowConsoleOutput() error {
} }
if err := s.Err(); err != nil { if err := s.Err(); err != nil {
zap.S().Errorw("error in scanner", zap.Error(err)) zap.S().Warnw("error processing scanner line in console output", zap.String("server", d.Server.Uuid), zap.Error(err))
} }
}(reader) }(reader)

View File

@ -7,6 +7,7 @@ type EventListenerFunction *func(string)
// Defines all of the possible output events for a server. // Defines all of the possible output events for a server.
const ( const (
ConsoleOutputEvent = "console" ConsoleOutputEvent = "console"
StatusEvent = "status"
) )
// Adds an event listener for the server instance. // Adds an event listener for the server instance.

View File

@ -1,8 +1,9 @@
package server package server
import ( import (
"errors"
"fmt"
"github.com/patrickmn/go-cache" "github.com/patrickmn/go-cache"
"github.com/pkg/errors"
"github.com/pterodactyl/wings/config" "github.com/pterodactyl/wings/config"
"github.com/remeh/sizedwaitgroup" "github.com/remeh/sizedwaitgroup"
"go.uber.org/zap" "go.uber.org/zap"
@ -14,16 +15,6 @@ import (
"time" "time"
) )
// Defines states that identify if the server is running or not.
const (
StateOffline = "off"
StateStarting = "starting"
StateRunning = "running"
StateStopping = "stopping"
)
type ProcessState string
// High level definition for a server instance being controlled by Wings. // High level definition for a server instance being controlled by Wings.
type Server struct { type Server struct {
// The unique identifier for the server that should be used when referencing // The unique identifier for the server that should be used when referencing
@ -36,7 +27,7 @@ type Server struct {
Suspended bool `json:"suspended"` Suspended bool `json:"suspended"`
// The power state of the server. // The power state of the server.
State ProcessState `json:"state"` State string `json:"state"`
// The command that should be used when booting up the server instance. // The command that should be used when booting up the server instance.
Invocation string `json:"invocation"` Invocation string `json:"invocation"`
@ -190,7 +181,7 @@ func FromConfiguration(data []byte, cfg *config.SystemConfiguration) (*Server, e
return nil, err return nil, err
} }
withConfiguration := func (e *DockerEnvironment) { withConfiguration := func(e *DockerEnvironment) {
e.User = cfg.User.Uid e.User = cfg.User.Uid
e.TimezonePath = cfg.TimezonePath e.TimezonePath = cfg.TimezonePath
e.Server = s e.Server = s
@ -207,7 +198,7 @@ func FromConfiguration(data []byte, cfg *config.SystemConfiguration) (*Server, e
} }
s.Environment = env s.Environment = env
s.Cache = cache.New(time.Minute * 10, time.Minute * 15) s.Cache = cache.New(time.Minute*10, time.Minute*15)
s.Filesystem = &Filesystem{ s.Filesystem = &Filesystem{
Root: cfg.Data, Root: cfg.Data,
Server: s, Server: s,
@ -221,22 +212,6 @@ func (s *Server) ReadLogfile(len int64) ([]string, error) {
return s.Environment.Readlog(len) return s.Environment.Readlog(len)
} }
// Sets the state of the server process.
func (s *Server) SetState(state ProcessState) error {
switch state {
case StateOffline:
case StateRunning:
case StateStarting:
case StateStopping:
s.State = state
break
default:
return errors.New("invalid state provided")
}
return nil
}
// Determine if the server is bootable in it's current state or not. This will not // Determine if the server is bootable in it's current state or not. This will not
// indicate why a server is not bootable, only if it is. // indicate why a server is not bootable, only if it is.
func (s *Server) IsBootable() bool { func (s *Server) IsBootable() bool {
@ -249,4 +224,35 @@ func (s *Server) IsBootable() bool {
// for the server is setup, and that all of the necessary files are created. // for the server is setup, and that all of the necessary files are created.
func (s *Server) CreateEnvironment() error { func (s *Server) CreateEnvironment() error {
return s.Environment.Create() return s.Environment.Create()
} }
const (
ProcessOfflineState = "offline"
ProcessStartingState = "starting"
ProcessRunningState = "running"
ProcessStoppingState = "stopping"
)
// Sets the state of the server internally. This function handles crash detection as
// well as reporting to event listeners for the server.
func (s *Server) SetState(state string) error {
switch state {
case ProcessOfflineState:
case ProcessStartingState:
case ProcessRunningState:
case ProcessStoppingState:
s.State = state
break
default:
return errors.New(fmt.Sprintf("invalid server state received: %s", state))
}
// Emit the event to any listeners that are currently registered.
s.Emit(StatusEvent, state)
// @todo handle a crash event here. Need to port the logic from the Nodejs daemon
// into this daemon. I believe its basically just if state != stopping && newState = stopped
// then crashed.
return nil
}

View File

@ -12,6 +12,14 @@ import (
"sync" "sync"
) )
const (
SetStateEvent = "set state"
SendServerLogsEvent = "send logs"
SendCommandEvent = "send command"
ConsoleOutputEvent = "console output"
ServerStatusEvent = "status"
)
type WebsocketMessage struct { type WebsocketMessage struct {
// The event to perform. Should be one of the following that are supported: // The event to perform. Should be one of the following that are supported:
// //
@ -46,18 +54,28 @@ func (rt *Router) routeWebsocket(w http.ResponseWriter, r *http.Request, ps http
s := rt.Servers.Get(ps.ByName("server")) s := rt.Servers.Get(ps.ByName("server"))
handler := WebsocketHandler{ handler := WebsocketHandler{
Server: s, Server: s,
Mutex: sync.Mutex{}, Mutex: sync.Mutex{},
Connection: c, Connection: c,
} }
handleOutput := func(data string) { handleOutput := func(data string) {
handler.SendJson(&WebsocketMessage{ handler.SendJson(&WebsocketMessage{
Event: "console output", Event: ConsoleOutputEvent,
Args: []string{data}, Args: []string{data},
}) })
} }
handleServerStatus := func(data string) {
handler.SendJson(&WebsocketMessage{
Event: ServerStatusEvent,
Args: []string{data},
})
}
s.AddListener(server.StatusEvent, &handleServerStatus)
defer s.RemoveListener(server.StatusEvent, &handleServerStatus)
s.AddListener(server.ConsoleOutputEvent, &handleOutput) s.AddListener(server.ConsoleOutputEvent, &handleOutput)
defer s.RemoveListener(server.ConsoleOutputEvent, &handleOutput) defer s.RemoveListener(server.ConsoleOutputEvent, &handleOutput)
@ -102,7 +120,7 @@ func (wsh *WebsocketHandler) HandleInbound(m WebsocketMessage) error {
} }
switch m.Event { switch m.Event {
case "set state": case SetStateEvent:
{ {
var err error var err error
switch strings.Join(m.Args, "") { switch strings.Join(m.Args, "") {
@ -121,7 +139,7 @@ func (wsh *WebsocketHandler) HandleInbound(m WebsocketMessage) error {
return err return err
} }
} }
case "send logs": case SendServerLogsEvent:
{ {
logs, err := wsh.Server.Environment.Readlog(1024 * 5) logs, err := wsh.Server.Environment.Readlog(1024 * 5)
if err != nil { if err != nil {
@ -130,14 +148,14 @@ func (wsh *WebsocketHandler) HandleInbound(m WebsocketMessage) error {
for _, line := range logs { for _, line := range logs {
wsh.SendJson(&WebsocketMessage{ wsh.SendJson(&WebsocketMessage{
Event: "console output", Event: ConsoleOutputEvent,
Args: []string{line}, Args: []string{line},
}) })
} }
return nil return nil
} }
case "send command": case SendCommandEvent:
{ {
return wsh.Server.Environment.SendCommand(strings.Join(m.Args, "")) return wsh.Server.Environment.SendCommand(strings.Join(m.Args, ""))
} }