k8s.io/kubernetes@v1.29.3/pkg/volume/util/device_util_linux_test.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  /*
     5  Copyright 2016 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package util
    21  
    22  import (
    23  	"errors"
    24  	"os"
    25  	"reflect"
    26  	"regexp"
    27  	"testing"
    28  	"time"
    29  )
    30  
    31  type mockOsIOHandler struct{}
    32  
    33  func (handler *mockOsIOHandler) ReadFile(filename string) ([]byte, error) {
    34  	portPattern := regexp.MustCompile("^/sys/class/iscsi_host/(host\\d)/device/session\\d/connection\\d:0/iscsi_connection/connection\\d:0/(?:persistent_)?port$")
    35  	if portPattern.MatchString(filename) {
    36  		return []byte("3260"), nil
    37  	}
    38  	addressPattern := regexp.MustCompile("^/sys/class/iscsi_host/(host\\d)/device/session\\d/connection\\d:0/iscsi_connection/connection\\d:0/(?:persistent_)?address$")
    39  	matches := addressPattern.FindStringSubmatch(filename)
    40  	if nil != matches {
    41  		switch matches[1] {
    42  		case "host2":
    43  			return []byte("10.0.0.1"), nil
    44  		case "host3":
    45  			return []byte("10.0.0.2"), nil
    46  		}
    47  	}
    48  	targetNamePattern := regexp.MustCompile("^/sys/class/iscsi_host/(host\\d)/device/session\\d/iscsi_session/session\\d/targetname$")
    49  	matches = targetNamePattern.FindStringSubmatch(filename)
    50  	if nil != matches {
    51  		switch matches[1] {
    52  		case "host2":
    53  			return []byte("target1"), nil
    54  		case "host3":
    55  			return []byte("target2"), nil
    56  		}
    57  	}
    58  	return nil, errors.New("not Implemented for Mock")
    59  }
    60  
    61  func (handler *mockOsIOHandler) ReadDir(dirname string) ([]os.FileInfo, error) {
    62  	switch dirname {
    63  	case "/sys/block/dm-1/slaves":
    64  		f1 := &fakeFileInfo{
    65  			name: "sda",
    66  		}
    67  		f2 := &fakeFileInfo{
    68  			name: "sdb",
    69  		}
    70  		return []os.FileInfo{f1, f2}, nil
    71  	case "/sys/block/":
    72  		f1 := &fakeFileInfo{
    73  			name: "sda",
    74  		}
    75  		f2 := &fakeFileInfo{
    76  			name: "dm-1",
    77  		}
    78  		return []os.FileInfo{f1, f2}, nil
    79  	case "/sys/class/iscsi_host":
    80  		f1 := &fakeFileInfo{
    81  			name: "host2",
    82  		}
    83  		f2 := &fakeFileInfo{
    84  			name: "host3",
    85  		}
    86  		f3 := &fakeFileInfo{
    87  			name: "ignore",
    88  		}
    89  		return []os.FileInfo{f1, f2, f3}, nil
    90  	case "/sys/class/iscsi_host/host2/device":
    91  		f1 := &fakeFileInfo{
    92  			name: "session1",
    93  		}
    94  		f2 := &fakeFileInfo{
    95  			name: "ignore",
    96  		}
    97  		return []os.FileInfo{f1, f2}, nil
    98  	case "/sys/class/iscsi_host/host3/device":
    99  		f1 := &fakeFileInfo{
   100  			name: "session2",
   101  		}
   102  		f2 := &fakeFileInfo{
   103  			name: "ignore",
   104  		}
   105  		return []os.FileInfo{f1, f2}, nil
   106  	case "/sys/class/iscsi_host/host2/device/session1":
   107  		f1 := &fakeFileInfo{
   108  			name: "connection1:0",
   109  		}
   110  		f2 := &fakeFileInfo{
   111  			name: "ignore",
   112  		}
   113  		return []os.FileInfo{f1, f2}, nil
   114  	case "/sys/class/iscsi_host/host3/device/session2":
   115  		f1 := &fakeFileInfo{
   116  			name: "connection2:0",
   117  		}
   118  		f2 := &fakeFileInfo{
   119  			name: "ignore",
   120  		}
   121  		return []os.FileInfo{f1, f2}, nil
   122  	case "/sys/class/iscsi_host/host2/device/session1/target2:0:0/2:0:0:1/block":
   123  		f1 := &fakeFileInfo{
   124  			name: "sda",
   125  		}
   126  		return []os.FileInfo{f1}, nil
   127  	case "/sys/class/iscsi_host/host2/device/session1/target2:0:0/2:0:0:2/block":
   128  		f1 := &fakeFileInfo{
   129  			name: "sdc",
   130  		}
   131  		return []os.FileInfo{f1}, nil
   132  	case "/sys/class/iscsi_host/host3/device/session2/target3:0:0/3:0:0:1/block":
   133  		f1 := &fakeFileInfo{
   134  			name: "sdb",
   135  		}
   136  		return []os.FileInfo{f1}, nil
   137  	case "/sys/class/iscsi_host/host3/device/session2/target3:0:0/3:0:0:2/block":
   138  		f1 := &fakeFileInfo{
   139  			name: "sdd",
   140  		}
   141  		return []os.FileInfo{f1}, nil
   142  	}
   143  	return nil, errors.New("not Implemented for Mock")
   144  }
   145  
   146  func (handler *mockOsIOHandler) Lstat(name string) (os.FileInfo, error) {
   147  	links := map[string]string{
   148  		"/sys/block/dm-1/slaves/sda": "sda",
   149  		"/dev/sda":                   "sda",
   150  		"/sys/class/iscsi_host/host2/device/session1/target2:0:0/2:0:0:1": "2:0:0:1",
   151  		"/sys/class/iscsi_host/host2/device/session1/target2:0:0/2:0:0:2": "2:0:0:2",
   152  		"/sys/class/iscsi_host/host3/device/session2/target3:0:0/3:0:0:1": "3:0:0:1",
   153  		"/sys/class/iscsi_host/host3/device/session2/target3:0:0/3:0:0:2": "3:0:0:2",
   154  	}
   155  	if dev, ok := links[name]; ok {
   156  		return &fakeFileInfo{name: dev}, nil
   157  	}
   158  	return nil, errors.New("not Implemented for Mock")
   159  }
   160  
   161  func (handler *mockOsIOHandler) EvalSymlinks(path string) (string, error) {
   162  	links := map[string]string{
   163  		"/returns/a/dev":   "/dev/sde",
   164  		"/returns/non/dev": "/sys/block",
   165  		"/dev/disk/by-path/127.0.0.1:3260-eui.02004567A425678D-lun-0": "/dev/sda",
   166  		"/dev/disk/by-path/127.0.0.3:3260-eui.03004567A425678D-lun-0": "/dev/sdb",
   167  		"/dev/dm-2": "/dev/dm-2",
   168  		"/dev/dm-3": "/dev/dm-3",
   169  		"/dev/sdc":  "/dev/sdc",
   170  		"/dev/sde":  "/dev/sde",
   171  	}
   172  	return links[path], nil
   173  }
   174  
   175  func (handler *mockOsIOHandler) WriteFile(filename string, data []byte, perm os.FileMode) error {
   176  	return errors.New("not Implemented for Mock")
   177  }
   178  
   179  type fakeFileInfo struct {
   180  	name string
   181  }
   182  
   183  func (fi *fakeFileInfo) Name() string {
   184  	return fi.name
   185  }
   186  
   187  func (fi *fakeFileInfo) Size() int64 {
   188  	return 0
   189  }
   190  
   191  func (fi *fakeFileInfo) Mode() os.FileMode {
   192  	return 777
   193  }
   194  
   195  func (fi *fakeFileInfo) ModTime() time.Time {
   196  	return time.Now()
   197  }
   198  func (fi *fakeFileInfo) IsDir() bool {
   199  	return false
   200  }
   201  
   202  func (fi *fakeFileInfo) Sys() interface{} {
   203  	return nil
   204  }
   205  
   206  func TestFindMultipathDeviceForDevice(t *testing.T) {
   207  	mockDeviceUtil := NewDeviceHandler(&mockOsIOHandler{})
   208  	dev := mockDeviceUtil.FindMultipathDeviceForDevice("/dev/disk/by-path/127.0.0.1:3260-eui.02004567A425678D-lun-0")
   209  	if dev != "/dev/dm-1" {
   210  		t.Fatalf("mpio device not found dm-1 expected got [%s]", dev)
   211  	}
   212  	dev = mockDeviceUtil.FindMultipathDeviceForDevice("/dev/disk/by-path/empty")
   213  	if dev != "" {
   214  		t.Fatalf("mpio device not found '' expected got [%s]", dev)
   215  	}
   216  }
   217  
   218  func TestFindDeviceForPath(t *testing.T) {
   219  	io := &mockOsIOHandler{}
   220  
   221  	disk, err := findDeviceForPath("/dev/sde", io)
   222  	if err != nil {
   223  		t.Fatalf("error finding device for path /dev/sde:%v", err)
   224  	}
   225  	if disk != "sde" {
   226  		t.Fatalf("disk [%s] didn't match expected sde", disk)
   227  	}
   228  	disk, err = findDeviceForPath("/returns/a/dev", io)
   229  	if err != nil {
   230  		t.Fatalf("error finding device for path /returns/a/dev:%v", err)
   231  	}
   232  	if disk != "sde" {
   233  		t.Fatalf("disk [%s] didn't match expected sde", disk)
   234  	}
   235  	_, err = findDeviceForPath("/returns/non/dev", io)
   236  	if err == nil {
   237  		t.Fatalf("link is to incorrect dev")
   238  	}
   239  
   240  	_, err = findDeviceForPath("/path/doesnt/exist", &osIOHandler{})
   241  	if err == nil {
   242  		t.Fatalf("path shouldn't exist but still doesn't give an error")
   243  	}
   244  
   245  }
   246  
   247  func TestFindSlaveDevicesOnMultipath(t *testing.T) {
   248  	mockDeviceUtil := NewDeviceHandler(&mockOsIOHandler{})
   249  	devices := mockDeviceUtil.FindSlaveDevicesOnMultipath("/dev/dm-1")
   250  	if !reflect.DeepEqual(devices, []string{"/dev/sda", "/dev/sdb"}) {
   251  		t.Fatalf("failed to find devices managed by mpio device. /dev/sda, /dev/sdb expected got [%s]", devices)
   252  	}
   253  	dev := mockDeviceUtil.FindSlaveDevicesOnMultipath("/dev/sdc")
   254  	if len(dev) != 0 {
   255  		t.Fatalf("mpio device not found '' expected got [%s]", dev)
   256  	}
   257  }
   258  
   259  func TestGetISCSIPortalHostMapForTarget(t *testing.T) {
   260  	mockDeviceUtil := NewDeviceHandler(&mockOsIOHandler{})
   261  	portalHostMap, err := mockDeviceUtil.GetISCSIPortalHostMapForTarget("target1")
   262  	if err != nil {
   263  		t.Fatalf("error getting scsi hosts for target: %v", err)
   264  	}
   265  	if portalHostMap == nil {
   266  		t.Fatal("no portal host map returned")
   267  	}
   268  	if len(portalHostMap) != 1 {
   269  		t.Fatalf("wrong number of map entries in portal host map: %d", len(portalHostMap))
   270  	}
   271  	if portalHostMap["10.0.0.1:3260"] != 2 {
   272  		t.Fatalf("incorrect entry in portal host map: %v", portalHostMap)
   273  	}
   274  }
   275  
   276  func TestFindDevicesForISCSILun(t *testing.T) {
   277  	mockDeviceUtil := NewDeviceHandler(&mockOsIOHandler{})
   278  	devices, err := mockDeviceUtil.FindDevicesForISCSILun("target1", 1)
   279  	if err != nil {
   280  		t.Fatalf("error getting devices for lun: %v", err)
   281  	}
   282  	if devices == nil {
   283  		t.Fatal("no devices returned")
   284  	}
   285  	if len(devices) != 1 {
   286  		t.Fatalf("wrong number of devices: %d", len(devices))
   287  	}
   288  	if devices[0] != "sda" {
   289  		t.Fatalf("incorrect device %v", devices)
   290  	}
   291  }