k8s.io/kubernetes@v1.29.3/pkg/volume/util/device_util_linux.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 "fmt" 25 "net" 26 "os" 27 "path/filepath" 28 "strconv" 29 "strings" 30 31 "k8s.io/klog/v2" 32 ) 33 34 // FindMultipathDeviceForDevice given a device name like /dev/sdx, find the devicemapper parent 35 func (handler *deviceHandler) FindMultipathDeviceForDevice(device string) string { 36 io := handler.getIo 37 disk, err := findDeviceForPath(device, io) 38 if err != nil { 39 return "" 40 } 41 sysPath := "/sys/block/" 42 if dirs, err := io.ReadDir(sysPath); err == nil { 43 for _, f := range dirs { 44 name := f.Name() 45 if strings.HasPrefix(name, "dm-") { 46 if _, err1 := io.Lstat(sysPath + name + "/slaves/" + disk); err1 == nil { 47 return "/dev/" + name 48 } 49 } 50 } 51 } 52 return "" 53 } 54 55 // findDeviceForPath Find the underlying disk for a linked path such as /dev/disk/by-path/XXXX or /dev/mapper/XXXX 56 // will return sdX or hdX etc, if /dev/sdX is passed in then sdX will be returned 57 func findDeviceForPath(path string, io IoUtil) (string, error) { 58 devicePath, err := io.EvalSymlinks(path) 59 if err != nil { 60 return "", err 61 } 62 // if path /dev/hdX split into "", "dev", "hdX" then we will 63 // return just the last part 64 parts := strings.Split(devicePath, "/") 65 if len(parts) == 3 && strings.HasPrefix(parts[1], "dev") { 66 return parts[2], nil 67 } 68 return "", errors.New("Illegal path for device " + devicePath) 69 } 70 71 // FindSlaveDevicesOnMultipath given a dm name like /dev/dm-1, find all devices 72 // which are managed by the devicemapper dm-1. 73 func (handler *deviceHandler) FindSlaveDevicesOnMultipath(dm string) []string { 74 var devices []string 75 io := handler.getIo 76 // Split path /dev/dm-1 into "", "dev", "dm-1" 77 parts := strings.Split(dm, "/") 78 if len(parts) != 3 || !strings.HasPrefix(parts[1], "dev") { 79 return devices 80 } 81 disk := parts[2] 82 slavesPath := filepath.Join("/sys/block/", disk, "/slaves/") 83 if files, err := io.ReadDir(slavesPath); err == nil { 84 for _, f := range files { 85 devices = append(devices, filepath.Join("/dev/", f.Name())) 86 } 87 } 88 return devices 89 } 90 91 // GetISCSIPortalHostMapForTarget given a target iqn, find all the scsi hosts logged into 92 // that target. Returns a map of iSCSI portals (string) to SCSI host numbers (integers). 93 // 94 // For example: { 95 // "192.168.30.7:3260": 2, 96 // "192.168.30.8:3260": 3, 97 // } 98 func (handler *deviceHandler) GetISCSIPortalHostMapForTarget(targetIqn string) (map[string]int, error) { 99 portalHostMap := make(map[string]int) 100 io := handler.getIo 101 102 // Iterate over all the iSCSI hosts in sysfs 103 sysPath := "/sys/class/iscsi_host" 104 hostDirs, err := io.ReadDir(sysPath) 105 if err != nil { 106 if os.IsNotExist(err) { 107 return portalHostMap, nil 108 } 109 return nil, err 110 } 111 for _, hostDir := range hostDirs { 112 // iSCSI hosts are always of the format "host%d" 113 // See drivers/scsi/hosts.c in Linux 114 hostName := hostDir.Name() 115 if !strings.HasPrefix(hostName, "host") { 116 continue 117 } 118 hostNumber, err := strconv.Atoi(strings.TrimPrefix(hostName, "host")) 119 if err != nil { 120 klog.Errorf("Could not get number from iSCSI host: %s", hostName) 121 continue 122 } 123 124 // Iterate over the children of the iscsi_host device 125 // We are looking for the associated session 126 devicePath := sysPath + "/" + hostName + "/device" 127 deviceDirs, err := io.ReadDir(devicePath) 128 if err != nil { 129 return nil, err 130 } 131 for _, deviceDir := range deviceDirs { 132 // Skip over files that aren't the session 133 // Sessions are of the format "session%u" 134 // See drivers/scsi/scsi_transport_iscsi.c in Linux 135 sessionName := deviceDir.Name() 136 if !strings.HasPrefix(sessionName, "session") { 137 continue 138 } 139 140 sessionPath := devicePath + "/" + sessionName 141 142 // Read the target name for the iSCSI session 143 targetNamePath := sessionPath + "/iscsi_session/" + sessionName + "/targetname" 144 targetName, err := io.ReadFile(targetNamePath) 145 if err != nil { 146 klog.Infof("Failed to process session %s, assuming this session is unavailable: %s", sessionName, err) 147 continue 148 } 149 150 // Ignore hosts that don't matchthe target we were looking for. 151 if strings.TrimSpace(string(targetName)) != targetIqn { 152 continue 153 } 154 155 // Iterate over the children of the iSCSI session looking 156 // for the iSCSI connection. 157 dirs2, err := io.ReadDir(sessionPath) 158 if err != nil { 159 klog.Infof("Failed to process session %s, assuming this session is unavailable: %s", sessionName, err) 160 continue 161 } 162 for _, dir2 := range dirs2 { 163 // Skip over files that aren't the connection 164 // Connections are of the format "connection%d:%u" 165 // See drivers/scsi/scsi_transport_iscsi.c in Linux 166 dirName := dir2.Name() 167 if !strings.HasPrefix(dirName, "connection") { 168 continue 169 } 170 171 connectionPath := sessionPath + "/" + dirName + "/iscsi_connection/" + dirName 172 173 // Read the current and persistent portal information for the connection. 174 addrPath := connectionPath + "/address" 175 addr, err := io.ReadFile(addrPath) 176 if err != nil { 177 klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err) 178 continue 179 } 180 181 portPath := connectionPath + "/port" 182 port, err := io.ReadFile(portPath) 183 if err != nil { 184 klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err) 185 continue 186 } 187 188 persistentAddrPath := connectionPath + "/persistent_address" 189 persistentAddr, err := io.ReadFile(persistentAddrPath) 190 if err != nil { 191 klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err) 192 continue 193 } 194 195 persistentPortPath := connectionPath + "/persistent_port" 196 persistentPort, err := io.ReadFile(persistentPortPath) 197 if err != nil { 198 klog.Infof("Failed to process connection %s, assuming this connection is unavailable: %s", dirName, err) 199 continue 200 } 201 202 // Add entries to the map for both the current and persistent portals 203 // pointing to the SCSI host for those connections 204 // JoinHostPort will add `[]` around IPv6 addresses. 205 portal := net.JoinHostPort(strings.TrimSpace(string(addr)), strings.TrimSpace(string(port))) 206 portalHostMap[portal] = hostNumber 207 208 persistentPortal := net.JoinHostPort(strings.TrimSpace(string(persistentAddr)), strings.TrimSpace(string(persistentPort))) 209 portalHostMap[persistentPortal] = hostNumber 210 } 211 } 212 } 213 214 return portalHostMap, nil 215 } 216 217 // FindDevicesForISCSILun given an iqn, and lun number, find all the devices 218 // corresponding to that LUN. 219 func (handler *deviceHandler) FindDevicesForISCSILun(targetIqn string, lun int) ([]string, error) { 220 devices := make([]string, 0) 221 io := handler.getIo 222 223 // Iterate over all the iSCSI hosts in sysfs 224 sysPath := "/sys/class/iscsi_host" 225 hostDirs, err := io.ReadDir(sysPath) 226 if err != nil { 227 return nil, err 228 } 229 for _, hostDir := range hostDirs { 230 // iSCSI hosts are always of the format "host%d" 231 // See drivers/scsi/hosts.c in Linux 232 hostName := hostDir.Name() 233 if !strings.HasPrefix(hostName, "host") { 234 continue 235 } 236 hostNumber, err := strconv.Atoi(strings.TrimPrefix(hostName, "host")) 237 if err != nil { 238 klog.Errorf("Could not get number from iSCSI host: %s", hostName) 239 continue 240 } 241 242 // Iterate over the children of the iscsi_host device 243 // We are looking for the associated session 244 devicePath := sysPath + "/" + hostName + "/device" 245 deviceDirs, err := io.ReadDir(devicePath) 246 if err != nil { 247 return nil, err 248 } 249 for _, deviceDir := range deviceDirs { 250 // Skip over files that aren't the session 251 // Sessions are of the format "session%u" 252 // See drivers/scsi/scsi_transport_iscsi.c in Linux 253 sessionName := deviceDir.Name() 254 if !strings.HasPrefix(sessionName, "session") { 255 continue 256 } 257 258 // Read the target name for the iSCSI session 259 targetNamePath := devicePath + "/" + sessionName + "/iscsi_session/" + sessionName + "/targetname" 260 targetName, err := io.ReadFile(targetNamePath) 261 if err != nil { 262 return nil, err 263 } 264 265 // Only if the session matches the target we were looking for, 266 // add it to the map 267 if strings.TrimSpace(string(targetName)) != targetIqn { 268 continue 269 } 270 271 // The list of block devices on the scsi bus will be in a 272 // directory called "target%d:%d:%d". 273 // See drivers/scsi/scsi_scan.c in Linux 274 // We assume the channel/bus and device/controller are always zero for iSCSI 275 targetPath := devicePath + "/" + sessionName + fmt.Sprintf("/target%d:0:0", hostNumber) 276 277 // The block device for a given lun will be "%d:%d:%d:%d" -- 278 // host:channel:bus:LUN 279 blockDevicePath := targetPath + fmt.Sprintf("/%d:0:0:%d", hostNumber, lun) 280 281 // If the LUN doesn't exist on this bus, continue on 282 _, err = io.Lstat(blockDevicePath) 283 if err != nil { 284 continue 285 } 286 287 // Read the block directory, there should only be one child -- 288 // the block device "sd*" 289 path := blockDevicePath + "/block" 290 dirs, err := io.ReadDir(path) 291 if err != nil { 292 return nil, err 293 } 294 if 0 < len(dirs) { 295 devices = append(devices, dirs[0].Name()) 296 } 297 } 298 } 299 300 return devices, nil 301 }