1
16
17 package fc
18
19 import (
20 "fmt"
21 "io/ioutil"
22 "os"
23 "path/filepath"
24 "regexp"
25 "strconv"
26 "strings"
27
28 "k8s.io/klog/v2"
29 "k8s.io/mount-utils"
30 utilexec "k8s.io/utils/exec"
31
32 "k8s.io/kubernetes/pkg/volume"
33 volumeutil "k8s.io/kubernetes/pkg/volume/util"
34 )
35
36 type ioHandler interface {
37 ReadDir(dirname string) ([]os.FileInfo, error)
38 Lstat(name string) (os.FileInfo, error)
39 EvalSymlinks(path string) (string, error)
40 WriteFile(filename string, data []byte, perm os.FileMode) error
41 }
42
43 type osIOHandler struct{}
44
45 const (
46 byPath = "/dev/disk/by-path/"
47 byID = "/dev/disk/by-id/"
48 )
49
50 func (handler *osIOHandler) ReadDir(dirname string) ([]os.FileInfo, error) {
51 return ioutil.ReadDir(dirname)
52 }
53 func (handler *osIOHandler) Lstat(name string) (os.FileInfo, error) {
54 return os.Lstat(name)
55 }
56 func (handler *osIOHandler) EvalSymlinks(path string) (string, error) {
57 return filepath.EvalSymlinks(path)
58 }
59 func (handler *osIOHandler) WriteFile(filename string, data []byte, perm os.FileMode) error {
60 return ioutil.WriteFile(filename, data, perm)
61 }
62
63
64 func findDisk(wwn, lun string, io ioHandler, deviceUtil volumeutil.DeviceUtil) (string, string) {
65 fcPathExp := "^(pci-.*-fc|fc)-0x" + wwn + "-lun-" + lun + "$"
66 r := regexp.MustCompile(fcPathExp)
67 devPath := byPath
68 if dirs, err := io.ReadDir(devPath); err == nil {
69 for _, f := range dirs {
70 name := f.Name()
71 if r.MatchString(name) {
72 if disk, err1 := io.EvalSymlinks(devPath + name); err1 == nil {
73 dm := deviceUtil.FindMultipathDeviceForDevice(disk)
74 klog.Infof("fc: find disk: %v, dm: %v, fc path: %v", disk, dm, name)
75 return disk, dm
76 }
77 }
78 }
79 }
80 return "", ""
81 }
82
83
84 func findDiskWWIDs(wwid string, io ioHandler, deviceUtil volumeutil.DeviceUtil) (string, string) {
85
86
87
88
89
90
91
92
93
94 fcPath := "scsi-" + wwid
95 devID := byID
96 if dirs, err := io.ReadDir(devID); err == nil {
97 for _, f := range dirs {
98 name := f.Name()
99 if name == fcPath {
100 disk, err := io.EvalSymlinks(devID + name)
101 if err != nil {
102 klog.V(2).Infof("fc: failed to find a corresponding disk from symlink[%s], error %v", devID+name, err)
103 return "", ""
104 }
105 dm := deviceUtil.FindMultipathDeviceForDevice(disk)
106 klog.Infof("fc: find disk: %v, dm: %v", disk, dm)
107 return disk, dm
108 }
109 }
110 }
111 klog.V(2).Infof("fc: failed to find a disk [%s]", devID+fcPath)
112 return "", ""
113 }
114
115
116 func flushDevice(deviceName string, exec utilexec.Interface) {
117 out, err := exec.Command("blockdev", "--flushbufs", deviceName).CombinedOutput()
118 if err != nil {
119
120 klog.Warningf("Failed to flush device %s: %s\n%s", deviceName, err, string(out))
121 }
122 klog.V(4).Infof("Flushed device %s", deviceName)
123 }
124
125
126 func removeFromScsiSubsystem(deviceName string, io ioHandler) {
127 fileName := "/sys/block/" + deviceName + "/device/delete"
128 klog.V(4).Infof("fc: remove device from scsi-subsystem: path: %s", fileName)
129 data := []byte("1")
130 io.WriteFile(fileName, data, 0666)
131 }
132
133
134 func scsiHostRescan(io ioHandler) {
135 scsiPath := "/sys/class/scsi_host/"
136 if dirs, err := io.ReadDir(scsiPath); err == nil {
137 for _, f := range dirs {
138 name := scsiPath + f.Name() + "/scan"
139 data := []byte("- - -")
140 io.WriteFile(name, data, 0666)
141 }
142 }
143 }
144
145
146 func makePDNameInternal(host volume.VolumeHost, wwns []string, lun string, wwids []string) string {
147 if len(wwns) != 0 {
148 w := strings.Join(wwns, "-")
149 return filepath.Join(host.GetPluginDir(fcPluginName), w+"-lun-"+lun)
150 }
151 return filepath.Join(host.GetPluginDir(fcPluginName), strings.Join(wwids, "-"))
152 }
153
154
155 func makeVDPDNameInternal(host volume.VolumeHost, wwns []string, lun string, wwids []string) string {
156 if len(wwns) != 0 {
157 w := strings.Join(wwns, "-")
158 return filepath.Join(host.GetVolumeDevicePluginDir(fcPluginName), w+"-lun-"+lun)
159 }
160 return filepath.Join(host.GetVolumeDevicePluginDir(fcPluginName), strings.Join(wwids, "-"))
161 }
162
163 func parsePDName(path string) (wwns []string, lun int32, wwids []string, err error) {
164
165 dirname := filepath.Base(path)
166 components := strings.Split(dirname, "-")
167 l := len(components)
168 if l == 1 {
169
170 return nil, 0, components, nil
171 }
172 if components[l-2] == "lun" {
173
174 if l == 2 {
175 return nil, 0, nil, fmt.Errorf("no wwn in: %s", dirname)
176 }
177 lun, err := strconv.Atoi(components[l-1])
178 if err != nil {
179 return nil, 0, nil, err
180 }
181
182 return components[:l-2], int32(lun), nil, nil
183 }
184
185 return nil, 0, components, nil
186 }
187
188 type fcUtil struct{}
189
190 func (util *fcUtil) MakeGlobalPDName(fc fcDisk) string {
191 return makePDNameInternal(fc.plugin.host, fc.wwns, fc.lun, fc.wwids)
192 }
193
194
195 func (util *fcUtil) MakeGlobalVDPDName(fc fcDisk) string {
196 return makeVDPDNameInternal(fc.plugin.host, fc.wwns, fc.lun, fc.wwids)
197 }
198
199 func searchDisk(b fcDiskMounter) (string, error) {
200 var diskIDs []string
201 var disk string
202 var dm string
203 io := b.io
204 wwids := b.wwids
205 wwns := b.wwns
206 lun := b.lun
207
208 if len(wwns) != 0 {
209 diskIDs = wwns
210 } else {
211 diskIDs = wwids
212 }
213
214 rescanned := false
215
216
217
218 for true {
219 for _, diskID := range diskIDs {
220 if len(wwns) != 0 {
221 disk, dm = findDisk(diskID, lun, io, b.deviceUtil)
222 } else {
223 disk, dm = findDiskWWIDs(diskID, io, b.deviceUtil)
224 }
225
226 if dm != "" {
227 break
228 }
229 }
230
231 if rescanned || dm != "" {
232 break
233 }
234
235
236 scsiHostRescan(io)
237 rescanned = true
238 }
239
240 if disk == "" && dm == "" {
241 return "", fmt.Errorf("no fc disk found")
242 }
243
244
245 if dm != "" {
246 return dm, nil
247 }
248 return disk, nil
249 }
250
251 func (util *fcUtil) AttachDisk(b fcDiskMounter) (string, error) {
252 devicePath, err := searchDisk(b)
253 if err != nil {
254 return "", err
255 }
256
257 exists, err := mount.PathExists(devicePath)
258 if exists && err == nil {
259 return devicePath, nil
260 }
261 if exists == false {
262 return "", fmt.Errorf("device %s does not exist", devicePath)
263 } else {
264 return "", err
265 }
266 }
267
268
269 func (util *fcUtil) DetachDisk(c fcDiskUnmounter, devicePath string) error {
270 var devices []string
271
272 dstPath, err := c.io.EvalSymlinks(devicePath)
273 if err != nil {
274 return err
275 }
276
277 if strings.HasPrefix(dstPath, "/dev/dm-") {
278 devices = c.deviceUtil.FindSlaveDevicesOnMultipath(dstPath)
279 if err := util.deleteMultipathDevice(c.exec, dstPath); err != nil {
280 return err
281 }
282 } else {
283
284 devices = append(devices, dstPath)
285 }
286 klog.V(4).Infof("fc: DetachDisk devicePath: %v, dstPath: %v, devices: %v", devicePath, dstPath, devices)
287 var lastErr error
288 for _, device := range devices {
289 err := util.detachFCDisk(c.io, c.exec, device)
290 if err != nil {
291 klog.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err)
292 lastErr = fmt.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err)
293 }
294 }
295 if lastErr != nil {
296 klog.Errorf("fc: last error occurred during detach disk:\n%v", lastErr)
297 return lastErr
298 }
299 return nil
300 }
301
302
303 func (util *fcUtil) detachFCDisk(io ioHandler, exec utilexec.Interface, devicePath string) error {
304
305 if !strings.HasPrefix(devicePath, "/dev/") {
306 return fmt.Errorf("fc detach disk: invalid device name: %s", devicePath)
307 }
308 flushDevice(devicePath, exec)
309 arr := strings.Split(devicePath, "/")
310 dev := arr[len(arr)-1]
311 removeFromScsiSubsystem(dev, io)
312 return nil
313 }
314
315
316
317 func (util *fcUtil) DetachBlockFCDisk(c fcDiskUnmapper, mapPath, devicePath string) error {
318
319 if len(devicePath) != 0 {
320 if pathExists, pathErr := checkPathExists(devicePath); !pathExists || pathErr != nil {
321 return pathErr
322 }
323 } else {
324
325
326 klog.Infof("fc: devicePath is empty. Try to retrieve FC configuration from global map path: %v", mapPath)
327 }
328
329
330
331
332
333 if pathExists, pathErr := checkPathExists(mapPath); !pathExists || pathErr != nil {
334 return pathErr
335 }
336
337
338 arr := strings.Split(mapPath, "/")
339 if len(arr) < 1 {
340 return fmt.Errorf("failed to retrieve volume plugin information from global map path: %v", mapPath)
341 }
342 volumeInfo := arr[len(arr)-1]
343
344
345
346 searchPath := byID
347 if strings.Contains(volumeInfo, "-lun-") {
348 searchPath = byPath
349 }
350 fis, err := ioutil.ReadDir(searchPath)
351 if err != nil {
352 return err
353 }
354 for _, fi := range fis {
355 if strings.Contains(fi.Name(), volumeInfo) {
356 devicePath = filepath.Join(searchPath, fi.Name())
357 klog.V(5).Infof("fc: updated devicePath: %s", devicePath)
358 break
359 }
360 }
361 if len(devicePath) == 0 {
362 return fmt.Errorf("fc: failed to find corresponding device from searchPath: %v", searchPath)
363 }
364 dstPath, err := c.io.EvalSymlinks(devicePath)
365 if err != nil {
366 return err
367 }
368 klog.V(4).Infof("fc: find destination device path from symlink: %v", dstPath)
369
370 var devices []string
371 dm := c.deviceUtil.FindMultipathDeviceForDevice(dstPath)
372 if len(dm) != 0 {
373 dstPath = dm
374 }
375
376
377 if len(dm) != 0 {
378
379 devices = c.deviceUtil.FindSlaveDevicesOnMultipath(dm)
380 if err := util.deleteMultipathDevice(c.exec, dm); err != nil {
381 return err
382 }
383 } else {
384
385 devices = append(devices, dstPath)
386 }
387 var lastErr error
388 for _, device := range devices {
389 err = util.detachFCDisk(c.io, c.exec, device)
390 if err != nil {
391 klog.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err)
392 lastErr = fmt.Errorf("fc: detachFCDisk failed. device: %v err: %v", device, err)
393 }
394 }
395 if lastErr != nil {
396 klog.Errorf("fc: last error occurred during detach disk:\n%v", lastErr)
397 return lastErr
398 }
399 return nil
400 }
401
402 func (util *fcUtil) deleteMultipathDevice(exec utilexec.Interface, dmDevice string) error {
403 out, err := exec.Command("multipath", "-f", dmDevice).CombinedOutput()
404 if err != nil {
405 return fmt.Errorf("failed to flush multipath device %s: %s\n%s", dmDevice, err, string(out))
406 }
407 klog.V(4).Infof("Flushed multipath device: %s", dmDevice)
408 return nil
409 }
410
411 func checkPathExists(path string) (bool, error) {
412 if pathExists, pathErr := mount.PathExists(path); pathErr != nil {
413 return pathExists, fmt.Errorf("error checking if path exists: %w", pathErr)
414 } else if !pathExists {
415 klog.Warningf("Warning: Unmap skipped because path does not exist: %v", path)
416 return pathExists, nil
417 }
418 return true, nil
419 }
420
View as plain text