Package CedarBackup2 :: Package extend :: Module capacity
[hide private]
[frames] | no frames]

Source Code for Module CedarBackup2.extend.capacity

  1  # -*- coding: iso-8859-1 -*- 
  2  # vim: set ft=python ts=3 sw=3 expandtab: 
  3  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
  4  # 
  5  #              C E D A R 
  6  #          S O L U T I O N S       "Software done right." 
  7  #           S O F T W A R E 
  8  # 
  9  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
 10  # 
 11  # Copyright (c) 2008,2010 Kenneth J. Pronovici. 
 12  # All rights reserved. 
 13  # 
 14  # This program is free software; you can redistribute it and/or 
 15  # modify it under the terms of the GNU General Public License, 
 16  # Version 2, as published by the Free Software Foundation. 
 17  # 
 18  # This program is distributed in the hope that it will be useful, 
 19  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 20  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 21  # 
 22  # Copies of the GNU General Public License are available from 
 23  # the Free Software Foundation website, http://www.gnu.org/. 
 24  # 
 25  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
 26  # 
 27  # Author   : Kenneth J. Pronovici <pronovic@ieee.org> 
 28  # Language : Python (>= 2.5) 
 29  # Project  : Cedar Backup, release 2 
 30  # Revision : $Id: capacity.py 1006 2010-07-07 21:03:57Z pronovic $ 
 31  # Purpose  : Provides an extension to check remaining media capacity. 
 32  # 
 33  # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
 34   
 35  ######################################################################## 
 36  # Module documentation 
 37  ######################################################################## 
 38   
 39  """ 
 40  Provides an extension to check remaining media capacity. 
 41   
 42  Some users have asked for advance warning that their media is beginning to fill 
 43  up.  This is an extension that checks the current capacity of the media in the 
 44  writer, and prints a warning if the media is more than X% full, or has fewer 
 45  than X bytes of capacity remaining. 
 46   
 47  @author: Kenneth J. Pronovici <pronovic@ieee.org> 
 48  """ 
 49   
 50  ######################################################################## 
 51  # Imported modules 
 52  ######################################################################## 
 53   
 54  # System modules 
 55  import logging 
 56   
 57  # Cedar Backup modules 
 58  from CedarBackup2.util import displayBytes 
 59  from CedarBackup2.config import ByteQuantity, readByteQuantity, addByteQuantityNode 
 60  from CedarBackup2.xmlutil import createInputDom, addContainerNode, addStringNode 
 61  from CedarBackup2.xmlutil import readFirstChild, readString 
 62  from CedarBackup2.actions.util import createWriter, checkMediaState 
 63   
 64   
 65  ######################################################################## 
 66  # Module-wide constants and variables 
 67  ######################################################################## 
 68   
 69  logger = logging.getLogger("CedarBackup2.log.extend.capacity") 
