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/generate.go b/generate.go index 3b6c740ad..edf068c28 100644 --- a/generate.go +++ b/generate.go @@ -82,7 +82,7 @@ var generateCommand = cli.Command{ Usage: "generate a OCI spec file", Flags: generateFlags, Action: func(context *cli.Context) { - spec := getDefaultTemplate() + spec := GetDefaultTemplate() template := context.String("template") if template != "" { var err error @@ -143,10 +143,11 @@ func modify(spec *rspec.Spec, context *cli.Context) error { spec.Process.Terminal = context.Bool("tty") } + fmt.Println(context.StringSlice("args")) for i, a := range context.StringSlice("args") { if a != "" { if i == 0 { - // Replace "sh" from getDefaultTemplate() + // Replace "sh" from GetDefaultTemplate() spec.Process.Args[0] = a } else { spec.Process.Args = append(spec.Process.Args, a) @@ -675,7 +676,8 @@ func setupNamespaces(spec *rspec.Spec, context *cli.Context) { func sPtr(s string) *string { return &s } -func getDefaultTemplate() *rspec.Spec { +//GetDefaultTemplate provides a default oci spec. +func GetDefaultTemplate() *rspec.Spec { spec := rspec.Spec{ Version: rspec.Version, Platform: rspec.Platform{ diff --git a/runtimetest.go b/runtimetest.go index d73dc4a47..587fe63c5 100644 --- a/runtimetest.go +++ b/runtimetest.go @@ -1,62 +1,59 @@ package main import ( + "errors" + "fmt" + "io" "os" + "path" + "runtime" + "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 + logrus.Fatalf("Failed to create cache dir: %s", bundleCacheDir) } - - 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) + _, err := testState(runtime) + if err != nil { + os.RemoveAll(bundleCacheDir) + logrus.Fatalf("\n%v", err) } + logrus.Info("Runtime state test succeeded.") - if err := os.Remove("./runtime.json"); err != nil { - logrus.Fatalf("remove ./runtime.json err: %v\n", err) + output, err := testMainConfigs(runtime) + if err != nil { + os.RemoveAll(bundleCacheDir) + logrus.Infof("\n%s", output) + logrus.Fatalf("\n%v", err) } - - if err := os.Remove("./config.json"); err != nil { - logrus.Fatalf("remove ./config.json err: %v\n", err) + if output != "" { + logrus.Infof("\n%s", output) } + logrus.Info("Runtime main config test succeeded.") }, } @@ -69,12 +66,121 @@ 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) (string, error) { + testConfig := getDefaultConfig() + testConfig.Process.Args = []string{"sleep", "60"} + //TODO: use UUID + testID := "12345678" + unit := TestUnit{ + Name: "state", + Runtime: runtime, + Config: testConfig, + 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 { + return "", err + } + + defer unit.Stop() + if state.ID != testID { + return "", fmt.Errorf("Expect container ID: %s to match: %s", state.ID, testID) + } + if state.BundlePath != unit.GetBundlePath() { + return "", fmt.Errorf("Expect container bundle path: %s to match: %s", state.BundlePath, unit.GetBundlePath()) + } + + unitDup := TestUnit{ + Name: "state-dup", + Runtime: runtime, + Config: testConfig, + ID: testID, + } + unitDup.Start() + defer unitDup.Stop() + // Expected to get error + if output, err := unitDup.GetOutput(); err != nil { + return output, nil + } else { + return output, errors.New("Failed to popup error with duplicated container ID") + } +} + +func testMainConfigs(runtime string) (string, error) { + testConfig := getDefaultConfig() + testConfig.Process.Args = []string{"./runtimetest"} + testConfig.Hostname = "zenlin" + + hostnameUnit := TestUnit{ + Name: "configs", + Runtime: runtime, + Config: testConfig, + ExpectedResult: true, + } + + hostnameUnit.Prepare() + defer hostnameUnit.Clean() + + // Copy runtimtest from plugins to rootfs + src := "./runtimetest" + dest := path.Join(hostnameUnit.GetBundlePath(), "rootfs", "runtimetest") + if err := copyFile(dest, src); err != nil { + return "", fmt.Errorf("Failed to copy '%s' to '%s': %v\n", src, dest, err) + } + if err := os.Chmod(dest, os.ModePerm); err != nil { + return "", fmt.Errorf("Failed to chmod runtimetest: %v\n", err) + } + + src = path.Join(hostnameUnit.GetBundlePath(), configFile) + dest = path.Join(hostnameUnit.GetBundlePath(), "rootfs", configFile) + if err := copyFile(dest, src); err != nil { + return "", fmt.Errorf("Failed to copy '%s' to '%s': %v\n", src, dest, err) + } + + hostnameUnit.Start() + defer hostnameUnit.Stop() + if output, err := hostnameUnit.GetOutput(); err != nil { + return output, fmt.Errorf("Failed to test main config '%s' case: %v", hostnameUnit.Name, err) } else { - unit.Run() + return output, nil + } +} + +func copyFile(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 } - return + defer out.Close() + _, err = io.Copy(out, in) + cerr := out.Close() + if err != nil { + return err + } + return cerr +} + +func getDefaultConfig() *rspec.Spec { + config := GetDefaultTemplate() + config.Root.Path = "rootfs" + config.Platform.OS = runtime.GOOS + config.Platform.Arch = runtime.GOARCH + config.Process.Cwd = "/" + + return config } diff --git a/testunit.go b/testunit.go new file mode 100644 index 000000000..4f31103c1 --- /dev/null +++ b/testunit.go @@ -0,0 +1,177 @@ +package main + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path" + + rspec "github.com/opencontainers/runtime-spec/specs-go" +) + +const ( + testCacheDir = "./bundles/" + configFile = "config.json" + ociTools = "ocitools" + TEST_READY = "test ready" +) + +// TestUnit for storage testcase +type TestUnit struct { + ID string + Name string + Runtime string + Config *rspec.Spec + ExpectedOutput string + ExpectedResult bool + + output string + err error + bundlePath string + ready string +} + +// Prepare a bundle for a test unit +func (unit *TestUnit) Prepare() error { + if unit.ready == TEST_READY { + return nil + } + if unit.Name == "" || unit.Runtime == "" || unit.Config == nil { + return errors.New("Could not prepare a test unit which does not have 'Name', 'Runtime' or 'Config'.") + } + + if err := unit.prepareBundle(); err != nil { + return errors.New("Failed to prepare bundle") + } + + unit.ready = TEST_READY + return nil +} + +// Clean the generated bundle of a test unit +func (unit *TestUnit) Clean() error { + if unit.ready == TEST_READY { + unit.ready = "" + return os.RemoveAll(unit.bundlePath) + } + return nil +} + +// Start a test unit +// Generate a bundle from unit's args and start it by unit's runtime +func (unit *TestUnit) Start() error { + if unit.ready != TEST_READY { + if err := unit.Prepare(); err != nil { + return err + } + } + + if unit.ID == "" { + unit.ID = "test" + } + + var stderr bytes.Buffer + var stdout bytes.Buffer + + cmd := exec.Command(unit.Runtime, "start", "-b", unit.bundlePath, unit.ID) + cmd.Stdin = os.Stdin + cmd.Stderr = &stderr + cmd.Stdout = &stdout + err := cmd.Run() + if err != nil { + unit.err = errors.New(stderr.String()) + } + unit.output = stdout.String() + + return 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() + + unit.Clean() + return output, err +} + +// GetState return the state of a running test unit +func (unit *TestUnit) GetState() (rspec.State, error) { + 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() + + var state rspec.State + 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) GetOutput() (string, error) { + return unit.output, unit.err +} + +func (unit *TestUnit) prepareBundle() error { + // Create bundle follder + cwd, _ := os.Getwd() + unit.bundlePath = path.Join(cwd, testCacheDir, unit.Name) + if err := os.RemoveAll(unit.bundlePath); err != nil { + return err + } + + if err := os.Mkdir(unit.bundlePath, os.ModePerm); err != nil { + return err + } + + // Create rootfs for bundle + rootfs := unit.bundlePath + "/rootfs" + if err := untarRootfs(rootfs); err != nil { + return err + } + + data, err := json.MarshalIndent(&unit.Config, "", "\t") + if err != nil { + return err + } + cName := path.Join(unit.bundlePath, configFile) + if err := ioutil.WriteFile(cName, data, 0666); err != nil { + return err + } + + return nil +} + +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 +} 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 -}