From e1bc41a42a239f2ac77e9940f5e688ba8b6c69c5 Mon Sep 17 00:00:00 2001 From: Piotr Skamruk Date: Mon, 7 Mar 2016 16:40:27 +0100 Subject: [PATCH 1/7] pkg/utils: add functions to work with sysctl --- utils/sysctl/sysctl_linux.go | 58 ++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 utils/sysctl/sysctl_linux.go diff --git a/utils/sysctl/sysctl_linux.go b/utils/sysctl/sysctl_linux.go new file mode 100644 index 00000000..c0fba382 --- /dev/null +++ b/utils/sysctl/sysctl_linux.go @@ -0,0 +1,58 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// build +linux + +package sysctl + +import ( + "fmt" + "io/ioutil" + "path/filepath" + "strings" +) + +// Sysctl provides a method to set/get values from /proc/sys - in linux systems +// new interface to set/get values of variables formerly handled by sysctl syscall +// If optional `params` have only one string value - this function will +// set this value into coresponding sysctl variable +func Sysctl(name string, params ...string) (string, error) { + if len(params) > 1 { + return "", fmt.Errorf("unexcepted additional parameters") + } else if len(params) == 1 { + return setSysctl(name, params[0]) + } + return getSysctl(name) +} + +func getSysctl(name string) (string, error) { + fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1)) + fullName = filepath.Clean(fullName) + data, err := ioutil.ReadFile(fullName) + if err != nil { + return "", err + } + + return string(data[:len(data)-1]), nil +} + +func setSysctl(name, value string) (string, error) { + fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1)) + fullName = filepath.Clean(fullName) + if err := ioutil.WriteFile(fullName, []byte(value), 0644); err != nil { + return "", err + } + + return getSysctl(name) +} From 4a79ac4cda42489cff0dde37156f15bf34c3093c Mon Sep 17 00:00:00 2001 From: Gabe Rosenhouse Date: Sun, 17 Apr 2016 18:28:10 -0700 Subject: [PATCH 2/7] Extract testhelpers from loopback test suite --- testhelpers/testhelpers.go | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 testhelpers/testhelpers.go diff --git a/testhelpers/testhelpers.go b/testhelpers/testhelpers.go new file mode 100644 index 00000000..f1ccb24e --- /dev/null +++ b/testhelpers/testhelpers.go @@ -0,0 +1,69 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package testhelpers + +import ( + "fmt" + "os" + "runtime" + + "golang.org/x/sys/unix" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func MakeNetworkNS(containerID string) string { + namespace := "/var/run/netns/" + containerID + pid := unix.Getpid() + tid := unix.Gettid() + + err := os.MkdirAll("/var/run/netns", 0600) + Expect(err).NotTo(HaveOccurred()) + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + go (func() { + defer GinkgoRecover() + + err = unix.Unshare(unix.CLONE_NEWNET) + Expect(err).NotTo(HaveOccurred()) + + fd, err := os.Create(namespace) + Expect(err).NotTo(HaveOccurred()) + defer fd.Close() + + err = unix.Mount("/proc/self/ns/net", namespace, "none", unix.MS_BIND, "") + Expect(err).NotTo(HaveOccurred()) + })() + + Eventually(namespace).Should(BeAnExistingFile()) + + fd, err := unix.Open(fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid), unix.O_RDONLY, 0) + Expect(err).NotTo(HaveOccurred()) + + defer unix.Close(fd) + + _, _, e1 := unix.Syscall(unix.SYS_SETNS, uintptr(fd), uintptr(unix.CLONE_NEWNET), 0) + Expect(e1).To(BeZero()) + + return namespace +} + +func RemoveNetworkNS(networkNS string) error { + err := unix.Unmount(networkNS, unix.MNT_DETACH) + + err = os.RemoveAll(networkNS) + return err +} From 5f757b6af7677a06ef976d8d37d7f8d316cf00f3 Mon Sep 17 00:00:00 2001 From: Gabe Rosenhouse Date: Sun, 17 Apr 2016 18:35:49 -0700 Subject: [PATCH 3/7] Extract inode inspection functions into testhelpers --- ns/ns_test.go | 38 +++++++++++--------------------------- testhelpers/testhelpers.go | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/ns/ns_test.go b/ns/ns_test.go index d9f182cf..42fc6322 100644 --- a/ns/ns_test.go +++ b/ns/ns_test.go @@ -22,28 +22,12 @@ import ( "os/exec" "path/filepath" - "golang.org/x/sys/unix" - "github.com/appc/cni/pkg/ns" + "github.com/appc/cni/pkg/testhelpers" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" ) -func getInode(path string) (uint64, error) { - file, err := os.Open(path) - if err != nil { - return 0, err - } - defer file.Close() - return getInodeF(file) -} - -func getInodeF(file *os.File) (uint64, error) { - stat := &unix.Stat_t{} - err := unix.Fstat(int(file.Fd()), stat) - return stat.Ino, err -} - const CurrentNetNS = "/proc/self/ns/net" var _ = Describe("Linux namespace operations", func() { @@ -81,13 +65,13 @@ var _ = Describe("Linux namespace operations", func() { }) It("executes the callback within the target network namespace", func() { - expectedInode, err := getInode(targetNetNSPath) + expectedInode, err := testhelpers.GetInode(targetNetNSPath) Expect(err).NotTo(HaveOccurred()) var actualInode uint64 var innerErr error err = ns.WithNetNS(targetNetNS, false, func(*os.File) error { - actualInode, innerErr = getInode(CurrentNetNS) + actualInode, innerErr = testhelpers.GetInode(CurrentNetNS) return nil }) Expect(err).NotTo(HaveOccurred()) @@ -97,13 +81,13 @@ var _ = Describe("Linux namespace operations", func() { }) It("provides the original namespace as the argument to the callback", func() { - hostNSInode, err := getInode(CurrentNetNS) + hostNSInode, err := testhelpers.GetInode(CurrentNetNS) Expect(err).NotTo(HaveOccurred()) var inputNSInode uint64 var innerErr error err = ns.WithNetNS(targetNetNS, false, func(inputNS *os.File) error { - inputNSInode, err = getInodeF(inputNS) + inputNSInode, err = testhelpers.GetInodeF(inputNS) return nil }) Expect(err).NotTo(HaveOccurred()) @@ -113,7 +97,7 @@ var _ = Describe("Linux namespace operations", func() { }) It("restores the calling thread to the original network namespace", func() { - preTestInode, err := getInode(CurrentNetNS) + preTestInode, err := testhelpers.GetInode(CurrentNetNS) Expect(err).NotTo(HaveOccurred()) err = ns.WithNetNS(targetNetNS, false, func(*os.File) error { @@ -121,7 +105,7 @@ var _ = Describe("Linux namespace operations", func() { }) Expect(err).NotTo(HaveOccurred()) - postTestInode, err := getInode(CurrentNetNS) + postTestInode, err := testhelpers.GetInode(CurrentNetNS) Expect(err).NotTo(HaveOccurred()) Expect(postTestInode).To(Equal(preTestInode)) @@ -129,14 +113,14 @@ var _ = Describe("Linux namespace operations", func() { Context("when the callback returns an error", func() { It("restores the calling thread to the original namespace before returning", func() { - preTestInode, err := getInode(CurrentNetNS) + preTestInode, err := testhelpers.GetInode(CurrentNetNS) Expect(err).NotTo(HaveOccurred()) _ = ns.WithNetNS(targetNetNS, false, func(*os.File) error { return errors.New("potato") }) - postTestInode, err := getInode(CurrentNetNS) + postTestInode, err := testhelpers.GetInode(CurrentNetNS) Expect(err).NotTo(HaveOccurred()) Expect(postTestInode).To(Equal(preTestInode)) @@ -152,10 +136,10 @@ var _ = Describe("Linux namespace operations", func() { Describe("validating inode mapping to namespaces", func() { It("checks that different namespaces have different inodes", func() { - hostNSInode, err := getInode(CurrentNetNS) + hostNSInode, err := testhelpers.GetInode(CurrentNetNS) Expect(err).NotTo(HaveOccurred()) - testNsInode, err := getInode(targetNetNSPath) + testNsInode, err := testhelpers.GetInode(targetNetNSPath) Expect(err).NotTo(HaveOccurred()) Expect(hostNSInode).NotTo(Equal(0)) diff --git a/testhelpers/testhelpers.go b/testhelpers/testhelpers.go index f1ccb24e..4eb42bdc 100644 --- a/testhelpers/testhelpers.go +++ b/testhelpers/testhelpers.go @@ -11,6 +11,8 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +// Package testhelpers provides common support behavior for tests package testhelpers import ( @@ -24,6 +26,21 @@ import ( . "github.com/onsi/gomega" ) +func GetInode(path string) (uint64, error) { + file, err := os.Open(path) + if err != nil { + return 0, err + } + defer file.Close() + return GetInodeF(file) +} + +func GetInodeF(file *os.File) (uint64, error) { + stat := &unix.Stat_t{} + err := unix.Fstat(int(file.Fd()), stat) + return stat.Ino, err +} + func MakeNetworkNS(containerID string) string { namespace := "/var/run/netns/" + containerID pid := unix.Getpid() From 82851a860e80bd60e2240fecf765aa6107c0e651 Mon Sep 17 00:00:00 2001 From: Gabe Rosenhouse Date: Sun, 17 Apr 2016 18:48:50 -0700 Subject: [PATCH 4/7] Add basic unit tests of testhelpers --- testhelpers/testhelpers_suite_test.go | 31 +++++++++ testhelpers/testhelpers_test.go | 96 +++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 testhelpers/testhelpers_suite_test.go create mode 100644 testhelpers/testhelpers_test.go diff --git a/testhelpers/testhelpers_suite_test.go b/testhelpers/testhelpers_suite_test.go new file mode 100644 index 00000000..88bfc3d6 --- /dev/null +++ b/testhelpers/testhelpers_suite_test.go @@ -0,0 +1,31 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package testhelpers_test + +import ( + "math/rand" + + . "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/config" + . "github.com/onsi/gomega" + + "testing" +) + +func TestTesthelpers(t *testing.T) { + rand.Seed(config.GinkgoConfig.RandomSeed) + RegisterFailHandler(Fail) + RunSpecs(t, "Testhelpers Suite") +} diff --git a/testhelpers/testhelpers_test.go b/testhelpers/testhelpers_test.go new file mode 100644 index 00000000..ce328f01 --- /dev/null +++ b/testhelpers/testhelpers_test.go @@ -0,0 +1,96 @@ +// Copyright 2016 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package testhelpers_test contains unit tests of the testhelpers +// +// Some of this stuff is non-trivial and can interact in surprising ways +// with the Go runtime. Better be safe. +package testhelpers_test + +import ( + "fmt" + "math/rand" + "path/filepath" + + "golang.org/x/sys/unix" + + "github.com/appc/cni/pkg/testhelpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Test helper functions", func() { + Describe("MakeNetworkNS", func() { + It("should return the filepath to a network namespace", func() { + containerID := fmt.Sprintf("c-%x", rand.Int31()) + nsPath := testhelpers.MakeNetworkNS(containerID) + + Expect(nsPath).To(BeAnExistingFile()) + + testhelpers.RemoveNetworkNS(containerID) + }) + + It("should return a network namespace different from that of the caller", func() { + containerID := fmt.Sprintf("c-%x", rand.Int31()) + + By("discovering the inode of the current netns") + originalNetNSPath := currentNetNSPath() + originalNetNSInode, err := testhelpers.GetInode(originalNetNSPath) + Expect(err).NotTo(HaveOccurred()) + + By("creating a new netns") + createdNetNSPath := testhelpers.MakeNetworkNS(containerID) + defer testhelpers.RemoveNetworkNS(createdNetNSPath) + + By("discovering the inode of the created netns") + createdNetNSInode, err := testhelpers.GetInode(createdNetNSPath) + Expect(err).NotTo(HaveOccurred()) + + By("comparing the inodes") + Expect(createdNetNSInode).NotTo(Equal(originalNetNSInode)) + }) + + It("should not leak the new netns onto any threads in the process", func() { + containerID := fmt.Sprintf("c-%x", rand.Int31()) + + By("creating a new netns") + createdNetNSPath := testhelpers.MakeNetworkNS(containerID) + defer testhelpers.RemoveNetworkNS(createdNetNSPath) + + By("discovering the inode of the created netns") + createdNetNSInode, err := testhelpers.GetInode(createdNetNSPath) + Expect(err).NotTo(HaveOccurred()) + + By("comparing against the netns inode of every thread in the process") + for _, netnsPath := range allNetNSInCurrentProcess() { + netnsInode, err := testhelpers.GetInode(netnsPath) + Expect(err).NotTo(HaveOccurred()) + Expect(netnsInode).NotTo(Equal(createdNetNSInode)) + } + }) + }) +}) + +func currentNetNSPath() string { + pid := unix.Getpid() + tid := unix.Gettid() + return fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid) +} + +func allNetNSInCurrentProcess() []string { + pid := unix.Getpid() + paths, err := filepath.Glob(fmt.Sprintf("/proc/%d/task/*/ns/net", pid)) + Expect(err).NotTo(HaveOccurred()) + return paths +} From c085ec98fdb537c8d6b7a62e7073853582d096df Mon Sep 17 00:00:00 2001 From: Gabe Rosenhouse Date: Sun, 17 Apr 2016 19:44:00 -0700 Subject: [PATCH 5/7] Fix issues with MakeNetworkNS test helper --- testhelpers/testhelpers.go | 49 +++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/testhelpers/testhelpers.go b/testhelpers/testhelpers.go index 4eb42bdc..0963121d 100644 --- a/testhelpers/testhelpers.go +++ b/testhelpers/testhelpers.go @@ -19,6 +19,7 @@ import ( "fmt" "os" "runtime" + "sync" "golang.org/x/sys/unix" @@ -43,37 +44,51 @@ func GetInodeF(file *os.File) (uint64, error) { func MakeNetworkNS(containerID string) string { namespace := "/var/run/netns/" + containerID - pid := unix.Getpid() - tid := unix.Gettid() err := os.MkdirAll("/var/run/netns", 0600) Expect(err).NotTo(HaveOccurred()) - runtime.LockOSThread() - defer runtime.UnlockOSThread() + // create an empty file at the mount point + mountPointFd, err := os.Create(namespace) + Expect(err).NotTo(HaveOccurred()) + mountPointFd.Close() + + var wg sync.WaitGroup + wg.Add(1) + + // do namespace work in a dedicated goroutine, so that we can safely + // Lock/Unlock OSThread without upsetting the lock/unlock state of + // the caller of this function go (func() { + defer wg.Done() + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + defer GinkgoRecover() + // capture current thread's original netns + pid := unix.Getpid() + tid := unix.Gettid() + currentThreadNetNSPath := fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid) + originalNetNS, err := unix.Open(currentThreadNetNSPath, unix.O_RDONLY, 0) + Expect(err).NotTo(HaveOccurred()) + defer unix.Close(originalNetNS) + + // create a new netns on the current thread err = unix.Unshare(unix.CLONE_NEWNET) Expect(err).NotTo(HaveOccurred()) - fd, err := os.Create(namespace) + // bind mount the new netns from the current thread onto the mount point + err = unix.Mount(currentThreadNetNSPath, namespace, "none", unix.MS_BIND, "") Expect(err).NotTo(HaveOccurred()) - defer fd.Close() - err = unix.Mount("/proc/self/ns/net", namespace, "none", unix.MS_BIND, "") - Expect(err).NotTo(HaveOccurred()) + // reset current thread's netns to the original + _, _, e1 := unix.Syscall(unix.SYS_SETNS, uintptr(originalNetNS), uintptr(unix.CLONE_NEWNET), 0) + Expect(e1).To(BeZero()) })() - Eventually(namespace).Should(BeAnExistingFile()) - - fd, err := unix.Open(fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid), unix.O_RDONLY, 0) - Expect(err).NotTo(HaveOccurred()) - - defer unix.Close(fd) - - _, _, e1 := unix.Syscall(unix.SYS_SETNS, uintptr(fd), uintptr(unix.CLONE_NEWNET), 0) - Expect(e1).To(BeZero()) + wg.Wait() return namespace } From d701ca6c824bb22f84714be93ba3c1553a14a90f Mon Sep 17 00:00:00 2001 From: Gabe Rosenhouse Date: Sun, 17 Apr 2016 20:27:02 -0700 Subject: [PATCH 6/7] Document use of goroutine and lockosthread in test helpers --- testhelpers/testhelpers.go | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/testhelpers/testhelpers.go b/testhelpers/testhelpers.go index 0963121d..5ceb3389 100644 --- a/testhelpers/testhelpers.go +++ b/testhelpers/testhelpers.go @@ -42,6 +42,33 @@ func GetInodeF(file *os.File) (uint64, error) { return stat.Ino, err } +/* +A note about goroutines, Linux namespaces and runtime.LockOSThread + +In Linux, network namespaces have thread affinity. + +In the Go language runtime, goroutines do not have affinity for OS threads. +The Go runtime scheduler moves goroutines around amongst OS threads. It +is supposed to be transparent to the Go programmer. + +In order to address cases where the programmer needs thread affinity, Go +provides runtime.LockOSThread and runtime.UnlockOSThread() + +However, the Go runtime does not reference count the Lock and Unlock calls. +Repeated calls to Lock will succeed, but the first call to Unlock will unlock +everything. Therefore, it is dangerous to hide a Lock/Unlock in a library +function, such as in this package. + +The code below, in MakeNetworkNS, avoids this problem by spinning up a new +Go routine specifically so that LockOSThread can be called on it. Thus +goroutine-thread affinity is maintained long enough to perform all the required +namespace operations. + +Because the LockOSThread call is performed inside this short-lived goroutine, +there is no effect either way on the caller's goroutine-thread affinity. + +* */ + func MakeNetworkNS(containerID string) string { namespace := "/var/run/netns/" + containerID @@ -58,7 +85,7 @@ func MakeNetworkNS(containerID string) string { // do namespace work in a dedicated goroutine, so that we can safely // Lock/Unlock OSThread without upsetting the lock/unlock state of - // the caller of this function + // the caller of this function. See block comment above. go (func() { defer wg.Done() From c272c49555be0dce320288aa88cd11095598b89c Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Wed, 6 Apr 2016 11:03:31 -0500 Subject: [PATCH 7/7] ns: fix reading net namespace in multi-threaded processes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /proc/self/ns/net gives the main thread's namespace, not necessarily the namespace of the thread that's running the testcases. This causes sporadic failures of the tests. For example, with a testcase reading inodes after switching netns: /proc/27686/task/27689/ns/net 4026532565 /proc/self/ns/net 4026531969 /proc/27686/task/27689/ns/net 4026532565 See also: https://github.com/vishvananda/netns/commit/008d17ae001344769b031375bdb38a86219154c6 Running Suite: pkg/ns Suite =========================== Random Seed: 1459953577 Will run 6 of 6 specs • Failure [0.028 seconds] Linux namespace operations /cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:167 WithNetNS /cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:166 executes the callback within the target network namespace [It] /cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:97 Expected : 4026531969 to equal : 4026532565 /cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:96 ------------------------------ ••••• Summarizing 1 Failure: [Fail] Linux namespace operations WithNetNS [It] executes the callback within the target network namespace /cni/gopath/src/github.com/appc/cni/pkg/ns/ns_test.go:96 Ran 6 of 6 Specs in 0.564 seconds FAIL! -- 5 Passed | 1 Failed | 0 Pending | 0 Skipped --- FAIL: TestNs (0.56s) FAIL --- ns/ns_test.go | 22 +++++++--------------- testhelpers/testhelpers.go | 14 +++++++++++--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ns/ns_test.go b/ns/ns_test.go index 42fc6322..7ad882f5 100644 --- a/ns/ns_test.go +++ b/ns/ns_test.go @@ -28,13 +28,9 @@ import ( . "github.com/onsi/gomega" ) -const CurrentNetNS = "/proc/self/ns/net" - var _ = Describe("Linux namespace operations", func() { Describe("WithNetNS", func() { var ( - originalNetNS *os.File - targetNetNSName string targetNetNSPath string targetNetNS *os.File @@ -42,8 +38,6 @@ var _ = Describe("Linux namespace operations", func() { BeforeEach(func() { var err error - originalNetNS, err = os.Open(CurrentNetNS) - Expect(err).NotTo(HaveOccurred()) targetNetNSName = fmt.Sprintf("test-netns-%d", rand.Int()) @@ -60,8 +54,6 @@ var _ = Describe("Linux namespace operations", func() { err := exec.Command("ip", "netns", "del", targetNetNSName).Run() Expect(err).NotTo(HaveOccurred()) - - Expect(originalNetNS.Close()).To(Succeed()) }) It("executes the callback within the target network namespace", func() { @@ -71,7 +63,7 @@ var _ = Describe("Linux namespace operations", func() { var actualInode uint64 var innerErr error err = ns.WithNetNS(targetNetNS, false, func(*os.File) error { - actualInode, innerErr = testhelpers.GetInode(CurrentNetNS) + actualInode, innerErr = testhelpers.GetInodeCurNetNS() return nil }) Expect(err).NotTo(HaveOccurred()) @@ -81,7 +73,7 @@ var _ = Describe("Linux namespace operations", func() { }) It("provides the original namespace as the argument to the callback", func() { - hostNSInode, err := testhelpers.GetInode(CurrentNetNS) + hostNSInode, err := testhelpers.GetInodeCurNetNS() Expect(err).NotTo(HaveOccurred()) var inputNSInode uint64 @@ -97,7 +89,7 @@ var _ = Describe("Linux namespace operations", func() { }) It("restores the calling thread to the original network namespace", func() { - preTestInode, err := testhelpers.GetInode(CurrentNetNS) + preTestInode, err := testhelpers.GetInodeCurNetNS() Expect(err).NotTo(HaveOccurred()) err = ns.WithNetNS(targetNetNS, false, func(*os.File) error { @@ -105,7 +97,7 @@ var _ = Describe("Linux namespace operations", func() { }) Expect(err).NotTo(HaveOccurred()) - postTestInode, err := testhelpers.GetInode(CurrentNetNS) + postTestInode, err := testhelpers.GetInodeCurNetNS() Expect(err).NotTo(HaveOccurred()) Expect(postTestInode).To(Equal(preTestInode)) @@ -113,14 +105,14 @@ var _ = Describe("Linux namespace operations", func() { Context("when the callback returns an error", func() { It("restores the calling thread to the original namespace before returning", func() { - preTestInode, err := testhelpers.GetInode(CurrentNetNS) + preTestInode, err := testhelpers.GetInodeCurNetNS() Expect(err).NotTo(HaveOccurred()) _ = ns.WithNetNS(targetNetNS, false, func(*os.File) error { return errors.New("potato") }) - postTestInode, err := testhelpers.GetInode(CurrentNetNS) + postTestInode, err := testhelpers.GetInodeCurNetNS() Expect(err).NotTo(HaveOccurred()) Expect(postTestInode).To(Equal(preTestInode)) @@ -136,7 +128,7 @@ var _ = Describe("Linux namespace operations", func() { Describe("validating inode mapping to namespaces", func() { It("checks that different namespaces have different inodes", func() { - hostNSInode, err := testhelpers.GetInode(CurrentNetNS) + hostNSInode, err := testhelpers.GetInodeCurNetNS() Expect(err).NotTo(HaveOccurred()) testNsInode, err := testhelpers.GetInode(targetNetNSPath) diff --git a/testhelpers/testhelpers.go b/testhelpers/testhelpers.go index 0963121d..004006a9 100644 --- a/testhelpers/testhelpers.go +++ b/testhelpers/testhelpers.go @@ -27,6 +27,16 @@ import ( . "github.com/onsi/gomega" ) +func getCurrentThreadNetNSPath() string { + pid := unix.Getpid() + tid := unix.Gettid() + return fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid) +} + +func GetInodeCurNetNS() (uint64, error) { + return GetInode(getCurrentThreadNetNSPath()) +} + func GetInode(path string) (uint64, error) { file, err := os.Open(path) if err != nil { @@ -68,9 +78,7 @@ func MakeNetworkNS(containerID string) string { defer GinkgoRecover() // capture current thread's original netns - pid := unix.Getpid() - tid := unix.Gettid() - currentThreadNetNSPath := fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid) + currentThreadNetNSPath := getCurrentThreadNetNSPath() originalNetNS, err := unix.Open(currentThreadNetNSPath, unix.O_RDONLY, 0) Expect(err).NotTo(HaveOccurred()) defer unix.Close(originalNetNS)