70 71 72 ######################################################################## 73 # Percentage class definition 74 ######################################################################## 75 76 -class PercentageQuantity(object):
77 78 """ 79 Class representing a percentage quantity. 80 81 The percentage is maintained internally as a string so that issues of 82 precision can be avoided. It really isn't possible to store a floating 83 point number here while being able to losslessly translate back and forth 84 between XML and object representations. (Perhaps the Python 2.4 Decimal 85 class would have been an option, but I originally wanted to stay compatible 86 with Python 2.3.) 87 88 Even though the quantity is maintained as a string, the string must be in a 89 valid floating point positive number. Technically, any floating point 90 string format supported by Python is allowble. However, it does not make 91 sense to have a negative percentage in this context. 92 93 @sort: __init__, __repr__, __str__, __cmp__, quantity 94 """ 95
96 - def __init__(self, quantity=None):
97 """ 98 Constructor for the C{PercentageQuantity} class. 99 @param quantity: Percentage quantity, as a string (i.e. "99.9" or "12") 100 @raise ValueError: If the quantity value is invaid. 101 """ 102 self._quantity = None 103 self.quantity = quantity
104
105 - def __repr__(self):
106 """ 107 Official string representation for class instance. 108 """ 109 return "PercentageQuantity(%s)" % (self.quantity)
110
111 - def __str__(self):
112 """ 113 Informal string representation for class instance. 114 """ 115 return self.__repr__()
116
117 - def __cmp__(self, other):
118 """ 119 Definition of equals operator for this class. 120 Lists within this class are "unordered" for equality comparisons. 121 @param other: Other object to compare to. 122 @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other. 123 """ 124 if other is None: 125 return 1 126 if self.quantity != other.quantity: 127 if self.quantity < other.quantity: 128 return -1 129 else: 130 return 1 131 return 0
132
133 - def _setQuantity(self, value):
134 """ 135 Property target used to set the quantity 136 The value must be a non-empty string if it is not C{None}. 137 @raise ValueError: If the value is an empty string. 138 @raise ValueError: If the value is not a valid floating point number 139 @raise ValueError: If the value is less than zero 140 """ 141 if value is not None: 142 if len(value) < 1: 143 raise ValueError("Percentage must be a non-empty string.") 144 floatValue = float(value) 145 if floatValue < 0.0 or floatValue > 100.0: 146 raise ValueError("Percentage must be a positive value from 0.0 to 100.0") 147 self._quantity = value # keep around string
148
149 - def _getQuantity(self):
150 """ 151 Property target used to get the quantity. 152 """ 153 return self._quantity
154
155 - def _getPercentage(self):
156 """ 157 Property target used to get the quantity as a floating point number. 158 If there is no quantity set, then a value of 0.0 is returned. 159 """ 160 if self.quantity is not None: 161 return float(self.quantity) 162 return 0.0
163 164 quantity = property(_getQuantity, _setQuantity, None, doc="Percentage value, as a string") 165 percentage = property(_getPercentage, None, None, "Percentage value, as a floating point number.")
166
167 168 ######################################################################## 169 # CapacityConfig class definition 170 ######################################################################## 171 172 -class CapacityConfig(object):
173 174 """ 175 Class representing capacity configuration. 176 177 The following restrictions exist on data in this class: 178 179 - The maximum percentage utilized must be a PercentageQuantity 180 - The minimum bytes remaining must be a ByteQuantity 181 182 @sort: __init__, __repr__, __str__, __cmp__, maxPercentage, minBytes 183 """ 184
185 - def __init__(self, maxPercentage=None, minBytes=None):
186 """ 187 Constructor for the C{CapacityConfig} class. 188 189 @param maxPercentage: Maximum percentage of the media that may be utilized 190 @param minBytes: Minimum number of free bytes that must be available 191 """ 192 self._maxPercentage = None 193 self._minBytes = None 194 self.maxPercentage = maxPercentage 195 self.minBytes = minBytes
196
197 - def __repr__(self):
198 """ 199 Official string representation for class instance. 200 """ 201 return "CapacityConfig(%s, %s)" % (self.maxPercentage, self.minBytes)
202
203 - def __str__(self):
204 """ 205 Informal string representation for class instance. 206 """ 207 return self.__repr__()
208
209 - def __cmp__(self, other):
210 """ 211 Definition of equals operator for this class. 212 @param other: Other object to compare to. 213 @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other. 214 """ 215 if other is None: 216 return 1 217 if self.maxPercentage != other.maxPercentage: 218 if self.maxPercentage < other.maxPercentage: 219 return -1 220 else: 221 return 1 222 if self.minBytes != other.minBytes: 223 if self.minBytes < other.minBytes: 224 return -1 225 else: 226 return 1 227 return 0
228
229 - def _setMaxPercentage(self, value):
230 """ 231 Property target used to set the maxPercentage value. 232 If not C{None}, the value must be a C{PercentageQuantity} object. 233 @raise ValueError: If the value is not a C{PercentageQuantity} 234 """ 235 if value is None: 236 self._maxPercentage = None 237 else: 238 if not isinstance(value, PercentageQuantity): 239 raise ValueError("Value must be a C{PercentageQuantity} object.") 240 self._maxPercentage = value
241
242 - def _getMaxPercentage(self):
243 """ 244 Property target used to get the maxPercentage value 245 """ 246 return self._maxPercentage
247
248 - def _setMinBytes(self, value):
249 """ 250 Property target used to set the bytes utilized value. 251 If not C{None}, the value must be a C{ByteQuantity} object. 252 @raise ValueError: If the value is not a C{ByteQuantity} 253 """ 254 if value is None: 255 self._minBytes = None 256 else: 257 if not isinstance(value, ByteQuantity): 258 raise ValueError("Value must be a C{ByteQuantity} object.") 259 self._minBytes = value
260
261 - def _getMinBytes(self):
262 """ 263 Property target used to get the bytes remaining value. 264 """ 265 return self._minBytes
266 267 maxPercentage = property(_getMaxPercentage, _setMaxPercentage, None, "Maximum percentage of the media that may be utilized.") 268 minBytes = property(_getMinBytes, _setMinBytes, None, "Minimum number of free bytes that must be available.")
269
270 271 ######################################################################## 272 # LocalConfig class definition 273 ######################################################################## 274 275 -class LocalConfig(object):
276 277 """ 278 Class representing this extension's configuration document. 279 280 This is not a general-purpose configuration object like the main Cedar 281 Backup configuration object. Instead, it just knows how to parse and emit 282 specific configuration values to this extension. Third parties who need to 283 read and write configuration related to this extension should access it 284 through the constructor, C{validate} and C{addConfig} methods. 285 286 @note: Lists within this class are "unordered" for equality comparisons. 287 288 @sort: __init__, __repr__, __str__, __cmp__, capacity, validate, addConfig 289 """ 290
291 - def __init__(self, xmlData=None, xmlPath=None, validate=True):
292 """ 293 Initializes a configuration object. 294 295 If you initialize the object without passing either C{xmlData} or 296 C{xmlPath} then configuration will be empty and will be invalid until it 297 is filled in properly. 298 299 No reference to the original XML data or original path is saved off by 300 this class. Once the data has been parsed (successfully or not) this 301 original information is discarded. 302 303 Unless the C{validate} argument is C{False}, the L{LocalConfig.validate} 304 method will be called (with its default arguments) against configuration 305 after successfully parsing any passed-in XML. Keep in mind that even if 306 C{validate} is C{False}, it might not be possible to parse the passed-in 307 XML document if lower-level validations fail. 308 309 @note: It is strongly suggested that the C{validate} option always be set 310 to C{True} (the default) unless there is a specific need to read in 311 invalid configuration from disk. 312 313 @param xmlData: XML data representing configuration. 314 @type xmlData: String data. 315 316 @param xmlPath: Path to an XML file on disk. 317 @type xmlPath: Absolute path to a file on disk. 318 319 @param validate: Validate the document after parsing it. 320 @type validate: Boolean true/false. 321 322 @raise ValueError: If both C{xmlData} and C{xmlPath} are passed-in. 323 @raise ValueError: If the XML data in C{xmlData} or C{xmlPath} cannot be parsed. 324 @raise ValueError: If the parsed configuration document is not valid. 325 """ 326 self._capacity = None 327 self.capacity = None 328 if xmlData is not None and xmlPath is not None: 329 raise ValueError("Use either xmlData or xmlPath, but not both.") 330 if xmlData is not None: 331 self._parseXmlData(xmlData) 332 if validate: 333 self.validate() 334 elif xmlPath is not None: 335 xmlData = open(xmlPath).read() 336 self._parseXmlData(xmlData) 337 if validate: 338 self.validate()
339
340 - def __repr__(self):
341 """ 342 Official string representation for class instance. 343 """ 344 return "LocalConfig(%s)" % (self.capacity)
345
346 - def __str__(self):
347 """ 348 Informal string representation for class instance. 349 """ 350 return self.__repr__()
351
352 - def __cmp__(self, other):
353 """ 354 Definition of equals operator for this class. 355 Lists within this class are "unordered" for equality comparisons. 356 @param other: Other object to compare to. 357 @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other. 358 """ 359 if other is None: 360 return 1 361 if self.capacity != other.capacity: 362 if self.capacity < other.capacity: 363 return -1 364 else: 365 return 1 366 return 0
367
368 - def _setCapacity(self, value):
369 """ 370 Property target used to set the capacity configuration value. 371 If not C{None}, the value must be a C{CapacityConfig} object. 372 @raise ValueError: If the value is not a C{CapacityConfig} 373 """ 374 if value is None: 375 self._capacity = None 376 else: 377 if not isinstance(value, CapacityConfig): 378 raise ValueError("Value must be a C{CapacityConfig} object.") 379 self._capacity = value
380
381 - def _getCapacity(self):
382 """ 383 Property target used to get the capacity configuration value. 384 """ 385 return self._capacity
386 387 capacity = property(_getCapacity, _setCapacity, None, "Capacity configuration in terms of a C{CapacityConfig} object.") 388
389 - def validate(self):
390 """ 391 Validates configuration represented by the object. 392 THere must be either a percentage, or a byte capacity, but not both. 393 @raise ValueError: If one of the validations fails. 394 """ 395 if self.capacity is None: 396 raise ValueError("Capacity section is required.") 397 if self.capacity.maxPercentage is None and self.capacity.minBytes is None: 398 raise ValueError("Must provide either max percentage or min bytes.") 399 if self.capacity.maxPercentage is not None and self.capacity.minBytes is not None: 400 raise ValueError("Must provide either max percentage or min bytes, but not both.")
401
402 - def addConfig(self, xmlDom, parentNode):
403 """ 404 Adds a <capacity> configuration section as the next child of a parent. 405 406 Third parties should use this function to write configuration related to 407 this extension. 408 409 We add the following fields to the document:: 410 411 maxPercentage //cb_config/capacity/max_percentage 412 minBytes //cb_config/capacity/min_bytes 413 414 @param xmlDom: DOM tree as from C{impl.createDocument()}. 415 @param parentNode: Parent that the section should be appended to. 416 """ 417 if self.capacity is not None: 418 sectionNode = addContainerNode(xmlDom, parentNode, "capacity") 419 LocalConfig._addPercentageQuantity(xmlDom, sectionNode, "max_percentage", self.capacity.maxPercentage) 420 if self.capacity.minBytes is not None: # because utility function fills in empty section on None 421 addByteQuantityNode(xmlDom, sectionNode, "min_bytes", self.capacity.minBytes)
422
423 - def _parseXmlData(self, xmlData):
424 """ 425 Internal method to parse an XML string into the object. 426 427 This method parses the XML document into a DOM tree (C{xmlDom}) and then 428 calls a static method to parse the capacity configuration section. 429 430 @param xmlData: XML data to be parsed 431 @type xmlData: String data 432 433 @raise ValueError: If the XML cannot be successfully parsed. 434 """ 435 (xmlDom, parentNode) = createInputDom(xmlData) 436 self._capacity = LocalConfig._parseCapacity(parentNode)
437 438 @staticmethod
439 - def _parseCapacity(parentNode):
440 """ 441 Parses a capacity configuration section. 442 443 We read the following fields:: 444 445 maxPercentage //cb_config/capacity/max_percentage 446 minBytes //cb_config/capacity/min_bytes 447 448 @param parentNode: Parent node to search beneath. 449 450 @return: C{CapacityConfig} object or C{None} if the section does not exist. 451 @raise ValueError: If some filled-in value is invalid. 452 """ 453 capacity = None 454 section = readFirstChild(parentNode, "capacity") 455 if section is not None: 456 capacity = CapacityConfig() 457 capacity.maxPercentage = LocalConfig._readPercentageQuantity(section, "max_percentage") 458 capacity.minBytes = readByteQuantity(section, "min_bytes") 459 return capacity
460 461 @staticmethod
462 - def _readPercentageQuantity(parent, name):
463 """ 464 Read a percentage quantity value from an XML document. 465 @param parent: Parent node to search beneath. 466 @param name: Name of node to search for. 467 @return: Percentage quantity parsed from XML document 468 """ 469 quantity = readString(parent, name) 470 if quantity is None: 471 return None 472 return PercentageQuantity(quantity)
473 474 @staticmethod
475 - def _addPercentageQuantity(xmlDom, parentNode, nodeName, percentageQuantity):
476 """ 477 Adds a text node as the next child of a parent, to contain a percentage quantity. 478 479 If the C{percentageQuantity} is None, then no node will be created. 480 481 @param xmlDom: DOM tree as from C{impl.createDocument()}. 482 @param parentNode: Parent node to create child for. 483 @param nodeName: Name of the new container node. 484 @param percentageQuantity: PercentageQuantity object to put into the XML document 485 486 @return: Reference to the newly-created node. 487 """ 488 if percentageQuantity is not None: 489 addStringNode(xmlDom, parentNode, nodeName, percentageQuantity.quantity)
490
491 492 ######################################################################## 493 # Public functions 494 ######################################################################## 495 496 ########################### 497 # executeAction() function 498 ########################### 499 500 -def executeAction(configPath, options, config):
501 """ 502 Executes the capacity action. 503 504 @param configPath: Path to configuration file on disk. 505 @type configPath: String representing a path on disk. 506 507 @param options: Program command-line options. 508 @type options: Options object. 509 510 @param config: Program configuration. 511 @type config: Config object. 512 513 @raise ValueError: Under many generic error conditions 514 @raise IOError: If there are I/O problems reading or writing files 515 """ 516 logger.debug("Executing capacity extended action.") 517 if config.options is None or config.store is None: 518 raise ValueError("Cedar Backup configuration is not properly filled in.") 519 local = LocalConfig(xmlPath=configPath) 520 if config.store.checkMedia: 521 checkMediaState(config.store) # raises exception if media is not initialized 522 capacity = createWriter(config).retrieveCapacity() 523 logger.debug("Media capacity: %s" % capacity) 524 if local.capacity.maxPercentage is not None: 525 if capacity.utilized > local.capacity.maxPercentage.percentage: 526 logger.error("Media has reached capacity limit of %s%%: %.2f%% utilized" % 527 (local.capacity.maxPercentage.quantity, capacity.utilized)) 528 else: # if local.capacity.bytes is not None 529 if capacity.bytesAvailable < local.capacity.minBytes.bytes: 530 logger.error("Media has reached capacity limit of %s: only %s available" % 531 (displayBytes(local.capacity.minBytes.bytes), displayBytes(capacity.bytesAvailable))) 532 logger.info("Executed the capacity extended action successfully.")
533