Skip to content

Commit

Permalink
Merge pull request #620 from kinvolk/dongsu/test-ns-without-path
Browse files Browse the repository at this point in the history
validation: add tests for NSNewNSWithoutPath & NSInheritWithoutType
  • Loading branch information
Zhou Hao authored May 3, 2018
2 parents a02e0cb + e830fa3 commit 1f9c0f1
Show file tree
Hide file tree
Showing 4 changed files with 301 additions and 0 deletions.
8 changes: 8 additions & 0 deletions generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,14 @@ func (g *Generator) RemoveAnnotation(key string) {
delete(g.Config.Annotations, key)
}

// RemoveHostname removes g.Config.Hostname, setting it to an empty string.
func (g *Generator) RemoveHostname() {
if g.Config == nil {
return
}
g.Config.Hostname = ""
}

// SetProcessConsoleSize sets g.Config.Process.ConsoleSize.
func (g *Generator) SetProcessConsoleSize(width, height uint) {
g.initConfigProcessConsoleSize()
Expand Down
131 changes: 131 additions & 0 deletions validation/linux_ns_itype.go
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()
}
132 changes: 132 additions & 0 deletions validation/linux_ns_nopath.go
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()
}
30 changes: 30 additions & 0 deletions validation/util/linux_namespace.go
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
}

0 comments on commit 1f9c0f1

Please sign in to comment.