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