Merge pull request #618 from cloudfoundry-incubator/serialize-hooks

Serialize CommandHooks to state so that PostStop hooks execute during 'runc delete'
This commit is contained in:
Michael Crosby 2016-03-08 10:51:54 -08:00
commit 8cc43a6c69
2 changed files with 75 additions and 3 deletions

View File

@ -4,6 +4,8 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"os/exec" "os/exec"
"github.com/Sirupsen/logrus"
) )
type Rlimit struct { type Rlimit struct {
@ -175,8 +177,8 @@ type Config struct {
NoNewPrivileges bool `json:"no_new_privileges,omitempty"` NoNewPrivileges bool `json:"no_new_privileges,omitempty"`
// Hooks are a collection of actions to perform at various container lifecycle events. // Hooks are a collection of actions to perform at various container lifecycle events.
// Hooks are not able to be marshaled to json but they are also not needed to. // CommandHooks are serialized to JSON, but other hooks are not.
Hooks *Hooks `json:"-"` Hooks *Hooks
// Version is the version of opencontainer specification that is supported. // Version is the version of opencontainer specification that is supported.
Version string `json:"version"` Version string `json:"version"`
@ -197,6 +199,52 @@ type Hooks struct {
Poststop []Hook Poststop []Hook
} }
func (hooks *Hooks) UnmarshalJSON(b []byte) error {
var state struct {
Prestart []CommandHook
Poststart []CommandHook
Poststop []CommandHook
}
if err := json.Unmarshal(b, &state); err != nil {
return err
}
deserialize := func(shooks []CommandHook) (hooks []Hook) {
for _, shook := range shooks {
hooks = append(hooks, shook)
}
return hooks
}
hooks.Prestart = deserialize(state.Prestart)
hooks.Poststart = deserialize(state.Poststart)
hooks.Poststop = deserialize(state.Poststop)
return nil
}
func (hooks Hooks) MarshalJSON() ([]byte, error) {
serialize := func(hooks []Hook) (serializableHooks []CommandHook) {
for _, hook := range hooks {
switch chook := hook.(type) {
case CommandHook:
serializableHooks = append(serializableHooks, chook)
default:
logrus.Warnf("cannot serialize hook of type %T, skipping", hook)
}
}
return serializableHooks
}
return json.Marshal(map[string]interface{}{
"prestart": serialize(hooks.Prestart),
"poststart": serialize(hooks.Poststart),
"poststop": serialize(hooks.Poststop),
})
}
// HookState is the payload provided to a hook on execution. // HookState is the payload provided to a hook on execution.
type HookState struct { type HookState struct {
Version string `json:"version"` Version string `json:"version"`

View File

@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"syscall" "syscall"
"testing" "testing"
@ -132,9 +133,22 @@ func TestFactoryLoadContainer(t *testing.T) {
defer os.RemoveAll(root) defer os.RemoveAll(root)
// setup default container config and state for mocking // setup default container config and state for mocking
var ( var (
id = "1" id = "1"
expectedHooks = &configs.Hooks{
Prestart: []configs.Hook{
configs.CommandHook{Command: configs.Command{Path: "prestart-hook"}},
},
Poststart: []configs.Hook{
configs.CommandHook{Command: configs.Command{Path: "poststart-hook"}},
},
Poststop: []configs.Hook{
unserializableHook{},
configs.CommandHook{Command: configs.Command{Path: "poststop-hook"}},
},
}
expectedConfig = &configs.Config{ expectedConfig = &configs.Config{
Rootfs: "/mycontainer/root", Rootfs: "/mycontainer/root",
Hooks: expectedHooks,
} }
expectedState = &State{ expectedState = &State{
BaseState: BaseState{ BaseState: BaseState{
@ -164,6 +178,10 @@ func TestFactoryLoadContainer(t *testing.T) {
if config.Rootfs != expectedConfig.Rootfs { if config.Rootfs != expectedConfig.Rootfs {
t.Fatalf("expected rootfs %q but received %q", expectedConfig.Rootfs, config.Rootfs) t.Fatalf("expected rootfs %q but received %q", expectedConfig.Rootfs, config.Rootfs)
} }
expectedHooks.Poststop = expectedHooks.Poststop[1:] // expect unserializable hook to be skipped
if !reflect.DeepEqual(config.Hooks, expectedHooks) {
t.Fatalf("expects hooks %q but received %q", expectedHooks, config.Hooks)
}
lcontainer, ok := container.(*linuxContainer) lcontainer, ok := container.(*linuxContainer)
if !ok { if !ok {
t.Fatal("expected linux container on linux based systems") t.Fatal("expected linux container on linux based systems")
@ -181,3 +199,9 @@ func marshal(path string, v interface{}) error {
defer f.Close() defer f.Close()
return utils.WriteJSON(f, v) return utils.WriteJSON(f, v)
} }
type unserializableHook struct{}
func (unserializableHook) Run(configs.HookState) error {
return nil
}