github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/nsfix/nsfix_test.go (about) 1 /* 2 Copyright 2018 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package nsfix 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "os" 23 "os/exec" 24 "path/filepath" 25 "runtime" 26 "strconv" 27 "strings" 28 "testing" 29 "time" 30 31 "github.com/golang/glog" 32 33 "github.com/Mirantis/virtlet/pkg/utils" 34 testutils "github.com/Mirantis/virtlet/pkg/utils/testing" 35 ) 36 37 type nsFixTestArg struct { 38 DirPath string 39 } 40 41 type nsFixTestRet struct { 42 Files []string 43 IsRoot bool 44 } 45 46 func listFiles(dirPath string) ([]string, error) { 47 matches, err := filepath.Glob(filepath.Join(dirPath, "*")) 48 if err != nil { 49 return nil, fmt.Errorf("Glob(): %v", err) 50 } 51 52 var r []string 53 for _, m := range matches { 54 r = append(r, filepath.Base(m)) 55 } 56 return r, nil 57 } 58 59 func handleNsFixTest1(data interface{}) (interface{}, error) { 60 arg := data.(*nsFixTestArg) 61 files, err := listFiles(arg.DirPath) 62 if err != nil { 63 return nil, err 64 } 65 return nsFixTestRet{files, os.Getuid() == 0}, nil 66 } 67 68 func handleNsFixTest2(data interface{}) (interface{}, error) { 69 arg := data.(*nsFixTestArg) 70 files, err := listFiles(arg.DirPath) 71 if err != nil { 72 return nil, err 73 } 74 75 contents := []byte(strings.Join(files, "\n")) 76 if err := ioutil.WriteFile(filepath.Join(arg.DirPath, "out"), contents, 0666); err != nil { 77 return nil, err 78 } 79 80 return nil, nil 81 } 82 83 func handleNsFixWithNilArg(data interface{}) (interface{}, error) { 84 return 42, nil 85 } 86 87 func handleNsFixWithNilResult(data interface{}) (interface{}, error) { 88 targetPath := data.(*string) 89 return nil, os.Mkdir(filepath.Join(*targetPath, "foobar"), 0777) 90 } 91 92 func verifyNsFix(t *testing.T, toRun func(tmpDir string, dirs map[string]string, pids []int)) { 93 if runtime.GOOS != "linux" { 94 t.Skip("The namespace fix only works on Linux") 95 } 96 if os.Getuid() != 0 { 97 t.Skip("This test requires root privs") 98 } 99 100 tmpDir, err := ioutil.TempDir("", "nsfix-") 101 if err != nil { 102 t.Fatalf("Can't create temp dir for config image: %v", err) 103 } 104 defer os.RemoveAll(tmpDir) 105 106 if err := os.Chmod(tmpDir, 0755); err != nil { 107 t.Fatalf("Chown(): %v", err) 108 } 109 110 // TEST_NSFIX is handled by init() below 111 os.Setenv("TEST_NSFIX", "1") 112 defer os.Setenv("TEST_NSFIX", "") 113 114 dirA := filepath.Join(tmpDir, "a") 115 dirD := filepath.Join(tmpDir, "d") 116 dirs := map[string]string{ 117 "a": dirA, 118 "b": filepath.Join(tmpDir, "b"), 119 "c": filepath.Join(dirA, "c"), 120 "d": dirD, 121 "e": filepath.Join(dirD, "e"), 122 } 123 for _, p := range []string{"a", "b", "c", "d", "e"} { 124 if err := os.Mkdir(dirs[p], 0777); err != nil { 125 t.Fatalf("Can't create dir %q: %v", p, err) 126 } 127 } 128 129 doneFiles := []string{ 130 filepath.Join(tmpDir, "done1"), 131 filepath.Join(tmpDir, "done2"), 132 } 133 var pids []int 134 for _, cmd := range []string{ 135 fmt.Sprintf("mount --bind %q %q && touch %q && sleep 10000", dirs["a"], dirs["b"], doneFiles[0]), 136 fmt.Sprintf("mount --bind %q %q && touch %q && sleep 10000", dirs["d"], dirs["b"], doneFiles[1]), 137 } { 138 tc := testutils.RunProcess(t, "unshare", []string{ 139 "-m", "/bin/bash", "-c", cmd, 140 }, nil) 141 defer tc.Stop() 142 pids = append(pids, tc.Pid()) 143 } 144 if err := utils.WaitLoop(func() (bool, error) { 145 for _, fileName := range doneFiles { 146 switch _, err := os.Stat(fileName); { 147 case err == nil: 148 // ok 149 case os.IsNotExist(err): 150 return false, nil 151 default: 152 return false, err 153 } 154 } 155 return true, nil 156 }, 50*time.Millisecond, 10*time.Second, nil); err != nil { 157 t.Fatal(err) 158 } 159 160 toRun(tmpDir, dirs, pids) 161 } 162 163 func TestNsFix(t *testing.T) { 164 verifyNsFix(t, func(tmpDir string, dirs map[string]string, pids []int) { 165 var r nsFixTestRet 166 if err := NewCall("nsFixTest1"). 167 TargetPid(pids[0]). 168 Arg(nsFixTestArg{dirs["b"]}). 169 SpawnInNamespaces(&r); err != nil { 170 t.Fatalf("SpawnInNamespaces(): %v", err) 171 } 172 resultStr := strings.Join(r.Files, "\n") 173 expectedResultStr := "c" 174 if resultStr != expectedResultStr { 175 t.Errorf("Bad result from SpawnInNamespaces(): %q instead of %q", resultStr, expectedResultStr) 176 } 177 if !r.IsRoot { 178 t.Errorf("SpawnInNamespaces dropped privs when not requested to do so") 179 } 180 181 if err := NewCall("nsFixTest1"). 182 TargetPid(pids[1]). 183 Arg(nsFixTestArg{dirs["b"]}). 184 DropPrivs(). 185 SpawnInNamespaces(&r); err != nil { 186 t.Fatalf("SpawnInNamespaces(): %v", err) 187 } 188 resultStr = strings.Join(r.Files, "\n") 189 expectedResultStr = "e" 190 if resultStr != expectedResultStr { 191 t.Errorf("Bad result from SpawnInNamespaces(): %q instead of %q", resultStr, expectedResultStr) 192 } 193 if r.IsRoot { 194 t.Errorf("SpawnInNamespaces didn't drop privs not requested to do so") 195 } 196 197 // we examine SwitchToNamespace in the same test to make 198 // sure the calls don't interfere between themselves 199 cmd := exec.Command(os.Args[0]) 200 cmd.Stderr = os.Stderr 201 cmd.Stdout = os.Stdout 202 cmd.Env = append(os.Environ(), fmt.Sprintf("TEST_NSFIX_SWITCH=%d:%s", pids[1], dirs["b"])) 203 if err := cmd.Run(); err != nil { 204 if exitErr, ok := err.(*exec.ExitError); ok { 205 t.Errorf("Error rerunning the test executable: %v\nstderr:\n%s", err, exitErr.Stderr) 206 } else { 207 t.Errorf("Error rerunning the test executable: %v", err) 208 } 209 } 210 211 outPath := filepath.Join(dirs["d"], "out") 212 expectedContents := "e" 213 switch outStr, err := ioutil.ReadFile(outPath); { 214 case err != nil: 215 t.Errorf("Error reading %q: %v", outPath, err) 216 case string(outStr) != expectedContents: 217 t.Errorf("bad out file contents: %q instead of %q", outStr, expectedContents) 218 } 219 }) 220 } 221 222 func TestNsFixWithNilArg(t *testing.T) { 223 verifyNsFix(t, func(tmpDir string, dirs map[string]string, pids []int) { 224 var r int 225 if err := NewCall("nsFixTestNilArg"). 226 TargetPid(pids[0]). 227 SpawnInNamespaces(&r); err != nil { 228 t.Fatalf("SpawnInNamespaces(): %v", err) 229 } 230 expectedResult := 42 231 if r != expectedResult { 232 t.Errorf("Bad result from SpawnInNamespaces(): %d instead of %d", r, expectedResult) 233 } 234 }) 235 } 236 237 func TestNsFixWithNilResult(t *testing.T) { 238 verifyNsFix(t, func(tmpDir string, dirs map[string]string, pids []int) { 239 if err := NewCall("nsFixTestNilResult"). 240 TargetPid(pids[0]). 241 Arg(dirs["b"]). 242 SpawnInNamespaces(nil); err != nil { 243 t.Fatalf("SpawnInNamespaces(): %v", err) 244 } 245 // dirs["a"] is bind-mounted under dirs["b"] 246 if _, err := os.Stat(filepath.Join(dirs["a"], "foobar")); err != nil { 247 t.Errorf("Stat(): %v", err) 248 } 249 }) 250 } 251 252 func init() { 253 if switchStr := os.Getenv("TEST_NSFIX_SWITCH"); switchStr != "" { 254 os.Setenv("TEST_NSFIX_SWITCH", "") 255 parts := strings.SplitN(switchStr, ":", 2) 256 if len(parts) != 2 { 257 glog.Fatalf("bad TEST_NSFIX_SWITCH: %q", switchStr) 258 } 259 pid, err := strconv.Atoi(parts[0]) 260 if err != nil { 261 glog.Fatalf("bad TEST_NSFIX_SWITCH: %q", switchStr) 262 } 263 if err := NewCall("nsFixTest2"). 264 TargetPid(pid). 265 Arg(nsFixTestArg{parts[1]}). 266 SwitchToNamespaces(); err != nil { 267 glog.Fatalf("SwitchToNamespaces(): %v", err) 268 } 269 } 270 RegisterReexec("nsFixTest1", handleNsFixTest1, nsFixTestArg{}) 271 RegisterReexec("nsFixTest2", handleNsFixTest2, nsFixTestArg{}) 272 RegisterReexec("nsFixTestNilArg", handleNsFixWithNilArg, nil) 273 RegisterReexec("nsFixTestNilResult", handleNsFixWithNilResult, "") 274 if os.Getenv("TEST_NSFIX") != "" { 275 // NOTE: this is not a recommended way to invoke 276 // reexec, but may be the easiest one for testing 277 HandleReexec() 278 } 279 }