2020-04-10 22:33:30 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2020-04-11 01:22:18 +00:00
|
|
|
"fmt"
|
2020-04-10 22:33:30 +00:00
|
|
|
"github.com/pkg/errors"
|
2020-04-11 01:22:18 +00:00
|
|
|
"go.uber.org/zap"
|
2020-04-10 23:55:36 +00:00
|
|
|
"io/ioutil"
|
2020-04-10 22:33:30 +00:00
|
|
|
"os"
|
2020-04-10 22:37:10 +00:00
|
|
|
"sync"
|
2020-04-10 22:33:30 +00:00
|
|
|
)
|
|
|
|
|
2020-04-11 01:07:57 +00:00
|
|
|
const stateFileLocation = "data/.states.json"
|
|
|
|
|
|
|
|
var stateMutex sync.Mutex
|
2020-04-10 22:33:30 +00:00
|
|
|
|
2020-04-11 01:07:57 +00:00
|
|
|
// Checks if the state tracking file exists, if not it is generated.
|
|
|
|
func ensureStateFileExists() (bool, error) {
|
|
|
|
stateMutex.Lock()
|
|
|
|
defer stateMutex.Unlock()
|
2020-04-10 22:37:10 +00:00
|
|
|
|
2020-04-11 01:07:57 +00:00
|
|
|
if _, err := os.Stat(stateFileLocation); err != nil {
|
2020-04-10 22:33:30 +00:00
|
|
|
if !os.IsNotExist(err) {
|
|
|
|
return false, errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
2020-04-11 01:07:57 +00:00
|
|
|
// Returns the state of the servers.
|
|
|
|
func getServerStates() (map[string]string, error) {
|
2020-04-10 22:33:30 +00:00
|
|
|
// Check if the states file exists.
|
2020-04-11 01:07:57 +00:00
|
|
|
exists, err := ensureStateFileExists()
|
2020-04-10 22:33:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
2020-04-10 22:37:10 +00:00
|
|
|
// Request a lock after we check if the file exists.
|
2020-04-11 01:07:57 +00:00
|
|
|
stateMutex.Lock()
|
|
|
|
defer stateMutex.Unlock()
|
2020-04-10 22:37:10 +00:00
|
|
|
|
2020-04-10 22:33:30 +00:00
|
|
|
// Return an empty map if the file does not exist.
|
|
|
|
if !exists {
|
|
|
|
return map[string]string{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open the states file.
|
2020-04-11 01:07:57 +00:00
|
|
|
f, err := os.Open(stateFileLocation)
|
2020-04-10 22:33:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.WithStack(err)
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
// Convert the json object to a map.
|
|
|
|
states := map[string]string{}
|
2020-04-10 23:55:36 +00:00
|
|
|
if err := json.NewDecoder(f).Decode(&states); err != nil {
|
2020-04-10 22:33:30 +00:00
|
|
|
return nil, errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return states, nil
|
|
|
|
}
|
|
|
|
|
2020-04-11 01:22:18 +00:00
|
|
|
// saveServerStates .
|
|
|
|
func saveServerStates() error {
|
2020-04-10 22:33:30 +00:00
|
|
|
// Get the states of all servers on the daemon.
|
|
|
|
states := map[string]string{}
|
|
|
|
for _, s := range GetServers().All() {
|
2020-04-11 01:22:18 +00:00
|
|
|
states[s.Uuid] = s.GetState()
|
2020-04-10 22:33:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Convert the map to a json object.
|
|
|
|
data, err := json.Marshal(states)
|
|
|
|
if err != nil {
|
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
2020-04-11 01:07:57 +00:00
|
|
|
stateMutex.Lock()
|
|
|
|
defer stateMutex.Unlock()
|
2020-04-10 22:37:10 +00:00
|
|
|
|
2020-04-10 22:33:30 +00:00
|
|
|
// Write the data to the file
|
2020-04-11 01:07:57 +00:00
|
|
|
if err := ioutil.WriteFile(stateFileLocation, data, 0644); err != nil {
|
2020-04-10 22:33:30 +00:00
|
|
|
return errors.WithStack(err)
|
|
|
|
}
|
|
|
|
|
2020-04-10 23:55:36 +00:00
|
|
|
return nil
|
2020-04-10 22:33:30 +00:00
|
|
|
}
|
2020-04-11 01:22:18 +00:00
|
|
|
|
|
|
|
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 {
|
|
|
|
if state != ProcessOfflineState && state != ProcessStartingState && state != ProcessRunningState && state != ProcessStoppingState {
|
|
|
|
return errors.New(fmt.Sprintf("invalid server state received: %s", state))
|
|
|
|
}
|
|
|
|
|
|
|
|
prevState := s.GetState()
|
|
|
|
|
|
|
|
// Obtain a mutex lock and update the current state of the server.
|
|
|
|
s.Lock()
|
|
|
|
s.State = state
|
|
|
|
|
|
|
|
// Emit the event to any listeners that are currently registered.
|
|
|
|
zap.S().Debugw("saw server status change event", zap.String("server", s.Uuid), zap.String("status", s.State))
|
|
|
|
s.Events().Publish(StatusEvent, s.State)
|
|
|
|
|
|
|
|
// Release the lock as it is no longer needed for the following actions.
|
|
|
|
s.Unlock()
|
|
|
|
|
|
|
|
// Persist this change to the disk immediately so that should the Daemon be stopped or
|
|
|
|
// crash we can immediately restore the server state.
|
|
|
|
//
|
|
|
|
// This really only makes a difference if all of the Docker containers are also stopped,
|
|
|
|
// but this was a highly requested feature and isn't hard to work with, so lets do it.
|
|
|
|
//
|
|
|
|
// 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() {
|
|
|
|
if err := saveServerStates(); err != nil {
|
|
|
|
zap.S().Warnw("failed to write server states to disk", zap.Error(err))
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// If server was in an online state, and is now in an offline state we should handle
|
|
|
|
// that as a crash event. In that scenario, check the last crash time, and the crash
|
|
|
|
// counter.
|
|
|
|
//
|
|
|
|
// In the event that we have passed the thresholds, don't do anything, otherwise
|
|
|
|
// automatically attempt to start the process back up for the user. This is done in a
|
|
|
|
// separate thread as to not block any actions currently taking place in the flow
|
|
|
|
// that called this function.
|
|
|
|
if (prevState == ProcessStartingState || prevState == ProcessRunningState) && s.GetState() == ProcessOfflineState {
|
|
|
|
zap.S().Infow("detected server as entering a potentially crashed state; running handler", zap.String("server", s.Uuid))
|
|
|
|
|
|
|
|
go func(server *Server) {
|
|
|
|
if err := server.handleServerCrash(); err != nil {
|
|
|
|
if IsTooFrequentCrashError(err) {
|
|
|
|
zap.S().Infow("did not restart server after crash; occurred too soon after last", zap.String("server", server.Uuid))
|
|
|
|
} else {
|
|
|
|
zap.S().Errorw("failed to handle server crash state", zap.String("server", server.Uuid), zap.Error(err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the current state of the server in a race-safe manner.
|
|
|
|
func (s *Server) GetState() string {
|
|
|
|
s.RLock()
|
|
|
|
defer s.RUnlock()
|
|
|
|
|
|
|
|
return s.State
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determines if the server state is running or not. This is different than the
|
|
|
|
// environment state, it is simply the tracked state from this daemon instance, and
|
|
|
|
// not the response from Docker.
|
|
|
|
func (s *Server) IsRunning() bool {
|
|
|
|
return s.GetState() == ProcessRunningState || s.GetState() == ProcessStartingState
|
|
|
|
}
|