Skip to content

Commit ccdd757

Browse files
author
Renaud Gaubert
committed
Add the CreateRuntime, CreateContainer and StartContainer Hooks
Signed-off-by: Renaud Gaubert <rgaubert@nvidia.com>
1 parent 82d2fa4 commit ccdd757

File tree

9 files changed

+166
-66
lines changed

9 files changed

+166
-66
lines changed

libcontainer/configs/config.go

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
"time"
99

1010
"github.com/opencontainers/runtime-spec/specs-go"
11-
11+
"github.com/pkg/errors"
1212
"github.com/sirupsen/logrus"
1313
)
1414

@@ -176,7 +176,7 @@ type Config struct {
176176

177177
// Hooks are a collection of actions to perform at various container lifecycle events.
178178
// CommandHooks are serialized to JSON, but other hooks are not.
179-
Hooks *Hooks
179+
Hooks Hooks
180180

181181
// Version is the version of opencontainer specification that is supported.
182182
Version string `json:"version"`
@@ -203,17 +203,54 @@ type Config struct {
203203
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
204204
}
205205

206-
type Hooks struct {
206+
type HookName string
207+
type HookList []Hook
208+
type Hooks map[HookName]HookList
209+
210+
const (
207211
// Prestart commands are executed after the container namespaces are created,
208212
// but before the user supplied command is executed from init.
209-
Prestart []Hook
213+
// Note: This hook is now deprecated
214+
// Prestart commands are called in the Runtime namespace.
215+
Prestart HookName = "prestart"
216+
217+
// CreateRuntime commands MUST be called as part of the create operation after
218+
// the runtime environment has been created but before the pivot_root has been executed.
219+
// CreateRuntime is called immediately after the deprecated Prestart hook.
220+
// CreateRuntime commands are called in the Runtime Namespace.
221+
CreateRuntime = "createRuntime"
222+
223+
// CreateContainer commands MUST be called as part of the create operation after
224+
// the runtime environment has been created but before the pivot_root has been executed.
225+
// CreateContainer commands are called in the Container namespace.
226+
CreateContainer = "createContainer"
227+
228+
// StartContainer commands MUST be called as part of the start operation and before
229+
// the container process is started.
230+
// StartContainer commands are called in the Container namespace.
231+
StartContainer = "startContainer"
210232

211233
// Poststart commands are executed after the container init process starts.
212-
Poststart []Hook
234+
// Poststart commands are called in the Runtime Namespace.
235+
Poststart = "poststart"
213236

214237
// Poststop commands are executed after the container init process exits.
215-
Poststop []Hook
216-
}
238+
// Poststop commands are called in the Runtime Namespace.
239+
Poststop = "poststop"
240+
)
241+
242+
var (
243+
HookNameList = []HookName{Prestart, CreateRuntime, CreateContainer, StartContainer, Poststart, Poststop}
244+
)
245+
246+
// TODO move this to runtime-spec
247+
// See: https://github.com/opencontainers/runtime-spec/pull/1046
248+
const (
249+
Creating = "creating"
250+
Created = "created"
251+
Running = "running"
252+
Stopped = "stopped"
253+
)
217254

218255
type Capabilities struct {
219256
// Bounding is the set of capabilities checked by the kernel.
@@ -228,32 +265,39 @@ type Capabilities struct {
228265
Ambient []string
229266
}
230267

231-
func (hooks *Hooks) UnmarshalJSON(b []byte) error {
232-
var state struct {
233-
Prestart []CommandHook
234-
Poststart []CommandHook
235-
Poststop []CommandHook
268+
func (hooks HookList) RunHooks(state *specs.State) error {
269+
for i, h := range hooks {
270+
if err := h.Run(state); err != nil {
271+
return errors.Wrapf(err, "Running hook #%d:", i)
272+
}
236273
}
237274

275+
return nil
276+
}
277+
278+
func (hooks *Hooks) UnmarshalJSON(b []byte) error {
279+
var state map[HookName][]CommandHook
280+
238281
if err := json.Unmarshal(b, &state); err != nil {
239282
return err
240283
}
241284

242-
deserialize := func(shooks []CommandHook) (hooks []Hook) {
243-
for _, shook := range shooks {
244-
hooks = append(hooks, shook)
285+
*hooks = Hooks{}
286+
for n, commandHooks := range state {
287+
if len(commandHooks) == 0 {
288+
continue
245289
}
246290

247-
return hooks
291+
(*hooks)[n] = HookList{}
292+
for _, h := range commandHooks {
293+
(*hooks)[n] = append((*hooks)[n], h)
294+
}
248295
}
249296

250-
hooks.Prestart = deserialize(state.Prestart)
251-
hooks.Poststart = deserialize(state.Poststart)
252-
hooks.Poststop = deserialize(state.Poststop)
253297
return nil
254298
}
255299

256-
func (hooks Hooks) MarshalJSON() ([]byte, error) {
300+
func (hooks *Hooks) MarshalJSON() ([]byte, error) {
257301
serialize := func(hooks []Hook) (serializableHooks []CommandHook) {
258302
for _, hook := range hooks {
259303
switch chook := hook.(type) {
@@ -268,9 +312,12 @@ func (hooks Hooks) MarshalJSON() ([]byte, error) {
268312
}
269313

270314
return json.Marshal(map[string]interface{}{
271-
"prestart": serialize(hooks.Prestart),
272-
"poststart": serialize(hooks.Poststart),
273-
"poststop": serialize(hooks.Poststop),
315+
"prestart": serialize((*hooks)[Prestart]),
316+
"createRuntime": serialize((*hooks)[CreateRuntime]),
317+
"createContainer": serialize((*hooks)[CreateContainer]),
318+
"startContainer": serialize((*hooks)[StartContainer]),
319+
"poststart": serialize((*hooks)[Poststart]),
320+
"poststop": serialize((*hooks)[Poststop]),
274321
})
275322
}
276323

libcontainer/container_linux.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/checkpoint-restore/go-criu/v4"
3030
criurpc "github.com/checkpoint-restore/go-criu/v4/rpc"
3131
"github.com/golang/protobuf/proto"
32+
errorsf "github.com/pkg/errors"
3233
"github.com/sirupsen/logrus"
3334
"github.com/vishvananda/netlink/nl"
3435
"golang.org/x/sys/unix"
@@ -379,13 +380,12 @@ func (c *linuxContainer) start(process *Process) error {
379380
if err != nil {
380381
return err
381382
}
382-
for i, hook := range c.config.Hooks.Poststart {
383-
if err := hook.Run(s); err != nil {
384-
if err := ignoreTerminateErrors(parent.terminate()); err != nil {
385-
logrus.Warn(err)
386-
}
387-
return newSystemErrorWithCausef(err, "running poststart hook %d", i)
383+
384+
if err := c.config.Hooks[configs.Poststart].RunHooks(s); err != nil {
385+
if err := ignoreTerminateErrors(parent.terminate()); err != nil {
386+
logrus.Warn(errorsf.Wrapf(err, "Running Poststart hook"))
388387
}
388+
return err
389389
}
390390
}
391391
}
@@ -1621,10 +1621,12 @@ func (c *linuxContainer) criuNotifications(resp *criurpc.CriuResp, process *Proc
16211621
return nil
16221622
}
16231623
s.Pid = int(notify.GetPid())
1624-
for i, hook := range c.config.Hooks.Prestart {
1625-
if err := hook.Run(s); err != nil {
1626-
return newSystemErrorWithCausef(err, "running prestart hook %d", i)
1627-
}
1624+
1625+
if err := c.config.Hooks[configs.Prestart].RunHooks(s); err != nil {
1626+
return err
1627+
}
1628+
if err := c.config.Hooks[configs.CreateRuntime].RunHooks(s); err != nil {
1629+
return err
16281630
}
16291631
}
16301632
case "post-restore":

libcontainer/factory_linux_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,14 @@ func TestFactoryLoadContainer(t *testing.T) {
157157
// setup default container config and state for mocking
158158
var (
159159
id = "1"
160-
expectedHooks = &configs.Hooks{
161-
Prestart: []configs.Hook{
160+
expectedHooks = configs.Hooks{
161+
configs.Prestart: configs.HookList{
162162
configs.CommandHook{Command: configs.Command{Path: "prestart-hook"}},
163163
},
164-
Poststart: []configs.Hook{
164+
configs.Poststart: configs.HookList{
165165
configs.CommandHook{Command: configs.Command{Path: "poststart-hook"}},
166166
},
167-
Poststop: []configs.Hook{
167+
configs.Poststop: configs.HookList{
168168
unserializableHook{},
169169
configs.CommandHook{Command: configs.Command{Path: "poststop-hook"}},
170170
},
@@ -201,7 +201,7 @@ func TestFactoryLoadContainer(t *testing.T) {
201201
if config.Rootfs != expectedConfig.Rootfs {
202202
t.Fatalf("expected rootfs %q but received %q", expectedConfig.Rootfs, config.Rootfs)
203203
}
204-
expectedHooks.Poststop = expectedHooks.Poststop[1:] // expect unserializable hook to be skipped
204+
expectedHooks[configs.Poststop] = expectedHooks[configs.Poststop][1:] // expect unserializable hook to be skipped
205205
if !reflect.DeepEqual(config.Hooks, expectedHooks) {
206206
t.Fatalf("expects hooks %q but received %q", expectedHooks, config.Hooks)
207207
}

libcontainer/init_linux.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/opencontainers/runc/libcontainer/system"
2121
"github.com/opencontainers/runc/libcontainer/user"
2222
"github.com/opencontainers/runc/libcontainer/utils"
23+
"github.com/opencontainers/runtime-spec/specs-go"
2324
"github.com/pkg/errors"
2425
"github.com/sirupsen/logrus"
2526
"github.com/vishvananda/netlink"
@@ -67,6 +68,7 @@ type initConfig struct {
6768
ConsoleHeight uint16 `json:"console_height"`
6869
RootlessEUID bool `json:"rootless_euid,omitempty"`
6970
RootlessCgroups bool `json:"rootless_cgroups,omitempty"`
71+
SpecState *specs.State `json:"spec_state,omitempty"`
7072
}
7173

7274
type initer interface {

libcontainer/process_linux.go

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,9 @@ func (p *initProcess) start() (retErr error) {
362362
if err := p.createNetworkInterfaces(); err != nil {
363363
return newSystemErrorWithCause(err, "creating network interfaces")
364364
}
365+
if err := p.updateSpecState(); err != nil {
366+
return newSystemErrorWithCause(err, "updating the spec state")
367+
}
365368
if err := p.sendConfig(); err != nil {
366369
return newSystemErrorWithCause(err, "sending config to init process")
367370
}
@@ -378,9 +381,9 @@ func (p *initProcess) start() (retErr error) {
378381
if err := setupRlimits(p.config.Rlimits, p.pid()); err != nil {
379382
return newSystemErrorWithCause(err, "setting rlimits for ready process")
380383
}
381-
// call prestart hooks
384+
// call prestart and CreateRuntime hooks
382385
if !p.config.Config.Namespaces.Contains(configs.NEWNS) {
383-
// Setup cgroup before prestart hook, so that the prestart hook could apply cgroup permissions.
386+
// Setup cgroup before the hook, so that the prestart and CreateRuntime hook could apply cgroup permissions.
384387
if err := p.manager.Set(p.config.Config); err != nil {
385388
return newSystemErrorWithCause(err, "setting cgroup config for ready process")
386389
}
@@ -397,11 +400,14 @@ func (p *initProcess) start() (retErr error) {
397400
}
398401
// initProcessStartTime hasn't been set yet.
399402
s.Pid = p.cmd.Process.Pid
400-
s.Status = "creating"
401-
for i, hook := range p.config.Config.Hooks.Prestart {
402-
if err := hook.Run(s); err != nil {
403-
return newSystemErrorWithCausef(err, "running prestart hook %d", i)
404-
}
403+
s.Status = configs.Creating
404+
hooks := p.config.Config.Hooks
405+
406+
if err := hooks[configs.Prestart].RunHooks(s); err != nil {
407+
return err
408+
}
409+
if err := hooks[configs.CreateRuntime].RunHooks(s); err != nil {
410+
return err
405411
}
406412
}
407413
}
@@ -427,11 +433,14 @@ func (p *initProcess) start() (retErr error) {
427433
}
428434
// initProcessStartTime hasn't been set yet.
429435
s.Pid = p.cmd.Process.Pid
430-
s.Status = "creating"
431-
for i, hook := range p.config.Config.Hooks.Prestart {
432-
if err := hook.Run(s); err != nil {
433-
return newSystemErrorWithCausef(err, "running prestart hook %d", i)
434-
}
436+
s.Status = configs.Creating
437+
hooks := p.config.Config.Hooks
438+
439+
if err := hooks[configs.Prestart].RunHooks(s); err != nil {
440+
return err
441+
}
442+
if err := hooks[configs.CreateRuntime].RunHooks(s); err != nil {
443+
return err
435444
}
436445
}
437446
// Sync with child.
@@ -450,7 +459,7 @@ func (p *initProcess) start() (retErr error) {
450459
return newSystemErrorWithCause(ierr, "container init")
451460
}
452461
if p.config.Config.Namespaces.Contains(configs.NEWNS) && !sentResume {
453-
return newSystemError(errors.New("could not synchronise after executing prestart hooks with container process"))
462+
return newSystemError(errors.New("could not synchronise after executing prestart and CreateRuntime hooks with container process"))
454463
}
455464
if err := unix.Shutdown(int(p.messageSockPair.parent.Fd()), unix.SHUT_WR); err != nil {
456465
return newSystemErrorWithCause(err, "shutting down init pipe")
@@ -492,6 +501,16 @@ func (p *initProcess) startTime() (uint64, error) {
492501
return stat.StartTime, err
493502
}
494503

504+
func (p *initProcess) updateSpecState() error {
505+
s, err := p.container.currentOCIState()
506+
if err != nil {
507+
return err
508+
}
509+
510+
p.config.SpecState = s
511+
return nil
512+
}
513+
495514
func (p *initProcess) sendConfig() error {
496515
// send the config to the container's init process, we don't use JSON Encode
497516
// here because there might be a problem in JSON decoder in some cases, see:

libcontainer/rootfs_linux.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig) (err error) {
9898
return newSystemErrorWithCausef(err, "changing dir to %q", config.Rootfs)
9999
}
100100

101+
s := iConfig.SpecState
102+
s.Pid = unix.Getpid()
103+
s.Status = configs.Creating
104+
if err := iConfig.Config.Hooks[configs.CreateContainer].RunHooks(s); err != nil {
105+
return err
106+
}
107+
101108
if config.NoPivotRoot {
102109
err = msMoveRoot(config.Rootfs)
103110
} else if config.Namespaces.Contains(configs.NEWNS) {

libcontainer/specconv/spec_linux.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -881,20 +881,31 @@ func SetupSeccomp(config *specs.LinuxSeccomp) (*configs.Seccomp, error) {
881881
}
882882

883883
func createHooks(rspec *specs.Spec, config *configs.Config) {
884-
config.Hooks = &configs.Hooks{}
884+
config.Hooks = configs.Hooks{}
885885
if rspec.Hooks != nil {
886-
887886
for _, h := range rspec.Hooks.Prestart {
888887
cmd := createCommandHook(h)
889-
config.Hooks.Prestart = append(config.Hooks.Prestart, configs.NewCommandHook(cmd))
888+
config.Hooks[configs.Prestart] = append(config.Hooks[configs.Prestart], configs.NewCommandHook(cmd))
889+
}
890+
for _, h := range rspec.Hooks.CreateRuntime {
891+
cmd := createCommandHook(h)
892+
config.Hooks[configs.CreateRuntime] = append(config.Hooks[configs.CreateRuntime], configs.NewCommandHook(cmd))
893+
}
894+
for _, h := range rspec.Hooks.CreateContainer {
895+
cmd := createCommandHook(h)
896+
config.Hooks[configs.CreateContainer] = append(config.Hooks[configs.CreateContainer], configs.NewCommandHook(cmd))
897+
}
898+
for _, h := range rspec.Hooks.StartContainer {
899+
cmd := createCommandHook(h)
900+
config.Hooks[configs.StartContainer] = append(config.Hooks[configs.StartContainer], configs.NewCommandHook(cmd))
890901
}
891902
for _, h := range rspec.Hooks.Poststart {
892903
cmd := createCommandHook(h)
893-
config.Hooks.Poststart = append(config.Hooks.Poststart, configs.NewCommandHook(cmd))
904+
config.Hooks[configs.Poststart] = append(config.Hooks[configs.Poststart], configs.NewCommandHook(cmd))
894905
}
895906
for _, h := range rspec.Hooks.Poststop {
896907
cmd := createCommandHook(h)
897-
config.Hooks.Poststop = append(config.Hooks.Poststop, configs.NewCommandHook(cmd))
908+
config.Hooks[configs.Poststop] = append(config.Hooks[configs.Poststop], configs.NewCommandHook(cmd))
898909
}
899910
}
900911
}

libcontainer/standard_init_linux.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,14 @@ func (l *linuxStandardInit) Init() error {
207207
return newSystemErrorWithCause(err, "init seccomp")
208208
}
209209
}
210+
211+
s := l.config.SpecState
212+
s.Pid = unix.Getpid()
213+
s.Status = configs.Created
214+
if err := l.config.Config.Hooks[configs.StartContainer].RunHooks(s); err != nil {
215+
return err
216+
}
217+
210218
if err := unix.Exec(name, l.config.Args[0:], os.Environ()); err != nil {
211219
return newSystemErrorWithCause(err, "exec user process")
212220
}

0 commit comments

Comments
 (0)