Skip to content

Commit 3fe7d7f

Browse files
committed
Add create and start command for container lifecycle
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
1 parent 75fb70b commit 3fe7d7f

File tree

13 files changed

+209
-29
lines changed

13 files changed

+209
-29
lines changed

create.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package main
2+
3+
import (
4+
"os"
5+
6+
"github.com/codegangsta/cli"
7+
)
8+
9+
var createCommand = cli.Command{
10+
Name: "create",
11+
Usage: "create a container",
12+
ArgsUsage: `<container-id>
13+
14+
Where "<container-id>" is your name for the instance of the container that you
15+
are starting. The name you provide for the container instance must be unique on
16+
your host.`,
17+
Description: `The create command creates an instance of a container for a bundle. The bundle
18+
is a directory with a specification file named "` + specConfig + `" and a root
19+
filesystem.
20+
21+
The specification file includes an args parameter. The args parameter is used
22+
to specify command(s) that get run when the container is started. To change the
23+
command(s) that get executed on start, edit the args parameter of the spec. See
24+
"runc spec --help" for more explanation.`,
25+
Flags: []cli.Flag{
26+
cli.StringFlag{
27+
Name: "bundle, b",
28+
Value: "",
29+
Usage: `path to the root of the bundle directory, defaults to the current directory`,
30+
},
31+
cli.StringFlag{
32+
Name: "console",
33+
Value: "",
34+
Usage: "specify the pty slave path for use with the container",
35+
},
36+
cli.StringFlag{
37+
Name: "pid-file",
38+
Value: "",
39+
Usage: "specify the file to write the process id to",
40+
},
41+
cli.BoolFlag{
42+
Name: "no-pivot",
43+
Usage: "do not use pivot root to jail process inside rootfs. This should be used whenever the rootfs is on top of a ramdisk",
44+
},
45+
},
46+
Action: func(context *cli.Context) {
47+
bundle := context.String("bundle")
48+
if bundle != "" {
49+
if err := os.Chdir(bundle); err != nil {
50+
fatal(err)
51+
}
52+
}
53+
spec, err := loadSpec(specConfig)
54+
if err != nil {
55+
fatal(err)
56+
}
57+
notifySocket := os.Getenv("NOTIFY_SOCKET")
58+
if notifySocket != "" {
59+
setupSdNotify(spec, notifySocket)
60+
}
61+
if os.Geteuid() != 0 {
62+
fatalf("runc should be run as root")
63+
}
64+
status, err := startContainer(context, spec, true)
65+
if err != nil {
66+
fatal(err)
67+
}
68+
// exit with the container's exit status so any external supervisor is
69+
// notified of the exit with the correct exit status.
70+
os.Exit(status)
71+
},
72+
}

libcontainer/container.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ const (
2929

3030
// Destroyed is the status that denotes the container does not exist.
3131
Destroyed
32+
33+
// Stopped is the status that denotes the container does not have a created or running process.
34+
Stopped
35+
36+
// Initialized is the status where the container has all the namespaces created but the user
37+
// process has not been start.
38+
Initialized
3239
)
3340

