Skip to content

Commit ae559b8

Browse files
committed
check multipath consistency on connection/disconnection
1 parent d1abc07 commit ae559b8

File tree

3 files changed

+257
-38
lines changed

3 files changed

+257
-38
lines changed

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ github.com/prashantv/gostub v1.0.0/go.mod h1:dP1v6T1QzyGJJKFocwAU0lSZKpfjstjH8Tl
77
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
88
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
99
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
10+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1011
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1112
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
1213
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

iscsi/iscsi.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ type Device struct {
6060
Size string `json:"size,omitempty"`
6161
}
6262

63+
type HCTL struct {
64+
HBA int
65+
Channel int
66+
Target int
67+
LUN int
68+
}
69+
6370
// Connector provides a struct to hold all of the needed parameters to make our iSCSI connection
6471
type Connector struct {
6572
VolumeName string `json:"volume_name"`
@@ -291,6 +298,12 @@ func (c *Connector) Connect() (string, error) {
291298
return "", err
292299
}
293300

301+
if c.IsMultipathEnabled() {
302+
if err := c.IsMultipathConsistent(); err != nil {
303+
return "", fmt.Errorf("multipath is inconsistent: %v", err)
304+
}
305+
}
306+
294307
return c.MountTargetDevice.GetPath(), nil
295308
}
296309

@@ -394,6 +407,10 @@ func (c *Connector) DisconnectVolume() error {
394407
// Note: make sure the volume is already unmounted before calling this method.
395408

396409
if c.IsMultipathEnabled() {
410+
if err := c.IsMultipathConsistent(); err != nil {
411+
return fmt.Errorf("multipath is inconsistent: %v", err)
412+
}
413+
397414
debug.Printf("Removing multipath device in path %s.\n", c.MountTargetDevice.GetPath())
398415
err := FlushMultipathDevice(c.MountTargetDevice)
399416
if err != nil {
@@ -599,6 +616,52 @@ func GetConnectorFromFile(filePath string) (*Connector, error) {
599616
return &c, nil
600617
}
601618

619+
// IsMultipathConsistent check if the currently used device is using a consistent multipath mapping
620+
func (c *Connector) IsMultipathConsistent() error {
621+
devices := append([]Device{*c.MountTargetDevice}, c.Devices...)
622+
623+
referenceLUN := struct {
624+
LUN int
625+
Name string
626+
}{LUN: -1, Name: ""}
627+
HBA := map[int]string{}
628+
referenceDevice := devices[0]
629+
for _, device := range devices {
630+
if device.Size != referenceDevice.Size {
631+
return fmt.Errorf("devices size differ: %s (%s) != %s (%s)", device.Name, device.Size, referenceDevice.Name, referenceDevice.Size)
632+
}
633+
634+
if device.Type != "mpath" {
635+
hctl, err := device.HCTL()
636+
if err != nil {
637+
return err
638+
}
639+
if referenceLUN.LUN == -1 {
640+
referenceLUN.LUN = hctl.LUN
641+
referenceLUN.Name = device.Name
642+
} else if hctl.LUN != referenceLUN.LUN {
643+
return fmt.Errorf("devices LUNs differ: %s (%d) != %s (%d)", device.Name, hctl.LUN, referenceLUN.Name, referenceLUN.LUN)
644+
}
645+
646+
if name, ok := HBA[hctl.HBA]; !ok {
647+
HBA[hctl.HBA] = device.Name
648+
} else {
649+
return fmt.Errorf("two devices are using the same controller (%d): %s and %s", hctl.HBA, device.Name, name)
650+
}
651+
}
652+
653+
wwid, err := device.WWID()
654+
if err != nil {
655+
return fmt.Errorf("could not find WWID for device %s: %v", device.Name, err)
656+
}
657+
if wwid != referenceDevice.Name {
658+
return fmt.Errorf("devices WWIDs differ: %s (wwid:%s) != %s (wwid:%s)", device.Name, wwid, referenceDevice.Name, referenceDevice.Name)
659+
}
660+
}
661+
662+
return nil
663+
}
664+
602665
// Exists check if the device exists at its path and returns an error otherwise
603666
func (d *Device) Exists() error {
604667
_, err := osStat(d.GetPath())
@@ -614,6 +677,42 @@ func (d *Device) GetPath() string {
614677
return filepath.Join("/dev", d.Name)
615678
}
616679

680+
// WWID returns the WWID of a device
681+
func (d *Device) WWID() (string, error) {
682+
timeout := 1 * time.Second
683+
out, err := execWithTimeout("scsi_id", []string{"-g", "-u", d.GetPath()}, timeout)
684+
if err != nil {
685+
return "", err
686+
}
687+
688+
return string(out[:len(out)-1]), nil
689+
}
690+
691+
// HCTL returns the HCTL of a device
692+
func (d *Device) HCTL() (*HCTL, error) {
693+
var hctl []int
694+
695+
for _, idstr := range strings.Split(d.Hctl, ":") {
696+
id, err := strconv.Atoi(idstr)
697+
if err != nil {
698+
hctl = []int{}
699+
break
700+
}
701+
hctl = append(hctl, id)
702+
}
703+
704+
if len(hctl) != 4 {
705+
return nil, fmt.Errorf("invalid HCTL (%s) for device %q", d.Hctl, d.Name)
706+
}
707+
708+
return &HCTL{
709+
HBA: hctl[0],
710+
Channel: hctl[1],
711+
Target: hctl[2],
712+
LUN: hctl[3],
713+
}, nil
714+
}
715+
617716
// WriteDeviceFile write in a device file
618717
func (d *Device) WriteDeviceFile(name string, content string) error {
619718
return writeInSCSIDeviceFile(d.Hctl, name, content)

0 commit comments

Comments
 (0)