Skip to content

Commit 1f9c0f1

Browse files
author
Zhou Hao
authored
Merge pull request #620 from kinvolk/dongsu/test-ns-without-path
validation: add tests for NSNewNSWithoutPath & NSInheritWithoutType
2 parents a02e0cb + e830fa3 commit 1f9c0f1

File tree

4 files changed

+301
-0
lines changed

4 files changed

+301
-0
lines changed

generate/generate.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,14 @@ func (g *Generator) RemoveAnnotation(key string) {
370370
delete(g.Config.Annotations, key)
371371
}
372372

373+
// RemoveHostname removes g.Config.Hostname, setting it to an empty string.
374+
func (g *Generator) RemoveHostname() {
375+
if g.Config == nil {
376+
return
377+
}
378+
g.Config.Hostname = ""
379+
}
380+
373381
// SetProcessConsoleSize sets g.Config.Process.ConsoleSize.
374382
func (g *Generator) SetProcessConsoleSize(width, height uint) {
375383
g.initConfigProcessConsoleSize()

validation/linux_ns_itype.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"runtime"
8+
9+
"github.com/mndrix/tap-go"
10+
rspec "github.com/opencontainers/runtime-spec/specs-go"
11+
"github.com/opencontainers/runtime-tools/specerror"
12+
"github.com/opencontainers/runtime-tools/validation/util"
13+
)
14+
15+
func printDiag(t *tap.T, diagActual, diagExpected, diagNsType string, errNs error) {
16+
specErr := specerror.NewError(specerror.NSInheritWithoutType,
17+
errNs, rspec.Version)
18+
diagnostic := map[string]string{
19+
"actual": diagActual,
20+
"expected": diagExpected,
21+
"namespace type": diagNsType,
22+
"level": specErr.(*specerror.Error).Err.Level.String(),
23+
"reference": specErr.(*specerror.Error).Err.Reference,
24+
}
25+
t.YAML(diagnostic)
26+
}
27+
28+
func testNamespaceInheritType(t *tap.T) error {
29+
var errNs error
30+
diagActual := ""
31+
diagExpected := ""
32+
diagNsType := ""
33+
34+
// To be able to print out diagnostics for all kinds of error cases
35+
// at the end of the tests, we make use of defer function. To do that,
36+
// each error handling routine should set diagActual, diagExpected,
37+
// diagNsType, and errNs, before returning an error.
38+
defer func() {
39+
if errNs != nil {
40+
printDiag(t, diagActual, diagExpected, diagNsType, errNs)
41+
}
42+
}()
43+
44+
g, err := util.GetDefaultGenerator()
45+
if err != nil {
46+
errNs = fmt.Errorf("cannot get the default generator: %v", err)
47+
diagActual = fmt.Sprintf("err == %v", errNs)
48+
diagExpected = "err == nil"
49+
// NOTE: we don't have a namespace type
50+
return errNs
51+
}
52+
53+
// Obtain a map for host (runtime) namespace, and remove every namespace
54+
// from the generated config, to be able to see if each container namespace
55+
// becomes inherited from its corresponding host namespace.
56+
hostNsPath := fmt.Sprintf("/proc/%d/ns", os.Getpid())
57+
hostNsInodes := map[string]string{}
58+
for _, nsName := range util.ProcNamespaces {
59+
nsPathAbs := filepath.Join(hostNsPath, nsName)
60+
nsInode, err := os.Readlink(nsPathAbs)
61+
if err != nil {
62+
errNs = fmt.Errorf("cannot resolve symlink %q: %v", nsPathAbs, err)
63+
diagActual = fmt.Sprintf("err == %v", errNs)
64+
diagExpected = "err == nil"
65+
diagNsType = nsName
66+
return errNs
67+
}
68+
hostNsInodes[nsName] = nsInode
69+
70+
if err := g.RemoveLinuxNamespace(util.GetRuntimeToolsNamespace(nsName)); err != nil {
71+
errNs = fmt.Errorf("cannot remove namespace %s: %v", nsName, err)
72+
diagActual = fmt.Sprintf("err == %v", errNs)
73+
diagExpected = "err == nil"
74+
diagNsType = nsName
75+
return errNs
76+
}
77+
}
78+
79+
// We need to remove hostname to avoid test failures when not creating UTS namespace
80+
g.RemoveHostname()
81+
82+
err = util.RuntimeOutsideValidate(g, func(config *rspec.Spec, state *rspec.State) error {
83+
containerNsPath := fmt.Sprintf("/proc/%d/ns", state.Pid)
84+
85+
for _, nsName := range util.ProcNamespaces {
86+
nsPathAbs := filepath.Join(containerNsPath, nsName)
87+
nsInode, err := os.Readlink(nsPathAbs)
88+
if err != nil {
89+
errNs = fmt.Errorf("cannot resolve symlink %q: %v", nsPathAbs, err)
90+
diagActual = fmt.Sprintf("err == %v", errNs)
91+
diagExpected = "err == nil"
92+
diagNsType = nsName
93+
return errNs
94+
}
95+
96+
t.Ok(hostNsInodes[nsName] == nsInode, fmt.Sprintf("inherit namespace %s without type", nsName))
97+
if hostNsInodes[nsName] != nsInode {
98+
// NOTE: for such inode match cases, we should print out diagnostics
99+
// for each case, not only at the end of tests. So we should simply
100+
// call once printDiag(), then continue testing next namespaces.
101+
// Thus we don't need to set diagActual, diagExpected, diagNsType, etc.
102+
printDiag(t, nsInode, hostNsInodes[nsName], nsName,
103+
fmt.Errorf("namespace %s (inode %s) does not inherit runtime namespace %s", nsName, nsInode, hostNsInodes[nsName]))
104+
continue
105+
}
106+
}
107+
108+
return nil
109+
})
110+
if err != nil {
111+
errNs = fmt.Errorf("cannot run validation tests: %v", err)
112+
}
113+
114+
return errNs
115+
}
116+
117+
func main() {
118+
t := tap.New()
119+
t.Header(0)
120+
121+
if "linux" != runtime.GOOS {
122+
t.Skip(1, fmt.Sprintf("linux-specific namespace test"))
123+
}
124+
125+
err := testNamespaceInheritType(t)
126+
if err != nil {
127+
util.Fatal(err)
128+
}
129+
130+
t.AutoPlan()
131+
}

