// Copyright (c) 2018 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 // package nsenter import ( "fmt" "io/ioutil" "os" "os/exec" "path/filepath" "runtime" "strconv" "syscall" "testing" "github.com/stretchr/testify/assert" "golang.org/x/sys/unix" ) const testPID = 12345 func TestGetNSPathFromPID(t *testing.T) { for nsType := range CloneFlagsTable { expectedPath := fmt.Sprintf("/proc/%d/ns/%s", testPID, nsType) path := getNSPathFromPID(testPID, nsType) assert.Equal(t, path, expectedPath) } } func TestGetCurrentThreadNSPath(t *testing.T) { runtime.LockOSThread() defer runtime.UnlockOSThread() currentPID := os.Getpid() currentTID := unix.Gettid() for nsType := range CloneFlagsTable { expectedPath := fmt.Sprintf("/proc/%d/task/%d/ns/%s", currentPID, currentTID, nsType) path := getCurrentThreadNSPath(nsType) assert.Equal(t, path, expectedPath) } } func TestGetFileFromNSEmptyNSPathFailure(t *testing.T) { nsFile, err := getFileFromNS("") assert.NotNil(t, err, "Empty path should result as a failure") assert.Nil(t, nsFile, "The file handler returned should be nil") } func TestGetFileFromNSNotExistingNSPathFailure(t *testing.T) { nsFile, err := ioutil.TempFile("", "not-existing-ns-path") if err != nil { t.Fatal(err) } nsFilePath := nsFile.Name() nsFile.Close() if err := os.Remove(nsFilePath); err != nil { t.Fatal(err) } nsFile, err = getFileFromNS(nsFilePath) assert.NotNil(t, err, "Not existing path should result as a failure") assert.Nil(t, nsFile, "The file handler returned should be nil") } func TestGetFileFromNSWrongNSPathFailure(t *testing.T) { nsFile, err := ioutil.TempFile("", "wrong-ns-path") if err != nil { t.Fatal(err) } nsFilePath := nsFile.Name() nsFile.Close() defer os.Remove(nsFilePath) nsFile, err = getFileFromNS(nsFilePath) assert.NotNil(t, err, "Should fail because wrong filesystem") assert.Nil(t, nsFile, "The file handler returned should be nil") } func TestGetFileFromNSSuccessful(t *testing.T) { for nsType := range CloneFlagsTable { nsFilePath := fmt.Sprintf("/proc/self/ns/%s", string(nsType)) nsFile, err := getFileFromNS(nsFilePath) assert.Nil(t, err, "Should have succeeded: %v", err) assert.NotNil(t, nsFile, "The file handler should not be nil") if nsFile != nil { nsFile.Close() } } } func startSleepBinary(duration int, cloneFlags int) (int, error) { sleepBinName := "sleep" sleepPath, err := exec.LookPath(sleepBinName) if err != nil { return -1, fmt.Errorf("Could not find %q: %v", sleepBinName, err) } cmd := exec.Command(sleepPath, strconv.Itoa(duration)) cmd.SysProcAttr = &syscall.SysProcAttr{ Cloneflags: uintptr(cloneFlags), } if err := cmd.Start(); err != nil { return -1, err } return cmd.Process.Pid, nil } func TestSetNSNilFileHandlerFailure(t *testing.T) { err := setNS(nil, "") assert.NotNil(t, err, "Should fail because file handler is nil") } func TestSetNSUnknownNSTypeFailure(t *testing.T) { file := &os.File{} err := setNS(file, "") assert.NotNil(t, err, "Should fail because unknown ns type") } func TestSetNSWrongFileFailure(t *testing.T) { nsFile, err := ioutil.TempFile("", "wrong-ns-path") if err != nil { t.Fatal(err) } defer func() { nsFilePath := nsFile.Name() nsFile.Close() os.Remove(nsFilePath) }() err = setNS(nsFile, NSTypeIPC) assert.NotNil(t, err, "Should fail because file is not a namespace") } var testNamespaceList = []Namespace{ { Type: NSTypeCGroup, }, { Type: NSTypeIPC, }, { Type: NSTypeNet, }, { Type: NSTypePID, }, { Type: NSTypeUTS, }, } func testToRunNil() error { return nil } func TestNsEnterEmptyPathAndPIDFromNSListFailure(t *testing.T) { err := NsEnter(testNamespaceList, testToRunNil) assert.NotNil(t, err, "Should fail because neither a path nor a PID"+ " has been provided by every namespace of the list") } func TestNsEnterEmptyNamespaceListSuccess(t *testing.T) { err := NsEnter([]Namespace{}, testToRunNil) assert.Nil(t, err, "Should not fail since closure should return nil: %v", err) } func TestNsEnterSuccessful(t *testing.T) { nsList := testNamespaceList sleepDuration := 60 cloneFlags := 0 for _, ns := range nsList { cloneFlags |= CloneFlagsTable[ns.Type] } sleepPID, err := startSleepBinary(sleepDuration, cloneFlags) if err != nil { t.Fatal(err) } defer func() { if sleepPID > 1 { unix.Kill(sleepPID, syscall.SIGKILL) } }() for idx := range nsList { nsList[idx].Path = getNSPathFromPID(sleepPID, nsList[idx].Type) nsList[idx].PID = sleepPID } var sleepPIDFromNsEnter int testToRun := func() error { sleepPIDFromNsEnter, err = startSleepBinary(sleepDuration, 0) if err != nil { return err } return nil } err = NsEnter(nsList, testToRun) assert.Nil(t, err, "%v", err) defer func() { if sleepPIDFromNsEnter > 1 { unix.Kill(sleepPIDFromNsEnter, syscall.SIGKILL) } }() for _, ns := range nsList { nsPathEntered := getNSPathFromPID(sleepPIDFromNsEnter, ns.Type) // Here we are trying to resolve the path but it fails because // namespaces links don't really exist. For this reason, the // call to EvalSymlinks will fail when it will try to stat the // resolved path found. As we only care about the path, we can // retrieve it from the PathError structure. evalExpectedNSPath, err := filepath.EvalSymlinks(ns.Path) if err != nil { evalExpectedNSPath = err.(*os.PathError).Path } // Same thing here, resolving the namespace path. evalNSEnteredPath, err := filepath.EvalSymlinks(nsPathEntered) if err != nil { evalNSEnteredPath = err.(*os.PathError).Path } _, evalExpectedNS := filepath.Split(evalExpectedNSPath) _, evalNSEntered := filepath.Split(evalNSEnteredPath) assert.Equal(t, evalExpectedNS, evalNSEntered) } }