3441
func (s Status) String() string {
@@ -43,6 +50,10 @@ func (s Status) String() string {
4350
return "paused"
4451
case Destroyed:
4552
return "destroyed"
53+
case Stopped:
54+
return "stopped"
55+
case Initialized:
56+
return "initialized"
4657
default:
4758
return "unknown"
4859
}

libcontainer/container_linux.go

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,6 @@ func (c *linuxContainer) Start(process *Process) error {
195195
}
196196
// generate a timestamp indicating when the container was started
197197
c.created = time.Now().UTC()
198-
199198
c.state = &runningState{
200199
c: c,
201200
}
@@ -1034,31 +1033,47 @@ func (c *linuxContainer) refreshState() error {
10341033
if paused {
10351034
return c.state.transition(&pausedState{c: c})
10361035
}
1037-
running, err := c.isRunning()
1036+
t, err := c.runType()
10381037
if err != nil {
10391038
return err
10401039
}
1041-
if running {
1040+
switch t {
1041+
case Initialized:
1042+
return c.state.transition(&initializedState{c: c})
1043+
case Running:
10421044
return c.state.transition(&runningState{c: c})
10431045
}
10441046
return c.state.transition(&stoppedState{c: c})
10451047
}
10461048

1047-
func (c *linuxContainer) isRunning() (bool, error) {
1049+
func (c *linuxContainer) runType() (Status, error) {
10481050
if c.initProcess == nil {
1049-
return false, nil
1051+
return Stopped, nil
10501052
}
1053+
pid := c.initProcess.pid()
10511054
// return Running if the init process is alive
1052-
if err := syscall.Kill(c.initProcess.pid(), 0); err != nil {
1055+
if err := syscall.Kill(pid, 0); err != nil {
10531056
if err == syscall.ESRCH {
10541057
// It means the process does not exist anymore, could happen when the
10551058
// process exited just when we call the function, we should not return
10561059
// error in this case.
1057-
return false, nil
1060+
return Stopped, nil
1061+
}
1062+
return Stopped, newSystemErrorWithCausef(err, "sending signal 0 to pid %d", pid)
1063+
}
1064+
// check if the process that is running is the init process or the user's process.
1065+
// this is the difference between the container Running and Created.
1066+
environ, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/environ", pid))
1067+
if err != nil {
1068+
return Stopped, newSystemErrorWithCausef(err, "reading /proc/%d/environ", pid)
1069+
}
1070+
check := []byte("_LIBCONTAINER")
1071+
for _, v := range bytes.Split(environ, []byte("\x00")) {
1072+
if bytes.Contains(v, check) {
1073+
return Initialized, nil
10581074
}
1059-
return false, newSystemErrorWithCausef(err, "sending signal 0 to pid %d", c.initProcess.pid())
10601075
}
1061-
return true, nil
1076+
return Running, nil
10621077
}
10631078

10641079
func (c *linuxContainer) isPaused() (bool, error) {

libcontainer/factory_linux.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"os"
99
"os/exec"
10+
"os/signal"
1011
"path/filepath"
1112
"regexp"
1213
"runtime/debug"
@@ -219,6 +220,9 @@ func (l *LinuxFactory) Type() string {
219220
// StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state
220221
// This is a low level implementation detail of the reexec and should not be consumed externally
221222
func (l *LinuxFactory) StartInitialization() (err error) {
223+
// start the signal handler as soon as we can
224+
s := make(chan os.Signal, 1024)
225+
signal.Notify(s, syscall.SIGCONT)
222226
fdStr := os.Getenv("_LIBCONTAINER_INITPIPE")
223227
pipefd, err := strconv.Atoi(fdStr)
224228
if err != nil {
@@ -260,7 +264,7 @@ func (l *LinuxFactory) StartInitialization() (err error) {
260264
if err != nil {
261265
return err
262266
}
263-
return i.Init()
267+
return i.Init(s)
264268
}
265269

266270
func (l *LinuxFactory) loadState(root string) (*State, error) {

libcontainer/init_linux.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ type initConfig struct {
6161
}
6262

6363
type initer interface {
64-
Init() error
64+
Init(s chan os.Signal) error
6565
}
6666

6767
func newContainerInit(t initType, pipe *os.File) (initer, error) {

libcontainer/integration/utils_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ func runContainer(config *configs.Config, console string, args ...string) (buffe
127127
if err != nil {
128128
return buffers, -1, err
129129
}
130+
if err := container.Signal(syscall.SIGCONT); err != nil {
131+
return buffers, -1, err
132+
}
130133
ps, err := process.Wait()
131134
if err != nil {
132135
return buffers, -1, err

libcontainer/setns_init_linux.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package libcontainer
55
import (
66
"fmt"
77
"os"
8+
"os/signal"
89

910
"github.com/opencontainers/runc/libcontainer/apparmor"
1011
"github.com/opencontainers/runc/libcontainer/keys"
@@ -23,7 +24,7 @@ func (l *linuxSetnsInit) getSessionRingName() string {
2324
return fmt.Sprintf("_ses.%s", l.config.ContainerId)
2425
}
2526

26-
func (l *linuxSetnsInit) Init() error {
27+
func (l *linuxSetnsInit) Init(s chan os.Signal) error {
2728
// do not inherit the parent's session keyring
2829
if _, err := keyctl.JoinSessionKeyring(l.getSessionRingName()); err != nil {
2930
return err
@@ -49,5 +50,7 @@ func (l *linuxSetnsInit) Init() error {
4950
return err
5051
}
5152
}
53+
signal.Stop(s)
54+
close(s)
5255
return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ())
5356
}

libcontainer/standard_init_linux.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"fmt"
77
"io"
88
"os"
9+
"os/exec"
10+
"os/signal"
911
"syscall"
1012

1113
"github.com/opencontainers/runc/libcontainer/apparmor"
@@ -17,7 +19,7 @@ import (
1719
)
1820

1921
type linuxStandardInit struct {
20-
pipe io.ReadWriter
22+
pipe io.ReadWriteCloser
2123
parentPid int
2224
config *initConfig
2325
}
@@ -42,7 +44,7 @@ func (l *linuxStandardInit) getSessionRingParams() (string, uint32, uint32) {
4244
// the kernel
4345
const PR_SET_NO_NEW_PRIVS = 0x26
4446

45-
func (l *linuxStandardInit) Init() error {
47+
func (l *linuxStandardInit) Init(s chan os.Signal) error {
4648
ringname, keepperms, newperms := l.getSessionRingParams()
4749

4850
// do not inherit the parent's session keyring
@@ -150,6 +152,18 @@ func (l *linuxStandardInit) Init() error {
150152
return err
151153
}
152154
}
153-
154-
return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ())
155+
// check for the arg before waiting to make sure it exists and it is returned
156+
// as a create time error
157+
name, err := exec.LookPath(l.config.Args[0])
158+
if err != nil {
159+
return err
160+
}
161+
// close the pipe to signal that we have completed our init
162+
l.pipe.Close()
163+
// wait for the signal to exec the users process
164+
<-s
165+
// clean up the signal handler
166+
signal.Stop(s)
167+
close(s)
168+
return syscall.Exec(name, l.config.Args[0:], os.Environ())
155169
}

libcontainer/state_linux.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,11 @@ func (r *runningState) status() Status {
110110
func (r *runningState) transition(s containerState) error {
111111
switch s.(type) {
112112
case *stoppedState:
113-
running, err := r.c.isRunning()
113+
t, err := r.c.runType()
114114
if err != nil {
115115
return err
116116
}
117-
if running {
117+
if t == Running {
118118
return newGenericError(fmt.Errorf("container still running"), ContainerNotStopped)
119119
}
120120
r.c.state = s
@@ -129,16 +129,38 @@ func (r *runningState) transition(s containerState) error {
129129
}
130130

131131
func (r *runningState) destroy() error {
132-
running, err := r.c.isRunning()
132+
t, err := r.c.runType()
133133
if err != nil {
134134
return err
135135
}
136-
if running {
136+
if t == Running {
137137
return newGenericError(fmt.Errorf("container is not destroyed"), ContainerNotStopped)
138138
}
139139
return destroy(r.c)
140140
}
141141

142+
type initializedState struct {
143+
c *linuxContainer
144+
}
145+
146+
func (i *initializedState) status() Status {
147+
return Initialized
148+
}
149+
150+
func (i *initializedState) transition(s containerState) error {
151+
switch s.(type) {
152+
case *runningState:
153+
i.c.state = s
154+
case *initializedState:
155+
return nil
156+
}
157+
return newStateTransitionError(i, s)
158+
}
159+
160+
func (i *initializedState) destroy() error {
161+
return destroy(i.c)
162+
}
163+
142164
// pausedState represents a container that is currently pause. It cannot be destroyed in a
143165
// paused state and must transition back to running first.
144166
type pausedState struct {
@@ -161,11 +183,11 @@ func (p *pausedState) transition(s containerState) error {
161183
}
162184

163185
func (p *pausedState) destroy() error {
164-
isRunning, err := p.c.isRunning()
186+
t, err := p.c.runType()
165187
if err != nil {
166188
return err
167189
}
168-
if !isRunning {
190+
if t != Running && t != Created {
169191
if err := p.c.cgroupManager.Freeze(configs.Thawed); err != nil {
170192
return err
171193
}

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func main() {
8686
}
8787
app.Commands = []cli.Command{
8888
checkpointCommand,
89+
createCommand,
8990
deleteCommand,
9091
eventsCommand,
9192
execCommand,
@@ -98,6 +99,7 @@ func main() {
9899
resumeCommand,
99100
runCommand,
100101
specCommand,
102+
startCommand,
101103
stateCommand,
102104
updateCommand,
103105
}

0 commit comments

Comments
 (0)