@@ -20,10 +20,13 @@ import (
20
20
"os/exec"
21
21
"path"
22
22
"path/filepath"
23
+ "regexp"
23
24
"strings"
25
+ "time"
24
26
25
- "k8s.io/apimachinery/pkg/util/sets "
27
+ "k8s.io/apimachinery/pkg/util/wait "
26
28
"k8s.io/klog"
29
+ pathutils "k8s.io/utils/path"
27
30
)
28
31
29
32
const (
@@ -46,6 +49,14 @@ const (
46
49
// 'fsck' found errors but exited without correcting them
47
50
fsckErrorsUncorrected = 4
48
51
defaultMountCommand = "mount"
52
+ // scsi_id output should be in the form of:
53
+ // 0Google PersistentDisk <disk name>
54
+ scsiPattern = `^0Google\s+PersistentDisk\s+([\S]+)\s*$`
55
+ )
56
+
57
+ var (
58
+ // regex to parse scsi_id output and extract the serial
59
+ scsiRegex = regexp .MustCompile (scsiPattern )
49
60
)
50
61
51
62
// DeviceUtils are a collection of methods that act on the devices attached
@@ -57,7 +68,7 @@ type DeviceUtils interface {
57
68
58
69
// VerifyDevicePath returns the first of the list of device paths that
59
70
// exists on the machine, or an empty string if none exists
60
- VerifyDevicePath (devicePaths []string ) (string , error )
71
+ VerifyDevicePath (devicePaths []string , diskName string ) (string , error )
61
72
}
62
73
63
74
type deviceUtils struct {
@@ -85,75 +96,166 @@ func (m *deviceUtils) GetDiskByIdPaths(deviceName string, partition string) []st
85
96
return devicePaths
86
97
}
87
98
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 )
99
+ func existingDevicePath (devicePaths []string ) (string , error ) {
100
+ for _ , devicePath := range devicePaths {
101
+ if pathExists , err := pathExists (devicePath ); err != nil {
102
+ return "" , fmt .Errorf ("error checking if path exists: %v" , err )
103
+ } else if pathExists {
104
+ return devicePath , nil
105
+ }
94
106
}
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 )
107
+ return "" , nil
108
+ }
109
+
110
+ // getScsiSerial assumes that /lib/udev/scsi_id exists and will error if it
111
+ // doesnt. It is the callers responsibility to verify the existence of this
112
+ // tool. Calls scsi_id on the given devicePath to get the serial number reported
113
+ // by that device.
114
+ func getScsiSerial (devicePath string ) (string , error ) {
115
+ out , err := exec .Command (
116
+ "/lib/udev_containerized/scsi_id" ,
117
+ "--page=0x83" ,
118
+ "--whitelisted" ,
119
+ fmt .Sprintf ("--device=%v" , devicePath )).CombinedOutput ()
120
+ if err != nil {
121
+ return "" , fmt .Errorf ("scsi_id failed for device %q with output %s: %v" , devicePath , string (out ), err )
100
122
}
101
123
102
- for _ , path := range devicePaths {
103
- if pathExists , err := pathExists (path ); err != nil {
104
- return "" , fmt .Errorf ("Error checking if path exists: %v" , err )
105
- } else if pathExists {
106
- return path , nil
107
- }
124
+ return parseScsiSerial (string (out ))
125
+ }
126
+
127
+ // Parse the output returned by scsi_id and extract the serial number
128
+ func parseScsiSerial (output string ) (string , error ) {
129
+ substrings := scsiRegex .FindStringSubmatch (output )
130
+ if substrings == nil {
131
+ return "" , fmt .Errorf ("scsi_id output cannot be parsed: %q" , output )
108
132
}
109
133
110
- return "" , nil
134
+ return substrings [ 1 ] , nil
111
135
}
112
136
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 )
137
+ // VerifyDevicePath returns the first devicePath that maps to a real disk in the
138
+ // candidate devicePaths or an empty string if none is found. If
139
+ // /lib/udev_containerized/scsi_id exists it will attempt to fix any issues
140
+ // caused by missing paths or mismatched devices by running a udevadm --trigger.
141
+ func (m * deviceUtils ) VerifyDevicePath (devicePaths []string , diskName string ) (string , error ) {
142
+ var devicePath string
143
+ var err error
144
+ const (
145
+ pollInterval = 500 * time .Millisecond
146
+ pollTimeout = 3 * time .Second
147
+ )
148
+
149
+ scsiIDPath := "/lib/udev_containerized/scsi_id"
150
+ exists , err := pathutils .Exists (pathutils .CheckFollowSymlink , scsiIDPath )
119
151
if err != nil {
120
- return fmt .Errorf ("Error filepath.Glob(\" %s\" ): %v\r \n " , diskSDPattern , err )
152
+ return "" , fmt .Errorf ("failed to check scsi_id existence: %v" , err )
153
+ }
154
+ if ! exists {
155
+ // No SCSI ID tool, the driver should be containerized with the tool so
156
+ // maybe something is wrong with the build process
157
+ return "" , fmt .Errorf ("could not find scsi_id tool at %s, no udevadm fixing is possible so we just return the best effort devicePath" , scsiIDPath )
121
158
}
122
159
123
- for _ , sd := range sdAfter {
124
- if ! sdBeforeSet .Has (sd ) {
125
- return udevadmChangeToDrive (sd )
160
+ err = wait .Poll (pollInterval , pollTimeout , func () (bool , error ) {
161
+ var innerErr error
162
+
163
+ devicePath , innerErr = existingDevicePath (devicePaths )
164
+ if innerErr != nil {
165
+ return false , fmt .Errorf ("failed to check for existing device path: %v" , innerErr )
166
+ }
167
+
168
+ if len (devicePath ) == 0 {
169
+ // Couldn't find the path so we need to find a /dev/sdx with the SCSI
170
+ // serial that matches diskName. Then we run udevadm trigger on that
171
+ // device to get the device to show up in /dev/by-id/
172
+ innerErr := udevadmTriggerForDiskIfExists (diskName )
173
+ if innerErr != nil {
174
+ return false , fmt .Errorf ("failed to trigger udevadm fix for disk %s: %v" , diskName , innerErr )
175
+ }
176
+ // Go to next retry loop to get the deviceName again after
177
+ // potentially fixing it with the udev command
178
+ return false , nil
179
+ }
180
+
181
+ // If there exists a devicePath we make sure disk at /dev/sdx matches the
182
+ // expected disk at devicePath by matching SCSI Serial to the disk name
183
+ devSDX , innerErr := filepath .EvalSymlinks (devicePath )
184
+ if innerErr != nil {
185
+ return false , fmt .Errorf ("filepath.EvalSymlinks(%q) failed with %v" , devicePath , innerErr )
186
+ }
187
+ // Check to make sure device path maps to the correct disk
188
+ if strings .Contains (devSDX , diskSDPath ) {
189
+ scsiSerial , innerErr := getScsiSerial (devSDX )
190
+ if innerErr != nil {
191
+ return false , fmt .Errorf ("couldn't get SCSI serial number for disk %s: %v" , diskName , innerErr )
192
+ }
193
+ // SUCCESS! devicePath points to a /dev/sdx that has a SCSI serial
194
+ // equivilant to our disk name
195
+ if scsiSerial == diskName {
196
+ return true , nil
197
+ }
126
198
}
199
+ // The devicePath is not mapped to the correct disk
200
+ innerErr = udevadmTriggerForDiskIfExists (diskName )
201
+ if innerErr != nil {
202
+ return false , fmt .Errorf ("failed to trigger udevadm fix for disk %s: %v" , diskName , innerErr )
203
+ }
204
+ // Go to next retry loop to get the deviceName again after
205
+ // potentially fixing it with the udev command
206
+ return false , nil
207
+ })
208
+
209
+ if err != nil {
210
+ return "" , fmt .Errorf ("failed to find and re-link disk %s with udevadm after retrying for %v: %v" , diskName , pollTimeout , err )
127
211
}
128
212
129
- return nil
213
+ return devicePath , nil
130
214
}
131
215
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 )
137
-
138
- // Evaluate symlink, if any
139
- drive , err := filepath .EvalSymlinks (drivePath )
216
+ func udevadmTriggerForDiskIfExists (diskName string ) error {
217
+ sds , err := filepath .Glob (diskSDPattern )
140
218
if err != nil {
141
- return fmt .Errorf ("udevadmChangeToDrive: filepath.EvalSymlinks(%q) failed with %v. " , drivePath , err )
219
+ return fmt .Errorf ("failed to filepath.Glob( \" %s \" ): %v " , diskSDPattern , err )
142
220
}
143
- // 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 )
221
+ for _ , devSDX := range sds {
222
+ scsiSerial , err := getScsiSerial (devSDX )
223
+ if err != nil {
224
+ return fmt .Errorf ("failed to get SCSI Serial num for %s: %v" , devSDX , err )
225
+ }
226
+ if scsiSerial == diskName {
227
+ // Found the disk that we're looking for so run a trigger on it
228
+ // to resolve its /dev/by-id/ path
229
+ klog .Warningf ("udevadm --trigger running to fix disk at path %s which has SCSI ID %s" , devSDX , scsiSerial )
230
+ err := udevadmChangeToDrive (devSDX )
231
+ if err != nil {
232
+ return fmt .Errorf ("failed to fix disk at path %s which has SCSI ID %s: %v" , devSDX , scsiSerial , err )
233
+ }
234
+ return nil
235
+ }
146
236
}
237
+ return fmt .Errorf ("udevadm --trigger requested to fix disk %s but no such disk was found in %v" , diskName , sds )
238
+ }
147
239
240
+ // Calls "udevadm trigger --action=change" on the specified drive. drivePath
241
+ // must be the block device path to trigger on, in the format "/dev/sd*", or a
242
+ // symlink to it. This is workaround for Issue #7972. Once the underlying issue
243
+ // has been resolved, this may be removed.
244
+ // udevadm takes a little bit to work its magic in the background so any callers
245
+ // should not expect the trigger to complete instantly and may need to poll for
246
+ // the change
247
+ func udevadmChangeToDrive (devSDX string ) error {
148
248
// Call "udevadm trigger --action=change --property-match=DEVNAME=/dev/sd..."
149
- _ , err = exec .Command (
249
+ out , err : = exec .Command (
150
250
"udevadm" ,
151
251
"trigger" ,
152
252
"--action=change" ,
153
- fmt .Sprintf ("--property-match=DEVNAME=%s" , drive )).CombinedOutput ()
253
+ fmt .Sprintf ("--property-match=DEVNAME=%s" , devSDX )).CombinedOutput ()
154
254
if err != nil {
155
- return fmt .Errorf ("udevadmChangeToDrive: udevadm trigger failed for drive %q with % v." , drive , err )
255
+ return fmt .Errorf ("udevadmChangeToDrive: udevadm trigger failed for drive %q with output %s: % v." , devSDX , string ( out ) , err )
156
256
}
257
+
258
+ time .Sleep (time .Second )
157
259
return nil
158
260
}
159
261
0 commit comments