Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

validation: add tests for NSNewNSWithoutPath & NSInheritWithoutType #620

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}