1// +build !windows
2
3/*
4Copyright 2019 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	"path/filepath"
25	"reflect"
26	"testing"
27)
28
29func writeFile(content string) (string, string, error) {
30	tempDir, err := ioutil.TempDir("", "mounter_shared_test")
31	if err != nil {
32		return "", "", err
33	}
34	filename := filepath.Join(tempDir, "mountinfo")
35	err = ioutil.WriteFile(filename, []byte(content), 0600)
36	if err != nil {
37		os.RemoveAll(tempDir)
38		return "", "", err
39	}
40	return tempDir, filename, nil
41}
42
43func TestParseMountInfo(t *testing.T) {
44	info :=
45		`62 0 253:0 / / rw,relatime shared:1 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
4678 62 0:41 / /tmp rw,nosuid,nodev shared:30 - tmpfs tmpfs rw,seclabel
4780 62 0:42 / /var/lib/nfs/rpc_pipefs rw,relatime shared:31 - rpc_pipefs sunrpc rw
4882 62 0:43 / /var/lib/foo rw,relatime shared:32 - tmpfs tmpfs rw
4983 63 0:44 / /var/lib/bar rw,relatime - tmpfs tmpfs rw
50227 62 253:0 /var/lib/docker/devicemapper /var/lib/docker/devicemapper rw,relatime - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
51224 62 253:0 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered
5276 17 8:1 / /mnt/stateful_partition rw,nosuid,nodev,noexec,relatime - ext4 /dev/sda1 rw,commit=30,data=ordered
5380 17 8:1 /var /var rw,nosuid,nodev,noexec,relatime shared:30 - ext4 /dev/sda1 rw,commit=30,data=ordered
54189 80 8:1 /var/lib/kubelet /var/lib/kubelet rw,relatime shared:30 - ext4 /dev/sda1 rw,commit=30,data=ordered
55818 77 8:40 / /var/lib/kubelet/pods/c25464af-e52e-11e7-ab4d-42010a800002/volumes/kubernetes.io~gce-pd/vol1 rw,relatime shared:290 - ext4 /dev/sdc rw,data=ordered
56819 78 8:48 / /var/lib/kubelet/pods/c25464af-e52e-11e7-ab4d-42010a800002/volumes/kubernetes.io~gce-pd/vol1 rw,relatime shared:290 - ext4 /dev/sdd rw,data=ordered
57900 100 8:48 /dir1 /var/lib/kubelet/pods/c25464af-e52e-11e7-ab4d-42010a800002/volume-subpaths/vol1/subpath1/0 rw,relatime shared:290 - ext4 /dev/sdd rw,data=ordered
58901 101 8:1 /dir1 /var/lib/kubelet/pods/c25464af-e52e-11e7-ab4d-42010a800002/volume-subpaths/vol1/subpath1/1 rw,relatime shared:290 - ext4 /dev/sdd rw,data=ordered
59902 102 8:1 /var/lib/kubelet/pods/d4076f24-e53a-11e7-ba15-42010a800002/volumes/kubernetes.io~empty-dir/vol1/dir1 /var/lib/kubelet/pods/d4076f24-e53a-11e7-ba15-42010a800002/volume-subpaths/vol1/subpath1/0 rw,relatime shared:30 - ext4 /dev/sda1 rw,commit=30,data=ordered
60903 103 8:1 /var/lib/kubelet/pods/d4076f24-e53a-11e7-ba15-42010a800002/volumes/kubernetes.io~empty-dir/vol2/dir1 /var/lib/kubelet/pods/d4076f24-e53a-11e7-ba15-42010a800002/volume-subpaths/vol1/subpath1/1 rw,relatime shared:30 - ext4 /dev/sda1 rw,commit=30,data=ordered
61178 25 253:0 /etc/bar /var/lib/kubelet/pods/12345/volume-subpaths/vol1/subpath1/0 rw,relatime shared:1 - ext4 /dev/sdb2 rw,errors=remount-ro,data=ordered
62698 186 0:41 /tmp1/dir1 /var/lib/kubelet/pods/41135147-e697-11e7-9342-42010a800002/volume-subpaths/vol1/subpath1/0 rw shared:26 - tmpfs tmpfs rw
63918 77 8:50 / /var/lib/kubelet/pods/2345/volumes/kubernetes.io~gce-pd/vol1 rw,relatime shared:290 - ext4 /dev/sdc rw,data=ordered
64919 78 8:58 / /var/lib/kubelet/pods/2345/volumes/kubernetes.io~gce-pd/vol1 rw,relatime shared:290 - ext4 /dev/sdd rw,data=ordered
65920 100 8:50 /dir1 /var/lib/kubelet/pods/2345/volume-subpaths/vol1/subpath1/0 rw,relatime shared:290 - ext4 /dev/sdc rw,data=ordered
66150 23 1:58 / /media/nfs_vol rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
67151 24 1:58 / /media/nfs_bindmount rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs/foo rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
68134 23 0:58 / /var/lib/kubelet/pods/43219158-e5e1-11e7-a392-0e858b8eaf40/volumes/kubernetes.io~nfs/nfs1 rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
69187 23 0:58 / /var/lib/kubelet/pods/1fc5ea21-eff4-11e7-ac80-0e858b8eaf40/volumes/kubernetes.io~nfs/nfs2 rw,relatime shared:96 - nfs4 172.18.4.223:/srv/nfs2 rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
70188 24 0:58 / /var/lib/kubelet/pods/43219158-e5e1-11e7-a392-0e858b8eaf40/volume-subpaths/nfs1/subpath1/0 rw,relatime shared:89 - nfs4 172.18.4.223:/srv/nfs/foo rw,vers=4.0,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.18.4.223,local_lock=none,addr=172.18.4.223
71347 60 0:71 / /var/lib/kubelet/pods/13195d46-f9fa-11e7-bbf1-5254007a695a/volumes/kubernetes.io~nfs/vol2 rw,relatime shared:170 - nfs 172.17.0.3:/exports/2 rw,vers=3,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,mountaddr=172.17.0.3,mountvers=3,mountport=20048,mountproto=udp,local_lock=none,addr=172.17.0.3
72222 24 253:0 /tmp/src /mnt/dst rw,relatime shared:1 - ext4 /dev/mapper/vagrant--vg-root rw,errors=remount-ro,data=ordered
7328 18 0:24 / /sys/fs/cgroup ro,nosuid,nodev,noexec shared:9 - tmpfs tmpfs ro,mode=755
7429 28 0:25 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:10 - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
7531 28 0:27 / /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime shared:13 - cgroup cgroup rw,cpuset
7632 28 0:28 / /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime shared:14 - cgroup cgroup rw,cpu,cpuacct
7733 28 0:29 / /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime shared:15 - cgroup cgroup rw,freezer
7834 28 0:30 / /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime shared:16 - cgroup cgroup rw,net_cls,net_prio
7935 28 0:31 / /sys/fs/cgroup/pids rw,nosuid,nodev,noexec,relatime shared:17 - cgroup cgroup rw,pids
8036 28 0:32 / /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:18 - cgroup cgroup rw,devices
8137 28 0:33 / /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime shared:19 - cgroup cgroup rw,hugetlb
8238 28 0:34 / /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime shared:20 - cgroup cgroup rw,blkio
8339 28 0:35 / /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:21 - cgroup cgroup rw,memory
8440 28 0:36 / /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime shared:22 - cgroup cgroup rw,perf_event
85`
86	tempDir, filename, err := writeFile(info)
87	if err != nil {
88		t.Fatalf("cannot create temporary file: %v", err)
89	}
90	defer os.RemoveAll(tempDir)
91
92	tests := []struct {
93		name         string
94		id           int
95		expectedInfo MountInfo
96	}{
97		{
98			"simple bind mount",
99			189,
100			MountInfo{
101				ID:             189,
102				ParentID:       80,
103				Major:          8,
104				Minor:          1,
105				Root:           "/var/lib/kubelet",
106				Source:         "/dev/sda1",
107				MountPoint:     "/var/lib/kubelet",
108				OptionalFields: []string{"shared:30"},
109				FsType:         "ext4",
110				MountOptions:   []string{"rw", "relatime"},
111				SuperOptions:   []string{"rw", "commit=30", "data=ordered"},
112			},
113		},
114		{
115			"bind mount a directory",
116			222,
117			MountInfo{
118				ID:             222,
119				ParentID:       24,
120				Major:          253,
121				Minor:          0,
122				Root:           "/tmp/src",
123				Source:         "/dev/mapper/vagrant--vg-root",
124				MountPoint:     "/mnt/dst",
125				OptionalFields: []string{"shared:1"},
126				FsType:         "ext4",
127				MountOptions:   []string{"rw", "relatime"},
128				SuperOptions:   []string{"rw", "errors=remount-ro", "data=ordered"},
129			},
130		},
131		{
132			"more than one optional fields",
133			224,
134			MountInfo{
135				ID:             224,
136				ParentID:       62,
137				Major:          253,
138				Minor:          0,
139				Root:           "/var/lib/docker/devicemapper/test/shared",
140				Source:         "/dev/mapper/ssd-root",
141				MountPoint:     "/var/lib/docker/devicemapper/test/shared",
142				OptionalFields: []string{"master:1", "shared:44"},
143				FsType:         "ext4",
144				MountOptions:   []string{"rw", "relatime"},
145				SuperOptions:   []string{"rw", "seclabel", "data=ordered"},
146			},
147		},
148		{
149			"cgroup-mountpoint",
150			28,
151			MountInfo{
152				ID:             28,
153				ParentID:       18,
154				Major:          0,
155				Minor:          24,
156				Root:           "/",
157				Source:         "tmpfs",
158				MountPoint:     "/sys/fs/cgroup",
159				OptionalFields: []string{"shared:9"},
160				FsType:         "tmpfs",
161				MountOptions:   []string{"ro", "nosuid", "nodev", "noexec"},
162				SuperOptions:   []string{"ro", "mode=755"},
163			},
164		},
165		{
166			"cgroup-subsystem-systemd-mountpoint",
167			29,
168			MountInfo{
169				ID:             29,
170				ParentID:       28,
171				Major:          0,
172				Minor:          25,
173				Root:           "/",
174				Source:         "cgroup",
175				MountPoint:     "/sys/fs/cgroup/systemd",
176				OptionalFields: []string{"shared:10"},
177				FsType:         "cgroup",
178				MountOptions:   []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
179				SuperOptions:   []string{"rw", "xattr", "release_agent=/lib/systemd/systemd-cgroups-agent", "name=systemd"},
180			},
181		},
182		{
183			"cgroup-subsystem-cpuset-mountpoint",
184			31,
185			MountInfo{
186				ID:             31,
187				ParentID:       28,
188				Major:          0,
189				Minor:          27,
190				Root:           "/",
191				Source:         "cgroup",
192				MountPoint:     "/sys/fs/cgroup/cpuset",
193				OptionalFields: []string{"shared:13"},
194				FsType:         "cgroup",
195				MountOptions:   []string{"rw", "nosuid", "nodev", "noexec", "relatime"},
196				SuperOptions:   []string{"rw", "cpuset"},
197			},
198		},
199	}
200
201	infos, err := ParseMountInfo(filename)
202	if err != nil {
203		t.Fatalf("Cannot parse %s: %s", filename, err)
204	}
205
206	for _, test := range tests {
207		found := false
208		for _, info := range infos {
209			if info.ID == test.id {
210				found = true
211				if !reflect.DeepEqual(info, test.expectedInfo) {
212					t.Errorf("Test case %q:\n expected: %+v\n got:      %+v", test.name, test.expectedInfo, info)
213				}
214				break
215			}
216		}
217		if !found {
218			t.Errorf("Test case %q: mountPoint %d not found", test.name, test.id)
219		}
220	}
221}
222
223func TestBadParseMountInfo(t *testing.T) {
224	tests := []struct {
225		info         string
226		name         string
227		id           int
228		expectedInfo *MountInfo
229		error        string
230	}{
231		{
232			`224 62 253:0 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered`,
233			"good major:minor field",
234			224,
235			&MountInfo{
236				ID:             224,
237				ParentID:       62,
238				Major:          253,
239				Minor:          0,
240				Root:           "/var/lib/docker/devicemapper/test/shared",
241				Source:         "/dev/mapper/ssd-root",
242				MountPoint:     "/var/lib/docker/devicemapper/test/shared",
243				OptionalFields: []string{"master:1", "shared:44"},
244				FsType:         "ext4",
245				MountOptions:   []string{"rw", "relatime"},
246				SuperOptions:   []string{"rw", "seclabel", "data=ordered"},
247			},
248			"",
249		},
250		{
251			`224 62 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered`,
252			"missing major:minor field",
253			224,
254			nil,
255			`parsing '224 62 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered' failed: unexpected minor:major pair [/var/lib/docker/devicemapper/test/shared]`,
256		},
257		{
258			`224 62 :0 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered`,
259			"empty major field",
260			224,
261			nil,
262			`parsing '' failed: unable to parse major device id, err:strconv.Atoi: parsing "": invalid syntax`,
263		},
264		{
265			`224 62 253: /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered`,
266			"empty minor field",
267			224,
268			nil,
269			`parsing '' failed: unable to parse minor device id, err:strconv.Atoi: parsing "": invalid syntax`,
270		},
271		{
272			`224 62 foo:0 /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered`,
273			"alphabet in major field",
274			224,
275			nil,
276			`parsing 'foo' failed: unable to parse major device id, err:strconv.Atoi: parsing "foo": invalid syntax`,
277		},
278		{
279			`224 62 253:bar /var/lib/docker/devicemapper/test/shared /var/lib/docker/devicemapper/test/shared rw,relatime master:1 shared:44 - ext4 /dev/mapper/ssd-root rw,seclabel,data=ordered`,
280			"alphabet in minor field",
281			224,
282			nil,
283			`parsing 'bar' failed: unable to parse minor device id, err:strconv.Atoi: parsing "bar": invalid syntax`,
284		},
285	}
286
287	for _, test := range tests {
288		tempDir, filename, err := writeFile(test.info)
289		if err != nil {
290			t.Fatalf("cannot create temporary file: %v", err)
291		}
292		defer os.RemoveAll(tempDir)
293
294		infos, err := ParseMountInfo(filename)
295		if err != nil {
296			if err.Error() != test.error {
297				t.Errorf("Test case %q:\n expected error: %+v\n got:      %+v", test.name, test.error, err.Error())
298			}
299			continue
300		}
301
302		found := false
303		for _, info := range infos {
304			if info.ID == test.id {
305				found = true
306				if !reflect.DeepEqual(info, *test.expectedInfo) {
307					t.Errorf("Test case %q:\n expected: %+v\n got:      %+v", test.name, test.expectedInfo, info)
308				}
309				break
310			}
311		}
312		if !found {
313			t.Errorf("Test case %q: mountPoint %d not found", test.name, test.id)
314		}
315	}
316}
317