github.com/openebs/node-disk-manager@v1.9.1-0.20230225014141-4531f06ffa1e/pkg/mount/mountutil_test.go (about)

     1  /*
     2  Copyright 2019 OpenEBS Authors.
     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 mount
    18  
    19  import (
    20  	"errors"
    21  	"io/ioutil"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/stretchr/testify/assert"
    28  )
    29  
    30  const (
    31  	getMountName = iota
    32  	getPartitionName
    33  )
    34  
    35  func TestNewMountUtil(t *testing.T) {
    36  	filePath := "/host/proc/1/mounts"
    37  	devPath := "/dev/sda"
    38  	mountPoint := "/home"
    39  	// TODO
    40  	expectedMountUtil1 := DiskMountUtil{
    41  		filePath: filePath,
    42  		devPath:  devPath,
    43  	}
    44  	expectedMountUtil2 := DiskMountUtil{
    45  		filePath:   filePath,
    46  		mountPoint: mountPoint,
    47  	}
    48  
    49  	tests := map[string]struct {
    50  		actualMU   DiskMountUtil
    51  		expectedMU DiskMountUtil
    52  	}{
    53  		"test for generated mount util with devPath":    {NewMountUtil(filePath, devPath, ""), expectedMountUtil1},
    54  		"test for generated mount util with mountpoint": {NewMountUtil(filePath, "", mountPoint), expectedMountUtil2},
    55  	}
    56  
    57  	for name, test := range tests {
    58  		t.Run(name, func(t *testing.T) {
    59  			assert.Equal(t, test.expectedMU, test.actualMU)
    60  		})
    61  	}
    62  }
    63  
    64  func TestGetMountAttr(t *testing.T) {
    65  	filePath := "/tmp/data"
    66  	fileContent1 := []byte("/dev/sda4 / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0")
    67  	fileContent2 := []byte("/dev/sda3 /home ext4 rw,relatime,errors=remount-ro,data=ordered 0 0")
    68  	fileContent3 := []byte("sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0")
    69  	fileContent4 := []byte(`/dev/sda3 /home ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
    70  /dev/sda3 /usr ext4 rw,relatime,errors=remount-ro,data=ordered 0 0`)
    71  
    72  	mountAttrTests := map[string]struct {
    73  		devPath           string
    74  		mountPoint        string
    75  		attrFunc          int
    76  		expectedMountAttr DeviceMountAttr
    77  		expectedError     error
    78  		fileContent       []byte
    79  	}{
    80  		"sda4 mounted at /": {
    81  			"/dev/sda4",
    82  			"",
    83  			getMountName,
    84  			DeviceMountAttr{MountPoint: []string{"/"}, FileSystem: "ext4"},
    85  			nil,
    86  			fileContent1,
    87  		},
    88  		"sda3 mounted at /home": {
    89  			"/dev/sda3",
    90  			"",
    91  			getMountName,
    92  			DeviceMountAttr{MountPoint: []string{"/home"}, FileSystem: "ext4"},
    93  			nil,
    94  			fileContent2,
    95  		},
    96  		"device is not mounted": {
    97  			"/dev/sda3",
    98  			"",
    99  			getMountName,
   100  			DeviceMountAttr{},
   101  			errors.New("could not get device mount attributes, Path/MountPoint not present in mounts file"),
   102  			fileContent3,
   103  		},
   104  		"sda3 mounted at /home and /usr": {
   105  			"/dev/sda3",
   106  			"",
   107  			getMountName,
   108  			DeviceMountAttr{MountPoint: []string{"/home", "/usr"}, FileSystem: "ext4"},
   109  			nil,
   110  			fileContent4,
   111  		},
   112  		"Mountpoint /": {
   113  			"",
   114  			"/",
   115  			getPartitionName,
   116  			DeviceMountAttr{DevPath: "sda4"},
   117  			nil,
   118  			fileContent1,
   119  		},
   120  		"Mountpoint /home": {
   121  			"",
   122  			"/home",
   123  			getPartitionName,
   124  			DeviceMountAttr{DevPath: "sda3"},
   125  			nil,
   126  			fileContent2,
   127  		},
   128  		"Mountpoint not found": {
   129  			"",
   130  			"/usr",
   131  			getPartitionName,
   132  			DeviceMountAttr{},
   133  			errors.New("could not get device mount attributes, Path/MountPoint not present in mounts file"),
   134  			fileContent2,
   135  		},
   136  		"Mountpoint found but device not /dev/*": {
   137  			"",
   138  			"/sys",
   139  			getPartitionName,
   140  			DeviceMountAttr{},
   141  			errors.New("could not get device mount attributes, Path/MountPoint not present in mounts file"),
   142  			fileContent3,
   143  		},
   144  		"Mountpoint /home, /dev/sda3 mounted twice": {
   145  			"",
   146  			"/home",
   147  			getPartitionName,
   148  			DeviceMountAttr{DevPath: "sda3"},
   149  			nil,
   150  			fileContent4,
   151  		},
   152  		"Mountpoint /usr, /dev/sda3 mounted twice": {
   153  			"",
   154  			"/usr",
   155  			getPartitionName,
   156  			DeviceMountAttr{DevPath: "sda3"},
   157  			nil,
   158  			fileContent4,
   159  		},
   160  	}
   161  	for name, test := range mountAttrTests {
   162  		t.Run(name, func(t *testing.T) {
   163  			var fn getMountData
   164  			mountUtil := NewMountUtil(filePath, test.devPath, test.mountPoint)
   165  
   166  			// create the temp file which will be read for getting attributes
   167  			err := ioutil.WriteFile(filePath, test.fileContent, 0644)
   168  			if err != nil {
   169  				t.Fatal(err)
   170  			}
   171  
   172  			switch test.attrFunc {
   173  			case getMountName:
   174  				fn = mountUtil.getMountName
   175  			case getPartitionName:
   176  				fn = mountUtil.getPartitionName
   177  			}
   178  			mountAttr, err := mountUtil.getDeviceMountAttr(fn)
   179  
   180  			assert.Equal(t, test.expectedMountAttr, mountAttr)
   181  			assert.Equal(t, test.expectedError, err)
   182  
   183  			// remove the temp file
   184  			os.Remove(filePath)
   185  		})
   186  	}
   187  
   188  	// invalid path mountAttrTests
   189  	mountUtil := NewMountUtil(filePath, "/dev/sda3", "")
   190  	_, err := mountUtil.getDeviceMountAttr(mountUtil.getMountName)
   191  	assert.NotNil(t, err)
   192  }
   193  
   194  func TestGetPartitionName(t *testing.T) {
   195  	mountLine := "/dev/sda4 /home ext4 rw,relatime,errors=remount-ro,data=ordered 0 0"
   196  	mountPoint1 := "/home"
   197  	mountPoint2 := "/"
   198  	tests := map[string]struct {
   199  		expectedAttr DeviceMountAttr
   200  		expectedOk   bool
   201  		mountPoint   string
   202  		line         string
   203  	}{
   204  		"mount point is present in line":     {DeviceMountAttr{DevPath: "sda4"}, true, mountPoint1, mountLine},
   205  		"mount point is not present in line": {DeviceMountAttr{}, false, mountPoint2, mountLine},
   206  		"mountline is empty":                 {DeviceMountAttr{}, false, mountPoint2, ""},
   207  	}
   208  
   209  	for name, test := range tests {
   210  		t.Run(name, func(t *testing.T) {
   211  			mountPointUtil := NewMountUtil("", "", test.mountPoint)
   212  			mountAttr, ok := mountPointUtil.getPartitionName(test.line)
   213  			assert.Equal(t, test.expectedAttr, mountAttr)
   214  			assert.Equal(t, test.expectedOk, ok)
   215  		})
   216  	}
   217  }
   218  
   219  func TestGetMountName(t *testing.T) {
   220  	mountLine := "/dev/sda4 /home ext4 rw,relatime,errors=remount-ro,data=ordered 0 0"
   221  	devPath1 := "/dev/sda4"
   222  	devPath2 := "/dev/sda3"
   223  	fsType := "ext4"
   224  	mountPoint := "/home"
   225  	tests := map[string]struct {
   226  		expectedMountAttr DeviceMountAttr
   227  		expectedOk        bool
   228  		devPath           string
   229  		line              string
   230  	}{
   231  		"device sda4 is mounted":     {DeviceMountAttr{MountPoint: []string{mountPoint}, FileSystem: fsType}, true, devPath1, mountLine},
   232  		"device sda3 is not mounted": {DeviceMountAttr{}, false, devPath2, mountLine},
   233  		"mount line is empty":        {DeviceMountAttr{}, false, devPath2, ""},
   234  	}
   235  
   236  	for name, test := range tests {
   237  		t.Run(name, func(t *testing.T) {
   238  			mountPointUtil := NewMountUtil("", test.devPath, "")
   239  			attr, ok := mountPointUtil.getMountName(test.line)
   240  			assert.Equal(t, test.expectedMountAttr, attr)
   241  			assert.Equal(t, test.expectedOk, ok)
   242  		})
   243  	}
   244  }
   245  
   246  func TestOsDiskPath(t *testing.T) {
   247  	filePath := "/proc/self/mounts"
   248  	mountPointUtil := NewMountUtil(filePath, "", "/")
   249  	path, err := mountPointUtil.GetDiskPath()
   250  	tests := map[string]struct {
   251  		actualPath    string
   252  		actualError   error
   253  		expectedError error
   254  	}{
   255  		"test case for os disk path": {actualPath: path, actualError: err, expectedError: nil},
   256  	}
   257  	for name, test := range tests {
   258  		t.Run(name, func(t *testing.T) {
   259  			_, err := filepath.EvalSymlinks(test.actualPath)
   260  			if err != nil {
   261  				t.Error(err)
   262  			}
   263  			assert.Equal(t, test.expectedError, test.actualError)
   264  		})
   265  	}
   266  }
   267  
   268  func TestGetParentBlockDevice(t *testing.T) {
   269  	tests := map[string]struct {
   270  		syspath                   string
   271  		expectedParentBlockDevice string
   272  		expectedOk                bool
   273  	}{
   274  		"getting parent of a main blockdevice itself": {
   275  			syspath:                   "/sys/devices/pci0000:00/0000:00:0d.0/ata1/host0/target0:0:0/0:0:0:0/block/sda",
   276  			expectedParentBlockDevice: "sda",
   277  			expectedOk:                true,
   278  		},
   279  		"getting parent of a partition": {
   280  			syspath:                   "/sys/devices/pci0000:00/0000:00:0d.0/ata1/host0/target0:0:0/0:0:0:0/block/sda/sda1",
   281  			expectedParentBlockDevice: "sda",
   282  			expectedOk:                true,
   283  		},
   284  		"getting parent of main NVMe blockdevice": {
   285  			syspath:                   "/sys/devices/pci0000:00/0000:00:0e.0/nvme/nvme0/nvme0n1",
   286  			expectedParentBlockDevice: "nvme0n1",
   287  			expectedOk:                true,
   288  		},
   289  		"getting parent of partitioned NVMe blockdevice": {
   290  			syspath:                   "/sys/devices/pci0000:00/0000:00:0e.0/nvme/nvme0/nvme0n1/nvme0n1p1",
   291  			expectedParentBlockDevice: "nvme0n1",
   292  			expectedOk:                true,
   293  		},
   294  		"getting parent of main virtual NVMe blockdevice": {
   295  			syspath:                   "/sys/devices/virtual/nvme-subsystem/nvme-subsys0/nvme0n1",
   296  			expectedParentBlockDevice: "nvme0n1",
   297  			expectedOk:                true,
   298  		},
   299  		"getting parent of partitioned virtual NVMe blockdevice": {
   300  			syspath:                   "/sys/devices/virtual/nvme-subsystem/nvme-subsys0/nvme0n1/nvme0n1p1",
   301  			expectedParentBlockDevice: "nvme0n1",
   302  			expectedOk:                true,
   303  		},
   304  		"getting parent of wrong disk": {
   305  			syspath:                   "/sys/devices/pci0000:00/0000:00:0e.0/nvme/nvme0",
   306  			expectedParentBlockDevice: "",
   307  			expectedOk:                false,
   308  		},
   309  		"giving a wrong syspath": {
   310  			syspath:                   "/sys/devices/pci0000:00/0000:00:0e.0",
   311  			expectedParentBlockDevice: "",
   312  			expectedOk:                false,
   313  		},
   314  	}
   315  
   316  	for name, test := range tests {
   317  		t.Run(name, func(t *testing.T) {
   318  			parentBlockDevice, ok := getParentBlockDevice(test.syspath)
   319  			assert.Equal(t, test.expectedParentBlockDevice, parentBlockDevice)
   320  			assert.Equal(t, test.expectedOk, ok)
   321  		})
   322  	}
   323  }
   324  
   325  func TestParseRootDeviceLink(t *testing.T) {
   326  	tests := map[string]struct {
   327  		content       string
   328  		expectedPath  string
   329  		expectedError error
   330  	}{
   331  		"empty content": {
   332  			"",
   333  			"",
   334  			ErrCouldNotFindRootDevice,
   335  		},
   336  		"single line with root only": {
   337  			"root=UUID=d41162ba-25e4-4c44-8793-2abef96d27e9",
   338  			"/dev/disk/by-uuid/d41162ba-25e4-4c44-8793-2abef96d27e9",
   339  			nil,
   340  		},
   341  		"single line with multiple attributes": {
   342  			"BOOT_IMAGE=/boot/vmlinuz-5.4.0-48-generic root=UUID=d41162ba-25e4-4c44-8793-2abef96d27e9 ro intel_iommu=on quiet splash vt.handoff=7",
   343  			"/dev/disk/by-uuid/d41162ba-25e4-4c44-8793-2abef96d27e9",
   344  			nil,
   345  		},
   346  		"single line without root attribute": {
   347  			"BOOT_IMAGE=/boot/vmlinuz-5.4.0-48-generic ro intel_iommu=on quiet splash vt.handoff=7",
   348  			"",
   349  			ErrCouldNotFindRootDevice,
   350  		},
   351  		"multi line with multiple attributes": {
   352  			"\n\nBOOT_IMAGE=/boot/vmlinuz-5.4.0-48-generic root=PARTUUID=325c5bfa-08a8-433c-bc62-2dd5255213fd ro\n",
   353  			"/dev/disk/by-partuuid/325c5bfa-08a8-433c-bc62-2dd5255213fd",
   354  			nil,
   355  		},
   356  		"single line with root on dm device, (simulates cmdline in GKE)": {
   357  			"BOOT_IMAGE=/syslinux/vmlinuz.A root=/dev/dm-0",
   358  			"/dev/dm-0",
   359  			nil,
   360  		},
   361  	}
   362  
   363  	for name, test := range tests {
   364  		t.Run(name, func(t *testing.T) {
   365  
   366  			actualPath, actualError := parseRootDeviceLink(strings.NewReader(test.content))
   367  
   368  			assert.Equal(t, test.expectedError, actualError)
   369  
   370  			if actualError == nil {
   371  				assert.Equal(t, test.expectedPath, actualPath)
   372  			}
   373  		})
   374  	}
   375  }
   376  
   377  func TestGetSoftLinkForPartition(t *testing.T) {
   378  	tests := map[string]string{
   379  		"sda1":    "/sys/class/block/sda1",
   380  		"nvme0n1": "/sys/class/block/nvme0n1",
   381  	}
   382  
   383  	for partition, expectedSoftlink := range tests {
   384  		t.Run(partition, func(t *testing.T) {
   385  			actualSoftLink, actualError := getSoftLinkForPartition(partition)
   386  			assert.NoError(t, actualError)
   387  			assert.Equal(t, expectedSoftlink, actualSoftLink)
   388  		})
   389  	}
   390  
   391  	t.Run("root", func(t *testing.T) {
   392  		actualSoftLink, actualError := getSoftLinkForPartition("root")
   393  		assert.NoError(t, actualError)
   394  		assert.NotEqual(t, "/sys/class/block/root", actualSoftLink)
   395  		assert.True(t, strings.HasPrefix(actualSoftLink, "/sys/class/block/"))
   396  	})
   397  }
   398  
   399  func TestGetDiskDevPath_WithRoot(t *testing.T) {
   400  	path, err := getPartitionDevPath("root")
   401  
   402  	assert.NoError(t, err)
   403  	assert.True(t, strings.HasPrefix(path, "/dev/"))
   404  }
   405  
   406  func TestMergeDeviceMountAttrs(t *testing.T) {
   407  	tests := map[string]struct {
   408  		first    DeviceMountAttr
   409  		second   DeviceMountAttr
   410  		expected DeviceMountAttr
   411  	}{
   412  		"First empty, second has DevPath only": {
   413  			first:    DeviceMountAttr{},
   414  			second:   DeviceMountAttr{DevPath: "/dev/sda3"},
   415  			expected: DeviceMountAttr{DevPath: "/dev/sda3"},
   416  		},
   417  		"First has DevPath, does not match with second's DevPath": {
   418  			first: DeviceMountAttr{DevPath: "/dev/sda3"},
   419  			second: DeviceMountAttr{
   420  				DevPath:    "/dev/sda4",
   421  				MountPoint: []string{"/home"},
   422  				FileSystem: "ext4",
   423  			},
   424  			expected: DeviceMountAttr{DevPath: "/dev/sda3"},
   425  		},
   426  		"DevPaths match, FileSystem empty in first only": {
   427  			first: DeviceMountAttr{
   428  				DevPath: "/dev/sda3",
   429  			},
   430  			second: DeviceMountAttr{
   431  				DevPath:    "/dev/sda3",
   432  				MountPoint: []string{"/"},
   433  				FileSystem: "ext4",
   434  			},
   435  			expected: DeviceMountAttr{
   436  				DevPath:    "/dev/sda3",
   437  				MountPoint: []string{"/"},
   438  				FileSystem: "ext4",
   439  			},
   440  		},
   441  		"DevPaths match, FileSystems don't match": {
   442  			first: DeviceMountAttr{
   443  				DevPath:    "/dev/sda3",
   444  				MountPoint: []string{"/"},
   445  				FileSystem: "ext3",
   446  			},
   447  			second: DeviceMountAttr{
   448  				DevPath:    "/dev/sda3",
   449  				MountPoint: []string{"/home"},
   450  				FileSystem: "ext4",
   451  			},
   452  			expected: DeviceMountAttr{
   453  				DevPath:    "/dev/sda3",
   454  				MountPoint: []string{"/"},
   455  				FileSystem: "ext3",
   456  			},
   457  		},
   458  		"Both DevPath and FileSystem match, FileSystem empty": {
   459  			first: DeviceMountAttr{
   460  				DevPath:    "/dev/sda3",
   461  				MountPoint: []string{"/"},
   462  				FileSystem: "",
   463  			},
   464  			second: DeviceMountAttr{
   465  				DevPath:    "/dev/sda3",
   466  				MountPoint: []string{"/home"},
   467  				FileSystem: "",
   468  			},
   469  			expected: DeviceMountAttr{
   470  				DevPath:    "/dev/sda3",
   471  				MountPoint: []string{"/"},
   472  				FileSystem: "",
   473  			},
   474  		},
   475  		"Both DevPath and FileSystem match, FileSystem non-empty": {
   476  			first: DeviceMountAttr{
   477  				DevPath:    "/dev/sda3",
   478  				MountPoint: []string{"/"},
   479  				FileSystem: "ext4",
   480  			},
   481  			second: DeviceMountAttr{
   482  				DevPath:    "/dev/sda3",
   483  				MountPoint: []string{"/home"},
   484  				FileSystem: "ext4",
   485  			},
   486  			expected: DeviceMountAttr{
   487  				DevPath:    "/dev/sda3",
   488  				MountPoint: []string{"/", "/home"},
   489  				FileSystem: "ext4",
   490  			},
   491  		},
   492  		"Both DevPaths empty, FileSystems match and non-empty": {
   493  			first: DeviceMountAttr{
   494  				DevPath:    "",
   495  				MountPoint: []string{"/"},
   496  				FileSystem: "ext4",
   497  			},
   498  			second: DeviceMountAttr{
   499  				DevPath:    "",
   500  				MountPoint: []string{"/home"},
   501  				FileSystem: "ext4",
   502  			},
   503  			expected: DeviceMountAttr{
   504  				DevPath:    "",
   505  				MountPoint: []string{"/", "/home"},
   506  				FileSystem: "ext4",
   507  			},
   508  		},
   509  	}
   510  
   511  	for name, test := range tests {
   512  		t.Run(name, func(t *testing.T) {
   513  			mergeDeviceMountAttrs(&test.first, &test.second)
   514  			assert.Equal(t, test.first, test.expected)
   515  		})
   516  	}
   517  }