# -*- coding: iso-8859-1 -*-
# vim: set ft=python ts=3 sw=3 expandtab:
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# C E D A R
# S O L U T I O N S "Software done right."
# S O F T W A R E
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Copyright (c) 2004-2008,2010,2015 Kenneth J. Pronovici.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
# Version 2, as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Copies of the GNU General Public License are available from
# the Free Software Foundation website, http://www.gnu.org/.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Author : Kenneth J. Pronovici <pronovic@ieee.org>
# Language : Python 3 (>= 3.4)
# Project : Cedar Backup, release 3
# Purpose : Provides configuration-related objects.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
########################################################################
# Module documentation
########################################################################
"""
Provides configuration-related objects.
Summary
=======
Cedar Backup stores all of its configuration in an XML document typically
called ``cback3.conf``. The standard location for this document is in
``/etc``, but users can specify a different location if they want to.
The ``Config`` class is a Python object representation of a Cedar Backup XML
configuration file. The representation is two-way: XML data can be used to
create a ``Config`` object, and then changes to the object can be propogated
back to disk. A ``Config`` object can even be used to create a configuration
file from scratch programmatically.
The ``Config`` class is intended to be the only Python-language interface to
Cedar Backup configuration on disk. Cedar Backup will use the class as its
internal representation of configuration, and applications external to Cedar
Backup itself (such as a hypothetical third-party configuration tool written
in Python or a third party extension module) should also use the class when
they need to read and write configuration files.
Backwards Compatibility
=======================
The configuration file format has changed between Cedar Backup 1.x and Cedar
Backup 2.x. Any Cedar Backup 1.x configuration file is also a valid Cedar
Backup 2.x configuration file. However, it doesn't work to go the other
direction, as the 2.x configuration files contains additional configuration
is not accepted by older versions of the software.
XML Configuration Structure
===========================
A ``Config`` object can either be created "empty", or can be created based on
XML input (either in the form of a string or read in from a file on disk).
Generally speaking, the XML input *must* result in a ``Config`` object which
passes the validations laid out below in the *Validation* section.
An XML configuration file is composed of seven sections:
- *reference*: specifies reference information about the file (author, revision, etc)
- *extensions*: specifies mappings to Cedar Backup extensions (external code)
- *options*: specifies global configuration options
- *peers*: specifies the set of peers in a master's backup pool
- *collect*: specifies configuration related to the collect action
- *stage*: specifies configuration related to the stage action
- *store*: specifies configuration related to the store action
- *purge*: specifies configuration related to the purge action
Each section is represented by an class in this module, and then the overall
``Config`` class is a composition of the various other classes.
Any configuration section that is missing in the XML document (or has not
been filled into an "empty" document) will just be set to ``None`` in the
object representation. The same goes for individual fields within each
configuration section. Keep in mind that the document might not be
completely valid if some sections or fields aren't filled in - but that
won't matter until validation takes place (see the *Validation* section
below).
Unicode vs. String Data
=======================
By default, all string data that comes out of XML documents in Python is
unicode data (i.e. ``u"whatever"``). This is fine for many things, but when
it comes to filesystem paths, it can cause us some problems. We really want
strings to be encoded in the filesystem encoding rather than being unicode.
So, most elements in configuration which represent filesystem paths are
coverted to plain strings using :any:`util.encodePath`. The main exception is
the various ``absoluteExcludePath`` and ``relativeExcludePath`` lists. These
are *not* converted, because they are generally only used for filtering,
not for filesystem operations.
Validation
==========
There are two main levels of validation in the ``Config`` class and its
children. The first is field-level validation. Field-level validation
comes into play when a given field in an object is assigned to or updated.
We use Python's ``property`` functionality to enforce specific validations on
field values, and in some places we even use customized list classes to
enforce validations on list members. You should expect to catch a
``ValueError`` exception when making assignments to configuration class
fields.
The second level of validation is post-completion validation. Certain
validations don't make sense until a document is fully "complete". We don't
want these validations to apply all of the time, because it would make
building up a document from scratch a real pain. For instance, we might
have to do things in the right order to keep from throwing exceptions, etc.
All of these post-completion validations are encapsulated in the
:any:`Config.validate` method. This method can be called at any time by a
client, and will always be called immediately after creating a ``Config``
object from XML data and before exporting a ``Config`` object to XML. This
way, we get decent ease-of-use but we also don't accept or emit invalid
configuration files.
The :any:`Config.validate` implementation actually takes two passes to
completely validate a configuration document. The first pass at validation
is to ensure that the proper sections are filled into the document. There
are default requirements, but the caller has the opportunity to override
these defaults.
The second pass at validation ensures that any filled-in section contains
valid data. Any section which is not set to ``None`` is validated according
to the rules for that section (see below).
*Reference Validations*
No validations.
*Extensions Validations*
The list of actions may be either ``None`` or an empty list ``[]`` if desired.
Each extended action must include a name, a module and a function. Then, an
extended action must include either an index or dependency information.
Which one is required depends on which order mode is configured.
*Options Validations*
All fields must be filled in except the rsh command. The rcp and rsh
commands are used as default values for all remote peers. Remote peers can
also rely on the backup user as the default remote user name if they choose.
*Peers Validations*
Local peers must be completely filled in, including both name and collect
directory. Remote peers must also fill in the name and collect directory,
but can leave the remote user and rcp command unset. In this case, the
remote user is assumed to match the backup user from the options section and
rcp command is taken directly from the options section.
*Collect Validations*
The target directory must be filled in. The collect mode, archive mode and
ignore file are all optional. The list of absolute paths to exclude and
patterns to exclude may be either ``None`` or an empty list ``[]`` if desired.
Each collect directory entry must contain an absolute path to collect, and
then must either be able to take collect mode, archive mode and ignore file
configuration from the parent ``CollectConfig`` object, or must set each
value on its own. The list of absolute paths to exclude, relative paths to
exclude and patterns to exclude may be either ``None`` or an empty list ``[]``
if desired. Any list of absolute paths to exclude or patterns to exclude
will be combined with the same list in the ``CollectConfig`` object to make
the complete list for a given directory.
*Stage Validations*
The target directory must be filled in. There must be at least one peer
(remote or local) between the two lists of peers. A list with no entries
can be either ``None`` or an empty list ``[]`` if desired.
If a set of peers is provided, this configuration completely overrides
configuration in the peers configuration section, and the same validations
apply.
*Store Validations*
The device type and drive speed are optional, and all other values are
required (missing booleans will be set to defaults, which is OK).
The image writer functionality in the ``writer`` module is supposed to be
able to handle a device speed of ``None``. Any caller which needs a "real"
(non-``None``) value for the device type can use ``DEFAULT_DEVICE_TYPE``,
which is guaranteed to be sensible.
*Purge Validations*
The list of purge directories may be either ``None`` or an empty list ``[]``
if desired. All purge directories must contain a path and a retain days
value.
Module Attributes
=================
Attributes:
DEFAULT_DEVICE_TYPE: The default device type
DEFAULT_MEDIA_TYPE: The default media type
VALID_DEVICE_TYPES: List of valid device types
VALID_MEDIA_TYPES: List of valid media types
VALID_COLLECT_MODES: List of valid collect modes
VALID_COMPRESS_MODES: List of valid compress modes
VALID_ARCHIVE_MODES: List of valid archive modes
VALID_ORDER_MODES: List of valid extension order modes
:author: Kenneth J. Pronovici <pronovic@ieee.org>
"""
########################################################################
# Imported modules
########################################################################
# System modules
import os
import re
import logging
from functools import total_ordering
# Cedar Backup modules
from CedarBackup3.writers.util import validateScsiId, validateDriveSpeed
from CedarBackup3.util import UnorderedList, AbsolutePathList, ObjectTypeList, parseCommaSeparatedString
from CedarBackup3.util import RegexMatchList, RegexList, encodePath, checkUnique
from CedarBackup3.util import convertSize, displayBytes, UNIT_BYTES, UNIT_KBYTES, UNIT_MBYTES, UNIT_GBYTES
from CedarBackup3.xmlutil import isElement, readChildren, readFirstChild
from CedarBackup3.xmlutil import readStringList, readString, readInteger, readBoolean
from CedarBackup3.xmlutil import addContainerNode, addStringNode, addIntegerNode, addBooleanNode
from CedarBackup3.xmlutil import createInputDom, createOutputDom, serializeDom
########################################################################
# Module-wide constants and variables
########################################################################
logger = logging.getLogger("CedarBackup3.log.config")
DEFAULT_DEVICE_TYPE = "cdwriter"
DEFAULT_MEDIA_TYPE = "cdrw-74"
VALID_DEVICE_TYPES = [ "cdwriter", "dvdwriter", ]
VALID_CD_MEDIA_TYPES = [ "cdr-74", "cdrw-74", "cdr-80", "cdrw-80", ]
VALID_DVD_MEDIA_TYPES = [ "dvd+r", "dvd+rw", ]
VALID_MEDIA_TYPES = VALID_CD_MEDIA_TYPES + VALID_DVD_MEDIA_TYPES
VALID_COLLECT_MODES = [ "daily", "weekly", "incr", ]
VALID_ARCHIVE_MODES = [ "tar", "targz", "tarbz2", ]
VALID_COMPRESS_MODES = [ "none", "gzip", "bzip2", ]
VALID_ORDER_MODES = [ "index", "dependency", ]
VALID_BLANK_MODES = [ "daily", "weekly", ]
VALID_BYTE_UNITS = [ UNIT_BYTES, UNIT_KBYTES, UNIT_MBYTES, UNIT_GBYTES, ]
VALID_FAILURE_MODES = [ "none", "all", "daily", "weekly", ]
REWRITABLE_MEDIA_TYPES = [ "cdrw-74", "cdrw-80", "dvd+rw", ]
ACTION_NAME_REGEX = r"^[a-z0-9]*$"
########################################################################
# ByteQuantity class definition
########################################################################
@total_ordering
[docs]class ByteQuantity(object):
"""
Class representing a byte quantity.
A byte quantity has both a quantity and a byte-related unit. Units are
maintained using the constants from util.py. If no units are provided,
``UNIT_BYTES`` is assumed.
The quantity is maintained internally as a string so that issues of
precision can be avoided. It really isn't possible to store a floating
point number here while being able to losslessly translate back and forth
between XML and object representations. (Perhaps the Python 2.4 Decimal
class would have been an option, but I originally wanted to stay compatible
with Python 2.3.)
Even though the quantity is maintained as a string, the string must be in a
valid floating point positive number. Technically, any floating point
string format supported by Python is allowble. However, it does not make
sense to have a negative quantity of bytes in this context.
"""
[docs] def __init__(self, quantity=None, units=None):
"""
Constructor for the ``ByteQuantity`` class.
Args:
quantity: Quantity of bytes, something interpretable as a float
units: Unit of bytes, one of VALID_BYTE_UNITS
Raises:
ValueError: If one of the values is invalid
"""
self._quantity = None
self._units = None
self.quantity = quantity
self.units = units
def __repr__(self):
"""
Official string representation for class instance.
"""
return "ByteQuantity(%s, %s)" % (self.quantity, self.units)
def __str__(self):
"""
Informal string representation for class instance.
"""
return "%s" % displayBytes(self.bytes)
def __eq__(self, other):
"""Equals operator, implemented in terms of Python 2-style compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of Python 2-style compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of Python 2-style compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Python 2-style comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
elif isinstance(other, ByteQuantity):
if self.bytes != other.bytes:
if self.bytes < other.bytes:
return -1
else:
return 1
return 0
else:
return self.__cmp__(ByteQuantity(other, UNIT_BYTES)) # will fail if other can't be coverted to float
def _setQuantity(self, value):
"""
Property target used to set the quantity
The value must be interpretable as a float if it is not None
Raises:
ValueError: If the value is an empty string
ValueError: If the value is not a valid floating point number
ValueError: If the value is less than zero
"""
if value is None:
self._quantity = None
else:
try:
floatValue = float(value) # allow integer, float, string, etc.
except:
raise ValueError("Quantity must be interpretable as a float")
if floatValue < 0.0:
raise ValueError("Quantity cannot be negative.")
self._quantity = str(value) # keep around string
def _getQuantity(self):
"""
Property target used to get the quantity.
"""
return self._quantity
def _setUnits(self, value):
"""
Property target used to set the units value.
If not ``None``, the units value must be one of the values in :any:`VALID_BYTE_UNITS`.
Raises:
ValueError: If the value is not valid
"""
if value is None:
self._units = UNIT_BYTES
else:
if value not in VALID_BYTE_UNITS:
raise ValueError("Units value must be one of %s." % VALID_BYTE_UNITS)
self._units = value
def _getUnits(self):
"""
Property target used to get the units value.
"""
return self._units
def _getBytes(self):
"""
Property target used to return the byte quantity as a floating point number.
If there is no quantity set, then a value of 0.0 is returned.
"""
if self.quantity is not None and self.units is not None:
return convertSize(self.quantity, self.units, UNIT_BYTES)
return 0.0
quantity = property(_getQuantity, _setQuantity, None, doc="Byte quantity, as a string")
units = property(_getUnits, _setUnits, None, doc="Units for byte quantity, for instance UNIT_BYTES")
bytes = property(_getBytes, None, None, doc="Byte quantity, as a floating point number.")
########################################################################
# ActionDependencies class definition
########################################################################
@total_ordering
[docs]class ActionDependencies(object):
"""
Class representing dependencies associated with an extended action.
Execution ordering for extended actions is done in one of two ways: either by using
index values (lower index gets run first) or by having the extended action specify
dependencies in terms of other named actions. This class encapsulates the dependency
information for an extended action.
The following restrictions exist on data in this class:
- Any action name must be a non-empty string matching ``ACTION_NAME_REGEX``
"""
[docs] def __init__(self, beforeList=None, afterList=None):
"""
Constructor for the ``ActionDependencies`` class.
Args:
beforeList: List of named actions that this action must be run before
afterList: List of named actions that this action must be run after
Raises:
ValueError: If one of the values is invalid
"""
self._beforeList = None
self._afterList = None
self.beforeList = beforeList
self.afterList = afterList
def __repr__(self):
"""
Official string representation for class instance.
"""
return "ActionDependencies(%s, %s)" % (self.beforeList, self.afterList)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.beforeList != other.beforeList:
if self.beforeList < other.beforeList:
return -1
else:
return 1
if self.afterList != other.afterList:
if self.afterList < other.afterList:
return -1
else:
return 1
return 0
def _setBeforeList(self, value):
"""
Property target used to set the "run before" list.
Either the value must be ``None`` or each element must be a string matching ACTION_NAME_REGEX.
Raises:
ValueError: If the value does not match the regular expression
"""
if value is None:
self._beforeList = None
else:
try:
saved = self._beforeList
self._beforeList = RegexMatchList(ACTION_NAME_REGEX, emptyAllowed=False, prefix="Action name")
self._beforeList.extend(value)
except Exception as e:
self._beforeList = saved
raise e
def _getBeforeList(self):
"""
Property target used to get the "run before" list.
"""
return self._beforeList
def _setAfterList(self, value):
"""
Property target used to set the "run after" list.
Either the value must be ``None`` or each element must be a string matching ACTION_NAME_REGEX.
Raises:
ValueError: If the value does not match the regular expression
"""
if value is None:
self._afterList = None
else:
try:
saved = self._afterList
self._afterList = RegexMatchList(ACTION_NAME_REGEX, emptyAllowed=False, prefix="Action name")
self._afterList.extend(value)
except Exception as e:
self._afterList = saved
raise e
def _getAfterList(self):
"""
Property target used to get the "run after" list.
"""
return self._afterList
beforeList = property(_getBeforeList, _setBeforeList, None, "List of named actions that this action must be run before.")
afterList = property(_getAfterList, _setAfterList, None, "List of named actions that this action must be run after.")
########################################################################
# ActionHook class definition
########################################################################
@total_ordering
[docs]class ActionHook(object):
"""
Class representing a hook associated with an action.
A hook associated with an action is a shell command to be executed either
before or after a named action is executed.
The following restrictions exist on data in this class:
- The action name must be a non-empty string matching ``ACTION_NAME_REGEX``
- The shell command must be a non-empty string.
The internal ``before`` and ``after`` instance variables are always set to
False in this parent class.
"""
[docs] def __init__(self, action=None, command=None):
"""
Constructor for the ``ActionHook`` class.
Args:
action: Action this hook is associated with
command: Shell command to execute
Raises:
ValueError: If one of the values is invalid
"""
self._action = None
self._command = None
self._before = False
self._after = False
self.action = action
self.command = command
def __repr__(self):
"""
Official string representation for class instance.
"""
return "ActionHook(%s, %s, %s, %s)" % (self.action, self.command, self.before, self.after)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.action != other.action:
if str(self.action or "") < str(other.action or ""):
return -1
else:
return 1
if self.command != other.command:
if str(self.command or "") < str(other.command or ""):
return -1
else:
return 1
if self.before != other.before:
if self.before < other.before:
return -1
else:
return 1
if self.after != other.after:
if self.after < other.after:
return -1
else:
return 1
return 0
def _setAction(self, value):
"""
Property target used to set the action name.
The value must be a non-empty string if it is not ``None``.
It must also consist only of lower-case letters and digits.
Raises:
ValueError: If the value is an empty string
"""
pattern = re.compile(ACTION_NAME_REGEX)
if value is not None:
if len(value) < 1:
raise ValueError("The action name must be a non-empty string.")
if not pattern.search(value):
raise ValueError("The action name must consist of only lower-case letters and digits.")
self._action = value
def _getAction(self):
"""
Property target used to get the action name.
"""
return self._action
def _setCommand(self, value):
"""
Property target used to set the command.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("The command must be a non-empty string.")
self._command = value
def _getCommand(self):
"""
Property target used to get the command.
"""
return self._command
def _getBefore(self):
"""
Property target used to get the before flag.
"""
return self._before
def _getAfter(self):
"""
Property target used to get the after flag.
"""
return self._after
action = property(_getAction, _setAction, None, "Action this hook is associated with.")
command = property(_getCommand, _setCommand, None, "Shell command to execute.")
before = property(_getBefore, None, None, "Indicates whether command should be executed before action.")
after = property(_getAfter, None, None, "Indicates whether command should be executed after action.")
@total_ordering
[docs]class PreActionHook(ActionHook):
"""
Class representing a pre-action hook associated with an action.
A hook associated with an action is a shell command to be executed either
before or after a named action is executed. In this case, a pre-action hook
is executed before the named action.
The following restrictions exist on data in this class:
- The action name must be a non-empty string consisting of lower-case letters and digits.
- The shell command must be a non-empty string.
The internal ``before`` instance variable is always set to True in this
class.
"""
[docs] def __init__(self, action=None, command=None):
"""
Constructor for the ``PreActionHook`` class.
Args:
action: Action this hook is associated with
command: Shell command to execute
Raises:
ValueError: If one of the values is invalid
"""
ActionHook.__init__(self, action, command)
self._before = True
def __repr__(self):
"""
Official string representation for class instance.
"""
return "PreActionHook(%s, %s, %s, %s)" % (self.action, self.command, self.before, self.after)
@total_ordering
[docs]class PostActionHook(ActionHook):
"""
Class representing a pre-action hook associated with an action.
A hook associated with an action is a shell command to be executed either
before or after a named action is executed. In this case, a post-action hook
is executed after the named action.
The following restrictions exist on data in this class:
- The action name must be a non-empty string consisting of lower-case letters and digits.
- The shell command must be a non-empty string.
The internal ``before`` instance variable is always set to True in this
class.
"""
[docs] def __init__(self, action=None, command=None):
"""
Constructor for the ``PostActionHook`` class.
Args:
action: Action this hook is associated with
command: Shell command to execute
Raises:
ValueError: If one of the values is invalid
"""
ActionHook.__init__(self, action, command)
self._after = True
def __repr__(self):
"""
Official string representation for class instance.
"""
return "PostActionHook(%s, %s, %s, %s)" % (self.action, self.command, self.before, self.after)
########################################################################
# BlankBehavior class definition
########################################################################
@total_ordering
[docs]class BlankBehavior(object):
"""
Class representing optimized store-action media blanking behavior.
The following restrictions exist on data in this class:
- The blanking mode must be a one of the values in ``VALID_BLANK_MODES``
- The blanking factor must be a positive floating point number
"""
[docs] def __init__(self, blankMode=None, blankFactor=None):
"""
Constructor for the ``BlankBehavior`` class.
Args:
blankMode: Blanking mode
blankFactor: Blanking factor
Raises:
ValueError: If one of the values is invalid
"""
self._blankMode = None
self._blankFactor = None
self.blankMode = blankMode
self.blankFactor = blankFactor
def __repr__(self):
"""
Official string representation for class instance.
"""
return "BlankBehavior(%s, %s)" % (self.blankMode, self.blankFactor)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.blankMode != other.blankMode:
if str(self.blankMode or "") < str(other.blankMode or ""):
return -1
else:
return 1
if self.blankFactor != other.blankFactor:
if float(self.blankFactor or 0.0) < float(other.blankFactor or 0.0):
return -1
else:
return 1
return 0
def _setBlankMode(self, value):
"""
Property target used to set the blanking mode.
The value must be one of ``VALID_BLANK_MODES``.
Raises:
ValueError: If the value is not valid
"""
if value is not None:
if value not in VALID_BLANK_MODES:
raise ValueError("Blanking mode must be one of %s." % VALID_BLANK_MODES)
self._blankMode = value
def _getBlankMode(self):
"""
Property target used to get the blanking mode.
"""
return self._blankMode
def _setBlankFactor(self, value):
"""
Property target used to set the blanking factor.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
ValueError: If the value is not a valid floating point number
ValueError: If the value is less than zero
"""
if value is not None:
if len(value) < 1:
raise ValueError("Blanking factor must be a non-empty string.")
floatValue = float(value)
if floatValue < 0.0:
raise ValueError("Blanking factor cannot be negative.")
self._blankFactor = value # keep around string
def _getBlankFactor(self):
"""
Property target used to get the blanking factor.
"""
return self._blankFactor
blankMode = property(_getBlankMode, _setBlankMode, None, "Blanking mode")
blankFactor = property(_getBlankFactor, _setBlankFactor, None, "Blanking factor")
########################################################################
# ExtendedAction class definition
########################################################################
@total_ordering
[docs]class ExtendedAction(object):
"""
Class representing an extended action.
Essentially, an extended action needs to allow the following to happen::
exec("from %s import %s" % (module, function))
exec("%s(action, configPath")" % function)
The following restrictions exist on data in this class:
- The action name must be a non-empty string consisting of lower-case letters and digits.
- The module must be a non-empty string and a valid Python identifier.
- The function must be an on-empty string and a valid Python identifier.
- If set, the index must be a positive integer.
- If set, the dependencies attribute must be an ``ActionDependencies`` object.
"""
[docs] def __init__(self, name=None, module=None, function=None, index=None, dependencies=None):
"""
Constructor for the ``ExtendedAction`` class.
Args:
name: Name of the extended action
module: Name of the module containing the extended action function
function: Name of the extended action function
index: Index of action, used for execution ordering
dependencies: Dependencies for action, used for execution ordering
Raises:
ValueError: If one of the values is invalid
"""
self._name = None
self._module = None
self._function = None
self._index = None
self._dependencies = None
self.name = name
self.module = module
self.function = function
self.index = index
self.dependencies = dependencies
def __repr__(self):
"""
Official string representation for class instance.
"""
return "ExtendedAction(%s, %s, %s, %s, %s)" % (self.name, self.module, self.function, self.index, self.dependencies)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.name != other.name:
if str(self.name or "") < str(other.name or ""):
return -1
else:
return 1
if self.module != other.module:
if str(self.module or "") < str(other.module or ""):
return -1
else:
return 1
if self.function != other.function:
if str(self.function or "") < str(other.function or ""):
return -1
else:
return 1
if self.index != other.index:
if int(self.index or 0) < int(other.index or 0):
return -1
else:
return 1
if self.dependencies != other.dependencies:
if self.dependencies < other.dependencies:
return -1
else:
return 1
return 0
def _setName(self, value):
"""
Property target used to set the action name.
The value must be a non-empty string if it is not ``None``.
It must also consist only of lower-case letters and digits.
Raises:
ValueError: If the value is an empty string
"""
pattern = re.compile(ACTION_NAME_REGEX)
if value is not None:
if len(value) < 1:
raise ValueError("The action name must be a non-empty string.")
if not pattern.search(value):
raise ValueError("The action name must consist of only lower-case letters and digits.")
self._name = value
def _getName(self):
"""
Property target used to get the action name.
"""
return self._name
def _setModule(self, value):
"""
Property target used to set the module name.
The value must be a non-empty string if it is not ``None``.
It must also be a valid Python identifier.
Raises:
ValueError: If the value is an empty string
"""
pattern = re.compile(r"^([A-Za-z_][A-Za-z0-9_]*)(\.[A-Za-z_][A-Za-z0-9_]*)*$")
if value is not None:
if len(value) < 1:
raise ValueError("The module name must be a non-empty string.")
if not pattern.search(value):
raise ValueError("The module name must be a valid Python identifier.")
self._module = value
def _getModule(self):
"""
Property target used to get the module name.
"""
return self._module
def _setFunction(self, value):
"""
Property target used to set the function name.
The value must be a non-empty string if it is not ``None``.
It must also be a valid Python identifier.
Raises:
ValueError: If the value is an empty string
"""
pattern = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
if value is not None:
if len(value) < 1:
raise ValueError("The function name must be a non-empty string.")
if not pattern.search(value):
raise ValueError("The function name must be a valid Python identifier.")
self._function = value
def _getFunction(self):
"""
Property target used to get the function name.
"""
return self._function
def _setIndex(self, value):
"""
Property target used to set the action index.
The value must be an integer >= 0.
Raises:
ValueError: If the value is not valid
"""
if value is None:
self._index = None
else:
try:
value = int(value)
except TypeError:
raise ValueError("Action index value must be an integer >= 0.")
if value < 0:
raise ValueError("Action index value must be an integer >= 0.")
self._index = value
def _getIndex(self):
"""
Property target used to get the action index.
"""
return self._index
def _setDependencies(self, value):
"""
Property target used to set the action dependencies information.
If not ``None``, the value must be a ``ActionDependecies`` object.
Raises:
ValueError: If the value is not a ``ActionDependencies`` object
"""
if value is None:
self._dependencies = None
else:
if not isinstance(value, ActionDependencies):
raise ValueError("Value must be a ``ActionDependencies`` object.")
self._dependencies = value
def _getDependencies(self):
"""
Property target used to get action dependencies information.
"""
return self._dependencies
name = property(_getName, _setName, None, "Name of the extended action.")
module = property(_getModule, _setModule, None, "Name of the module containing the extended action function.")
function = property(_getFunction, _setFunction, None, "Name of the extended action function.")
index = property(_getIndex, _setIndex, None, "Index of action, used for execution ordering.")
dependencies = property(_getDependencies, _setDependencies, None, "Dependencies for action, used for execution ordering.")
########################################################################
# CommandOverride class definition
########################################################################
@total_ordering
[docs]class CommandOverride(object):
"""
Class representing a piece of Cedar Backup command override configuration.
The following restrictions exist on data in this class:
- The absolute path must be absolute
*Note:* Lists within this class are "unordered" for equality comparisons.
"""
[docs] def __init__(self, command=None, absolutePath=None):
"""
Constructor for the ``CommandOverride`` class.
Args:
command: Name of command to be overridden
absolutePath: Absolute path of the overrridden command
Raises:
ValueError: If one of the values is invalid
"""
self._command = None
self._absolutePath = None
self.command = command
self.absolutePath = absolutePath
def __repr__(self):
"""
Official string representation for class instance.
"""
return "CommandOverride(%s, %s)" % (self.command, self.absolutePath)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.command != other.command:
if str(self.command or "") < str(other.command or ""):
return -1
else:
return 1
if self.absolutePath != other.absolutePath:
if str(self.absolutePath or "") < str(other.absolutePath or ""):
return -1
else:
return 1
return 0
def _setCommand(self, value):
"""
Property target used to set the command.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("The command must be a non-empty string.")
self._command = value
def _getCommand(self):
"""
Property target used to get the command.
"""
return self._command
def _setAbsolutePath(self, value):
"""
Property target used to set the absolute path.
The value must be an absolute path if it is not ``None``.
It does not have to exist on disk at the time of assignment.
Raises:
ValueError: If the value is not an absolute path
ValueError: If the value cannot be encoded properly
"""
if value is not None:
if not os.path.isabs(value):
raise ValueError("Not an absolute path: [%s]" % value)
self._absolutePath = encodePath(value)
def _getAbsolutePath(self):
"""
Property target used to get the absolute path.
"""
return self._absolutePath
command = property(_getCommand, _setCommand, None, doc="Name of command to be overridden.")
absolutePath = property(_getAbsolutePath, _setAbsolutePath, None, doc="Absolute path of the overrridden command.")
########################################################################
# CollectFile class definition
########################################################################
@total_ordering
[docs]class CollectFile(object):
"""
Class representing a Cedar Backup collect file.
The following restrictions exist on data in this class:
- Absolute paths must be absolute
- The collect mode must be one of the values in :any:`VALID_COLLECT_MODES`.
- The archive mode must be one of the values in :any:`VALID_ARCHIVE_MODES`.
"""
[docs] def __init__(self, absolutePath=None, collectMode=None, archiveMode=None):
"""
Constructor for the ``CollectFile`` class.
Args:
absolutePath: Absolute path of the file to collect
collectMode: Overridden collect mode for this file
archiveMode: Overridden archive mode for this file
Raises:
ValueError: If one of the values is invalid
"""
self._absolutePath = None
self._collectMode = None
self._archiveMode = None
self.absolutePath = absolutePath
self.collectMode = collectMode
self.archiveMode = archiveMode
def __repr__(self):
"""
Official string representation for class instance.
"""
return "CollectFile(%s, %s, %s)" % (self.absolutePath, self.collectMode, self.archiveMode)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.absolutePath != other.absolutePath:
if str(self.absolutePath or "") < str(other.absolutePath or ""):
return -1
else:
return 1
if self.collectMode != other.collectMode:
if str(self.collectMode or "") < str(other.collectMode or ""):
return -1
else:
return 1
if self.archiveMode != other.archiveMode:
if str(self.archiveMode or "") < str(other.archiveMode or ""):
return -1
else:
return 1
return 0
def _setAbsolutePath(self, value):
"""
Property target used to set the absolute path.
The value must be an absolute path if it is not ``None``.
It does not have to exist on disk at the time of assignment.
Raises:
ValueError: If the value is not an absolute path
ValueError: If the value cannot be encoded properly
"""
if value is not None:
if not os.path.isabs(value):
raise ValueError("Not an absolute path: [%s]" % value)
self._absolutePath = encodePath(value)
def _getAbsolutePath(self):
"""
Property target used to get the absolute path.
"""
return self._absolutePath
def _setCollectMode(self, value):
"""
Property target used to set the collect mode.
If not ``None``, the mode must be one of the values in :any:`VALID_COLLECT_MODES`.
Raises:
ValueError: If the value is not valid
"""
if value is not None:
if value not in VALID_COLLECT_MODES:
raise ValueError("Collect mode must be one of %s." % VALID_COLLECT_MODES)
self._collectMode = value
def _getCollectMode(self):
"""
Property target used to get the collect mode.
"""
return self._collectMode
def _setArchiveMode(self, value):
"""
Property target used to set the archive mode.
If not ``None``, the mode must be one of the values in :any:`VALID_ARCHIVE_MODES`.
Raises:
ValueError: If the value is not valid
"""
if value is not None:
if value not in VALID_ARCHIVE_MODES:
raise ValueError("Archive mode must be one of %s." % VALID_ARCHIVE_MODES)
self._archiveMode = value
def _getArchiveMode(self):
"""
Property target used to get the archive mode.
"""
return self._archiveMode
absolutePath = property(_getAbsolutePath, _setAbsolutePath, None, doc="Absolute path of the file to collect.")
collectMode = property(_getCollectMode, _setCollectMode, None, doc="Overridden collect mode for this file.")
archiveMode = property(_getArchiveMode, _setArchiveMode, None, doc="Overridden archive mode for this file.")
########################################################################
# CollectDir class definition
########################################################################
@total_ordering
[docs]class CollectDir(object):
"""
Class representing a Cedar Backup collect directory.
The following restrictions exist on data in this class:
- Absolute paths must be absolute
- The collect mode must be one of the values in :any:`VALID_COLLECT_MODES`.
- The archive mode must be one of the values in :any:`VALID_ARCHIVE_MODES`.
- The ignore file must be a non-empty string.
For the ``absoluteExcludePaths`` list, validation is accomplished through the
:any:`util.AbsolutePathList` list implementation that overrides common list
methods and transparently does the absolute path validation for us.
*Note:* Lists within this class are "unordered" for equality comparisons.
"""
[docs] def __init__(self, absolutePath=None, collectMode=None, archiveMode=None, ignoreFile=None,
absoluteExcludePaths=None, relativeExcludePaths=None, excludePatterns=None,
linkDepth=None, dereference=False, recursionLevel=None):
"""
Constructor for the ``CollectDir`` class.
Args:
absolutePath: Absolute path of the directory to collect
collectMode: Overridden collect mode for this directory
archiveMode: Overridden archive mode for this directory
ignoreFile: Overidden ignore file name for this directory
linkDepth: Maximum at which soft links should be followed
dereference: Whether to dereference links that are followed
absoluteExcludePaths: List of absolute paths to exclude
relativeExcludePaths: List of relative paths to exclude
excludePatterns: List of regular expression patterns to exclude
Raises:
ValueError: If one of the values is invalid
"""
self._absolutePath = None
self._collectMode = None
self._archiveMode = None
self._ignoreFile = None
self._linkDepth = None
self._dereference = None
self._recursionLevel = None
self._absoluteExcludePaths = None
self._relativeExcludePaths = None
self._excludePatterns = None
self.absolutePath = absolutePath
self.collectMode = collectMode
self.archiveMode = archiveMode
self.ignoreFile = ignoreFile
self.linkDepth = linkDepth
self.dereference = dereference
self.recursionLevel = recursionLevel
self.absoluteExcludePaths = absoluteExcludePaths
self.relativeExcludePaths = relativeExcludePaths
self.excludePatterns = excludePatterns
def __repr__(self):
"""
Official string representation for class instance.
"""
return "CollectDir(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.absolutePath, self.collectMode,
self.archiveMode, self.ignoreFile,
self.absoluteExcludePaths,
self.relativeExcludePaths,
self.excludePatterns,
self.linkDepth, self.dereference,
self.recursionLevel)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Lists within this class are "unordered" for equality comparisons.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.absolutePath != other.absolutePath:
if str(self.absolutePath or "") < str(other.absolutePath or ""):
return -1
else:
return 1
if self.collectMode != other.collectMode:
if str(self.collectMode or "") < str(other.collectMode or ""):
return -1
else:
return 1
if self.archiveMode != other.archiveMode:
if str(self.archiveMode or "") < str(other.archiveMode or ""):
return -1
else:
return 1
if self.ignoreFile != other.ignoreFile:
if str(self.ignoreFile or "") < str(other.ignoreFile or ""):
return -1
else:
return 1
if self.linkDepth != other.linkDepth:
if int(self.linkDepth or 0) < int(other.linkDepth or 0):
return -1
else:
return 1
if self.dereference != other.dereference:
if self.dereference < other.dereference:
return -1
else:
return 1
if self.recursionLevel != other.recursionLevel:
if int(self.recursionLevel or 0) < int(other.recursionLevel or 0):
return -1
else:
return 1
if self.absoluteExcludePaths != other.absoluteExcludePaths:
if self.absoluteExcludePaths < other.absoluteExcludePaths:
return -1
else:
return 1
if self.relativeExcludePaths != other.relativeExcludePaths:
if self.relativeExcludePaths < other.relativeExcludePaths:
return -1
else:
return 1
if self.excludePatterns != other.excludePatterns:
if self.excludePatterns < other.excludePatterns:
return -1
else:
return 1
return 0
def _setAbsolutePath(self, value):
"""
Property target used to set the absolute path.
The value must be an absolute path if it is not ``None``.
It does not have to exist on disk at the time of assignment.
Raises:
ValueError: If the value is not an absolute path
ValueError: If the value cannot be encoded properly
"""
if value is not None:
if not os.path.isabs(value):
raise ValueError("Not an absolute path: [%s]" % value)
self._absolutePath = encodePath(value)
def _getAbsolutePath(self):
"""
Property target used to get the absolute path.
"""
return self._absolutePath
def _setCollectMode(self, value):
"""
Property target used to set the collect mode.
If not ``None``, the mode must be one of the values in :any:`VALID_COLLECT_MODES`.
Raises:
ValueError: If the value is not valid
"""
if value is not None:
if value not in VALID_COLLECT_MODES:
raise ValueError("Collect mode must be one of %s." % VALID_COLLECT_MODES)
self._collectMode = value
def _getCollectMode(self):
"""
Property target used to get the collect mode.
"""
return self._collectMode
def _setArchiveMode(self, value):
"""
Property target used to set the archive mode.
If not ``None``, the mode must be one of the values in :any:`VALID_ARCHIVE_MODES`.
Raises:
ValueError: If the value is not valid
"""
if value is not None:
if value not in VALID_ARCHIVE_MODES:
raise ValueError("Archive mode must be one of %s." % VALID_ARCHIVE_MODES)
self._archiveMode = value
def _getArchiveMode(self):
"""
Property target used to get the archive mode.
"""
return self._archiveMode
def _setIgnoreFile(self, value):
"""
Property target used to set the ignore file.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("The ignore file must be a non-empty string.")
self._ignoreFile = value
def _getIgnoreFile(self):
"""
Property target used to get the ignore file.
"""
return self._ignoreFile
def _setLinkDepth(self, value):
"""
Property target used to set the link depth.
The value must be an integer >= 0.
Raises:
ValueError: If the value is not valid
"""
if value is None:
self._linkDepth = None
else:
try:
value = int(value)
except TypeError:
raise ValueError("Link depth value must be an integer >= 0.")
if value < 0:
raise ValueError("Link depth value must be an integer >= 0.")
self._linkDepth = value
def _getLinkDepth(self):
"""
Property target used to get the action linkDepth.
"""
return self._linkDepth
def _setDereference(self, value):
"""
Property target used to set the dereference flag.
No validations, but we normalize the value to ``True`` or ``False``.
"""
if value:
self._dereference = True
else:
self._dereference = False
def _getDereference(self):
"""
Property target used to get the dereference flag.
"""
return self._dereference
def _setRecursionLevel(self, value):
"""
Property target used to set the recursionLevel.
The value must be an integer.
Raises:
ValueError: If the value is not valid
"""
if value is None:
self._recursionLevel = None
else:
try:
value = int(value)
except TypeError:
raise ValueError("Recusion level value must be an integer.")
self._recursionLevel = value
def _getRecursionLevel(self):
"""
Property target used to get the action recursionLevel.
"""
return self._recursionLevel
def _setAbsoluteExcludePaths(self, value):
"""
Property target used to set the absolute exclude paths list.
Either the value must be ``None`` or each element must be an absolute path.
Elements do not have to exist on disk at the time of assignment.
Raises:
ValueError: If the value is not an absolute path
"""
if value is None:
self._absoluteExcludePaths = None
else:
try:
saved = self._absoluteExcludePaths
self._absoluteExcludePaths = AbsolutePathList()
self._absoluteExcludePaths.extend(value)
except Exception as e:
self._absoluteExcludePaths = saved
raise e
def _getAbsoluteExcludePaths(self):
"""
Property target used to get the absolute exclude paths list.
"""
return self._absoluteExcludePaths
def _setRelativeExcludePaths(self, value):
"""
Property target used to set the relative exclude paths list.
Elements do not have to exist on disk at the time of assignment.
"""
if value is None:
self._relativeExcludePaths = None
else:
try:
saved = self._relativeExcludePaths
self._relativeExcludePaths = UnorderedList()
self._relativeExcludePaths.extend(value)
except Exception as e:
self._relativeExcludePaths = saved
raise e
def _getRelativeExcludePaths(self):
"""
Property target used to get the relative exclude paths list.
"""
return self._relativeExcludePaths
def _setExcludePatterns(self, value):
"""
Property target used to set the exclude patterns list.
"""
if value is None:
self._excludePatterns = None
else:
try:
saved = self._excludePatterns
self._excludePatterns = RegexList()
self._excludePatterns.extend(value)
except Exception as e:
self._excludePatterns = saved
raise e
def _getExcludePatterns(self):
"""
Property target used to get the exclude patterns list.
"""
return self._excludePatterns
absolutePath = property(_getAbsolutePath, _setAbsolutePath, None, doc="Absolute path of the directory to collect.")
collectMode = property(_getCollectMode, _setCollectMode, None, doc="Overridden collect mode for this directory.")
archiveMode = property(_getArchiveMode, _setArchiveMode, None, doc="Overridden archive mode for this directory.")
ignoreFile = property(_getIgnoreFile, _setIgnoreFile, None, doc="Overridden ignore file name for this directory.")
linkDepth = property(_getLinkDepth, _setLinkDepth, None, doc="Maximum at which soft links should be followed.")
dereference = property(_getDereference, _setDereference, None, doc="Whether to dereference links that are followed.")
recursionLevel = property(_getRecursionLevel, _setRecursionLevel, None, "Recursion level to use for recursive directory collection")
absoluteExcludePaths = property(_getAbsoluteExcludePaths, _setAbsoluteExcludePaths, None, "List of absolute paths to exclude.")
relativeExcludePaths = property(_getRelativeExcludePaths, _setRelativeExcludePaths, None, "List of relative paths to exclude.")
excludePatterns = property(_getExcludePatterns, _setExcludePatterns, None, "List of regular expression patterns to exclude.")
########################################################################
# PurgeDir class definition
########################################################################
@total_ordering
[docs]class PurgeDir(object):
"""
Class representing a Cedar Backup purge directory.
The following restrictions exist on data in this class:
- The absolute path must be an absolute path
- The retain days value must be an integer >= 0.
"""
[docs] def __init__(self, absolutePath=None, retainDays=None):
"""
Constructor for the ``PurgeDir`` class.
Args:
absolutePath: Absolute path of the directory to be purged
retainDays: Number of days content within directory should be retained
Raises:
ValueError: If one of the values is invalid
"""
self._absolutePath = None
self._retainDays = None
self.absolutePath = absolutePath
self.retainDays = retainDays
def __repr__(self):
"""
Official string representation for class instance.
"""
return "PurgeDir(%s, %s)" % (self.absolutePath, self.retainDays)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.absolutePath != other.absolutePath:
if str(self.absolutePath or "") < str(other.absolutePath or ""):
return -1
else:
return 1
if self.retainDays != other.retainDays:
if int(self.retainDays or 0) < int(other.retainDays or 0):
return -1
else:
return 1
return 0
def _setAbsolutePath(self, value):
"""
Property target used to set the absolute path.
The value must be an absolute path if it is not ``None``.
It does not have to exist on disk at the time of assignment.
Raises:
ValueError: If the value is not an absolute path
ValueError: If the value cannot be encoded properly
"""
if value is not None:
if not os.path.isabs(value):
raise ValueError("Absolute path must, er, be an absolute path.")
self._absolutePath = encodePath(value)
def _getAbsolutePath(self):
"""
Property target used to get the absolute path.
"""
return self._absolutePath
def _setRetainDays(self, value):
"""
Property target used to set the retain days value.
The value must be an integer >= 0.
Raises:
ValueError: If the value is not valid
"""
if value is None:
self._retainDays = None
else:
try:
value = int(value)
except TypeError:
raise ValueError("Retain days value must be an integer >= 0.")
if value < 0:
raise ValueError("Retain days value must be an integer >= 0.")
self._retainDays = value
def _getRetainDays(self):
"""
Property target used to get the absolute path.
"""
return self._retainDays
absolutePath = property(_getAbsolutePath, _setAbsolutePath, None, "Absolute path of directory to purge.")
retainDays = property(_getRetainDays, _setRetainDays, None, "Number of days content within directory should be retained.")
########################################################################
# LocalPeer class definition
########################################################################
@total_ordering
[docs]class LocalPeer(object):
"""
Class representing a Cedar Backup peer.
The following restrictions exist on data in this class:
- The peer name must be a non-empty string.
- The collect directory must be an absolute path.
- The ignore failure mode must be one of the values in ``VALID_FAILURE_MODES``.
"""
[docs] def __init__(self, name=None, collectDir=None, ignoreFailureMode=None):
"""
Constructor for the ``LocalPeer`` class.
Args:
name: Name of the peer, typically a valid hostname
collectDir: Collect directory to stage files from on peer
ignoreFailureMode: Ignore failure mode for peer
Raises:
ValueError: If one of the values is invalid
"""
self._name = None
self._collectDir = None
self._ignoreFailureMode = None
self.name = name
self.collectDir = collectDir
self.ignoreFailureMode = ignoreFailureMode
def __repr__(self):
"""
Official string representation for class instance.
"""
return "LocalPeer(%s, %s, %s)" % (self.name, self.collectDir, self.ignoreFailureMode)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.name != other.name:
if str(self.name or "") < str(other.name or ""):
return -1
else:
return 1
if self.collectDir != other.collectDir:
if str(self.collectDir or "") < str(other.collectDir or ""):
return -1
else:
return 1
if self.ignoreFailureMode != other.ignoreFailureMode:
if str(self.ignoreFailureMode or "") < str(other.ignoreFailureMode or ""):
return -1
else:
return 1
return 0
def _setName(self, value):
"""
Property target used to set the peer name.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("The peer name must be a non-empty string.")
self._name = value
def _getName(self):
"""
Property target used to get the peer name.
"""
return self._name
def _setCollectDir(self, value):
"""
Property target used to set the collect directory.
The value must be an absolute path if it is not ``None``.
It does not have to exist on disk at the time of assignment.
Raises:
ValueError: If the value is not an absolute path
ValueError: If the value cannot be encoded properly
"""
if value is not None:
if not os.path.isabs(value):
raise ValueError("Collect directory must be an absolute path.")
self._collectDir = encodePath(value)
def _getCollectDir(self):
"""
Property target used to get the collect directory.
"""
return self._collectDir
def _setIgnoreFailureMode(self, value):
"""
Property target used to set the ignoreFailure mode.
If not ``None``, the mode must be one of the values in ``VALID_FAILURE_MODES``.
Raises:
ValueError: If the value is not valid
"""
if value is not None:
if value not in VALID_FAILURE_MODES:
raise ValueError("Ignore failure mode must be one of %s." % VALID_FAILURE_MODES)
self._ignoreFailureMode = value
def _getIgnoreFailureMode(self):
"""
Property target used to get the ignoreFailure mode.
"""
return self._ignoreFailureMode
name = property(_getName, _setName, None, "Name of the peer, typically a valid hostname.")
collectDir = property(_getCollectDir, _setCollectDir, None, "Collect directory to stage files from on peer.")
ignoreFailureMode = property(_getIgnoreFailureMode, _setIgnoreFailureMode, None, "Ignore failure mode for peer.")
########################################################################
# RemotePeer class definition
########################################################################
@total_ordering
[docs]class RemotePeer(object):
"""
Class representing a Cedar Backup peer.
The following restrictions exist on data in this class:
- The peer name must be a non-empty string.
- The collect directory must be an absolute path.
- The remote user must be a non-empty string.
- The rcp command must be a non-empty string.
- The rsh command must be a non-empty string.
- The cback command must be a non-empty string.
- Any managed action name must be a non-empty string matching ``ACTION_NAME_REGEX``
- The ignore failure mode must be one of the values in ``VALID_FAILURE_MODES``.
"""
[docs] def __init__(self, name=None, collectDir=None, remoteUser=None,
rcpCommand=None, rshCommand=None, cbackCommand=None,
managed=False, managedActions=None, ignoreFailureMode=None):
"""
Constructor for the ``RemotePeer`` class.
Args:
name: Name of the peer, must be a valid hostname
collectDir: Collect directory to stage files from on peer
remoteUser: Name of backup user on remote peer
rcpCommand: Overridden rcp-compatible copy command for peer
rshCommand: Overridden rsh-compatible remote shell command for peer
cbackCommand: Overridden cback-compatible command to use on remote peer
managed: Indicates whether this is a managed peer
managedActions: Overridden set of actions that are managed on the peer
ignoreFailureMode: Ignore failure mode for peer
Raises:
ValueError: If one of the values is invalid
"""
self._name = None
self._collectDir = None
self._remoteUser = None
self._rcpCommand = None
self._rshCommand = None
self._cbackCommand = None
self._managed = None
self._managedActions = None
self._ignoreFailureMode = None
self.name = name
self.collectDir = collectDir
self.remoteUser = remoteUser
self.rcpCommand = rcpCommand
self.rshCommand = rshCommand
self.cbackCommand = cbackCommand
self.managed = managed
self.managedActions = managedActions
self.ignoreFailureMode = ignoreFailureMode
def __repr__(self):
"""
Official string representation for class instance.
"""
return "RemotePeer(%s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.name, self.collectDir, self.remoteUser,
self.rcpCommand, self.rshCommand, self.cbackCommand,
self.managed, self.managedActions, self.ignoreFailureMode)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.name != other.name:
if str(self.name or "") < str(other.name or ""):
return -1
else:
return 1
if self.collectDir != other.collectDir:
if str(self.collectDir or "") < str(other.collectDir or ""):
return -1
else:
return 1
if self.remoteUser != other.remoteUser:
if str(self.remoteUser or "") < str(other.remoteUser or ""):
return -1
else:
return 1
if self.rcpCommand != other.rcpCommand:
if str(self.rcpCommand or "") < str(other.rcpCommand or ""):
return -1
else:
return 1
if self.rshCommand != other.rshCommand:
if str(self.rshCommand or "") < str(other.rshCommand or ""):
return -1
else:
return 1
if self.cbackCommand != other.cbackCommand:
if str(self.cbackCommand or "") < str(other.cbackCommand or ""):
return -1
else:
return 1
if self.managed != other.managed:
if str(self.managed or "") < str(other.managed or ""):
return -1
else:
return 1
if self.managedActions != other.managedActions:
if self.managedActions < other.managedActions:
return -1
else:
return 1
if self.ignoreFailureMode != other.ignoreFailureMode:
if str(self.ignoreFailureMode or "") < str(other.ignoreFailureMode or ""):
return -1
else:
return 1
return 0
def _setName(self, value):
"""
Property target used to set the peer name.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("The peer name must be a non-empty string.")
self._name = value
def _getName(self):
"""
Property target used to get the peer name.
"""
return self._name
def _setCollectDir(self, value):
"""
Property target used to set the collect directory.
The value must be an absolute path if it is not ``None``.
It does not have to exist on disk at the time of assignment.
Raises:
ValueError: If the value is not an absolute path
ValueError: If the value cannot be encoded properly
"""
if value is not None:
if not os.path.isabs(value):
raise ValueError("Collect directory must be an absolute path.")
self._collectDir = encodePath(value)
def _getCollectDir(self):
"""
Property target used to get the collect directory.
"""
return self._collectDir
def _setRemoteUser(self, value):
"""
Property target used to set the remote user.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("The remote user must be a non-empty string.")
self._remoteUser = value
def _getRemoteUser(self):
"""
Property target used to get the remote user.
"""
return self._remoteUser
def _setRcpCommand(self, value):
"""
Property target used to set the rcp command.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("The rcp command must be a non-empty string.")
self._rcpCommand = value
def _getRcpCommand(self):
"""
Property target used to get the rcp command.
"""
return self._rcpCommand
def _setRshCommand(self, value):
"""
Property target used to set the rsh command.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("The rsh command must be a non-empty string.")
self._rshCommand = value
def _getRshCommand(self):
"""
Property target used to get the rsh command.
"""
return self._rshCommand
def _setCbackCommand(self, value):
"""
Property target used to set the cback command.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("The cback command must be a non-empty string.")
self._cbackCommand = value
def _getCbackCommand(self):
"""
Property target used to get the cback command.
"""
return self._cbackCommand
def _setManaged(self, value):
"""
Property target used to set the managed flag.
No validations, but we normalize the value to ``True`` or ``False``.
"""
if value:
self._managed = True
else:
self._managed = False
def _getManaged(self):
"""
Property target used to get the managed flag.
"""
return self._managed
def _setManagedActions(self, value):
"""
Property target used to set the managed actions list.
Elements do not have to exist on disk at the time of assignment.
"""
if value is None:
self._managedActions = None
else:
try:
saved = self._managedActions
self._managedActions = RegexMatchList(ACTION_NAME_REGEX, emptyAllowed=False, prefix="Action name")
self._managedActions.extend(value)
except Exception as e:
self._managedActions = saved
raise e
def _getManagedActions(self):
"""
Property target used to get the managed actions list.
"""
return self._managedActions
def _setIgnoreFailureMode(self, value):
"""
Property target used to set the ignoreFailure mode.
If not ``None``, the mode must be one of the values in ``VALID_FAILURE_MODES``.
Raises:
ValueError: If the value is not valid
"""
if value is not None:
if value not in VALID_FAILURE_MODES:
raise ValueError("Ignore failure mode must be one of %s." % VALID_FAILURE_MODES)
self._ignoreFailureMode = value
def _getIgnoreFailureMode(self):
"""
Property target used to get the ignoreFailure mode.
"""
return self._ignoreFailureMode
name = property(_getName, _setName, None, "Name of the peer, must be a valid hostname.")
collectDir = property(_getCollectDir, _setCollectDir, None, "Collect directory to stage files from on peer.")
remoteUser = property(_getRemoteUser, _setRemoteUser, None, "Name of backup user on remote peer.")
rcpCommand = property(_getRcpCommand, _setRcpCommand, None, "Overridden rcp-compatible copy command for peer.")
rshCommand = property(_getRshCommand, _setRshCommand, None, "Overridden rsh-compatible remote shell command for peer.")
cbackCommand = property(_getCbackCommand, _setCbackCommand, None, "Overridden cback-compatible command to use on remote peer.")
managed = property(_getManaged, _setManaged, None, "Indicates whether this is a managed peer.")
managedActions = property(_getManagedActions, _setManagedActions, None, "Overridden set of actions that are managed on the peer.")
ignoreFailureMode = property(_getIgnoreFailureMode, _setIgnoreFailureMode, None, "Ignore failure mode for peer.")
########################################################################
# ReferenceConfig class definition
########################################################################
@total_ordering
[docs]class ReferenceConfig(object):
"""
Class representing a Cedar Backup reference configuration.
The reference information is just used for saving off metadata about
configuration and exists mostly for backwards-compatibility with Cedar
Backup 1.x.
"""
[docs] def __init__(self, author=None, revision=None, description=None, generator=None):
"""
Constructor for the ``ReferenceConfig`` class.
Args:
author: Author of the configuration file
revision: Revision of the configuration file
description: Description of the configuration file
generator: Tool that generated the configuration file
"""
self._author = None
self._revision = None
self._description = None
self._generator = None
self.author = author
self.revision = revision
self.description = description
self.generator = generator
def __repr__(self):
"""
Official string representation for class instance.
"""
return "ReferenceConfig(%s, %s, %s, %s)" % (self.author, self.revision, self.description, self.generator)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.author != other.author:
if str(self.author or "") < str(other.author or ""):
return -1
else:
return 1
if self.revision != other.revision:
if str(self.revision or "") < str(other.revision or ""):
return -1
else:
return 1
if self.description != other.description:
if str(self.description or "") < str(other.description or ""):
return -1
else:
return 1
if self.generator != other.generator:
if str(self.generator or "") < str(other.generator or ""):
return -1
else:
return 1
return 0
def _setAuthor(self, value):
"""
Property target used to set the author value.
No validations.
"""
self._author = value
def _getAuthor(self):
"""
Property target used to get the author value.
"""
return self._author
def _setRevision(self, value):
"""
Property target used to set the revision value.
No validations.
"""
self._revision = value
def _getRevision(self):
"""
Property target used to get the revision value.
"""
return self._revision
def _setDescription(self, value):
"""
Property target used to set the description value.
No validations.
"""
self._description = value
def _getDescription(self):
"""
Property target used to get the description value.
"""
return self._description
def _setGenerator(self, value):
"""
Property target used to set the generator value.
No validations.
"""
self._generator = value
def _getGenerator(self):
"""
Property target used to get the generator value.
"""
return self._generator
author = property(_getAuthor, _setAuthor, None, "Author of the configuration file.")
revision = property(_getRevision, _setRevision, None, "Revision of the configuration file.")
description = property(_getDescription, _setDescription, None, "Description of the configuration file.")
generator = property(_getGenerator, _setGenerator, None, "Tool that generated the configuration file.")
########################################################################
# ExtensionsConfig class definition
########################################################################
@total_ordering
[docs]class ExtensionsConfig(object):
"""
Class representing Cedar Backup extensions configuration.
Extensions configuration is used to specify "extended actions" implemented
by code external to Cedar Backup. For instance, a hypothetical third party
might write extension code to collect database repository data. If they
write a properly-formatted extension function, they can use the extension
configuration to map a command-line Cedar Backup action (i.e. "database")
to their function.
The following restrictions exist on data in this class:
- If set, the order mode must be one of the values in ``VALID_ORDER_MODES``
- The actions list must be a list of ``ExtendedAction`` objects.
"""
[docs] def __init__(self, actions=None, orderMode=None):
"""
Constructor for the ``ExtensionsConfig`` class.
Args:
actions: List of extended actions
"""
self._orderMode = None
self._actions = None
self.orderMode = orderMode
self.actions = actions
def __repr__(self):
"""
Official string representation for class instance.
"""
return "ExtensionsConfig(%s, %s)" % (self.orderMode, self.actions)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.orderMode != other.orderMode:
if str(self.orderMode or "") < str(other.orderMode or ""):
return -1
else:
return 1
if self.actions != other.actions:
if self.actions < other.actions:
return -1
else:
return 1
return 0
def _setOrderMode(self, value):
"""
Property target used to set the order mode.
The value must be one of :any:`VALID_ORDER_MODES`.
Raises:
ValueError: If the value is not valid
"""
if value is not None:
if value not in VALID_ORDER_MODES:
raise ValueError("Order mode must be one of %s." % VALID_ORDER_MODES)
self._orderMode = value
def _getOrderMode(self):
"""
Property target used to get the order mode.
"""
return self._orderMode
def _setActions(self, value):
"""
Property target used to set the actions list.
Either the value must be ``None`` or each element must be an ``ExtendedAction``.
Raises:
ValueError: If the value is not a ``ExtendedAction``
"""
if value is None:
self._actions = None
else:
try:
saved = self._actions
self._actions = ObjectTypeList(ExtendedAction, "ExtendedAction")
self._actions.extend(value)
except Exception as e:
self._actions = saved
raise e
def _getActions(self):
"""
Property target used to get the actions list.
"""
return self._actions
orderMode = property(_getOrderMode, _setOrderMode, None, "Order mode for extensions, to control execution ordering.")
actions = property(_getActions, _setActions, None, "List of extended actions.")
########################################################################
# OptionsConfig class definition
########################################################################
@total_ordering
[docs]class OptionsConfig(object):
"""
Class representing a Cedar Backup global options configuration.
The options section is used to store global configuration options and
defaults that can be applied to other sections.
The following restrictions exist on data in this class:
- The working directory must be an absolute path.
- The starting day must be a day of the week in English, i.e. ``"monday"``, ``"tuesday"``, etc.
- All of the other values must be non-empty strings if they are set to something other than ``None``.
- The overrides list must be a list of ``CommandOverride`` objects.
- The hooks list must be a list of ``ActionHook`` objects.
- The cback command must be a non-empty string.
- Any managed action name must be a non-empty string matching ``ACTION_NAME_REGEX``
"""
[docs] def __init__(self, startingDay=None, workingDir=None, backupUser=None,
backupGroup=None, rcpCommand=None, overrides=None,
hooks=None, rshCommand=None, cbackCommand=None,
managedActions=None):
"""
Constructor for the ``OptionsConfig`` class.
Args:
startingDay: Day that starts the week
workingDir: Working (temporary) directory to use for backups
backupUser: Effective user that backups should run as
backupGroup: Effective group that backups should run as
rcpCommand: Default rcp-compatible copy command for staging
rshCommand: Default rsh-compatible command to use for remote shells
cbackCommand: Default cback-compatible command to use on managed remote peers
overrides: List of configured command path overrides, if any
hooks: List of configured pre- and post-action hooks
managedActions: Default set of actions that are managed on remote peers
Raises:
ValueError: If one of the values is invalid
"""
self._startingDay = None
self._workingDir = None
self._backupUser = None
self._backupGroup = None
self._rcpCommand = None
self._rshCommand = None
self._cbackCommand = None
self._overrides = None
self._hooks = None
self._managedActions = None
self.startingDay = startingDay
self.workingDir = workingDir
self.backupUser = backupUser
self.backupGroup = backupGroup
self.rcpCommand = rcpCommand
self.rshCommand = rshCommand
self.cbackCommand = cbackCommand
self.overrides = overrides
self.hooks = hooks
self.managedActions = managedActions
def __repr__(self):
"""
Official string representation for class instance.
"""
return "OptionsConfig(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.startingDay, self.workingDir,
self.backupUser, self.backupGroup,
self.rcpCommand, self.overrides,
self.hooks, self.rshCommand,
self.cbackCommand, self.managedActions)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.startingDay != other.startingDay:
if str(self.startingDay or "") < str(other.startingDay or ""):
return -1
else:
return 1
if self.workingDir != other.workingDir:
if str(self.workingDir or "") < str(other.workingDir or ""):
return -1
else:
return 1
if self.backupUser != other.backupUser:
if str(self.backupUser or "") < str(other.backupUser or ""):
return -1
else:
return 1
if self.backupGroup != other.backupGroup:
if str(self.backupGroup or "") < str(other.backupGroup or ""):
return -1
else:
return 1
if self.rcpCommand != other.rcpCommand:
if str(self.rcpCommand or "") < str(other.rcpCommand or ""):
return -1
else:
return 1
if self.rshCommand != other.rshCommand:
if str(self.rshCommand or "") < str(other.rshCommand or ""):
return -1
else:
return 1
if self.cbackCommand != other.cbackCommand:
if str(self.cbackCommand or "") < str(other.cbackCommand or ""):
return -1
else:
return 1
if self.overrides != other.overrides:
if self.overrides < other.overrides:
return -1
else:
return 1
if self.hooks != other.hooks:
if self.hooks < other.hooks:
return -1
else:
return 1
if self.managedActions != other.managedActions:
if self.managedActions < other.managedActions:
return -1
else:
return 1
return 0
[docs] def addOverride(self, command, absolutePath):
"""
If no override currently exists for the command, add one.
Args:
command: Name of command to be overridden
absolutePath: Absolute path of the overrridden command
"""
override = CommandOverride(command, absolutePath)
if self.overrides is None:
self.overrides = [ override, ]
else:
exists = False
for obj in self.overrides:
if obj.command == override.command:
exists = True
break
if not exists:
self.overrides.append(override)
[docs] def replaceOverride(self, command, absolutePath):
"""
If override currently exists for the command, replace it; otherwise add it.
Args:
command: Name of command to be overridden
absolutePath: Absolute path of the overrridden command
"""
override = CommandOverride(command, absolutePath)
if self.overrides is None:
self.overrides = [ override, ]
else:
exists = False
for obj in self.overrides:
if obj.command == override.command:
exists = True
obj.absolutePath = override.absolutePath
break
if not exists:
self.overrides.append(override)
def _setStartingDay(self, value):
"""
Property target used to set the starting day.
If it is not ``None``, the value must be a valid English day of the week,
one of ``"monday"``, ``"tuesday"``, ``"wednesday"``, etc.
Raises:
ValueError: If the value is not a valid day of the week
"""
if value is not None:
if value not in ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday", ]:
raise ValueError("Starting day must be an English day of the week, i.e. \"monday\".")
self._startingDay = value
def _getStartingDay(self):
"""
Property target used to get the starting day.
"""
return self._startingDay
def _setWorkingDir(self, value):
"""
Property target used to set the working directory.
The value must be an absolute path if it is not ``None``.
It does not have to exist on disk at the time of assignment.
Raises:
ValueError: If the value is not an absolute path
ValueError: If the value cannot be encoded properly
"""
if value is not None:
if not os.path.isabs(value):
raise ValueError("Working directory must be an absolute path.")
self._workingDir = encodePath(value)
def _getWorkingDir(self):
"""
Property target used to get the working directory.
"""
return self._workingDir
def _setBackupUser(self, value):
"""
Property target used to set the backup user.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("Backup user must be a non-empty string.")
self._backupUser = value
def _getBackupUser(self):
"""
Property target used to get the backup user.
"""
return self._backupUser
def _setBackupGroup(self, value):
"""
Property target used to set the backup group.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("Backup group must be a non-empty string.")
self._backupGroup = value
def _getBackupGroup(self):
"""
Property target used to get the backup group.
"""
return self._backupGroup
def _setRcpCommand(self, value):
"""
Property target used to set the rcp command.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("The rcp command must be a non-empty string.")
self._rcpCommand = value
def _getRcpCommand(self):
"""
Property target used to get the rcp command.
"""
return self._rcpCommand
def _setRshCommand(self, value):
"""
Property target used to set the rsh command.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("The rsh command must be a non-empty string.")
self._rshCommand = value
def _getRshCommand(self):
"""
Property target used to get the rsh command.
"""
return self._rshCommand
def _setCbackCommand(self, value):
"""
Property target used to set the cback command.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
"""
if value is not None:
if len(value) < 1:
raise ValueError("The cback command must be a non-empty string.")
self._cbackCommand = value
def _getCbackCommand(self):
"""
Property target used to get the cback command.
"""
return self._cbackCommand
def _setOverrides(self, value):
"""
Property target used to set the command path overrides list.
Either the value must be ``None`` or each element must be a ``CommandOverride``.
Raises:
ValueError: If the value is not a ``CommandOverride``
"""
if value is None:
self._overrides = None
else:
try:
saved = self._overrides
self._overrides = ObjectTypeList(CommandOverride, "CommandOverride")
self._overrides.extend(value)
except Exception as e:
self._overrides = saved
raise e
def _getOverrides(self):
"""
Property target used to get the command path overrides list.
"""
return self._overrides
def _setHooks(self, value):
"""
Property target used to set the pre- and post-action hooks list.
Either the value must be ``None`` or each element must be an ``ActionHook``.
Raises:
ValueError: If the value is not a ``CommandOverride``
"""
if value is None:
self._hooks = None
else:
try:
saved = self._hooks
self._hooks = ObjectTypeList(ActionHook, "ActionHook")
self._hooks.extend(value)
except Exception as e:
self._hooks = saved
raise e
def _getHooks(self):
"""
Property target used to get the command path hooks list.
"""
return self._hooks
def _setManagedActions(self, value):
"""
Property target used to set the managed actions list.
Elements do not have to exist on disk at the time of assignment.
"""
if value is None:
self._managedActions = None
else:
try:
saved = self._managedActions
self._managedActions = RegexMatchList(ACTION_NAME_REGEX, emptyAllowed=False, prefix="Action name")
self._managedActions.extend(value)
except Exception as e:
self._managedActions = saved
raise e
def _getManagedActions(self):
"""
Property target used to get the managed actions list.
"""
return self._managedActions
startingDay = property(_getStartingDay, _setStartingDay, None, "Day that starts the week.")
workingDir = property(_getWorkingDir, _setWorkingDir, None, "Working (temporary) directory to use for backups.")
backupUser = property(_getBackupUser, _setBackupUser, None, "Effective user that backups should run as.")
backupGroup = property(_getBackupGroup, _setBackupGroup, None, "Effective group that backups should run as.")
rcpCommand = property(_getRcpCommand, _setRcpCommand, None, "Default rcp-compatible copy command for staging.")
rshCommand = property(_getRshCommand, _setRshCommand, None, "Default rsh-compatible command to use for remote shells.")
cbackCommand = property(_getCbackCommand, _setCbackCommand, None, "Default cback-compatible command to use on managed remote peers.")
overrides = property(_getOverrides, _setOverrides, None, "List of configured command path overrides, if any.")
hooks = property(_getHooks, _setHooks, None, "List of configured pre- and post-action hooks.")
managedActions = property(_getManagedActions, _setManagedActions, None, "Default set of actions that are managed on remote peers.")
########################################################################
# PeersConfig class definition
########################################################################
@total_ordering
[docs]class PeersConfig(object):
"""
Class representing Cedar Backup global peer configuration.
This section contains a list of local and remote peers in a master's backup
pool. The section is optional. If a master does not define this section,
then all peers are unmanaged, and the stage configuration section must
explicitly list any peer that is to be staged. If this section is
configured, then peers may be managed or unmanaged, and the stage section
peer configuration (if any) completely overrides this configuration.
The following restrictions exist on data in this class:
- The list of local peers must contain only ``LocalPeer`` objects
- The list of remote peers must contain only ``RemotePeer`` objects
*Note:* Lists within this class are "unordered" for equality comparisons.
"""
[docs] def __init__(self, localPeers=None, remotePeers=None):
"""
Constructor for the ``PeersConfig`` class.
Args:
localPeers: List of local peers
remotePeers: List of remote peers
Raises:
ValueError: If one of the values is invalid
"""
self._localPeers = None
self._remotePeers = None
self.localPeers = localPeers
self.remotePeers = remotePeers
def __repr__(self):
"""
Official string representation for class instance.
"""
return "PeersConfig(%s, %s)" % (self.localPeers, self.remotePeers)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Lists within this class are "unordered" for equality comparisons.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.localPeers != other.localPeers:
if self.localPeers < other.localPeers:
return -1
else:
return 1
if self.remotePeers != other.remotePeers:
if self.remotePeers < other.remotePeers:
return -1
else:
return 1
return 0
[docs] def hasPeers(self):
"""
Indicates whether any peers are filled into this object.
Returns:
Boolean true if any local or remote peers are filled in, false otherwise
"""
return ((self.localPeers is not None and len(self.localPeers) > 0) or
(self.remotePeers is not None and len(self.remotePeers) > 0))
def _setLocalPeers(self, value):
"""
Property target used to set the local peers list.
Either the value must be ``None`` or each element must be a ``LocalPeer``.
Raises:
ValueError: If the value is not an absolute path
"""
if value is None:
self._localPeers = None
else:
try:
saved = self._localPeers
self._localPeers = ObjectTypeList(LocalPeer, "LocalPeer")
self._localPeers.extend(value)
except Exception as e:
self._localPeers = saved
raise e
def _getLocalPeers(self):
"""
Property target used to get the local peers list.
"""
return self._localPeers
def _setRemotePeers(self, value):
"""
Property target used to set the remote peers list.
Either the value must be ``None`` or each element must be a ``RemotePeer``.
Raises:
ValueError: If the value is not a ``RemotePeer``
"""
if value is None:
self._remotePeers = None
else:
try:
saved = self._remotePeers
self._remotePeers = ObjectTypeList(RemotePeer, "RemotePeer")
self._remotePeers.extend(value)
except Exception as e:
self._remotePeers = saved
raise e
def _getRemotePeers(self):
"""
Property target used to get the remote peers list.
"""
return self._remotePeers
localPeers = property(_getLocalPeers, _setLocalPeers, None, "List of local peers.")
remotePeers = property(_getRemotePeers, _setRemotePeers, None, "List of remote peers.")
########################################################################
# CollectConfig class definition
########################################################################
@total_ordering
[docs]class CollectConfig(object):
"""
Class representing a Cedar Backup collect configuration.
The following restrictions exist on data in this class:
- The target directory must be an absolute path.
- The collect mode must be one of the values in :any:`VALID_COLLECT_MODES`.
- The archive mode must be one of the values in :any:`VALID_ARCHIVE_MODES`.
- The ignore file must be a non-empty string.
- Each of the paths in ``absoluteExcludePaths`` must be an absolute path
- The collect file list must be a list of ``CollectFile`` objects.
- The collect directory list must be a list of ``CollectDir`` objects.
For the ``absoluteExcludePaths`` list, validation is accomplished through the
:any:`util.AbsolutePathList` list implementation that overrides common list
methods and transparently does the absolute path validation for us.
For the ``collectFiles`` and ``collectDirs`` list, validation is accomplished
through the :any:`util.ObjectTypeList` list implementation that overrides common
list methods and transparently ensures that each element has an appropriate
type.
*Note:* Lists within this class are "unordered" for equality comparisons.
"""
[docs] def __init__(self, targetDir=None, collectMode=None, archiveMode=None, ignoreFile=None,
absoluteExcludePaths=None, excludePatterns=None, collectFiles=None,
collectDirs=None):
"""
Constructor for the ``CollectConfig`` class.
Args:
targetDir: Directory to collect files into
collectMode: Default collect mode
archiveMode: Default archive mode for collect files
ignoreFile: Default ignore file name
absoluteExcludePaths: List of absolute paths to exclude
excludePatterns: List of regular expression patterns to exclude
collectFiles: List of collect files
collectDirs: List of collect directories
Raises:
ValueError: If one of the values is invalid
"""
self._targetDir = None
self._collectMode = None
self._archiveMode = None
self._ignoreFile = None
self._absoluteExcludePaths = None
self._excludePatterns = None
self._collectFiles = None
self._collectDirs = None
self.targetDir = targetDir
self.collectMode = collectMode
self.archiveMode = archiveMode
self.ignoreFile = ignoreFile
self.absoluteExcludePaths = absoluteExcludePaths
self.excludePatterns = excludePatterns
self.collectFiles = collectFiles
self.collectDirs = collectDirs
def __repr__(self):
"""
Official string representation for class instance.
"""
return "CollectConfig(%s, %s, %s, %s, %s, %s, %s, %s)" % (self.targetDir, self.collectMode, self.archiveMode,
self.ignoreFile, self.absoluteExcludePaths,
self.excludePatterns, self.collectFiles, self.collectDirs)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Lists within this class are "unordered" for equality comparisons.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.targetDir != other.targetDir:
if str(self.targetDir or "") < str(other.targetDir or ""):
return -1
else:
return 1
if self.collectMode != other.collectMode:
if str(self.collectMode or "") < str(other.collectMode or ""):
return -1
else:
return 1
if self.archiveMode != other.archiveMode:
if str(self.archiveMode or "") < str(other.archiveMode or ""):
return -1
else:
return 1
if self.ignoreFile != other.ignoreFile:
if str(self.ignoreFile or "") < str(other.ignoreFile or ""):
return -1
else:
return 1
if self.absoluteExcludePaths != other.absoluteExcludePaths:
if self.absoluteExcludePaths < other.absoluteExcludePaths:
return -1
else:
return 1
if self.excludePatterns != other.excludePatterns:
if self.excludePatterns < other.excludePatterns:
return -1
else:
return 1
if self.collectFiles != other.collectFiles:
if self.collectFiles < other.collectFiles:
return -1
else:
return 1
if self.collectDirs != other.collectDirs:
if self.collectDirs < other.collectDirs:
return -1
else:
return 1
return 0
def _setTargetDir(self, value):
"""
Property target used to set the target directory.
The value must be an absolute path if it is not ``None``.
It does not have to exist on disk at the time of assignment.
Raises:
ValueError: If the value is not an absolute path
ValueError: If the value cannot be encoded properly
"""
if value is not None:
if not os.path.isabs(value):
raise ValueError("Target directory must be an absolute path.")
self._targetDir = encodePath(value)
def _getTargetDir(self):
"""
Property target used to get the target directory.
"""
return self._targetDir
def _setCollectMode(self, value):
"""
Property target used to set the collect mode.
If not ``None``, the mode must be one of :any:`VALID_COLLECT_MODES`.
Raises:
ValueError: If the value is not valid
"""
if value is not None:
if value not in VALID_COLLECT_MODES:
raise ValueError("Collect mode must be one of %s." % VALID_COLLECT_MODES)
self._collectMode = value
def _getCollectMode(self):
"""
Property target used to get the collect mode.
"""
return self._collectMode
def _setArchiveMode(self, value):
"""
Property target used to set the archive mode.
If not ``None``, the mode must be one of :any:`VALID_ARCHIVE_MODES`.
Raises:
ValueError: If the value is not valid
"""
if value is not None:
if value not in VALID_ARCHIVE_MODES:
raise ValueError("Archive mode must be one of %s." % VALID_ARCHIVE_MODES)
self._archiveMode = value
def _getArchiveMode(self):
"""
Property target used to get the archive mode.
"""
return self._archiveMode
def _setIgnoreFile(self, value):
"""
Property target used to set the ignore file.
The value must be a non-empty string if it is not ``None``.
Raises:
ValueError: If the value is an empty string
ValueError: If the value cannot be encoded properly
"""
if value is not None:
if len(value) < 1:
raise ValueError("The ignore file must be a non-empty string.")
self._ignoreFile = encodePath(value)
def _getIgnoreFile(self):
"""
Property target used to get the ignore file.
"""
return self._ignoreFile
def _setAbsoluteExcludePaths(self, value):
"""
Property target used to set the absolute exclude paths list.
Either the value must be ``None`` or each element must be an absolute path.
Elements do not have to exist on disk at the time of assignment.
Raises:
ValueError: If the value is not an absolute path
"""
if value is None:
self._absoluteExcludePaths = None
else:
try:
saved = self._absoluteExcludePaths
self._absoluteExcludePaths = AbsolutePathList()
self._absoluteExcludePaths.extend(value)
except Exception as e:
self._absoluteExcludePaths = saved
raise e
def _getAbsoluteExcludePaths(self):
"""
Property target used to get the absolute exclude paths list.
"""
return self._absoluteExcludePaths
def _setExcludePatterns(self, value):
"""
Property target used to set the exclude patterns list.
"""
if value is None:
self._excludePatterns = None
else:
try:
saved = self._excludePatterns
self._excludePatterns = RegexList()
self._excludePatterns.extend(value)
except Exception as e:
self._excludePatterns = saved
raise e
def _getExcludePatterns(self):
"""
Property target used to get the exclude patterns list.
"""
return self._excludePatterns
def _setCollectFiles(self, value):
"""
Property target used to set the collect files list.
Either the value must be ``None`` or each element must be a ``CollectFile``.
Raises:
ValueError: If the value is not a ``CollectFile``
"""
if value is None:
self._collectFiles = None
else:
try:
saved = self._collectFiles
self._collectFiles = ObjectTypeList(CollectFile, "CollectFile")
self._collectFiles.extend(value)
except Exception as e:
self._collectFiles = saved
raise e
def _getCollectFiles(self):
"""
Property target used to get the collect files list.
"""
return self._collectFiles
def _setCollectDirs(self, value):
"""
Property target used to set the collect dirs list.
Either the value must be ``None`` or each element must be a ``CollectDir``.
Raises:
ValueError: If the value is not a ``CollectDir``
"""
if value is None:
self._collectDirs = None
else:
try:
saved = self._collectDirs
self._collectDirs = ObjectTypeList(CollectDir, "CollectDir")
self._collectDirs.extend(value)
except Exception as e:
self._collectDirs = saved
raise e
def _getCollectDirs(self):
"""
Property target used to get the collect dirs list.
"""
return self._collectDirs
targetDir = property(_getTargetDir, _setTargetDir, None, "Directory to collect files into.")
collectMode = property(_getCollectMode, _setCollectMode, None, "Default collect mode.")
archiveMode = property(_getArchiveMode, _setArchiveMode, None, "Default archive mode for collect files.")
ignoreFile = property(_getIgnoreFile, _setIgnoreFile, None, "Default ignore file name.")
absoluteExcludePaths = property(_getAbsoluteExcludePaths, _setAbsoluteExcludePaths, None, "List of absolute paths to exclude.")
excludePatterns = property(_getExcludePatterns, _setExcludePatterns, None, "List of regular expressions patterns to exclude.")
collectFiles = property(_getCollectFiles, _setCollectFiles, None, "List of collect files.")
collectDirs = property(_getCollectDirs, _setCollectDirs, None, "List of collect directories.")
########################################################################
# StageConfig class definition
########################################################################
@total_ordering
[docs]class StageConfig(object):
"""
Class representing a Cedar Backup stage configuration.
The following restrictions exist on data in this class:
- The target directory must be an absolute path
- The list of local peers must contain only ``LocalPeer`` objects
- The list of remote peers must contain only ``RemotePeer`` objects
*Note:* Lists within this class are "unordered" for equality comparisons.
"""
[docs] def __init__(self, targetDir=None, localPeers=None, remotePeers=None):
"""
Constructor for the ``StageConfig`` class.
Args:
targetDir: Directory to stage files into, by peer name
localPeers: List of local peers
remotePeers: List of remote peers
Raises:
ValueError: If one of the values is invalid
"""
self._targetDir = None
self._localPeers = None
self._remotePeers = None
self.targetDir = targetDir
self.localPeers = localPeers
self.remotePeers = remotePeers
def __repr__(self):
"""
Official string representation for class instance.
"""
return "StageConfig(%s, %s, %s)" % (self.targetDir, self.localPeers, self.remotePeers)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Lists within this class are "unordered" for equality comparisons.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.targetDir != other.targetDir:
if str(self.targetDir or "") < str(other.targetDir or ""):
return -1
else:
return 1
if self.localPeers != other.localPeers:
if self.localPeers < other.localPeers:
return -1
else:
return 1
if self.remotePeers != other.remotePeers:
if self.remotePeers < other.remotePeers:
return -1
else:
return 1
return 0
[docs] def hasPeers(self):
"""
Indicates whether any peers are filled into this object.
Returns:
Boolean true if any local or remote peers are filled in, false otherwise
"""
return ((self.localPeers is not None and len(self.localPeers) > 0) or
(self.remotePeers is not None and len(self.remotePeers) > 0))
def _setTargetDir(self, value):
"""
Property target used to set the target directory.
The value must be an absolute path if it is not ``None``.
It does not have to exist on disk at the time of assignment.
Raises:
ValueError: If the value is not an absolute path
ValueError: If the value cannot be encoded properly
"""
if value is not None:
if not os.path.isabs(value):
raise ValueError("Target directory must be an absolute path.")
self._targetDir = encodePath(value)
def _getTargetDir(self):
"""
Property target used to get the target directory.
"""
return self._targetDir
def _setLocalPeers(self, value):
"""
Property target used to set the local peers list.
Either the value must be ``None`` or each element must be a ``LocalPeer``.
Raises:
ValueError: If the value is not an absolute path
"""
if value is None:
self._localPeers = None
else:
try:
saved = self._localPeers
self._localPeers = ObjectTypeList(LocalPeer, "LocalPeer")
self._localPeers.extend(value)
except Exception as e:
self._localPeers = saved
raise e
def _getLocalPeers(self):
"""
Property target used to get the local peers list.
"""
return self._localPeers
def _setRemotePeers(self, value):
"""
Property target used to set the remote peers list.
Either the value must be ``None`` or each element must be a ``RemotePeer``.
Raises:
ValueError: If the value is not a ``RemotePeer``
"""
if value is None:
self._remotePeers = None
else:
try:
saved = self._remotePeers
self._remotePeers = ObjectTypeList(RemotePeer, "RemotePeer")
self._remotePeers.extend(value)
except Exception as e:
self._remotePeers = saved
raise e
def _getRemotePeers(self):
"""
Property target used to get the remote peers list.
"""
return self._remotePeers
targetDir = property(_getTargetDir, _setTargetDir, None, "Directory to stage files into, by peer name.")
localPeers = property(_getLocalPeers, _setLocalPeers, None, "List of local peers.")
remotePeers = property(_getRemotePeers, _setRemotePeers, None, "List of remote peers.")
########################################################################
# StoreConfig class definition
########################################################################
@total_ordering
[docs]class StoreConfig(object):
"""
Class representing a Cedar Backup store configuration.
The following restrictions exist on data in this class:
- The source directory must be an absolute path.
- The media type must be one of the values in :any:`VALID_MEDIA_TYPES`.
- The device type must be one of the values in :any:`VALID_DEVICE_TYPES`.
- The device path must be an absolute path.
- The SCSI id, if provided, must be in the form specified by :any:`validateScsiId`.
- The drive speed must be an integer >= 1
- The blanking behavior must be a ``BlankBehavior`` object
- The refresh media delay must be an integer >= 0
- The eject delay must be an integer >= 0
Note that although the blanking factor must be a positive floating point
number, it is stored as a string. This is done so that we can losslessly go
back and forth between XML and object representations of configuration.
"""
[docs] def __init__(self, sourceDir=None, mediaType=None, deviceType=None,
devicePath=None, deviceScsiId=None, driveSpeed=None,
checkData=False, warnMidnite=False, noEject=False,
checkMedia=False, blankBehavior=None, refreshMediaDelay=None,
ejectDelay=None):
"""
Constructor for the ``StoreConfig`` class.
Args:
sourceDir: Directory whose contents should be written to media
mediaType: Type of the media (see notes above)
deviceType: Type of the device (optional, see notes above)
devicePath: Filesystem device name for writer device, i.e. ``/dev/cdrw``
deviceScsiId: SCSI id for writer device, i.e. ``[<method>:]scsibus,target,lun``
driveSpeed: Speed of the drive, i.e. ``2`` for 2x drive, etc
checkData: Whether resulting image should be validated
checkMedia: Whether media should be checked before being written to
warnMidnite: Whether to generate warnings for crossing midnite
noEject: Indicates that the writer device should not be ejected
blankBehavior: Controls optimized blanking behavior
refreshMediaDelay: Delay, in seconds, to add after refreshing media
ejectDelay: Delay, in seconds, to add after ejecting media before closing the tray
Raises:
ValueError: If one of the values is invalid
"""
self._sourceDir = None
self._mediaType = None
self._deviceType = None
self._devicePath = None
self._deviceScsiId = None
self._driveSpeed = None
self._checkData = None
self._checkMedia = None
self._warnMidnite = None
self._noEject = None
self._blankBehavior = None
self._refreshMediaDelay = None
self._ejectDelay = None
self.sourceDir = sourceDir
self.mediaType = mediaType
self.deviceType = deviceType
self.devicePath = devicePath
self.deviceScsiId = deviceScsiId
self.driveSpeed = driveSpeed
self.checkData = checkData
self.checkMedia = checkMedia
self.warnMidnite = warnMidnite
self.noEject = noEject
self.blankBehavior = blankBehavior
self.refreshMediaDelay = refreshMediaDelay
self.ejectDelay = ejectDelay
def __repr__(self):
"""
Official string representation for class instance.
"""
return "StoreConfig(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (
self.sourceDir, self.mediaType, self.deviceType,
self.devicePath, self.deviceScsiId, self.driveSpeed,
self.checkData, self.warnMidnite, self.noEject,
self.checkMedia, self.blankBehavior, self.refreshMediaDelay,
self.ejectDelay)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.sourceDir != other.sourceDir:
if str(self.sourceDir or "") < str(other.sourceDir or ""):
return -1
else:
return 1
if self.mediaType != other.mediaType:
if str(self.mediaType or "") < str(other.mediaType or ""):
return -1
else:
return 1
if self.deviceType != other.deviceType:
if str(self.deviceType or "") < str(other.deviceType or ""):
return -1
else:
return 1
if self.devicePath != other.devicePath:
if str(self.devicePath or "") < str(other.devicePath or ""):
return -1
else:
return 1
if self.deviceScsiId != other.deviceScsiId:
if str(self.deviceScsiId or "") < str(other.deviceScsiId or ""):
return -1
else:
return 1
if self.driveSpeed != other.driveSpeed:
if str(self.driveSpeed or "") < str(other.driveSpeed or ""):
return -1
else:
return 1
if self.checkData != other.checkData:
if self.checkData < other.checkData:
return -1
else:
return 1
if self.checkMedia != other.checkMedia:
if self.checkMedia < other.checkMedia:
return -1
else:
return 1
if self.warnMidnite != other.warnMidnite:
if self.warnMidnite < other.warnMidnite:
return -1
else:
return 1
if self.noEject != other.noEject:
if self.noEject < other.noEject:
return -1
else:
return 1
if self.blankBehavior != other.blankBehavior:
if str(self.blankBehavior or "") < str(other.blankBehavior or ""):
return -1
else:
return 1
if self.refreshMediaDelay != other.refreshMediaDelay:
if int(self.refreshMediaDelay or 0) < int(other.refreshMediaDelay or 0):
return -1
else:
return 1
if self.ejectDelay != other.ejectDelay:
if int(self.ejectDelay or 0) < int(other.ejectDelay or 0):
return -1
else:
return 1
return 0
def _setSourceDir(self, value):
"""
Property target used to set the source directory.
The value must be an absolute path if it is not ``None``.
It does not have to exist on disk at the time of assignment.
Raises:
ValueError: If the value is not an absolute path
ValueError: If the value cannot be encoded properly
"""
if value is not None:
if not os.path.isabs(value):
raise ValueError("Source directory must be an absolute path.")
self._sourceDir = encodePath(value)
def _getSourceDir(self):
"""
Property target used to get the source directory.
"""
return self._sourceDir
def _setMediaType(self, value):
"""
Property target used to set the media type.
The value must be one of :any:`VALID_MEDIA_TYPES`.
Raises:
ValueError: If the value is not valid
"""
if value is not None:
if value not in VALID_MEDIA_TYPES:
raise ValueError("Media type must be one of %s." % VALID_MEDIA_TYPES)
self._mediaType = value
def _getMediaType(self):
"""
Property target used to get the media type.
"""
return self._mediaType
def _setDeviceType(self, value):
"""
Property target used to set the device type.
The value must be one of :any:`VALID_DEVICE_TYPES`.
Raises:
ValueError: If the value is not valid
"""
if value is not None:
if value not in VALID_DEVICE_TYPES:
raise ValueError("Device type must be one of %s." % VALID_DEVICE_TYPES)
self._deviceType = value
def _getDeviceType(self):
"""
Property target used to get the device type.
"""
return self._deviceType
def _setDevicePath(self, value):
"""
Property target used to set the device path.
The value must be an absolute path if it is not ``None``.
It does not have to exist on disk at the time of assignment.
Raises:
ValueError: If the value is not an absolute path
ValueError: If the value cannot be encoded properly
"""
if value is not None:
if not os.path.isabs(value):
raise ValueError("Device path must be an absolute path.")
self._devicePath = encodePath(value)
def _getDevicePath(self):
"""
Property target used to get the device path.
"""
return self._devicePath
def _setDeviceScsiId(self, value):
"""
Property target used to set the SCSI id
The SCSI id must be valid per :any:`validateScsiId`.
Raises:
ValueError: If the value is not valid
"""
if value is None:
self._deviceScsiId = None
else:
self._deviceScsiId = validateScsiId(value)
def _getDeviceScsiId(self):
"""
Property target used to get the SCSI id.
"""
return self._deviceScsiId
def _setDriveSpeed(self, value):
"""
Property target used to set the drive speed.
The drive speed must be valid per :any:`validateDriveSpeed`.
Raises:
ValueError: If the value is not valid
"""
self._driveSpeed = validateDriveSpeed(value)
def _getDriveSpeed(self):
"""
Property target used to get the drive speed.
"""
return self._driveSpeed
def _setCheckData(self, value):
"""
Property target used to set the check data flag.
No validations, but we normalize the value to ``True`` or ``False``.
"""
if value:
self._checkData = True
else:
self._checkData = False
def _getCheckData(self):
"""
Property target used to get the check data flag.
"""
return self._checkData
def _setCheckMedia(self, value):
"""
Property target used to set the check media flag.
No validations, but we normalize the value to ``True`` or ``False``.
"""
if value:
self._checkMedia = True
else:
self._checkMedia = False
def _getCheckMedia(self):
"""
Property target used to get the check media flag.
"""
return self._checkMedia
def _setWarnMidnite(self, value):
"""
Property target used to set the midnite warning flag.
No validations, but we normalize the value to ``True`` or ``False``.
"""
if value:
self._warnMidnite = True
else:
self._warnMidnite = False
def _getWarnMidnite(self):
"""
Property target used to get the midnite warning flag.
"""
return self._warnMidnite
def _setNoEject(self, value):
"""
Property target used to set the no-eject flag.
No validations, but we normalize the value to ``True`` or ``False``.
"""
if value:
self._noEject = True
else:
self._noEject = False
def _getNoEject(self):
"""
Property target used to get the no-eject flag.
"""
return self._noEject
def _setBlankBehavior(self, value):
"""
Property target used to set blanking behavior configuration.
If not ``None``, the value must be a ``BlankBehavior`` object.
Raises:
ValueError: If the value is not a ``BlankBehavior``
"""
if value is None:
self._blankBehavior = None
else:
if not isinstance(value, BlankBehavior):
raise ValueError("Value must be a ``BlankBehavior`` object.")
self._blankBehavior = value
def _getBlankBehavior(self):
"""
Property target used to get the blanking behavior configuration.
"""
return self._blankBehavior
def _setRefreshMediaDelay(self, value):
"""
Property target used to set the refreshMediaDelay.
The value must be an integer >= 0.
Raises:
ValueError: If the value is not valid
"""
if value is None:
self._refreshMediaDelay = None
else:
try:
value = int(value)
except TypeError:
raise ValueError("Action refreshMediaDelay value must be an integer >= 0.")
if value < 0:
raise ValueError("Action refreshMediaDelay value must be an integer >= 0.")
if value == 0:
value = None # normalize this out, since it's the default
self._refreshMediaDelay = value
def _getRefreshMediaDelay(self):
"""
Property target used to get the action refreshMediaDelay.
"""
return self._refreshMediaDelay
def _setEjectDelay(self, value):
"""
Property target used to set the ejectDelay.
The value must be an integer >= 0.
Raises:
ValueError: If the value is not valid
"""
if value is None:
self._ejectDelay = None
else:
try:
value = int(value)
except TypeError:
raise ValueError("Action ejectDelay value must be an integer >= 0.")
if value < 0:
raise ValueError("Action ejectDelay value must be an integer >= 0.")
if value == 0:
value = None # normalize this out, since it's the default
self._ejectDelay = value
def _getEjectDelay(self):
"""
Property target used to get the action ejectDelay.
"""
return self._ejectDelay
sourceDir = property(_getSourceDir, _setSourceDir, None, "Directory whose contents should be written to media.")
mediaType = property(_getMediaType, _setMediaType, None, "Type of the media (see notes above).")
deviceType = property(_getDeviceType, _setDeviceType, None, "Type of the device (optional, see notes above).")
devicePath = property(_getDevicePath, _setDevicePath, None, "Filesystem device name for writer device.")
deviceScsiId = property(_getDeviceScsiId, _setDeviceScsiId, None, "SCSI id for writer device (optional, see notes above).")
driveSpeed = property(_getDriveSpeed, _setDriveSpeed, None, "Speed of the drive.")
checkData = property(_getCheckData, _setCheckData, None, "Whether resulting image should be validated.")
checkMedia = property(_getCheckMedia, _setCheckMedia, None, "Whether media should be checked before being written to.")
warnMidnite = property(_getWarnMidnite, _setWarnMidnite, None, "Whether to generate warnings for crossing midnite.")
noEject = property(_getNoEject, _setNoEject, None, "Indicates that the writer device should not be ejected.")
blankBehavior = property(_getBlankBehavior, _setBlankBehavior, None, "Controls optimized blanking behavior.")
refreshMediaDelay = property(_getRefreshMediaDelay, _setRefreshMediaDelay, None, "Delay, in seconds, to add after refreshing media.")
ejectDelay = property(_getEjectDelay, _setEjectDelay, None, "Delay, in seconds, to add after ejecting media before closing the tray")
########################################################################
# PurgeConfig class definition
########################################################################
@total_ordering
[docs]class PurgeConfig(object):
"""
Class representing a Cedar Backup purge configuration.
The following restrictions exist on data in this class:
- The purge directory list must be a list of ``PurgeDir`` objects.
For the ``purgeDirs`` list, validation is accomplished through the
:any:`util.ObjectTypeList` list implementation that overrides common list
methods and transparently ensures that each element is a ``PurgeDir``.
*Note:* Lists within this class are "unordered" for equality comparisons.
"""
[docs] def __init__(self, purgeDirs=None):
"""
Constructor for the ``Purge`` class.
Args:
purgeDirs: List of purge directories
Raises:
ValueError: If one of the values is invalid
"""
self._purgeDirs = None
self.purgeDirs = purgeDirs
def __repr__(self):
"""
Official string representation for class instance.
"""
return "PurgeConfig(%s)" % self.purgeDirs
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Lists within this class are "unordered" for equality comparisons.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.purgeDirs != other.purgeDirs:
if self.purgeDirs < other.purgeDirs:
return -1
else:
return 1
return 0
def _setPurgeDirs(self, value):
"""
Property target used to set the purge dirs list.
Either the value must be ``None`` or each element must be a ``PurgeDir``.
Raises:
ValueError: If the value is not a ``PurgeDir``
"""
if value is None:
self._purgeDirs = None
else:
try:
saved = self._purgeDirs
self._purgeDirs = ObjectTypeList(PurgeDir, "PurgeDir")
self._purgeDirs.extend(value)
except Exception as e:
self._purgeDirs = saved
raise e
def _getPurgeDirs(self):
"""
Property target used to get the purge dirs list.
"""
return self._purgeDirs
purgeDirs = property(_getPurgeDirs, _setPurgeDirs, None, "List of directories to purge.")
########################################################################
# Config class definition
########################################################################
@total_ordering
[docs]class Config(object):
######################
# Class documentation
######################
"""
Class representing a Cedar Backup XML configuration document.
The ``Config`` class is a Python object representation of a Cedar Backup XML
configuration file. It is intended to be the only Python-language interface
to Cedar Backup configuration on disk for both Cedar Backup itself and for
external applications.
The object representation is two-way: XML data can be used to create a
``Config`` object, and then changes to the object can be propogated back to
disk. A ``Config`` object can even be used to create a configuration file
from scratch programmatically.
This class and the classes it is composed from often use Python's
``property`` construct to validate input and limit access to values. Some
validations can only be done once a document is considered "complete"
(see module notes for more details).
Assignments to the various instance variables must match the expected
type, i.e. ``reference`` must be a ``ReferenceConfig``. The internal check
uses the built-in ``isinstance`` function, so it should be OK to use
subclasses if you want to.
If an instance variable is not set, its value will be ``None``. When an
object is initialized without using an XML document, all of the values
will be ``None``. Even when an object is initialized using XML, some of
the values might be ``None`` because not every section is required.
*Note:* Lists within this class are "unordered" for equality comparisons.
"""
##############
# Constructor
##############
[docs] def __init__(self, xmlData=None, xmlPath=None, validate=True):
"""
Initializes a configuration object.
If you initialize the object without passing either ``xmlData`` or
``xmlPath``, then configuration will be empty and will be invalid until it
is filled in properly.
No reference to the original XML data or original path is saved off by
this class. Once the data has been parsed (successfully or not) this
original information is discarded.
Unless the ``validate`` argument is ``False``, the :any:`Config.validate`
method will be called (with its default arguments) against configuration
after successfully parsing any passed-in XML. Keep in mind that even if
``validate`` is ``False``, it might not be possible to parse the passed-in
XML document if lower-level validations fail.
*Note:* It is strongly suggested that the ``validate`` option always be set
to ``True`` (the default) unless there is a specific need to read in
invalid configuration from disk.
Args:
xmlData (String data): XML data representing configuration
xmlPath (Absolute path to a file on disk): Path to an XML file on disk
validate (Boolean true/false): Validate the document after parsing it
Raises:
ValueError: If both ``xmlData`` and ``xmlPath`` are passed-in
ValueError: If the XML data in ``xmlData`` or ``xmlPath`` cannot be parsed
ValueError: If the parsed configuration document is not valid
"""
self._reference = None
self._extensions = None
self._options = None
self._peers = None
self._collect = None
self._stage = None
self._store = None
self._purge = None
self.reference = None
self.extensions = None
self.options = None
self.peers = None
self.collect = None
self.stage = None
self.store = None
self.purge = None
if xmlData is not None and xmlPath is not None:
raise ValueError("Use either xmlData or xmlPath, but not both.")
if xmlData is not None:
self._parseXmlData(xmlData)
if validate:
self.validate()
elif xmlPath is not None:
with open(xmlPath) as f:
xmlData = f.read()
self._parseXmlData(xmlData)
if validate:
self.validate()
#########################
# String representations
#########################
def __repr__(self):
"""
Official string representation for class instance.
"""
return "Config(%s, %s, %s, %s, %s, %s, %s, %s)" % (self.reference, self.extensions, self.options,
self.peers, self.collect, self.stage, self.store,
self.purge)
def __str__(self):
"""
Informal string representation for class instance.
"""
return self.__repr__()
#############################
# Standard comparison method
#############################
def __eq__(self, other):
"""Equals operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) == 0
def __lt__(self, other):
"""Less-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) < 0
def __gt__(self, other):
"""Greater-than operator, implemented in terms of original Python 2 compare operator."""
return self.__cmp__(other) > 0
def __cmp__(self, other):
"""
Original Python 2 comparison operator.
Lists within this class are "unordered" for equality comparisons.
Args:
other: Other object to compare to
Returns:
-1/0/1 depending on whether self is ``<``, ``=`` or ``>`` other
"""
if other is None:
return 1
if self.reference != other.reference:
if self.reference < other.reference:
return -1
else:
return 1
if self.extensions != other.extensions:
if self.extensions < other.extensions:
return -1
else:
return 1
if self.options != other.options:
if self.options < other.options:
return -1
else:
return 1
if self.peers != other.peers:
if self.peers < other.peers:
return -1
else:
return 1
if self.collect != other.collect:
if self.collect < other.collect:
return -1
else:
return 1
if self.stage != other.stage:
if self.stage < other.stage:
return -1
else:
return 1
if self.store != other.store:
if self.store < other.store:
return -1
else:
return 1
if self.purge != other.purge:
if self.purge < other.purge:
return -1
else:
return 1
return 0
#############
# Properties
#############
def _setReference(self, value):
"""
Property target used to set the reference configuration value.
If not ``None``, the value must be a ``ReferenceConfig`` object.
Raises:
ValueError: If the value is not a ``ReferenceConfig``
"""
if value is None:
self._reference = None
else:
if not isinstance(value, ReferenceConfig):
raise ValueError("Value must be a ``ReferenceConfig`` object.")
self._reference = value
def _getReference(self):
"""
Property target used to get the reference configuration value.
"""
return self._reference
def _setExtensions(self, value):
"""
Property target used to set the extensions configuration value.
If not ``None``, the value must be a ``ExtensionsConfig`` object.
Raises:
ValueError: If the value is not a ``ExtensionsConfig``
"""
if value is None:
self._extensions = None
else:
if not isinstance(value, ExtensionsConfig):
raise ValueError("Value must be a ``ExtensionsConfig`` object.")
self._extensions = value
def _getExtensions(self):
"""
Property target used to get the extensions configuration value.
"""
return self._extensions
def _setOptions(self, value):
"""
Property target used to set the options configuration value.
If not ``None``, the value must be an ``OptionsConfig`` object.
Raises:
ValueError: If the value is not a ``OptionsConfig``
"""
if value is None:
self._options = None
else:
if not isinstance(value, OptionsConfig):
raise ValueError("Value must be a ``OptionsConfig`` object.")
self._options = value
def _getOptions(self):
"""
Property target used to get the options configuration value.
"""
return self._options
def _setPeers(self, value):
"""
Property target used to set the peers configuration value.
If not ``None``, the value must be an ``PeersConfig`` object.
Raises:
ValueError: If the value is not a ``PeersConfig``
"""
if value is None:
self._peers = None
else:
if not isinstance(value, PeersConfig):
raise ValueError("Value must be a ``PeersConfig`` object.")
self._peers = value
def _getPeers(self):
"""
Property target used to get the peers configuration value.
"""
return self._peers
def _setCollect(self, value):
"""
Property target used to set the collect configuration value.
If not ``None``, the value must be a ``CollectConfig`` object.
Raises:
ValueError: If the value is not a ``CollectConfig``
"""
if value is None:
self._collect = None
else:
if not isinstance(value, CollectConfig):
raise ValueError("Value must be a ``CollectConfig`` object.")
self._collect = value
def _getCollect(self):
"""
Property target used to get the collect configuration value.
"""
return self._collect
def _setStage(self, value):
"""
Property target used to set the stage configuration value.
If not ``None``, the value must be a ``StageConfig`` object.
Raises:
ValueError: If the value is not a ``StageConfig``
"""
if value is None:
self._stage = None
else:
if not isinstance(value, StageConfig):
raise ValueError("Value must be a ``StageConfig`` object.")
self._stage = value
def _getStage(self):
"""
Property target used to get the stage configuration value.
"""
return self._stage
def _setStore(self, value):
"""
Property target used to set the store configuration value.
If not ``None``, the value must be a ``StoreConfig`` object.
Raises:
ValueError: If the value is not a ``StoreConfig``
"""
if value is None:
self._store = None
else:
if not isinstance(value, StoreConfig):
raise ValueError("Value must be a ``StoreConfig`` object.")
self._store = value
def _getStore(self):
"""
Property target used to get the store configuration value.
"""
return self._store
def _setPurge(self, value):
"""
Property target used to set the purge configuration value.
If not ``None``, the value must be a ``PurgeConfig`` object.
Raises:
ValueError: If the value is not a ``PurgeConfig``
"""
if value is None:
self._purge = None
else:
if not isinstance(value, PurgeConfig):
raise ValueError("Value must be a ``PurgeConfig`` object.")
self._purge = value
def _getPurge(self):
"""
Property target used to get the purge configuration value.
"""
return self._purge
reference = property(_getReference, _setReference, None, "Reference configuration in terms of a ``ReferenceConfig`` object.")
extensions = property(_getExtensions, _setExtensions, None, "Extensions configuration in terms of a ``ExtensionsConfig`` object.")
options = property(_getOptions, _setOptions, None, "Options configuration in terms of a ``OptionsConfig`` object.")
peers = property(_getPeers, _setPeers, None, "Peers configuration in terms of a ``PeersConfig`` object.")
collect = property(_getCollect, _setCollect, None, "Collect configuration in terms of a ``CollectConfig`` object.")
stage = property(_getStage, _setStage, None, "Stage configuration in terms of a ``StageConfig`` object.")
store = property(_getStore, _setStore, None, "Store configuration in terms of a ``StoreConfig`` object.")
purge = property(_getPurge, _setPurge, None, "Purge configuration in terms of a ``PurgeConfig`` object.")
#################
# Public methods
#################
[docs] def validate(self, requireOneAction=True, requireReference=False, requireExtensions=False, requireOptions=True,
requireCollect=False, requireStage=False, requireStore=False, requirePurge=False, requirePeers=False):
"""
Validates configuration represented by the object.
This method encapsulates all of the validations that should apply to a
fully "complete" document but are not already taken care of by earlier
validations. It also provides some extra convenience functionality which
might be useful to some people. The process of validation is laid out in
the *Validation* section in the class notes (above).
Args:
requireOneAction: Require at least one of the collect, stage, store or purge sections
requireReference: Require the reference section
requireExtensions: Require the extensions section
requireOptions: Require the options section
requirePeers: Require the peers section
requireCollect: Require the collect section
requireStage: Require the stage section
requireStore: Require the store section
requirePurge: Require the purge section
Raises:
ValueError: If one of the validations fails
"""
if requireOneAction and (self.collect, self.stage, self.store, self.purge) == (None, None, None, None):
raise ValueError("At least one of the collect, stage, store and purge sections is required.")
if requireReference and self.reference is None:
raise ValueError("The reference is section is required.")
if requireExtensions and self.extensions is None:
raise ValueError("The extensions is section is required.")
if requireOptions and self.options is None:
raise ValueError("The options is section is required.")
if requirePeers and self.peers is None:
raise ValueError("The peers is section is required.")
if requireCollect and self.collect is None:
raise ValueError("The collect is section is required.")
if requireStage and self.stage is None:
raise ValueError("The stage is section is required.")
if requireStore and self.store is None:
raise ValueError("The store is section is required.")
if requirePurge and self.purge is None:
raise ValueError("The purge is section is required.")
self._validateContents()
#####################################
# High-level methods for parsing XML
#####################################
def _parseXmlData(self, xmlData):
"""
Internal method to parse an XML string into the object.
This method parses the XML document into a DOM tree (``xmlDom``) and then
calls individual static methods to parse each of the individual
configuration sections.
Most of the validation we do here has to do with whether the document can
be parsed and whether any values which exist are valid. We don't do much
validation as to whether required elements actually exist unless we have
to to make sense of the document (instead, that's the job of the
:any:`validate` method).
Args:
xmlData (String data): XML data to be parsed
Raises:
ValueError: If the XML cannot be successfully parsed
"""
(xmlDom, parentNode) = createInputDom(xmlData)
self._reference = Config._parseReference(parentNode)
self._extensions = Config._parseExtensions(parentNode)
self._options = Config._parseOptions(parentNode)
self._peers = Config._parsePeers(parentNode)
self._collect = Config._parseCollect(parentNode)
self._stage = Config._parseStage(parentNode)
self._store = Config._parseStore(parentNode)
self._purge = Config._parsePurge(parentNode)
@staticmethod
def _parseReference(parentNode):
"""
Parses a reference configuration section.
We read the following fields::
author //cb_config/reference/author
revision //cb_config/reference/revision
description //cb_config/reference/description
generator //cb_config/reference/generator
Args:
parentNode: Parent node to search beneath
Returns:
``ReferenceConfig`` object or ``None`` if the section does not exist
Raises:
ValueError: If some filled-in value is invalid
"""
reference = None
sectionNode = readFirstChild(parentNode, "reference")
if sectionNode is not None:
reference = ReferenceConfig()
reference.author = readString(sectionNode, "author")
reference.revision = readString(sectionNode, "revision")
reference.description = readString(sectionNode, "description")
reference.generator = readString(sectionNode, "generator")
return reference
@staticmethod
def _parseExtensions(parentNode):
"""
Parses an extensions configuration section.
We read the following fields::
orderMode //cb_config/extensions/order_mode
We also read groups of the following items, one list element per item::
name //cb_config/extensions/action/name
module //cb_config/extensions/action/module
function //cb_config/extensions/action/function
index //cb_config/extensions/action/index
dependencies //cb_config/extensions/action/depends
The extended actions are parsed by :any:`_parseExtendedActions`.
Args:
parentNode: Parent node to search beneath
Returns:
``ExtensionsConfig`` object or ``None`` if the section does not exist
Raises:
ValueError: If some filled-in value is invalid
"""
extensions = None
sectionNode = readFirstChild(parentNode, "extensions")
if sectionNode is not None:
extensions = ExtensionsConfig()
extensions.orderMode = readString(sectionNode, "order_mode")
extensions.actions = Config._parseExtendedActions(sectionNode)
return extensions
@staticmethod
def _parseOptions(parentNode):
"""
Parses a options configuration section.
We read the following fields::
startingDay //cb_config/options/starting_day
workingDir //cb_config/options/working_dir
backupUser //cb_config/options/backup_user
backupGroup //cb_config/options/backup_group
rcpCommand //cb_config/options/rcp_command
rshCommand //cb_config/options/rsh_command
cbackCommand //cb_config/options/cback_command
managedActions //cb_config/options/managed_actions
The list of managed actions is a comma-separated list of action names.
We also read groups of the following items, one list element per
item::
overrides //cb_config/options/override
hooks //cb_config/options/hook
The overrides are parsed by :any:`_parseOverrides` and the hooks are parsed
by :any:`_parseHooks`.
Args:
parentNode: Parent node to search beneath
Returns:
``OptionsConfig`` object or ``None`` if the section does not exist
Raises:
ValueError: If some filled-in value is invalid
"""
options = None
sectionNode = readFirstChild(parentNode, "options")
if sectionNode is not None:
options = OptionsConfig()
options.startingDay = readString(sectionNode, "starting_day")
options.workingDir = readString(sectionNode, "working_dir")
options.backupUser = readString(sectionNode, "backup_user")
options.backupGroup = readString(sectionNode, "backup_group")
options.rcpCommand = readString(sectionNode, "rcp_command")
options.rshCommand = readString(sectionNode, "rsh_command")
options.cbackCommand = readString(sectionNode, "cback_command")
options.overrides = Config._parseOverrides(sectionNode)
options.hooks = Config._parseHooks(sectionNode)
managedActions = readString(sectionNode, "managed_actions")
options.managedActions = parseCommaSeparatedString(managedActions)
return options
@staticmethod
def _parsePeers(parentNode):
"""
Parses a peers configuration section.
We read groups of the following items, one list element per
item::
localPeers //cb_config/stage/peer
remotePeers //cb_config/stage/peer
The individual peer entries are parsed by :any:`_parsePeerList`.
Args:
parentNode: Parent node to search beneath
Returns:
``StageConfig`` object or ``None`` if the section does not exist
Raises:
ValueError: If some filled-in value is invalid
"""
peers = None
sectionNode = readFirstChild(parentNode, "peers")
if sectionNode is not None:
peers = PeersConfig()
(peers.localPeers, peers.remotePeers) = Config._parsePeerList(sectionNode)
return peers
@staticmethod
def _parseCollect(parentNode):
"""
Parses a collect configuration section.
We read the following individual fields::
targetDir //cb_config/collect/collect_dir
collectMode //cb_config/collect/collect_mode
archiveMode //cb_config/collect/archive_mode
ignoreFile //cb_config/collect/ignore_file
We also read groups of the following items, one list element per
item::
absoluteExcludePaths //cb_config/collect/exclude/abs_path
excludePatterns //cb_config/collect/exclude/pattern
collectFiles //cb_config/collect/file
collectDirs //cb_config/collect/dir
The exclusions are parsed by :any:`_parseExclusions`, the collect files are
parsed by :any:`_parseCollectFiles`, and the directories are parsed by
:any:`_parseCollectDirs`.
Args:
parentNode: Parent node to search beneath
Returns:
``CollectConfig`` object or ``None`` if the section does not exist
Raises:
ValueError: If some filled-in value is invalid
"""
collect = None
sectionNode = readFirstChild(parentNode, "collect")
if sectionNode is not None:
collect = CollectConfig()
collect.targetDir = readString(sectionNode, "collect_dir")
collect.collectMode = readString(sectionNode, "collect_mode")
collect.archiveMode = readString(sectionNode, "archive_mode")
collect.ignoreFile = readString(sectionNode, "ignore_file")
(collect.absoluteExcludePaths, unused, collect.excludePatterns) = Config._parseExclusions(sectionNode)
collect.collectFiles = Config._parseCollectFiles(sectionNode)
collect.collectDirs = Config._parseCollectDirs(sectionNode)
return collect
@staticmethod
def _parseStage(parentNode):
"""
Parses a stage configuration section.
We read the following individual fields::
targetDir //cb_config/stage/staging_dir
We also read groups of the following items, one list element per
item::
localPeers //cb_config/stage/peer
remotePeers //cb_config/stage/peer
The individual peer entries are parsed by :any:`_parsePeerList`.
Args:
parentNode: Parent node to search beneath
Returns:
``StageConfig`` object or ``None`` if the section does not exist
Raises:
ValueError: If some filled-in value is invalid
"""
stage = None
sectionNode = readFirstChild(parentNode, "stage")
if sectionNode is not None:
stage = StageConfig()
stage.targetDir = readString(sectionNode, "staging_dir")
(stage.localPeers, stage.remotePeers) = Config._parsePeerList(sectionNode)
return stage
@staticmethod
def _parseStore(parentNode):
"""
Parses a store configuration section.
We read the following fields::
sourceDir //cb_config/store/source_dir
mediaType //cb_config/store/media_type
deviceType //cb_config/store/device_type
devicePath //cb_config/store/target_device
deviceScsiId //cb_config/store/target_scsi_id
driveSpeed //cb_config/store/drive_speed
checkData //cb_config/store/check_data
checkMedia //cb_config/store/check_media
warnMidnite //cb_config/store/warn_midnite
noEject //cb_config/store/no_eject
Blanking behavior configuration is parsed by the ``_parseBlankBehavior``
method.
Args:
parentNode: Parent node to search beneath
Returns:
``StoreConfig`` object or ``None`` if the section does not exist
Raises:
ValueError: If some filled-in value is invalid
"""
store = None
sectionNode = readFirstChild(parentNode, "store")
if sectionNode is not None:
store = StoreConfig()
store.sourceDir = readString(sectionNode, "source_dir")
store.mediaType = readString(sectionNode, "media_type")
store.deviceType = readString(sectionNode, "device_type")
store.devicePath = readString(sectionNode, "target_device")
store.deviceScsiId = readString(sectionNode, "target_scsi_id")
store.driveSpeed = readInteger(sectionNode, "drive_speed")
store.checkData = readBoolean(sectionNode, "check_data")
store.checkMedia = readBoolean(sectionNode, "check_media")
store.warnMidnite = readBoolean(sectionNode, "warn_midnite")
store.noEject = readBoolean(sectionNode, "no_eject")
store.blankBehavior = Config._parseBlankBehavior(sectionNode)
store.refreshMediaDelay = readInteger(sectionNode, "refresh_media_delay")
store.ejectDelay = readInteger(sectionNode, "eject_delay")
return store
@staticmethod
def _parsePurge(parentNode):
"""
Parses a purge configuration section.
We read groups of the following items, one list element per
item::
purgeDirs //cb_config/purge/dir
The individual directory entries are parsed by :any:`_parsePurgeDirs`.
Args:
parentNode: Parent node to search beneath
Returns:
``PurgeConfig`` object or ``None`` if the section does not exist
Raises:
ValueError: If some filled-in value is invalid
"""
purge = None
sectionNode = readFirstChild(parentNode, "purge")
if sectionNode is not None:
purge = PurgeConfig()
purge.purgeDirs = Config._parsePurgeDirs(sectionNode)
return purge
@staticmethod
def _parseExtendedActions(parentNode):
"""
Reads extended actions data from immediately beneath the parent.
We read the following individual fields from each extended action::
name name
module module
function function
index index
dependencies depends
Dependency information is parsed by the ``_parseDependencies`` method.
Args:
parentNode: Parent node to search beneath
Returns:
List of extended actions
Raises:
ValueError: If the data at the location can't be read
"""
lst = []
for entry in readChildren(parentNode, "action"):
if isElement(entry):
action = ExtendedAction()
action.name = readString(entry, "name")
action.module = readString(entry, "module")
action.function = readString(entry, "function")
action.index = readInteger(entry, "index")
action.dependencies = Config._parseDependencies(entry)
lst.append(action)
if lst == []:
lst = None
return lst
@staticmethod
def _parseExclusions(parentNode):
"""
Reads exclusions data from immediately beneath the parent.
We read groups of the following items, one list element per item::
absolute exclude/abs_path
relative exclude/rel_path
patterns exclude/pattern
If there are none of some pattern (i.e. no relative path items) then
``None`` will be returned for that item in the tuple.
This method can be used to parse exclusions on both the collect
configuration level and on the collect directory level within collect
configuration.
Args:
parentNode: Parent node to search beneath
Returns:
Tuple of (absolute, relative, patterns) exclusions
"""
sectionNode = readFirstChild(parentNode, "exclude")
if sectionNode is None:
return (None, None, None)
else:
absolute = readStringList(sectionNode, "abs_path")
relative = readStringList(sectionNode, "rel_path")
patterns = readStringList(sectionNode, "pattern")
return (absolute, relative, patterns)
@staticmethod
def _parseOverrides(parentNode):
"""
Reads a list of ``CommandOverride`` objects from immediately beneath the parent.
We read the following individual fields::
command command
absolutePath abs_path
Args:
parentNode: Parent node to search beneath
Returns:
List of ``CommandOverride`` objects or ``None`` if none are found
Raises:
ValueError: If some filled-in value is invalid
"""
lst = []
for entry in readChildren(parentNode, "override"):
if isElement(entry):
override = CommandOverride()
override.command = readString(entry, "command")
override.absolutePath = readString(entry, "abs_path")
lst.append(override)
if lst == []:
lst = None
return lst
@staticmethod
def _parseHooks(parentNode):
"""
Reads a list of ``ActionHook`` objects from immediately beneath the parent.
We read the following individual fields::
action action
command command
Args:
parentNode: Parent node to search beneath
Returns:
List of ``ActionHook`` objects or ``None`` if none are found
Raises:
ValueError: If some filled-in value is invalid
"""
lst = []
for entry in readChildren(parentNode, "pre_action_hook"):
if isElement(entry):
hook = PreActionHook()
hook.action = readString(entry, "action")
hook.command = readString(entry, "command")
lst.append(hook)
for entry in readChildren(parentNode, "post_action_hook"):
if isElement(entry):
hook = PostActionHook()
hook.action = readString(entry, "action")
hook.command = readString(entry, "command")
lst.append(hook)
if lst == []:
lst = None
return lst
@staticmethod
def _parseCollectFiles(parentNode):
"""
Reads a list of ``CollectFile`` objects from immediately beneath the parent.
We read the following individual fields::
absolutePath abs_path
collectMode mode *or* collect_mode
archiveMode archive_mode
The collect mode is a special case. Just a ``mode`` tag is accepted, but
we prefer ``collect_mode`` for consistency with the rest of the config
file and to avoid confusion with the archive mode. If both are provided,
only ``mode`` will be used.
Args:
parentNode: Parent node to search beneath
Returns:
List of ``CollectFile`` objects or ``None`` if none are found
Raises:
ValueError: If some filled-in value is invalid
"""
lst = []
for entry in readChildren(parentNode, "file"):
if isElement(entry):
cfile = CollectFile()
cfile.absolutePath = readString(entry, "abs_path")
cfile.collectMode = readString(entry, "mode")
if cfile.collectMode is None:
cfile.collectMode = readString(entry, "collect_mode")
cfile.archiveMode = readString(entry, "archive_mode")
lst.append(cfile)
if lst == []:
lst = None
return lst
@staticmethod
def _parseCollectDirs(parentNode):
"""
Reads a list of ``CollectDir`` objects from immediately beneath the parent.
We read the following individual fields::
absolutePath abs_path
collectMode mode *or* collect_mode
archiveMode archive_mode
ignoreFile ignore_file
linkDepth link_depth
dereference dereference
recursionLevel recursion_level
The collect mode is a special case. Just a ``mode`` tag is accepted for
backwards compatibility, but we prefer ``collect_mode`` for consistency
with the rest of the config file and to avoid confusion with the archive
mode. If both are provided, only ``mode`` will be used.
We also read groups of the following items, one list element per
item::
absoluteExcludePaths exclude/abs_path
relativeExcludePaths exclude/rel_path
excludePatterns exclude/pattern
The exclusions are parsed by :any:`_parseExclusions`.
Args:
parentNode: Parent node to search beneath
Returns:
List of ``CollectDir`` objects or ``None`` if none are found
Raises:
ValueError: If some filled-in value is invalid
"""
lst = []
for entry in readChildren(parentNode, "dir"):
if isElement(entry):
cdir = CollectDir()
cdir.absolutePath = readString(entry, "abs_path")
cdir.collectMode = readString(entry, "mode")
if cdir.collectMode is None:
cdir.collectMode = readString(entry, "collect_mode")
cdir.archiveMode = readString(entry, "archive_mode")
cdir.ignoreFile = readString(entry, "ignore_file")
cdir.linkDepth = readInteger(entry, "link_depth")
cdir.dereference = readBoolean(entry, "dereference")
cdir.recursionLevel = readInteger(entry, "recursion_level")
(cdir.absoluteExcludePaths, cdir.relativeExcludePaths, cdir.excludePatterns) = Config._parseExclusions(entry)
lst.append(cdir)
if lst == []:
lst = None
return lst
@staticmethod
def _parsePurgeDirs(parentNode):
"""
Reads a list of ``PurgeDir`` objects from immediately beneath the parent.
We read the following individual fields::
absolutePath <baseExpr>/abs_path
retainDays <baseExpr>/retain_days
Args:
parentNode: Parent node to search beneath
Returns:
List of ``PurgeDir`` objects or ``None`` if none are found
Raises:
ValueError: If the data at the location can't be read
"""
lst = []
for entry in readChildren(parentNode, "dir"):
if isElement(entry):
cdir = PurgeDir()
cdir.absolutePath = readString(entry, "abs_path")
cdir.retainDays = readInteger(entry, "retain_days")
lst.append(cdir)
if lst == []:
lst = None
return lst
@staticmethod
def _parsePeerList(parentNode):
"""
Reads remote and local peer data from immediately beneath the parent.
We read the following individual fields for both remote
and local peers::
name name
collectDir collect_dir
We also read the following individual fields for remote peers
only::
remoteUser backup_user
rcpCommand rcp_command
rshCommand rsh_command
cbackCommand cback_command
managed managed
managedActions managed_actions
Additionally, the value in the ``type`` field is used to determine whether
this entry is a remote peer. If the type is ``"remote"``, it's a remote
peer, and if the type is ``"local"``, it's a remote peer.
If there are none of one type of peer (i.e. no local peers) then ``None``
will be returned for that item in the tuple.
Args:
parentNode: Parent node to search beneath
Returns:
Tuple of (local, remote) peer lists
Raises:
ValueError: If the data at the location can't be read
"""
localPeers = []
remotePeers = []
for entry in readChildren(parentNode, "peer"):
if isElement(entry):
peerType = readString(entry, "type")
if peerType == "local":
localPeer = LocalPeer()
localPeer.name = readString(entry, "name")
localPeer.collectDir = readString(entry, "collect_dir")
localPeer.ignoreFailureMode = readString(entry, "ignore_failures")
localPeers.append(localPeer)
elif peerType == "remote":
remotePeer = RemotePeer()
remotePeer.name = readString(entry, "name")
remotePeer.collectDir = readString(entry, "collect_dir")
remotePeer.remoteUser = readString(entry, "backup_user")
remotePeer.rcpCommand = readString(entry, "rcp_command")
remotePeer.rshCommand = readString(entry, "rsh_command")
remotePeer.cbackCommand = readString(entry, "cback_command")
remotePeer.ignoreFailureMode = readString(entry, "ignore_failures")
remotePeer.managed = readBoolean(entry, "managed")
managedActions = readString(entry, "managed_actions")
remotePeer.managedActions = parseCommaSeparatedString(managedActions)
remotePeers.append(remotePeer)
if localPeers == []:
localPeers = None
if remotePeers == []:
remotePeers = None
return (localPeers, remotePeers)
@staticmethod
def _parseDependencies(parentNode):
"""
Reads extended action dependency information from a parent node.
We read the following individual fields::
runBefore depends/run_before
runAfter depends/run_after
Each of these fields is a comma-separated list of action names.
The result is placed into an ``ActionDependencies`` object.
If the dependencies parent node does not exist, ``None`` will be returned.
Otherwise, an ``ActionDependencies`` object will always be created, even
if it does not contain any actual dependencies in it.
Args:
parentNode: Parent node to search beneath
Returns:
``ActionDependencies`` object or ``None``
Raises:
ValueError: If the data at the location can't be read
"""
sectionNode = readFirstChild(parentNode, "depends")
if sectionNode is None:
return None
else:
runBefore = readString(sectionNode, "run_before")
runAfter = readString(sectionNode, "run_after")
beforeList = parseCommaSeparatedString(runBefore)
afterList = parseCommaSeparatedString(runAfter)
return ActionDependencies(beforeList, afterList)
@staticmethod
def _parseBlankBehavior(parentNode):
"""
Reads a single ``BlankBehavior`` object from immediately beneath the parent.
We read the following individual fields::
blankMode blank_behavior/mode
blankFactor blank_behavior/factor
Args:
parentNode: Parent node to search beneath
Returns:
``BlankBehavior`` object or ``None`` if none if the section is not found
Raises:
ValueError: If some filled-in value is invalid
"""
blankBehavior = None
sectionNode = readFirstChild(parentNode, "blank_behavior")
if sectionNode is not None:
blankBehavior = BlankBehavior()
blankBehavior.blankMode = readString(sectionNode, "mode")
blankBehavior.blankFactor = readString(sectionNode, "factor")
return blankBehavior
########################################
# High-level methods for generating XML
########################################
def _extractXml(self):
"""
Internal method to extract configuration into an XML string.
This method assumes that the internal :any:`validate` method has been called
prior to extracting the XML, if the caller cares. No validation will be
done internally.
As a general rule, fields that are set to ``None`` will be extracted into
the document as empty tags. The same goes for container tags that are
filled based on lists - if the list is empty or ``None``, the container
tag will be empty.
"""
(xmlDom, parentNode) = createOutputDom()
Config._addReference(xmlDom, parentNode, self.reference)
Config._addExtensions(xmlDom, parentNode, self.extensions)
Config._addOptions(xmlDom, parentNode, self.options)
Config._addPeers(xmlDom, parentNode, self.peers)
Config._addCollect(xmlDom, parentNode, self.collect)
Config._addStage(xmlDom, parentNode, self.stage)
Config._addStore(xmlDom, parentNode, self.store)
Config._addPurge(xmlDom, parentNode, self.purge)
xmlData = serializeDom(xmlDom)
xmlDom.unlink()
return xmlData
@staticmethod
def _addReference(xmlDom, parentNode, referenceConfig):
"""
Adds a <reference> configuration section as the next child of a parent.
We add the following fields to the document::
author //cb_config/reference/author
revision //cb_config/reference/revision
description //cb_config/reference/description
generator //cb_config/reference/generator
If ``referenceConfig`` is ``None``, then no container will be added.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
referenceConfig: Reference configuration section to be added to the document
"""
if referenceConfig is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "reference")
addStringNode(xmlDom, sectionNode, "author", referenceConfig.author)
addStringNode(xmlDom, sectionNode, "revision", referenceConfig.revision)
addStringNode(xmlDom, sectionNode, "description", referenceConfig.description)
addStringNode(xmlDom, sectionNode, "generator", referenceConfig.generator)
@staticmethod
def _addExtensions(xmlDom, parentNode, extensionsConfig):
"""
Adds an <extensions> configuration section as the next child of a parent.
We add the following fields to the document::
order_mode //cb_config/extensions/order_mode
We also add groups of the following items, one list element per item::
actions //cb_config/extensions/action
The extended action entries are added by :any:`_addExtendedAction`.
If ``extensionsConfig`` is ``None``, then no container will be added.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
extensionsConfig: Extensions configuration section to be added to the document
"""
if extensionsConfig is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "extensions")
addStringNode(xmlDom, sectionNode, "order_mode", extensionsConfig.orderMode)
if extensionsConfig.actions is not None:
for action in extensionsConfig.actions:
Config._addExtendedAction(xmlDom, sectionNode, action)
@staticmethod
def _addOptions(xmlDom, parentNode, optionsConfig):
"""
Adds a <options> configuration section as the next child of a parent.
We add the following fields to the document::
startingDay //cb_config/options/starting_day
workingDir //cb_config/options/working_dir
backupUser //cb_config/options/backup_user
backupGroup //cb_config/options/backup_group
rcpCommand //cb_config/options/rcp_command
rshCommand //cb_config/options/rsh_command
cbackCommand //cb_config/options/cback_command
managedActions //cb_config/options/managed_actions
We also add groups of the following items, one list element per
item::
overrides //cb_config/options/override
hooks //cb_config/options/pre_action_hook
hooks //cb_config/options/post_action_hook
The individual override items are added by :any:`_addOverride`. The
individual hook items are added by :any:`_addHook`.
If ``optionsConfig`` is ``None``, then no container will be added.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
optionsConfig: Options configuration section to be added to the document
"""
if optionsConfig is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "options")
addStringNode(xmlDom, sectionNode, "starting_day", optionsConfig.startingDay)
addStringNode(xmlDom, sectionNode, "working_dir", optionsConfig.workingDir)
addStringNode(xmlDom, sectionNode, "backup_user", optionsConfig.backupUser)
addStringNode(xmlDom, sectionNode, "backup_group", optionsConfig.backupGroup)
addStringNode(xmlDom, sectionNode, "rcp_command", optionsConfig.rcpCommand)
addStringNode(xmlDom, sectionNode, "rsh_command", optionsConfig.rshCommand)
addStringNode(xmlDom, sectionNode, "cback_command", optionsConfig.cbackCommand)
managedActions = Config._buildCommaSeparatedString(optionsConfig.managedActions)
addStringNode(xmlDom, sectionNode, "managed_actions", managedActions)
if optionsConfig.overrides is not None:
for override in optionsConfig.overrides:
Config._addOverride(xmlDom, sectionNode, override)
if optionsConfig.hooks is not None:
for hook in optionsConfig.hooks:
Config._addHook(xmlDom, sectionNode, hook)
@staticmethod
def _addPeers(xmlDom, parentNode, peersConfig):
"""
Adds a <peers> configuration section as the next child of a parent.
We add groups of the following items, one list element per
item::
localPeers //cb_config/peers/peer
remotePeers //cb_config/peers/peer
The individual local and remote peer entries are added by
:any:`_addLocalPeer` and :any:`_addRemotePeer`, respectively.
If ``peersConfig`` is ``None``, then no container will be added.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
peersConfig: Peers configuration section to be added to the document
"""
if peersConfig is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "peers")
if peersConfig.localPeers is not None:
for localPeer in peersConfig.localPeers:
Config._addLocalPeer(xmlDom, sectionNode, localPeer)
if peersConfig.remotePeers is not None:
for remotePeer in peersConfig.remotePeers:
Config._addRemotePeer(xmlDom, sectionNode, remotePeer)
@staticmethod
def _addCollect(xmlDom, parentNode, collectConfig):
"""
Adds a <collect> configuration section as the next child of a parent.
We add the following fields to the document::
targetDir //cb_config/collect/collect_dir
collectMode //cb_config/collect/collect_mode
archiveMode //cb_config/collect/archive_mode
ignoreFile //cb_config/collect/ignore_file
We also add groups of the following items, one list element per
item::
absoluteExcludePaths //cb_config/collect/exclude/abs_path
excludePatterns //cb_config/collect/exclude/pattern
collectFiles //cb_config/collect/file
collectDirs //cb_config/collect/dir
The individual collect files are added by :any:`_addCollectFile` and
individual collect directories are added by :any:`_addCollectDir`.
If ``collectConfig`` is ``None``, then no container will be added.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
collectConfig: Collect configuration section to be added to the document
"""
if collectConfig is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "collect")
addStringNode(xmlDom, sectionNode, "collect_dir", collectConfig.targetDir)
addStringNode(xmlDom, sectionNode, "collect_mode", collectConfig.collectMode)
addStringNode(xmlDom, sectionNode, "archive_mode", collectConfig.archiveMode)
addStringNode(xmlDom, sectionNode, "ignore_file", collectConfig.ignoreFile)
if ((collectConfig.absoluteExcludePaths is not None and collectConfig.absoluteExcludePaths != []) or
(collectConfig.excludePatterns is not None and collectConfig.excludePatterns != [])):
excludeNode = addContainerNode(xmlDom, sectionNode, "exclude")
if collectConfig.absoluteExcludePaths is not None:
for absolutePath in collectConfig.absoluteExcludePaths:
addStringNode(xmlDom, excludeNode, "abs_path", absolutePath)
if collectConfig.excludePatterns is not None:
for pattern in collectConfig.excludePatterns:
addStringNode(xmlDom, excludeNode, "pattern", pattern)
if collectConfig.collectFiles is not None:
for collectFile in collectConfig.collectFiles:
Config._addCollectFile(xmlDom, sectionNode, collectFile)
if collectConfig.collectDirs is not None:
for collectDir in collectConfig.collectDirs:
Config._addCollectDir(xmlDom, sectionNode, collectDir)
@staticmethod
def _addStage(xmlDom, parentNode, stageConfig):
"""
Adds a <stage> configuration section as the next child of a parent.
We add the following fields to the document::
targetDir //cb_config/stage/staging_dir
We also add groups of the following items, one list element per
item::
localPeers //cb_config/stage/peer
remotePeers //cb_config/stage/peer
The individual local and remote peer entries are added by
:any:`_addLocalPeer` and :any:`_addRemotePeer`, respectively.
If ``stageConfig`` is ``None``, then no container will be added.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
stageConfig: Stage configuration section to be added to the document
"""
if stageConfig is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "stage")
addStringNode(xmlDom, sectionNode, "staging_dir", stageConfig.targetDir)
if stageConfig.localPeers is not None:
for localPeer in stageConfig.localPeers:
Config._addLocalPeer(xmlDom, sectionNode, localPeer)
if stageConfig.remotePeers is not None:
for remotePeer in stageConfig.remotePeers:
Config._addRemotePeer(xmlDom, sectionNode, remotePeer)
@staticmethod
def _addStore(xmlDom, parentNode, storeConfig):
"""
Adds a <store> configuration section as the next child of a parent.
We add the following fields to the document::
sourceDir //cb_config/store/source_dir
mediaType //cb_config/store/media_type
deviceType //cb_config/store/device_type
devicePath //cb_config/store/target_device
deviceScsiId //cb_config/store/target_scsi_id
driveSpeed //cb_config/store/drive_speed
checkData //cb_config/store/check_data
checkMedia //cb_config/store/check_media
warnMidnite //cb_config/store/warn_midnite
noEject //cb_config/store/no_eject
refreshMediaDelay //cb_config/store/refresh_media_delay
ejectDelay //cb_config/store/eject_delay
Blanking behavior configuration is added by the :any:`_addBlankBehavior`
method.
If ``storeConfig`` is ``None``, then no container will be added.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
storeConfig: Store configuration section to be added to the document
"""
if storeConfig is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "store")
addStringNode(xmlDom, sectionNode, "source_dir", storeConfig.sourceDir)
addStringNode(xmlDom, sectionNode, "media_type", storeConfig.mediaType)
addStringNode(xmlDom, sectionNode, "device_type", storeConfig.deviceType)
addStringNode(xmlDom, sectionNode, "target_device", storeConfig.devicePath)
addStringNode(xmlDom, sectionNode, "target_scsi_id", storeConfig.deviceScsiId)
addIntegerNode(xmlDom, sectionNode, "drive_speed", storeConfig.driveSpeed)
addBooleanNode(xmlDom, sectionNode, "check_data", storeConfig.checkData)
addBooleanNode(xmlDom, sectionNode, "check_media", storeConfig.checkMedia)
addBooleanNode(xmlDom, sectionNode, "warn_midnite", storeConfig.warnMidnite)
addBooleanNode(xmlDom, sectionNode, "no_eject", storeConfig.noEject)
addIntegerNode(xmlDom, sectionNode, "refresh_media_delay", storeConfig.refreshMediaDelay)
addIntegerNode(xmlDom, sectionNode, "eject_delay", storeConfig.ejectDelay)
Config._addBlankBehavior(xmlDom, sectionNode, storeConfig.blankBehavior)
@staticmethod
def _addPurge(xmlDom, parentNode, purgeConfig):
"""
Adds a <purge> configuration section as the next child of a parent.
We add the following fields to the document::
purgeDirs //cb_config/purge/dir
The individual directory entries are added by :any:`_addPurgeDir`.
If ``purgeConfig`` is ``None``, then no container will be added.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
purgeConfig: Purge configuration section to be added to the document
"""
if purgeConfig is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "purge")
if purgeConfig.purgeDirs is not None:
for purgeDir in purgeConfig.purgeDirs:
Config._addPurgeDir(xmlDom, sectionNode, purgeDir)
@staticmethod
def _addExtendedAction(xmlDom, parentNode, action):
"""
Adds an extended action container as the next child of a parent.
We add the following fields to the document::
name action/name
module action/module
function action/function
index action/index
dependencies action/depends
Dependencies are added by the :any:`_addDependencies` method.
The <action> node itself is created as the next child of the parent node.
This method only adds one action node. The parent must loop for each action
in the ``ExtensionsConfig`` object.
If ``action`` is ``None``, this method call will be a no-op.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
action: Purge directory to be added to the document
"""
if action is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "action")
addStringNode(xmlDom, sectionNode, "name", action.name)
addStringNode(xmlDom, sectionNode, "module", action.module)
addStringNode(xmlDom, sectionNode, "function", action.function)
addIntegerNode(xmlDom, sectionNode, "index", action.index)
Config._addDependencies(xmlDom, sectionNode, action.dependencies)
@staticmethod
def _addOverride(xmlDom, parentNode, override):
"""
Adds a command override container as the next child of a parent.
We add the following fields to the document::
command override/command
absolutePath override/abs_path
The <override> node itself is created as the next child of the parent
node. This method only adds one override node. The parent must loop for
each override in the ``OptionsConfig`` object.
If ``override`` is ``None``, this method call will be a no-op.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
override: Command override to be added to the document
"""
if override is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "override")
addStringNode(xmlDom, sectionNode, "command", override.command)
addStringNode(xmlDom, sectionNode, "abs_path", override.absolutePath)
@staticmethod
def _addHook(xmlDom, parentNode, hook):
"""
Adds an action hook container as the next child of a parent.
The behavior varies depending on the value of the ``before`` and ``after``
flags on the hook. If the ``before`` flag is set, it's a pre-action hook,
and we'll add the following fields::
action pre_action_hook/action
command pre_action_hook/command
If the ``after`` flag is set, it's a post-action hook, and we'll add the
following fields::
action post_action_hook/action
command post_action_hook/command
The <pre_action_hook> or <post_action_hook> node itself is created as the
next child of the parent node. This method only adds one hook node. The
parent must loop for each hook in the ``OptionsConfig`` object.
If ``hook`` is ``None``, this method call will be a no-op.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
hook: Command hook to be added to the document
"""
if hook is not None:
if hook.before:
sectionNode = addContainerNode(xmlDom, parentNode, "pre_action_hook")
else:
sectionNode = addContainerNode(xmlDom, parentNode, "post_action_hook")
addStringNode(xmlDom, sectionNode, "action", hook.action)
addStringNode(xmlDom, sectionNode, "command", hook.command)
@staticmethod
def _addCollectFile(xmlDom, parentNode, collectFile):
"""
Adds a collect file container as the next child of a parent.
We add the following fields to the document::
absolutePath dir/abs_path
collectMode dir/collect_mode
archiveMode dir/archive_mode
Note that for consistency with collect directory handling we'll only emit
the preferred ``collect_mode`` tag.
The <file> node itself is created as the next child of the parent node.
This method only adds one collect file node. The parent must loop
for each collect file in the ``CollectConfig`` object.
If ``collectFile`` is ``None``, this method call will be a no-op.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
collectFile: Collect file to be added to the document
"""
if collectFile is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "file")
addStringNode(xmlDom, sectionNode, "abs_path", collectFile.absolutePath)
addStringNode(xmlDom, sectionNode, "collect_mode", collectFile.collectMode)
addStringNode(xmlDom, sectionNode, "archive_mode", collectFile.archiveMode)
@staticmethod
def _addCollectDir(xmlDom, parentNode, collectDir):
"""
Adds a collect directory container as the next child of a parent.
We add the following fields to the document::
absolutePath dir/abs_path
collectMode dir/collect_mode
archiveMode dir/archive_mode
ignoreFile dir/ignore_file
linkDepth dir/link_depth
dereference dir/dereference
recursionLevel dir/recursion_level
Note that an original XML document might have listed the collect mode
using the ``mode`` tag, since we accept both ``collect_mode`` and ``mode``.
However, here we'll only emit the preferred ``collect_mode`` tag.
We also add groups of the following items, one list element per item::
absoluteExcludePaths dir/exclude/abs_path
relativeExcludePaths dir/exclude/rel_path
excludePatterns dir/exclude/pattern
The <dir> node itself is created as the next child of the parent node.
This method only adds one collect directory node. The parent must loop
for each collect directory in the ``CollectConfig`` object.
If ``collectDir`` is ``None``, this method call will be a no-op.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
collectDir: Collect directory to be added to the document
"""
if collectDir is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "dir")
addStringNode(xmlDom, sectionNode, "abs_path", collectDir.absolutePath)
addStringNode(xmlDom, sectionNode, "collect_mode", collectDir.collectMode)
addStringNode(xmlDom, sectionNode, "archive_mode", collectDir.archiveMode)
addStringNode(xmlDom, sectionNode, "ignore_file", collectDir.ignoreFile)
addIntegerNode(xmlDom, sectionNode, "link_depth", collectDir.linkDepth)
addBooleanNode(xmlDom, sectionNode, "dereference", collectDir.dereference)
addIntegerNode(xmlDom, sectionNode, "recursion_level", collectDir.recursionLevel)
if ((collectDir.absoluteExcludePaths is not None and collectDir.absoluteExcludePaths != []) or
(collectDir.relativeExcludePaths is not None and collectDir.relativeExcludePaths != []) or
(collectDir.excludePatterns is not None and collectDir.excludePatterns != [])):
excludeNode = addContainerNode(xmlDom, sectionNode, "exclude")
if collectDir.absoluteExcludePaths is not None:
for absolutePath in collectDir.absoluteExcludePaths:
addStringNode(xmlDom, excludeNode, "abs_path", absolutePath)
if collectDir.relativeExcludePaths is not None:
for relativePath in collectDir.relativeExcludePaths:
addStringNode(xmlDom, excludeNode, "rel_path", relativePath)
if collectDir.excludePatterns is not None:
for pattern in collectDir.excludePatterns:
addStringNode(xmlDom, excludeNode, "pattern", pattern)
@staticmethod
def _addLocalPeer(xmlDom, parentNode, localPeer):
"""
Adds a local peer container as the next child of a parent.
We add the following fields to the document::
name peer/name
collectDir peer/collect_dir
ignoreFailureMode peer/ignore_failures
Additionally, ``peer/type`` is filled in with ``"local"``, since this is a
local peer.
The <peer> node itself is created as the next child of the parent node.
This method only adds one peer node. The parent must loop for each peer
in the ``StageConfig`` object.
If ``localPeer`` is ``None``, this method call will be a no-op.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
localPeer: Purge directory to be added to the document
"""
if localPeer is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "peer")
addStringNode(xmlDom, sectionNode, "name", localPeer.name)
addStringNode(xmlDom, sectionNode, "type", "local")
addStringNode(xmlDom, sectionNode, "collect_dir", localPeer.collectDir)
addStringNode(xmlDom, sectionNode, "ignore_failures", localPeer.ignoreFailureMode)
@staticmethod
def _addRemotePeer(xmlDom, parentNode, remotePeer):
"""
Adds a remote peer container as the next child of a parent.
We add the following fields to the document::
name peer/name
collectDir peer/collect_dir
remoteUser peer/backup_user
rcpCommand peer/rcp_command
rcpCommand peer/rcp_command
rshCommand peer/rsh_command
cbackCommand peer/cback_command
ignoreFailureMode peer/ignore_failures
managed peer/managed
managedActions peer/managed_actions
Additionally, ``peer/type`` is filled in with ``"remote"``, since this is a
remote peer.
The <peer> node itself is created as the next child of the parent node.
This method only adds one peer node. The parent must loop for each peer
in the ``StageConfig`` object.
If ``remotePeer`` is ``None``, this method call will be a no-op.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
remotePeer: Purge directory to be added to the document
"""
if remotePeer is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "peer")
addStringNode(xmlDom, sectionNode, "name", remotePeer.name)
addStringNode(xmlDom, sectionNode, "type", "remote")
addStringNode(xmlDom, sectionNode, "collect_dir", remotePeer.collectDir)
addStringNode(xmlDom, sectionNode, "backup_user", remotePeer.remoteUser)
addStringNode(xmlDom, sectionNode, "rcp_command", remotePeer.rcpCommand)
addStringNode(xmlDom, sectionNode, "rsh_command", remotePeer.rshCommand)
addStringNode(xmlDom, sectionNode, "cback_command", remotePeer.cbackCommand)
addStringNode(xmlDom, sectionNode, "ignore_failures", remotePeer.ignoreFailureMode)
addBooleanNode(xmlDom, sectionNode, "managed", remotePeer.managed)
managedActions = Config._buildCommaSeparatedString(remotePeer.managedActions)
addStringNode(xmlDom, sectionNode, "managed_actions", managedActions)
@staticmethod
def _addPurgeDir(xmlDom, parentNode, purgeDir):
"""
Adds a purge directory container as the next child of a parent.
We add the following fields to the document::
absolutePath dir/abs_path
retainDays dir/retain_days
The <dir> node itself is created as the next child of the parent node.
This method only adds one purge directory node. The parent must loop for
each purge directory in the ``PurgeConfig`` object.
If ``purgeDir`` is ``None``, this method call will be a no-op.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
purgeDir: Purge directory to be added to the document
"""
if purgeDir is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "dir")
addStringNode(xmlDom, sectionNode, "abs_path", purgeDir.absolutePath)
addIntegerNode(xmlDom, sectionNode, "retain_days", purgeDir.retainDays)
@staticmethod
def _addDependencies(xmlDom, parentNode, dependencies):
"""
Adds a extended action dependencies to parent node.
We add the following fields to the document::
runBefore depends/run_before
runAfter depends/run_after
If ``dependencies`` is ``None``, this method call will be a no-op.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
dependencies: ``ActionDependencies`` object to be added to the document
"""
if dependencies is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "depends")
runBefore = Config._buildCommaSeparatedString(dependencies.beforeList)
runAfter = Config._buildCommaSeparatedString(dependencies.afterList)
addStringNode(xmlDom, sectionNode, "run_before", runBefore)
addStringNode(xmlDom, sectionNode, "run_after", runAfter)
@staticmethod
def _buildCommaSeparatedString(valueList):
"""
Creates a comma-separated string from a list of values.
As a special case, if ``valueList`` is ``None``, then ``None`` will be
returned.
Args:
valueList: List of values to be placed into a string
Returns:
Values from valueList as a comma-separated string
"""
if valueList is None:
return None
return ",".join(valueList)
@staticmethod
def _addBlankBehavior(xmlDom, parentNode, blankBehavior):
"""
Adds a blanking behavior container as the next child of a parent.
We add the following fields to the document::
blankMode blank_behavior/mode
blankFactor blank_behavior/factor
The <blank_behavior> node itself is created as the next child of the
parent node.
If ``blankBehavior`` is ``None``, this method call will be a no-op.
Args:
xmlDom: DOM tree as from :any:`createOutputDom`
parentNode: Parent that the section should be appended to
blankBehavior: Blanking behavior to be added to the document
"""
if blankBehavior is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "blank_behavior")
addStringNode(xmlDom, sectionNode, "mode", blankBehavior.blankMode)
addStringNode(xmlDom, sectionNode, "factor", blankBehavior.blankFactor)
#################################################
# High-level methods used for validating content
#################################################
def _validateContents(self):
"""
Validates configuration contents per rules discussed in module
documentation.
This is the second pass at validation. It ensures that any filled-in
section contains valid data. Any sections which is not set to ``None`` is
validated per the rules for that section, laid out in the module
documentation (above).
Raises:
ValueError: If configuration is invalid
"""
self._validateReference()
self._validateExtensions()
self._validateOptions()
self._validatePeers()
self._validateCollect()
self._validateStage()
self._validateStore()
self._validatePurge()
def _validateReference(self):
"""
Validates reference configuration.
There are currently no reference-related validations.
Raises:
ValueError: If reference configuration is invalid
"""
pass
def _validateExtensions(self):
"""
Validates extensions configuration.
The list of actions may be either ``None`` or an empty list ``[]`` if
desired. Each extended action must include a name, a module, and a
function.
Then, if the order mode is None or "index", an index is required; and if
the order mode is "dependency", dependency information is required.
Raises:
ValueError: If reference configuration is invalid
"""
if self.extensions is not None:
if self.extensions.actions is not None:
names = []
for action in self.extensions.actions:
if action.name is None:
raise ValueError("Each extended action must set a name.")
names.append(action.name)
if action.module is None:
raise ValueError("Each extended action must set a module.")
if action.function is None:
raise ValueError("Each extended action must set a function.")
if self.extensions.orderMode is None or self.extensions.orderMode == "index":
if action.index is None:
raise ValueError("Each extended action must set an index, based on order mode.")
elif self.extensions.orderMode == "dependency":
if action.dependencies is None:
raise ValueError("Each extended action must set dependency information, based on order mode.")
checkUnique("Duplicate extension names exist:", names)
def _validateOptions(self):
"""
Validates options configuration.
All fields must be filled in except the rsh command. The rcp and rsh
commands are used as default values for all remote peers. Remote peers
can also rely on the backup user as the default remote user name if they
choose.
Raises:
ValueError: If reference configuration is invalid
"""
if self.options is not None:
if self.options.startingDay is None:
raise ValueError("Options section starting day must be filled in.")
if self.options.workingDir is None:
raise ValueError("Options section working directory must be filled in.")
if self.options.backupUser is None:
raise ValueError("Options section backup user must be filled in.")
if self.options.backupGroup is None:
raise ValueError("Options section backup group must be filled in.")
if self.options.rcpCommand is None:
raise ValueError("Options section remote copy command must be filled in.")
def _validatePeers(self):
"""
Validates peers configuration per rules in :any:`_validatePeerList`.
Raises:
ValueError: If peers configuration is invalid
"""
if self.peers is not None:
self._validatePeerList(self.peers.localPeers, self.peers.remotePeers)
def _validateCollect(self):
"""
Validates collect configuration.
The target directory must be filled in. The collect mode, archive mode,
ignore file, and recursion level are all optional. The list of absolute
paths to exclude and patterns to exclude may be either ``None`` or an
empty list ``[]`` if desired.
Each collect directory entry must contain an absolute path to collect,
and then must either be able to take collect mode, archive mode and
ignore file configuration from the parent ``CollectConfig`` object, or
must set each value on its own. The list of absolute paths to exclude,
relative paths to exclude and patterns to exclude may be either ``None``
or an empty list ``[]`` if desired. Any list of absolute paths to exclude
or patterns to exclude will be combined with the same list in the
``CollectConfig`` object to make the complete list for a given directory.
Raises:
ValueError: If collect configuration is invalid
"""
if self.collect is not None:
if self.collect.targetDir is None:
raise ValueError("Collect section target directory must be filled in.")
if self.collect.collectFiles is not None:
for collectFile in self.collect.collectFiles:
if collectFile.absolutePath is None:
raise ValueError("Each collect file must set an absolute path.")
if self.collect.collectMode is None and collectFile.collectMode is None:
raise ValueError("Collect mode must either be set in parent collect section or individual collect file.")
if self.collect.archiveMode is None and collectFile.archiveMode is None:
raise ValueError("Archive mode must either be set in parent collect section or individual collect file.")
if self.collect.collectDirs is not None:
for collectDir in self.collect.collectDirs:
if collectDir.absolutePath is None:
raise ValueError("Each collect directory must set an absolute path.")
if self.collect.collectMode is None and collectDir.collectMode is None:
raise ValueError("Collect mode must either be set in parent collect section or individual collect directory.")
if self.collect.archiveMode is None and collectDir.archiveMode is None:
raise ValueError("Archive mode must either be set in parent collect section or individual collect directory.")
if self.collect.ignoreFile is None and collectDir.ignoreFile is None:
raise ValueError("Ignore file must either be set in parent collect section or individual collect directory.")
if (collectDir.linkDepth is None or collectDir.linkDepth < 1) and collectDir.dereference:
raise ValueError("Dereference flag is only valid when a non-zero link depth is in use.")
def _validateStage(self):
"""
Validates stage configuration.
The target directory must be filled in, and the peers are
also validated.
Peers are only required in this section if the peers configuration
section is not filled in. However, if any peers are filled in
here, they override the peers configuration and must meet the
validation criteria in :any:`_validatePeerList`.
Raises:
ValueError: If stage configuration is invalid
"""
if self.stage is not None:
if self.stage.targetDir is None:
raise ValueError("Stage section target directory must be filled in.")
if self.peers is None:
# In this case, stage configuration is our only configuration and must be valid.
self._validatePeerList(self.stage.localPeers, self.stage.remotePeers)
else:
# In this case, peers configuration is the default and stage configuration overrides.
# Validation is only needed if it's stage configuration is actually filled in.
if self.stage.hasPeers():
self._validatePeerList(self.stage.localPeers, self.stage.remotePeers)
def _validateStore(self):
"""
Validates store configuration.
The device type, drive speed, and blanking behavior are optional. All
other values are required. Missing booleans will be set to defaults.
If blanking behavior is provided, then both a blanking mode and a
blanking factor are required.
The image writer functionality in the ``writer`` module is supposed to be
able to handle a device speed of ``None``.
Any caller which needs a "real" (non-``None``) value for the device type
can use ``DEFAULT_DEVICE_TYPE``, which is guaranteed to be sensible.
This is also where we make sure that the media type -- which is already a
valid type -- matches up properly with the device type.
Raises:
ValueError: If store configuration is invalid
"""
if self.store is not None:
if self.store.sourceDir is None:
raise ValueError("Store section source directory must be filled in.")
if self.store.mediaType is None:
raise ValueError("Store section media type must be filled in.")
if self.store.devicePath is None:
raise ValueError("Store section device path must be filled in.")
if self.store.deviceType is None or self.store.deviceType == "cdwriter":
if self.store.mediaType not in VALID_CD_MEDIA_TYPES:
raise ValueError("Media type must match device type.")
elif self.store.deviceType == "dvdwriter":
if self.store.mediaType not in VALID_DVD_MEDIA_TYPES:
raise ValueError("Media type must match device type.")
if self.store.blankBehavior is not None:
if self.store.blankBehavior.blankMode is None and self.store.blankBehavior.blankFactor is None:
raise ValueError("If blanking behavior is provided, all values must be filled in.")
def _validatePurge(self):
"""
Validates purge configuration.
The list of purge directories may be either ``None`` or an empty list
``[]`` if desired. All purge directories must contain a path and a retain
days value.
Raises:
ValueError: If purge configuration is invalid
"""
if self.purge is not None:
if self.purge.purgeDirs is not None:
for purgeDir in self.purge.purgeDirs:
if purgeDir.absolutePath is None:
raise ValueError("Each purge directory must set an absolute path.")
if purgeDir.retainDays is None:
raise ValueError("Each purge directory must set a retain days value.")
def _validatePeerList(self, localPeers, remotePeers):
"""
Validates the set of local and remote peers.
Local peers must be completely filled in, including both name and collect
directory. Remote peers must also fill in the name and collect
directory, but can leave the remote user and rcp command unset. In this
case, the remote user is assumed to match the backup user from the
options section and rcp command is taken directly from the options
section.
Args:
localPeers: List of local peers
remotePeers: List of remote peers
Raises:
ValueError: If stage configuration is invalid
"""
if localPeers is None and remotePeers is None:
raise ValueError("Peer list must contain at least one backup peer.")
if localPeers is None and remotePeers is not None:
if len(remotePeers) < 1:
raise ValueError("Peer list must contain at least one backup peer.")
elif localPeers is not None and remotePeers is None:
if len(localPeers) < 1:
raise ValueError("Peer list must contain at least one backup peer.")
elif localPeers is not None and remotePeers is not None:
if len(localPeers) + len(remotePeers) < 1:
raise ValueError("Peer list must contain at least one backup peer.")
names = []
if localPeers is not None:
for localPeer in localPeers:
if localPeer.name is None:
raise ValueError("Local peers must set a name.")
names.append(localPeer.name)
if localPeer.collectDir is None:
raise ValueError("Local peers must set a collect directory.")
if remotePeers is not None:
for remotePeer in remotePeers:
if remotePeer.name is None:
raise ValueError("Remote peers must set a name.")
names.append(remotePeer.name)
if remotePeer.collectDir is None:
raise ValueError("Remote peers must set a collect directory.")
if (self.options is None or self.options.backupUser is None) and remotePeer.remoteUser is None:
raise ValueError("Remote user must either be set in options section or individual remote peer.")
if (self.options is None or self.options.rcpCommand is None) and remotePeer.rcpCommand is None:
raise ValueError("Remote copy command must either be set in options section or individual remote peer.")
if remotePeer.managed:
if (self.options is None or self.options.rshCommand is None) and remotePeer.rshCommand is None:
raise ValueError("Remote shell command must either be set in options section or individual remote peer.")
if (self.options is None or self.options.cbackCommand is None) and remotePeer.cbackCommand is None:
raise ValueError("Remote cback command must either be set in options section or individual remote peer.")
if ((self.options is None or self.options.managedActions is None or len(self.options.managedActions) < 1)
and (remotePeer.managedActions is None or len(remotePeer.managedActions) < 1)):
raise ValueError("Managed actions list must be set in options section or individual remote peer.")
checkUnique("Duplicate peer names exist:", names)
########################################################################
# General utility functions
########################################################################
[docs]def readByteQuantity(parent, name):
"""
Read a byte size value from an XML document.
A byte size value is an interpreted string value. If the string value
ends with "MB" or "GB", then the string before that is interpreted as
megabytes or gigabytes. Otherwise, it is intepreted as bytes.
Args:
parent: Parent node to search beneath
name: Name of node to search for
Returns:
ByteQuantity parsed from XML document
"""
data = readString(parent, name)
if data is None:
return None
data = data.strip()
if data.endswith("KB"):
quantity = data[0:data.rfind("KB")].strip()
units = UNIT_KBYTES
elif data.endswith("MB"):
quantity = data[0:data.rfind("MB")].strip()
units = UNIT_MBYTES
elif data.endswith("GB"):
quantity = data[0:data.rfind("GB")].strip()
units = UNIT_GBYTES
else:
quantity = data.strip()
units = UNIT_BYTES
return ByteQuantity(quantity, units)
[docs]def addByteQuantityNode(xmlDom, parentNode, nodeName, byteQuantity):
"""
Adds a text node as the next child of a parent, to contain a byte size.
If the ``byteQuantity`` is None, then the node will be created, but will
be empty (i.e. will contain no text node child).
The size in bytes will be normalized. If it is larger than 1.0 GB, it will
be shown in GB ("1.0 GB"). If it is larger than 1.0 MB ("1.0 MB"), it will
be shown in MB. Otherwise, it will be shown in bytes ("423413").
Args:
xmlDom: DOM tree as from ``impl.createDocument()``
parentNode: Parent node to create child for
nodeName: Name of the new container node
byteQuantity: ByteQuantity object to put into the XML document
Returns:
Reference to the newly-created node
"""
if byteQuantity is None:
byteString = None
elif byteQuantity.units == UNIT_KBYTES:
byteString = "%s KB" % byteQuantity.quantity
elif byteQuantity.units == UNIT_MBYTES:
byteString = "%s MB" % byteQuantity.quantity
elif byteQuantity.units == UNIT_GBYTES:
byteString = "%s GB" % byteQuantity.quantity
else:
byteString = byteQuantity.quantity
return addStringNode(xmlDom, parentNode, nodeName, byteString)