From 15577bd11fd79f9204660190fbf92db41449bc2b Mon Sep 17 00:00:00 2001 From: Liang Chenye Date: Thu, 24 Aug 2017 21:58:07 +0800 Subject: [PATCH] add runtime struct; add create test Signed-off-by: Liang Chenye --- error/runtime_spec.go | 16 +++++ validation/container.go | 124 ++++++++++++++++++++++++++++++++++ validation/validation_test.go | 122 ++++++++++++++++++++------------- 3 files changed, 215 insertions(+), 47 deletions(-) create mode 100644 validation/container.go diff --git a/error/runtime_spec.go b/error/runtime_spec.go index 2bc97ce3c..5e5f32c82 100644 --- a/error/runtime_spec.go +++ b/error/runtime_spec.go @@ -43,6 +43,13 @@ const ( // DefaultFilesystems represents the error code of default filesystems test DefaultFilesystems + + // CreateWithID represents the error code of 'create' lifecyle test with 'id' provided + CreateWithID + // CreateWithUniqueID represents the error code of 'create' lifecyle test with unique 'id' provided + CreateWithUniqueID + // CreateNewContainer represents the error code 'create' lifecyle test that creates new container + CreateNewContainer ) type errorTemplate struct { @@ -63,6 +70,9 @@ var ( defaultFSRef = func(version string) (reference string, err error) { return fmt.Sprintf(referenceTemplate, version, "config-linux.md#default-filesystems"), nil } + runtimeCreateRef = func(version string) (reference string, err error) { + return fmt.Sprintf(referenceTemplate, version, "runtime.md#create"), nil + } ) var ociErrors = map[SpecErrorCode]errorTemplate{ @@ -87,6 +97,12 @@ var ociErrors = map[SpecErrorCode]errorTemplate{ // Config-Linux.md // Default Filesystems DefaultFilesystems: errorTemplate{Level: Should, Reference: defaultFSRef}, + + // Runtime.md + // Create + CreateWithID: errorTemplate{Level: Must, Reference: runtimeCreateRef}, + CreateWithUniqueID: errorTemplate{Level: Must, Reference: runtimeCreateRef}, + CreateNewContainer: errorTemplate{Level: Must, Reference: runtimeCreateRef}, } // NewError creates an Error referencing a spec violation. The error diff --git a/validation/container.go b/validation/container.go new file mode 100644 index 000000000..295b81e7d --- /dev/null +++ b/validation/container.go @@ -0,0 +1,124 @@ +package validation + +import ( + "encoding/json" + "errors" + "os" + "os/exec" + "path/filepath" + + rspecs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" +) + +// Runtime represents the basic requirement of a container runtime +type Runtime struct { + RuntimeCommand string + BundleDir string + ID string +} + +// NewRuntime create a runtime by command and the bundle directory +func NewRuntime(runtimeCommand string, bundleDir string) (Runtime, error) { + var r Runtime + var err error + r.RuntimeCommand, err = exec.LookPath(runtimeCommand) + if err != nil { + return Runtime{}, err + } + + r.BundleDir = bundleDir + return r, err +} + +// SetConfig creates a 'config.json' by the generator +func (r *Runtime) SetConfig(g *generate.Generator) error { + if r.BundleDir == "" { + return errors.New("Please set the bundle directory first") + } + return g.SaveToFile(filepath.Join(r.BundleDir, "config.json"), generate.ExportOptions{}) +} + +// SetID sets the container ID +func (r *Runtime) SetID(id string) { + r.ID = id +} + +// Create a container +func (r *Runtime) Create() error { + var args []string + args = append(args, "create") + if r.ID != "" { + args = append(args, r.ID) + } + + // TODO: following the spec, we need define the bundle, but 'runc' does not.. + // if r.BundleDir != "" { + // args = append(args, r.BundleDir) + // } + cmd := exec.Command(r.RuntimeCommand, args...) + cmd.Dir = r.BundleDir + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +// Start a container +func (r *Runtime) Start() error { + var args []string + args = append(args, "start") + if r.ID != "" { + args = append(args, r.ID) + } + + cmd := exec.Command(r.RuntimeCommand, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +// State a container information +func (r *Runtime) State() (rspecs.State, error) { + var args []string + args = append(args, "state") + if r.ID != "" { + args = append(args, r.ID) + } + + out, err := exec.Command(r.RuntimeCommand, args...).Output() + if err != nil { + return rspecs.State{}, err + } + + var state rspecs.State + err = json.Unmarshal(out, &state) + return state, err +} + +// Delete a container +func (r *Runtime) Delete() error { + var args []string + args = append(args, "delete") + if r.ID != "" { + args = append(args, r.ID) + } + + cmd := exec.Command(r.RuntimeCommand, args...) + return cmd.Run() +} + +// Clean deletes the container and removes the bundle file according to the input parameter +func (r *Runtime) Clean(removeBundle bool) error { + err := r.Delete() + if err != nil { + return err + } + + if removeBundle { + os.RemoveAll(r.BundleDir) + } + + return nil +} diff --git a/validation/validation_test.go b/validation/validation_test.go index d2ef9c501..163613e8a 100644 --- a/validation/validation_test.go +++ b/validation/validation_test.go @@ -1,7 +1,6 @@ package validation import ( - "fmt" "io/ioutil" "os" "os/exec" @@ -9,8 +8,12 @@ import ( "testing" "github.com/mrunalp/fileutils" - "github.com/opencontainers/runtime-tools/generate" + rspecs "github.com/opencontainers/runtime-spec/specs-go" "github.com/satori/go.uuid" + "github.com/stretchr/testify/assert" + + rerr "github.com/opencontainers/runtime-tools/error" + "github.com/opencontainers/runtime-tools/generate" ) var ( @@ -24,81 +27,106 @@ func init() { } } -func runtimeValidate(runtime string, g *generate.Generator) error { - // Find the runtime binary in the PATH - runtimePath, err := exec.LookPath(runtime) +func prepareBundle() (string, error) { + // Setup a temporary test directory + bundleDir, err := ioutil.TempDir("", "ocitest") if err != nil { - return err + return "", err } - // Setup a temporary test directory - tmpDir, err := ioutil.TempDir("", "ocitest") + // Untar the root fs + untarCmd := exec.Command("tar", "-xf", "../rootfs.tar.gz", "-C", bundleDir) + _, err = untarCmd.CombinedOutput() if err != nil { - return err + os.RemoveAll(bundleDir) + return "", err } - defer os.RemoveAll(tmpDir) - // Create bundle directory for the test container - bundleDir := tmpDir + "/busybox" - if err := os.MkdirAll(bundleDir, 0755); err != nil { + return bundleDir, nil +} + +func getDefaultGenerator() *generate.Generator { + g := generate.New() + g.SetRootPath(".") + g.SetProcessArgs([]string{"/runtimetest"}) + return &g +} + +func runtimeInsideValidate(g *generate.Generator) error { + bundleDir, err := prepareBundle() + if err != nil { return err } - - // Untar the root fs - untarCmd := exec.Command("tar", "-xf", "../rootfs.tar.gz", "-C", bundleDir) - output, err := untarCmd.CombinedOutput() + r, err := NewRuntime(runtime, bundleDir) if err != nil { - fmt.Println(string(output)) + os.RemoveAll(bundleDir) return err } - - // Copy the runtimetest binary to the rootfs - err = fileutils.CopyFile("../runtimetest", filepath.Join(bundleDir, "runtimetest")) + defer r.Clean(true) + err = r.SetConfig(g) if err != nil { return err } - - // Generate test configuration - err = g.SaveToFile(filepath.Join(bundleDir, "config.json"), generate.ExportOptions{}) + err = fileutils.CopyFile("../runtimetest", filepath.Join(r.BundleDir, "runtimetest")) if err != nil { return err } - // TODO: Use a library to split run into create/start - // Launch the OCI runtime - containerID := uuid.NewV4() - runtimeCmd := exec.Command(runtimePath, "run", containerID.String()) - runtimeCmd.Dir = bundleDir - runtimeCmd.Stdin = os.Stdin - runtimeCmd.Stdout = os.Stdout - runtimeCmd.Stderr = os.Stderr - if err = runtimeCmd.Run(); err != nil { + r.SetID(uuid.NewV4().String()) + err = r.Create() + if err != nil { return err } - - return nil -} - -func getDefaultGenerator() *generate.Generator { - g := generate.New() - g.SetRootPath(".") - g.SetProcessArgs([]string{"/runtimetest"}) - return &g + return r.Start() } func TestValidateBasic(t *testing.T) { g := getDefaultGenerator() - if err := runtimeValidate(runtime, g); err != nil { - t.Errorf("%s failed validation: %v", runtime, err) - } + assert.Nil(t, runtimeInsideValidate(g)) } func TestValidateSysctls(t *testing.T) { g := getDefaultGenerator() g.AddLinuxSysctl("net.ipv4.ip_forward", "1") - if err := runtimeValidate(runtime, g); err != nil { - t.Errorf("%s failed validation: %v", runtime, err) + assert.Nil(t, runtimeInsideValidate(g)) +} + +func TestValidateCreate(t *testing.T) { + g := generate.New() + g.SetRootPath(".") + g.SetProcessArgs([]string{"ls"}) + + bundleDir, err := prepareBundle() + assert.Nil(t, err) + + r, err := NewRuntime(runtime, bundleDir) + assert.Nil(t, err) + defer r.Clean(true) + + err = r.SetConfig(&g) + assert.Nil(t, err) + + containerID := uuid.NewV4().String() + cases := []struct { + id string + errExpected bool + err error + }{ + {"", false, rerr.NewError(rerr.CreateWithID, "'Create' MUST generate an error if the ID is not provided", rspecs.Version)}, + {containerID, true, rerr.NewError(rerr.CreateNewContainer, "'Create' MUST create a new container", rspecs.Version)}, + {containerID, false, rerr.NewError(rerr.CreateWithUniqueID, "'Create' MUST generate an error if the ID provided is not unique", rspecs.Version)}, + } + + for _, c := range cases { + r.SetID(c.id) + err := r.Create() + assert.Equal(t, c.errExpected, err == nil, c.err.Error()) + + if err == nil { + state, _ := r.State() + assert.Equal(t, c.id, state.ID, c.err.Error()) + } } }