From bf3eeeea9fde18773c3c8c5b5d27bb9405726c69 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 9d1c354029ee2682ba6151b7a16a473a2948f3bb Mon Sep 17 00:00:00 2001 From: "linzhinan(zen lin)" Date: Mon, 9 May 2016 21:16:43 +0800 Subject: [PATCH 2/6] Update Readme.MD for runtimetest Signed-off-by: linzhinan(zen lin) --- README.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index df82c3e0c..3312f87b3 100644 --- a/README.md +++ b/README.md @@ -29,18 +29,19 @@ FATA[0000] Bundle path shouldn't be empty ## Testing OCI runtimes ```sh -$ 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 ``` [bundle]: https://github.com/opencontainers/runtime-spec/blob/master/bundle.md From 1f8cb69a3d1b2810957ee11aa6af5ed3bb0a25a6 Mon Sep 17 00:00:00 2001 From: liang chenye Date: Tue, 10 May 2016 14:38:28 +0800 Subject: [PATCH 3/6] rename cmd/runtimetest to main config test; add state test Signed-off-by: liang chenye --- config/config.go | 57 --------- generate.go | 8 +- runtimetest.go | 174 +++++++++++++++++++++----- testunit.go | 177 ++++++++++++++++++++++++++ units/unit.go | 318 ----------------------------------------------- 5 files changed, 322 insertions(+), 412 deletions(-) delete mode 100644 config/config.go create mode 100644 testunit.go delete mode 100644 units/unit.go 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 -} From 712c4208b243ddb52348d131c6ae213a12e52fd1 Mon Sep 17 00:00:00 2001 From: liang chenye Date: Tue, 10 May 2016 21:00:54 +0800 Subject: [PATCH 4/6] add uuid, update Godeps; add lifecircle test Signed-off-by: liang chenye --- Godeps/Godeps.json | 7 +- .../src/github.com/pborman/uuid/.travis.yml | 10 + .../github.com/pborman/uuid/CONTRIBUTING.md | 10 + .../src/github.com/pborman/uuid/CONTRIBUTORS | 1 + .../src/github.com/pborman/uuid/LICENSE | 27 + .../src/github.com/pborman/uuid/README.md | 13 + .../src/github.com/pborman/uuid/dce.go | 84 +++ .../src/github.com/pborman/uuid/doc.go | 8 + .../src/github.com/pborman/uuid/hash.go | 53 ++ .../src/github.com/pborman/uuid/json.go | 34 ++ .../src/github.com/pborman/uuid/json_test.go | 61 ++ .../src/github.com/pborman/uuid/node.go | 117 ++++ .../src/github.com/pborman/uuid/seq_test.go | 66 +++ .../src/github.com/pborman/uuid/sql.go | 66 +++ .../src/github.com/pborman/uuid/sql_test.go | 96 ++++ .../src/github.com/pborman/uuid/time.go | 132 +++++ .../src/github.com/pborman/uuid/util.go | 43 ++ .../src/github.com/pborman/uuid/uuid.go | 201 +++++++ .../src/github.com/pborman/uuid/uuid_test.go | 543 ++++++++++++++++++ .../src/github.com/pborman/uuid/version1.go | 41 ++ .../src/github.com/pborman/uuid/version4.go | 25 + README.md | 20 +- cases.conf | 2 - runtimetest.go | 120 ++-- testunit.go | 24 +- 25 files changed, 1743 insertions(+), 61 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/CONTRIBUTING.md create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/CONTRIBUTORS create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/LICENSE create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/README.md create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/dce.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/doc.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/hash.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/json.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/json_test.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/node.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/seq_test.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/sql.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/sql_test.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/time.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/util.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/uuid.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/uuid_test.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/version1.go create mode 100644 Godeps/_workspace/src/github.com/pborman/uuid/version4.go delete mode 100644 cases.conf diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 39d20e66a..1fda82f42 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "github.com/opencontainers/ocitools", - "GoVersion": "go1.4", + "GoVersion": "go1.6", "Deps": [ { "ImportPath": "github.com/Sirupsen/logrus", @@ -17,6 +17,11 @@ "Comment": "v0.5.0-34-g6e08c69", "Rev": "6e08c6983ef8c2173f10ca09266907d4e9e71716" }, + { + "ImportPath": "github.com/pborman/uuid", + "Comment": "v1.0-11-gc55201b", + "Rev": "c55201b036063326c5b1b89ccfe45a184973d073" + }, { "ImportPath": "github.com/syndtr/gocapability/capability", "Rev": "2c00daeb6c3b45114c80ac44119e7b8801fdd852" diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/.travis.yml b/Godeps/_workspace/src/github.com/pborman/uuid/.travis.yml new file mode 100644 index 000000000..a6a98db8a --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/.travis.yml @@ -0,0 +1,10 @@ +language: go + +go: + - 1.4.3 + - 1.5.3 + - release + - tip + +script: + - go test -v ./... diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/CONTRIBUTING.md b/Godeps/_workspace/src/github.com/pborman/uuid/CONTRIBUTING.md new file mode 100644 index 000000000..04fdf09f1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# How to contribute + +We definitely welcome patches and contribution to this project! + +### Legal requirements + +In order to protect both you and ourselves, you will need to sign the +[Contributor License Agreement](https://cla.developers.google.com/clas). + +You may have already signed it for other Google projects. diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/CONTRIBUTORS b/Godeps/_workspace/src/github.com/pborman/uuid/CONTRIBUTORS new file mode 100644 index 000000000..b382a04ed --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/CONTRIBUTORS @@ -0,0 +1 @@ +Paul Borman diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/LICENSE b/Godeps/_workspace/src/github.com/pborman/uuid/LICENSE new file mode 100644 index 000000000..5dc68268d --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/README.md b/Godeps/_workspace/src/github.com/pborman/uuid/README.md new file mode 100644 index 000000000..f023d47ca --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/README.md @@ -0,0 +1,13 @@ +This project was automatically exported from code.google.com/p/go-uuid + +# uuid ![build status](https://travis-ci.org/pborman/uuid.svg?branch=master) +The uuid package generates and inspects UUIDs based on [RFC 412](http://tools.ietf.org/html/rfc4122) and DCE 1.1: Authentication and Security Services. + +###### Install +`go get github.com/pborman/uuid` + +###### Documentation +[![GoDoc](https://godoc.org/github.com/pborman/uuid?status.svg)](http://godoc.org/github.com/pborman/uuid) + +Full `go doc` style documentation for the package can be viewed online without installing this package by using the GoDoc site here: +http://godoc.org/github.com/pborman/uuid diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/dce.go b/Godeps/_workspace/src/github.com/pborman/uuid/dce.go new file mode 100644 index 000000000..50a0f2d09 --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/dce.go @@ -0,0 +1,84 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "fmt" + "os" +) + +// A Domain represents a Version 2 domain +type Domain byte + +// Domain constants for DCE Security (Version 2) UUIDs. +const ( + Person = Domain(0) + Group = Domain(1) + Org = Domain(2) +) + +// NewDCESecurity returns a DCE Security (Version 2) UUID. +// +// The domain should be one of Person, Group or Org. +// On a POSIX system the id should be the users UID for the Person +// domain and the users GID for the Group. The meaning of id for +// the domain Org or on non-POSIX systems is site defined. +// +// For a given domain/id pair the same token may be returned for up to +// 7 minutes and 10 seconds. +func NewDCESecurity(domain Domain, id uint32) UUID { + uuid := NewUUID() + if uuid != nil { + uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2 + uuid[9] = byte(domain) + binary.BigEndian.PutUint32(uuid[0:], id) + } + return uuid +} + +// NewDCEPerson returns a DCE Security (Version 2) UUID in the person +// domain with the id returned by os.Getuid. +// +// NewDCEPerson(Person, uint32(os.Getuid())) +func NewDCEPerson() UUID { + return NewDCESecurity(Person, uint32(os.Getuid())) +} + +// NewDCEGroup returns a DCE Security (Version 2) UUID in the group +// domain with the id returned by os.Getgid. +// +// NewDCEGroup(Group, uint32(os.Getgid())) +func NewDCEGroup() UUID { + return NewDCESecurity(Group, uint32(os.Getgid())) +} + +// Domain returns the domain for a Version 2 UUID or false. +func (uuid UUID) Domain() (Domain, bool) { + if v, _ := uuid.Version(); v != 2 { + return 0, false + } + return Domain(uuid[9]), true +} + +// Id returns the id for a Version 2 UUID or false. +func (uuid UUID) Id() (uint32, bool) { + if v, _ := uuid.Version(); v != 2 { + return 0, false + } + return binary.BigEndian.Uint32(uuid[0:4]), true +} + +func (d Domain) String() string { + switch d { + case Person: + return "Person" + case Group: + return "Group" + case Org: + return "Org" + } + return fmt.Sprintf("Domain%d", int(d)) +} diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/doc.go b/Godeps/_workspace/src/github.com/pborman/uuid/doc.go new file mode 100644 index 000000000..d8bd013e6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/doc.go @@ -0,0 +1,8 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The uuid package generates and inspects UUIDs. +// +// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security Services. +package uuid diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/hash.go b/Godeps/_workspace/src/github.com/pborman/uuid/hash.go new file mode 100644 index 000000000..a0420c1ef --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/hash.go @@ -0,0 +1,53 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "crypto/md5" + "crypto/sha1" + "hash" +) + +// Well known Name Space IDs and UUIDs +var ( + NameSpace_DNS = Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + NameSpace_URL = Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8") + NameSpace_OID = Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8") + NameSpace_X500 = Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8") + NIL = Parse("00000000-0000-0000-0000-000000000000") +) + +// NewHash returns a new UUID derived from the hash of space concatenated with +// data generated by h. The hash should be at least 16 byte in length. The +// first 16 bytes of the hash are used to form the UUID. The version of the +// UUID will be the lower 4 bits of version. NewHash is used to implement +// NewMD5 and NewSHA1. +func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { + h.Reset() + h.Write(space) + h.Write([]byte(data)) + s := h.Sum(nil) + uuid := make([]byte, 16) + copy(uuid, s) + uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4) + uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant + return uuid +} + +// NewMD5 returns a new MD5 (Version 3) UUID based on the +// supplied name space and data. +// +// NewHash(md5.New(), space, data, 3) +func NewMD5(space UUID, data []byte) UUID { + return NewHash(md5.New(), space, data, 3) +} + +// NewSHA1 returns a new SHA1 (Version 5) UUID based on the +// supplied name space and data. +// +// NewHash(sha1.New(), space, data, 5) +func NewSHA1(space UUID, data []byte) UUID { + return NewHash(sha1.New(), space, data, 5) +} diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/json.go b/Godeps/_workspace/src/github.com/pborman/uuid/json.go new file mode 100644 index 000000000..9dda1dfba --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/json.go @@ -0,0 +1,34 @@ +// Copyright 2014 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "errors" + +func (u UUID) MarshalJSON() ([]byte, error) { + if len(u) != 16 { + return []byte(`""`), nil + } + var js [38]byte + js[0] = '"' + encodeHex(js[1:], u) + js[37] = '"' + return js[:], nil +} + +func (u *UUID) UnmarshalJSON(data []byte) error { + if string(data) == `""` { + return nil + } + if data[0] != '"' { + return errors.New("invalid UUID format") + } + data = data[1 : len(data)-1] + uu := Parse(string(data)) + if uu == nil { + return errors.New("invalid UUID format") + } + *u = uu + return nil +} diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/json_test.go b/Godeps/_workspace/src/github.com/pborman/uuid/json_test.go new file mode 100644 index 000000000..2866b8dc8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/json_test.go @@ -0,0 +1,61 @@ +// Copyright 2014 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/json" + "reflect" + "testing" +) + +var testUUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + +func TestJSON(t *testing.T) { + type S struct { + ID1 UUID + ID2 UUID + } + s1 := S{ID1: testUUID} + data, err := json.Marshal(&s1) + if err != nil { + t.Fatal(err) + } + var s2 S + if err := json.Unmarshal(data, &s2); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(&s1, &s2) { + t.Errorf("got %#v, want %#v", s2, s1) + } +} + +func BenchmarkUUID_MarshalJSON(b *testing.B) { + x := &struct { + UUID UUID `json:"uuid"` + }{} + x.UUID = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if x.UUID == nil { + b.Fatal("invalid uuid") + } + for i := 0; i < b.N; i++ { + js, err := json.Marshal(x) + if err != nil { + b.Fatalf("marshal json: %#v (%v)", js, err) + } + } +} + +func BenchmarkUUID_UnmarshalJSON(b *testing.B) { + js := []byte(`{"uuid":"f47ac10b-58cc-0372-8567-0e02b2c3d479"}`) + var x *struct { + UUID UUID `json:"uuid"` + } + for i := 0; i < b.N; i++ { + err := json.Unmarshal(js, &x) + if err != nil { + b.Fatalf("marshal json: %#v (%v)", js, err) + } + } +} diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/node.go b/Godeps/_workspace/src/github.com/pborman/uuid/node.go new file mode 100644 index 000000000..42d60da8f --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/node.go @@ -0,0 +1,117 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "net" + "sync" +) + +var ( + nodeMu sync.Mutex + interfaces []net.Interface // cached list of interfaces + ifname string // name of interface being used + nodeID []byte // hardware for version 1 UUIDs +) + +// NodeInterface returns the name of the interface from which the NodeID was +// derived. The interface "user" is returned if the NodeID was set by +// SetNodeID. +func NodeInterface() string { + defer nodeMu.Unlock() + nodeMu.Lock() + return ifname +} + +// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs. +// If name is "" then the first usable interface found will be used or a random +// Node ID will be generated. If a named interface cannot be found then false +// is returned. +// +// SetNodeInterface never fails when name is "". +func SetNodeInterface(name string) bool { + defer nodeMu.Unlock() + nodeMu.Lock() + return setNodeInterface(name) +} + +func setNodeInterface(name string) bool { + if interfaces == nil { + var err error + interfaces, err = net.Interfaces() + if err != nil && name != "" { + return false + } + } + + for _, ifs := range interfaces { + if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { + if setNodeID(ifs.HardwareAddr) { + ifname = ifs.Name + return true + } + } + } + + // We found no interfaces with a valid hardware address. If name + // does not specify a specific interface generate a random Node ID + // (section 4.1.6) + if name == "" { + if nodeID == nil { + nodeID = make([]byte, 6) + } + randomBits(nodeID) + return true + } + return false +} + +// NodeID returns a slice of a copy of the current Node ID, setting the Node ID +// if not already set. +func NodeID() []byte { + defer nodeMu.Unlock() + nodeMu.Lock() + if nodeID == nil { + setNodeInterface("") + } + nid := make([]byte, 6) + copy(nid, nodeID) + return nid +} + +// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes +// of id are used. If id is less than 6 bytes then false is returned and the +// Node ID is not set. +func SetNodeID(id []byte) bool { + defer nodeMu.Unlock() + nodeMu.Lock() + if setNodeID(id) { + ifname = "user" + return true + } + return false +} + +func setNodeID(id []byte) bool { + if len(id) < 6 { + return false + } + if nodeID == nil { + nodeID = make([]byte, 6) + } + copy(nodeID, id) + return true +} + +// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is +// not valid. The NodeID is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) NodeID() []byte { + if len(uuid) != 16 { + return nil + } + node := make([]byte, 6) + copy(node, uuid[10:]) + return node +} diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/seq_test.go b/Godeps/_workspace/src/github.com/pborman/uuid/seq_test.go new file mode 100644 index 000000000..3b3d1430d --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/seq_test.go @@ -0,0 +1,66 @@ +// Copyright 2014 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "flag" + "runtime" + "testing" + "time" +) + +// This test is only run when --regressions is passed on the go test line. +var regressions = flag.Bool("regressions", false, "run uuid regression tests") + +// TestClockSeqRace tests for a particular race condition of returning two +// identical Version1 UUIDs. The duration of 1 minute was chosen as the race +// condition, before being fixed, nearly always occured in under 30 seconds. +func TestClockSeqRace(t *testing.T) { + if !*regressions { + t.Skip("skipping regression tests") + } + duration := time.Minute + + done := make(chan struct{}) + defer close(done) + + ch := make(chan UUID, 10000) + ncpu := runtime.NumCPU() + switch ncpu { + case 0, 1: + // We can't run the test effectively. + t.Skip("skipping race test, only one CPU detected") + return + default: + runtime.GOMAXPROCS(ncpu) + } + for i := 0; i < ncpu; i++ { + go func() { + for { + select { + case <-done: + return + case ch <- NewUUID(): + } + } + }() + } + + uuids := make(map[string]bool) + cnt := 0 + start := time.Now() + for u := range ch { + s := u.String() + if uuids[s] { + t.Errorf("duplicate uuid after %d in %v: %s", cnt, time.Since(start), s) + return + } + uuids[s] = true + if time.Since(start) > duration { + return + } + cnt++ + } +} diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/sql.go b/Godeps/_workspace/src/github.com/pborman/uuid/sql.go new file mode 100644 index 000000000..d015bfd13 --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/sql.go @@ -0,0 +1,66 @@ +// Copyright 2015 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "database/sql/driver" + "errors" + "fmt" +) + +// Scan implements sql.Scanner so UUIDs can be read from databases transparently +// Currently, database types that map to string and []byte are supported. Please +// consult database-specific driver documentation for matching types. +func (uuid *UUID) Scan(src interface{}) error { + switch src.(type) { + case string: + // if an empty UUID comes from a table, we return a null UUID + if src.(string) == "" { + return nil + } + + // see uuid.Parse for required string format + parsed := Parse(src.(string)) + + if parsed == nil { + return errors.New("Scan: invalid UUID format") + } + + *uuid = parsed + case []byte: + b := src.([]byte) + + // if an empty UUID comes from a table, we return a null UUID + if len(b) == 0 { + return nil + } + + // assumes a simple slice of bytes if 16 bytes + // otherwise attempts to parse + if len(b) == 16 { + *uuid = UUID(b) + } else { + u := Parse(string(b)) + + if u == nil { + return errors.New("Scan: invalid UUID format") + } + + *uuid = u + } + + default: + return fmt.Errorf("Scan: unable to scan type %T into UUID", src) + } + + return nil +} + +// Value implements sql.Valuer so that UUIDs can be written to databases +// transparently. Currently, UUIDs map to strings. Please consult +// database-specific driver documentation for matching types. +func (uuid UUID) Value() (driver.Value, error) { + return uuid.String(), nil +} diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/sql_test.go b/Godeps/_workspace/src/github.com/pborman/uuid/sql_test.go new file mode 100644 index 000000000..103095156 --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/sql_test.go @@ -0,0 +1,96 @@ +// Copyright 2015 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "strings" + "testing" +) + +func TestScan(t *testing.T) { + var stringTest string = "f47ac10b-58cc-0372-8567-0e02b2c3d479" + var byteTest []byte = Parse(stringTest) + var badTypeTest int = 6 + var invalidTest string = "f47ac10b-58cc-0372-8567-0e02b2c3d4" + + // sunny day tests + + var uuid UUID + err := (&uuid).Scan(stringTest) + if err != nil { + t.Fatal(err) + } + + err = (&uuid).Scan([]byte(stringTest)) + if err != nil { + t.Fatal(err) + } + + err = (&uuid).Scan(byteTest) + if err != nil { + t.Fatal(err) + } + + // bad type tests + + err = (&uuid).Scan(badTypeTest) + if err == nil { + t.Error("int correctly parsed and shouldn't have") + } + if !strings.Contains(err.Error(), "unable to scan type") { + t.Error("attempting to parse an int returned an incorrect error message") + } + + // invalid/incomplete uuids + + err = (&uuid).Scan(invalidTest) + if err == nil { + t.Error("invalid uuid was parsed without error") + } + if !strings.Contains(err.Error(), "invalid UUID") { + t.Error("attempting to parse an invalid UUID returned an incorrect error message") + } + + err = (&uuid).Scan(byteTest[:len(byteTest)-2]) + if err == nil { + t.Error("invalid byte uuid was parsed without error") + } + if !strings.Contains(err.Error(), "invalid UUID") { + t.Error("attempting to parse an invalid byte UUID returned an incorrect error message") + } + + // empty tests + + uuid = nil + var emptySlice []byte + err = (&uuid).Scan(emptySlice) + if err != nil { + t.Fatal(err) + } + + if uuid != nil { + t.Error("UUID was not nil after scanning empty byte slice") + } + + uuid = nil + var emptyString string + err = (&uuid).Scan(emptyString) + if err != nil { + t.Fatal(err) + } + + if uuid != nil { + t.Error("UUID was not nil after scanning empty string") + } +} + +func TestValue(t *testing.T) { + stringTest := "f47ac10b-58cc-0372-8567-0e02b2c3d479" + uuid := Parse(stringTest) + val, _ := uuid.Value() + if val != stringTest { + t.Error("Value() did not return expected string") + } +} diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/time.go b/Godeps/_workspace/src/github.com/pborman/uuid/time.go new file mode 100644 index 000000000..eedf24219 --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/time.go @@ -0,0 +1,132 @@ +// Copyright 2014 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "sync" + "time" +) + +// A Time represents a time as the number of 100's of nanoseconds since 15 Oct +// 1582. +type Time int64 + +const ( + lillian = 2299160 // Julian day of 15 Oct 1582 + unix = 2440587 // Julian day of 1 Jan 1970 + epoch = unix - lillian // Days between epochs + g1582 = epoch * 86400 // seconds between epochs + g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs +) + +var ( + timeMu sync.Mutex + lasttime uint64 // last time we returned + clock_seq uint16 // clock sequence for this run + + timeNow = time.Now // for testing +) + +// UnixTime converts t the number of seconds and nanoseconds using the Unix +// epoch of 1 Jan 1970. +func (t Time) UnixTime() (sec, nsec int64) { + sec = int64(t - g1582ns100) + nsec = (sec % 10000000) * 100 + sec /= 10000000 + return sec, nsec +} + +// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and +// clock sequence as well as adjusting the clock sequence as needed. An error +// is returned if the current time cannot be determined. +func GetTime() (Time, uint16, error) { + defer timeMu.Unlock() + timeMu.Lock() + return getTime() +} + +func getTime() (Time, uint16, error) { + t := timeNow() + + // If we don't have a clock sequence already, set one. + if clock_seq == 0 { + setClockSequence(-1) + } + now := uint64(t.UnixNano()/100) + g1582ns100 + + // If time has gone backwards with this clock sequence then we + // increment the clock sequence + if now <= lasttime { + clock_seq = ((clock_seq + 1) & 0x3fff) | 0x8000 + } + lasttime = now + return Time(now), clock_seq, nil +} + +// ClockSequence returns the current clock sequence, generating one if not +// already set. The clock sequence is only used for Version 1 UUIDs. +// +// The uuid package does not use global static storage for the clock sequence or +// the last time a UUID was generated. Unless SetClockSequence a new random +// clock sequence is generated the first time a clock sequence is requested by +// ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) sequence is generated +// for +func ClockSequence() int { + defer timeMu.Unlock() + timeMu.Lock() + return clockSequence() +} + +func clockSequence() int { + if clock_seq == 0 { + setClockSequence(-1) + } + return int(clock_seq & 0x3fff) +} + +// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to +// -1 causes a new sequence to be generated. +func SetClockSequence(seq int) { + defer timeMu.Unlock() + timeMu.Lock() + setClockSequence(seq) +} + +func setClockSequence(seq int) { + if seq == -1 { + var b [2]byte + randomBits(b[:]) // clock sequence + seq = int(b[0])<<8 | int(b[1]) + } + old_seq := clock_seq + clock_seq = uint16(seq&0x3fff) | 0x8000 // Set our variant + if old_seq != clock_seq { + lasttime = 0 + } +} + +// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in +// uuid. It returns false if uuid is not valid. The time is only well defined +// for version 1 and 2 UUIDs. +func (uuid UUID) Time() (Time, bool) { + if len(uuid) != 16 { + return 0, false + } + time := int64(binary.BigEndian.Uint32(uuid[0:4])) + time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 + time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 + return Time(time), true +} + +// ClockSequence returns the clock sequence encoded in uuid. It returns false +// if uuid is not valid. The clock sequence is only well defined for version 1 +// and 2 UUIDs. +func (uuid UUID) ClockSequence() (int, bool) { + if len(uuid) != 16 { + return 0, false + } + return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff, true +} diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/util.go b/Godeps/_workspace/src/github.com/pborman/uuid/util.go new file mode 100644 index 000000000..fc8e052c7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/util.go @@ -0,0 +1,43 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "io" +) + +// randomBits completely fills slice b with random data. +func randomBits(b []byte) { + if _, err := io.ReadFull(rander, b); err != nil { + panic(err.Error()) // rand should never fail + } +} + +// xvalues returns the value of a byte as a hexadecimal digit or 255. +var xvalues = [256]byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +} + +// xtob converts the the first two hex bytes of x into a byte. +func xtob(x string) (byte, bool) { + b1 := xvalues[x[0]] + b2 := xvalues[x[1]] + return (b1 << 4) | b2, b1 != 255 && b2 != 255 +} diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/uuid.go b/Godeps/_workspace/src/github.com/pborman/uuid/uuid.go new file mode 100644 index 000000000..82c9e7ee7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/uuid.go @@ -0,0 +1,201 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "fmt" + "io" + "strings" +) + +// Array is a pass-by-value UUID that can be used as an effecient key in a map. +type Array [16]byte + +// UUID converts uuid into a slice. +func (uuid Array) UUID() UUID { + return uuid[:] +} + +// String returns the string representation of uuid, +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. +func (uuid Array) String() string { + return uuid.UUID().String() +} + +// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC +// 4122. +type UUID []byte + +// A Version represents a UUIDs version. +type Version byte + +// A Variant represents a UUIDs variant. +type Variant byte + +// Constants returned by Variant. +const ( + Invalid = Variant(iota) // Invalid UUID + RFC4122 // The variant specified in RFC4122 + Reserved // Reserved, NCS backward compatibility. + Microsoft // Reserved, Microsoft Corporation backward compatibility. + Future // Reserved for future definition. +) + +var rander = rand.Reader // random function + +// New returns a new random (version 4) UUID as a string. It is a convenience +// function for NewRandom().String(). +func New() string { + return NewRandom().String() +} + +// Parse decodes s into a UUID or returns nil. Both the UUID form of +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded. +func Parse(s string) UUID { + if len(s) == 36+9 { + if strings.ToLower(s[:9]) != "urn:uuid:" { + return nil + } + s = s[9:] + } else if len(s) != 36 { + return nil + } + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return nil + } + var uuid [16]byte + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + if v, ok := xtob(s[x:]); !ok { + return nil + } else { + uuid[i] = v + } + } + return uuid[:] +} + +// Equal returns true if uuid1 and uuid2 are equal. +func Equal(uuid1, uuid2 UUID) bool { + return bytes.Equal(uuid1, uuid2) +} + +// Array returns an array representation of uuid that can be used as a map key. +// Array panics if uuid is not valid. +func (uuid UUID) Array() Array { + if len(uuid) != 16 { + panic("invalid uuid") + } + var a Array + copy(a[:], uuid) + return a +} + +// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// , or "" if uuid is invalid. +func (uuid UUID) String() string { + if len(uuid) != 16 { + return "" + } + var buf [36]byte + encodeHex(buf[:], uuid) + return string(buf[:]) +} + +// URN returns the RFC 2141 URN form of uuid, +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid. +func (uuid UUID) URN() string { + if len(uuid) != 16 { + return "" + } + var buf [36 + 9]byte + copy(buf[:], "urn:uuid:") + encodeHex(buf[9:], uuid) + return string(buf[:]) +} + +func encodeHex(dst []byte, uuid UUID) { + hex.Encode(dst[:], uuid[:4]) + dst[8] = '-' + hex.Encode(dst[9:13], uuid[4:6]) + dst[13] = '-' + hex.Encode(dst[14:18], uuid[6:8]) + dst[18] = '-' + hex.Encode(dst[19:23], uuid[8:10]) + dst[23] = '-' + hex.Encode(dst[24:], uuid[10:]) +} + +// Variant returns the variant encoded in uuid. It returns Invalid if +// uuid is invalid. +func (uuid UUID) Variant() Variant { + if len(uuid) != 16 { + return Invalid + } + switch { + case (uuid[8] & 0xc0) == 0x80: + return RFC4122 + case (uuid[8] & 0xe0) == 0xc0: + return Microsoft + case (uuid[8] & 0xe0) == 0xe0: + return Future + default: + return Reserved + } +} + +// Version returns the version of uuid. It returns false if uuid is not +// valid. +func (uuid UUID) Version() (Version, bool) { + if len(uuid) != 16 { + return 0, false + } + return Version(uuid[6] >> 4), true +} + +func (v Version) String() string { + if v > 15 { + return fmt.Sprintf("BAD_VERSION_%d", v) + } + return fmt.Sprintf("VERSION_%d", v) +} + +func (v Variant) String() string { + switch v { + case RFC4122: + return "RFC4122" + case Reserved: + return "Reserved" + case Microsoft: + return "Microsoft" + case Future: + return "Future" + case Invalid: + return "Invalid" + } + return fmt.Sprintf("BadVariant%d", int(v)) +} + +// SetRand sets the random number generator to r, which implents io.Reader. +// If r.Read returns an error when the package requests random data then +// a panic will be issued. +// +// Calling SetRand with nil sets the random number generator to the default +// generator. +func SetRand(r io.Reader) { + if r == nil { + rander = rand.Reader + return + } + rander = r +} diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/uuid_test.go b/Godeps/_workspace/src/github.com/pborman/uuid/uuid_test.go new file mode 100644 index 000000000..cb1cd5cf9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/uuid_test.go @@ -0,0 +1,543 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "fmt" + "os" + "strings" + "testing" + "time" +) + +type test struct { + in string + version Version + variant Variant + isuuid bool +} + +var tests = []test{ + {"f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, RFC4122, true}, + {"f47ac10b-58cc-1372-8567-0e02b2c3d479", 1, RFC4122, true}, + {"f47ac10b-58cc-2372-8567-0e02b2c3d479", 2, RFC4122, true}, + {"f47ac10b-58cc-3372-8567-0e02b2c3d479", 3, RFC4122, true}, + {"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-5372-8567-0e02b2c3d479", 5, RFC4122, true}, + {"f47ac10b-58cc-6372-8567-0e02b2c3d479", 6, RFC4122, true}, + {"f47ac10b-58cc-7372-8567-0e02b2c3d479", 7, RFC4122, true}, + {"f47ac10b-58cc-8372-8567-0e02b2c3d479", 8, RFC4122, true}, + {"f47ac10b-58cc-9372-8567-0e02b2c3d479", 9, RFC4122, true}, + {"f47ac10b-58cc-a372-8567-0e02b2c3d479", 10, RFC4122, true}, + {"f47ac10b-58cc-b372-8567-0e02b2c3d479", 11, RFC4122, true}, + {"f47ac10b-58cc-c372-8567-0e02b2c3d479", 12, RFC4122, true}, + {"f47ac10b-58cc-d372-8567-0e02b2c3d479", 13, RFC4122, true}, + {"f47ac10b-58cc-e372-8567-0e02b2c3d479", 14, RFC4122, true}, + {"f47ac10b-58cc-f372-8567-0e02b2c3d479", 15, RFC4122, true}, + + {"urn:uuid:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"URN:UUID:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-1567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-2567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-3567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-4567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-5567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-6567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-7567-0e02b2c3d479", 4, Reserved, true}, + {"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-9567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-a567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-b567-0e02b2c3d479", 4, RFC4122, true}, + {"f47ac10b-58cc-4372-c567-0e02b2c3d479", 4, Microsoft, true}, + {"f47ac10b-58cc-4372-d567-0e02b2c3d479", 4, Microsoft, true}, + {"f47ac10b-58cc-4372-e567-0e02b2c3d479", 4, Future, true}, + {"f47ac10b-58cc-4372-f567-0e02b2c3d479", 4, Future, true}, + + {"f47ac10b158cc-5372-a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc25372-a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-53723a567-0e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-5372-a56740e02b2c3d479", 0, Invalid, false}, + {"f47ac10b-58cc-5372-a567-0e02-2c3d479", 0, Invalid, false}, + {"g47ac10b-58cc-4372-a567-0e02b2c3d479", 0, Invalid, false}, +} + +var constants = []struct { + c interface{} + name string +}{ + {Person, "Person"}, + {Group, "Group"}, + {Org, "Org"}, + {Invalid, "Invalid"}, + {RFC4122, "RFC4122"}, + {Reserved, "Reserved"}, + {Microsoft, "Microsoft"}, + {Future, "Future"}, + {Domain(17), "Domain17"}, + {Variant(42), "BadVariant42"}, +} + +func testTest(t *testing.T, in string, tt test) { + uuid := Parse(in) + if ok := (uuid != nil); ok != tt.isuuid { + t.Errorf("Parse(%s) got %v expected %v\b", in, ok, tt.isuuid) + } + if uuid == nil { + return + } + + if v := uuid.Variant(); v != tt.variant { + t.Errorf("Variant(%s) got %d expected %d\b", in, v, tt.variant) + } + if v, _ := uuid.Version(); v != tt.version { + t.Errorf("Version(%s) got %d expected %d\b", in, v, tt.version) + } +} + +func TestUUID(t *testing.T) { + for _, tt := range tests { + testTest(t, tt.in, tt) + testTest(t, strings.ToUpper(tt.in), tt) + } +} + +func TestConstants(t *testing.T) { + for x, tt := range constants { + v, ok := tt.c.(fmt.Stringer) + if !ok { + t.Errorf("%x: %v: not a stringer", x, v) + } else if s := v.String(); s != tt.name { + v, _ := tt.c.(int) + t.Errorf("%x: Constant %T:%d gives %q, expected %q", x, tt.c, v, s, tt.name) + } + } +} + +func TestRandomUUID(t *testing.T) { + m := make(map[string]bool) + for x := 1; x < 32; x++ { + uuid := NewRandom() + s := uuid.String() + if m[s] { + t.Errorf("NewRandom returned duplicated UUID %s", s) + } + m[s] = true + if v, _ := uuid.Version(); v != 4 { + t.Errorf("Random UUID of version %s", v) + } + if uuid.Variant() != RFC4122 { + t.Errorf("Random UUID is variant %d", uuid.Variant()) + } + } +} + +func TestNew(t *testing.T) { + m := make(map[string]bool) + for x := 1; x < 32; x++ { + s := New() + if m[s] { + t.Errorf("New returned duplicated UUID %s", s) + } + m[s] = true + uuid := Parse(s) + if uuid == nil { + t.Errorf("New returned %q which does not decode", s) + continue + } + if v, _ := uuid.Version(); v != 4 { + t.Errorf("Random UUID of version %s", v) + } + if uuid.Variant() != RFC4122 { + t.Errorf("Random UUID is variant %d", uuid.Variant()) + } + } +} + +func clockSeq(t *testing.T, uuid UUID) int { + seq, ok := uuid.ClockSequence() + if !ok { + t.Fatalf("%s: invalid clock sequence", uuid) + } + return seq +} + +func TestClockSeq(t *testing.T) { + // Fake time.Now for this test to return a monotonically advancing time; restore it at end. + defer func(orig func() time.Time) { timeNow = orig }(timeNow) + monTime := time.Now() + timeNow = func() time.Time { + monTime = monTime.Add(1 * time.Second) + return monTime + } + + SetClockSequence(-1) + uuid1 := NewUUID() + uuid2 := NewUUID() + + if clockSeq(t, uuid1) != clockSeq(t, uuid2) { + t.Errorf("clock sequence %d != %d", clockSeq(t, uuid1), clockSeq(t, uuid2)) + } + + SetClockSequence(-1) + uuid2 = NewUUID() + + // Just on the very off chance we generated the same sequence + // two times we try again. + if clockSeq(t, uuid1) == clockSeq(t, uuid2) { + SetClockSequence(-1) + uuid2 = NewUUID() + } + if clockSeq(t, uuid1) == clockSeq(t, uuid2) { + t.Errorf("Duplicate clock sequence %d", clockSeq(t, uuid1)) + } + + SetClockSequence(0x1234) + uuid1 = NewUUID() + if seq := clockSeq(t, uuid1); seq != 0x1234 { + t.Errorf("%s: expected seq 0x1234 got 0x%04x", uuid1, seq) + } +} + +func TestCoding(t *testing.T) { + text := "7d444840-9dc0-11d1-b245-5ffdce74fad2" + urn := "urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2" + data := UUID{ + 0x7d, 0x44, 0x48, 0x40, + 0x9d, 0xc0, + 0x11, 0xd1, + 0xb2, 0x45, + 0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2, + } + if v := data.String(); v != text { + t.Errorf("%x: encoded to %s, expected %s", data, v, text) + } + if v := data.URN(); v != urn { + t.Errorf("%x: urn is %s, expected %s", data, v, urn) + } + + uuid := Parse(text) + if !Equal(uuid, data) { + t.Errorf("%s: decoded to %s, expected %s", text, uuid, data) + } +} + +func TestVersion1(t *testing.T) { + uuid1 := NewUUID() + uuid2 := NewUUID() + + if Equal(uuid1, uuid2) { + t.Errorf("%s:duplicate uuid", uuid1) + } + if v, _ := uuid1.Version(); v != 1 { + t.Errorf("%s: version %s expected 1", uuid1, v) + } + if v, _ := uuid2.Version(); v != 1 { + t.Errorf("%s: version %s expected 1", uuid2, v) + } + n1 := uuid1.NodeID() + n2 := uuid2.NodeID() + if !bytes.Equal(n1, n2) { + t.Errorf("Different nodes %x != %x", n1, n2) + } + t1, ok := uuid1.Time() + if !ok { + t.Errorf("%s: invalid time", uuid1) + } + t2, ok := uuid2.Time() + if !ok { + t.Errorf("%s: invalid time", uuid2) + } + q1, ok := uuid1.ClockSequence() + if !ok { + t.Errorf("%s: invalid clock sequence", uuid1) + } + q2, ok := uuid2.ClockSequence() + if !ok { + t.Errorf("%s: invalid clock sequence", uuid2) + } + + switch { + case t1 == t2 && q1 == q2: + t.Error("time stopped") + case t1 > t2 && q1 == q2: + t.Error("time reversed") + case t1 < t2 && q1 != q2: + t.Error("clock sequence chaned unexpectedly") + } +} + +func TestNode(t *testing.T) { + // This test is mostly to make sure we don't leave nodeMu locked. + ifname = "" + if ni := NodeInterface(); ni != "" { + t.Errorf("NodeInterface got %q, want %q", ni, "") + } + if SetNodeInterface("xyzzy") { + t.Error("SetNodeInterface succeeded on a bad interface name") + } + if !SetNodeInterface("") { + t.Error("SetNodeInterface failed") + } + if ni := NodeInterface(); ni == "" { + t.Error("NodeInterface returned an empty string") + } + + ni := NodeID() + if len(ni) != 6 { + t.Errorf("ni got %d bytes, want 6", len(ni)) + } + hasData := false + for _, b := range ni { + if b != 0 { + hasData = true + } + } + if !hasData { + t.Error("nodeid is all zeros") + } + + id := []byte{1, 2, 3, 4, 5, 6, 7, 8} + SetNodeID(id) + ni = NodeID() + if !bytes.Equal(ni, id[:6]) { + t.Errorf("got nodeid %v, want %v", ni, id[:6]) + } + + if ni := NodeInterface(); ni != "user" { + t.Errorf("got inteface %q, want %q", ni, "user") + } +} + +func TestNodeAndTime(t *testing.T) { + // Time is February 5, 1998 12:30:23.136364800 AM GMT + + uuid := Parse("7d444840-9dc0-11d1-b245-5ffdce74fad2") + node := []byte{0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2} + + ts, ok := uuid.Time() + if ok { + c := time.Unix(ts.UnixTime()) + want := time.Date(1998, 2, 5, 0, 30, 23, 136364800, time.UTC) + if !c.Equal(want) { + t.Errorf("Got time %v, want %v", c, want) + } + } else { + t.Errorf("%s: bad time", uuid) + } + if !bytes.Equal(node, uuid.NodeID()) { + t.Errorf("Expected node %v got %v", node, uuid.NodeID()) + } +} + +func TestMD5(t *testing.T) { + uuid := NewMD5(NameSpace_DNS, []byte("python.org")).String() + want := "6fa459ea-ee8a-3ca4-894e-db77e160355e" + if uuid != want { + t.Errorf("MD5: got %q expected %q", uuid, want) + } +} + +func TestSHA1(t *testing.T) { + uuid := NewSHA1(NameSpace_DNS, []byte("python.org")).String() + want := "886313e1-3b8a-5372-9b90-0c9aee199e5d" + if uuid != want { + t.Errorf("SHA1: got %q expected %q", uuid, want) + } +} + +func TestNodeID(t *testing.T) { + nid := []byte{1, 2, 3, 4, 5, 6} + SetNodeInterface("") + s := NodeInterface() + if s == "" || s == "user" { + t.Errorf("NodeInterface %q after SetInteface", s) + } + node1 := NodeID() + if node1 == nil { + t.Error("NodeID nil after SetNodeInterface", s) + } + SetNodeID(nid) + s = NodeInterface() + if s != "user" { + t.Errorf("Expected NodeInterface %q got %q", "user", s) + } + node2 := NodeID() + if node2 == nil { + t.Error("NodeID nil after SetNodeID", s) + } + if bytes.Equal(node1, node2) { + t.Error("NodeID not changed after SetNodeID", s) + } else if !bytes.Equal(nid, node2) { + t.Errorf("NodeID is %x, expected %x", node2, nid) + } +} + +func testDCE(t *testing.T, name string, uuid UUID, domain Domain, id uint32) { + if uuid == nil { + t.Errorf("%s failed", name) + return + } + if v, _ := uuid.Version(); v != 2 { + t.Errorf("%s: %s: expected version 2, got %s", name, uuid, v) + return + } + if v, ok := uuid.Domain(); !ok || v != domain { + if !ok { + t.Errorf("%s: %d: Domain failed", name, uuid) + } else { + t.Errorf("%s: %s: expected domain %d, got %d", name, uuid, domain, v) + } + } + if v, ok := uuid.Id(); !ok || v != id { + if !ok { + t.Errorf("%s: %d: Id failed", name, uuid) + } else { + t.Errorf("%s: %s: expected id %d, got %d", name, uuid, id, v) + } + } +} + +func TestDCE(t *testing.T) { + testDCE(t, "NewDCESecurity", NewDCESecurity(42, 12345678), 42, 12345678) + testDCE(t, "NewDCEPerson", NewDCEPerson(), Person, uint32(os.Getuid())) + testDCE(t, "NewDCEGroup", NewDCEGroup(), Group, uint32(os.Getgid())) +} + +type badRand struct{} + +func (r badRand) Read(buf []byte) (int, error) { + for i, _ := range buf { + buf[i] = byte(i) + } + return len(buf), nil +} + +func TestBadRand(t *testing.T) { + SetRand(badRand{}) + uuid1 := New() + uuid2 := New() + if uuid1 != uuid2 { + t.Errorf("execpted duplicates, got %q and %q", uuid1, uuid2) + } + SetRand(nil) + uuid1 = New() + uuid2 = New() + if uuid1 == uuid2 { + t.Errorf("unexecpted duplicates, got %q", uuid1) + } +} + +func TestUUID_Array(t *testing.T) { + expect := Array{ + 0xf4, 0x7a, 0xc1, 0x0b, + 0x58, 0xcc, + 0x03, 0x72, + 0x85, 0x67, + 0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79, + } + uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if uuid == nil { + t.Fatal("invalid uuid") + } + if uuid.Array() != expect { + t.Fatal("invalid array") + } +} + +func TestArray_UUID(t *testing.T) { + array := Array{ + 0xf4, 0x7a, 0xc1, 0x0b, + 0x58, 0xcc, + 0x03, 0x72, + 0x85, 0x67, + 0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79, + } + expect := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if expect == nil { + t.Fatal("invalid uuid") + } + if !bytes.Equal(array.UUID(), expect) { + t.Fatal("invalid uuid") + } +} + +func BenchmarkParse(b *testing.B) { + for i := 0; i < b.N; i++ { + uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if uuid == nil { + b.Fatal("invalid uuid") + } + } +} + +func BenchmarkNew(b *testing.B) { + for i := 0; i < b.N; i++ { + New() + } +} + +func BenchmarkUUID_String(b *testing.B) { + uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if uuid == nil { + b.Fatal("invalid uuid") + } + for i := 0; i < b.N; i++ { + if uuid.String() == "" { + b.Fatal("invalid uuid") + } + } +} + +func BenchmarkUUID_URN(b *testing.B) { + uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if uuid == nil { + b.Fatal("invalid uuid") + } + for i := 0; i < b.N; i++ { + if uuid.URN() == "" { + b.Fatal("invalid uuid") + } + } +} + +func BenchmarkUUID_Array(b *testing.B) { + expect := Array{ + 0xf4, 0x7a, 0xc1, 0x0b, + 0x58, 0xcc, + 0x03, 0x72, + 0x85, 0x67, + 0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79, + } + uuid := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if uuid == nil { + b.Fatal("invalid uuid") + } + for i := 0; i < b.N; i++ { + if uuid.Array() != expect { + b.Fatal("invalid array") + } + } +} + +func BenchmarkArray_UUID(b *testing.B) { + array := Array{ + 0xf4, 0x7a, 0xc1, 0x0b, + 0x58, 0xcc, + 0x03, 0x72, + 0x85, 0x67, + 0x0e, 0x02, 0xb2, 0xc3, 0xd4, 0x79, + } + expect := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479") + if expect == nil { + b.Fatal("invalid uuid") + } + for i := 0; i < b.N; i++ { + if !bytes.Equal(array.UUID(), expect) { + b.Fatal("invalid uuid") + } + } +} diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/version1.go b/Godeps/_workspace/src/github.com/pborman/uuid/version1.go new file mode 100644 index 000000000..0127eacfa --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/version1.go @@ -0,0 +1,41 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" +) + +// NewUUID returns a Version 1 UUID based on the current NodeID and clock +// sequence, and the current time. If the NodeID has not been set by SetNodeID +// or SetNodeInterface then it will be set automatically. If the NodeID cannot +// be set NewUUID returns nil. If clock sequence has not been set by +// SetClockSequence then it will be set automatically. If GetTime fails to +// return the current NewUUID returns nil. +func NewUUID() UUID { + if nodeID == nil { + SetNodeInterface("") + } + + now, seq, err := GetTime() + if err != nil { + return nil + } + + uuid := make([]byte, 16) + + time_low := uint32(now & 0xffffffff) + time_mid := uint16((now >> 32) & 0xffff) + time_hi := uint16((now >> 48) & 0x0fff) + time_hi |= 0x1000 // Version 1 + + binary.BigEndian.PutUint32(uuid[0:], time_low) + binary.BigEndian.PutUint16(uuid[4:], time_mid) + binary.BigEndian.PutUint16(uuid[6:], time_hi) + binary.BigEndian.PutUint16(uuid[8:], seq) + copy(uuid[10:], nodeID) + + return uuid +} diff --git a/Godeps/_workspace/src/github.com/pborman/uuid/version4.go b/Godeps/_workspace/src/github.com/pborman/uuid/version4.go new file mode 100644 index 000000000..b3d4a368d --- /dev/null +++ b/Godeps/_workspace/src/github.com/pborman/uuid/version4.go @@ -0,0 +1,25 @@ +// Copyright 2011 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +// Random returns a Random (Version 4) UUID or panics. +// +// The strength of the UUIDs is based on the strength of the crypto/rand +// package. +// +// A note about uniqueness derived from from the UUID Wikipedia entry: +// +// Randomly generated UUIDs have 122 random bits. One's annual risk of being +// hit by a meteorite is estimated to be one chance in 17 billion, that +// means the probability is about 0.00000000006 (6 × 10−11), +// equivalent to the odds of creating a few tens of trillions of UUIDs in a +// year and having one duplicate. +func NewRandom() UUID { + uuid := make([]byte, 16) + randomBits([]byte(uuid)) + uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 + uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 + return uuid +} diff --git a/README.md b/README.md index 3312f87b3..8cc229a26 100644 --- a/README.md +++ b/README.md @@ -30,18 +30,14 @@ FATA[0000] Bundle path shouldn't be empty ```sh -# 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 +# ocitools runtimetest -r runc +INFO[0000] Start to test runtime lifecircle... +INFO[0001] Runtime lifecircle test succeeded. +INFO[0001] Start to test runtime state... +INFO[0006] Runtime state test succeeded. +INFO[0006] Start to test runtime main config... +INFO[0006] validating container process +validating capabilities ``` [bundle]: https://github.com/opencontainers/runtime-spec/blob/master/bundle.md diff --git a/cases.conf b/cases.conf deleted file mode 100644 index d16d7d9a1..000000000 --- a/cases.conf +++ /dev/null @@ -1,2 +0,0 @@ -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/runtimetest.go b/runtimetest.go index 587fe63c5..31807ce32 100644 --- a/runtimetest.go +++ b/runtimetest.go @@ -14,11 +14,8 @@ import ( 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.BoolFlag{Name: "debug, d", Usage: "switch of debug mode, default to 'false', with '--debug' to enable debug mode"}, } var runtimeTestCommand = cli.Command{ @@ -34,43 +31,42 @@ var runtimeTestCommand = cli.Command{ logrus.Fatalf("'%s' is currently not supported", runtime) } - if err := os.MkdirAll(bundleCacheDir, os.ModePerm); err != nil { - logrus.Fatalf("Failed to create cache dir: %s", bundleCacheDir) + if err := os.MkdirAll(TestCacheDir, os.ModePerm); err != nil { + logrus.Fatalf("Failed to create cache dir: %s", TestCacheDir) } - _, err := testState(runtime) - if err != nil { - os.RemoveAll(bundleCacheDir) - logrus.Fatalf("\n%v", err) + defer os.RemoveAll(TestCacheDir) + + logrus.Info("Start to test runtime lifecycle...") + if _, err := testLifecycle(runtime); err != nil { + os.RemoveAll(TestCacheDir) + logrus.Fatal(err) } - logrus.Info("Runtime state test succeeded.") + logrus.Info("Runtime lifecycle test succeeded.") - output, err := testMainConfigs(runtime) - if err != nil { - os.RemoveAll(bundleCacheDir) - logrus.Infof("\n%s", output) - logrus.Fatalf("\n%v", err) + logrus.Info("Start to test runtime state...") + if _, err := testState(runtime); err != nil { + os.RemoveAll(TestCacheDir) + logrus.Fatal(err) } - if output != "" { - logrus.Infof("\n%s", output) + logrus.Info("Runtime state test succeeded.") + + logrus.Info("Start to test runtime main config...") + if output, err := testMainConfigs(runtime); err != nil { + os.RemoveAll(TestCacheDir) + logrus.Info(output) + logrus.Fatal(err) + } else if output != "" { + logrus.Info(output) } logrus.Info("Runtime main config test succeeded.") }, } -func setDebugMode(debug bool) { - if !debug { - logrus.SetLevel(logrus.InfoLevel) - } else { - logrus.SetLevel(logrus.DebugLevel) - } -} - func testState(runtime string) (string, error) { testConfig := getDefaultConfig() testConfig.Process.Args = []string{"sleep", "60"} - //TODO: use UUID - testID := "12345678" + testID := GetFreeUUID(runtime) unit := TestUnit{ Name: "state", Runtime: runtime, @@ -94,10 +90,10 @@ func testState(runtime string) (string, error) { defer unit.Stop() if state.ID != testID { - return "", fmt.Errorf("Expect container ID: %s to match: %s", state.ID, testID) + return "", fmt.Errorf("Expected 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()) + return "", fmt.Errorf("Expected container bundle path: %s to match: %s", state.BundlePath, unit.GetBundlePath()) } unitDup := TestUnit{ @@ -112,28 +108,70 @@ func testState(runtime string) (string, error) { if output, err := unitDup.GetOutput(); err != nil { return output, nil } else { - return output, errors.New("Failed to popup error with duplicated container ID") + return output, errors.New("Expected to get an error when start with a duplicated container ID") + } +} + +func testLifecycle(runtime string) (string, error) { + OKArgs := []string{"true"} + OKHooks := []rspec.Hook{{Path: "/bin/true", Args: []string{"true"}}} + FailHooks := []rspec.Hook{{Path: "/bin/false", Args: []string{"false"}}} + + allOK := getDefaultConfig() + allOK.Process.Args = OKArgs + allOK.Hooks.Prestart = OKHooks + allOK.Hooks.Poststart = OKHooks + allOK.Hooks.Poststop = OKHooks + allOKUnit := TestUnit{ + Name: "allOK", + Runtime: runtime, + Config: allOK, + } + allOKUnit.Start() + defer allOKUnit.Stop() + if output, err := allOKUnit.GetOutput(); err != nil { + return output, err + } + + prestartFailed := allOK + prestartFailed.Hooks.Prestart = FailHooks + poststartFailed := allOK + poststartFailed.Hooks.Poststart = FailHooks + poststopFailed := allOK + poststopFailed.Hooks.Poststop = FailHooks + hookFailedUnit := []TestUnit{ + {Name: "prestart", Runtime: runtime, Config: prestartFailed}, + {Name: "poststart", Runtime: runtime, Config: poststartFailed}, + {Name: "poststop", Runtime: runtime, Config: poststopFailed}, + } + for _, unit := range hookFailedUnit { + unit.Start() + defer unit.Stop() + if output, err := unit.GetOutput(); err == nil { + return output, fmt.Errorf("Expected to get an error when %s fails", unit.Name) + } } + + return "", nil } func testMainConfigs(runtime string) (string, error) { testConfig := getDefaultConfig() testConfig.Process.Args = []string{"./runtimetest"} - testConfig.Hostname = "zenlin" - hostnameUnit := TestUnit{ + defaultUnit := TestUnit{ Name: "configs", Runtime: runtime, Config: testConfig, ExpectedResult: true, } - hostnameUnit.Prepare() - defer hostnameUnit.Clean() + defaultUnit.Prepare() + defer defaultUnit.Clean() // Copy runtimtest from plugins to rootfs src := "./runtimetest" - dest := path.Join(hostnameUnit.GetBundlePath(), "rootfs", "runtimetest") + dest := path.Join(defaultUnit.GetBundlePath(), "rootfs", "runtimetest") if err := copyFile(dest, src); err != nil { return "", fmt.Errorf("Failed to copy '%s' to '%s': %v\n", src, dest, err) } @@ -141,16 +179,16 @@ func testMainConfigs(runtime string) (string, error) { return "", fmt.Errorf("Failed to chmod runtimetest: %v\n", err) } - src = path.Join(hostnameUnit.GetBundlePath(), configFile) - dest = path.Join(hostnameUnit.GetBundlePath(), "rootfs", configFile) + src = path.Join(defaultUnit.GetBundlePath(), configFile) + dest = path.Join(defaultUnit.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) + defaultUnit.Start() + defer defaultUnit.Stop() + if output, err := defaultUnit.GetOutput(); err != nil { + return output, fmt.Errorf("Failed to test main config '%s' case: %v", defaultUnit.Name, err) } else { return output, nil } diff --git a/testunit.go b/testunit.go index 4f31103c1..3ecb8ad15 100644 --- a/testunit.go +++ b/testunit.go @@ -11,10 +11,11 @@ import ( "path" rspec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pborman/uuid" ) const ( - testCacheDir = "./bundles/" + TestCacheDir = "./bundles/" configFile = "config.json" ociTools = "ocitools" TEST_READY = "test ready" @@ -71,12 +72,13 @@ func (unit *TestUnit) Start() error { } if unit.ID == "" { - unit.ID = "test" + unit.ID = GetFreeUUID(unit.Runtime) } var stderr bytes.Buffer var stdout bytes.Buffer + //FIXME: it is runc preferred. cmd := exec.Command(unit.Runtime, "start", "-b", unit.bundlePath, unit.ID) cmd.Stdin = os.Stdin cmd.Stderr = &stderr @@ -93,7 +95,7 @@ func (unit *TestUnit) Start() error { // 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'.") + return nil, errors.New("Could not stop a test unit which does not have 'ID'.") } cmd := exec.Command(unit.Runtime, "stop", unit.ID) cmd.Stdin = os.Stdin @@ -106,7 +108,7 @@ func (unit *TestUnit) Stop() ([]byte, error) { // 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'.") + return rspec.State{}, errors.New("Could not get the state of a test unit which does not have 'ID'.") } cmd := exec.Command(unit.Runtime, "state", unit.ID) cmd.Stdin = os.Stdin @@ -132,7 +134,7 @@ func (unit *TestUnit) GetOutput() (string, error) { func (unit *TestUnit) prepareBundle() error { // Create bundle follder cwd, _ := os.Getwd() - unit.bundlePath = path.Join(cwd, testCacheDir, unit.Name) + unit.bundlePath = path.Join(cwd, TestCacheDir, unit.Name) if err := os.RemoveAll(unit.bundlePath); err != nil { return err } @@ -175,3 +177,15 @@ func untarRootfs(rootfs string) error { } return nil } + +// GetFreeUUID provides a free uuid +func GetFreeUUID(runtime string) string { + id := uuid.NewUUID() + + unit := TestUnit{ID: id.String()} + if _, err := unit.GetState(); err != nil { + return id.String() + } else { + return GetFreeUUID(runtime) + } +} From 3c7a49e201fe089b3a74961a0719cbb2e84cbb7a Mon Sep 17 00:00:00 2001 From: liang chenye Date: Fri, 13 May 2016 17:10:19 +0800 Subject: [PATCH 5/6] add start test Signed-off-by: liang chenye --- README.md | 8 ++-- runtimetest.go | 109 ++++++++++++++++++++++++++++++------------------- testunit.go | 39 +++++++----------- 3 files changed, 86 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 8cc229a26..91538f59c 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,10 @@ FATA[0000] Bundle path shouldn't be empty ```sh # ocitools runtimetest -r runc -INFO[0000] Start to test runtime lifecircle... -INFO[0001] Runtime lifecircle test succeeded. -INFO[0001] Start to test runtime state... -INFO[0006] Runtime state test succeeded. +INFO[0000] Start to test runtime lifecycle... +INFO[0001] Runtime lifecycle test succeeded. +INFO[0001] Start to test runtime operation... +INFO[0006] Runtime operation test succeeded. INFO[0006] Start to test runtime main config... INFO[0006] validating container process validating capabilities diff --git a/runtimetest.go b/runtimetest.go index 31807ce32..ef423d19a 100644 --- a/runtimetest.go +++ b/runtimetest.go @@ -1,7 +1,6 @@ package main import ( - "errors" "fmt" "io" "os" @@ -43,12 +42,12 @@ var runtimeTestCommand = cli.Command{ } logrus.Info("Runtime lifecycle test succeeded.") - logrus.Info("Start to test runtime state...") - if _, err := testState(runtime); err != nil { + logrus.Info("Start to test runtime operation...") + if _, err := testOperation(runtime); err != nil { os.RemoveAll(TestCacheDir) logrus.Fatal(err) } - logrus.Info("Runtime state test succeeded.") + logrus.Info("Runtime operation test succeeded.") logrus.Info("Start to test runtime main config...") if output, err := testMainConfigs(runtime); err != nil { @@ -63,23 +62,29 @@ var runtimeTestCommand = cli.Command{ }, } -func testState(runtime string) (string, error) { - testConfig := getDefaultConfig() - testConfig.Process.Args = []string{"sleep", "60"} - testID := GetFreeUUID(runtime) - unit := TestUnit{ - Name: "state", +func testOperation(runtime string) (string, error) { + testRunningConfig := getDefaultConfig() + testRunningConfig.Process.Args = []string{"sleep", "60"} + runningUnit := TestUnit{ + Name: "running", Runtime: runtime, - Config: testConfig, - ID: testID, + Config: testRunningConfig, + } + if _, err := runningUnit.GetState(); err == nil { + return "", ErrStateWithoutID } + + runningID := GetFreeUUID(runtime) + runningUnit.ID = runningID + // Start a running container (terminated in 60s) go func() { - unit.Start() + runningUnit.Prepare() + runningUnit.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 { + if state, err = runningUnit.GetState(); err == nil { break } } @@ -88,28 +93,37 @@ func testState(runtime string) (string, error) { return "", err } - defer unit.Stop() - if state.ID != testID { - return "", fmt.Errorf("Expected container ID: %s to match: %s", state.ID, testID) - } - if state.BundlePath != unit.GetBundlePath() { - return "", fmt.Errorf("Expected container bundle path: %s to match: %s", state.BundlePath, unit.GetBundlePath()) + defer runningUnit.Stop() + if err := checkState(state, runningUnit); err != nil { + return "", err } - unitDup := TestUnit{ - Name: "state-dup", - Runtime: runtime, - Config: testConfig, - ID: testID, + type testOperationUnit struct { + Unit TestUnit + prepare bool + expectedErr error } - unitDup.Start() - defer unitDup.Stop() - // Expected to get error - if output, err := unitDup.GetOutput(); err != nil { - return output, nil - } else { - return output, errors.New("Expected to get an error when start with a duplicated container ID") + + testConfig := getDefaultConfig() + testConfig.Process.Args = []string{"true"} + startOperUnits := []testOperationUnit{ + {Unit: TestUnit{Name: "start-with-dup-id", Runtime: runtime, Config: testConfig, ID: runningID}, prepare: true, expectedErr: ErrStartWithDupID}, + {Unit: TestUnit{Name: "start-without-id", Runtime: runtime, Config: testConfig}, prepare: true, expectedErr: ErrStartWithoutID}, + {Unit: TestUnit{Name: "start-without-bundle", Runtime: runtime, Config: testConfig, ID: GetFreeUUID(runtime)}, prepare: false, expectedErr: ErrStartWithoutBundle}, + } + for _, operUnit := range startOperUnits { + if operUnit.prepare { + operUnit.Unit.Prepare() + } + err := operUnit.Unit.Start() + defer operUnit.Unit.Stop() + if err != nil && operUnit.expectedErr == nil { + return "", err + } else if err == nil && operUnit.expectedErr != nil { + return "", operUnit.expectedErr + } } + return "", nil } func testLifecycle(runtime string) (string, error) { @@ -126,7 +140,9 @@ func testLifecycle(runtime string) (string, error) { Name: "allOK", Runtime: runtime, Config: allOK, + ID: GetFreeUUID(runtime), } + allOKUnit.Prepare() allOKUnit.Start() defer allOKUnit.Stop() if output, err := allOKUnit.GetOutput(); err != nil { @@ -139,12 +155,13 @@ func testLifecycle(runtime string) (string, error) { poststartFailed.Hooks.Poststart = FailHooks poststopFailed := allOK poststopFailed.Hooks.Poststop = FailHooks - hookFailedUnit := []TestUnit{ - {Name: "prestart", Runtime: runtime, Config: prestartFailed}, - {Name: "poststart", Runtime: runtime, Config: poststartFailed}, - {Name: "poststop", Runtime: runtime, Config: poststopFailed}, + hookFailedUnits := []TestUnit{ + {Name: "prestart", Runtime: runtime, Config: prestartFailed, ID: GetFreeUUID(runtime)}, + {Name: "poststart", Runtime: runtime, Config: poststartFailed, ID: GetFreeUUID(runtime)}, + {Name: "poststop", Runtime: runtime, Config: poststopFailed, ID: GetFreeUUID(runtime)}, } - for _, unit := range hookFailedUnit { + for _, unit := range hookFailedUnits { + unit.Prepare() unit.Start() defer unit.Stop() if output, err := unit.GetOutput(); err == nil { @@ -160,10 +177,10 @@ func testMainConfigs(runtime string) (string, error) { testConfig.Process.Args = []string{"./runtimetest"} defaultUnit := TestUnit{ - Name: "configs", - Runtime: runtime, - Config: testConfig, - ExpectedResult: true, + Name: "configs", + Runtime: runtime, + Config: testConfig, + ID: GetFreeUUID(runtime), } defaultUnit.Prepare() @@ -222,3 +239,13 @@ func getDefaultConfig() *rspec.Spec { return config } + +func checkState(state rspec.State, unit TestUnit) error { + if state.ID != unit.ID { + return fmt.Errorf("Expected container ID: %s to match: %s", state.ID, unit.ID) + } + if state.BundlePath != unit.GetBundlePath() { + return fmt.Errorf("Expected container bundle path: %s to match: %s", state.BundlePath, unit.GetBundlePath()) + } + return nil +} diff --git a/testunit.go b/testunit.go index 3ecb8ad15..d126cc0d3 100644 --- a/testunit.go +++ b/testunit.go @@ -18,29 +18,29 @@ const ( TestCacheDir = "./bundles/" configFile = "config.json" ociTools = "ocitools" - TEST_READY = "test ready" +) + +var ( + ErrStartWithDupID = errors.New("Expected to get an error when start with a duplicated container ID") + ErrStartWithoutID = errors.New("Expected to get an error when start without provide a container ID") + ErrStartWithoutBundle = errors.New("Expected to get an error when start without provide a bundle") + ErrStateWithoutID = errors.New("Expected to get an error when state without provide a container ID") ) // TestUnit for storage testcase type TestUnit struct { - ID string - Name string - Runtime string - Config *rspec.Spec - ExpectedOutput string - ExpectedResult bool + ID string + Name string + Runtime string + Config *rspec.Spec 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'.") } @@ -49,30 +49,19 @@ func (unit *TestUnit) Prepare() error { 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 + return os.RemoveAll(unit.bundlePath) } // 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 = GetFreeUUID(unit.Runtime) + if unit.Runtime == "" { + return errors.New("'Runtime' must be set before start") } var stderr bytes.Buffer From 2fced63f4fce325d77b618bffca62bd262cfe980 Mon Sep 17 00:00:00 2001 From: liang chenye Date: Wed, 18 May 2016 17:31:45 +0800 Subject: [PATCH 6/6] test if process runs successfully Signed-off-by: liang chenye --- runtimetest.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/runtimetest.go b/runtimetest.go index ef423d19a..f10d56348 100644 --- a/runtimetest.go +++ b/runtimetest.go @@ -6,6 +6,7 @@ import ( "os" "path" "runtime" + "strings" "time" "github.com/Sirupsen/logrus" @@ -127,12 +128,12 @@ func testOperation(runtime string) (string, error) { } func testLifecycle(runtime string) (string, error) { - OKArgs := []string{"true"} OKHooks := []rspec.Hook{{Path: "/bin/true", Args: []string{"true"}}} FailHooks := []rspec.Hook{{Path: "/bin/false", Args: []string{"false"}}} + processOutput := "hello, ocitools" allOK := getDefaultConfig() - allOK.Process.Args = OKArgs + allOK.Process.Args = []string{"echo", processOutput} allOK.Hooks.Prestart = OKHooks allOK.Hooks.Poststart = OKHooks allOK.Hooks.Poststop = OKHooks @@ -147,6 +148,8 @@ func testLifecycle(runtime string) (string, error) { defer allOKUnit.Stop() if output, err := allOKUnit.GetOutput(); err != nil { return output, err + } else if processOutput != strings.TrimSpace(output) { + return "", fmt.Errorf("Failed to run 'Process' successfully") } prestartFailed := allOK