summaryrefslogtreecommitdiffhomepage
path: root/runsc/cmd/checkpoint.go
blob: 96d3c33786d1bf27ac17a19bddfcaa1464eb22ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
	"context"
	"os"
	"path/filepath"
	"syscall"

	"flag"
	"github.com/google/subcommands"
	"gvisor.googlesource.com/gvisor/pkg/log"
	"gvisor.googlesource.com/gvisor/runsc/boot"
	"gvisor.googlesource.com/gvisor/runsc/container"
	"gvisor.googlesource.com/gvisor/runsc/specutils"
)

// File containing the container's saved image/state within the given image-path's directory.
const checkpointFileName = "checkpoint.img"

// Checkpoint implements subcommands.Command for the "checkpoint" command.
type Checkpoint struct {
	imagePath    string
	leaveRunning bool
}

// Name implements subcommands.Command.Name.
func (*Checkpoint) Name() string {
	return "checkpoint"
}

// Synopsis implements subcommands.Command.Synopsis.
func (*Checkpoint) Synopsis() string {
	return "checkpoint current state of container (experimental)"
}

// Usage implements subcommands.Command.Usage.
func (*Checkpoint) Usage() string {
	return `checkpoint [flags] <container id> - save current state of container.
`
}

// SetFlags implements subcommands.Command.SetFlags.
func (c *Checkpoint) SetFlags(f *flag.FlagSet) {
	f.StringVar(&c.imagePath, "image-path", "", "directory path to saved container image")
	f.BoolVar(&c.leaveRunning, "leave-running", false, "restart the container after checkpointing")

	// Unimplemented flags necessary for compatibility with docker.
	var wp string
	f.StringVar(&wp, "work-path", "", "ignored")
}

// Execute implements subcommands.Command.Execute.
func (c *Checkpoint) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {

	if f.NArg() != 1 {
		f.Usage()
		return subcommands.ExitUsageError
	}

	id := f.Arg(0)
	conf := args[0].(*boot.Config)
	waitStatus := args[1].(*syscall.WaitStatus)

	cont, err := container.Load(conf.RootDir, id)
	if err != nil {
		Fatalf("loading container: %v", err)
	}

	if c.imagePath == "" {
		Fatalf("image-path flag must be provided")
	}

	if err := os.MkdirAll(c.imagePath, 0755); err != nil {
		Fatalf("making directories at path provided: %v", err)
	}

	fullImagePath := filepath.Join(c.imagePath, checkpointFileName)

	// Create the image file and open for writing.
	file, err := os.OpenFile(fullImagePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644)
	if err != nil {
		Fatalf("os.OpenFile(%q) failed: %v", fullImagePath, err)
	}
	defer file.Close()

	if err := cont.Checkpoint(file); err != nil {
		Fatalf("checkpoint failed: %v", err)
	}

	if !c.leaveRunning {
		return subcommands.ExitSuccess
	}

	// TODO(b/110843694): Make it possible to restore into same container.
	// For now, we can fake it by destroying the container and making a
	// new container with the same ID. This hack does not work with docker
	// which uses the container pid to ensure that the restore-container is
	// actually the same as the checkpoint-container. By restoring into
	// the same container, we will solve the docker incompatibility.

	// Restore into new container with same ID.
	bundleDir := cont.BundleDir
	if bundleDir == "" {
		Fatalf("setting bundleDir")
	}

	spec, err := specutils.ReadSpec(bundleDir)
	if err != nil {
		Fatalf("reading spec: %v", err)
	}

	specutils.LogSpec(spec)

	if cont.ConsoleSocket != "" {
		log.Warningf("ignoring console socket since it cannot be restored")
	}

	if err := cont.Destroy(); err != nil {
		Fatalf("destroying container: %v", err)
	}

	cont, err = container.Create(id, spec, conf, bundleDir, "", "", "")
	if err != nil {
		Fatalf("restoring container: %v", err)
	}
	defer cont.Destroy()

	if err := cont.Restore(spec, conf, fullImagePath); err != nil {
		Fatalf("starting container: %v", err)
	}

	ws, err := cont.Wait()
	*waitStatus = ws

	return subcommands.ExitSuccess
}