From 55d7e14fbffcab34e6420e6c45fd30856709e8ac Mon Sep 17 00:00:00 2001 From: Liang Chenye Date: Mon, 12 Feb 2018 16:52:42 +0800 Subject: [PATCH] implement kill tests Signed-off-by: Liang Chenye --- validation/kill.go | 75 ++++++++++++++++++++++++++++++++++++ validation/killsig.go | 59 ++++++++++++++++++++++++++++ validation/util/container.go | 22 +++++++++++ validation/util/test.go | 6 ++- 4 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 validation/kill.go create mode 100644 validation/killsig.go diff --git a/validation/kill.go b/validation/kill.go new file mode 100644 index 000000000..eee445fb0 --- /dev/null +++ b/validation/kill.go @@ -0,0 +1,75 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/mndrix/tap-go" + rspecs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" + "github.com/opencontainers/runtime-tools/specerror" + "github.com/opencontainers/runtime-tools/validation/util" + uuid "github.com/satori/go.uuid" +) + +func main() { + t := tap.New() + t.Header(0) + bundleDir, err := util.PrepareBundle() + if err != nil { + util.Fatal(err) + } + defer os.RemoveAll(bundleDir) + + stoppedConfig := util.GetDefaultGenerator() + stoppedConfig.SetProcessArgs([]string{"true"}) + runningConfig := util.GetDefaultGenerator() + runningConfig.SetProcessArgs([]string{"sleep", "30"}) + containerID := uuid.NewV4().String() + + cases := []struct { + config *generate.Generator + id string + action util.LifecycleAction + errExpected bool + err error + }{ + // Note: the nil config test case should run first since we are re-using the bundle + // kill without id + {nil, "", util.LifecycleActionNone, false, specerror.NewError(specerror.KillWithoutIDGenError, fmt.Errorf("`kill` operation MUST generate an error if it is not provided the container ID"), rspecs.Version)}, + // kill a non exist container + {nil, containerID, util.LifecycleActionNone, false, specerror.NewError(specerror.KillNonCreateRunGenError, fmt.Errorf("attempting to send a signal to a container that is neither `created` nor `running` MUST generate an error"), rspecs.Version)}, + // kill a created + {stoppedConfig, containerID, util.LifecycleActionCreate | util.LifecycleActionDelete, true, specerror.NewError(specerror.KillSignalImplement, fmt.Errorf("`kill` operation MUST send the specified signal to the container process"), rspecs.Version)}, + // kill a stopped + {stoppedConfig, containerID, util.LifecycleActionCreate | util.LifecycleActionStart | util.LifecycleActionDelete, false, specerror.NewError(specerror.KillSignalImplement, fmt.Errorf("`kill` operation MUST send the specified signal to the container process"), rspecs.Version)}, + // kill a running + {runningConfig, containerID, util.LifecycleActionCreate | util.LifecycleActionStart | util.LifecycleActionDelete, true, specerror.NewError(specerror.KillSignalImplement, fmt.Errorf("`kill` operation MUST send the specified signal to the container process"), rspecs.Version)}, + } + + for _, c := range cases { + config := util.LifecycleConfig{ + Config: c.config, + BundleDir: bundleDir, + Actions: c.action, + PreCreate: func(r *util.Runtime) error { + r.SetID(c.id) + return nil + }, + PreDelete: func(r *util.Runtime) error { + // waiting the 'stoppedConfig' testcase to stop + // the 'runningConfig' testcase sleeps 30 seconds, so 10 seconds are enough for this case + util.WaitingForStatus(*r, util.LifecycleStatusCreated|util.LifecycleStatusStopped, time.Second*10, time.Second*1) + // KILL MUST be supported and KILL cannot be trapped + err := r.Kill("KILL") + util.WaitingForStatus(*r, util.LifecycleStatusStopped, time.Second*10, time.Second*1) + return err + }, + } + err := util.RuntimeLifecycleValidate(config) + util.SpecErrorOK(t, (err == nil) == c.errExpected, c.err, err) + } + + t.AutoPlan() +} diff --git a/validation/killsig.go b/validation/killsig.go new file mode 100644 index 000000000..73c311c1c --- /dev/null +++ b/validation/killsig.go @@ -0,0 +1,59 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/mndrix/tap-go" + rspecs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/specerror" + "github.com/opencontainers/runtime-tools/validation/util" + uuid "github.com/satori/go.uuid" +) + +var signals = []string{ + "TERM", +} + +func main() { + t := tap.New() + t.Header(0) + bundleDir, err := util.PrepareBundle() + if err != nil { + util.Fatal(err) + } + defer os.RemoveAll(bundleDir) + + containerID := uuid.NewV4().String() + sigConfig := util.GetDefaultGenerator() + rootDir := filepath.Join(bundleDir, sigConfig.Spec().Root.Path) + for _, signal := range signals { + sigConfig.SetProcessArgs([]string{"sh", "-c", fmt.Sprintf("trap 'touch /%s' %s; sleep 10 & wait $!", signal, signal)}) + config := util.LifecycleConfig{ + Config: sigConfig, + BundleDir: bundleDir, + Actions: util.LifecycleActionCreate | util.LifecycleActionStart | util.LifecycleActionDelete, + PreCreate: func(r *util.Runtime) error { + r.SetID(containerID) + return nil + }, + PreDelete: func(r *util.Runtime) error { + util.WaitingForStatus(*r, util.LifecycleStatusRunning, time.Second*5, time.Second*1) + err := r.Kill(signal) + // wait before the container been deleted + util.WaitingForStatus(*r, util.LifecycleStatusStopped, time.Second*5, time.Second*1) + return err + }, + } + err := util.RuntimeLifecycleValidate(config) + if err != nil { + util.SpecErrorOK(t, false, specerror.NewError(specerror.KillSignalImplement, fmt.Errorf("`kill` operation MUST send the specified signal to the container process"), rspecs.Version), err) + } else { + _, err = os.Stat(filepath.Join(rootDir, signal)) + util.SpecErrorOK(t, err == nil, specerror.NewError(specerror.KillSignalImplement, fmt.Errorf("`kill` operation MUST send the specified signal to the container process"), rspecs.Version), err) + } + } + t.AutoPlan() +} diff --git a/validation/util/container.go b/validation/util/container.go index a339ccb2c..f81b58aa6 100644 --- a/validation/util/container.go +++ b/validation/util/container.go @@ -26,6 +26,9 @@ type Runtime struct { stderr *os.File } +// DefaultSignal represents the default signal sends to a container +const DefaultSignal = "TERM" + // NewRuntime create a runtime by command and the bundle directory func NewRuntime(runtimeCommand string, bundleDir string) (Runtime, error) { var r Runtime @@ -158,6 +161,25 @@ func (r *Runtime) State() (rspecs.State, error) { return state, err } +// Kill a container +func (r *Runtime) Kill(sig string) (err error) { + var args []string + args = append(args, "kill") + if r.ID != "" { + args = append(args, r.ID) + } + if sig != "" { + // TODO: runc does not support this + // args = append(args, "--signal", sig) + args = append(args, sig) + } else { + args = append(args, DefaultSignal) + } + + cmd := exec.Command(r.RuntimeCommand, args...) + return execWithStderrFallbackToStdout(cmd) +} + // Delete a container func (r *Runtime) Delete() (err error) { var args []string diff --git a/validation/util/test.go b/validation/util/test.go index 022d7e9cd..f96e9d294 100644 --- a/validation/util/test.go +++ b/validation/util/test.go @@ -327,8 +327,12 @@ func RuntimeLifecycleValidate(config LifecycleConfig) error { if _, err := r.State(); err != nil { return } - if err := WaitingForStatus(r, LifecycleStatusCreated|LifecycleStatusStopped, time.Second*10, time.Second*1); err != nil { + err := WaitingForStatus(r, LifecycleStatusCreated|LifecycleStatusStopped, time.Second*10, time.Second*1) + if err == nil { r.Delete() + } else { + os.Stderr.WriteString("failed to delete the container\n") + os.Stderr.WriteString(err.Error()) } }() }