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   
 32   
 33   
 34   
 35   
 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   
 51   
 52   
 53   
 54  import logging 
 55   
 56   
 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   
 66   
 67   
 68  logger = logging.getLogger("CedarBackup2.log.extend.capacity") 
 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   
 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   
105        """ 
106        Official string representation for class instance. 
107        """ 
108        return "PercentageQuantity(%s)" % (self.quantity) 
 109   
111        """ 
112        Informal string representation for class instance. 
113        """ 
114        return self.__repr__() 
 115   
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   
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  
 147   
149        """ 
150        Property target used to get the quantity. 
151        """ 
152        return self._quantity 
 153   
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   
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   
197        """ 
198        Official string representation for class instance. 
199        """ 
200        return "CapacityConfig(%s, %s)" % (self.maxPercentage, self.minBytes) 
 201   
203        """ 
204        Informal string representation for class instance. 
205        """ 
206        return self.__repr__() 
 207   
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   
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   
242        """ 
243        Property target used to get the maxPercentage value 
244        """ 
245        return self._maxPercentage 
 246   
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   
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   
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   
340        """ 
341        Official string representation for class instance. 
342        """ 
343        return "LocalConfig(%s)" % (self.capacity) 
 344   
346        """ 
347        Informal string representation for class instance. 
348        """ 
349        return self.__repr__() 
 350   
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   
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   
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   
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   
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:  
420              addByteQuantityNode(xmlDom, sectionNode, "min_bytes", self.capacity.minBytes) 
 421   
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 
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 
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 
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   
493   
494   
495   
496   
497   
498   
499   
500 -def executeAction(configPath, options, config): 
 533