Skip to content

Commit 261e307

Browse files
committed
Update VerifyDevicePath logic to run udevadm --trigger to fix scenarios where disk is not found or is wrong device
1 parent dd14907 commit 261e307

File tree

4 files changed

+117
-49
lines changed

4 files changed

+117
-49
lines changed

Dockerfile

+5-1
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,15 @@ ADD . .
2020
ARG TAG
2121
RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-X main.vendorVersion='"${TAG:-latest}"' -extldflags "-static"' -o bin/gce-pd-csi-driver ./cmd/
2222

23+
# MAD HACKS: Build a version first so we can take the scsi_id bin and put it somewhere else in our real build
24+
FROM gcr.io/google-containers/debian-base-amd64:v2.0.0 as base
25+
RUN clean-install udev
26+
2327
# Start from Google Debian base
2428
FROM gcr.io/google-containers/debian-base-amd64:v2.0.0
2529
COPY --from=builder /go/src/sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/bin/gce-pd-csi-driver /gce-pd-csi-driver
26-
2730
# Install necessary dependencies
2831
RUN clean-install util-linux e2fsprogs mount ca-certificates udev xfsprogs
32+
COPY --from=base /lib/udev/scsi_id /lib/udev_containerized/scsi_id
2933

3034
ENTRYPOINT ["/gce-pd-csi-driver"]

pkg/gce-pd-csi-driver/node.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ func (ns *GCENodeServer) getDevicePath(volumeID string, partition string) (strin
528528
}
529529

530530
devicePaths := ns.DeviceUtils.GetDiskByIdPaths(deviceName, partition)
531-
devicePath, err := ns.DeviceUtils.VerifyDevicePath(devicePaths)
531+
devicePath, err := ns.DeviceUtils.VerifyDevicePath(devicePaths, volumeKey.Name)
532532

