[#3896cn] Add support for suspending a server and blocking boot when suspended
This commit is contained in:
		
							parent
							
								
									fa42ed92fb
								
							
						
					
					
						commit
						0ce95aa2e3
					
				
							
								
								
									
										11
									
								
								http.go
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								http.go
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -152,6 +152,17 @@ func (rt *Router) routeServerPower(w http.ResponseWriter, r *http.Request, ps ht
 | 
			
		|||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Because we route all of the actual bootup process to a seperate thread we need to
 | 
			
		||||
	// check the suspension status here, otherwise the user will hit the endpoint and then
 | 
			
		||||
	// just sit there wondering why it returns a success but nothing actually happens.
 | 
			
		||||
	//
 | 
			
		||||
	// We don't really care about any of the other actions at this point, they'll all result
 | 
			
		||||
	// in the process being stopped, which should have happened anyways if the server is suspended.
 | 
			
		||||
	if action.Action == "start" && s.Suspended {
 | 
			
		||||
		http.Error(w, "server is suspended", http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Pass the actual heavy processing off to a seperate thread to handle so that
 | 
			
		||||
	// we can immediately return a response from the server.
 | 
			
		||||
	go func(a string, s *server.Server) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -215,6 +215,16 @@ func (d *DockerEnvironment) Start() error {
 | 
			
		|||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// If the server is suspended the user shouldn't be able to boot it, in those cases
 | 
			
		||||
	// return a suspension error and let the calling area handle the issue.
 | 
			
		||||
	//
 | 
			
		||||
	// Theoretically you'd have the Panel handle all of this logic, but we cannot do that
 | 
			
		||||
	// because we allow the websocket to control the server power state as well, so we'll
 | 
			
		||||
	// need to handle that action in here.
 | 
			
		||||
	if d.Server.Suspended {
 | 
			
		||||
		return &suspendedError{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c, err := d.Client.ContainerInspect(context.Background(), d.Server.Uuid)
 | 
			
		||||
	if err != nil && !client.IsErrNotFound(err) {
 | 
			
		||||
		return errors.WithStack(err)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										14
									
								
								server/errors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								server/errors.go
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
package server
 | 
			
		||||
 | 
			
		||||
type suspendedError struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *suspendedError) Error() string {
 | 
			
		||||
	return "server is currently in a suspended state"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsSuspendedError(err error) bool {
 | 
			
		||||
	_, ok := err.(*suspendedError)
 | 
			
		||||
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,6 +5,8 @@ import (
 | 
			
		|||
	"github.com/buger/jsonparser"
 | 
			
		||||
	"github.com/imdario/mergo"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"go.uber.org/zap"
 | 
			
		||||
	"os"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Merges data passed through in JSON form into the existing server object.
 | 
			
		||||
| 
						 | 
				
			
			@ -59,5 +61,41 @@ func (s *Server) UpdateDataStructure(data []byte) error {
 | 
			
		|||
		return errors.WithStack(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return s.Environment.InSituUpdate()
 | 
			
		||||
	s.runBackgroundActions()
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
			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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -276,8 +276,10 @@ func (wsh *WebsocketHandler) SendErrorJson(err error) error {
 | 
			
		|||
	defer wsh.Mutex.Unlock()
 | 
			
		||||
 | 
			
		||||
	message := "an unexpected error was encountered while handling this request"
 | 
			
		||||
	if wsh.JWT != nil && wsh.JWT.HasPermission(PermissionReceiveErrors) {
 | 
			
		||||
		message = err.Error()
 | 
			
		||||
	if wsh.JWT != nil {
 | 
			
		||||
		if server.IsSuspendedError(err) || wsh.JWT.HasPermission(PermissionReceiveErrors) {
 | 
			
		||||
			message = err.Error()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m, u := wsh.GetErrorMessage(message)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user