Package ganeti :: Package storage :: Module base
[hide private]
[frames] | no frames]

Source Code for Module ganeti.storage.base

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2006, 2007, 2010, 2011, 2012, 2013 Google Inc. 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are 
  9  # met: 
 10  # 
 11  # 1. Redistributions of source code must retain the above copyright notice, 
 12  # this list of conditions and the following disclaimer. 
 13  # 
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 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 
39 40 41 -class BlockDev(object):
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 # pylint: disable=W0613
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
96 - def Assemble(self):
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
110 - def Attach(self):
111 """Find a device which matches our config and attach to it. 112 113 """ 114 raise NotImplementedError
115
116 - def Close(self):
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
156 - def Remove(self):
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
166 - def Rename(self, new_id):
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
191 - def Shutdown(self):
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
201 - def Import(self):
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 # we use the 'notrunc' argument to not attempt to truncate on the 219 # given device 220 return [constants.DD_CMD, 221 "of=%s" % self.dev_path, 222 "bs=%s" % constants.DD_BLOCK_SIZE, 223 "oflag=direct", "conv=notrunc"]
224
225 - def Export(self):
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
264 - def SetSyncParams(self, params):
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
282 - def PauseResumeSync(self, pause):
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
297 - def GetSyncStatus(self):
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
327 - def CombinedSyncStatus(self):
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
373 - def SetInfo(self, text):
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
399 - def GetActualSize(self):
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
416 - def GetActualSpindles(self):
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
427 - def GetActualDimensions(self):
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
438 - def GetUserspaceAccessUri(self, hypervisor):
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
450 - def __repr__(self):
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
455 456 -def ThrowError(msg, *args):
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
469 470 -def IgnoreError(fn, *args, **kwargs):
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