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