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