validation/linux_ns_nopath.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"runtime"
8+
9+
"github.com/mndrix/tap-go"
10+
rspec "github.com/opencontainers/runtime-spec/specs-go"
11+
"github.com/opencontainers/runtime-tools/specerror"
12+
"github.com/opencontainers/runtime-tools/validation/util"
13+
)
14+
15+
func printDiag(t *tap.T, diagActual, diagExpected, diagNsType string, errNs error) {
16+
specErr := specerror.NewError(specerror.NSNewNSWithoutPath,
17+
errNs, rspec.Version)
18+
diagnostic := map[string]string{
19+
"actual": diagActual,
20+
"expected": diagExpected,
21+
"namespace type": diagNsType,
22+
"level": specErr.(*specerror.Error).Err.Level.String(),
23+
"reference": specErr.(*specerror.Error).Err.Reference,
24+
}
25+
t.YAML(diagnostic)
26+
}
27+
28+
func testNamespaceNoPath(t *tap.T) error {
29+
var errNs error
30+
diagActual := ""
31+
diagExpected := ""
32+
diagNsType := ""
33+
34+
// To be able to print out diagnostics for all kinds of error cases
35+
// at the end of the tests, we make use of defer function. To do that,
36+
// each error handling routine should set diagActual, diagExpected,
37+
// diagNsType, and errNs, before returning an error.
38+
defer func() {
39+
if errNs != nil {
40+
printDiag(t, diagActual, diagExpected, diagNsType, errNs)
41+
}
42+
}()
43+
44+
hostNsPath := fmt.Sprintf("/proc/%d/ns", os.Getpid())
45+
hostNsInodes := map[string]string{}
46+
47+
for _, nsName := range util.ProcNamespaces {
48+
nsPathAbs := filepath.Join(hostNsPath, nsName)
49+
nsInode, err := os.Readlink(nsPathAbs)
50+
if err != nil {
51+
errNs = fmt.Errorf("cannot resolve symlink %q: %v", nsPathAbs, err)
52+
diagActual = fmt.Sprintf("err == %v", errNs)
53+
diagExpected = "err == nil"
54+
diagNsType = nsName
55+
return errNs
56+
}
57+
hostNsInodes[nsName] = nsInode
58+
}
59+
60+
g, err := util.GetDefaultGenerator()
61+
if err != nil {
62+
errNs = fmt.Errorf("cannot get the default generator: %v", err)
63+
diagActual = fmt.Sprintf("err == %v", errNs)
64+
diagExpected = "err == nil"
65+
// NOTE: we don't have a namespace type
66+
return errNs
67+
}
68+
69+
// As the namespaces, cgroups and user, are not set by GetDefaultGenerator(),
70+
// others are set by default. We just set them explicitly to avoid confusion.
71+
g.AddOrReplaceLinuxNamespace("cgroup", "")
72+
g.AddOrReplaceLinuxNamespace("ipc", "")
73+
g.AddOrReplaceLinuxNamespace("mount", "")
74+
g.AddOrReplaceLinuxNamespace("network", "")
75+
g.AddOrReplaceLinuxNamespace("pid", "")
76+
g.AddOrReplaceLinuxNamespace("user", "")
77+
g.AddOrReplaceLinuxNamespace("uts", "")
78+
79+
// For user namespaces, we need to set uid/gid maps to create a container
80+
g.AddLinuxUIDMapping(uint32(1000), uint32(0), uint32(1000))
81+
g.AddLinuxGIDMapping(uint32(1000), uint32(0), uint32(1000))
82+
83+
err = util.RuntimeOutsideValidate(g, func(config *rspec.Spec, state *rspec.State) error {
84+
containerNsPath := fmt.Sprintf("/proc/%d/ns", state.Pid)
85+
86+
for _, nsName := range util.ProcNamespaces {
87+
nsPathAbs := filepath.Join(containerNsPath, nsName)
88+
nsInode, err := os.Readlink(nsPathAbs)
89+
if err != nil {
90+
errNs = fmt.Errorf("cannot resolve symlink %q: %v", nsPathAbs, err)
91+
diagActual = fmt.Sprintf("err == %v", errNs)
92+
diagExpected = "err == nil"
93+
diagNsType = nsName
94+
return errNs
95+
}
96+
97+
t.Ok(hostNsInodes[nsName] != nsInode, fmt.Sprintf("create namespace %s without path", nsName))
98+
if hostNsInodes[nsName] == nsInode {
99+
// NOTE: for such inode match cases, we should print out diagnostics
100+
// for each case, not only at the end of tests. So we should simply
101+
// call once printDiag(), then continue testing next namespaces.
102+
// Thus we don't need to set diagActual, diagExpected, diagNsType, etc.
103+
printDiag(t, nsInode, fmt.Sprintf("!= %s", hostNsInodes[nsName]), nsName,
104+
fmt.Errorf("both namespaces for %s have the same inode %s", nsName, nsInode))
105+
continue
106+
}
107+
}
108+
109+
return nil
110+
})
111+
if err != nil {
112+
errNs = fmt.Errorf("cannot run validation tests: %v", err)
113+
}
114+
115+
return errNs
116+
}
117+
118+
func main() {
119+
t := tap.New()
120+
t.Header(0)
121+
122+
if "linux" != runtime.GOOS {
123+
t.Skip(1, fmt.Sprintf("linux-specific namespace test"))
124+
}
125+
126+
err := testNamespaceNoPath(t)
127+
if err != nil {
128+
util.Fatal(err)
129+
}
130+
131+
t.AutoPlan()
132+
}

validation/util/linux_namespace.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package util
2+
3+
// ProcNamespaces defines a list of namespaces to be found under /proc/*/ns/.
4+
// NOTE: it is not the same as generate.Namespaces, because of naming
5+
// mismatches like "mnt" vs "mount" or "net" vs "network".
6+
var ProcNamespaces = []string{
7+
"cgroup",
8+
"ipc",
9+
"mnt",
10+
"net",
11+
"pid",
12+
"user",
13+
"uts",
14+
}
15+
16+
// GetRuntimeToolsNamespace converts a namespace type string for /proc into
17+
// a string for runtime-tools. It deals with exceptional cases of "net" and
18+
// "mnt", because those strings cannot be recognized by mapStrToNamespace(),
19+
// which actually expects "network" and "mount" respectively.
20+
func GetRuntimeToolsNamespace(ns string) string {
21+
switch ns {
22+
case "net":
23+
return "network"
24+
case "mnt":
25+
return "mount"
26+
}
27+
28+
// In other cases, return just the original string
29+
return ns
30+
}

0 commit comments

Comments
 (0)