Implement server deletion

This commit is contained in:
Dane Everitt 2019-12-21 23:23:56 -08:00
parent 038436c781
commit 0866b84d8e
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
5 changed files with 100 additions and 4 deletions

48
http.go
View File

@ -474,6 +474,53 @@ func (rt *Router) routeSystemInformation(w http.ResponseWriter, r *http.Request,
json.NewEncoder(w).Encode(s) json.NewEncoder(w).Encode(s)
} }
func (rt *Router) routeServerDelete(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
s := rt.GetServer(ps.ByName("server"))
defer r.Body.Close()
// Immediately suspend the server to prevent a user from attempting
// to start it while this process is running.
s.Suspended = true
// Destroy the environment; in Docker this will handle a running container and
// forcibly terminate it before removing the container, so we do not need to handle
// that here.
if err := s.Environment.Destroy(); err != nil {
zap.S().Errorw("failed to destroy server environment", zap.Error(errors.WithStack(err)))
http.Error(w, "failed to destroy server environment", http.StatusInternalServerError)
return
}
// Once the environment is terminated, remove the server files from the system. This is
// done in a seperate process since failure is not the end of the world and can be
// manually cleaned up after the fact.
//
// In addition, servers with large amounts of files can take some time to finish deleting
// so we don't want to block the HTTP call while waiting on this.
go func(p string) {
if err := os.RemoveAll(p); err != nil {
zap.S().Warnw("failed to remove server files on deletion", zap.String("path", p), zap.Error(errors.WithStack(err)))
}
}(s.Filesystem.Path())
var uuid = s.Uuid
server.GetServers().Remove(func(s2 *server.Server) bool {
return s2.Uuid == uuid
})
s = nil
// Remove the configuration file stored on the Daemon for this server.
go func(u string) {
if err := os.Remove("data/servers/" + u + ".yml"); err != nil {
zap.S().Warnw("failed to delete server configuration file on deletion", zap.String("server", u), zap.Error(errors.WithStack(err)))
}
}(uuid)
w.WriteHeader(http.StatusAccepted)
}
func (rt *Router) ReaderToBytes(r io.Reader) []byte { func (rt *Router) ReaderToBytes(r io.Reader) []byte {
buf := bytes.Buffer{} buf := bytes.Buffer{}
buf.ReadFrom(r) buf.ReadFrom(r)
@ -506,6 +553,7 @@ func (rt *Router) ConfigureRouter() *httprouter.Router {
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)) router.PATCH("/api/servers/:server", rt.AuthenticateRequest(rt.routeServerUpdate))
router.DELETE("/api/servers/:server", rt.AuthenticateRequest(rt.routeServerDelete))
return router return router
} }

View File

@ -1,7 +1,18 @@
package server package server
import "sync"
type Collection struct { type Collection struct {
items []*Server items []*Server
mutex *sync.Mutex
}
// Create a new collection from a slice of servers.
func NewCollection(servers []*Server) *Collection {
return &Collection{
items: servers,
mutex: &sync.Mutex{},
}
} }
// Return all of the items in the collection. // Return all of the items in the collection.
@ -11,11 +22,17 @@ func (c *Collection) All() []*Server {
// Adds an item to the collection store. // Adds an item to the collection store.
func (c *Collection) Add(s *Server) { func (c *Collection) Add(s *Server) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.items = append(c.items, s) c.items = append(c.items, s)
} }
// Returns only those items matching the filter criteria. // Returns only those items matching the filter criteria.
func (c *Collection) Filter(filter func (*Server) bool) []*Server { func (c *Collection) Filter(filter func(*Server) bool) []*Server {
c.mutex.Lock()
defer c.mutex.Unlock()
r := make([]*Server, 0) r := make([]*Server, 0)
for _, v := range c.items { for _, v := range c.items {
if filter(v) { if filter(v) {
@ -28,7 +45,7 @@ func (c *Collection) Filter(filter func (*Server) bool) []*Server {
// Returns a single element from the collection matching the filter. If nothing is // Returns a single element from the collection matching the filter. If nothing is
// found a nil result is returned. // found a nil result is returned.
func (c *Collection) Find(filter func (*Server) bool) *Server { func (c *Collection) Find(filter func(*Server) bool) *Server {
for _, v := range c.items { for _, v := range c.items {
if filter(v) { if filter(v) {
return v return v
@ -37,3 +54,18 @@ func (c *Collection) Find(filter func (*Server) bool) *Server {
return nil return nil
} }
// Removes all items from the collection that match the filter function.
func (c *Collection) Remove(filter func(*Server) bool) {
c.mutex.Lock()
defer c.mutex.Unlock()
r := make([]*Server, 0)
for _, v := range c.items {
if !filter(v) {
r = append(r, v)
}
}
c.items = r
}

View File

@ -40,6 +40,10 @@ type Environment interface {
// is not running no error should be returned. // is not running no error should be returned.
Terminate(signal os.Signal) error Terminate(signal os.Signal) error
// Destroys the environment removing any containers that were created (in Docker
// environments at least).
Destroy() error
// Returns the exit state of the process. The first result is the exit code, the second // Returns the exit state of the process. The first result is the exit code, the second
// determines if the process was killed by the system OOM killer. // determines if the process was killed by the system OOM killer.
ExitState() (uint32, bool, error) ExitState() (uint32, bool, error)

View File

@ -325,6 +325,18 @@ func (d *DockerEnvironment) Terminate(signal os.Signal) error {
) )
} }
// Remove the Docker container from the machine. If the container is currently running
// it will be forcibly stopped by Docker.
func (d *DockerEnvironment) Destroy() error {
ctx := context.Background()
return d.Client.ContainerRemove(ctx, d.Server.Uuid, types.ContainerRemoveOptions{
RemoveVolumes: true,
RemoveLinks: false,
Force: true,
})
}
// Determine the container exit state and return the exit code and wether or not // Determine the container exit state and return the exit code and wether or not
// the container was killed by the OOM killer. // the container was killed by the OOM killer.
func (d *DockerEnvironment) ExitState() (uint32, bool, error) { func (d *DockerEnvironment) ExitState() (uint32, bool, error) {

View File

@ -158,7 +158,7 @@ func LoadDirectory(dir string, cfg *config.SystemConfiguration) error {
return err return err
} }
servers = new(Collection) servers = NewCollection(nil)
for _, file := range f { for _, file := range f {
if !strings.HasSuffix(file.Name(), ".yml") || file.IsDir() { if !strings.HasSuffix(file.Name(), ".yml") || file.IsDir() {