1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Block device abstraction - base class and utility functions"""
32
33 import logging
34
35 from ganeti import objects
36 from ganeti import constants
37 from ganeti import utils
38 from ganeti import errors
42 """Block device abstract class.
43
44 A block device can be in the following states:
45 - not existing on the system, and by `Create()` it goes into:
46 - existing but not setup/not active, and by `Assemble()` goes into:
47 - active read-write and by `Open()` it goes into
48 - online (=used, or ready for use)
49
50 A device can also be online but read-only, however we are not using
51 the readonly state (LV has it, if needed in the future) and we are
52 usually looking at this like at a stack, so it's easier to
53 conceptualise the transition from not-existing to online and back
54 like a linear one.
55
56 The many different states of the device are due to the fact that we
57 need to cover many device types:
58 - logical volumes are created, lvchange -a y $lv, and used
59 - drbd devices are attached to a local disk/remote peer and made primary
60
61 A block device is identified by three items:
62 - the /dev path of the device (dynamic)
63 - a unique ID of the device (static)
64 - it's major/minor pair (dynamic)
65
66 Not all devices implement both the first two as distinct items. LVM
67 logical volumes have their unique ID (the pair volume group, logical
68 volume name) in a 1-to-1 relation to the dev path. For DRBD devices,
69 the /dev path is again dynamic and the unique id is the pair (host1,
70 dev1), (host2, dev2).
71
72 You can get to a device in two ways:
73 - creating the (real) device, which returns you
74 an attached instance (lvcreate)
75 - attaching of a python instance to an existing (real) device
76
77 The second point, the attachment to a device, is different
78 depending on whether the device is assembled or not. At init() time,
79 we search for a device with the same unique_id as us. If found,
80 good. It also means that the device is already assembled. If not,
81 after assembly we'll have our correct major/minor.
82
83 """
84
85 - def __init__(self, unique_id, children, size, params, dyn_params, *args):
86 self._children = children
87 self.dev_path = None
88 self.unique_id = unique_id
89 self.major = None
90 self.minor = None
91 self.attached = False
92 self.size = size
93 self.params = params
94 self.dyn_params = dyn_params
95
97 """Assemble the device from its components.
98
99 Implementations of this method by child classes must ensure that:
100 - after the device has been assembled, it knows its major/minor
101 numbers; this allows other devices (usually parents) to probe
102 correctly for their children
103 - calling this method on an existing, in-use device is safe
104 - if the device is already configured (and in an OK state),
105 this method is idempotent
106
107 """
108 pass
109
111 """Find a device which matches our config and attach to it.
112
113 """
114 raise NotImplementedError
115
117 """Notifies that the device will no longer be used for I/O.
118
119 """
120 raise NotImplementedError
121
122 @classmethod
123 - def Create(cls, unique_id, children, size, spindles, params, excl_stor,
124 dyn_params, *args):
125 """Create the device.
126
127 If the device cannot be created, it will return None
128 instead. Error messages go to the logging system.
129
130 Note that for some devices, the unique_id is used, and for other,
131 the children. The idea is that these two, taken together, are
132 enough for both creation and assembly (later).
133
134 @type unique_id: 2-element tuple or list
135 @param unique_id: unique identifier; the details depend on the actual device
136 type
137 @type children: list of L{BlockDev}
138 @param children: for hierarchical devices, the child devices
139 @type size: float
140 @param size: size in MiB
141 @type spindles: int
142 @param spindles: number of physical disk to dedicate to the device
143 @type params: dict
144 @param params: device-specific options/parameters
145 @type excl_stor: bool
146 @param excl_stor: whether exclusive_storage is active
147 @type dyn_params: dict
148 @param dyn_params: dynamic parameters of the disk only valid for this node.
149 As set by L{objects.Disk.UpdateDynamicDiskParams}.
150 @rtype: L{BlockDev}
151 @return: the created device, or C{None} in case of an error
152
153 """
154 raise NotImplementedError
155
157 """Remove this device.
158
159 This makes sense only for some of the device types: LV and file
160 storage. Also note that if the device can't attach, the removal
161 can't be completed.
162
163 """
164 raise NotImplementedError
165
167 """Rename this device.
168
169 This may or may not make sense for a given device type.
170
171 """
172 raise NotImplementedError
173
174 - def Open(self, force=False, exclusive=True):
175 """Make the device ready for use.
176
177 This makes the device ready for I/O.
178
179 The force parameter signifies that if the device has any kind of
180 --force thing, it should be used, we know what we are doing.
181
182 The exclusive parameter denotes whether the device will
183 be opened for exclusive access (True) or for concurrent shared
184 access by multiple nodes (False) (e.g. during migration).
185
186 @type force: boolean
187
188 """
189 raise NotImplementedError
190
192 """Shut down the device, freeing its children.
193
194 This undoes the `Assemble()` work, except for the child
195 assembling; as such, the children on the device are still
196 assembled after this call.
197
198 """
199 raise NotImplementedError
200
202 """Builds the shell command for importing data to device.
203
204 This method returns the command that will be used by the caller to
205 import data to the target device during the disk template conversion
206 operation.
207
208 Block devices that provide a more efficient way to transfer their
209 data can override this method to use their specific utility.
210
211 @rtype: list of strings
212 @return: List containing the import command for device
213
214 """
215 if not self.minor and not self.Attach():
216 ThrowError("Can't attach to target device during Import()")
217
218
219
220 return [constants.DD_CMD,
221 "of=%s" % self.dev_path,
222 "bs=%s" % constants.DD_BLOCK_SIZE,
223 "oflag=direct", "conv=notrunc"]
224
226 """Builds the shell command for exporting data from device.
227
228 This method returns the command that will be used by the caller to
229 export data from the source device during the disk template conversion
230 operation.
231
232 Block devices that provide a more efficient way to transfer their
233 data can override this method to use their specific utility.
234
235 @rtype: list of strings
236 @return: List containing the export command for device
237
238 """
239 if not self.minor and not self.Attach():
240 ThrowError("Can't attach to source device during Import()")
241
242 return [constants.DD_CMD,
243 "if=%s" % self.dev_path,
244 "bs=%s" % constants.DD_BLOCK_SIZE,
245 "count=%s" % self.size,
246 "iflag=direct"]
247
248 - def Snapshot(self, snap_name, snap_size):
249 """Creates a snapshot of the block device.
250
251 Currently this is used only during LUInstanceExport.
252
253 @type snap_name: string
254 @param snap_name: The name of the snapshot.
255 @type snap_size: int
256 @param snap_size: The size of the snapshot.
257 @rtype: tuple
258 @return: The logical id of the newly created disk.
259
260 """
261 ThrowError("Snapshot is not supported for disk %s of type %s.",
262 self.unique_id, self.__class__.__name__)
263
265 """Adjust the synchronization parameters of the mirror.
266
267 In case this is not a mirroring device, this is no-op.
268
269 @param params: dictionary of LD level disk parameters related to the
270 synchronization.
271 @rtype: list
272 @return: a list of error messages, emitted both by the current node and by
273 children. An empty list means no errors.
274
275 """
276 result = []
277 if self._children:
278 for child in self._children:
279 result.extend(child.SetSyncParams(params))
280 return result
281
283 """Pause/Resume the sync of the mirror.
284
285 In case this is not a mirroring device, this is no-op.
286
287 @type pause: boolean
288 @param pause: Whether to pause or resume
289
290 """
291 result = True
292 if self._children:
293 for child in self._children:
294 result = result and child.PauseResumeSync(pause)
295 return result
296
298 """Returns the sync status of the device.
299
300 If this device is a mirroring device, this function returns the
301 status of the mirror.
302
303 If sync_percent is None, it means the device is not syncing.
304
305 If estimated_time is None, it means we can't estimate
306 the time needed, otherwise it's the time left in seconds.
307
308 If is_degraded is True, it means the device is missing
309 redundancy. This is usually a sign that something went wrong in
310 the device setup, if sync_percent is None.
311
312 The ldisk parameter represents the degradation of the local
313 data. This is only valid for some devices, the rest will always
314 return False (not degraded).
315
316 @rtype: objects.BlockDevStatus
317
318 """
319 return objects.BlockDevStatus(dev_path=self.dev_path,
320 major=self.major,
321 minor=self.minor,
322 sync_percent=None,
323 estimated_time=None,
324 is_degraded=False,
325 ldisk_status=constants.LDS_OKAY)
326
328 """Calculate the mirror status recursively for our children.
329
330 The return value is the same as for `GetSyncStatus()` except the
331 minimum percent and maximum time are calculated across our
332 children.
333
334 @rtype: objects.BlockDevStatus
335
336 """
337 status = self.GetSyncStatus()
338
339 min_percent = status.sync_percent
340 max_time = status.estimated_time
341 is_degraded = status.is_degraded
342 ldisk_status = status.ldisk_status
343
344 if self._children:
345 for child in self._children:
346 child_status = child.GetSyncStatus()
347
348 if min_percent is None:
349 min_percent = child_status.sync_percent
350 elif child_status.sync_percent is not None:
351 min_percent = min(min_percent, child_status.sync_percent)
352
353 if max_time is None:
354 max_time = child_status.estimated_time
355 elif child_status.estimated_time is not None:
356 max_time = max(max_time, child_status.estimated_time)
357
358 is_degraded = is_degraded or child_status.is_degraded
359
360 if ldisk_status is None:
361 ldisk_status = child_status.ldisk_status
362 elif child_status.ldisk_status is not None:
363 ldisk_status = max(ldisk_status, child_status.ldisk_status)
364
365 return objects.BlockDevStatus(dev_path=self.dev_path,
366 major=self.major,
367 minor=self.minor,
368 sync_percent=min_percent,
369 estimated_time=max_time,
370 is_degraded=is_degraded,
371 ldisk_status=ldisk_status)
372
374 """Update metadata with info text.
375
376 Only supported for some device types.
377
378 """
379 for child in self._children:
380 child.SetInfo(text)
381
382 - def Grow(self, amount, dryrun, backingstore, excl_stor):
383 """Grow the block device.
384
385 @type amount: integer
386 @param amount: the amount (in mebibytes) to grow with
387 @type dryrun: boolean
388 @param dryrun: whether to execute the operation in simulation mode
389 only, without actually increasing the size
390 @param backingstore: whether to execute the operation on backing storage
391 only, or on "logical" storage only; e.g. DRBD is logical storage,
392 whereas LVM, file, RBD are backing storage
393 @type excl_stor: boolean
394 @param excl_stor: Whether exclusive_storage is active
395
396 """
397 raise NotImplementedError
398
400 """Return the actual disk size.
401
402 @note: the device needs to be active when this is called
403
404 """
405 assert self.attached, "BlockDevice not attached in GetActualSize()"
406 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path])
407 if result.failed:
408 ThrowError("blockdev failed (%s): %s",
409 result.fail_reason, result.output)
410 try:
411 sz = int(result.output.strip())
412 except (ValueError, TypeError), err:
413 ThrowError("Failed to parse blockdev output: %s", str(err))
414 return sz
415
417 """Return the actual number of spindles used.
418
419 This is not supported by all devices; if not supported, C{None} is returned.
420
421 @note: the device needs to be active when this is called
422
423 """
424 assert self.attached, "BlockDevice not attached in GetActualSpindles()"
425 return None
426
428 """Return the actual disk size and number of spindles used.
429
430 @rtype: tuple
431 @return: (size, spindles); spindles is C{None} when they are not supported
432
433 @note: the device needs to be active when this is called
434
435 """
436 return (self.GetActualSize(), self.GetActualSpindles())
437
439 """Return URIs hypervisors can use to access disks in userspace mode.
440
441 @rtype: string
442 @return: userspace device URI
443 @raise errors.BlockDeviceError: if userspace access is not supported
444
445 """
446 ThrowError("Userspace access with %s block device and %s hypervisor is not "
447 "supported." % (self.__class__.__name__,
448 hypervisor))
449
451 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" %
452 (self.__class__, self.unique_id, self._children,
453 self.major, self.minor, self.dev_path))
454
457 """Log an error to the node daemon and the raise an exception.
458
459 @type msg: string
460 @param msg: the text of the exception
461 @raise errors.BlockDeviceError
462
463 """
464 if args:
465 msg = msg % args
466 logging.error(msg)
467 raise errors.BlockDeviceError(msg)
468
471 """Executes the given function, ignoring BlockDeviceErrors.
472
473 This is used in order to simplify the execution of cleanup or
474 rollback functions.
475
476 @rtype: boolean
477 @return: True when fn didn't raise an exception, False otherwise
478
479 """
480 try:
481 fn(*args, **kwargs)
482 return True
483 except errors.BlockDeviceError, err:
484 logging.warning("Caught BlockDeviceError but ignoring: %s", str(err))
485 return False
486