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):
175 """Make the device ready for use. 176 177 This makes the device ready for I/O. For now, just the DRBD 178 devices need this. 179 180 The force parameter signifies that if the device has any kind of 181 --force thing, it should be used, we know what we are doing. 182 183 @type force: boolean 184 185 """ 186 raise NotImplementedError
187
188 - def Shutdown(self):
189 """Shut down the device, freeing its children. 190 191 This undoes the `Assemble()` work, except for the child 192 assembling; as such, the children on the device are still 193 assembled after this call. 194 195 """ 196 raise NotImplementedError
197
198 - def SetSyncParams(self, params):
199 """Adjust the synchronization parameters of the mirror. 200 201 In case this is not a mirroring device, this is no-op. 202 203 @param params: dictionary of LD level disk parameters related to the 204 synchronization. 205 @rtype: list 206 @return: a list of error messages, emitted both by the current node and by 207 children. An empty list means no errors. 208 209 """ 210 result = [] 211 if self._children: 212 for child in self._children: 213 result.extend(child.SetSyncParams(params)) 214 return result
215
216 - def PauseResumeSync(self, pause):
217 """Pause/Resume the sync of the mirror. 218 219 In case this is not a mirroring device, this is no-op. 220 221 @type pause: boolean 222 @param pause: Whether to pause or resume 223 224 """ 225 result = True 226 if self._children: 227 for child in self._children: 228 result = result and child.PauseResumeSync(pause) 229 return result
230
231 - def GetSyncStatus(self):
232 """Returns the sync status of the device. 233 234 If this device is a mirroring device, this function returns the 235 status of the mirror. 236 237 If sync_percent is None, it means the device is not syncing. 238 239 If estimated_time is None, it means we can't estimate 240 the time needed, otherwise it's the time left in seconds. 241 242 If is_degraded is True, it means the device is missing 243 redundancy. This is usually a sign that something went wrong in 244 the device setup, if sync_percent is None. 245 246 The ldisk parameter represents the degradation of the local 247 data. This is only valid for some devices, the rest will always 248 return False (not degraded). 249 250 @rtype: objects.BlockDevStatus 251 252 """ 253 return objects.BlockDevStatus(dev_path=self.dev_path, 254 major=self.major, 255 minor=self.minor, 256 sync_percent=None, 257 estimated_time=None, 258 is_degraded=False, 259 ldisk_status=constants.LDS_OKAY)
260
261 - def CombinedSyncStatus(self):
262 """Calculate the mirror status recursively for our children. 263 264 The return value is the same as for `GetSyncStatus()` except the 265 minimum percent and maximum time are calculated across our 266 children. 267 268 @rtype: objects.BlockDevStatus 269 270 """ 271 status = self.GetSyncStatus() 272 273 min_percent = status.sync_percent 274 max_time = status.estimated_time 275 is_degraded = status.is_degraded 276 ldisk_status = status.ldisk_status 277 278 if self._children: 279 for child in self._children: 280 child_status = child.GetSyncStatus() 281 282 if min_percent is None: 283 min_percent = child_status.sync_percent 284 elif child_status.sync_percent is not None: 285 min_percent = min(min_percent, child_status.sync_percent) 286 287 if max_time is None: 288 max_time = child_status.estimated_time 289 elif child_status.estimated_time is not None: 290 max_time = max(max_time, child_status.estimated_time) 291 292 is_degraded = is_degraded or child_status.is_degraded 293 294 if ldisk_status is None: 295 ldisk_status = child_status.ldisk_status 296 elif child_status.ldisk_status is not None: 297 ldisk_status = max(ldisk_status, child_status.ldisk_status) 298 299 return objects.BlockDevStatus(dev_path=self.dev_path, 300 major=self.major, 301 minor=self.minor, 302 sync_percent=min_percent, 303 estimated_time=max_time, 304 is_degraded=is_degraded, 305 ldisk_status=ldisk_status)
306
307 - def SetInfo(self, text):
308 """Update metadata with info text. 309 310 Only supported for some device types. 311 312 """ 313 for child in self._children: 314 child.SetInfo(text)
315
316 - def Grow(self, amount, dryrun, backingstore, excl_stor):
317 """Grow the block device. 318 319 @type amount: integer 320 @param amount: the amount (in mebibytes) to grow with 321 @type dryrun: boolean 322 @param dryrun: whether to execute the operation in simulation mode 323 only, without actually increasing the size 324 @param backingstore: whether to execute the operation on backing storage 325 only, or on "logical" storage only; e.g. DRBD is logical storage, 326 whereas LVM, file, RBD are backing storage 327 @type excl_stor: boolean 328 @param excl_stor: Whether exclusive_storage is active 329 330 """ 331 raise NotImplementedError
332
333 - def GetActualSize(self):
334 """Return the actual disk size. 335 336 @note: the device needs to be active when this is called 337 338 """ 339 assert self.attached, "BlockDevice not attached in GetActualSize()" 340 result = utils.RunCmd(["blockdev", "--getsize64", self.dev_path]) 341 if result.failed: 342 ThrowError("blockdev failed (%s): %s", 343 result.fail_reason, result.output) 344 try: 345 sz = int(result.output.strip()) 346 except (ValueError, TypeError), err: 347 ThrowError("Failed to parse blockdev output: %s", str(err)) 348 return sz
349
350 - def GetActualSpindles(self):
351 """Return the actual number of spindles used. 352 353 This is not supported by all devices; if not supported, C{None} is returned. 354 355 @note: the device needs to be active when this is called 356 357 """ 358 assert self.attached, "BlockDevice not attached in GetActualSpindles()" 359 return None
360
361 - def GetActualDimensions(self):
362 """Return the actual disk size and number of spindles used. 363 364 @rtype: tuple 365 @return: (size, spindles); spindles is C{None} when they are not supported 366 367 @note: the device needs to be active when this is called 368 369 """ 370 return (self.GetActualSize(), self.GetActualSpindles())
371
372 - def GetUserspaceAccessUri(self, hypervisor):
373 """Return URIs hypervisors can use to access disks in userspace mode. 374 375 @rtype: string 376 @return: userspace device URI 377 @raise errors.BlockDeviceError: if userspace access is not supported 378 379 """ 380 ThrowError("Userspace access with %s block device and %s hypervisor is not " 381 "supported." % (self.__class__.__name__, 382 hypervisor))
383
384 - def __repr__(self):
385 return ("<%s: unique_id: %s, children: %s, %s:%s, %s>" % 386 (self.__class__, self.unique_id, self._children, 387 self.major, self.minor, self.dev_path))
388
389 390 -def ThrowError(msg, *args):
391 """Log an error to the node daemon and the raise an exception. 392 393 @type msg: string 394 @param msg: the text of the exception 395 @raise errors.BlockDeviceError 396 397 """ 398 if args: 399 msg = msg % args 400 logging.error(msg) 401 raise errors.BlockDeviceError(msg)
402
403 404 -def IgnoreError(fn, *args, **kwargs):
405 """Executes the given function, ignoring BlockDeviceErrors. 406 407 This is used in order to simplify the execution of cleanup or 408 rollback functions. 409 410 @rtype: boolean 411 @return: True when fn didn't raise an exception, False otherwise 412 413 """ 414 try: 415 fn(*args, **kwargs) 416 return True 417 except errors.BlockDeviceError, err: 418 logging.warning("Caught BlockDeviceError but ignoring: %s", str(err)) 419 return False
420