-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #620 from kinvolk/dongsu/test-ns-without-path
validation: add tests for NSNewNSWithoutPath & NSInheritWithoutType
- Loading branch information
Showing
4 changed files
with
301 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"runtime" | ||
|
||
"github.com/mndrix/tap-go" | ||
rspec "github.com/opencontainers/runtime-spec/specs-go" | ||
"github.com/opencontainers/runtime-tools/specerror" | ||
"github.com/opencontainers/runtime-tools/validation/util" | ||
) | ||
|
||
func printDiag(t *tap.T, diagActual, diagExpected, diagNsType string, errNs error) { | ||
specErr := specerror.NewError(specerror.NSInheritWithoutType, | ||
errNs, rspec.Version) | ||
diagnostic := map[string]string{ | ||
"actual": diagActual, | ||
"expected": diagExpected, | ||
"namespace type": diagNsType, | ||
"level": specErr.(*specerror.Error).Err.Level.String(), | ||
"reference": specErr.(*specerror.Error).Err.Reference, | ||
} | ||
t.YAML(diagnostic) | ||
} | ||
|
||
func testNamespaceInheritType(t *tap.T) error { | ||
var errNs error | ||
diagActual := "" | ||
diagExpected := "" | ||
diagNsType := "" | ||
|
||
// To be able to print out diagnostics for all kinds of error cases | ||
// at the end of the tests, we make use of defer function. To do that, | ||
// each error handling routine should set diagActual, diagExpected, | ||
// diagNsType, and errNs, before returning an error. | ||
defer func() { | ||
if errNs != nil { | ||
printDiag(t, diagActual, diagExpected, diagNsType, errNs) | ||
} | ||
}() | ||
|
||
g, err := util.GetDefaultGenerator() | ||
if err != nil { | ||
errNs = fmt.Errorf("cannot get the default generator: %v", err) | ||
diagActual = fmt.Sprintf("err == %v", errNs) | ||
diagExpected = "err == nil" | ||
// NOTE: we don't have a namespace type | ||
return errNs | ||
} | ||
|
||
// Obtain a map for host (runtime) namespace, and remove every namespace | ||
// from the generated config, to be able to see if each container namespace | ||
// becomes inherited from its corresponding host namespace. | ||
hostNsPath := fmt.Sprintf("/proc/%d/ns", os.Getpid()) | ||
hostNsInodes := map[string]string{} | ||
for _, nsName := range util.ProcNamespaces { | ||
nsPathAbs := filepath.Join(hostNsPath, nsName) | ||
nsInode, err := os.Readlink(nsPathAbs) | ||
if err != nil { | ||
errNs = fmt.Errorf("cannot resolve symlink %q: %v", nsPathAbs, err) | ||
diagActual = fmt.Sprintf("err == %v", errNs) | ||
diagExpected = "err == nil" | ||
diagNsType = nsName | ||
return errNs | ||
} | ||
hostNsInodes[nsName] = nsInode | ||
|
||
if err := g.RemoveLinuxNamespace(util.GetRuntimeToolsNamespace(nsName)); err != nil { | ||
errNs = fmt.Errorf("cannot remove namespace %s: %v", nsName, err) | ||
diagActual = fmt.Sprintf("err == %v", errNs) | ||
diagExpected = "err == nil" | ||
diagNsType = nsName | ||
return errNs | ||
} | ||
} | ||
|
||
// We need to remove hostname to avoid test failures when not creating UTS namespace | ||
g.RemoveHostname() | ||
|
||
err = util.RuntimeOutsideValidate(g, func(config *rspec.Spec, state *rspec.State) error { | ||
containerNsPath := fmt.Sprintf("/proc/%d/ns", state.Pid) | ||
|
||
for _, nsName := range util.ProcNamespaces { | ||
nsPathAbs := filepath.Join(containerNsPath, nsName) | ||
nsInode, err := os.Readlink(nsPathAbs) | ||
if err != nil { | ||
errNs = fmt.Errorf("cannot resolve symlink %q: %v", nsPathAbs, err) | ||
diagActual = fmt.Sprintf("err == %v", errNs) | ||
diagExpected = "err == nil" | ||
diagNsType = nsName | ||
return errNs | ||
} | ||
|
||
t.Ok(hostNsInodes[nsName] == nsInode, fmt.Sprintf("inherit namespace %s without type", nsName)) | ||
if hostNsInodes[nsName] != nsInode { | ||
// NOTE: for such inode match cases, we should print out diagnostics | ||
// for each case, not only at the end of tests. So we should simply | ||
// call once printDiag(), then continue testing next namespaces. | ||
// Thus we don't need to set diagActual, diagExpected, diagNsType, etc. | ||
printDiag(t, nsInode, hostNsInodes[nsName], nsName, | ||
fmt.Errorf("namespace %s (inode %s) does not inherit runtime namespace %s", nsName, nsInode, hostNsInodes[nsName])) | ||
continue | ||
} | ||
} | ||
|
||
return nil | ||
}) | ||
if err != nil { | ||
errNs = fmt.Errorf("cannot run validation tests: %v", err) | ||
} | ||
|
||
return errNs | ||
} | ||
|
||
func main() { | ||
t := tap.New() | ||
t.Header(0) | ||
|
||
if "linux" != runtime.GOOS { | ||
t.Skip(1, fmt.Sprintf("linux-specific namespace test")) | ||
} | ||
|
||
err := testNamespaceInheritType(t) | ||
if err != nil { | ||
util.Fatal(err) | ||
} | ||
|
||
t.AutoPlan() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"runtime" | ||
|
||
"github.com/mndrix/tap-go" | ||
rspec "github.com/opencontainers/runtime-spec/specs-go" | ||
"github.com/opencontainers/runtime-tools/specerror" | ||
"github.com/opencontainers/runtime-tools/validation/util" | ||
) | ||
|
||
func printDiag(t *tap.T, diagActual, diagExpected, diagNsType string, errNs error) { | ||
specErr := specerror.NewError(specerror.NSNewNSWithoutPath, | ||
errNs, rspec.Version) | ||
diagnostic := map[string]string{ | ||
"actual": diagActual, | ||
"expected": diagExpected, | ||
"namespace type": diagNsType, | ||
"level": specErr.(*specerror.Error).Err.Level.String(), | ||
"reference": specErr.(*specerror.Error).Err.Reference, | ||
} | ||
t.YAML(diagnostic) | ||
} | ||
|
||
func testNamespaceNoPath(t *tap.T) error { | ||
var errNs error | ||
diagActual := "" | ||
diagExpected := "" | ||
diagNsType := "" | ||
|
||
// To be able to print out diagnostics for all kinds of error cases | ||
// at the end of the tests, we make use of defer function. To do that, | ||
// each error handling routine should set diagActual, diagExpected, | ||
// diagNsType, and errNs, before returning an error. | ||
defer func() { | ||
if errNs != nil { | ||
printDiag(t, diagActual, diagExpected, diagNsType, errNs) | ||
} | ||
}() | ||
|
||
hostNsPath := fmt.Sprintf("/proc/%d/ns", os.Getpid()) | ||
hostNsInodes := map[string]string{} | ||
|
||
for _, nsName := range util.ProcNamespaces { | ||
nsPathAbs := filepath.Join(hostNsPath, nsName) | ||
nsInode, err := os.Readlink(nsPathAbs) | ||
if err != nil { | ||
errNs = fmt.Errorf("cannot resolve symlink %q: %v", nsPathAbs, err) | ||
diagActual = fmt.Sprintf("err == %v", errNs) | ||
diagExpected = "err == nil" | ||
diagNsType = nsName | ||
return errNs | ||
} | ||
hostNsInodes[nsName] = nsInode | ||
} | ||
|
||
g, err := util.GetDefaultGenerator() | ||
if err != nil { | ||
errNs = fmt.Errorf("cannot get the default generator: %v", err) | ||
diagActual = fmt.Sprintf("err == %v", errNs) | ||
diagExpected = "err == nil" | ||
// NOTE: we don't have a namespace type | ||
return errNs | ||
} | ||
|
||
// As the namespaces, cgroups and user, are not set by GetDefaultGenerator(), | ||
// others are set by default. We just set them explicitly to avoid confusion. | ||
g.AddOrReplaceLinuxNamespace("cgroup", "") | ||
g.AddOrReplaceLinuxNamespace("ipc", "") | ||
g.AddOrReplaceLinuxNamespace("mount", "") | ||
g.AddOrReplaceLinuxNamespace("network", "") | ||
g.AddOrReplaceLinuxNamespace("pid", "") | ||
g.AddOrReplaceLinuxNamespace("user", "") | ||
g.AddOrReplaceLinuxNamespace("uts", "") | ||
|
||
// For user namespaces, we need to set uid/gid maps to create a container | ||
g.AddLinuxUIDMapping(uint32(1000), uint32(0), uint32(1000)) | ||
g.AddLinuxGIDMapping(uint32(1000), uint32(0), uint32(1000)) | ||
|
||
err = util.RuntimeOutsideValidate(g, func(config *rspec.Spec, state *rspec.State) error { | ||
containerNsPath := fmt.Sprintf("/proc/%d/ns", state.Pid) | ||
|
||
for _, nsName := range util.ProcNamespaces { | ||
nsPathAbs := filepath.Join(containerNsPath, nsName) | ||
nsInode, err := os.Readlink(nsPathAbs) | ||
if err != nil { | ||
errNs = fmt.Errorf("cannot resolve symlink %q: %v", nsPathAbs, err) | ||
diagActual = fmt.Sprintf("err == %v", errNs) | ||
diagExpected = "err == nil" | ||
diagNsType = nsName | ||
return errNs | ||
} | ||
|
||
t.Ok(hostNsInodes[nsName] != nsInode, fmt.Sprintf("create namespace %s without path", nsName)) | ||
if hostNsInodes[nsName] == nsInode { | ||
// NOTE: for such inode match cases, we should print out diagnostics | ||
// for each case, not only at the end of tests. So we should simply | ||
// call once printDiag(), then continue testing next namespaces. | ||
// Thus we don't need to set diagActual, diagExpected, diagNsType, etc. | ||
printDiag(t, nsInode, fmt.Sprintf("!= %s", hostNsInodes[nsName]), nsName, | ||
fmt.Errorf("both namespaces for %s have the same inode %s", nsName, nsInode)) | ||
continue | ||
} | ||
} | ||
|
||
return nil | ||
}) | ||
if err != nil { | ||
errNs = fmt.Errorf("cannot run validation tests: %v", err) | ||
} | ||
|
||
return errNs | ||
} | ||
|
||
func main() { | ||
t := tap.New() | ||
t.Header(0) | ||
|
||
if "linux" != runtime.GOOS { | ||
t.Skip(1, fmt.Sprintf("linux-specific namespace test")) | ||
} | ||
|
||
err := testNamespaceNoPath(t) | ||
if err != nil { | ||
util.Fatal(err) | ||
} | ||
|
||
t.AutoPlan() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package util | ||
|
||
// ProcNamespaces defines a list of namespaces to be found under /proc/*/ns/. | ||
// NOTE: it is not the same as generate.Namespaces, because of naming | ||
// mismatches like "mnt" vs "mount" or "net" vs "network". | ||
var ProcNamespaces = []string{ | ||
"cgroup", | ||
"ipc", | ||
"mnt", | ||
"net", | ||
"pid", | ||
"user", | ||
"uts", | ||
} | ||
|
||
// GetRuntimeToolsNamespace converts a namespace type string for /proc into | ||
// a string for runtime-tools. It deals with exceptional cases of "net" and | ||
// "mnt", because those strings cannot be recognized by mapStrToNamespace(), | ||
// which actually expects "network" and "mount" respectively. | ||
func GetRuntimeToolsNamespace(ns string) string { | ||
switch ns { | ||
case "net": | ||
return "network" | ||
case "mnt": | ||
return "mount" | ||
} | ||
|
||
// In other cases, return just the original string | ||
return ns | ||
} |