Skip to content

Commit fa24ebf

Browse files
author
Mrunal Patel
committed
Merge pull request #311 from crosbymichael/destory-state
Implement Container States
2 parents 5a398b5 + 4415446 commit fa24ebf

10 files changed

Lines changed: 435 additions & 131 deletions

File tree

checkpoint.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,8 @@ var checkpointCommand = cli.Command{
3030
if err != nil {
3131
fatal(err)
3232
}
33+
defer destroy(container)
3334
options := criuOptions(context)
34-
status, err := container.Status()
35-
if err != nil {
36-
fatal(err)
37-
}
38-
if status == libcontainer.Checkpointed {
39-
fatal(fmt.Errorf("Container with id %s already checkpointed", context.GlobalString("id")))
40-
}
4135
// these are the mandatory criu options for a container
4236
setPageServer(context, options)
4337
setManageCgroupsMode(context, options)

libcontainer/container.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@ const (
3030
Destroyed
3131
)
3232

33+
func (s Status) String() string {
34+
switch s {
35+
case Running:
36+
return "running"
37+
case Pausing:
38+
return "pausing"
39+
case Paused:
40+
return "paused"
41+
case Checkpointed:
42+
return "checkpointed"
43+
case Destroyed:
44+
return "destroyed"
45+
default:
46+
return "undefined"
47+
}
48+
}
49+
3350
// BaseState represents the platform agnostic pieces relating to a
3451
// running container's state
3552
type BaseState struct {

libcontainer/container_linux.go

Lines changed: 96 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type linuxContainer struct {
3737
criuPath string
3838
m sync.Mutex
3939
criuVersion int
40+
state containerState
4041
}
4142

4243
// State represents a running container's state
@@ -183,7 +184,14 @@ func (c *linuxContainer) Start(process *Process) error {
183184
return newSystemError(err)
184185
}
185186
if doInit {
186-
c.updateState(parent)
187+
if err := c.updateState(parent); err != nil {
188+
return err
189+
}
190+
} else {
191+
c.state.transition(&nullState{
192+
c: c,
193+
s: Running,
194+
})
187195
}
188196
if c.config.Hooks != nil {
189197
s := configs.HookState{
@@ -320,48 +328,29 @@ func newPipe() (parent *os.File, child *os.File, err error) {
320328
func (c *linuxContainer) Destroy() error {
321329
c.m.Lock()
322330
defer c.m.Unlock()
323-
status, err := c.currentStatus()
324-
if err != nil {
325-
return err
326-
}
327-
if status != Destroyed {
328-
return newGenericError(fmt.Errorf("container is not destroyed"), ContainerNotStopped)
329-
}
330-
if !c.config.Namespaces.Contains(configs.NEWPID) {
331-
if err := killCgroupProcesses(c.cgroupManager); err != nil {
332-
logrus.Warn(err)
333-
}
334-
}
335-
err = c.cgroupManager.Destroy()
336-
if rerr := os.RemoveAll(c.root); err == nil {
337-
err = rerr
338-
}
339-
c.initProcess = nil
340-
if c.config.Hooks != nil {
341-
s := configs.HookState{
342-
Version: c.config.Version,
343-
ID: c.id,
344-
Root: c.config.Rootfs,
345-
}
346-
for _, hook := range c.config.Hooks.Poststop {
347-
if err := hook.Run(s); err != nil {
348-
return err
349-
}
350-
}
351-
}
352-
return err
331+
return c.state.destroy()
353332
}
354333

355334
func (c *linuxContainer) Pause() error {
356335
c.m.Lock()
357336
defer c.m.Unlock()
358-
return c.cgroupManager.Freeze(configs.Frozen)
337+
if err := c.cgroupManager.Freeze(configs.Frozen); err != nil {
338+
return err
339+
}
340+
return c.state.transition(&pausedState{
341+
c: c,
342+
})
359343
}
360344

361345
func (c *linuxContainer) Resume() error {
362346
c.m.Lock()
363347
defer c.m.Unlock()
364-
return c.cgroupManager.Freeze(configs.Thawed)
348+
if err := c.cgroupManager.Freeze(configs.Thawed); err != nil {
349+
return err
350+
}
351+
return c.state.transition(&runningState{
352+
c: c,
353+
})
365354
}
366355

367356
func (c *linuxContainer) NotifyOOM() (<-chan struct{}, error) {
@@ -459,7 +448,7 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
459448
}
460449

461450
if criuOpts.ImagesDirectory == "" {
462-
criuOpts.ImagesDirectory = filepath.Join(c.root, "criu.image")
451+
return fmt.Errorf("invalid directory to save checkpoint")
463452
}
464453

465454
// Since a container can be C/R'ed multiple times,
@@ -578,11 +567,9 @@ func (c *linuxContainer) addCriuRestoreMount(req *criurpc.CriuReq, m *configs.Mo
578567
func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
579568
c.m.Lock()
580569
defer c.m.Unlock()
581-
582570
if err := c.checkCriuVersion("1.5.2"); err != nil {
583571
return err
584572
}
585-
586573
if criuOpts.WorkDirectory == "" {
587574
criuOpts.WorkDirectory = filepath.Join(c.root, "criu.work")
588575
}
@@ -591,22 +578,19 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
591578
if err := os.Mkdir(criuOpts.WorkDirectory, 0655); err != nil && !os.IsExist(err) {
592579
return err
593580
}
594-
595581
workDir, err := os.Open(criuOpts.WorkDirectory)
596582
if err != nil {
597583
return err
598584
}
599585
defer workDir.Close()
600-
601586
if criuOpts.ImagesDirectory == "" {
602-
criuOpts.ImagesDirectory = filepath.Join(c.root, "criu.image")
587+
return fmt.Errorf("invalid directory to restore checkpoint")
603588
}
604589
imageDir, err := os.Open(criuOpts.ImagesDirectory)
605590
if err != nil {
606591
return err
607592
}
608593
defer imageDir.Close()
609-
610594
// CRIU has a few requirements for a root directory:
611595
// * it must be a mount point
612596
// * its parent must not be overmounted
@@ -617,18 +601,15 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
617601
return err
618602
}
619603
defer os.Remove(root)
620-
621604
root, err = filepath.EvalSymlinks(root)
622605
if err != nil {
623606
return err
624607
}
625-
626608
err = syscall.Mount(c.config.Rootfs, root, "", syscall.MS_BIND|syscall.MS_REC, "")
627609
if err != nil {
628610
return err
629611
}
630612
defer syscall.Unmount(root, syscall.MNT_DETACH)
631-
632613
t := criurpc.CriuReqType_RESTORE
633614
req := &criurpc.CriuReq{
634615
Type: &t,
@@ -696,15 +677,13 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
696677
fds []string
697678
fdJSON []byte
698679
)
699-
700680
if fdJSON, err = ioutil.ReadFile(filepath.Join(criuOpts.ImagesDirectory, descriptorsFilename)); err != nil {
701681
return err
702682
}
703683

704-
if err = json.Unmarshal(fdJSON, &fds); err != nil {
684+
if err := json.Unmarshal(fdJSON, &fds); err != nil {
705685
return err
706686
}
707-
708687
for i := range fds {
709688
if s := fds[i]; strings.Contains(s, "pipe:") {
710689
inheritFd := new(criurpc.InheritFd)
@@ -713,12 +692,7 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
713692
req.Opts.InheritFd = append(req.Opts.InheritFd, inheritFd)
714693
}
715694
}
716-
717-
err = c.criuSwrk(process, req, criuOpts, true)
718-
if err != nil {
719-
return err
720-
}
721-
return nil
695+
return c.criuSwrk(process, req, criuOpts, true)
722696
}
723697

724698
func (c *linuxContainer) criuApplyCgroups(pid int, req *criurpc.CriuReq) error {
@@ -913,82 +887,123 @@ func (c *linuxContainer) criuNotifications(resp *criurpc.CriuResp, process *Proc
913887
if notify == nil {
914888
return fmt.Errorf("invalid response: %s", resp.String())
915889
}
916-
917890
switch {
918891
case notify.GetScript() == "post-dump":
919-
if !opts.LeaveRunning {
920-
f, err := os.Create(filepath.Join(c.root, "checkpoint"))
921-
if err != nil {
922-
return err
923-
}
924-
f.Close()
892+
f, err := os.Create(filepath.Join(c.root, "checkpoint"))
893+
if err != nil {
894+
return err
925895
}
926-
break
927-
896+
f.Close()
928897
case notify.GetScript() == "network-unlock":
929898
if err := unlockNetwork(c.config); err != nil {
930899
return err
931900
}
932-
break
933-
934901
case notify.GetScript() == "network-lock":
935902
if err := lockNetwork(c.config); err != nil {
936903
return err
937904
}
938-
break
939-
940905
case notify.GetScript() == "post-restore":
941906
pid := notify.GetPid()
942907
r, err := newRestoredProcess(int(pid), fds)
943908
if err != nil {
944909
return err
945910
}
946-
947-
// TODO: crosbymichael restore previous process information by saving the init process information in
948-
// the container's state file or separate process state files.
911+
process.ops = r
912+
if err := c.state.transition(&restoredState{
913+
imageDir: opts.ImagesDirectory,
914+
c: c,
915+
}); err != nil {
916+
return err
917+
}
949918
if err := c.updateState(r); err != nil {
950919
return err
951920
}
952-
process.ops = r
953-
break
921+
if err := os.Remove(filepath.Join(c.root, "checkpoint")); err != nil {
922+
if !os.IsNotExist(err) {
923+
logrus.Error(err)
924+
}
925+
}
954926
}
955-
956927
return nil
957928
}
958929

959930
func (c *linuxContainer) updateState(process parentProcess) error {
960931
c.initProcess = process
932+
if err := c.refreshState(); err != nil {
933+
return err
934+
}
961935
state, err := c.currentState()
962936
if err != nil {
963937
return err
964938
}
939+
return c.saveState(state)
940+
}
941+
942+
func (c *linuxContainer) saveState(s *State) error {
965943
f, err := os.Create(filepath.Join(c.root, stateFilename))
966944
if err != nil {
967945
return err
968946
}
969947
defer f.Close()
970-
os.Remove(filepath.Join(c.root, "checkpoint"))
971-
return json.NewEncoder(f).Encode(state)
948+
return json.NewEncoder(f).Encode(s)
949+
}
950+
951+
func (c *linuxContainer) deleteState() error {
952+
return os.Remove(filepath.Join(c.root, stateFilename))
972953
}
973954

974955
func (c *linuxContainer) currentStatus() (Status, error) {
975-
if _, err := os.Stat(filepath.Join(c.root, "checkpoint")); err == nil {
976-
return Checkpointed, nil
956+
if err := c.refreshState(); err != nil {
957+
return -1, err
958+
}
959+
return c.state.status(), nil
960+
}
961+
962+
// refreshState needs to be called to verify that the current state on the
963+
// container is what is true. Because consumers of libcontainer can use it
964+
// out of process we need to verify the container's status based on runtime
965+
// information and not rely on our in process info.
966+
func (c *linuxContainer) refreshState() error {
967+
paused, err := c.isPaused()
968+
if err != nil {
969+
return err
977970
}
971+
if paused {
972+
return c.state.transition(&pausedState{c: c})
973+
}
974+
running, err := c.isRunning()
975+
if err != nil {
976+
return err
977+
}
978+
if running {
979+
return c.state.transition(&runningState{c: c})
980+
}
981+
return c.state.transition(&stoppedState{c: c})
982+
}
983+
984+
func (c *linuxContainer) isRunning() (bool, error) {
978985
if c.initProcess == nil {
979-
return Destroyed, nil
986+
return false, nil
980987
}
981988
// return Running if the init process is alive
982989
if err := syscall.Kill(c.initProcess.pid(), 0); err != nil {
983990
if err == syscall.ESRCH {
984-
return Destroyed, nil
991+
return false, nil
985992
}
986-
return 0, newSystemError(err)
993+
return false, newSystemError(err)
987994
}
988-
if c.config.Cgroups != nil && c.config.Cgroups.Resources != nil && c.config.Cgroups.Resources.Freezer == configs.Frozen {
989-
return Paused, nil
995+
return true, nil
996+
}
997+
998+
func (c *linuxContainer) isPaused() (bool, error) {
999+
data, err := ioutil.ReadFile(filepath.Join(c.cgroupManager.GetPaths()["freezer"], "freezer.state"))
1000+
if err != nil {
1001+
if os.IsNotExist(err) {
1002+
return false, nil
1003+
}
1004+
return false, newSystemError(err)
9901005
}
991-
return Running, nil
1006+
return bytes.Equal(bytes.TrimSpace(data), []byte("FROZEN")), nil
9921007
}
9931008

9941009
func (c *linuxContainer) currentState() (*State, error) {

libcontainer/container_linux_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ func TestGetContainerState(t *testing.T) {
161161
},
162162
},
163163
}
164+
container.state = &nullState{c: container}
164165
state, err := container.State()
165166
if err != nil {
166167
t.Fatal(err)

0 commit comments

Comments
 (0)