From 273b86dbee04f4dbe401401b70746b9f2fc0689c Mon Sep 17 00:00:00 2001 From: "linzhinan(zen Lin)" Date: Thu, 24 Mar 2016 10:35:02 +0800 Subject: [PATCH 1/6] Rebase the initial code of runtimetest. Signed-off-by: linzhinan(zen Lin) --- cases.conf | 2 + config/config.go | 57 +++++++++ main.go | 1 + runtimetest.go | 80 ++++++++++++ units/unit.go | 318 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 458 insertions(+) create mode 100644 cases.conf create mode 100644 config/config.go create mode 100644 runtimetest.go create mode 100644 units/unit.go diff --git a/cases.conf b/cases.conf new file mode 100644 index 000000000..d16d7d9a1 --- /dev/null +++ b/cases.conf @@ -0,0 +1,2 @@ +process= --args=./runtimetest --rootfs=rootfs --read-only=false;--args=./runtimetest --rootfs=rootfs --read-only=true +hostname= --args=./runtimetest --rootfs=rootfs --hostname=zenlin diff --git a/config/config.go b/config/config.go new file mode 100644 index 000000000..01054c37b --- /dev/null +++ b/config/config.go @@ -0,0 +1,57 @@ +package config + +import ( + "bufio" + "io" + "os" + "strconv" + "strings" + + "github.com/Sirupsen/logrus" +) + +const configPath = "cases.conf" + +var ( + // BundleMap for config, key is the bundlename, value is the params + BundleMap = make(map[string]string) + configLen int +) + +func init() { + f, err := os.Open(configPath) + if err != nil { + logrus.Fatalf("open file %v error %v", configPath, err) + } + defer f.Close() + + rd := bufio.NewReader(f) + count := 0 + + for { + + line, err := rd.ReadString('\n') + if err != nil || io.EOF == err { + break + } + + prefix := strings.Split(line, "=") + caseName := strings.TrimSpace(prefix[0]) + caseArg := strings.TrimPrefix(line, caseName+"=") + for i, arg := range splitArgs(caseArg) { + BundleMap[caseName+strconv.FormatInt(int64(i), 10)] = arg + count = count + 1 + } + } + configLen = count +} + +func splitArgs(args string) []string { + + argArray := strings.Split(args, ";") + resArray := make([]string, len(argArray)) + for count, arg := range argArray { + resArray[count] = strings.TrimSpace(arg) + } + return resArray +} diff --git a/main.go b/main.go index 642a394cd..efb9af308 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ func main() { app.Commands = []cli.Command{ generateCommand, bundleValidateCommand, + runtimeTestCommand, } if err := app.Run(os.Args); err != nil { diff --git a/runtimetest.go b/runtimetest.go new file mode 100644 index 000000000..d73dc4a47 --- /dev/null +++ b/runtimetest.go @@ -0,0 +1,80 @@ +package main + +import ( + "os" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/opencontainers/ocitools/units" +) + +const bundleCacheDir = "./bundles" + +var runtimetestFlags = []cli.Flag{ + cli.StringFlag{Name: "runtime, r", Usage: "runtime to be tested"}, + cli.StringFlag{Name: "output, o", Usage: "output format, \n" + + "-o=all: ouput sucessful details and statics, -o=err-only: ouput failure details and statics"}, + cli.BoolFlag{Name: "debug, d", Usage: "switch of debug mode, defaults to false, with '--debug' to enable debug mode"}, +} + +var runtimeTestCommand = cli.Command{ + Name: "runtimetest", + Usage: "test if a runtime is comlpliant to oci specs", + Flags: runtimetestFlags, + Action: func(context *cli.Context) { + + if os.Geteuid() != 0 { + logrus.Fatalln("runtimetest should be run as root") + } + var runtime string + if runtime = context.String("runtime"); runtime != "runc" { + logrus.Fatalf("runtimetest have not support %v\n", runtime) + } + output := context.String("output") + setDebugMode(context.Bool("debug")) + + units.LoadTestUnits("./cases.conf") + + if err := os.MkdirAll(bundleCacheDir, os.ModePerm); err != nil { + logrus.Printf("create cache dir for bundle cases err: %v\ns", bundleCacheDir) + return + } + + for _, tu := range *units.Units { + testTask(tu, runtime) + } + + units.OutputResult(output) + + if err := os.RemoveAll(bundleCacheDir); err != nil { + logrus.Fatalf("remove cache dir of bundles %v err: %v\n", bundleCacheDir, err) + } + + if err := os.Remove("./runtime.json"); err != nil { + logrus.Fatalf("remove ./runtime.json err: %v\n", err) + } + + if err := os.Remove("./config.json"); err != nil { + logrus.Fatalf("remove ./config.json err: %v\n", err) + } + + }, +} + +func setDebugMode(debug bool) { + if !debug { + logrus.SetLevel(logrus.InfoLevel) + } else { + logrus.SetLevel(logrus.DebugLevel) + } +} + +func testTask(unit *units.TestUnit, runtime string) { + logrus.Debugf("test bundle name: %v, Test args: %v\n", unit.Name, unit.Args) + if err := unit.SetRuntime(runtime); err != nil { + logrus.Fatalf("failed to setup runtime %s , error: %v\n", runtime, err) + } else { + unit.Run() + } + return +} diff --git a/units/unit.go b/units/unit.go new file mode 100644 index 000000000..627cb07b8 --- /dev/null +++ b/units/unit.go @@ -0,0 +1,318 @@ +package units + +import ( + "errors" + "io" + "os" + "os/exec" + "path" + "strings" + + "github.com/Sirupsen/logrus" + "github.com/opencontainers/ocitools/config" +) + +const ( + testCacheDir = "./bundles/" + runtimePrefix = "runtime.json" + configPrefix = "config.json" + pass = "SUCCESS" + fail = "FAILED" +) + +// TestUnit for storage testcase +type TestUnit struct { + Runtime string + + // Case name + Name string + // Args is used to generate bundle + Args string + // Describle what does this unit test for. It is optional. + Description string + + BundleDir string + // Success or failed + Result string + // When result == failed, ErrInfo is err code, or, ErrInfo is nil + ErrInfo error +} + +type testUnits []*TestUnit + +// Units is the object of testUnits +var Units = new(testUnits) + +// LoadTestUnits load TestUnits configuration from config +func LoadTestUnits(filename string) { + + for key, value := range config.BundleMap { + // TODO: config.BundleMap should support 'Description' + unit := NewTestUnit(key, value, "") + *Units = append(*Units, unit) + } +} + +// NewTestUnit new a TestUnit +func NewTestUnit(name string, args string, desc string) *TestUnit { + + tu := new(TestUnit) + tu.Name = name + tu.Args = args + tu.Description = desc + + return tu +} + +// OutputResult output results, ouput value: err-only or all +func OutputResult(output string) { + + if output != "err-only" && output != "all" { + logrus.Fatalf("eerror output mode: %v\n", output) + } + + SuccessCount := 0 + failCount := 0 + + // Can not be merged into on range, because output should be devided into two parts, successful and + // failure + if output == "all" { + logrus.Println("successful Details:") + echoDividing() + } + + for _, tu := range *Units { + if tu.Result == pass { + SuccessCount++ + if output == "all" { + tu.EchoSUnit() + } + } + } + + logrus.Println("failure Details:") + echoDividing() + + for _, tu := range *Units { + if tu.Result == fail { + failCount++ + tu.EchoFUnit() + } + } + + echoDividing() + logrus.Printf("statistics: %v bundles success, %v bundles failed\n", SuccessCount, failCount) +} + +// EchoSUnit echo sucessful test units after validation +func (unit *TestUnit) EchoSUnit() { + + logrus.Printf("\nBundleName:\n %v\nBundleDir:\n %v\nCaseArgs:\n %v\nTestResult:\n %v\n", + unit.Name, unit.BundleDir, unit.Args, unit.Result) +} + +// EchoFUnit echo failed test units after validation +func (unit *TestUnit) EchoFUnit() { + logrus.Printf("\nBundleName:\n %v\nBundleDir:\n %v\nCaseArgs:\n %v\nResult:\n %v\n"+ + "ErrInfo:\n %v\n", unit.Name, unit.BundleDir, unit.Args, unit.Result, unit.ErrInfo) +} + +func echoDividing() { + logrus.Println("============================================================================" + + "===================") +} + +func (unit *TestUnit) setResult(result string, err error) { + unit.Result = result + if result == pass { + unit.ErrInfo = nil + } else { + unit.ErrInfo = err + } +} + +// SetRuntime set runtime for validation +func (unit *TestUnit) SetRuntime(runtime string) error { + unit.Runtime = "runc" + return nil +} + +// Run run testunits +func (unit *TestUnit) Run() { + if unit.Runtime == "" { + logrus.Fatalf("set the runtime before testing") + } else if unit.Runtime != "runc" { + logrus.Fatalf("%v have not supported yet\n", unit.Runtime) + } + + unit.generateConfigs() + unit.prepareBundle() + + if _, err := runcStart(unit.BundleDir); err != nil { + unit.setResult(fail, err) + return + } + + unit.setResult(pass, nil) + return +} + +func (unit *TestUnit) prepareBundle() { + // Create bundle follder + unit.BundleDir = path.Join(testCacheDir, unit.Name) + if err := os.RemoveAll(unit.BundleDir); err != nil { + logrus.Fatalf("remove bundle %v err: %v\n", unit.Name, err) + } + + if err := os.Mkdir(unit.BundleDir, os.ModePerm); err != nil { + logrus.Fatalf("mkdir bundle %v dir err: %v\n", unit.BundleDir, err) + } + + // Create rootfs for bundle + rootfs := unit.BundleDir + "/rootfs" + if err := untarRootfs(rootfs); err != nil { + logrus.Fatalf("tar roofts.tar.gz to %v err: %v\n", rootfs, err) + } + + // Copy runtimtest from plugins to rootfs + src := "./runtimetest" + dRuntimeTest := rootfs + "/runtimetest" + + if err := copy(dRuntimeTest, src); err != nil { + logrus.Fatalf("Copy runtimetest to rootfs err: %v\n", err) + } + + if err := os.Chmod(dRuntimeTest, os.ModePerm); err != nil { + logrus.Fatalf("Chmod runtimetest mode err: %v\n", err) + } + + // Copy *.json to testroot and rootfs + csrc := configPrefix + rsrc := runtimePrefix + cdest := rootfs + "/" + configPrefix + rdest := rootfs + "/" + runtimePrefix + + if err := copy(cdest, csrc); err != nil { + logrus.Fatal(err) + } + + if err := copy(rdest, rsrc); err != nil { + logrus.Fatal(err) + } + + cdest = unit.BundleDir + "/" + configPrefix + rdest = unit.BundleDir + "/" + runtimePrefix + + if err := copy(cdest, csrc); err != nil { + logrus.Fatal(err) + } + + if err := copy(rdest, rsrc); err != nil { + logrus.Fatal(err) + } +} + +func (unit *TestUnit) generateConfigs() { + args := splitArgs(unit.Args) + + logrus.Debugf("args to the ocitools generate: ") + for _, a := range args { + logrus.Debugln(a) + } + + err := genConfigs(args) + if err != nil { + logrus.Fatalf("generate *.json err: %v\n", err) + } +} + +func untarRootfs(rootfs string) error { + // Create rootfs folder to bundle + if err := os.Mkdir(rootfs, os.ModePerm); err != nil { + logrus.Fatalf("mkdir rootfs for bundle %v err: %v\n", rootfs, err) + } + + cmd := exec.Command("tar", "-xf", "rootfs.tar.gz", "-C", rootfs) + cmd.Dir = "" + cmd.Stdin = os.Stdin + out, err := cmd.CombinedOutput() + + logrus.Debugln("command done\n") + logrus.Debugln(string(out)) + if err != nil { + return err + } + return nil +} + +func genConfigs(args []string) error { + argsNew := make([]string, len(args)+1) + argsNew[0] = "generate" + for i, a := range args { + argsNew[i+1] = a + } + + cmd := exec.Command("./ocitools", argsNew...) + cmd.Dir = "./" + cmd.Stdin = os.Stdin + out, err := cmd.CombinedOutput() + logrus.Debugf("command done\n") + logrus.Debugln(string(out)) + if err != nil { + return err + } + return nil +} + +func runcStart(specDir string) (string, error) { + logrus.Debugf("launcing runtime") + + cmd := exec.Command("runc", "start") + cmd.Dir = specDir + cmd.Stdin = os.Stdin + out, err := cmd.CombinedOutput() + + logrus.Debugf("command done") + if err != nil { + return string(out), errors.New(string(out) + err.Error()) + } + + return string(out), nil +} + +func splitArgs(args string) []string { + + argsnew := strings.TrimSpace(args) + + argArray := strings.Split(argsnew, "--") + + length := len(argArray) + resArray := make([]string, length-1) + for i, arg := range argArray { + if i == 0 || i == length { + continue + } else { + resArray[i-1] = "--" + strings.TrimSpace(arg) + } + } + return resArray +} + +func copy(dst string, src string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + _, err = io.Copy(out, in) + cerr := out.Close() + if err != nil { + return err + } + return cerr +} From 37b93464eaf73a2af08050006a5f13cd4bc4015a Mon Sep 17 00:00:00 2001 From: "linzhinan(zen Lin)" Date: Thu, 24 Mar 2016 10:40:33 +0800 Subject: [PATCH 2/6] Update README.md for runtimetest Signed-off-by: linzhinan(zen Lin) --- README.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 6c7e97675..0c4db1668 100644 --- a/README.md +++ b/README.md @@ -66,21 +66,22 @@ OPTIONS: --path path to a bundle ``` - -Testing OCI runtimes + +Testing OCI runtimes ------------------------------------------ ``` -$ make -$ sudo make install -$ sudo ./test_runtime.sh -r runc ------------------------------------------------------------------------------------ -VALIDATING RUNTIME: runc ------------------------------------------------------------------------------------ -validating container process -validating capabilities -validating hostname -validating rlimits -validating sysctls -Runtime runc passed validation -``` +# ocitools runtimetest --help +NAME: + runtimetest - test if a runtime is comlpliant to oci specs + +USAGE: + command runtimetest [command options] [arguments...] + +OPTIONS: + --runtime, -r runtime to be tested + --output, -o output format, +-o=all: ouput sucessful details and statics, -o=err-only: ouput failure details and statics + --debug, -d switch of debug mode, defaults to false, with '--debug' to enable debug mode +``` + From 5e909b552eeb4c4cc2143e67eb4c4899fff3ef12 Mon Sep 17 00:00:00 2001 From: "linzhinan(zen Lin)" Date: Thu, 31 Mar 2016 16:36:07 +0800 Subject: [PATCH 3/6] Correct format of code style. Signed-off-by: linzhinan(zen Lin) --- config/config.go | 4 ---- runtimetest.go | 6 ++---- units/unit.go | 3 --- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/config/config.go b/config/config.go index 01054c37b..e5c1a01b3 100644 --- a/config/config.go +++ b/config/config.go @@ -27,14 +27,11 @@ func init() { rd := bufio.NewReader(f) count := 0 - for { - line, err := rd.ReadString('\n') if err != nil || io.EOF == err { break } - prefix := strings.Split(line, "=") caseName := strings.TrimSpace(prefix[0]) caseArg := strings.TrimPrefix(line, caseName+"=") @@ -47,7 +44,6 @@ func init() { } func splitArgs(args string) []string { - argArray := strings.Split(args, ";") resArray := make([]string, len(argArray)) for count, arg := range argArray { diff --git a/runtimetest.go b/runtimetest.go index d73dc4a47..15fe5728b 100644 --- a/runtimetest.go +++ b/runtimetest.go @@ -13,7 +13,7 @@ const bundleCacheDir = "./bundles" var runtimetestFlags = []cli.Flag{ cli.StringFlag{Name: "runtime, r", Usage: "runtime to be tested"}, cli.StringFlag{Name: "output, o", Usage: "output format, \n" + - "-o=all: ouput sucessful details and statics, -o=err-only: ouput failure details and statics"}, + "-o=all: ouput sucessful details and statistics, -o=err-only: ouput failure details and statics"}, cli.BoolFlag{Name: "debug, d", Usage: "switch of debug mode, defaults to false, with '--debug' to enable debug mode"}, } @@ -22,13 +22,12 @@ var runtimeTestCommand = cli.Command{ Usage: "test if a runtime is comlpliant to oci specs", Flags: runtimetestFlags, Action: func(context *cli.Context) { - if os.Geteuid() != 0 { logrus.Fatalln("runtimetest should be run as root") } var runtime string if runtime = context.String("runtime"); runtime != "runc" { - logrus.Fatalf("runtimetest have not support %v\n", runtime) + logrus.Fatalf("runtimetest does not support %v\n", runtime) } output := context.String("output") setDebugMode(context.Bool("debug")) @@ -57,7 +56,6 @@ var runtimeTestCommand = cli.Command{ if err := os.Remove("./config.json"); err != nil { logrus.Fatalf("remove ./config.json err: %v\n", err) } - }, } diff --git a/units/unit.go b/units/unit.go index 627cb07b8..41efaa8ac 100644 --- a/units/unit.go +++ b/units/unit.go @@ -276,14 +276,11 @@ func runcStart(specDir string) (string, error) { if err != nil { return string(out), errors.New(string(out) + err.Error()) } - return string(out), nil } func splitArgs(args string) []string { - argsnew := strings.TrimSpace(args) - argArray := strings.Split(argsnew, "--") length := len(argArray) From 7430659d37952aee32b3ba6c8f42f05ada576d4b Mon Sep 17 00:00:00 2001 From: "linzhinan(zen Lin)" Date: Fri, 1 Apr 2016 16:12:30 +0800 Subject: [PATCH 4/6] Correct Error world: statics --> statistics Signed-off-by: linzhinan(zen Lin) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c4db1668..084e54e4f 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ USAGE: OPTIONS: --runtime, -r runtime to be tested --output, -o output format, --o=all: ouput sucessful details and statics, -o=err-only: ouput failure details and statics +-o=all: ouput sucessful details and statistics, -o=err-only: ouput failure details and statistics --debug, -d switch of debug mode, defaults to false, with '--debug' to enable debug mode ``` From d7f681c9087d95447f520f40f230cb99ab0e9c9d Mon Sep 17 00:00:00 2001 From: "linzhinan(zen Lin)" Date: Fri, 1 Apr 2016 16:16:56 +0800 Subject: [PATCH 5/6] Correct error world in runtimetest.go:statics --> statistics Signed-off-by: linzhinan(zen Lin) --- runtimetest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtimetest.go b/runtimetest.go index 15fe5728b..95db28096 100644 --- a/runtimetest.go +++ b/runtimetest.go @@ -13,7 +13,7 @@ const bundleCacheDir = "./bundles" var runtimetestFlags = []cli.Flag{ cli.StringFlag{Name: "runtime, r", Usage: "runtime to be tested"}, cli.StringFlag{Name: "output, o", Usage: "output format, \n" + - "-o=all: ouput sucessful details and statistics, -o=err-only: ouput failure details and statics"}, + "-o=all: ouput sucessful details and statistics, -o=err-only: ouput failure details and statistics"}, cli.BoolFlag{Name: "debug, d", Usage: "switch of debug mode, defaults to false, with '--debug' to enable debug mode"}, } From d0eecbfaf88c0883028c7986e7adf9b594d4df42 Mon Sep 17 00:00:00 2001 From: "linzhinan(zen Lin)" Date: Wed, 6 Apr 2016 11:55:34 +0800 Subject: [PATCH 6/6] Delete unecessary line in unit.go Signed-off-by: linzhinan(zen Lin) --- units/unit.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/units/unit.go b/units/unit.go index 41efaa8ac..648b22b96 100644 --- a/units/unit.go +++ b/units/unit.go @@ -55,7 +55,6 @@ func LoadTestUnits(filename string) { // NewTestUnit new a TestUnit func NewTestUnit(name string, args string, desc string) *TestUnit { - tu := new(TestUnit) tu.Name = name tu.Args = args @@ -66,7 +65,6 @@ func NewTestUnit(name string, args string, desc string) *TestUnit { // OutputResult output results, ouput value: err-only or all func OutputResult(output string) { - if output != "err-only" && output != "all" { logrus.Fatalf("eerror output mode: %v\n", output) } @@ -106,7 +104,6 @@ func OutputResult(output string) { // EchoSUnit echo sucessful test units after validation func (unit *TestUnit) EchoSUnit() { - logrus.Printf("\nBundleName:\n %v\nBundleDir:\n %v\nCaseArgs:\n %v\nTestResult:\n %v\n", unit.Name, unit.BundleDir, unit.Args, unit.Result) }