Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add 'Runtime' struct to make test easier; add create test #447

Merged
merged 1 commit into from
Aug 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions error/runtime_spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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{
Expand All @@ -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
Expand Down
124 changes: 124 additions & 0 deletions validation/container.go
Original file line number Diff line number Diff line change
@@ -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
}
122 changes: 75 additions & 47 deletions validation/validation_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package validation

import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"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 (
Expand All @@ -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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is broken, since you've deferred a delete call (via Clean) earlier in the function. That doesn't work reliably though, because delete MUST generate an error if the container is not stopped, and the fact that start completes doesn't mean you're stopped (you could also be running). In order for Clean() to work reliably, you either need to attempt to send a KILL to the container process with kill (although the spec is not clear on what signals are supported, you need something like #321 to see that KILL MUST be supported) or code that waits for the container process to exit on its own (more on this here, in #305, and in most of the other PRs that attempted to split run into create andstart, e.g. here and here).

}

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())
}
}
}