nsenter_mount_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. // +build linux
  2. /*
  3. Copyright 2017 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package nsenter
  15. import (
  16. "io/ioutil"
  17. "os"
  18. "os/user"
  19. "path/filepath"
  20. "testing"
  21. "k8s.io/utils/nsenter"
  22. )
  23. func TestParseFindMnt(t *testing.T) {
  24. tests := []struct {
  25. input string
  26. target string
  27. expectError bool
  28. }{
  29. {
  30. // standard mount name, e.g. for AWS
  31. "/var/lib/kubelet/plugins/kubernetes.io/aws-ebs/mounts/aws/us-east-1d/vol-020f82b0759f72389 ext4\n",
  32. "/var/lib/kubelet/plugins/kubernetes.io/aws-ebs/mounts/aws/us-east-1d/vol-020f82b0759f72389",
  33. false,
  34. },
  35. {
  36. // mount name with space, e.g. vSphere
  37. "/var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/[datastore1] kubevols/kubernetes-dynamic-pvc-4aacaa9b-6ba5-11e7-8f64-0050569f1b82.vmdk ext2\n",
  38. "/var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/[datastore1] kubevols/kubernetes-dynamic-pvc-4aacaa9b-6ba5-11e7-8f64-0050569f1b82.vmdk",
  39. false,
  40. },
  41. {
  42. // hypotetic mount with several spaces
  43. "/var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/[ d a t a s t o r e 1 ] kubevols/kubernetes-dynamic-pvc-4aacaa9b-6ba5-11e7-8f64-0050569f1b82.vmdk ext2\n",
  44. "/var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/[ d a t a s t o r e 1 ] kubevols/kubernetes-dynamic-pvc-4aacaa9b-6ba5-11e7-8f64-0050569f1b82.vmdk",
  45. false,
  46. },
  47. {
  48. // invalid output - no filesystem type
  49. "/var/lib/kubelet/plugins/kubernetes.io/vsphere-volume/mounts/blabla",
  50. "",
  51. true,
  52. },
  53. }
  54. for i, test := range tests {
  55. target, err := parseFindMnt(test.input)
  56. if test.expectError && err == nil {
  57. t.Errorf("test %d expected error, got nil", i)
  58. }
  59. if !test.expectError && err != nil {
  60. t.Errorf("test %d returned error: %s", i, err)
  61. }
  62. if target != test.target {
  63. t.Errorf("test %d expected %q, got %q", i, test.target, target)
  64. }
  65. }
  66. }
  67. func newFakeNsenterMounter(tmpdir string, t *testing.T) (mounter *Mounter, rootfsPath string, varlibPath string, err error) {
  68. rootfsPath = filepath.Join(tmpdir, "rootfs")
  69. if err := os.Mkdir(rootfsPath, 0755); err != nil {
  70. return nil, "", "", err
  71. }
  72. ne, err := nsenter.NewFakeNsenter(rootfsPath)
  73. if err != nil {
  74. return nil, "", "", err
  75. }
  76. varlibPath = filepath.Join(tmpdir, "/var/lib/kubelet")
  77. if err := os.MkdirAll(varlibPath, 0755); err != nil {
  78. return nil, "", "", err
  79. }
  80. return NewMounter(varlibPath, ne), rootfsPath, varlibPath, nil
  81. }
  82. func TestNsenterExistsFile(t *testing.T) {
  83. var isRoot bool
  84. usr, err := user.Current()
  85. if err == nil {
  86. isRoot = usr.Username == "root"
  87. } else {
  88. switch err.(type) {
  89. case user.UnknownUserIdError:
  90. // Root should be always known, this is some random UID
  91. isRoot = false
  92. default:
  93. t.Fatal(err)
  94. }
  95. }
  96. tests := []struct {
  97. name string
  98. prepare func(base, rootfs string) (string, error)
  99. expectedOutput bool
  100. expectError bool
  101. }{
  102. {
  103. name: "simple existing file",
  104. prepare: func(base, rootfs string) (string, error) {
  105. // On the host: /base/file
  106. path := filepath.Join(base, "file")
  107. if err := ioutil.WriteFile(path, []byte{}, 0644); err != nil {
  108. return "", err
  109. }
  110. // In kubelet: /rootfs/base/file
  111. if _, err := writeRootfsFile(rootfs, path, 0644); err != nil {
  112. return "", err
  113. }
  114. return path, nil
  115. },
  116. expectedOutput: true,
  117. },
  118. {
  119. name: "simple non-existing file",
  120. prepare: func(base, rootfs string) (string, error) {
  121. path := filepath.Join(base, "file")
  122. return path, nil
  123. },
  124. expectedOutput: false,
  125. },
  126. {
  127. name: "simple non-accessible file",
  128. prepare: func(base, rootfs string) (string, error) {
  129. // On the host:
  130. // create /base/dir/file, then make the dir inaccessible
  131. dir := filepath.Join(base, "dir")
  132. if err := os.MkdirAll(dir, 0755); err != nil {
  133. return "", err
  134. }
  135. path := filepath.Join(dir, "file")
  136. if err := ioutil.WriteFile(path, []byte{}, 0); err != nil {
  137. return "", err
  138. }
  139. if err := os.Chmod(dir, 0644); err != nil {
  140. return "", err
  141. }
  142. // In kubelet: do the same with /rootfs/base/dir/file
  143. rootfsPath, err := writeRootfsFile(rootfs, path, 0777)
  144. if err != nil {
  145. return "", err
  146. }
  147. rootfsDir := filepath.Dir(rootfsPath)
  148. if err := os.Chmod(rootfsDir, 0644); err != nil {
  149. return "", err
  150. }
  151. return path, nil
  152. },
  153. expectedOutput: isRoot, // ExistsPath success when running as root
  154. expectError: !isRoot, // ExistsPath must fail when running as not-root
  155. },
  156. {
  157. name: "relative symlink to existing file",
  158. prepare: func(base, rootfs string) (string, error) {
  159. // On the host: /base/link -> file
  160. file := filepath.Join(base, "file")
  161. if err := ioutil.WriteFile(file, []byte{}, 0); err != nil {
  162. return "", err
  163. }
  164. path := filepath.Join(base, "link")
  165. if err := os.Symlink("file", path); err != nil {
  166. return "", err
  167. }
  168. // In kubelet: /rootfs/base/file
  169. if _, err := writeRootfsFile(rootfs, file, 0644); err != nil {
  170. return "", err
  171. }
  172. return path, nil
  173. },
  174. expectedOutput: true,
  175. },
  176. {
  177. name: "absolute symlink to existing file",
  178. prepare: func(base, rootfs string) (string, error) {
  179. // On the host: /base/link -> /base/file
  180. file := filepath.Join(base, "file")
  181. if err := ioutil.WriteFile(file, []byte{}, 0); err != nil {
  182. return "", err
  183. }
  184. path := filepath.Join(base, "link")
  185. if err := os.Symlink(file, path); err != nil {
  186. return "", err
  187. }
  188. // In kubelet: /rootfs/base/file
  189. if _, err := writeRootfsFile(rootfs, file, 0644); err != nil {
  190. return "", err
  191. }
  192. return path, nil
  193. },
  194. expectedOutput: true,
  195. },
  196. {
  197. name: "relative symlink to non-existing file",
  198. prepare: func(base, rootfs string) (string, error) {
  199. path := filepath.Join(base, "link")
  200. if err := os.Symlink("file", path); err != nil {
  201. return "", err
  202. }
  203. return path, nil
  204. },
  205. expectedOutput: false,
  206. },
  207. {
  208. name: "absolute symlink to non-existing file",
  209. prepare: func(base, rootfs string) (string, error) {
  210. file := filepath.Join(base, "file")
  211. path := filepath.Join(base, "link")
  212. if err := os.Symlink(file, path); err != nil {
  213. return "", err
  214. }
  215. return path, nil
  216. },
  217. expectedOutput: false,
  218. },
  219. {
  220. name: "symlink loop",
  221. prepare: func(base, rootfs string) (string, error) {
  222. path := filepath.Join(base, "link")
  223. if err := os.Symlink(path, path); err != nil {
  224. return "", err
  225. }
  226. return path, nil
  227. },
  228. expectedOutput: false,
  229. // TODO: realpath -m is not able to detect symlink loop. Should we care?
  230. expectError: false,
  231. },
  232. }
  233. for _, test := range tests {
  234. tmpdir, err := ioutil.TempDir("", "nsenter-exists-file")
  235. if err != nil {
  236. t.Error(err)
  237. continue
  238. }
  239. defer os.RemoveAll(tmpdir)
  240. testBase := filepath.Join(tmpdir, "base")
  241. if err := os.Mkdir(testBase, 0755); err != nil {
  242. t.Error(err)
  243. continue
  244. }
  245. mounter, rootfs, _, err := newFakeNsenterMounter(tmpdir, t)
  246. if err != nil {
  247. t.Error(err)
  248. continue
  249. }
  250. path, err := test.prepare(testBase, rootfs)
  251. if err != nil {
  252. t.Error(err)
  253. continue
  254. }
  255. out, err := mounter.ExistsPath(path)
  256. if err != nil && !test.expectError {
  257. t.Errorf("Test %q: unexpected error: %s", test.name, err)
  258. }
  259. if err == nil && test.expectError {
  260. t.Errorf("Test %q: expected error, got none", test.name)
  261. }
  262. if out != test.expectedOutput {
  263. t.Errorf("Test %q: expected return value %v, got %v", test.name, test.expectedOutput, out)
  264. }
  265. }
  266. }
  267. func TestNsenterGetMode(t *testing.T) {
  268. tests := []struct {
  269. name string
  270. prepare func(base, rootfs string) (string, error)
  271. expectedMode os.FileMode
  272. expectError bool
  273. }{
  274. {
  275. name: "simple file",
  276. prepare: func(base, rootfs string) (string, error) {
  277. // On the host: /base/file
  278. path := filepath.Join(base, "file")
  279. if err := ioutil.WriteFile(path, []byte{}, 0644); err != nil {
  280. return "", err
  281. }
  282. // Prepare a different file as /rootfs/base/file (="the host
  283. // visible from container") to check that NsEnterMounter calls
  284. // stat on this file and not on /base/file.
  285. // Visible from kubelet: /rootfs/base/file
  286. if _, err := writeRootfsFile(rootfs, path, 0777); err != nil {
  287. return "", err
  288. }
  289. return path, nil
  290. },
  291. expectedMode: 0777,
  292. },
  293. {
  294. name: "non-existing file",
  295. prepare: func(base, rootfs string) (string, error) {
  296. path := filepath.Join(base, "file")
  297. return path, nil
  298. },
  299. expectedMode: 0,
  300. expectError: true,
  301. },
  302. {
  303. name: "absolute symlink to existing file",
  304. prepare: func(base, rootfs string) (string, error) {
  305. // On the host: /base/link -> /base/file
  306. file := filepath.Join(base, "file")
  307. if err := ioutil.WriteFile(file, []byte{}, 0644); err != nil {
  308. return "", err
  309. }
  310. path := filepath.Join(base, "link")
  311. if err := os.Symlink(file, path); err != nil {
  312. return "", err
  313. }
  314. // Visible from kubelet:
  315. // /rootfs/base/file
  316. if _, err := writeRootfsFile(rootfs, file, 0747); err != nil {
  317. return "", err
  318. }
  319. return path, nil
  320. },
  321. expectedMode: 0747,
  322. },
  323. {
  324. name: "relative symlink to existing file",
  325. prepare: func(base, rootfs string) (string, error) {
  326. // On the host: /base/link -> file
  327. file := filepath.Join(base, "file")
  328. if err := ioutil.WriteFile(file, []byte{}, 0741); err != nil {
  329. return "", err
  330. }
  331. path := filepath.Join(base, "link")
  332. if err := os.Symlink("file", path); err != nil {
  333. return "", err
  334. }
  335. // Visible from kubelet:
  336. // /rootfs/base/file
  337. if _, err := writeRootfsFile(rootfs, file, 0647); err != nil {
  338. return "", err
  339. }
  340. return path, nil
  341. },
  342. expectedMode: 0647,
  343. },
  344. }
  345. for _, test := range tests {
  346. tmpdir, err := ioutil.TempDir("", "nsenter-get-mode-")
  347. if err != nil {
  348. t.Error(err)
  349. continue
  350. }
  351. defer os.RemoveAll(tmpdir)
  352. testBase := filepath.Join(tmpdir, "base")
  353. if err := os.Mkdir(testBase, 0755); err != nil {
  354. t.Error(err)
  355. continue
  356. }
  357. mounter, rootfs, _, err := newFakeNsenterMounter(tmpdir, t)
  358. if err != nil {
  359. t.Error(err)
  360. continue
  361. }
  362. path, err := test.prepare(testBase, rootfs)
  363. if err != nil {
  364. t.Error(err)
  365. continue
  366. }
  367. mode, err := mounter.GetMode(path)
  368. if err != nil && !test.expectError {
  369. t.Errorf("Test %q: unexpected error: %s", test.name, err)
  370. }
  371. if err == nil && test.expectError {
  372. t.Errorf("Test %q: expected error, got none", test.name)
  373. }
  374. if mode != test.expectedMode {
  375. t.Errorf("Test %q: expected return value %v, got %v", test.name, test.expectedMode, mode)
  376. }
  377. }
  378. }
  379. func writeRootfsFile(rootfs, path string, mode os.FileMode) (string, error) {
  380. fullPath := filepath.Join(rootfs, path)
  381. dir := filepath.Dir(fullPath)
  382. if err := os.MkdirAll(dir, 0755); err != nil {
  383. return "", err
  384. }
  385. if err := ioutil.WriteFile(fullPath, []byte{}, mode); err != nil {
  386. return "", err
  387. }
  388. // Use chmod, io.WriteFile is affected by umask
  389. if err := os.Chmod(fullPath, mode); err != nil {
  390. return "", err
  391. }
  392. return fullPath, nil
  393. }