diff --git a/config/config.go b/config/config.go deleted file mode 100644 index 01054c37b..000000000 --- a/config/config.go +++ /dev/null @@ -1,57 +0,0 @@ -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/runtimetest.go b/runtimetest.go index d73dc4a47..289cfcb2e 100644 --- a/runtimetest.go +++ b/runtimetest.go @@ -1,62 +1,43 @@ package main import ( + "fmt" + "io/ioutil" "os" + "time" "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" - "github.com/opencontainers/ocitools/units" + rspec "github.com/opencontainers/runtime-spec/specs-go" ) 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"}, + cli.BoolFlag{Name: "debug, d", Usage: "switch of debug mode, default to 'false', with '--debug' to enable debug mode"}, } var runtimeTestCommand = cli.Command{ Name: "runtimetest", - Usage: "test if a runtime is comlpliant to oci specs", + Usage: "test if a runtime is compliant to OCI Runtime Specification", Flags: runtimetestFlags, Action: func(context *cli.Context) { - if os.Geteuid() != 0 { - logrus.Fatalln("runtimetest should be run as root") + logrus.Fatalln("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("'%s' is currently not supported", 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) + logrus.Fatalf("Failed to create cache dir: %s", bundleCacheDir) } + // defer os.RemoveAll(bundleCacheDir) + // testState(runtime) + // testLifecircle(runtime) + testMainConfigs(runtime) }, } @@ -69,12 +50,143 @@ func setDebugMode(debug bool) { } } -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) +func testState(runtime string) { + args := "--args={sleep,60}" + //TODO: use UUID + testID := "12345678" + unit := TestUnit{ + Name: "state", + Runtime: runtime, + Args: args, + ID: testID, + } + go func() { + unit.Start() + }() + var state rspec.State + var err error + for t := time.Now(); time.Since(t) < time.Minute; time.Sleep(time.Second * 5) { + if state, err = unit.GetState(); err == nil { + break + } + } + + if err != nil { + logrus.Fatal(err) + } + + defer unit.Stop() + if state.ID != testID { + logrus.Fatalf("Expect container ID: %s to match: %s", state.ID, testID) + } + if state.BundlePath != unit.GetBundlePath() { + logrus.Fatalf("Expect container bundle path: %s to match: %s", state.BundlePath, unit.GetBundlePath()) + } + + unitDup := TestUnit{ + Name: "state-dup", + Runtime: runtime, + Args: args, + ID: testID, + ExpectedResult: false, + } + unitDup.Start() + if ok, err := unitDup.IsPass(); !ok { + logrus.Fatal(err) } else { - unit.Run() + unitDup.Stop() } - return + + logrus.Infof("Runtime state test succeeded.") +} + +func testLifecircle(runtime string) { + //In this case, we should mount a volume + tmpFile, _ := ioutil.TempFile("", "lifecircle") + tmpFS := "--tmpfs={/tmp}" + prestartOK := fmt.Sprintf("--prestart={/bin/echo, 'prestart', >>, %s}", tmpFile.Name()) + prestartFailed := "--prestart={/bin/false}" + processOK := fmt.Sprintf("--args={/bin/echo, 'process', >>, %s}", tmpFile.Name()) + poststartOK := fmt.Sprintf("--poststart={/bin/echo, 'poststart', >>, %s}", tmpFile.Name()) + poststartFailed := "--poststart={/bin/false}" + poststopOK := fmt.Sprintf("--poststop={/bin/echo, 'poststop', >>, %s}", tmpFile.Name()) + poststopFailed := "--poststop={/bin/false}" + + allWorksUnit := TestUnit{ + Name: "allWorks", + Runtime: runtime, + Args: fmt.Sprintf("%s %s %s %s %s", tmpFS, prestartOK, processOK, poststartOK, poststopOK), + ExpectedOutput: "prestart\nprocess\npoststart\npoststop", + ExpectedResult: true, + } + allWorksUnit.Start() + defer allWorksUnit.Stop() + if ok, err := allWorksUnit.IsPass(); !ok { + logrus.Fatal(err) + } + + os.Create(tmpFile.Name()) + prestartFailedUnit := TestUnit{ + Name: "prestartFailed", + Runtime: runtime, + Args: fmt.Sprintf("%s %s %s %s %s", tmpFS, prestartFailed, processOK, poststartOK, poststopOK), + ExpectedOutput: "", + ExpectedResult: false, + } + prestartFailedUnit.Start() + defer prestartFailedUnit.Stop() + if ok, err := prestartFailedUnit.IsPass(); !ok { + logrus.Fatal(err) + } + + os.Create(tmpFile.Name()) + poststartFailedUnit := TestUnit{ + Name: "poststartFailed", + Runtime: runtime, + Args: fmt.Sprintf("%s %s %s %s %s", tmpFS, prestartOK, processOK, poststartFailed, poststopOK), + ExpectedOutput: "prestart\nprocess\n", + ExpectedResult: false, + } + poststartFailedUnit.Start() + defer poststartFailedUnit.Stop() + if ok, err := poststartFailedUnit.IsPass(); !ok { + logrus.Fatal(err) + } + + os.Create(tmpFile.Name()) + poststopFailedUnit := TestUnit{ + Name: "poststopFailed", + Runtime: runtime, + Args: fmt.Sprintf("%s %s %s %s %s", tmpFS, prestartOK, processOK, poststartOK, poststopFailed), + ExpectedOutput: "prestart\nprocess\npoststart\n", + ExpectedResult: false, + } + poststopFailedUnit.Start() + defer poststopFailedUnit.Stop() + if ok, err := poststopFailedUnit.IsPass(); !ok { + logrus.Fatal(err) + } + + os.Remove(tmpFile.Name()) + logrus.Infof("Runtime lifecircle test succeeded.") +} + +func testMainConfigs(runtime string) { + hostnameArgs := "--args=./runtimetest --rootfs=rootfs --hostname=zenlin" + + hostnameUnit := TestUnit{ + Name: "configs", + Runtime: runtime, + Args: hostnameArgs, + ExpectedResult: true, + } + if _, err := hostnameUnit.Start(); err != nil { + logrus.Fatalf("Failed to start main config '%s' case: %v", hostnameUnit.Name, err) + } + defer hostnameUnit.Stop() + if ok, err := hostnameUnit.IsPass(); !ok { + logrus.Fatalf("Failed to test main config '%s' case: %v", hostnameUnit.Name, err) + } + + logrus.Infof("Runtime main config test succeeded.") } diff --git a/testunit.go b/testunit.go new file mode 100644 index 000000000..0a79a89f8 --- /dev/null +++ b/testunit.go @@ -0,0 +1,201 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path" + "strings" + + "github.com/Sirupsen/logrus" + rspec "github.com/opencontainers/runtime-spec/specs-go" +) + +const ( + testCacheDir = "./bundles/" + configFile = "config.json" + ociTools = "ocitools" +) + +// TestUnit for storage testcase +type TestUnit struct { + ID string + Name string + Runtime string + Args string + ExpectedOutput string + ExpectedResult bool + + output []byte + err error + bundlePath string +} + +// Start a test unit +// Generate a bundle from unit's args and start it by unit's runtime +func (unit *TestUnit) Start() ([]byte, error) { + if unit.Name == "" || unit.Runtime == "" || unit.Args == "" { + return nil, errors.New("Could not start a test unit which does not have a 'Name', 'Runtime' or 'Args'.") + } + + unit.prepareBundle() + unit.generateConfigs() + + if unit.ID == "" { + unit.ID = "test" + } + cmd := exec.Command(unit.Runtime, "start", unit.ID, unit.bundlePath) + fmt.Println(cmd) + cmd.Stdin = os.Stdin + unit.output, unit.err = cmd.CombinedOutput() + + return unit.output, unit.err +} + +// Stop a test unit and remove the generated bundle +func (unit *TestUnit) Stop() ([]byte, error) { + if unit.ID == "" { + return nil, errors.New("Could not stop a test unit which does not have an 'ID'.") + } + cmd := exec.Command(unit.Runtime, "stop", unit.ID) + cmd.Stdin = os.Stdin + output, err := cmd.CombinedOutput() + + os.RemoveAll(unit.bundlePath) + return output, err +} + +// GetState return the state of a running test unit +func (unit *TestUnit) GetState() (rspec.State, error) { + var state rspec.State + if unit.ID == "" { + return rspec.State{}, errors.New("Could not get the state of a test unit which does not have an 'ID'.") + } + cmd := exec.Command(unit.Runtime, "state", unit.ID) + cmd.Stdin = os.Stdin + output, err := cmd.CombinedOutput() + + if err != nil { + err = json.Unmarshal(output, &state) + } + return state, err +} + +// GetBundlePath return the bundle of a test unit +func (unit *TestUnit) GetBundlePath() string { + return unit.bundlePath +} + +// IsPass checks whether a test is successful +func (unit *TestUnit) IsPass() (bool, error) { + var res bool + if unit.err != nil { + res = true + } else { + res = false + } + if res == unit.ExpectedResult { + if unit.ExpectedOutput != "" && unit.ExpectedOutput == string(unit.output) { + return true, unit.err + } + } + return false, unit.err +} + +func (unit *TestUnit) prepareBundle() { + // Create bundle follder + unit.bundlePath = path.Join(testCacheDir, unit.Name) + if err := os.RemoveAll(unit.bundlePath); err != nil { + logrus.Fatalf("Failed to remove '%s': %v\n", unit.bundlePath, err) + } + + if err := os.Mkdir(unit.bundlePath, os.ModePerm); err != nil { + logrus.Fatalf("Failed to create '%s': %v\n", unit.bundlePath, err) + } + + // Create rootfs for bundle + rootfs := unit.bundlePath + "/rootfs" + if err := untarRootfs(rootfs); err != nil { + logrus.Fatal(err) + } + + // Copy runtimtest from plugins to rootfs + src := "./runtimetest" + dest := rootfs + "/runtimetest" + + in, err := os.Open(src) + if err != nil { + logrus.Fatalf("Failed to open %s: %v\n", src, err) + } + defer in.Close() + + out, err := os.Create(dest) + if err != nil { + logrus.Fatalf("Failed to create %s: %v\n", dest, err) + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + logrus.Fatalf("Failed to copy runtimetest to rootfs: %v\n", err) + } + + if err := os.Chmod(dest, os.ModePerm); err != nil { + logrus.Fatalf("Failed to chmod runtimetest: %v\n", err) + } +} + +func (unit *TestUnit) generateConfigs() { + args := splitArgs(unit.Args) + + argsNew := make([]string, len(args)+1) + argsNew[0] = "generate" + for i, a := range args { + argsNew[i+1] = a + } + + cmd := exec.Command("./ocitools", argsNew...) + cmd.Stdin = os.Stdin + _, err := cmd.CombinedOutput() + if err != nil { + logrus.Fatalf("Failed to generate %s: %v\n", path.Join(unit.bundlePath, configFile), err) + } + + os.Rename(path.Join(".", configFile), path.Join(unit.bundlePath, configFile)) +} + +func untarRootfs(rootfs string) error { + // Create rootfs folder to bundle + if err := os.Mkdir(rootfs, os.ModePerm); err != nil { + return fmt.Errorf("Failed to create rootfs for bundle '%s': %v\n", rootfs, err) + } + + cmd := exec.Command("tar", "-xf", "rootfs.tar.gz", "-C", rootfs) + cmd.Dir = "" + cmd.Stdin = os.Stdin + _, err := cmd.CombinedOutput() + + if err != nil { + return err + } + return 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 +} diff --git a/units/unit.go b/units/unit.go deleted file mode 100644 index 627cb07b8..000000000 --- a/units/unit.go +++ /dev/null @@ -1,318 +0,0 @@ -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 -}