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 """
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
52
53
54
55 import logging
56
57
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
67
68
69 logger = logging.getLogger("CedarBackup2.log.extend.capacity")
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
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
106 """
107 Official string representation for class instance.
108 """
109 return "PercentageQuantity(%s)" % (self.quantity)
110
112 """
113 Informal string representation for class instance.
114 """
115 return self.__repr__()
116
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
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
148
150 """
151 Property target used to get the quantity.
152 """
153 return self._quantity
154
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
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
198 """
199 Official string representation for class instance.
200 """
201 return "CapacityConfig(%s, %s)" % (self.maxPercentage, self.minBytes)
202
204 """
205 Informal string representation for class instance.
206 """
207 return self.__repr__()
208
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
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
243 """
244 Property target used to get the maxPercentage value
245 """
246 return self._maxPercentage
247
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
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
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
341 """
342 Official string representation for class instance.
343 """
344 return "LocalConfig(%s)" % (self.capacity)
345
347 """
348 Informal string representation for class instance.
349 """
350 return self.__repr__()
351
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
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
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
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
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:
421 addByteQuantityNode(xmlDom, sectionNode, "min_bytes", self.capacity.minBytes)
422
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
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
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
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
494
495
496
497
498
499
500 -def executeAction(configPath, options, config):
533