1// +build linux
2
3/*
4Copyright 2014 The Kubernetes Authors.
5
6Licensed under the Apache License, Version 2.0 (the "License");
7you may not use this file except in compliance with the License.
8You may obtain a copy of the License at
9
10    http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing, software
13distributed under the License is distributed on an "AS IS" BASIS,
14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15See the License for the specific language governing permissions and
16limitations under the License.
17*/
18
19package mount
20
21import (
22	"io/ioutil"
23	"os"
24	"reflect"
25	"strings"
26	"testing"
27)
28
29func TestReadProcMountsFrom(t *testing.T) {
30	successCase :=
31		`/dev/0 /path/to/0 type0 flags 0 0
32/dev/1    /path/to/1   type1	flags 1 1
33/dev/2 /path/to/2 type2 flags,1,2=3 2 2
34`
35	// NOTE: readProcMountsFrom has been updated to using fnv.New32a()
36	mounts, err := parseProcMounts([]byte(successCase))
37	if err != nil {
38		t.Errorf("expected success, got %v", err)
39	}
40	if len(mounts) != 3 {
41		t.Fatalf("expected 3 mounts, got %d", len(mounts))
42	}
43	mp := MountPoint{"/dev/0", "/path/to/0", "type0", []string{"flags"}, 0, 0}
44	if !mountPointsEqual(&mounts[0], &mp) {
45		t.Errorf("got unexpected MountPoint[0]: %#v", mounts[0])
46	}
47	mp = MountPoint{"/dev/1", "/path/to/1", "type1", []string{"flags"}, 1, 1}
48	if !mountPointsEqual(&mounts[1], &mp) {
49		t.Errorf("got unexpected MountPoint[1]: %#v", mounts[1])
50	}
51	mp = MountPoint{"/dev/2", "/path/to/2", "type2", []string{"flags", "1", "2=3"}, 2, 2}
52	if !mountPointsEqual(&mounts[2], &mp) {
53		t.Errorf("got unexpected MountPoint[2]: %#v", mounts[2])
54	}
55
56	errorCases := []string{
57		"/dev/0 /path/to/mount\n",
58		"/dev/1 /path/to/mount type flags a 0\n",
59		"/dev/2 /path/to/mount type flags 0 b\n",
60	}
61	for _, ec := range errorCases {
62		_, err := parseProcMounts([]byte(ec))
63		if err == nil {
64			t.Errorf("expected error")
65		}
66	}
67}
68
69func mountPointsEqual(a, b *MountPoint) bool {
70	if a.Device != b.Device || a.Path != b.Path || a.Type != b.Type || !reflect.DeepEqual(a.Opts, b.Opts) || a.Pass != b.Pass || a.Freq != b.Freq {
71		return false
72	}
73	return true
74}
75
76func TestGetMountRefs(t *testing.T) {
77	fm := NewFakeMounter(
78		[]MountPoint{
79			{Device: "/dev/sdb", Path: "/var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/gce-pd"},
80			{Device: "/dev/sdb", Path: "/var/lib/kubelet/pods/some-pod/volumes/kubernetes.io~gce-pd/gce-pd-in-pod"},
81			{Device: "/dev/sdc", Path: "/var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/gce-pd2"},
82			{Device: "/dev/sdc", Path: "/var/lib/kubelet/pods/some-pod/volumes/kubernetes.io~gce-pd/gce-pd2-in-pod1"},
83			{Device: "/dev/sdc", Path: "/var/lib/kubelet/pods/some-pod/volumes/kubernetes.io~gce-pd/gce-pd2-in-pod2"},
84		})
85
86	tests := []struct {
87		mountPath    string
88		expectedRefs []string
89	}{
90		{
91			"/var/lib/kubelet/pods/some-pod/volumes/kubernetes.io~gce-pd/gce-pd-in-pod",
92			[]string{
93				"/var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/gce-pd",
94			},
95		},
96		{
97			"/var/lib/kubelet/pods/some-pod/volumes/kubernetes.io~gce-pd/gce-pd2-in-pod1",
98			[]string{
99				"/var/lib/kubelet/pods/some-pod/volumes/kubernetes.io~gce-pd/gce-pd2-in-pod2",
100				"/var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/gce-pd2",
101			},
102		},
103		{
104			"/var/fake/directory/that/doesnt/exist",
105			[]string{},
106		},
107	}
108
109	for i, test := range tests {
110		if refs, err := fm.GetMountRefs(test.mountPath); err != nil || !setEquivalent(test.expectedRefs, refs) {
111			t.Errorf("%d. getMountRefs(%q) = %v, %v; expected %v, nil", i, test.mountPath, refs, err, test.expectedRefs)
112		}
113	}
114}
115
116func setEquivalent(set1, set2 []string) bool {
117	map1 := make(map[string]bool)
118	map2 := make(map[string]bool)
119	for _, s := range set1 {
120		map1[s] = true
121	}
122	for _, s := range set2 {
123		map2[s] = true
124	}
125
126	for s := range map1 {
127		if !map2[s] {
128			return false
129		}
130	}
131	for s := range map2 {
132		if !map1[s] {
133			return false
134		}
135	}
136	return true
137}
138
139func TestGetDeviceNameFromMount(t *testing.T) {
140	fm := NewFakeMounter(
141		[]MountPoint{
142			{Device: "/dev/disk/by-path/prefix-lun-1",
143				Path: "/mnt/111"},
144			{Device: "/dev/disk/by-path/prefix-lun-1",
145				Path: "/mnt/222"},
146		})
147
148	tests := []struct {
149		mountPath      string
150		expectedDevice string
151		expectedRefs   int
152	}{
153		{
154			"/mnt/222",
155			"/dev/disk/by-path/prefix-lun-1",
156			2,
157		},
158	}
159
160	for i, test := range tests {
161		if device, refs, err := GetDeviceNameFromMount(fm, test.mountPath); err != nil || test.expectedRefs != refs || test.expectedDevice != device {
162			t.Errorf("%d. GetDeviceNameFromMount(%s) = (%s, %d), %v; expected (%s,%d), nil", i, test.mountPath, device, refs, err, test.expectedDevice, test.expectedRefs)
163		}
164	}
165}
166
167func TestGetMountRefsByDev(t *testing.T) {
168	fm := NewFakeMounter(
169		[]MountPoint{
170			{Device: "/dev/sdb", Path: "/var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/gce-pd"},
171			{Device: "/dev/sdb", Path: "/var/lib/kubelet/pods/some-pod/volumes/kubernetes.io~gce-pd/gce-pd-in-pod"},
172			{Device: "/dev/sdc", Path: "/var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/gce-pd2"},
173			{Device: "/dev/sdc", Path: "/var/lib/kubelet/pods/some-pod/volumes/kubernetes.io~gce-pd/gce-pd2-in-pod1"},
174			{Device: "/dev/sdc", Path: "/var/lib/kubelet/pods/some-pod/volumes/kubernetes.io~gce-pd/gce-pd2-in-pod2"},
175		})
176
177	tests := []struct {
178		mountPath    string
179		expectedRefs []string
180	}{
181		{
182			"/var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/gce-pd",
183			[]string{
184				"/var/lib/kubelet/pods/some-pod/volumes/kubernetes.io~gce-pd/gce-pd-in-pod",
185			},
186		},
187		{
188			"/var/lib/kubelet/plugins/kubernetes.io/gce-pd/mounts/gce-pd2",
189			[]string{
190				"/var/lib/kubelet/pods/some-pod/volumes/kubernetes.io~gce-pd/gce-pd2-in-pod1",
191				"/var/lib/kubelet/pods/some-pod/volumes/kubernetes.io~gce-pd/gce-pd2-in-pod2",
192			},
193		},
194	}
195
196	for i, test := range tests {
197
198		if refs, err := getMountRefsByDev(fm, test.mountPath); err != nil || !setEquivalent(test.expectedRefs, refs) {
199			t.Errorf("%d. getMountRefsByDev(%q) = %v, %v; expected %v, nil", i, test.mountPath, refs, err, test.expectedRefs)
200		}
201	}
202}
203
204func TestPathWithinBase(t *testing.T) {
205	tests := []struct {
206		name     string
207		fullPath string
208		basePath string
209		expected bool
210	}{
211		{
212			name:     "good subpath",
213			fullPath: "/a/b/c",
214			basePath: "/a",
215			expected: true,
216		},
217		{
218			name:     "good subpath 2",
219			fullPath: "/a/b/c",
220			basePath: "/a/b",
221			expected: true,
222		},
223		{
224			name:     "good subpath end slash",
225			fullPath: "/a/b/c/",
226			basePath: "/a/b",
227			expected: true,
228		},
229		{
230			name:     "good subpath backticks",
231			fullPath: "/a/b/../c",
232			basePath: "/a",
233			expected: true,
234		},
235		{
236			name:     "good subpath equal",
237			fullPath: "/a/b/c",
238			basePath: "/a/b/c",
239			expected: true,
240		},
241		{
242			name:     "good subpath equal 2",
243			fullPath: "/a/b/c/",
244			basePath: "/a/b/c",
245			expected: true,
246		},
247		{
248			name:     "good subpath root",
249			fullPath: "/a",
250			basePath: "/",
251			expected: true,
252		},
253		{
254			name:     "bad subpath parent",
255			fullPath: "/a/b/c",
256			basePath: "/a/b/c/d",
257			expected: false,
258		},
259		{
260			name:     "bad subpath outside",
261			fullPath: "/b/c",
262			basePath: "/a/b/c",
263			expected: false,
264		},
265		{
266			name:     "bad subpath prefix",
267			fullPath: "/a/b/cd",
268			basePath: "/a/b/c",
269			expected: false,
270		},
271		{
272			name:     "bad subpath backticks",
273			fullPath: "/a/../b",
274			basePath: "/a",
275			expected: false,
276		},
277		{
278			name:     "configmap subpath",
279			fullPath: "/var/lib/kubelet/pods/uuid/volumes/kubernetes.io~configmap/config/..timestamp/file.txt",
280			basePath: "/var/lib/kubelet/pods/uuid/volumes/kubernetes.io~configmap/config",
281			expected: true,
282		},
283	}
284	for _, test := range tests {
285		if PathWithinBase(test.fullPath, test.basePath) != test.expected {
286			t.Errorf("test %q failed: expected %v", test.name, test.expected)
287		}
288
289	}
290}
291
292func TestSearchMountPoints(t *testing.T) {
293	base := `
29419 25 0:18 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw
29520 25 0:4 / /proc rw,nosuid,nodev,noexec,relatime shared:12 - proc proc rw
29621 25 0:6 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=4058156k,nr_inodes=1014539,mode=755
29722 21 0:14 / /dev/pts rw,nosuid,noexec,relatime shared:3 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
29823 25 0:19 / /run rw,nosuid,noexec,relatime shared:5 - tmpfs tmpfs rw,size=815692k,mode=755
29925 0 252:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ubuntu--vg-root rw,errors=remount-ro,data=ordered
30026 19 0:12 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:8 - securityfs securityfs rw
30127 21 0:21 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw
30228 23 0:22 / /run/lock rw,nosuid,nodev,noexec,relatime shared:6 - tmpfs tmpfs rw,size=5120k
30329 19 0:23 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:9 - tmpfs tmpfs ro,mode=755
30430 29 0:24 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
30531 19 0:25 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:11 - pstore pstore rw
30632 29 0:26 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:13 - cgroup cgroup rw,devices
30733 29 0:27 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:14 - cgroup cgroup rw,freezer
30834 29 0:28 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,pids
30935 29 0:29 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,blkio
31036 29 0:30 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,memory
31137 29 0:31 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:18 - cgroup cgroup rw,perf_event
31238 29 0:32 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,hugetlb
31339 29 0:33 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:20 - cgroup cgroup rw,cpu,cpuacct
31440 29 0:34 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:21 - cgroup cgroup rw,cpuset
31541 29 0:35 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:22 - cgroup cgroup rw,net_cls,net_prio
31658 25 7:1 / /mnt/disks/blkvol1 rw,relatime shared:38 - ext4 /dev/loop1 rw,data=ordere
317`
318
319	testcases := []struct {
320		name         string
321		source       string
322		mountInfos   string
323		expectedRefs []string
324		expectedErr  error
325	}{
326		{
327			"dir",
328			"/mnt/disks/vol1",
329			base,
330			nil,
331			nil,
332		},
333		{
334			"dir-used",
335			"/mnt/disks/vol1",
336			base + `
33756 25 252:0 /mnt/disks/vol1 /var/lib/kubelet/pods/1890aef5-5a60-11e8-962f-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-test rw,relatime shared:1 - ext4 /dev/mapper/ubuntu--vg-root rw,errors=remount-ro,data=ordered
33857 25 0:45 / /mnt/disks/vol rw,relatime shared:36 - tmpfs tmpfs rw
339`,
340			[]string{"/var/lib/kubelet/pods/1890aef5-5a60-11e8-962f-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-test"},
341			nil,
342		},
343		{
344			"tmpfs-vol",
345			"/mnt/disks/vol1",
346			base + `120 25 0:76 / /mnt/disks/vol1 rw,relatime shared:41 - tmpfs vol1 rw,size=10000k
347`,
348			nil,
349			nil,
350		},
351		{
352			"tmpfs-vol-used-by-two-pods",
353			"/mnt/disks/vol1",
354			base + `120 25 0:76 / /mnt/disks/vol1 rw,relatime shared:41 - tmpfs vol1 rw,size=10000k
355196 25 0:76 / /var/lib/kubelet/pods/ade3ac21-5a5b-11e8-8559-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-8f263585 rw,relatime shared:41 - tmpfs vol1 rw,size=10000k
356228 25 0:76 / /var/lib/kubelet/pods/ac60532d-5a5b-11e8-8559-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-8f263585 rw,relatime shared:41 - tmpfs vol1 rw,size=10000k
357`,
358			[]string{
359				"/var/lib/kubelet/pods/ade3ac21-5a5b-11e8-8559-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-8f263585",
360				"/var/lib/kubelet/pods/ac60532d-5a5b-11e8-8559-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-8f263585",
361			},
362			nil,
363		},
364		{
365			"tmpfs-subdir-used-indirectly-via-bindmount-dir-by-one-pod",
366			"/mnt/vol1/foo",
367			base + `177 25 0:46 / /mnt/data rw,relatime shared:37 - tmpfs data rw
368190 25 0:46 /vol1 /mnt/vol1 rw,relatime shared:37 - tmpfs data rw
369191 25 0:46 /vol2 /mnt/vol2 rw,relatime shared:37 - tmpfs data rw
37062 25 0:46 /vol1/foo /var/lib/kubelet/pods/e25f2f01-5b06-11e8-8694-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-test rw,relatime shared:37 - tmpfs data rw
371`,
372			[]string{"/var/lib/kubelet/pods/e25f2f01-5b06-11e8-8694-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-test"},
373			nil,
374		},
375		{
376			"dir-bindmounted",
377			"/mnt/disks/vol2",
378			base + `342 25 252:0 /mnt/disks/vol2 /mnt/disks/vol2 rw,relatime shared:1 - ext4 /dev/mapper/ubuntu--vg-root rw,errors=remount-ro,data=ordered
379`,
380			nil,
381			nil,
382		},
383		{
384			"dir-bindmounted-used-by-one-pod",
385			"/mnt/disks/vol2",
386			base + `342 25 252:0 /mnt/disks/vol2 /mnt/disks/vol2 rw,relatime shared:1 - ext4 /dev/mapper/ubuntu--vg-root rw,errors=remount-ro,data=ordered
38777 25 252:0 /mnt/disks/vol2 /var/lib/kubelet/pods/f30dc360-5a5d-11e8-962f-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-1fb30a1c rw,relatime shared:1 - ext4 /dev/mapper/ubuntu--vg-root rw,errors=remount-ro,data=ordered
388`,
389			[]string{"/var/lib/kubelet/pods/f30dc360-5a5d-11e8-962f-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-1fb30a1c"},
390			nil,
391		},
392		{
393			"blockfs",
394			"/mnt/disks/blkvol1",
395			base + `58 25 7:1 / /mnt/disks/blkvol1 rw,relatime shared:38 - ext4 /dev/loop1 rw,data=ordered
396`,
397			nil,
398			nil,
399		},
400		{
401			"blockfs-used-by-one-pod",
402			"/mnt/disks/blkvol1",
403			base + `58 25 7:1 / /mnt/disks/blkvol1 rw,relatime shared:38 - ext4 /dev/loop1 rw,data=ordered
40462 25 7:1 / /var/lib/kubelet/pods/f19fe4e2-5a63-11e8-962f-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-test rw,relatime shared:38 - ext4 /dev/loop1 rw,data=ordered
405`,
406			[]string{"/var/lib/kubelet/pods/f19fe4e2-5a63-11e8-962f-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-test"},
407			nil,
408		},
409		{
410			"blockfs-used-by-two-pods",
411			"/mnt/disks/blkvol1",
412			base + `58 25 7:1 / /mnt/disks/blkvol1 rw,relatime shared:38 - ext4 /dev/loop1 rw,data=ordered
41362 25 7:1 / /var/lib/kubelet/pods/f19fe4e2-5a63-11e8-962f-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-test rw,relatime shared:38 - ext4 /dev/loop1 rw,data=ordered
41495 25 7:1 / /var/lib/kubelet/pods/4854a48b-5a64-11e8-962f-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-test rw,relatime shared:38 - ext4 /dev/loop1 rw,data=ordered
415`,
416			[]string{"/var/lib/kubelet/pods/f19fe4e2-5a63-11e8-962f-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-test",
417				"/var/lib/kubelet/pods/4854a48b-5a64-11e8-962f-000c29bb0377/volumes/kubernetes.io~local-volume/local-pv-test"},
418			nil,
419		},
420	}
421	tmpFile, err := ioutil.TempFile("", "test-get-filetype")
422	if err != nil {
423		t.Fatal(err)
424	}
425	defer os.Remove(tmpFile.Name())
426	defer tmpFile.Close()
427	for _, v := range testcases {
428		tmpFile.Truncate(0)
429		tmpFile.Seek(0, 0)
430		tmpFile.WriteString(v.mountInfos)
431		tmpFile.Sync()
432		refs, err := SearchMountPoints(v.source, tmpFile.Name())
433		if !reflect.DeepEqual(refs, v.expectedRefs) {
434			t.Errorf("test %q: expected Refs: %#v, got %#v", v.name, v.expectedRefs, refs)
435		}
436		if !reflect.DeepEqual(err, v.expectedErr) {
437			t.Errorf("test %q: expected err: %v, got %v", v.name, v.expectedErr, err)
438		}
439	}
440}
441
442func TestSensitiveMountOptions(t *testing.T) {
443	// Arrange
444	testcases := []struct {
445		source           string
446		target           string
447		fstype           string
448		options          []string
449		sensitiveOptions []string
450	}{
451		{
452
453			source:           "mySrc",
454			target:           "myTarget",
455			fstype:           "myFS",
456			options:          []string{"o1", "o2"},
457			sensitiveOptions: []string{"s1", "s2"},
458		},
459		{
460
461			source:           "mySrc",
462			target:           "myTarget",
463			fstype:           "myFS",
464			options:          []string{},
465			sensitiveOptions: []string{"s1", "s2"},
466		},
467		{
468
469			source:           "mySrc",
470			target:           "myTarget",
471			fstype:           "myFS",
472			options:          []string{"o1", "o2"},
473			sensitiveOptions: []string{},
474		},
475	}
476
477	for _, v := range testcases {
478		// Act
479		mountArgs, mountArgsLogStr := MakeMountArgsSensitive(v.source, v.target, v.fstype, v.options, v.sensitiveOptions)
480
481		// Assert
482		t.Logf("\r\nmountArgs =%q\r\nmountArgsLogStr=%q", mountArgs, mountArgsLogStr)
483		for _, option := range v.options {
484			if found := contains(mountArgs, option, t); !found {
485				t.Errorf("Expected option (%q) to exist in returned mountArts (%q), but it does not", option, mountArgs)
486			}
487			if !strings.Contains(mountArgsLogStr, option) {
488				t.Errorf("Expected option (%q) to exist in returned mountArgsLogStr (%q), but it does", option, mountArgsLogStr)
489			}
490		}
491		for _, sensitiveOption := range v.sensitiveOptions {
492			if found := contains(mountArgs, sensitiveOption, t); !found {
493				t.Errorf("Expected sensitiveOption (%q) to exist in returned mountArts (%q), but it does not", sensitiveOption, mountArgs)
494			}
495			if strings.Contains(mountArgsLogStr, sensitiveOption) {
496				t.Errorf("Expected sensitiveOption (%q) to not exist in returned mountArgsLogStr (%q), but it does", sensitiveOption, mountArgsLogStr)
497			}
498		}
499	}
500}
501
502func contains(slice []string, str string, t *testing.T) bool {
503	optionsIndex := -1
504	for i, s := range slice {
505		if s == "-o" {
506			optionsIndex = i + 1
507			break
508		}
509	}
510
511	if optionsIndex < 0 || optionsIndex >= len(slice) {
512		return false
513	}
514
515	return strings.Contains(slice[optionsIndex], str)
516}
517