From 9eb08b8971eb43a4f7c21b7c51ddb28be17238cd Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 4 Dec 2015 14:00:07 -0800 Subject: [PATCH] Refactor checkpoint information Signed-off-by: Michael Crosby --- api/v1/server.go | 36 +++++++++++++++++++++++++---- api/v1/types.go | 1 - checkpoint.go | 12 ++++++++++ event.go | 1 + linux/linux.go | 55 +++++++++++++++++++++++++------------------- runtime/container.go | 6 ++--- runtime/runtime.go | 1 + start.go | 5 +--- supervisor.go | 1 + worker.go | 11 +++------ 10 files changed, 85 insertions(+), 44 deletions(-) diff --git a/api/v1/server.go b/api/v1/server.go index 36ac3b4..0301ba3 100644 --- a/api/v1/server.go +++ b/api/v1/server.go @@ -26,7 +26,7 @@ func NewServer(supervisor *containerd.Supervisor) http.Handler { // checkpoint and restore handlers // TODO: PUT handler for adding a checkpoint to containerd?? r.HandleFunc("/containers/{id:.*}/checkpoint/{name:.*}", s.createCheckpoint).Methods("POST") - // r.HandleFunc("/containers/{id:.*}/checkpoint/{cid:.*}", s.deleteCheckpoint).Methods("DELETE") + r.HandleFunc("/containers/{id:.*}/checkpoint/{name:.*}", s.deleteCheckpoint).Methods("DELETE") r.HandleFunc("/containers/{id:.*}/checkpoint", s.listCheckpoints).Methods("GET") // container handlers @@ -263,7 +263,6 @@ func (s *server) createContainer(w http.ResponseWriter, r *http.Request) { if c.Checkpoint != nil { e.Checkpoint = &runtime.Checkpoint{ Name: c.Checkpoint.Name, - Path: c.Checkpoint.Path, } } e.Stdio = &runtime.Stdio{ @@ -309,13 +308,17 @@ func (s *server) listCheckpoints(w http.ResponseWriter, r *http.Request) { out := []Checkpoint{} for _, c := range checkpoints { out = append(out, Checkpoint{ - Path: c.Path, Name: c.Name, Tcp: c.Tcp, Shell: c.Shell, UnixSockets: c.UnixSockets, }) } + if err := json.NewEncoder(w).Encode(out); err != nil { + logrus.WithField("error", err).Error("encode checkpoints") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } } func (s *server) createCheckpoint(w http.ResponseWriter, r *http.Request) { @@ -337,7 +340,6 @@ func (s *server) createCheckpoint(w http.ResponseWriter, r *http.Request) { e.ID = id e.Checkpoint = &runtime.Checkpoint{ Name: name, - Path: cp.Path, Exit: cp.Exit, Tcp: cp.Tcp, UnixSockets: cp.UnixSockets, @@ -352,4 +354,30 @@ func (s *server) createCheckpoint(w http.ResponseWriter, r *http.Request) { } func (s *server) deleteCheckpoint(w http.ResponseWriter, r *http.Request) { + var ( + vars = mux.Vars(r) + id = vars["id"] + name = vars["name"] + ) + if name == "" { + http.Error(w, "checkpoint name cannot be empty", http.StatusBadRequest) + return + } + var cp Checkpoint + if r.ContentLength > 0 { + if err := json.NewDecoder(r.Body).Decode(&cp); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + } + e := containerd.NewEvent(containerd.DeleteCheckpointEventType) + e.ID = id + e.Checkpoint = &runtime.Checkpoint{ + Name: name, + } + s.supervisor.SendEvent(e) + if err := <-e.Err; err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } } diff --git a/api/v1/types.go b/api/v1/types.go index 530bb16..d841f16 100644 --- a/api/v1/types.go +++ b/api/v1/types.go @@ -58,7 +58,6 @@ type Event struct { type Checkpoint struct { Name string `json:"name,omitempty"` - Path string `json:"path"` Exit bool `json:"exit,omitempty"` Tcp bool `json:"tcp"` UnixSockets bool `json:"unixSockets"` diff --git a/checkpoint.go b/checkpoint.go index 55436c2..799b748 100644 --- a/checkpoint.go +++ b/checkpoint.go @@ -11,3 +11,15 @@ func (h *CreateCheckpointEvent) Handle(e *Event) error { } return container.Checkpoint(*e.Checkpoint) } + +type DeleteCheckpointEvent struct { + s *Supervisor +} + +func (h *DeleteCheckpointEvent) Handle(e *Event) error { + container, ok := h.s.containers[e.ID] + if !ok { + return ErrContainerNotFound + } + return container.DeleteCheckpoint(e.Checkpoint.Name) +} diff --git a/event.go b/event.go index 7425f00..ee25d28 100644 --- a/event.go +++ b/event.go @@ -20,6 +20,7 @@ const ( AddProcessEventType EventType = "addProcess" UpdateContainerEventType EventType = "updateContainer" CreateCheckpointEventType EventType = "createCheckpoint" + DeleteCheckpointEventType EventType = "deleteCheckpoint" ) func NewEvent(t EventType) *Event { diff --git a/linux/linux.go b/linux/linux.go index 35b0688..a605286 100644 --- a/linux/linux.go +++ b/linux/linux.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "os" "path/filepath" goruntime "runtime" @@ -191,19 +192,39 @@ type libcontainerContainer struct { exitStatus int exited bool path string - checkpoints map[string]runtime.Checkpoint } func (c *libcontainerContainer) Checkpoints() ([]runtime.Checkpoint, error) { out := []runtime.Checkpoint{} - for _, cp := range c.checkpoints { - out = append(out, cp) + files, err := ioutil.ReadDir(c.getCheckpointPath("")) + if err != nil { + return nil, err + } + for _, fi := range files { + out = append(out, runtime.Checkpoint{ + Name: fi.Name(), + }) } return out, nil } +func (c *libcontainerContainer) DeleteCheckpoint(name string) error { + path := c.getCheckpointPath(name) + if err := os.RemoveAll(path); err != nil { + if os.IsNotExist(err) { + return runtime.ErrCheckpointNotExists + } + return err + } + return nil +} + +func (c *libcontainerContainer) getCheckpointPath(name string) string { + return filepath.Join(c.path, "checkpoints", name) +} + func (c *libcontainerContainer) Checkpoint(cp runtime.Checkpoint) error { - opts := c.createCheckpointOpts(&cp) + opts := c.createCheckpointOpts(cp) if err := os.MkdirAll(opts.ImagesDirectory, 0755); err != nil { return err } @@ -211,37 +232,24 @@ func (c *libcontainerContainer) Checkpoint(cp runtime.Checkpoint) error { return err } cp.Timestamp = time.Now() - c.checkpoints[cp.Name] = cp return nil } -func (c *libcontainerContainer) createCheckpointOpts(cp *runtime.Checkpoint) *libcontainer.CriuOpts { +func (c *libcontainerContainer) createCheckpointOpts(cp runtime.Checkpoint) *libcontainer.CriuOpts { opts := libcontainer.CriuOpts{} opts.LeaveRunning = !cp.Exit opts.ShellJob = cp.Shell opts.TcpEstablished = cp.Tcp opts.ExternalUnixConnections = cp.UnixSockets - if cp.Path == "" { - cp.Path = filepath.Join(c.path, "checkpoints", cp.Name) - } - opts.ImagesDirectory = cp.Path + opts.ImagesDirectory = c.getCheckpointPath(cp.Name) return &opts } -func (c *libcontainerContainer) Restore(path, name string) error { - if path == "" { - path = filepath.Join(c.path, "checkpoints", name) - } +func (c *libcontainerContainer) Restore(name string) error { + path := c.getCheckpointPath(name) var opts libcontainer.CriuOpts - if cp, ok := c.checkpoints[name]; ok { - opts = *c.createCheckpointOpts(&cp) - } else { - opts.ImagesDirectory = path - } - if err := c.c.Restore(c.initProcess.process, &opts); err != nil { - return err - } - return nil + opts.ImagesDirectory = path + return c.c.Restore(c.initProcess.process, &opts) } func (c *libcontainerContainer) Resume() error { @@ -352,7 +360,6 @@ func (r *libcontainerRuntime) Create(id, bundlePath string, stdio *runtime.Stdio c := &libcontainerContainer{ c: container, additionalProcesses: make(map[int]*libcontainerProcess), - checkpoints: make(map[string]runtime.Checkpoint), initProcess: &libcontainerProcess{ process: process, spec: spec.Process, diff --git a/runtime/container.go b/runtime/container.go index c00d6db..43c6dd0 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -32,8 +32,6 @@ type Stdio struct { type Checkpoint struct { // Timestamp is the time that checkpoint happened Timestamp time.Time `json:"timestamp,omitempty"` - // Path is the custom path to the checkpoint, this is optional - Path string `json:"path,omitempty"` // Name is the name of the checkpoint Name string `json:"name,omitempty"` // Tcp checkpoints open tcp connections @@ -74,5 +72,7 @@ type Container interface { Checkpoint(Checkpoint) error - Restore(path, name string) error + DeleteCheckpoint(name string) error + + Restore(name string) error } diff --git a/runtime/runtime.go b/runtime/runtime.go index 0af71e9..230239d 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -9,6 +9,7 @@ import ( var ( ErrNotChildProcess = errors.New("containerd: not a child process for container") ErrInvalidContainerType = errors.New("containerd: invalid container type for runtime") + ErrCheckpointNotExists = errors.New("containerd: checkpoint does not exist for container") ) // runtime handles containers, containers handle their own actions. diff --git a/start.go b/start.go index 8477b50..36e3eac 100644 --- a/start.go +++ b/start.go @@ -16,10 +16,7 @@ func (h *StartEvent) Handle(e *Event) error { Container: container, } if e.Checkpoint != nil { - task.Checkpoint = &Checkpoint{ - Name: e.Checkpoint.Name, - Path: e.Checkpoint.Path, - } + task.Checkpoint = e.Checkpoint.Name } h.s.tasks <- task return errDeferedResponse diff --git a/supervisor.go b/supervisor.go index c2a269e..85a4b4c 100644 --- a/supervisor.go +++ b/supervisor.go @@ -52,6 +52,7 @@ func NewSupervisor(stateDir string, tasks chan *StartTask) (*Supervisor, error) AddProcessEventType: &AddProcessEvent{s}, UpdateContainerEventType: &UpdateEvent{s}, CreateCheckpointEventType: &CreateCheckpointEvent{s}, + DeleteCheckpointEventType: &DeleteCheckpointEvent{s}, } // start the container workers for concurrent container starts return s, nil diff --git a/worker.go b/worker.go index 8a4c872..4cebf7f 100644 --- a/worker.go +++ b/worker.go @@ -11,14 +11,9 @@ type Worker interface { Start() } -type Checkpoint struct { - Path string - Name string -} - type StartTask struct { Container runtime.Container - Checkpoint *Checkpoint + Checkpoint string Err chan error } @@ -38,8 +33,8 @@ func (w *worker) Start() { defer w.wg.Done() for t := range w.s.tasks { started := time.Now() - if t.Checkpoint != nil { - if err := t.Container.Restore(t.Checkpoint.Path, t.Checkpoint.Name); err != nil { + if t.Checkpoint != "" { + if err := t.Container.Restore(t.Checkpoint); err != nil { evt := NewEvent(DeleteEventType) evt.ID = t.Container.ID() w.s.SendEvent(evt)