533533
if err != nil {
534534
return "", fmt.Errorf("error verifying GCE PD (%q) is attached: %v", volumeKey.Name, err)

pkg/mount-manager/device-utils.go

+110-46
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ import (
2020
"os/exec"
2121
"path"
2222
"path/filepath"
23+
"regexp"
2324
"strings"
2425

25-
"k8s.io/apimachinery/pkg/util/sets"
2626
"k8s.io/klog"
27+
pathutils "k8s.io/utils/path"
2728
)
2829

2930
const (
@@ -46,6 +47,14 @@ const (
4647
// 'fsck' found errors but exited without correcting them
4748
fsckErrorsUncorrected = 4
4849
defaultMountCommand = "mount"
50+
// scsi_id output should be in the form of:
51+
// 0Google PersistentDisk <disk name>
52+
scsiPattern = `^0Google\s+PersistentDisk\s+([\S]+)\s*$`
53+
)
54+
55+
var (
56+
// regex to parse scsi_id output and extract the serial
57+
scsiRegex = regexp.MustCompile(scsiPattern)
4958
)
5059

5160
// DeviceUtils are a collection of methods that act on the devices attached
@@ -57,7 +66,7 @@ type DeviceUtils interface {
5766

5867
// VerifyDevicePath returns the first of the list of device paths that
5968
// exists on the machine, or an empty string if none exists
60-
VerifyDevicePath(devicePaths []string) (string, error)
69+
VerifyDevicePath(devicePaths []string, diskName string) (string, error)
6170
}
6271

6372
type deviceUtils struct {
@@ -85,74 +94,129 @@ func (m *deviceUtils) GetDiskByIdPaths(deviceName string, partition string) []st
8594
return devicePaths
8695
}
8796

88-
// Returns the first path that exists, or empty string if none exist.
89-
func (m *deviceUtils) VerifyDevicePath(devicePaths []string) (string, error) {
90-
sdBefore, err := filepath.Glob(diskSDPattern)
91-
if err != nil {
92-
// Seeing this error means that the diskSDPattern is malformed.
93-
klog.Errorf("Error filepath.Glob(\"%s\"): %v\r\n", diskSDPattern, err)
94-
}
95-
sdBeforeSet := sets.NewString(sdBefore...)
96-
// TODO(#69): Verify udevadm works as intended in driver
97-
if err := udevadmChangeToNewDrives(sdBeforeSet); err != nil {
98-
// udevadm errors should not block disk detachment, log and continue
99-
klog.Errorf("udevadmChangeToNewDrives failed with: %v", err)
100-
}
101-
102-
for _, path := range devicePaths {
103-
if pathExists, err := pathExists(path); err != nil {
104-
return "", fmt.Errorf("Error checking if path exists: %v", err)
97+
func existingDevicePath(devicePaths []string) (string, error) {
98+
for _, devicePath := range devicePaths {
99+
if pathExists, err := pathExists(devicePath); err != nil {
100+
return "", fmt.Errorf("error checking if path exists: %v", err)
105101
} else if pathExists {
106-
return path, nil
102+
return devicePath, nil
107103
}
108104
}
109-
110105
return "", nil
111106
}
112107

113-
// Triggers the application of udev rules by calling "udevadm trigger
114-
// --action=change" for newly created "/dev/sd*" drives (exist only in
115-
// after set). This is workaround for Issue #7972. Once the underlying
116-
// issue has been resolved, this may be removed.
117-
func udevadmChangeToNewDrives(sdBeforeSet sets.String) error {
118-
sdAfter, err := filepath.Glob(diskSDPattern)
108+
// getScsiSerial assumes that /lib/udev/scsi_id exists and will error if it
109+
// doesnt. It is the callers responsibility to verify the existence of this
110+
// tool. Calls scsi_id on the given devicePath to get the serial number reported
111+
// by that device.
112+
func getScsiSerial(devicePath string) (string, error) {
113+
out, err := exec.Command(
114+
"/lib/udev_containerized/scsi_id",
115+
"--page=0x83",
116+
"--whitelisted",
117+
fmt.Sprintf("--device=%v", devicePath)).CombinedOutput()
119118
if err != nil {
120-
return fmt.Errorf("Error filepath.Glob(\"%s\"): %v\r\n", diskSDPattern, err)
119+
return "", fmt.Errorf("scsi_id failed for device %q with output %s: %v", devicePath, string(out), err)
121120
}
122121

123-
for _, sd := range sdAfter {
124-
if !sdBeforeSet.Has(sd) {
125-
return udevadmChangeToDrive(sd)
126-
}
122+
return parseScsiSerial(string(out))
123+
}
124+
125+
// Parse the output returned by scsi_id and extract the serial number
126+
func parseScsiSerial(output string) (string, error) {
127+
substrings := scsiRegex.FindStringSubmatch(output)
128+
if substrings == nil {
129+
return "", fmt.Errorf("scsi_id output cannot be parsed: %q", output)
127130
}
128131

129-
return nil
132+
return substrings[1], nil
130133
}
131134

132-
// Calls "udevadm trigger --action=change" on the specified drive.
133-
// drivePath must be the block device path to trigger on, in the format "/dev/sd*", or a symlink to it.
134-
// This is workaround for Issue #7972. Once the underlying issue has been resolved, this may be removed.
135-
func udevadmChangeToDrive(drivePath string) error {
136-
klog.V(4).Infof("udevadmChangeToDrive: drive=%q", drivePath)
135+
// VerifyDevicePath returns the first devicePath that maps to a real disk in the
136+
// candidate devicePaths or an empty string if none is found. If
137+
// /lib/udev/scsi_id exists it will attempt to fix any issues caused by missing
138+
// paths or mismatched devices by running a udevadm --trigger.
139+
func (m *deviceUtils) VerifyDevicePath(devicePaths []string, diskName string) (string, error) {
140+
devicePath, err := existingDevicePath(devicePaths)
141+
if err != nil {
142+
return "", fmt.Errorf("failed to check for existing device path: %v", err)
143+
}
137144

138-
// Evaluate symlink, if any
139-
drive, err := filepath.EvalSymlinks(drivePath)
145+
exists, err := pathutils.Exists(pathutils.CheckFollowSymlink, "/lib/udev_containerized/scsi_id")
140146
if err != nil {
141-
return fmt.Errorf("udevadmChangeToDrive: filepath.EvalSymlinks(%q) failed with %v.", drivePath, err)
147+
return "", fmt.Errorf("failed to check scsi_id existence: %v", err)
148+
}
149+
if !exists {
150+
// No SCSI ID tool, no udevadm fixing is possible so just return best
151+
// effort devicePath.
152+
return devicePath, nil
153+
}
154+
155+
if len(devicePath) == 0 {
156+
// Couldn't find the path so we need to find a /dev/sdx with the SCSI
157+
// serial that matches diskName. Then we run udevadm trigger on that
158+
// device to get the device to show up in /dev/by-id/
159+
sds, err := filepath.Glob(diskSDPattern)
160+
if err != nil {
161+
return "", fmt.Errorf("failed to filepath.Glob(\"%s\"): %v", diskSDPattern, err)
162+
}
163+
for _, devSDX := range sds {
164+
scsiSerial, err := getScsiSerial(devSDX)
165+
if err != nil {
166+
return "", fmt.Errorf("failed to get SCSI Serial num for %s: %v", devSDX, err)
167+
}
168+
if scsiSerial == diskName {
169+
// Found the disk that we're looking for so run a trigger on it
170+
// to resolve its /dev/by-id/ path
171+
err := udevadmChangeToDrive(devSDX)
172+
if err != nil {
173+
return "", fmt.Errorf("failed to fix disk at path %s which has SCSI ID %s when expecting %s: %v", devicePath, scsiSerial, diskName, err)
174+
}
175+
break
176+
}
177+
}
178+
// Try get the deviceName again after potentially fixing it with the
179+
// udev command
180+
return existingDevicePath(devicePaths)
181+
}
182+
183+
// If there exists a devicePath we make sure disk at /dev/sdx matches the
184+
// expected disk at devicePath by matching SCSI Serial to the disk name
185+
devSDX, err := filepath.EvalSymlinks(devicePath)
186+
if err != nil {
187+
return "", fmt.Errorf("filepath.EvalSymlinks(%q) failed with %v.", devicePath, err)
142188
}
143189
// Check to make sure input is "/dev/sd*"
144-
if !strings.Contains(drive, diskSDPath) {
145-
return fmt.Errorf("udevadmChangeToDrive: expected input in the form \"%s\" but drive is %q.", diskSDPattern, drive)
190+
if !strings.Contains(devSDX, diskSDPath) {
191+
return "", fmt.Errorf("expected input in the form \"%s\" but drive is %q.", diskSDPattern, devSDX)
146192
}
193+
scsiSerial, err := getScsiSerial(devSDX)
194+
if err != nil {
195+
return "", fmt.Errorf("couldn't get SCSI serial number for disk %s: %v", diskName, err)
196+
}
197+
if scsiSerial != diskName {
198+
klog.Warningf("Disk found at path %s has SCSI serial ID %s but expected %s. Running udevadm trigger to resolve", devicePath, scsiSerial, diskName)
199+
err := udevadmChangeToDrive(devSDX)
200+
if err != nil {
201+
return "", fmt.Errorf("failed to fix disk at path %s which has SCSI ID %s when expecting %s: %v", devicePath, scsiSerial, diskName, err)
202+
}
203+
}
204+
return devicePath, nil
205+
}
147206

207+
// Calls "udevadm trigger --action=change" on the specified drive.
208+
// drivePath must be the block device path to trigger on, in the format "/dev/sd*", or a symlink to it.
209+
// This is workaround for Issue #7972. Once the underlying issue has been resolved, this may be removed.
210+
func udevadmChangeToDrive(devSDX string) error {
211+
klog.V(4).Infof("udevadmChangeToDrive: drive=%q", devSDX)
148212
// Call "udevadm trigger --action=change --property-match=DEVNAME=/dev/sd..."
149-
_, err = exec.Command(
213+
_, err := exec.Command(
150214
"udevadm",
151215
"trigger",
152216
"--action=change",
153-
fmt.Sprintf("--property-match=DEVNAME=%s", drive)).CombinedOutput()
217+
fmt.Sprintf("--property-match=DEVNAME=%s", devSDX)).CombinedOutput()
154218
if err != nil {
155-
return fmt.Errorf("udevadmChangeToDrive: udevadm trigger failed for drive %q with %v.", drive, err)
219+
return fmt.Errorf("udevadmChangeToDrive: udevadm trigger failed for drive %q with %v.", devSDX, err)
156220
}
157221
return nil
158222
}

pkg/mount-manager/fake-device-utils.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func (m *fakeDeviceUtils) GetDiskByIdPaths(pdName string, partition string) []st
3030
}
3131

3232
// Returns the first path that exists, or empty string if none exist.
33-
func (m *fakeDeviceUtils) VerifyDevicePath(devicePaths []string) (string, error) {
33+
func (m *fakeDeviceUtils) VerifyDevicePath(devicePaths []string, diskName string) (string, error) {
3434
// Return any random device path to use as mount source
3535
return "/dev/disk/fake-path", nil
3636
}

0 commit comments

Comments
 (0)