@@ -20,10 +20,11 @@ import (
20
20
"os/exec"
21
21
"path"
22
22
"path/filepath"
23
+ "regexp"
23
24
"strings"
24
25
25
- "k8s.io/apimachinery/pkg/util/sets"
26
26
"k8s.io/klog"
27
+ pathutils "k8s.io/utils/path"
27
28
)
28
29
29
30
const (
@@ -46,6 +47,14 @@ const (
46
47
// 'fsck' found errors but exited without correcting them
47
48
fsckErrorsUncorrected = 4
48
49
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 )
49
58
)
50
59
51
60
// DeviceUtils are a collection of methods that act on the devices attached
@@ -57,7 +66,7 @@ type DeviceUtils interface {
57
66
58
67
// VerifyDevicePath returns the first of the list of device paths that
59
68
// 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 )
61
70
}
62
71
63
72
type deviceUtils struct {
@@ -85,74 +94,129 @@ func (m *deviceUtils) GetDiskByIdPaths(deviceName string, partition string) []st
85
94
return devicePaths
86
95
}
87
96
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 )
105
101
} else if pathExists {
106
- return path , nil
102
+ return devicePath , nil
107
103
}
108
104
}
109
-
110
105
return "" , nil
111
106
}
112
107
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 ()
119
118
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 )
121
120
}
122
121
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 )
127
130
}
128
131
129
- return nil
132
+ return substrings [ 1 ], nil
130
133
}
131
134
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
+ }
137
144
138
- // Evaluate symlink, if any
139
- drive , err := filepath .EvalSymlinks (drivePath )
145
+ exists , err := pathutils .Exists (pathutils .CheckFollowSymlink , "/lib/udev_containerized/scsi_id" )
140
146
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 )
142
188
}
143
189
// 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 )
146
192
}
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
+ }
147
206
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 )
148
212
// Call "udevadm trigger --action=change --property-match=DEVNAME=/dev/sd..."
149
- _ , err = exec .Command (
213
+ _ , err : = exec .Command (
150
214
"udevadm" ,
151
215
"trigger" ,
152
216
"--action=change" ,
153
- fmt .Sprintf ("--property-match=DEVNAME=%s" , drive )).CombinedOutput ()
217
+ fmt .Sprintf ("--property-match=DEVNAME=%s" , devSDX )).CombinedOutput ()
154
218
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 )
156
220
}
157
221
return nil
158
222
}
0 commit comments