Thread: [cedar-backup-svn] SF.net SVN: cedar-backup: [805] cedar-backup2/trunk
Brought to you by:
pronovic
|
From: <pro...@us...> - 2007-12-15 20:52:15
|
Revision: 805
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=805&view=rev
Author: pronovic
Date: 2007-12-15 12:52:14 -0800 (Sat, 15 Dec 2007)
Log Message:
-----------
First pass at implementing managed peers
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/actions/stage.py
cedar-backup2/trunk/CedarBackup2/config.py
cedar-backup2/trunk/Changelog
cedar-backup2/trunk/test/configtests.py
Added Paths:
-----------
cedar-backup2/trunk/test/data/cback.conf.21
cedar-backup2/trunk/test/data/cback.conf.22
cedar-backup2/trunk/test/data/cback.conf.23
Modified: cedar-backup2/trunk/CedarBackup2/actions/stage.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/actions/stage.py 2007-12-15 19:20:09 UTC (rev 804)
+++ cedar-backup2/trunk/CedarBackup2/actions/stage.py 2007-12-15 20:52:14 UTC (rev 805)
@@ -212,8 +212,15 @@
@return: List of L{LocalPeer} objects.
"""
localPeers = []
- if config.stage.localPeers is not None:
- for peer in config.stage.localPeers:
+ configPeers = None
+ if config.stage.hasPeers():
+ logger.debug("Using list of local peers from stage configuration.")
+ configPeers = config.stage.localPeers
+ elif config.peers is not None and config.peers.hasPeers():
+ logger.debug("Using list of local peers from peers configuration.")
+ configPeers = config.peers.localPeers
+ if configPeers is not None:
+ for peer in configPeers:
localPeer = LocalPeer(peer.name, peer.collectDir)
localPeers.append(localPeer)
logger.debug("Found local peer: [%s]" % localPeer.name)
@@ -231,8 +238,15 @@
@return: List of L{RemotePeer} objects.
"""
remotePeers = []
- if config.stage.remotePeers is not None:
- for peer in config.stage.remotePeers:
+ configPeers = None
+ if config.stage.hasPeers():
+ logger.debug("Using list of remote peers from stage configuration.")
+ configPeers = config.stage.remotePeers
+ elif config.peers is not None and config.peers.hasPeers():
+ logger.debug("Using list of remote peers from peers configuration.")
+ configPeers = config.peers.remotePeers
+ if configPeers is not None:
+ for peer in config.peers.remotePeers:
remoteUser = _getRemoteUser(config, peer)
rcpCommand = _getRcpCommand(config, peer)
remotePeer = RemotePeer(peer.name, peer.collectDir, config.options.workingDir,
Modified: cedar-backup2/trunk/CedarBackup2/config.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/config.py 2007-12-15 19:20:09 UTC (rev 804)
+++ cedar-backup2/trunk/CedarBackup2/config.py 2007-12-15 20:52:14 UTC (rev 805)
@@ -65,8 +65,8 @@
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 may contain additional fields that
- are not accepted by older versions of the software.
+ direction, as the 2.x configuration files contains additional configuration
+ is not accepted by older versions of the software.
XML Configuration Structure
===========================
@@ -81,6 +81,7 @@
- I{reference}: specifies reference information about the file (author, revision, etc)
- I{extensions}: specifies mappings to Cedar Backup extensions (external code)
- I{options}: specifies global configuration options
+ - I{peers}: specifies the set of peers in a master's backup pool
- I{collect}: specifies configuration related to the collect action
- I{stage}: specifies configuration related to the stage action
- I{store}: specifies configuration related to the store action
@@ -162,6 +163,14 @@
for all remote peers in the staging section. Remote peers can also rely on
the backup user as the default remote user name if they choose.
+ I{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.
+
I{Collect Validations}
The target directory must be filled in. The collect mode, archive mode and
@@ -183,11 +192,9 @@
(remote or local) between the two lists of peers. A list with no entries
can be either C{None} or an empty list C{[]} if desired.
- 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.
+ If a set of peers is provided, this configuration completely overrides
+ configuration in the peers configuration section, and the same validations
+ apply.
I{Store Validations}
@@ -207,7 +214,7 @@
@sort: ActionDependencies, ActionHook, PreActionHook, PostActionHook,
ExtendedAction, CommandOverride, CollectFile, CollectDir, PurgeDir, LocalPeer,
- RemotePeer, ReferenceConfig, ExtensionsConfig, OptionsConfig,
+ RemotePeer, ReferenceConfig, ExtensionsConfig, OptionsConfig, PeersConfig,
CollectConfig, StageConfig, StoreConfig, PurgeConfig, Config,
DEFAULT_DEVICE_TYPE, DEFAULT_MEDIA_TYPE,
VALID_DEVICE_TYPES, VALID_MEDIA_TYPES,
@@ -2372,6 +2379,141 @@
########################################################################
+# PeersConfig class definition
+########################################################################
+
+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.
+
+ As with all of the other classes that represent configuration sections, all
+ of these values are optional. It is up to some higher-level construct to
+ decide whether everything they need is filled in.
+
+ The following restrictions exist on data in this class:
+
+ - The list of local peers must contain only C{LocalPeer} objects
+ - The list of remote peers must contain only C{RemotePeer} objects
+
+ @note: Lists within this class are "unordered" for equality comparisons.
+
+ @sort: __init__, __repr__, __str__, __cmp__, localPeers, remotePeers
+ """
+
+ def __init__(self, localPeers=None, remotePeers=None):
+ """
+ Constructor for the C{PeersConfig} class.
+
+ @param localPeers: List of local peers.
+ @param remotePeers: List of remote peers.
+
+ @raise 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 __cmp__(self, other):
+ """
+ Definition of equals operator for this class.
+ Lists within this class are "unordered" for equality comparisons.
+ @param other: Other object to compare to.
+ @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} 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
+
+ def hasPeers(self):
+ """
+ Indicates whether any peers are filled into this object.
+ @return: 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 C{None} or each element must be a C{LocalPeer}.
+ @raise 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, 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 C{None} or each element must be a C{RemotePeer}.
+ @raise ValueError: If the value is not a C{RemotePeer}
+ """
+ if value is None:
+ self._remotePeers = None
+ else:
+ try:
+ saved = self._remotePeers
+ self._remotePeers = ObjectTypeList(RemotePeer, "RemotePeer")
+ self._remotePeers.extend(value)
+ except Exception, 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
########################################################################
@@ -2763,6 +2905,14 @@
return 1
return 0
+ def hasPeers(self):
+ """
+ Indicates whether any peers are filled into this object.
+ @return: 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.
@@ -3324,8 +3474,9 @@
@sort: __init__, __repr__, __str__, __cmp__, extractXml, validate,
reference, extensions, options, collect, stage, store, purge,
_getReference, _setReference, _getExtensions, _setExtensions,
- _getOptions, _setOptions, _getCollect, _setCollect, _getStage,
- _setStage, _getStore, _setStore, _getPurge, _setPurge
+ _getOptions, _setOptions, _getPeers, _setPeers, _getCollect,
+ _setCollect, _getStage, _setStage, _getStore, _setStore,
+ _getPurge, _setPurge
"""
##############
@@ -3370,6 +3521,7 @@
self._reference = None
self._extensions = None
self._options = None
+ self._peers = None
self._collect = None
self._stage = None
self._store = None
@@ -3377,6 +3529,7 @@
self.reference = None
self.extensions = None
self.options = None
+ self.peers = None
self.collect = None
self.stage = None
self.store = None
@@ -3402,8 +3555,9 @@
"""
Official string representation for class instance.
"""
- return "Config(%s, %s, %s, %s, %s, %s, %s)" % (self.reference, self.extensions, self.options,
- self.collect, self.stage, self.store, self.purge)
+ 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):
"""
@@ -3440,6 +3594,11 @@
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
@@ -3524,6 +3683,25 @@
"""
return self._options
+ def _setPeers(self, value):
+ """
+ Property target used to set the peers configuration value.
+ If not C{None}, the value must be an C{PeersConfig} object.
+ @raise ValueError: If the value is not a C{PeersConfig}
+ """
+ if value is None:
+ self._peers = None
+ else:
+ if not isinstance(value, PeersConfig):
+ raise ValueError("Value must be a C{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.
@@ -3603,6 +3781,7 @@
reference = property(_getReference, _setReference, None, "Reference configuration in terms of a C{ReferenceConfig} object.")
extensions = property(_getExtensions, _setExtensions, None, "Extensions configuration in terms of a C{ExtensionsConfig} object.")
options = property(_getOptions, _setOptions, None, "Options configuration in terms of a C{OptionsConfig} object.")
+ peers = property(_getPeers, _setPeers, None, "Peers configuration in terms of a C{PeersConfig} object.")
collect = property(_getCollect, _setCollect, None, "Collect configuration in terms of a C{CollectConfig} object.")
stage = property(_getStage, _setStage, None, "Stage configuration in terms of a C{StageConfig} object.")
store = property(_getStore, _setStore, None, "Store configuration in terms of a C{StoreConfig} object.")
@@ -3652,7 +3831,7 @@
return xmlData
def validate(self, requireOneAction=True, requireReference=False, requireExtensions=False, requireOptions=True,
- requireCollect=False, requireStage=False, requireStore=False, requirePurge=False):
+ requireCollect=False, requireStage=False, requireStore=False, requirePurge=False, requirePeers=False):
"""
Validates configuration represented by the object.
@@ -3666,6 +3845,7 @@
@param requireReference: Require the reference section.
@param requireExtensions: Require the extensions section.
@param requireOptions: Require the options section.
+ @param requirePeers: Require the peers section.
@param requireCollect: Require the collect section.
@param requireStage: Require the stage section.
@param requireStore: Require the store section.
@@ -3681,6 +3861,8 @@
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:
@@ -3719,6 +3901,7 @@
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)
@@ -3821,6 +4004,31 @@
return options
_parseOptions = staticmethod(_parseOptions)
+ 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 L{_parsePeerList}.
+
+ @param parentNode: Parent node to search beneath.
+
+ @return: C{StageConfig} object or C{None} if the section does not exist.
+ @raise 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
+ _parsePeers = staticmethod(_parsePeers)
+
def _parseCollect(parentNode):
"""
Parses a collect configuration section.
@@ -3877,7 +4085,7 @@
localPeers //cb_config/stage/peer
remotePeers //cb_config/stage/peer
- The individual peer entries are parsed by L{_parsePeers}.
+ The individual peer entries are parsed by L{_parsePeerList}.
@param parentNode: Parent node to search beneath.
@@ -3889,7 +4097,7 @@
if sectionNode is not None:
stage = StageConfig()
stage.targetDir = readString(sectionNode, "staging_dir")
- (stage.localPeers, stage.remotePeers) = Config._parsePeers(sectionNode)
+ (stage.localPeers, stage.remotePeers) = Config._parsePeerList(sectionNode)
return stage
_parseStage = staticmethod(_parseStage)
@@ -4191,7 +4399,7 @@
return lst
_parsePurgeDirs = staticmethod(_parsePurgeDirs)
- def _parsePeers(parentNode):
+ def _parsePeerList(parentNode):
"""
Reads remote and local peer data from immediately beneath the parent.
@@ -4241,7 +4449,7 @@
if remotePeers == []:
remotePeers = None
return (localPeers, remotePeers)
- _parsePeers = staticmethod(_parsePeers)
+ _parsePeerList = staticmethod(_parsePeerList)
def _parseDependencies(parentNode):
"""
@@ -4344,6 +4552,7 @@
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)
@@ -4448,6 +4657,35 @@
Config._addHook(xmlDom, sectionNode, hook)
_addOptions = staticmethod(_addOptions)
+ 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
+ L{_addLocalPeer} and L{_addRemotePeer}, respectively.
+
+ If C{peersConfig} is C{None}, then no container will be added.
+
+ @param xmlDom: DOM tree as from L{createOutputDom}.
+ @param parentNode: Parent that the section should be appended to.
+ @param 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)
+ _addPeers = staticmethod(_addPeers)
+
def _addCollect(xmlDom, parentNode, collectConfig):
"""
Adds a <collect> configuration section as the next child of a parent.
@@ -4943,6 +5181,7 @@
self._validateReference()
self._validateExtensions()
self._validateOptions()
+ self._validatePeers()
self._validateCollect()
self._validateStage()
self._validateStore()
@@ -5010,6 +5249,14 @@
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 L{_validatePeerList}.
+ @raise 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.
@@ -5056,18 +5303,27 @@
"""
Validates stage configuration.
- 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 C{None} or an empty list C{[]} if desired.
+ The target directory must be filled in, and the peers are
+ also validated.
- Then, peer list validation (see L{_validatePeerList}) applies as well.
+ 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 L{_validatePeerList}.
@raise 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.")
- self._validatePeerList(self.stage.localPeers, self.stage.remotePeers)
+ 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):
"""
@@ -5148,10 +5404,10 @@
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("Peerl list must contain at least one backup peer.")
+ 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("Peerl list must contain at least one backup peer.")
+ raise ValueError("Peer list must contain at least one backup peer.")
names = []
if localPeers is not None:
for localPeer in localPeers:
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2007-12-15 19:20:09 UTC (rev 804)
+++ cedar-backup2/trunk/Changelog 2007-12-15 20:52:14 UTC (rev 805)
@@ -1,6 +1,10 @@
-Version 2.14.1 unreleased
+Version 2.15.0 unreleased
* Minor documentation tweaks discovered during 3.0 development.
+ * Add support for managed peers, where the mater kicks off remote actions.
+ - Add a new <peers> configuration section (PeersConfig)
+ - Change peers configuration in <stage> to just override <peers>
+ - Modify stage process to take peers list from peers section (if available)
Version 2.14.0 19 Sep 2007
Modified: cedar-backup2/trunk/test/configtests.py
===================================================================
--- cedar-backup2/trunk/test/configtests.py 2007-12-15 19:20:09 UTC (rev 804)
+++ cedar-backup2/trunk/test/configtests.py 2007-12-15 20:52:14 UTC (rev 805)
@@ -105,7 +105,7 @@
from CedarBackup2.config import ActionHook, PreActionHook, PostActionHook, CommandOverride
from CedarBackup2.config import ExtendedAction, ActionDependencies, BlankBehavior
from CedarBackup2.config import CollectFile, CollectDir, PurgeDir, LocalPeer, RemotePeer
-from CedarBackup2.config import ReferenceConfig, ExtensionsConfig, OptionsConfig
+from CedarBackup2.config import ReferenceConfig, ExtensionsConfig, OptionsConfig, PeersConfig
from CedarBackup2.config import CollectConfig, StageConfig, StoreConfig, PurgeConfig, Config
@@ -118,7 +118,8 @@
"cback.conf.5", "cback.conf.6", "cback.conf.7", "cback.conf.8",
"cback.conf.9", "cback.conf.10", "cback.conf.11", "cback.conf.12",
"cback.conf.13", "cback.conf.14", "cback.conf.15", "cback.conf.16",
- "cback.conf.17", "cback.conf.18", "cback.conf.19", "cback.conf.20", ]
+ "cback.conf.17", "cback.conf.18", "cback.conf.19", "cback.conf.20",
+ "cback.conf.21", "cback.conf.22", "cback.conf.23", ]
#######################################################################
@@ -4956,6 +4957,366 @@
self.failUnless(options1 != options2)
+########################
+# TestPeersConfig class
+########################
+
+class TestPeersConfig(unittest.TestCase):
+
+ """Tests for the PeersConfig class."""
+
+ ##################
+ # Utility methods
+ ##################
+
+ def failUnlessAssignRaises(self, exception, object, property, value):
+ """Equivalent of L{failUnlessRaises}, but used for property assignments instead."""
+ failUnlessAssignRaises(self, exception, object, property, value)
+
+
+ ############################
+ # Test __repr__ and __str__
+ ############################
+
+ def testStringFuncs_001(self):
+ """
+ Just make sure that the string functions don't have errors (i.e. bad variable names).
+ """
+ obj = PeersConfig()
+ obj.__repr__()
+ obj.__str__()
+
+
+ ##################################
+ # Test constructor and attributes
+ ##################################
+
+ def testConstructor_001(self):
+ """
+ Test constructor with no values filled in.
+ """
+ peers = PeersConfig()
+ self.failUnlessEqual(None, peers.localPeers)
+ self.failUnlessEqual(None, peers.remotePeers)
+
+ def testConstructor_002(self):
+ """
+ Test constructor with all values filled in, with valid values (empty lists).
+ """
+ peers = PeersConfig([], [])
+ self.failUnlessEqual([], peers.localPeers)
+ self.failUnlessEqual([], peers.remotePeers)
+
+ def testConstructor_003(self):
+ """
+ Test constructor with all values filled in, with valid values (non-empty lists).
+ """
+ peers = PeersConfig([LocalPeer(), ], [RemotePeer(), ])
+ self.failUnlessEqual([LocalPeer(), ], peers.localPeers)
+ self.failUnlessEqual([RemotePeer(), ], peers.remotePeers)
+
+ def testConstructor_004(self):
+ """
+ Test assignment of localPeers attribute, None value.
+ """
+ peers = PeersConfig(localPeers=[])
+ self.failUnlessEqual([], peers.localPeers)
+ peers.localPeers = None
+ self.failUnlessEqual(None, peers.localPeers)
+
+ def testConstructor_005(self):
+ """
+ Test assignment of localPeers attribute, empty list.
+ """
+ peers = PeersConfig()
+ self.failUnlessEqual(None, peers.localPeers)
+ peers.localPeers = []
+ self.failUnlessEqual([], peers.localPeers)
+
+ def testConstructor_006(self):
+ """
+ Test assignment of localPeers attribute, single valid entry.
+ """
+ peers = PeersConfig()
+ self.failUnlessEqual(None, peers.localPeers)
+ peers.localPeers = [LocalPeer(), ]
+ self.failUnlessEqual([LocalPeer(), ], peers.localPeers)
+
+ def testConstructor_007(self):
+ """
+ Test assignment of localPeers attribute, multiple valid
+ entries.
+ """
+ peers = PeersConfig()
+ self.failUnlessEqual(None, peers.localPeers)
+ peers.localPeers = [LocalPeer(name="one"), LocalPeer(name="two"), ]
+ self.failUnlessEqual([LocalPeer(name="one"), LocalPeer(name="two"), ], peers.localPeers)
+
+ def testConstructor_008(self):
+ """
+ Test assignment of localPeers attribute, single invalid entry
+ (None).
+ """
+ peers = PeersConfig()
+ self.failUnlessEqual(None, peers.localPeers)
+ self.failUnlessAssignRaises(ValueError, peers, "localPeers", [None, ])
+ self.failUnlessEqual(None, peers.localPeers)
+
+ def testConstructor_009(self):
+ """
+ Test assignment of localPeers attribute, single invalid entry
+ (not a LocalPeer).
+ """
+ peers = PeersConfig()
+ self.failUnlessEqual(None, peers.localPeers)
+ self.failUnlessAssignRaises(ValueError, peers, "localPeers", [RemotePeer(), ])
+ self.failUnlessEqual(None, peers.localPeers)
+
+ def testConstructor_010(self):
+ """
+ Test assignment of localPeers attribute, mixed valid and
+ invalid entries.
+ """
+ peers = PeersConfig()
+ self.failUnlessEqual(None, peers.localPeers)
+ self.failUnlessAssig...
[truncated message content] |
|
From: <pro...@us...> - 2007-12-15 21:12:42
|
Revision: 808
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=808&view=rev
Author: pronovic
Date: 2007-12-15 13:12:36 -0800 (Sat, 15 Dec 2007)
Log Message:
-----------
Update manual to discuss new peers configuration
Modified Paths:
--------------
cedar-backup2/trunk/Changelog
cedar-backup2/trunk/manual/src/config.xml
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2007-12-15 20:59:51 UTC (rev 807)
+++ cedar-backup2/trunk/Changelog 2007-12-15 21:12:36 UTC (rev 808)
@@ -5,6 +5,7 @@
- Add a new <peers> configuration section (PeersConfig)
- Change peers configuration in <stage> to just override <peers>
- Modify stage process to take peers list from peers section (if available)
+ - Updated user manual to discuss new peers configuration section
Version 2.14.0 19 Sep 2007
Modified: cedar-backup2/trunk/manual/src/config.xml
===================================================================
--- cedar-backup2/trunk/manual/src/config.xml 2007-12-15 20:59:51 UTC (rev 807)
+++ cedar-backup2/trunk/manual/src/config.xml 2007-12-15 21:12:36 UTC (rev 808)
@@ -187,6 +187,13 @@
<backup_group>group</backup_group>
<rcp_command>/usr/bin/scp -B</rcp_command>
</options>
+ <peers>
+ <peer>
+ <name>debian</name>
+ <type>local</type>
+ <collect_dir>/opt/backup/collect</collect_dir>
+ </peer>
+ </peers>
<collect>
<collect_dir>/opt/backup/collect</collect_dir>
<collect_mode>daily</collect_mode>
@@ -203,11 +210,6 @@
</collect>
<stage>
<staging_dir>/opt/backup/staging</staging_dir>
- <peer>
- <name>debian</name>
- <type>local</type>
- <collect_dir>/opt/backup/collect</collect_dir>
- </peer>
</stage>
<store>
<source_dir>/opt/backup/staging</source_dir>
@@ -408,7 +410,7 @@
</para>
<para>
This value is also used as the default remote backup user
- for remote peers in the staging section.
+ for remote peers.
</para>
<para>
<emphasis>Restrictions:</emphasis> Must be non-empty
@@ -447,9 +449,8 @@
</para>
<para>
This value is used as the default value for all remote
- peers in the staging section. Technically, this value is
- not needed by clients, but we require it for all config
- files anyway.
+ peers. Technically, this value is not needed by clients,
+ but we require it for all config files anyway.
</para>
<para>
<emphasis>Restrictions:</emphasis> Must be non-empty
@@ -672,6 +673,236 @@
<!-- ################################################################# -->
+ <sect2 id="cedar-config-configfile-peers">
+
+ <title>Peers Configuration</title>
+
+ <para>
+ The peers configuration section contains a list of the peers
+ managed by a master. This section is only required on a master.
+ </para>
+
+ <para>
+ This is an example peers configuration section:
+ </para>
+
+ <programlisting>
+<peers>
+ <peer>
+ <name>machine1</name>
+ <type>local</type>
+ <collect_dir>/opt/backup/collect</collect_dir>
+ </peer>
+ <peer>
+ <name>machine2</name>
+ <type>remote</type>
+ <backup_user>backup</backup_user>
+ <collect_dir>/opt/backup/collect</collect_dir>
+ </peer>
+</peers>
+ </programlisting>
+
+ <para>
+ The following elements are part of the peers configuration section:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>peer</literal> (local version)</term>
+ <listitem>
+ <para>Local client peer in a backup pool.</para>
+ <para>
+ This is a subsection which contains information about a
+ specific local client peer managed by a master.
+ </para>
+ <para>
+ This section can be repeated as many times as is
+ necessary. At least one remote or local peer must be
+ configured.
+ </para>
+ <para>
+ The local peer subsection must contain the following fields:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>name</literal></term>
+ <listitem>
+ <para>Name of the peer, typically a valid hostname.</para>
+ <para>
+ For local peers, this value is only used for
+ reference. However, it is good practice to list
+ the peer's hostname here, for consistency with
+ remote peers.
+ </para>
+ <para>
+ <emphasis>Restrictions:</emphasis> Must be non-empty,
+ and unique among all peers.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>type</literal></term>
+ <listitem>
+ <para>Type of this peer.</para>
+ <para>
+ This value identifies the type of the peer. For
+ a local peer, it must always be <literal>local</literal>.
+ </para>
+ <para>
+ <emphasis>Restrictions:</emphasis> Must be <literal>local</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>collect_dir</literal></term>
+ <listitem>
+ <para>Collect directory to stage from for this peer.</para>
+ <para>
+ The master will copy all files in this directory
+ into the appropriate staging directory. Since
+ this is a local peer, the directory is assumed to
+ be reachable via normal filesystem operations
+ (i.e. <command>cp</command>).
+ </para>
+ <para>
+ <emphasis>Restrictions:</emphasis> Must be an absolute path.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>peer</literal> (remote version)</term>
+ <listitem>
+ <para>Remote client peer in a backup pool.</para>
+ <para>
+ This is a subsection which contains information about a
+ specific remote client peer managed by a master. A remote
+ peer is one which can be reached via an rsh-based network
+ call.
+ </para>
+ <para>
+ This section can be repeated as many times as is
+ necessary. At least one remote or local peer must be
+ configured.
+ </para>
+ <para>
+ The remote peer subsection must contain the following fields:
+ </para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term><literal>name</literal></term>
+ <listitem>
+ <para>Hostname of the peer.</para>
+ <para>
+ For remote peers, this must be a valid DNS
+ hostname or IP address which can be resolved
+ during an rsh-based network call.
+ </para>
+ <para>
+ <emphasis>Restrictions:</emphasis> Must be non-empty,
+ and unique among all peers.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>type</literal></term>
+ <listitem>
+ <para>Type of this peer.</para>
+ <para>
+ This value identifies the type of the peer. For
+ a remote peer, it must always be <literal>remote</literal>.
+ </para>
+ <para>
+ <emphasis>Restrictions:</emphasis> Must be <literal>remote</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>collect_dir</literal></term>
+ <listitem>
+ <para>Collect directory to stage from for this peer.</para>
+ <para>
+ The master will copy all files in this directory
+ into the appropriate staging directory. Since
+ this is a remote peer, the directory is assumed to
+ be reachable via rsh-based network operations
+ (i.e. <command>scp</command> or the configured
+ rcp command).
+ </para>
+ <para>
+ <emphasis>Restrictions:</emphasis> Must be an absolute path.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>backup_user</literal></term>
+ <listitem>
+ <para>Name of backup user on the remote peer.</para>
+ <para>
+ This username will be used when copying files from
+ the remote peer via an rsh-based network connection.
+ </para>
+ <para>
+ This field is optional. if it doesn't exist, the
+ backup will use the default backup user from the
+ options section.
+ </para>
+ <para>
+ <emphasis>Restrictions:</emphasis> Must be non-empty.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>rcp_command</literal></term>
+ <listitem>
+ <para>The rcp-compatible copy command for this peer.</para>
+ <para>
+ The rcp command should be the exact command used for
+ remote copies, including any required options. If you are
+ using <command>scp</command>, you should pass it the
+ <option>-B</option> option, so <command>scp</command> will
+ not ask for any user input (which could hang the backup).
+ A common example is something like <command>/usr/bin/scp
+ -B</command>.
+ </para>
+ <para>
+ This field is optional. if it doesn't exist, the
+ backup will use the default rcp command from the
+ options section.
+ </para>
+ <para>
+ <emphasis>Restrictions:</emphasis> Must be non-empty.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </sect2>
+
+ <!-- ################################################################# -->
+
<sect2 id="cedar-config-configfile-collect">
<title>Collect Configuration</title>
@@ -1311,18 +1542,37 @@
<para>
The stage configuration section contains configuration options
- related the the stage action. The section defines the set of peers
- in a backup pool, and then also indicates where data from those
- peers should be staged to.
+ related the the stage action. The section indicates where date
+ from peers can be staged to.
</para>
+
+ <para>
+ This section can also (optionally) override the list of peers so
+ that not all peers are staged. If you provide
+ <emphasis>any</emphasis> peers in this section, then the list of
+ peers here completely replaces the list of peers in the peers
+ configuration section for the purposes of staging.
+ </para>
<para>
- This is an example stage configuration section:
+ This is an example stage configuration section for the simple case
+ where the list of peers is taken from peers configuration:
</para>
<programlisting>
<stage>
<staging_dir>/opt/backup/stage</staging_dir>
+</stage>
+ </programlisting>
+
+ <para>
+ This is an example stage configuration section that overrides the
+ default list of peers:
+ </para>
+
+ <programlisting>
+<stage>
+ <staging_dir>/opt/backup/stage</staging_dir>
<peer>
<name>machine1</name>
<type>local</type>
@@ -1387,6 +1637,12 @@
configured.
</para>
<para>
+ <emphasis>Remember</emphasis>, if you provide
+ <emphasis>any</emphasis> local or remote peer in staging
+ configuration, the global peer configuration is completely
+ replaced by the staging peer configuration.
+ </para>
+ <para>
The local peer subsection must contain the following fields:
</para>
@@ -1461,6 +1717,12 @@
configured.
</para>
<para>
+ <emphasis>Remember</emphasis>, if you provide
+ <emphasis>any</emphasis> local or remote peer in staging
+ configuration, the global peer configuration is completely
+ replaced by the staging peer configuration.
+ </para>
+ <para>
The remote peer subsection must contain the following fields:
</para>
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2007-12-15 21:40:56
|
Revision: 809
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=809&view=rev
Author: pronovic
Date: 2007-12-15 13:40:44 -0800 (Sat, 15 Dec 2007)
Log Message:
-----------
Add rshCommand to OptionsConfig (and fix some unit test)
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/config.py
cedar-backup2/trunk/test/configtests.py
cedar-backup2/trunk/test/data/cback.conf.15
cedar-backup2/trunk/test/data/cback.conf.20
cedar-backup2/trunk/test/data/cback.conf.21
cedar-backup2/trunk/test/data/cback.conf.6
Modified: cedar-backup2/trunk/CedarBackup2/config.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/config.py 2007-12-15 21:12:36 UTC (rev 808)
+++ cedar-backup2/trunk/CedarBackup2/config.py 2007-12-15 21:40:44 UTC (rev 809)
@@ -159,9 +159,9 @@
I{Options Validations}
- All fields must be filled in. The rcp command is used as a default value
- for all remote peers in the staging section. Remote peers can also rely on
- the backup user as the default remote user name if they choose.
+ 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.
I{Peers Validations}
@@ -2142,11 +2142,12 @@
- The hooks list must be a list of C{ActionHook} objects.
@sort: __init__, __repr__, __str__, __cmp__, startingDay, workingDir,
- backupUser, backupGroup, rcpCommand, overrides
+ backupUser, backupGroup, rcpCommand, rshCommand, overrides
"""
def __init__(self, startingDay=None, workingDir=None, backupUser=None,
- backupGroup=None, rcpCommand=None, overrides=None, hooks=None):
+ backupGroup=None, rcpCommand=None, overrides=None,
+ hooks=None, rshCommand=None):
"""
Constructor for the C{OptionsConfig} class.
@@ -2155,6 +2156,7 @@
@param backupUser: Effective user that backups should run as.
@param backupGroup: Effective group that backups should run as.
@param rcpCommand: Default rcp-compatible copy command for staging.
+ @param rshCommand: Default rsh-compatible command to use for remote shells.
@param overrides: List of configured command path overrides, if any.
@param hooks: List of configured pre- and post-action hooks.
@@ -2165,6 +2167,7 @@
self._backupUser = None
self._backupGroup = None
self._rcpCommand = None
+ self._rshCommand = None
self._overrides = None
self._hooks = None
self.startingDay = startingDay
@@ -2172,6 +2175,7 @@
self.backupUser = backupUser
self.backupGroup = backupGroup
self.rcpCommand = rcpCommand
+ self.rshCommand = rshCommand
self.overrides = overrides
self.hooks = hooks
@@ -2179,10 +2183,10 @@
"""
Official string representation for class instance.
"""
- return "OptionsConfig(%s, %s, %s, %s, %s, %s, %s)" % (self.startingDay, self.workingDir,
- self.backupUser, self.backupGroup,
- self.rcpCommand, self.overrides,
- self.hooks)
+ return "OptionsConfig(%s, %s, %s, %s, %s, %s, %s, %s)" % (self.startingDay, self.workingDir,
+ self.backupUser, self.backupGroup,
+ self.rcpCommand, self.overrides,
+ self.hooks, self.rshCommand)
def __str__(self):
"""
@@ -2223,6 +2227,11 @@
return -1
else:
return 1
+ if self._rshCommand != other._rshCommand:
+ if self._rshCommand < other._rshCommand:
+ return -1
+ else:
+ return 1
if self._overrides != other._overrides:
if self._overrides < other._overrides:
return -1
@@ -2323,6 +2332,23 @@
"""
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 C{None}.
+ @raise 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 _setOverrides(self, value):
"""
Property target used to set the command path overrides list.
@@ -2374,6 +2400,7 @@
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.")
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.")
@@ -3977,6 +4004,7 @@
backupUser //cb_config/options/backup_user
backupGroup //cb_config/options/backup_group
rcpCommand //cb_config/options/rcp_command
+ rshCommand //cb_config/options/rsh_command
We also read groups of the following items, one list element per
item::
@@ -3999,6 +4027,7 @@
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.overrides = Config._parseOverrides(sectionNode)
options.hooks = Config._parseHooks(sectionNode)
return options
@@ -4625,6 +4654,7 @@
backupUser //cb_config/options/backup_user
backupGroup //cb_config/options/backup_group
rcpCommand //cb_config/options/rcp_command
+ rshCommand //cb_config/options/rsh_command
We also add groups of the following items, one list element per
item::
@@ -4649,6 +4679,7 @@
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)
if optionsConfig.overrides is not None:
for override in optionsConfig.overrides:
Config._addOverride(xmlDom, sectionNode, override)
@@ -5231,9 +5262,10 @@
"""
Validates options configuration.
- All fields must be filled in. The rcp command is used as a default value
- for all remote peers in the staging section. Remote peers can also rely
- on the backup user as the default remote user name if they choose.
+ 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.
@raise ValueError: If reference configuration is invalid.
"""
Modified: cedar-backup2/trunk/test/configtests.py
===================================================================
--- cedar-backup2/trunk/test/configtests.py 2007-12-15 21:12:36 UTC (rev 808)
+++ cedar-backup2/trunk/test/configtests.py 2007-12-15 21:40:44 UTC (rev 809)
@@ -4299,19 +4299,22 @@
self.failUnlessEqual(None, options.backupUser)
self.failUnlessEqual(None, options.backupGroup)
self.failUnlessEqual(None, options.rcpCommand)
+ self.failUnlessEqual(None, options.rshCommand)
self.failUnlessEqual(None, options.overrides)
def testConstructor_002(self):
"""
Test constructor with all values filled in, with valid values (lists empty).
"""
- options = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", [])
+ options = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", [], [], "ssh")
self.failUnlessEqual("monday", options.startingDay)
self.failUnlessEqual("/tmp", options.workingDir)
self.failUnlessEqual("user", options.backupUser)
self.failUnlessEqual("group", options.backupGroup)
self.failUnlessEqual("scp -1 -B", options.rcpCommand)
+ self.failUnlessEqual("ssh", options.rshCommand)
self.failUnlessEqual([], options.overrides)
+ self.failUnlessEqual([], options.hooks)
def testConstructor_003(self):
"""
@@ -4483,13 +4486,14 @@
Test constructor with all values filled in, with valid values (lists not empty).
"""
overrides = [ CommandOverride("mkisofs", "/usr/bin/mkisofs"), ]
- hooks = [ ActionHook("collect", "ls -l"), ]
- options = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks)
+ hooks = [ PreActionHook("collect", "ls -l"), ]
+ options = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks, "ssh")
self.failUnlessEqual("monday", options.startingDay)
self.failUnlessEqual("/tmp", options.workingDir)
self.failUnlessEqual("user", options.backupUser)
self.failUnlessEqual("group", options.backupGroup)
self.failUnlessEqual("scp -1 -B", options.rcpCommand)
+ self.failUnlessEqual("ssh", options.rshCommand)
self.failUnlessEqual(overrides, options.overrides)
self.failUnlessEqual(hooks, options.hooks)
@@ -4584,8 +4588,8 @@
"""
collect = OptionsConfig()
self.failUnlessEqual(None, collect.hooks)
- collect.hooks = [ActionHook("stage", "df -k"), ]
- self.failUnlessEqual([ActionHook("stage", "df -k"), ], collect.hooks)
+ collect.hooks = [PreActionHook("stage", "df -k"), ]
+ self.failUnlessEqual([PreActionHook("stage", "df -k"), ], collect.hooks)
def testConstructor_031(self):
"""
@@ -4594,8 +4598,8 @@
"""
collect = OptionsConfig()
self.failUnlessEqual(None, collect.hooks)
- collect.hooks = [ ActionHook("stage", "df -k"), ActionHook("collect", "ls -l"), ]
- self.failUnlessEqual([ActionHook("stage", "df -k"), ActionHook("collect", "ls -l"), ], collect.hooks)
+ collect.hooks = [ PreActionHook("stage", "df -k"), PostActionHook("collect", "ls -l"), ]
+ self.failUnlessEqual([PreActionHook("stage", "df -k"), PostActionHook("collect", "ls -l"), ], collect.hooks)
def testConstructor_032(self):
"""
@@ -4624,10 +4628,37 @@
"""
collect = OptionsConfig()
self.failUnlessEqual(None, collect.hooks)
- self.failUnlessAssignRaises(ValueError, collect, "hooks", [ "hello", ActionHook("stage", "df -k"), ])
+ self.failUnlessAssignRaises(ValueError, collect, "hooks", [ "hello", PreActionHook("stage", "df -k"), ])
self.failUnlessEqual(None, collect.hooks)
+ def testConstructor_035(self):
+ """
+ Test assignment of rshCommand attribute, None value.
+ """
+ options = OptionsConfig(rshCommand="command")
+ self.failUnlessEqual("command", options.rshCommand)
+ options.rshCommand = None
+ self.failUnlessEqual(None, options.rshCommand)
+ def testConstructor_036(self):
+ """
+ Test assignment of rshCommand attribute, valid value.
+ """
+ options = OptionsConfig()
+ self.failUnlessEqual(None, options.rshCommand)
+ options.rshCommand = "command"
+ self.failUnlessEqual("command", options.rshCommand)
+
+ def testConstructor_037(self):
+ """
+ Test assignment of rshCommand attribute, invalid value (empty).
+ """
+ options = OptionsConfig()
+ self.failUnlessEqual(None, options.rshCommand)
+ self.failUnlessAssignRaises(ValueError, options, "rshCommand", "")
+ self.failUnlessEqual(None, options.rshCommand)
+
+
############################
# Test comparison operators
############################
@@ -4652,8 +4683,8 @@
"""
overrides = [ CommandOverride("one", "/one"), ]
hooks = [ PreActionHook("collect", "ls -l ") ]
- options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks)
- options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks)
+ options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks, "ssh")
+ options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks, "ssh")
self.failUnlessEqual(options1, options2)
self.failUnless(options1 == options2)
self.failUnless(not options1 < options2)
@@ -4682,8 +4713,8 @@
"""
overrides = [ CommandOverride("one", "/one"), ]
hooks = [ PreActionHook("collect", "ls -l ") ]
- options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks)
- options2 = OptionsConfig("tuesday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks)
+ options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks, "ssh")
+ options2 = OptionsConfig("tuesday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks, "ssh")
self.failIfEqual(options1, options2)
self.failUnless(not options1 == options2)
self.failUnless(options1 < options2)
@@ -4712,8 +4743,8 @@
"""
overrides = [ CommandOverride("one", "/one"), ]
hooks = [ PreActionHook("collect", "ls -l ") ]
- options1 = OptionsConfig("monday", "/tmp/whatever", "user", "group", "scp -1 -B", overrides, hooks)
- options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks)
+ options1 = OptionsConfig("monday", "/tmp/whatever", "user", "group", "scp -1 -B", overrides, hooks, "ssh")
+ options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks, "ssh")
self.failIfEqual(options1, options2)
self.failUnless(not options1 == options2)
self.failUnless(not options1 < options2)
@@ -4742,8 +4773,8 @@
"""
overrides = [ CommandOverride("one", "/one"), ]
hooks = [ PreActionHook("collect", "ls -l ") ]
- options1 = OptionsConfig("monday", "/tmp", "user2", "group", "scp -1 -B", overrides, hooks)
- options2 = OptionsConfig("monday", "/tmp", "user1", "group", "scp -1 -B", overrides, hooks)
+ options1 = OptionsConfig("monday", "/tmp", "user2", "group", "scp -1 -B", overrides, hooks, "ssh")
+ options2 = OptionsConfig("monday", "/tmp", "user1", "group", "scp -1 -B", overrides, hooks, "ssh")
self.failIfEqual(options1, options2)
self.failUnless(not options1 == options2)
self.failUnless(not options1 < options2)
@@ -4772,8 +4803,8 @@
"""
overrides = [ CommandOverride("one", "/one"), ]
hooks = [ PreActionHook("collect", "ls -l ") ]
- options1 = OptionsConfig("monday", "/tmp", "user", "group1", "scp -1 -B", overrides, hooks)
- options2 = OptionsConfig("monday", "/tmp", "user", "group2", "scp -1 -B", overrides, hooks)
+ options1 = OptionsConfig("monday", "/tmp", "user", "group1", "scp -1 -B", overrides, hooks, "ssh")
+ options2 = OptionsConfig("monday", "/tmp", "user", "group2", "scp -1 -B", overrides, hooks, "ssh")
self.failIfEqual(options1, options2)
self.failUnless(not options1 == options2)
self.failUnless(options1 < options2)
@@ -4802,8 +4833,8 @@
"""
overrides = [ CommandOverride("one", "/one"), ]
hooks = [ PreActionHook("collect", "ls -l ") ]
- options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -2 -B", overrides, hooks)
- options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks)
+ options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -2 -B", overrides, hooks, "ssh")
+ options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks, "ssh")
self.failIfEqual(options1, options2)
self.failUnless(not options1 == options2)
self.failUnless(not options1 < options2)
@@ -4820,8 +4851,8 @@
overrides1 = None
overrides2 = []
hooks = [ PreActionHook("collect", "ls -l ") ]
- options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides1, hooks)
- options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides2, hooks)
+ options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides1, hooks, "ssh")
+ options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides2, hooks, "ssh")
self.failIfEqual(options1, options2)
self.failUnless(not options1 == options2)
self.failUnless(options1 < options2)
@@ -4838,10 +4869,10 @@
overrides1 = None
overrides2 = [ CommandOverride("one", "/one"), ]
hooks = [ PreActionHook("collect", "ls -l ") ]
- options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides1, hooks)
- options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides2, hooks)
+ options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides1, hooks, "ssh")
+ options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides2, hooks, "ssh")
self.failIfEqual(options1, options2)
- self.failUnless(not options1 == options2)
+ self.failUnless(not options1 == options2, "ssh")
self.failUnless(options1 < options2)
self.failUnless(options1 <= options2)
self.failUnless(not options1 > options2)
@@ -4856,8 +4887,8 @@
overrides1 = [ CommandOverride("one", "/one"), ]
overrides2 = []
hooks = [ PreActionHook("collect", "ls -l ") ]
- options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides1, hooks)
- options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides2, hooks)
+ options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides1, hooks, "ssh")
+ options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides2, hooks, "ssh")
self.failIfEqual(options1, options2)
self.failUnless(not options1 == options2)
self.failUnless(not options1 < options2)
@@ -4874,8 +4905,8 @@
overrides1 = [ CommandOverride("one", "/one"), ]
overrides2 = [ CommandOverride(), ]
hooks = [ PreActionHook("collect", "ls -l ") ]
- options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides1, hooks)
- options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides2, hooks)
+ options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides1, hooks, "ssh")
+ options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides2, hooks, "ssh")
self.failIfEqual(options1, options2)
self.failUnless(not options1 == options2)
self.failUnless(not options1 < options2)
@@ -4892,8 +4923,8 @@
overrides = [ CommandOverride("one", "/one"), ]
hooks1 = None
hooks2 = []
- options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks1)
- options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks2)
+ options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks1, "ssh")
+ options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks2, "ssh")
self.failIfEqual(options1, options2)
self.failUnless(not options1 == options2)
self.failUnless(options1 < options2)
@@ -4910,8 +4941,8 @@
overrides = [ CommandOverride("one", "/one"), ]
hooks1 = [ PreActionHook("collect", "ls -l ") ]
hooks2 = [ PostActionHook("collect", "ls -l ") ]
- options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks1)
- options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks2)
+ options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks1, "ssh")
+ options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks2, "ssh")
self.failIfEqual(options1, options2)
self.failUnless(not options1 == options2)
self.failUnless(options1 > options2)
@@ -4928,8 +4959,8 @@
overrides = [ CommandOverride("one", "/one"), ]
hooks1 = [ PreActionHook("collect", "ls -l ") ]
hooks2 = [ PreActionHook("stage", "ls -l ") ]
- options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks1)
- options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks2)
+ options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks1, "ssh")
+ options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks2, "ssh")
self.failIfEqual(options1, options2)
self.failUnless(not options1 == options2)
self.failUnless(not options1 > options2)
@@ -4945,9 +4976,9 @@
"""
overrides = [ CommandOverride("one", "/one"), ]
hooks1 = [ PreActionHook("collect", "ls -l ") ]
- hooks2 = [ ActionHook("collect", "ls -l ") ]
- options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks1)
- options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks2)
+ hooks2 = [ PostActionHook("collect", "ls -l ") ]
+ options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks1, "ssh")
+ options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks2, "ssh")
self.failIfEqual(options1, options2)
self.failUnless(not options1 == options2)
self.failUnless(not options1 < options2)
@@ -4956,7 +4987,37 @@
self.failUnless(options1 >= options2)
self.failUnless(options1 != options2)
+ def testComparison_021(self):
+ """
+ Test comparison of two differing objects, rshCommand differs (one None).
+ """
+ options1 = OptionsConfig()
+ options2 = OptionsConfig(rshCommand="command")
+ self.failIfEqual(options1, options2)
+ self.failUnless(not options1 == options2)
+ self.failUnless(options1 < options2)
+ self.failUnless(options1 <= options2)
+ self.failUnless(not options1 > options2)
+ self.failUnless(not options1 >= options2)
+ self.failUnless(options1 != options2)
+ def testComparison_022(self):
+ """
+ Test comparison of two differing objects, rshCommand differs.
+ """
+ overrides = [ CommandOverride("one", "/one"), ]
+ hooks = [ PreActionHook("collect", "ls -l ") ]
+ options1 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks, "ssh2")
+ options2 = OptionsConfig("monday", "/tmp", "user", "group", "scp -1 -B", overrides, hooks, "ssh1")
+ self.failIfEqual(options1, options2)
+ self.failUnless(not options1 == options2)
+ self.failUnless(not options1 < options2)
+ self.failUnless(not options1 <= options2)
+ self.failUnless(options1 > options2)
+ self.failUnless(options1 >= options2)
+ self.failUnless(options1 != options2)
+
+
########################
# TestPeersConfig class
########################
@@ -7653,6 +7714,7 @@
self.failUnlessEqual(None, config.reference)
self.failUnlessEqual(None, config.extensions)
self.failUnlessEqual(None, config.options)
+ self.failUnlessEqual(None, config.peers)
self.failUnlessEqual(None, config.collect)
self.failUnlessEqual(None, config.stage)
self.failUnlessEqual(None, config.store)
@@ -7666,6 +7728,7 @@
self.failUnlessEqual(None, config.reference)
self.failUnlessEqual(None, config.extensions)
self.failUnlessEqual(None, config.options)
+ self.failUnlessEqual(None, config.peers)
self.failUnlessEqual(None, config.collect)
self.failUnlessEqual(None, config.stage)
self.failUnlessEqual(None, config.store)
@@ -7689,6 +7752,7 @@
self.failUnlessEqual(None, config.reference)
self.failUnlessEqual(None, config.extensions)
self.failUnlessEqual(None, config.options)
+ self.failUnlessEqual(None, config.peers)
self.failUnlessEqual(None, config.collect)
self.failUnlessEqual(None, config.stage)
self.failUnlessEqual(None, config.store)
@@ -7703,6 +7767,7 @@
self.failUnlessEqual(None, config.reference)
self.failUnlessEqual(None, config.extensions)
self.failUnlessEqual(None, config.options)
+ self.failUnlessEqual(None, config.peers)
self.failUnlessEqual(None, config.collect)
self.failUnlessEqual(None, config.stage)
self.failUnlessEqual(None, config.store)
@@ -7869,7 +7934,30 @@
config = Config()
self.failUnlessAssignRaises(ValueError, config, "purge", CollectDir())
+ def testConstructor_027(self):
+ """
+ Test assignment of peers attribute, None value.
+ """
+ config = Config()
+ config.peers = None
+ self.failUnlessEqual(None, config.peers)
+ def testConstructor_028(self):
+ """
+ Test assignment of peers attribute, valid value.
+ """
+ config = Config()
+ config.peers = PeersConfig()
+ self.failUnlessEqual(PeersConfig(), config.peers)
+
+ def testConstructor_029(self):
+ """
+ Test assignment of peers attribute, invalid value (not PeersConfig).
+ """
+ config = Config()
+ self.failUnlessAssignRaises(ValueError, config, "peers", CollectDir())
+
+
############################
# Test comparison operators
############################
@@ -9860,7 +9948,7 @@
path = self.resources["cback.conf.6"]
config = Config(xmlPath=path, validate=False)
expected = Config()
- expected.options = OptionsConfig("tuesday", "/opt/backup/tmp", "backup", "group", "/usr/bin/scp -1 -B")
+ expected.options = OptionsConfig("tuesday", "/opt/backup/tmp", "backup", "group", "/usr/bin/scp -1 -B", [], [], "/usr/bin/ssh")
expected.options.overrides = [ CommandOverride("mkisofs", "/usr/bin/mkisofs"), CommandOverride("svnlook", "/svnlook"), ]
expected.options.hooks = [ PreActionHook("collect", "ls -l"), PostActionHook("stage", "df -k"), ]
self.failUnlessEqual(expected, config)
@@ -10103,7 +10191,7 @@
expected.extensions.actions = []
expected.extensions.actions.append(ExtendedAction("example", "something.whatever", "example", 102))
expected.extensions.actions.append(ExtendedAction("bogus", "module", "something", 350))
- expected.options = OptionsConfig("tuesday", "/opt/backup/tmp", "backup", "group", "/usr/bin/scp -1 -B")
+ expected.options = OptionsConfig("tuesday", "/opt/backup/tmp", "backup", "group", "/usr/bin/scp -1 -B", [], [], "/usr/bin/ssh")
expected.options.overrides = [ CommandOverride("mkisofs", "/usr/bin/mkisofs"), CommandOverride("svnlook", "/svnlook"), ]
expected.options.hooks = [ PreActionHook("collect", "ls -l"), PreActionHook("subversion", "mailx -S \"hello\""), PostActionHook("stage", "df -k"), ]
expected.collect = CollectConfig("/opt/backup/collect", "daily", "targz", ".cbignore")
@@ -10168,7 +10256,7 @@
expected.extensions.actions.append(ExtendedAction("bogus", "module", "something", index=None,
dependencies=ActionDependencies(beforeList=["a", "b", "c",],
afterList=["one",])))
- expected.options = OptionsConfig("tuesday", "/opt/backup/tmp", "backup", "group", "/usr/bin/scp -1 -B")
+ expected.options = OptionsConfig("tuesday", "/opt/backup/tmp", "backup", "group", "/usr/bin/scp -1 -B", [], [], "/usr/bin/ssh")
expected.options.overrides = [ CommandOverride("mkisofs", "/usr/bin/mkisofs"), CommandOverride("svnlook", "/svnlook"), ]
expected.options.hooks = [ PreActionHook("collect", "ls -l"), PreActionHook("subversion", "mailx -S \"hello\""), PostActionHook("stage", "df -k"), ]
expected.collect = CollectConfig("/opt/backup/collect", "daily", "targz", ".cbignore")
@@ -10230,7 +10318,7 @@
expected.extensions.actions = []
expected.extensions.actions.append(ExtendedAction("example", "something.whatever", "example", 102))
expected.extensions.actions.append(ExtendedAction("bogus", "module", "something", 350))
- expected.options = OptionsConfig("tuesday", "/opt/backup/tmp", "backup", "group", "/usr/bin/scp -1 -B")
+ expected.options = OptionsConfig("tuesday", "/opt/backup/tmp", "backup", "group", "/usr/bin/scp -1 -B", [], [], "/usr/bin/ssh")
expected.options.overrides = [ CommandOverride("mkisofs", "/usr/bin/mkisofs"), CommandOverride("svnlook", "/svnlook"), ]
expected.options.hooks = [ PreActionHook("collect", "ls -l"), PreActionHook("subversion", "mailx -S \"hello\""), PostActionHook("stage", "df -k"), ]
expected.collect = CollectConfig("/opt/backup/collect", "daily", "targz", ".cbignore")
@@ -10295,7 +10383,7 @@
expected.extensions.actions.append(ExtendedAction("bogus", "module", "something", index=None,
dependencies=ActionDepende...
[truncated message content] |
|
From: <pro...@us...> - 2007-12-15 23:10:41
|
Revision: 811
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=811&view=rev
Author: pronovic
Date: 2007-12-15 15:10:39 -0800 (Sat, 15 Dec 2007)
Log Message:
-----------
Add configuration for managed remote peers
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/config.py
cedar-backup2/trunk/test/configtests.py
cedar-backup2/trunk/test/data/cback.conf.15
cedar-backup2/trunk/test/data/cback.conf.20
cedar-backup2/trunk/test/data/cback.conf.21
cedar-backup2/trunk/test/data/cback.conf.23
cedar-backup2/trunk/test/data/cback.conf.6
Modified: cedar-backup2/trunk/CedarBackup2/config.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/config.py 2007-12-15 21:43:57 UTC (rev 810)
+++ cedar-backup2/trunk/CedarBackup2/config.py 2007-12-15 23:10:39 UTC (rev 811)
@@ -1717,11 +1717,16 @@
- 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 C{ACTION_NAME_REGEX}
@sort: __init__, __repr__, __str__, __cmp__, name, collectDir, remoteUser, rcpCommand
"""
- def __init__(self, name=None, collectDir=None, remoteUser=None, rcpCommand=None):
+ def __init__(self, name=None, collectDir=None, remoteUser=None,
+ rcpCommand=None, rshCommand=None, cbackCommand=None,
+ managed=False, managedActions=None):
"""
Constructor for the C{RemotePeer} class.
@@ -1729,6 +1734,10 @@
@param collectDir: Collect directory to stage files from on peer.
@param remoteUser: Name of backup user on remote peer.
@param rcpCommand: Overridden rcp-compatible copy command for peer.
+ @param rshCommand: Overridden rsh-compatible remote shell command for peer.
+ @param cbackCommand: Overridden cback-compatible command to use on remote peer.
+ @param managed: Indicates whether this is a managed peer.
+ @param managedActions: Overridden set of actions that are managed on the peer.
@raise ValueError: If one of the values is invalid.
"""
@@ -1736,16 +1745,26 @@
self._collectDir = None
self._remoteUser = None
self._rcpCommand = None
+ self._rshCommand = None
+ self._cbackCommand = None
+ self._managed = None
+ self._managedActions = 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
def __repr__(self):
"""
Official string representation for class instance.
"""
- return "RemotePeer(%s, %s, %s, %s)" % (self.name, self.collectDir, self.remoteUser, self.rcpCommand)
+ return "RemotePeer(%s, %s, %s, %s, %s, %s, %s, %s)" % (self.name, self.collectDir, self.remoteUser,
+ self.rcpCommand, self.rshCommand, self.cbackCommand,
+ self.managed, self.managedActions)
def __str__(self):
"""
@@ -1781,6 +1800,26 @@
return -1
else:
return 1
+ if self._rshCommand != other._rshCommand:
+ if self._rshCommand < other._rshCommand:
+ return -1
+ else:
+ return 1
+ if self._cbackCommand != other._cbackCommand:
+ if self._cbackCommand < other._cbackCommand:
+ return -1
+ else:
+ return 1
+ if self._managed != other._managed:
+ if self._managed < other._managed:
+ return -1
+ else:
+ return 1
+ if self._managedActions != other._managedActions:
+ if self._managedActions < other._managedActions:
+ return -1
+ else:
+ return 1
return 0
def _setName(self, value):
@@ -1853,10 +1892,86 @@
"""
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 C{None}.
+ @raise 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 C{None}.
+ @raise 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 C{True} or C{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, e:
+ self._managedActions = saved
+ raise e
+
+ def _getManagedActions(self):
+ """
+ Property target used to get the managed actions list.
+ """
+ return self._managedActions
+
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.")
########################################################################
@@ -2140,6 +2255,8 @@
- All of the other values must be non-empty strings if they are set to something other than C{None}.
- The overrides list must be a list of C{CommandOverride} objects.
- The hooks list must be a list of C{ActionHook} objects.
+ - The cback command must be a non-empty string.
+ - Any managed action name must be a non-empty string matching C{ACTION_NAME_REGEX}
@sort: __init__, __repr__, __str__, __cmp__, startingDay, workingDir,
backupUser, backupGroup, rcpCommand, rshCommand, overrides
@@ -2147,7 +2264,8 @@
def __init__(self, startingDay=None, workingDir=None, backupUser=None,
backupGroup=None, rcpCommand=None, overrides=None,
- hooks=None, rshCommand=None):
+ hooks=None, rshCommand=None, cbackCommand=None,
+ managedActions=None):
"""
Constructor for the C{OptionsConfig} class.
@@ -2157,8 +2275,10 @@
@param backupGroup: Effective group that backups should run as.
@param rcpCommand: Default rcp-compatible copy command for staging.
@param rshCommand: Default rsh-compatible command to use for remote shells.
+ @param cbackCommand: Default cback-compatible command to use on managed remote peers.
@param overrides: List of configured command path overrides, if any.
@param hooks: List of configured pre- and post-action hooks.
+ @parma managedActions: Default set of actions that are managed on remote peers.
@raise ValueError: If one of the values is invalid.
"""
@@ -2168,25 +2288,30 @@
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)" % (self.startingDay, self.workingDir,
- self.backupUser, self.backupGroup,
- self.rcpCommand, self.overrides,
- self.hooks, self.rshCommand)
+ 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):
"""
@@ -2232,6 +2357,11 @@
return -1
else:
return 1
+ if self._cbackCommand != other._cbackCommand:
+ if self._cbackCommand < other._cbackCommand:
+ return -1
+ else:
+ return 1
if self._overrides != other._overrides:
if self._overrides < other._overrides:
return -1
@@ -2242,6 +2372,11 @@
return -1
else:
return 1
+ if self._managedActions != other._managedActions:
+ if self._managedActions < other._managedActions:
+ return -1
+ else:
+ return 1
return 0
def _setStartingDay(self, value):
@@ -2349,6 +2484,23 @@
"""
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 C{None}.
+ @raise 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.
@@ -2395,14 +2547,38 @@
"""
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, 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.")
########################################################################
@@ -4005,13 +4181,19 @@
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 L{_parseOverrides}.
+ The overrides are parsed by L{_parseOverrides} and the hooks are parsed
+ by L{_parseHooks}.
@param parentNode: Parent node to search beneath.
@@ -4028,8 +4210,11 @@
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 = Config._parseCommaSeparatedString(managedActions)
return options
_parseOptions = staticmethod(_parseOptions)
@@ -4441,8 +4626,12 @@
We also read the following individual fields for remote peers
only::
- remoteUser backup_user
- rcpCommand rcp_command
+ remoteUser backup_user
+ rcpCommand rcp_command
+ rshCommand rsh_command
+ cbackCommand cback_command
+ managed managed
+ managedActions managed_actions
Additionally, the value in the C{type} field is used to determine whether
this entry is a remote peer. If the type is C{"remote"}, it's a remote
@@ -4472,6 +4661,11 @@
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.managed = readBoolean(entry, "managed")
+ managedActions = readString(entry, "managed_actions")
+ remotePeer.managedActions = Config._parseCommaSeparatedString(managedActions)
remotePeers.append(remotePeer)
if localPeers == []:
localPeers = None
@@ -4655,6 +4849,8 @@
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::
@@ -4680,6 +4876,9 @@
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)
@@ -5077,10 +5276,15 @@
We add the following fields to the document::
- name peer/name
- collectDir peer/collect_dir
- remoteUser peer/backup_user
- rcpCommand peer/rcp_command
+ 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
+ managed peer/managed
+ managedActions peer/managed_actions
Additionally, C{peer/type} is filled in with C{"remote"}, since this is a
remote peer.
@@ -5102,6 +5306,11 @@
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)
+ addBooleanNode(xmlDom, sectionNode, "managed", remotePeer.managed)
+ managedActions = Config._buildCommaSeparatedString(remotePeer.managedActions)
+ addStringNode(xmlDom, sectionNode, "managed_actions", managedActions)
_addRemotePeer = staticmethod(_addRemotePeer)
def _addPurgeDir(xmlDom, parentNode, purgeDir):
@@ -5459,6 +5668,9 @@
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: # redundant
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: # redundant
+ raise ValueError("Remote shell command must either be set in options section or individual remote peer.")
Config._checkUnique("Duplicate peer names exist:", names)
Modified: cedar-backup2/trunk/test/configtests.py
===================================================================
--- cedar-backup2/trunk/test/configtests.py 2007-12-15 21:43:57 UTC (rev 810)
+++ cedar-backup2/trunk/test/configtests.py 2007-12-15 23:10:39 UTC (rev 811)
@@ -3313,16 +3313,24 @@
self.failUnlessEqual(None, remotePeer.collectDir)
self.failUnlessEqual(None, remotePeer.remoteUser)
self.failUnlessEqual(None, remotePeer.rcpCommand)
+ self.failUnlessEqual(None, remotePeer.rshCommand)
+ self.failUnlessEqual(None, remotePeer.cbackCommand)
+ self.failUnlessEqual(False, remotePeer.managed)
+ self.failUnlessEqual(None, remotePeer.managedActions)
def testConstructor_002(self):
"""
Test constructor with all values filled in, with valid values.
"""
- remotePeer = RemotePeer("myname", "/stuff", "backup", "scp -1 -B")
+ remotePeer = RemotePeer("myname", "/stuff", "backup", "scp -1 -B", "ssh", "cback", True, [ "collect", ])
self.failUnlessEqual("myname", remotePeer.name)
self.failUnlessEqual("/stuff", remotePeer.collectDir)
self.failUnlessEqual("backup", remotePeer.remoteUser)
self.failUnlessEqual("scp -1 -B", remotePeer.rcpCommand)
+ self.failUnlessEqual("ssh", remotePeer.rshCommand)
+ self.failUnlessEqual("cback", remotePeer.cbackCommand)
+ self.failUnlessEqual(True, remotePeer.managed)
+ self.failUnlessEqual(["collect", ], remotePeer.managedActions)
def testConstructor_003(self):
"""
@@ -3441,7 +3449,132 @@
self.failUnlessAssignRaises(ValueError, remotePeer, "rcpCommand", "")
self.failUnlessEqual(None, remotePeer.rcpCommand)
+ def testConstructor_016(self):
+ """
+ Test assignment of rshCommand attribute, valid value.
+ """
+ remotePeer = RemotePeer()
+ self.failUnlessEqual(None, remotePeer.rshCommand)
+ remotePeer.rshCommand = "scp"
+ self.failUnlessEqual("scp", remotePeer.rshCommand)
+ def testConstructor_017(self):
+ """
+ Test assignment of rshCommand attribute, invalid value (empty).
+ """
+ remotePeer = RemotePeer()
+ self.failUnlessEqual(None, remotePeer.rshCommand)
+ self.failUnlessAssignRaises(ValueError, remotePeer, "rshCommand", "")
+ self.failUnlessEqual(None, remotePeer.rshCommand)
+
+ def testConstructor_018(self):
+ """
+ Test assignment of cbackCommand attribute, valid value.
+ """
+ remotePeer = RemotePeer()
+ self.failUnlessEqual(None, remotePeer.cbackCommand)
+ remotePeer.cbackCommand = "scp"
+ self.failUnlessEqual("scp", remotePeer.cbackCommand)
+
+ def testConstructor_019(self):
+ """
+ Test assignment of cbackCommand attribute, invalid value (empty).
+ """
+ remotePeer = RemotePeer()
+ self.failUnlessEqual(None, remotePeer.cbackCommand)
+ self.failUnlessAssignRaises(ValueError, remotePeer, "cbackCommand", "")
+ self.failUnlessEqual(None, remotePeer.cbackCommand)
+
+ def testConstructor_021(self):
+ """
+ Test assignment of managed attribute, None value.
+ """
+ remotePeer = RemotePeer(managed=True)
+ self.failUnlessEqual(True, remotePeer.managed)
+ remotePeer.managed = None
+ self.failUnlessEqual(False, remotePeer.managed)
+
+ def testConstructor_022(self):
+ """
+ Test assignment of managed attribute, valid value (real boolean).
+ """
+ remotePeer = RemotePeer()
+ self.failUnlessEqual(False, remotePeer.managed)
+ remotePeer.managed = True
+ self.failUnlessEqual(True, remotePeer.managed)
+ remotePeer.managed = False
+ self.failUnlessEqual(False, remotePeer.managed)
+
+ def testConstructor_023(self):
+ """
+ Test assignment of managed attribute, valid value (expression).
+ """
+ remotePeer = RemotePeer()
+ self.failUnlessEqual(False, remotePeer.managed)
+ remotePeer.managed = 0
+ self.failUnlessEqual(False, remotePeer.managed)
+ remotePeer.managed = []
+ self.failUnlessEqual(False, remotePeer.managed)
+ remotePeer.managed = None
+ self.failUnlessEqual(False, remotePeer.managed)
+ remotePeer.managed = ['a']
+ self.failUnlessEqual(True, remotePeer.managed)
+ remotePeer.managed = 3
+ self.failUnlessEqual(True, remotePeer.managed)
+
+ def testConstructor_024(self):
+ """
+ Test assignment of managedActions attribute, None value.
+ """
+ remotePeer = RemotePeer()
+ self.failUnlessEqual(None, remotePeer.managedActions)
+ remotePeer.managedActions = None
+ self.failUnlessEqual(None, remotePeer.managedActions)
+
+ def testConstructor_025(self):
+ """
+ Test assignment of managedActions attribute, empty list.
+ """
+ remotePeer = RemotePeer()
+ self.failUnlessEqual(None, remotePeer.managedActions)
+ remotePeer.managedActions = []
+ self.failUnlessEqual([], remotePeer.managedActions)
+
+ def testConstructor_026(self):
+ """
+ Test assignment of managedActions attribute, non-empty list, valid values.
+ """
+ remotePeer = RemotePeer()
+ self.failUnlessEqual(None, remotePeer.managedActions)
+ remotePeer.managedActions = ['a', 'b', ]
+ self.failUnlessEqual(['a', 'b'], remotePeer.managedActions)
+
+ def testConstructor_027(self):
+ """
+ Test assignment of managedActions attribute, non-empty list, invalid value.
+ """
+ remotePeer = RemotePeer()
+ self.failUnlessEqual(None, remotePeer.managedActions)
+ self.failUnlessAssignRaises(ValueError, remotePeer, "managedActions", ["KEN", ])
+ self.failUnlessEqual(None, remotePeer.managedActions)
+ self.failUnlessAssignRaises(ValueError, remotePeer, "managedActions", ["hello, world" ])
+ self.failUnlessEqual(None, remotePeer.managedActions)
+ self.failUnlessAssignRaises(ValueError, remotePeer, "managedActions", ["dash-word", ])
+ self.failUnlessEqual(None, remotePeer.managedActions)
+ self.failUnlessAssignRaises(ValueError, remotePeer, "managedActions", ["", ])
+ self.failUnlessEqual(None, remotePeer.managedActions)
+ self.failUnlessAssignRaises(ValueError, remotePeer, "managedActions", [None, ])
+ self.failUnlessEqual(None, remotePeer.managedActions)
+
+ def testConstructor_028(self):
+ """
+ Test assignment of managedActions attribute, non-empty list, mixed values.
+ """
+ remotePeer = RemotePeer()
+ self.failUnlessEqual(None, remotePeer.managedActions)
+ self.failUnlessAssignRaises(ValueError, remotePeer, "managedActions", ["ken", "dash-word", ])
+
+
############################
# Test comparison operators
############################
@@ -3464,8 +3597,8 @@
"""
Test comparison of two identical objects, all attributes non-None.
"""
- remotePeer1 = RemotePeer("name", "/etc/stuff/tmp/X11", "backup", "scp -1 -B")
- remotePeer2 = RemotePeer("name", "/etc/stuff/tmp/X11", "backup", "scp -1 -B")
+ remotePeer1 = RemotePeer("name", "/etc/stuff/tmp/X11", "backup", "scp -1 -B", "ssh", "cback", True, [ "collect", ])
+ remotePeer2 = RemotePeer("name", "/etc/stuff/tmp/X11", "backup", "scp -1 -B", "ssh", "cback", True, [ "collect", ])
self.failUnless(remotePeer1 == remotePeer2)
self.failUnless(not remotePeer1 < remotePeer2)
self.failUnless(remotePeer1 <= remotePeer2)
@@ -3491,8 +3624,8 @@
"""
Test comparison of two differing objects, name differs.
"""
- remotePeer1 = RemotePeer("name1", "/etc/stuff/tmp/X11", "backup", "scp -1 -B")
- remotePeer2 = RemotePeer("name2", "/etc/stuff/tmp/X11", "backup", "scp -1 -B")
+ remotePeer1 = RemotePeer("name1", "/etc/stuff/tmp/X11", "backup", "scp -1 -B", "ssh", "cback", True, [ "collect", ])
+ remotePeer2 = RemotePeer("name2", "/etc/stuff/tmp/X11", "backup", "scp -1 -B", "ssh", "cback", True, [ "collect", ])
self.failIfEqual(remotePeer1, remotePeer2)
self.failUnless(not remotePeer1 == remotePeer2)
self.failUnless(remotePeer1 < remotePeer2)
@@ -3519,8 +3652,8 @@
"""
Test comparison of two differing objects, collectDir differs.
"""
- remotePeer1 = RemotePeer("name", "/etc", "backup", "scp -1 -B")
- remotePeer2 = RemotePeer("name", "/etc/stuff/tmp/X11", "backup", "scp -1 -B")
+ remotePeer1 = RemotePeer("name", "/etc", "backup", "scp -1 -B", "ssh", "cback", True, [ "collect", ])
+ remotePeer2 = RemotePeer("name", "/etc/stuff/tmp/X11", "backup", "scp -1 -B", "ssh", "cback", True, [ "collect", ])
self.failIfEqual(remotePeer1, remotePeer2)
self.failUnless(not remotePeer1 == remotePeer2)
self.failUnless(remotePeer1 < remotePeer2)
@@ -3547,8 +3680,8 @@
"""
Test comparison of two differing objects, remoteUser differs.
"""
- remotePeer1 = RemotePeer("name", "/etc/stuff/tmp/X11", "spot", "scp -1 -B")
- remotePeer2 = RemotePeer("name", "/etc/stuff/tmp/X11", "backup", "scp -1 -B")
+ remotePeer1 = RemotePeer("name", "/etc/stuff/tmp/X11", "spot", "scp -1 -B", "ssh", "cback", True, [ "collect", ])
+ remotePeer2 = RemotePeer("name", "/etc/stuff/tmp/X11", "backup", "scp -1 -B", "ssh", "cback", True, [ "collect", ])
self.failIfEqual(remotePeer1, remotePeer2)
self.failUnless(not remotePeer1 == remotePeer2)
self.failUnless(not remotePeer1 < remotePeer2)
@@ -3575,8 +3708,8 @@
"""
Test comparison of two differing objects, rcpCommand differs.
"""
- remotePeer1 = RemotePeer("name", "/etc/stuff/tmp/X11", "backup", "scp -2 -B")
- remotePeer2 = RemotePeer("name", "/etc/stuff/tmp/X11", "backup", "scp -1 -B")
+ remotePeer1 = RemotePeer("name", "/etc/stuff/tmp/X11", "backup", "scp -2 -B", "ssh", "cback", True, [ "collect", ])
+ remotePeer2 = RemotePeer("name", "/etc/st...
[truncated message content] |
|
From: <pro...@us...> - 2007-12-16 16:12:48
|
Revision: 812
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=812&view=rev
Author: pronovic
Date: 2007-12-16 08:12:43 -0800 (Sun, 16 Dec 2007)
Log Message:
-----------
Add the --managed and --managed-only options
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/cli.py
cedar-backup2/trunk/test/clitests.py
Modified: cedar-backup2/trunk/CedarBackup2/cli.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/cli.py 2007-12-15 23:10:39 UTC (rev 811)
+++ cedar-backup2/trunk/CedarBackup2/cli.py 2007-12-16 16:12:43 UTC (rev 812)
@@ -128,10 +128,11 @@
COMBINE_ACTIONS = [ "collect", "stage", "store", "purge", ]
NONCOMBINE_ACTIONS = [ "rebuild", "validate", "initialize", "all", ]
-SHORT_SWITCHES = "hVbqc:fl:o:m:Ods"
+SHORT_SWITCHES = "hVbqc:fMNl:o:m:Ods"
LONG_SWITCHES = [ 'help', 'version', 'verbose', 'quiet',
- 'config=', 'full', 'logfile=', 'owner=',
- 'mode=', 'output', 'debug', 'stack', ]
+ 'config=', 'full', 'managed', 'managed-only',
+ 'logfile=', 'owner=', 'mode=',
+ 'output', 'debug', 'stack', ]
#######################################################################
@@ -689,29 +690,31 @@
fd.write("\n")
fd.write(" The following switches are accepted:\n")
fd.write("\n")
- fd.write(" -h, --help Display this usage/help listing\n")
- fd.write(" -V, --version Display version information\n")
- fd.write(" -b, --verbose Print verbose output as well as logging to disk\n")
- fd.write(" -q, --quiet Run quietly (display no output to the screen)\n")
- fd.write(" -c, --config Path to config file (default: %s)\n" % DEFAULT_CONFIG)
- fd.write(" -f, --full Perform a full backup, regardless of configuration\n")
- fd.write(" -l, --logfile Path to logfile (default: %s)\n" % DEFAULT_LOGFILE)
- fd.write(" -o, --owner Logfile ownership, user:group (default: %s:%s)\n" % (DEFAULT_OWNERSHIP[0], DEFAULT_OWNERSHIP[1]))
- fd.write(" -m, --mode Octal logfile permissions mode (default: %o)\n" % DEFAULT_MODE)
- fd.write(" -O, --output Record some sub-command (i.e. cdrecord) output to the log\n")
- fd.write(" -d, --debug Write debugging information to the log (implies --output)\n")
- fd.write(" -s, --stack Dump a Python stack trace instead of swallowing exceptions\n")
+ fd.write(" -h, --help Display this usage/help listing\n")
+ fd.write(" -V, --version Display version information\n")
+ fd.write(" -b, --verbose Print verbose output as well as logging to disk\n")
+ fd.write(" -q, --quiet Run quietly (display no output to the screen)\n")
+ fd.write(" -c, --config Path to config file (default: %s)\n" % DEFAULT_CONFIG)
+ fd.write(" -f, --full Perform a full backup, regardless of configuration\n")
+ fd.write(" -M, --managed Include managed peers when executing actions\n")
+ fd.write(" -N, --managed-only Include ONLY managed peers when executing actions\n")
+ fd.write(" -l, --logfile Path to logfile (default: %s)\n" % DEFAULT_LOGFILE)
+ fd.write(" -o, --owner Logfile ownership, user:group (default: %s:%s)\n" % (DEFAULT_OWNERSHIP[0], DEFAULT_OWNERSHIP[1]))
+ fd.write(" -m, --mode Octal logfile permissions mode (default: %o)\n" % DEFAULT_MODE)
+ fd.write(" -O, --output Record some sub-command (i.e. cdrecord) output to the log\n")
+ fd.write(" -d, --debug Write debugging information to the log (implies --output)\n")
+ fd.write(" -s, --stack Dump a Python stack trace instead of swallowing exceptions\n") # exactly 80 characters in width!
fd.write("\n")
fd.write(" The following actions may be specified:\n")
fd.write("\n")
- fd.write(" all Take all normal actions (collect, stage, store, purge)\n")
- fd.write(" collect Take the collect action\n")
- fd.write(" stage Take the stage action\n")
- fd.write(" store Take the store action\n")
- fd.write(" purge Take the purge action\n")
- fd.write(" rebuild Rebuild \"this week's\" disc if possible\n")
- fd.write(" validate Validate configuration only\n")
- fd.write(" initialize Initialize media for use with Cedar Backup\n")
+ fd.write(" all Take all normal actions (collect, stage, store, purge)\n")
+ fd.write(" collect Take the collect action\n")
+ fd.write(" stage Take the stage action\n")
+ fd.write(" store Take the store action\n")
+ fd.write(" purge Take the purge action\n")
+ fd.write(" rebuild Rebuild \"this week's\" disc if possible\n")
+ fd.write(" validate Validate configuration only\n")
+ fd.write(" initialize Initialize media for use with Cedar Backup\n")
fd.write("\n")
fd.write(" You may also specify extended actions that have been defined in\n")
fd.write(" configuration.\n")
@@ -1035,6 +1038,8 @@
self._quiet = False
self._config = None
self._full = False
+ self._managed = False
+ self._managedOnly = False
self._logfile = None
self._owner = None
self._mode = None
@@ -1113,6 +1118,16 @@
return -1
else:
return 1
+ if self._managed != other._managed:
+ if self._managed < other._managed:
+ return -1
+ else:
+ return 1
+ if self._managedOnly != other._managedOnly:
+ if self._managedOnly < other._managedOnly:
+ return -1
+ else:
+ return 1
if self._logfile != other._logfile:
if self._logfile < other._logfile:
return -1
@@ -1250,6 +1265,38 @@
"""
return self._full
+ def _setManaged(self, value):
+ """
+ Property target used to set the managed flag.
+ No validations, but we normalize the value to C{True} or C{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 _setManagedOnly(self, value):
+ """
+ Property target used to set the managedOnly flag.
+ No validations, but we normalize the value to C{True} or C{False}.
+ """
+ if value:
+ self._managedOnly = True
+ else:
+ self._managedOnly = False
+
+ def _getManagedOnly(self):
+ """
+ Property target used to get the managedOnly flag.
+ """
+ return self._managedOnly
+
def _setLogfile(self, value):
"""
Property target used to set the logfile parameter.
@@ -1393,6 +1440,8 @@
quiet = property(_getQuiet, _setQuiet, None, "Command-line quiet (C{-q,--quiet}) flag.")
config = property(_getConfig, _setConfig, None, "Command-line configuration file (C{-c,--config}) parameter.")
full = property(_getFull, _setFull, None, "Command-line full-backup (C{-f,--full}) flag.")
+ managed = property(_getManaged, _setManaged, None, "Command-line managed (C{-M,--managed}) flag.")
+ managedOnly = property(_getManagedOnly, _setManagedOnly, None, "Command-line managed-only (C{-N,--managed-only}) flag.")
logfile = property(_getLogfile, _setLogfile, None, "Command-line logfile (C{-l,--logfile}) parameter.")
owner = property(_getOwner, _setOwner, None, "Command-line owner (C{-o,--owner}) parameter, as tuple C{(user,group)}.")
mode = property(_getMode, _setMode, None, "Command-line mode (C{-m,--mode}) parameter.")
@@ -1423,6 +1472,8 @@
if not self.help and not self.version:
if self.actions is None or len(self.actions) == 0:
raise ValueError("At least one action must be specified.")
+ if self.managed and self.managedOnly:
+ raise ValueError("The --managed and --managed-only options may not be combined.")
def buildArgumentList(self, validate=True):
"""
@@ -1467,6 +1518,10 @@
argumentList.append(self.config)
if self.full:
argumentList.append("--full")
+ if self.managed:
+ argumentList.append("--managed")
+ if self.managedOnly:
+ argumentList.append("--managed-only")
if self.logfile is not None:
argumentList.append("--logfile")
argumentList.append(self.logfile)
@@ -1529,6 +1584,10 @@
argumentString += "--config \"%s\" " % self.config
if self.full:
argumentString += "--full "
+ if self.managed:
+ argumentString += "--managed "
+ if self.managedOnly:
+ argumentString += "--managed-only "
if self.logfile is not None:
argumentString += "--logfile \"%s\" " % self.logfile
if self.owner is not None:
@@ -1585,6 +1644,10 @@
self.config = switches["--config"]
if switches.has_key("-f") or switches.has_key("--full"):
self.full = True
+ if switches.has_key("-M") or switches.has_key("--managed"):
+ self.managed = True
+ if switches.has_key("-N") or switches.has_key("--managed-only"):
+ self.managedOnly = True
if switches.has_key("-l"):
self.logfile = switches["-l"]
if switches.has_key("--logfile"):
Modified: cedar-backup2/trunk/test/clitests.py
===================================================================
--- cedar-backup2/trunk/test/clitests.py 2007-12-15 23:10:39 UTC (rev 811)
+++ cedar-backup2/trunk/test/clitests.py 2007-12-16 16:12:43 UTC (rev 812)
@@ -186,6 +186,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -205,6 +207,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -224,6 +228,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -243,6 +249,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -262,6 +270,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -281,6 +291,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -300,6 +312,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -319,6 +333,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -338,6 +354,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -357,6 +375,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -376,6 +396,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -395,6 +417,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -414,6 +438,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -433,6 +459,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -452,6 +480,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -471,6 +501,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -490,6 +522,8 @@
self.failUnlessEqual(True, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -509,6 +543,8 @@
self.failUnlessEqual(True, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -528,6 +564,8 @@
self.failUnlessEqual(True, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -547,6 +585,8 @@
self.failUnlessEqual(True, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -590,6 +630,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual("something", options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -609,6 +651,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual("something", options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -628,6 +672,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual("something", options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -647,6 +693,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual("something", options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -666,6 +714,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(True, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -686,6 +736,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(True, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -705,6 +757,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(True, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -724,6 +778,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(True, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -767,6 +823,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual("something", options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -786,6 +844,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual("something", options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -805,6 +865,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual("something", options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -824,6 +886,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual("something", options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -891,6 +955,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(("a", "b"), options.owner)
self.failUnlessEqual(None, options.mode)
@@ -910,6 +976,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(("a", "b"), options.owner)
self.failUnlessEqual(None, options.mode)
@@ -929,6 +997,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(("a", "b"), options.owner)
self.failUnlessEqual(None, options.mode)
@@ -948,6 +1018,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(("a", "b"), options.owner)
self.failUnlessEqual(None, options.mode)
@@ -1015,6 +1087,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(0631, options.mode)
@@ -1034,6 +1108,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(0631, options.mode)
@@ -1053,6 +1129,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(0631, options.mode)
@@ -1072,6 +1150,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(0631, options.mode)
@@ -1091,6 +1171,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -1110,6 +1192,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -1129,6 +1213,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -1148,6 +1234,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -1167,6 +1255,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -1186,6 +1276,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -1205,6 +1297,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -1224,6 +1318,8 @@
self.failUnlessEqual(False, options.quiet)
self.failUnlessEqual(None, options.config)
self.failUnlessEqual(False, options.full)
+ self.failUnlessEqual(False, options.managed)
+ self.failUnlessEqual(False, options.managedOnly)
self.failUnlessEqual(None, options.logfile)
self.failUnlessEqual(None, options.owner)
self.failUnlessEqual(None, options.mode)
@@ -1243,6 +1339,8 @@
self.failUnlessEqual(False, options.quiet)
self.failU...
[truncated message content] |
|
From: <pro...@us...> - 2007-12-16 16:55:29
|
Revision: 813
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=813&view=rev
Author: pronovic
Date: 2007-12-16 08:55:27 -0800 (Sun, 16 Dec 2007)
Log Message:
-----------
Require cback command and managed action list for managed peers
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/config.py
cedar-backup2/trunk/test/configtests.py
Modified: cedar-backup2/trunk/CedarBackup2/config.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/config.py 2007-12-16 16:12:43 UTC (rev 812)
+++ cedar-backup2/trunk/CedarBackup2/config.py 2007-12-16 16:55:27 UTC (rev 813)
@@ -5664,13 +5664,18 @@
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: # redundant
+ 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: # redundant
+ 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: # redundant
+ 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.")
Config._checkUnique("Duplicate peer names exist:", names)
Modified: cedar-backup2/trunk/test/configtests.py
===================================================================
--- cedar-backup2/trunk/test/configtests.py 2007-12-16 16:12:43 UTC (rev 812)
+++ cedar-backup2/trunk/test/configtests.py 2007-12-16 16:55:27 UTC (rev 813)
@@ -10263,7 +10263,104 @@
config._validatePeers()
self.failUnlessRaises(ValueError, config._validateStage)
+ def testValidate_073(self):
+ """
+ Confirm that remote peer is required to have backup user if not set in options.
+ """
+ config = Config()
+ config.options = OptionsConfig(backupUser="ken", rcpCommand="rcp", rshCommand="rsh", cbackCommand="cback", managedActions=["collect"], )
+ config.peers = PeersConfig()
+ config.peers.localPeers = []
+ config.peers.remotePeers = [ RemotePeer(name="remote", collectDir="/path"), ]
+ config._validatePeers()
+ config.options.backupUser = None
+ self.failUnlessRaises(ValueError, config._validatePeers)
+
+ config.peers.remotePeers[0].remoteUser = "ken"
+ config._validatePeers()
+
+ def testValidate_074(self):
+ """
+ Confirm that remote peer is required to have rcp command if not set in options.
+ """
+ config = Config()
+ config.options = OptionsConfig(backupUser="ken", rcpCommand="rcp", rshCommand="rsh", cbackCommand="cback", managedActions=["collect"], )
+ config.peers = PeersConfig()
+ config.peers.localPeers = []
+ config.peers.remotePeers = [ RemotePeer(name="remote", collectDir="/path"), ]
+ config._validatePeers()
+
+ config.options.rcpCommand = None
+ self.failUnlessRaises(ValueError, config._validatePeers)
+
+ config.peers.remotePeers[0].rcpCommand = "rcp"
+ config._validatePeers()
+
+ def testValidate_075(self):
+ """
+ Confirm that remote managed peer is required to have rsh command if not set in options.
+ """
+ config = Config()
+ config.options = OptionsConfig(backupUser="ken", rcpCommand="rcp", rshCommand="rsh", cbackCommand="cback", managedActions=["collect"], )
+ config.peers = PeersConfig()
+ config.peers.localPeers = []
+ config.peers.remotePeers = [ RemotePeer(name="remote", collectDir="/path"), ]
+ config._validatePeers()
+
+ config.options.rshCommand = None
+ config._validatePeers()
+
+ config.peers.remotePeers[0].managed = True
+ self.failUnlessRaises(ValueError, config._validatePeers)
+
+ config.peers.remotePeers[0].rshCommand = "rsh"
+ config._validatePeers()
+
+ def testValidate_076(self):
+ """
+ Confirm that remote managed peer is required to have cback command if not set in options.
+ """
+ config = Config()
+ config.options = OptionsConfig(backupUser="ken", rcpCommand="rcp", rshCommand="rsh", cbackCommand="cback", managedActions=["collect"], )
+ config.peers = PeersConfig()
+ config.peers.localPeers = []
+ config.peers.remotePeers = [ RemotePeer(name="remote", collectDir="/path"), ]
+ config._validatePeers()
+
+ config.options.cbackCommand = None
+ config._validatePeers()
+
+ config.peers.remotePeers[0].managed = True
+ self.failUnlessRaises(ValueError, config._validatePeers)
+
+ config.peers.remotePeers[0].cbackCommand = "cback"
+ config._validatePeers()
+
+ def testValidate_077(self):
+ """
+ Confirm that remote managed peer is required to have managed actions list if not set in options.
+ """
+ config = Config()
+ config.options = OptionsConfig(backupUser="ken", rcpCommand="rcp", rshCommand="rsh", cbackCommand="cback", managedActions=["collect"], )
+ config.peers = PeersConfig()
+ config.peers.localPeers = []
+ config.peers.remotePeers = [ RemotePeer(name="remote", collectDir="/path"), ]
+ config._validatePeers()
+
+ config.options.managedActions = None
+ config._validatePeers()
+
+ config.peers.remotePeers[0].managed = True
+ self.failUnlessRaises(ValueError, config._validatePeers)
+
+ config.options.managedActions = []
+ self.failUnlessRaises(ValueError, config._validatePeers)
+
+ config.peers.remotePeers[0].managedActions = ["collect", ]
+ config._validatePeers()
+
+
############################
# Test parsing of documents
############################
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2007-12-16 17:50:30
|
Revision: 814
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=814&view=rev
Author: pronovic
Date: 2007-12-16 09:50:28 -0800 (Sun, 16 Dec 2007)
Log Message:
-----------
Document new --managed and --managed-only command-line options
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/cli.py
cedar-backup2/trunk/doc/cback.1
cedar-backup2/trunk/manual/src/commandline.xml
Modified: cedar-backup2/trunk/CedarBackup2/cli.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/cli.py 2007-12-16 16:55:27 UTC (rev 813)
+++ cedar-backup2/trunk/CedarBackup2/cli.py 2007-12-16 17:50:28 UTC (rev 814)
@@ -696,8 +696,8 @@
fd.write(" -q, --quiet Run quietly (display no output to the screen)\n")
fd.write(" -c, --config Path to config file (default: %s)\n" % DEFAULT_CONFIG)
fd.write(" -f, --full Perform a full backup, regardless of configuration\n")
- fd.write(" -M, --managed Include managed peers when executing actions\n")
- fd.write(" -N, --managed-only Include ONLY managed peers when executing actions\n")
+ fd.write(" -M, --managed Include managed clients when executing actions\n")
+ fd.write(" -N, --managed-only Include ONLY managed clients when executing actions\n")
fd.write(" -l, --logfile Path to logfile (default: %s)\n" % DEFAULT_LOGFILE)
fd.write(" -o, --owner Logfile ownership, user:group (default: %s:%s)\n" % (DEFAULT_OWNERSHIP[0], DEFAULT_OWNERSHIP[1]))
fd.write(" -m, --mode Octal logfile permissions mode (default: %o)\n" % DEFAULT_MODE)
Modified: cedar-backup2/trunk/doc/cback.1
===================================================================
--- cedar-backup2/trunk/doc/cback.1 2007-12-16 16:55:27 UTC (rev 813)
+++ cedar-backup2/trunk/doc/cback.1 2007-12-16 17:50:28 UTC (rev 814)
@@ -15,9 +15,9 @@
.\" #
.\" # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
.\"
-.TH cback "1" "July 2005" "Cedar Backup" "Kenneth J. Pronovici"
+.TH cback "1" "December 2007" "Cedar Backup" "Kenneth J. Pronovici"
.SH NAME
-cback \- Local and remote backups to CD-R/CD-RW media under Linux
+cback \- Local and remote backups to CD-R/CD-RW media
.SH SYNOPSIS
.B cback
[\fIswitches\fR]
@@ -47,6 +47,12 @@
actions, but only the master executes the stage and store actions. The
configuration file \fI/etc/cback.conf\fR controls the actions taken during
collect, stage, store and purge actions.
+.PP
+Cedar Backup also supports the concept of \fImanaged clients\fR. Managed
+clients have their entire backup process managed by the master via a remote
+shell. The same actions are run as part of the backup process, but the master
+controls when the actions are executed on the clients rather than the clients
+controlling it for themselves.
.SH SWITCHES
.TP
\fB\-h\fR, \fB\-\-help\fR
@@ -73,6 +79,16 @@
ignored and rewritten; for the store action, this means that a new disc will be
started.
.TP
+\fB\-M\fR, \fB\-\-managed\fR
+Include managed clients when executing actions. If the action being executed
+is listed as a managed action for a managed client, execute the action on that
+client after executing the action locally.
+.TP
+\fB\-N\fR, \fB\-\-managed-only\fR
+Include only managed clients when executing actions. If the action being
+executed is listed as a managed action for a managed client, execute the action
+on that client -- but do not execute the action locally.
+.TP
\fB\-l\fR, \fB\-\-logfile\fR
Specify the path to an alternate logfile. The default logfile file is
/var/log/cback.log.
@@ -164,10 +180,9 @@
Error executing specified backup actions.
.SH NOTES
.PP
-The script is designed to run as root, since otherwise it's difficult to
-back up system directories or write the CD or DVD device. However, pains
-are taken to use a backup user's effective user id (specified in
-configuration) when appropriate.
+The script is designed to run as root, since otherwise it's difficult to back
+up system directories or write the CD or DVD device. However, pains are taken
+to switch to a backup user (specified in configuration) when appropriate.
.PP
To use the script, you must specify at least one action to take. More than one
of the "collect", "stage", "store" or "purge" actions may be specified, in any
@@ -184,12 +199,13 @@
.PP
Note that there is no facility for restoring backups. It is assumed that the
user can deal with copying tarfiles off disc and using them to restore missing
-files as needed.
+files as needed. The user manual provides detailed intructions in Appendix C.
.PP
Finally, you should be aware that backups to CD or DVD can probably be read
by any user which has permissions to mount the CD or DVD drive. If you
intend to leave the backup disc in the drive at all times, you may want to
-consider this when setting up device permissions on your machine.
+consider this when setting up device permissions on your machine. You might
+also want to investigate the encrypt extension.
.SH FILES
.TP
\fI/etc/cback.conf\fR - Default configuration file
Modified: cedar-backup2/trunk/manual/src/commandline.xml
===================================================================
--- cedar-backup2/trunk/manual/src/commandline.xml 2007-12-16 16:55:27 UTC (rev 813)
+++ cedar-backup2/trunk/manual/src/commandline.xml 2007-12-16 17:50:28 UTC (rev 814)
@@ -95,29 +95,31 @@
The following switches are accepted:
- -h, --help Display this usage/help listing
- -V, --version Display version information
- -b, --verbose Print verbose output as well as logging to disk
- -q, --quiet Run quietly (display no output to the screen)
- -c, --config Path to config file (default: /etc/cback.conf)
- -f, --full Perform a full backup, regardless of configuration
- -l, --logfile Path to logfile (default: /var/log/cback.log)
- -o, --owner Logfile ownership, user:group (default: root:adm)
- -m, --mode Octal logfile permissions mode (default: 640)
- -O, --output Record some sub-command (i.e. cdrecord) output to the log
- -d, --debug Write debugging information to the log (implies --output)
- -s, --stack Dump a Python stack trace instead of swallowing exceptions
+ -h, --help Display this usage/help listing
+ -V, --version Display version information
+ -b, --verbose Print verbose output as well as logging to disk
+ -q, --quiet Run quietly (display no output to the screen)
+ -c, --config Path to config file (default: /etc/cback.conf)
+ -f, --full Perform a full backup, regardless of configuration
+ -M, --managed Include managed clients when executing actions
+ -N, --managed-only Include ONLY managed clients when executing actions
+ -l, --logfile Path to logfile (default: /var/log/cback.log)
+ -o, --owner Logfile ownership, user:group (default: root:adm)
+ -m, --mode Octal logfile permissions mode (default: 640)
+ -O, --output Record some sub-command (i.e. cdrecord) output to the log
+ -d, --debug Write debugging information to the log (implies --output)
+ -s, --stack Dump a Python stack trace instead of swallowing exceptions
The following actions may be specified:
- all Take all normal actions (collect, stage, store, purge)
- collect Take the collect action
- stage Take the stage action
- store Take the store action
- purge Take the purge action
- rebuild Rebuild "this week's" disc if possible
- validate Validate configuration only
- initialize Initialize media for use with Cedar Backup
+ all Take all normal actions (collect, stage, store, purge)
+ collect Take the collect action
+ stage Take the stage action
+ store Take the store action
+ purge Take the purge action
+ rebuild Rebuild "this week's" disc if possible
+ validate Validate configuration only
+ initialize Initialize media for use with Cedar Backup
You may also specify extended actions that have been defined in
configuration.
@@ -208,6 +210,31 @@
</varlistentry>
<varlistentry>
+ <term><option>-M</option>, <option>--managed</option></term>
+ <listitem>
+ <para>
+ Include managed clients when executing actions. If the
+ action being executed is listed as a managed action for a
+ managed client, execute the action on that client after
+ executing the action locally.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-N</option>, <option>--managed-only</option></term>
+ <listitem>
+ <para>
+ Include <emphasis>only</emphasis> managed clients when
+ executing actions. If the action being executed is listed
+ as a managed action for a managed client, execute the action
+ on that client — but <emphasis>do not</emphasis>
+ execute the action locally.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>-l</option>, <option>--logfile</option></term>
<listitem>
<para>
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2007-12-18 02:42:11
|
Revision: 818
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=818&view=rev
Author: pronovic
Date: 2007-12-17 18:42:07 -0800 (Mon, 17 Dec 2007)
Log Message:
-----------
Implement executeRemoteCommand() and executeManagedAction
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/peer.py
cedar-backup2/trunk/test/peertests.py
Modified: cedar-backup2/trunk/CedarBackup2/peer.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/peer.py 2007-12-16 18:43:47 UTC (rev 817)
+++ cedar-backup2/trunk/CedarBackup2/peer.py 2007-12-18 02:42:07 UTC (rev 818)
@@ -8,7 +8,7 @@
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
-# Copyright (c) 2004-2006 Kenneth J. Pronovici.
+# Copyright (c) 2004-2007 Kenneth J. Pronovici.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
@@ -72,6 +72,9 @@
logger = logging.getLogger("CedarBackup2.log.peer")
DEF_RCP_COMMAND = [ "/usr/bin/scp", "-B", "-q", "-C" ]
+DEF_RSH_COMMAND = [ "/usr/bin/ssh", ]
+DEF_CBACK_COMMAND = "/usr/bin/cback"
+
DEF_COLLECT_INDICATOR = "cback.collect"
DEF_STAGE_INDICATOR = "cback.stage"
@@ -424,23 +427,25 @@
interface shared with the C{LocalPeer} class.
@sort: __init__, stagePeer, checkCollectIndicator, writeStageIndicator,
- _getDirContents, _copyRemoteDir, _copyRemoteFile, _pushLocalFile,
- name, collectDir, remoteUser, rcpCommand
+ executeRemoteCommand, executeManagedAction, _getDirContents,
+ _copyRemoteDir, _copyRemoteFile, _pushLocalFile, name, collectDir,
+ remoteUser, rcpCommand, rshCommand, cbackCommand
"""
##############
# Constructor
##############
- def __init__(self, name, collectDir, workingDir, remoteUser, rcpCommand=None, localUser=None):
+ def __init__(self, name, collectDir=None, workingDir=None, remoteUser=None,
+ rcpCommand=None, localUser=None, rshCommand=None, cbackCommand=None):
"""
Initializes a remote backup peer.
- @note: If provided, the rcp command will eventually be parsed into a list
- of strings suitable for passing to C{util.executeCommand} in order to
- avoid security holes related to shell interpolation. This parsing will
- be done by the L{util.splitCommandLine} function. See the documentation
- for that function for some important notes about its limitations.
+ @note: If provided, each command will eventually be parsed into a list of
+ strings suitable for passing to C{util.executeCommand} in order to avoid
+ security holes related to shell interpolation. This parsing will be
+ done by the L{util.splitCommandLine} function. See the documentation for
+ that function for some important notes about its limitations.
@param name: Name of the backup peer
@type name: String, must be a valid DNS hostname
@@ -452,14 +457,20 @@
@type workingDir: String representing an absolute path on the current host.
@param remoteUser: Name of the Cedar Backup user on the remote peer
- @type remoteUser: String representing a username, valid via the copy command
+ @type remoteUser: String representing a username, valid via remote shell to the peer
+ @param localUser: Name of the Cedar Backup user on the current host
+ @type localUser: String representing a username, valid on the current host
+
@param rcpCommand: An rcp-compatible copy command to use for copying files from the peer
@type rcpCommand: String representing a system command including required arguments
- @param localUser: Name of the Cedar Backup user on the current host
- @type localUser: String representing a username, valid on the current host
+ @param rshCommand: An rsh-compatible copy command to use for remote shells to the peer
+ @type rshCommand: String representing a system command including required arguments
+ @param cbackCommand: A chack-compatible command to use for executing managed actions
+ @type cbackCommand: String representing a system command including required arguments
+
@raise ValueError: If collect directory is not an absolute path
"""
self._name = None
@@ -469,12 +480,17 @@
self._localUser = None
self._rcpCommand = None
self._rcpCommandList = None
+ self._rshCommand = None
+ self._rshCommandList = None
+ self._cbackCommand = None
self.name = name
self.collectDir = collectDir
self.workingDir = workingDir
self.remoteUser = remoteUser
self.localUser = localUser
self.rcpCommand = rcpCommand
+ self.rshCommand = rshCommand
+ self.cbackCommand = cbackCommand
#############
@@ -505,8 +521,9 @@
@raise ValueError: If the value is C{None} or is not an absolute path.
@raise ValueError: If the value cannot be encoded properly.
"""
- if value is None or not os.path.isabs(value):
- raise ValueError("Collect directory must be an absolute path.")
+ 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):
@@ -522,8 +539,9 @@
@raise ValueError: If the value is C{None} or is not an absolute path.
@raise ValueError: If the value cannot be encoded properly.
"""
- if value is None or not os.path.isabs(value):
- raise ValueError("Working directory must be an absolute path.")
+ 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):
@@ -597,12 +615,70 @@
"""
return self._rcpCommand
+ def _setRshCommand(self, value):
+ """
+ Property target to set the rsh command.
+
+ The value must be a non-empty string or C{None}. Its value is stored in
+ the two forms: "raw" as provided by the client, and "parsed" into a list
+ suitable for being passed to L{util.executeCommand} via
+ L{util.splitCommandLine}.
+
+ However, all the caller will ever see via the property is the actual
+ value they set (which includes seeing C{None}, even if we translate that
+ internally to C{DEF_RSH_COMMAND}). Internally, we should always use
+ C{self._rshCommandList} if we want the actual command list.
+
+ @raise ValueError: If the value is an empty string.
+ """
+ if value is None:
+ self._rshCommand = None
+ self._rshCommandList = DEF_RSH_COMMAND
+ else:
+ if len(value) >= 1:
+ self._rshCommand = value
+ self._rshCommandList = splitCommandLine(self._rshCommand)
+ else:
+ raise ValueError("The rsh command must be a non-empty string.")
+
+ def _getRshCommand(self):
+ """
+ Property target used to get the rsh command.
+ """
+ return self._rshCommand
+
+ def _setCbackCommand(self, value):
+ """
+ Property target to set the cback command.
+
+ The value must be a non-empty string or C{None}. Unlike the other
+ command, this value is only stored in the "raw" form provided by the
+ client.
+
+ @raise ValueError: If the value is an empty string.
+ """
+ if value is None:
+ self._cbackCommand = None
+ else:
+ if len(value) >= 1:
+ self._cbackCommand = value
+ else:
+ raise ValueError("The cback command must be a non-empty string.")
+
+ def _getCbackCommand(self):
+ """
+ Property target used to get the cback command.
+ """
+ return self._cbackCommand
+
name = property(_getName, _setName, None, "Name of the peer (a valid DNS hostname).")
collectDir = property(_getCollectDir, _setCollectDir, None, "Path to the peer's collect directory (an absolute local path).")
workingDir = property(_getWorkingDir, _setWorkingDir, None, "Path to the peer's working directory (an absolute local path).")
remoteUser = property(_getRemoteUser, _setRemoteUser, None, "Name of the Cedar Backup user on the remote peer.")
localUser = property(_getLocalUser, _setLocalUser, None, "Name of the Cedar Backup user on the current host.")
rcpCommand = property(_getRcpCommand, _setRcpCommand, None, "An rcp-compatible copy command to use for copying files.")
+ rshCommand = property(_getRshCommand, _setRshCommand, None, "An rsh-compatible command to use for remote shells to the peer.")
+ cbackCommand = property(_getCbackCommand, _setCbackCommand, None, "A chack-compatible command to use for executing managed actions.")
#################
@@ -663,7 +739,6 @@
if count == 0:
raise IOError("Did not copy any files from local peer.")
return count
-
def checkCollectIndicator(self, collectIndicator=None):
"""
@@ -760,7 +835,33 @@
os.remove(sourceFile)
except: pass
+ def executeRemoteCommand(self, command):
+ """
+ Executes a command on the peer via remote shell.
+ @param command: Command to execute
+ @type command: String command-line suitable for use with rsh.
+
+ @raise IOError: If there is an error executing the command on the remote peer.
+ """
+ RemotePeer._executeRemoteCommand(self.remoteUser, self.localUser,
+ self.name, self._rshCommand,
+ self._rshCommandList, command)
+
+ def executeManagedAction(self, action, fullBackup):
+ """
+ Executes a managed action on this peer.
+
+ @param action: Name of the action to execute.
+ @param fullBackup: Whether a full backup should be executed.
+
+ @raise IOError: If there is an error executing the action on the remote peer.
+ """
+ log.debug("Executing managed action [%s] on peer [%s]." % (action, self.name))
+ command = RemotePeer._buildCbackCommand(self.cbackCommand, action, fullBackup)
+ self.executeRemoteCommand(command)
+
+
##################
# Private methods
##################
@@ -1036,3 +1137,67 @@
raise IOError("Error (%d) copying [%s] to remote host (using no local user)." % (result, sourceFile))
_pushLocalFile = staticmethod(_pushLocalFile)
+ def _executeRemoteCommand(remoteUser, localUser, remoteHost, rshCommand, rshCommandList, remoteCommand):
+ """
+ Executes a command on the peer via remote shell.
+
+ @param remoteUser: Name of the Cedar Backup user on the remote peer
+ @type remoteUser: String representing a username, valid on the remote host
+
+ @param localUser: Name of the Cedar Backup user on the current host
+ @type localUser: String representing a username, valid on the current host
+
+ @param remoteHost: Hostname of the remote peer
+ @type remoteHost: String representing a hostname, accessible via the copy command
+
+ @param rshCommand: An rsh-compatible copy command to use for remote shells to the peer
+ @type rshCommand: String representing a system command including required arguments
+
+ @param rshCommandList: An rsh-compatible copy command to use for remote shells to the peer
+ @type rshCommandList: Command as a list to be passed to L{util.executeCommand}
+
+ @param remoteCommand: The command to be executed on the remote host
+ @type remoteCommand: String command-line, with no special shell characters ($, <, etc.)
+
+ @raise IOError: If there is an error executing the remote command
+ """
+ if localUser is not None:
+ try:
+ if os.getuid() != 0:
+ raise IOError("Only root can remote shell as another user.")
+ except AttributeError: pass
+ command = resolveCommand(SU_COMMAND)
+ actualCommand = "%s %s@%s '%s'" % (rshCommand, remoteUser, remoteHost, remoteCommand)
+ result = executeCommand(command, [localUser, "-c", actualCommand])[0]
+ if result != 0:
+ raise IOError("Error (%d) executing command on remote host as local user [%s]." % (result, localUser))
+ else:
+ command = resolveCommand(rshCommandList)
+ result = executeCommand(command, ["%s@%s" % (remoteUser, remoteHost), "%s" % remoteCommand])[0]
+ if result != 0:
+ raise IOError("Error (%d) executing command on remote host (using no local user)." % result)
+ _executeRemoteCommand = staticmethod(_executeRemoteCommand)
+
+ def _buildCbackCommand(cbackCommand, action, fullBackup):
+ """
+ Builds a Cedar Backup command line for the named action.
+
+ @note: If the cback command is None, then DEF_CBACK_COMMAND is used.
+
+ @param cbackCommand: cback command to execute, including required options
+ @param action: Name of the action to execute.
+ @param fullBackup: Whether a full backup should be executed.
+
+ @return: String suitable for passing to L{_executeRemoteCommand} as remoteCommand.
+ @raise ValueError: If action is None.
+ """
+ if action is None:
+ raise ValueError("Action cannot be None.")
+ if cbackCommand is None:
+ cbackCommand = DEF_CBACK_COMMAND
+ if fullBackup:
+ return "%s --full %s" % (cbackCommand, action)
+ else:
+ return "%s %s" % (cbackCommand, action)
+ _buildCbackCommand = staticmethod(_buildCbackCommand)
+
Modified: cedar-backup2/trunk/test/peertests.py
===================================================================
--- cedar-backup2/trunk/test/peertests.py 2007-12-16 18:43:47 UTC (rev 817)
+++ cedar-backup2/trunk/test/peertests.py 2007-12-18 02:42:07 UTC (rev 818)
@@ -9,7 +9,7 @@
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
-# Copyright (c) 2004-2006 Kenneth J. Pronovici.
+# Copyright (c) 2004-2007 Kenneth J. Pronovici.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
@@ -102,7 +102,8 @@
from CedarBackup2.testutil import getMaskAsMode, getLogin, runningAsRoot
from CedarBackup2.testutil import platformSupportsPermissions, platformWindows
from CedarBackup2.peer import LocalPeer, RemotePeer
-from CedarBackup2.peer import DEF_RCP_COMMAND, DEF_COLLECT_INDICATOR, DEF_STAGE_INDICATOR
+from CedarBackup2.peer import DEF_RCP_COMMAND, DEF_RSH_COMMAND
+from CedarBackup2.peer import DEF_COLLECT_INDICATOR, DEF_STAGE_INDICATOR
#######################################################################
@@ -729,7 +730,10 @@
self.failUnlessEqual(remoteUser, peer.remoteUser)
self.failUnlessEqual(None, peer.localUser)
self.failUnlessEqual(None, peer.rcpCommand)
+ self.failUnlessEqual(None, peer.rshCommand)
+ self.failUnlessEqual(None, peer.cbackCommand)
self.failUnlessEqual(DEF_RCP_COMMAND, peer._rcpCommandList)
+ self.failUnlessEqual(DEF_RSH_COMMAND, peer._rshCommandList)
def testBasic_003(self):
"""
@@ -747,7 +751,10 @@
self.failUnlessEqual(remoteUser, peer.remoteUser)
self.failUnlessEqual(None, peer.localUser)
self.failUnlessEqual(None, peer.rcpCommand)
+ self.failUnlessEqual(None, peer.rshCommand)
+ self.failUnlessEqual(None, peer.cbackCommand)
self.failUnlessEqual(DEF_RCP_COMMAND, peer._rcpCommandList)
+ self.failUnlessEqual(DEF_RSH_COMMAND, peer._rshCommandList)
def testBasic_004(self):
"""
@@ -765,7 +772,10 @@
self.failUnlessEqual(remoteUser, peer.remoteUser)
self.failUnlessEqual(None, peer.localUser)
self.failUnlessEqual(rcpCommand, peer.rcpCommand)
+ self.failUnlessEqual(None, peer.rshCommand)
+ self.failUnlessEqual(None, peer.cbackCommand)
self.failUnlessEqual(["rcp", "-one", "--two", "three", "four five", "'six", "seven'", "eight", ], peer._rcpCommandList)
+ self.failUnlessEqual(DEF_RSH_COMMAND, peer._rshCommandList)
def testBasic_005(self):
"""
@@ -784,8 +794,46 @@
self.failUnlessEqual(localUser, peer.localUser)
self.failUnlessEqual(None, peer.rcpCommand)
self.failUnlessEqual(DEF_RCP_COMMAND, peer._rcpCommandList)
+ self.failUnlessEqual(DEF_RSH_COMMAND, peer._rshCommandList)
+ def testBasic_006(self):
+ """
+ Make sure attributes are set properly for valid constructor input, custom rsh command.
+ """
+ name = REMOTE_HOST
+ remoteUser = getLogin()
+ rshCommand = "rsh --whatever -something \"a b\" else"
+ peer = RemotePeer(name, remoteUser=remoteUser, rshCommand=rshCommand)
+ self.failUnlessEqual(name, peer.name)
+ self.failUnlessEqual(None, peer.collectDir)
+ self.failUnlessEqual(None, peer.workingDir)
+ self.failUnlessEqual(remoteUser, peer.remoteUser)
+ self.failUnlessEqual(None, peer.localUser)
+ self.failUnlessEqual(None, peer.rcpCommand)
+ self.failUnlessEqual(rshCommand, peer.rshCommand)
+ self.failUnlessEqual(None, peer.cbackCommand)
+ self.failUnlessEqual(DEF_RCP_COMMAND, peer._rcpCommandList)
+ self.failUnlessEqual(DEF_RCP_COMMAND, peer._rcpCommandList)
+ self.failUnlessEqual(["rsh", "--whatever", "-something", "a b", "else", ], peer._rshCommandList)
+ def testBasic_007(self):
+ """
+ Make sure attributes are set properly for valid constructor input, custom cback command.
+ """
+ name = REMOTE_HOST
+ remoteUser = getLogin()
+ cbackCommand = "cback --config=whatever --logfile=whatever --mode=064"
+ peer = RemotePeer(name, remoteUser=remoteUser, cbackCommand=cbackCommand)
+ self.failUnlessEqual(name, peer.name)
+ self.failUnlessEqual(None, peer.collectDir)
+ self.failUnlessEqual(None, peer.workingDir)
+ self.failUnlessEqual(remoteUser, peer.remoteUser)
+ self.failUnlessEqual(None, peer.localUser)
+ self.failUnlessEqual(None, peer.rcpCommand)
+ self.failUnlessEqual(None, peer.rshCommand)
+ self.failUnlessEqual(cbackCommand, peer.cbackCommand)
+
+
###############################
# Test checkCollectIndicator()
###############################
@@ -1429,6 +1477,56 @@
self.failUnlessEqual(permissions, self.getFileMode(["target", "file007", ]))
+ ##############################
+ # Test executeRemoteCommand()
+ ##############################
+
+ def testExecuteRemoteCommand(self):
+ """
+ Test that a simple remote command succeeds.
+ """
+ target = self.buildPath(["test.txt", ])
+ name = REMOTE_HOST
+ remoteUser = getLogin()
+ command = "touch %s" % target;
+ self.failIf(os.path.exists(target))
+ peer = RemotePeer(name=name, remoteUser=remoteUser)
+ peer.executeRemoteCommand(command)
+ self.failUnless(os.path.exists(target))
+
+
+ ############################
+ # Test _buildCbackCommand()
+ ############################
+
+ def testBuildCbackCommand_001(self):
+ """
+ Test with None for cbackCommand and action, False for fullBackup.
+ """
+ self.failUnlessRaises(ValueError, RemotePeer._buildCbackCommand, None, None, False)
+
+ def testBuildCbackCommand_002(self):
+ """
+ Test with None for cbackCommand, "collect" for action, False for fullBackup.
+ """
+ result = RemotePeer._buildCbackCommand(None, "collect", False)
+ self.failUnlessEqual("/usr/bin/cback collect", result)
+
+ def testBuildCbackCommand_003(self):
+ """
+ Test with "cback" for cbackCommand, "collect" for action, False for fullBackup.
+ """
+ result = RemotePeer._buildCbackCommand("cback", "collect", False)
+ self.failUnlessEqual("cback collect", result)
+
+ def testBuildCbackCommand_004(self):
+ """
+ Test with "cback" for cbackCommand, "collect" for action, True for fullBackup.
+ """
+ result = RemotePeer._buildCbackCommand("cback", "collect", True)
+ self.failUnlessEqual("cback --full collect", result)
+
+
#######################################################################
# Suite definition
#######################################################################
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2007-12-19 03:35:53
|
Revision: 822
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=822&view=rev
Author: pronovic
Date: 2007-12-18 19:35:49 -0800 (Tue, 18 Dec 2007)
Log Message:
-----------
Unit tests for managed peer actions
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/cli.py
cedar-backup2/trunk/test/clitests.py
Modified: cedar-backup2/trunk/CedarBackup2/cli.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/cli.py 2007-12-18 03:10:02 UTC (rev 821)
+++ cedar-backup2/trunk/CedarBackup2/cli.py 2007-12-19 03:35:49 UTC (rev 822)
@@ -88,8 +88,10 @@
# Cedar Backup modules
from CedarBackup2.release import AUTHOR, EMAIL, VERSION, DATE, COPYRIGHT
from CedarBackup2.util import RestrictedContentList, DirectedGraph, PathResolverSingleton
-from CedarBackup2.util import sortDict, splitCommandLine, executeCommand, getFunctionReference, getUidGid, encodePath
+from CedarBackup2.util import sortDict, splitCommandLine, executeCommand, getFunctionReference
+from CedarBackup2.util import getUidGid, encodePath
from CedarBackup2.config import Config
+from CedarBackup2.peer import RemotePeer
from CedarBackup2.actions.collect import executeCollect
from CedarBackup2.actions.stage import executeStage
from CedarBackup2.actions.store import executeStore
@@ -327,16 +329,17 @@
"""
if other is None:
return 1
- if self.SORT_ORDER != other.SORT_ORDER:
- if self.SORT_ORDER < other.SORT_ORDER:
- return -1
- else:
- return 1
if self.index != other.index:
if self.index < other.index:
return -1
else:
return 1
+ else:
+ if self.SORT_ORDER != other.SORT_ORDER:
+ if self.SORT_ORDER < other.SORT_ORDER:
+ return -1
+ else:
+ return 1
return 0
def executeAction(self, configPath, options, config):
@@ -427,16 +430,17 @@
"""
if other is None:
return 1
- if self.SORT_ORDER != other.SORT_ORDER:
- if self.SORT_ORDER < other.SORT_ORDER:
- return -1
- else:
- return 1
if self.index != other.index:
if self.index < other.index:
return -1
else:
return 1
+ else:
+ if self.SORT_ORDER != other.SORT_ORDER:
+ if self.SORT_ORDER < other.SORT_ORDER:
+ return -1
+ else:
+ return 1
return 0
def executeAction(self, configPath, options, config):
@@ -725,11 +729,11 @@
if peers.remotePeers is not None:
for peer in peers.remotePeers:
if peer.managed:
- remoteUser = _ActionSet._getRemoteUser(config, peer)
- localUser = _ActionSet._getRemoteUser(config, peer)
- rshCommand = _ActionSet._getRshCommand(config, peer)
- cbackCommand = _ActionSet._getCbackCommand(config, peer)
- managedActions = _ActionSet._getManagedActions(config, peer)
+ remoteUser = _ActionSet._getRemoteUser(options, peer)
+ localUser = _ActionSet._getRemoteUser(options, peer)
+ rshCommand = _ActionSet._getRshCommand(options, peer)
+ cbackCommand = _ActionSet._getCbackCommand(options, peer)
+ managedActions = _ActionSet._getManagedActions(options, peer)
remotePeer = RemotePeer(peer.name, None, options.workingDir, remoteUser, None,
options.backupUser, rshCommand, cbackCommand)
if managedActions is not None:
@@ -823,55 +827,55 @@
for actionItem in self.actionSet:
actionItem.executeAction(configPath, options, config)
- def _getRemoteUser(config, remotePeer):
+ def _getRemoteUser(options, remotePeer):
"""
Gets the remote user associated with a remote peer.
Use peer's if possible, otherwise take from options section.
- @param config: Config object.
+ @param options: OptionsConfig object, as from config.options
@param remotePeer: Configuration-style remote peer object.
@return: Name of remote user associated with remote peer.
"""
if remotePeer.remoteUser is None:
- return config.options.backupUser
+ return options.backupUser
return remotePeer.remoteUser
_getRemoteUser = staticmethod(_getRemoteUser)
- def _getRshCommand(config, remotePeer):
+ def _getRshCommand(options, remotePeer):
"""
Gets the RSH command associated with a remote peer.
Use peer's if possible, otherwise take from options section.
- @param config: Config object.
+ @param options: OptionsConfig object, as from config.options
@param remotePeer: Configuration-style remote peer object.
@return: RSH command associated with remote peer.
"""
if remotePeer.rshCommand is None:
- return config.options.rshCommand
+ return options.rshCommand
return remotePeer.rshCommand
_getRshCommand = staticmethod(_getRshCommand)
- def _getCbackCommand(config, remotePeer):
+ def _getCbackCommand(options, remotePeer):
"""
Gets the cback command associated with a remote peer.
Use peer's if possible, otherwise take from options section.
- @param config: Config object.
+ @param options: OptionsConfig object, as from config.options
@param remotePeer: Configuration-style remote peer object.
@return: cback command associated with remote peer.
"""
if remotePeer.cbackCommand is None:
- return config.options.cbackCommand
+ return options.cbackCommand
return remotePeer.cbackCommand
_getCbackCommand = staticmethod(_getCbackCommand)
- def _getManagedActions(config, remotePeer):
+ def _getManagedActions(options, remotePeer):
"""
Gets the managed actions list associated with a remote peer.
Use peer's if possible, otherwise take from options section.
- @param config: Config object.
+ @param options: OptionsConfig object, as from config.options
@param remotePeer: Configuration-style remote peer object.
@return: Set of managed actions associated with remote peer.
"""
if remotePeer.managedActions is None:
- return config.options.managedActions
+ return options.managedActions
return remotePeer.managedActions
_getManagedActions = staticmethod(_getManagedActions)
Modified: cedar-backup2/trunk/test/clitests.py
===================================================================
--- cedar-backup2/trunk/test/clitests.py 2007-12-18 03:10:02 UTC (rev 821)
+++ cedar-backup2/trunk/test/clitests.py 2007-12-19 03:35:49 UTC (rev 822)
@@ -81,7 +81,8 @@
from os.path import isdir, isfile, islink, isabs, exists
from getopt import GetoptError
from CedarBackup2.testutil import failUnlessAssignRaises, captureOutput
-from CedarBackup2.config import OptionsConfig, ExtensionsConfig
+from CedarBackup2.config import OptionsConfig, PeersConfig, ExtensionsConfig
+from CedarBackup2.config import LocalPeer, RemotePeer
from CedarBackup2.config import ExtendedAction, ActionDependencies, PreActionHook, PostActionHook
from CedarBackup2.cli import _usage, _version
from CedarBackup2.cli import Options
@@ -8595,6 +8596,4262 @@
self.failUnlessRaises(ValueError, _ActionSet, actions, extensions, options, None, False, True)
+ #########################################
+ # Test constructor, with managed peers
+ #########################################
+
+ def testManagedPeer_001(self):
+ """
+ Test with actions=[ collect ], extensions=[], peers=None, managed=True,
+ local=True
+ """
+ actions = [ "collect", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failIf(actionSet.actionSet is None)
+ self.failUnless(len(actionSet.actionSet) == 1)
+ self.failUnlessEqual(100, actionSet.actionSet[0].index)
+ self.failUnlessEqual("collect", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeCollect, actionSet.actionSet[0].function)
+
+ def testManagedPeer_002(self):
+ """
+ Test with actions=[ stage ], extensions=[], peers=None, managed=True,
+ local=True
+ """
+ actions = [ "stage", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failIf(actionSet.actionSet is None)
+ self.failUnless(len(actionSet.actionSet) == 1)
+ self.failUnlessEqual(200, actionSet.actionSet[0].index)
+ self.failUnlessEqual("stage", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeStage, actionSet.actionSet[0].function)
+
+ def testManagedPeer_003(self):
+ """
+ Test with actions=[ store ], extensions=[], peers=None, managed=True,
+ local=True
+ """
+ actions = [ "store", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failIf(actionSet.actionSet is None)
+ self.failUnless(len(actionSet.actionSet) == 1)
+ self.failUnlessEqual(300, actionSet.actionSet[0].index)
+ self.failUnlessEqual("store", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeStore, actionSet.actionSet[0].function)
+
+ def testManagedPeer_004(self):
+ """
+ Test with actions=[ purge ], extensions=[], peers=None, managed=True,
+ local=True
+ """
+ actions = [ "purge", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failIf(actionSet.actionSet is None)
+ self.failUnless(len(actionSet.actionSet) == 1)
+ self.failUnlessEqual(400, actionSet.actionSet[0].index)
+ self.failUnlessEqual("purge", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executePurge, actionSet.actionSet[0].function)
+
+ def testManagedPeer_005(self):
+ """
+ Test with actions=[ all ], extensions=[], peers=None, managed=True,
+ local=True
+ """
+ actions = [ "all", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failIf(actionSet.actionSet is None)
+ self.failUnless(len(actionSet.actionSet) == 4)
+ self.failUnlessEqual(100, actionSet.actionSet[0].index)
+ self.failUnlessEqual("collect", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeCollect, actionSet.actionSet[0].function)
+ self.failUnlessEqual(200, actionSet.actionSet[1].index)
+ self.failUnlessEqual("stage", actionSet.actionSet[1].name)
+ self.failUnlessEqual(executeStage, actionSet.actionSet[1].function)
+ self.failUnlessEqual(300, actionSet.actionSet[2].index)
+ self.failUnlessEqual("store", actionSet.actionSet[2].name)
+ self.failUnlessEqual(executeStore, actionSet.actionSet[2].function)
+ self.failUnlessEqual(400, actionSet.actionSet[3].index)
+ self.failUnlessEqual("purge", actionSet.actionSet[3].name)
+ self.failUnlessEqual(executePurge, actionSet.actionSet[3].function)
+
+ def testManagedPeer_006(self):
+ """
+ Test with actions=[ rebuild ], extensions=[], peers=None, managed=True,
+ local=True
+ """
+ actions = [ "rebuild", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 1)
+ self.failUnlessEqual(0, actionSet.actionSet[0].index)
+ self.failUnlessEqual("rebuild", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeRebuild, actionSet.actionSet[0].function)
+
+ def testManagedPeer_007(self):
+ """
+ Test with actions=[ validate ], extensions=[], peers=None, managed=True,
+ local=True
+ """
+ actions = [ "validate", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 1)
+ self.failUnlessEqual(0, actionSet.actionSet[0].index)
+ self.failUnlessEqual("validate", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeValidate, actionSet.actionSet[0].function)
+
+ def testManagedPeer_008(self):
+ """
+ Test with actions=[ collect, stage ], extensions=[], peers=None,
+ managed=True, local=True
+ """
+ actions = [ "collect", "stage", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 2)
+ self.failUnlessEqual(100, actionSet.actionSet[0].index)
+ self.failUnlessEqual("collect", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeCollect, actionSet.actionSet[0].function)
+ self.failUnlessEqual(200, actionSet.actionSet[1].index)
+ self.failUnlessEqual("stage", actionSet.actionSet[1].name)
+ self.failUnlessEqual(executeStage, actionSet.actionSet[1].function)
+
+ def testManagedPeer_009(self):
+ """
+ Test with actions=[ collect, store ], extensions=[], peers=None,
+ managed=True, local=True
+ """
+ actions = [ "collect", "store", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 2)
+ self.failUnlessEqual(100, actionSet.actionSet[0].index)
+ self.failUnlessEqual("collect", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeCollect, actionSet.actionSet[0].function)
+ self.failUnlessEqual(300, actionSet.actionSet[1].index)
+ self.failUnlessEqual("store", actionSet.actionSet[1].name)
+ self.failUnlessEqual(executeStore, actionSet.actionSet[1].function)
+
+ def testManagedPeer_010(self):
+ """
+ Test with actions=[ collect, purge ], extensions=[], peers=None,
+ managed=True, local=True
+ """
+ actions = [ "collect", "purge", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 2)
+ self.failUnlessEqual(100, actionSet.actionSet[0].index)
+ self.failUnlessEqual("collect", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeCollect, actionSet.actionSet[0].function)
+ self.failUnlessEqual(400, actionSet.actionSet[1].index)
+ self.failUnlessEqual("purge", actionSet.actionSet[1].name)
+ self.failUnlessEqual(executePurge, actionSet.actionSet[1].function)
+
+ def testManagedPeer_011(self):
+ """
+ Test with actions=[ stage, collect ], extensions=[], peers=None,
+ managed=True, local=True
+ """
+ actions = [ "stage", "collect", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 2)
+ self.failUnlessEqual(100, actionSet.actionSet[0].index)
+ self.failUnlessEqual("collect", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeCollect, actionSet.actionSet[0].function)
+ self.failUnlessEqual(200, actionSet.actionSet[1].index)
+ self.failUnlessEqual("stage", actionSet.actionSet[1].name)
+ self.failUnlessEqual(executeStage, actionSet.actionSet[1].function)
+
+ def testManagedPeer_012(self):
+ """
+ Test with actions=[ stage, stage ], extensions=[], peers=None,
+ managed=True, local=True
+ """
+ actions = [ "stage", "stage", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 2)
+ self.failUnlessEqual(200, actionSet.actionSet[0].index)
+ self.failUnlessEqual("stage", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeStage, actionSet.actionSet[0].function)
+ self.failUnlessEqual(200, actionSet.actionSet[1].index)
+ self.failUnlessEqual("stage", actionSet.actionSet[1].name)
+ self.failUnlessEqual(executeStage, actionSet.actionSet[1].function)
+
+ def testManagedPeer_013(self):
+ """
+ Test with actions=[ stage, store ], extensions=[], peers=None,
+ managed=True, local=True
+ """
+ actions = [ "stage", "store", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 2)
+ self.failUnlessEqual(200, actionSet.actionSet[0].index)
+ self.failUnlessEqual("stage", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeStage, actionSet.actionSet[0].function)
+ self.failUnlessEqual(300, actionSet.actionSet[1].index)
+ self.failUnlessEqual("store", actionSet.actionSet[1].name)
+ self.failUnlessEqual(executeStore, actionSet.actionSet[1].function)
+
+ def testManagedPeer_014(self):
+ """
+ Test with actions=[ stage, purge ], extensions=[], peers=None,
+ managed=True, local=True
+ """
+ actions = [ "stage", "purge", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 2)
+ self.failUnlessEqual(200, actionSet.actionSet[0].index)
+ self.failUnlessEqual("stage", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeStage, actionSet.actionSet[0].function)
+ self.failUnlessEqual(400, actionSet.actionSet[1].index)
+ self.failUnlessEqual("purge", actionSet.actionSet[1].name)
+ self.failUnlessEqual(executePurge, actionSet.actionSet[1].function)
+
+ def testManagedPeer_015(self):
+ """
+ Test with actions=[ collect, one ], extensions=[ (one, index 50) ],
+ peers=None, managed=True, local=True
+ """
+ actions = [ "collect", "one", ]
+ extensions = ExtensionsConfig([ ExtendedAction("one", "os.path", "isdir", 50), ], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 2)
+ self.failUnlessEqual(50, actionSet.actionSet[0].index)
+ self.failUnlessEqual("one", actionSet.actionSet[0].name)
+ self.failUnlessEqual(isdir, actionSet.actionSet[0].function)
+ self.failUnlessEqual(100, actionSet.actionSet[1].index)
+ self.failUnlessEqual("collect", actionSet.actionSet[1].name)
+ self.failUnlessEqual(executeCollect, actionSet.actionSet[1].function)
+
+ def testManagedPeer_016(self):
+ """
+ Test with actions=[ store, one ], extensions=[ (one, index 50) ],
+ peers=None, managed=True, local=True
+ """
+ actions = [ "store", "one", ]
+ extensions = ExtensionsConfig([ ExtendedAction("one", "os.path", "isdir", 50), ], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 2)
+ self.failUnlessEqual(50, actionSet.actionSet[0].index)
+ self.failUnlessEqual("one", actionSet.actionSet[0].name)
+ self.failUnlessEqual(isdir, actionSet.actionSet[0].function)
+ self.failUnlessEqual(300, actionSet.actionSet[1].index)
+ self.failUnlessEqual("store", actionSet.actionSet[1].name)
+ self.failUnlessEqual(executeStore, actionSet.actionSet[1].function)
+
+ def testManagedPeer_017(self):
+ """
+ Test with actions=[ collect, one ], extensions=[ (one, index 150) ],
+ peers=None, managed=True, local=True
+ """
+ actions = [ "collect", "one", ]
+ extensions = ExtensionsConfig([ ExtendedAction("one", "os.path", "isdir", 150), ], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 2)
+ self.failUnlessEqual(100, actionSet.actionSet[0].index)
+ self.failUnlessEqual("collect", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeCollect, actionSet.actionSet[0].function)
+ self.failUnlessEqual(150, actionSet.actionSet[1].index)
+ self.failUnlessEqual("one", actionSet.actionSet[1].name)
+ self.failUnlessEqual(isdir, actionSet.actionSet[1].function)
+
+ def testManagedPeer_018(self):
+ """
+ Test with actions=[ store, one ], extensions=[ (one, index 150) ],
+ peers=None, managed=True, local=True
+ """
+ actions = [ "store", "one", ]
+ extensions = ExtensionsConfig([ ExtendedAction("one", "os.path", "isdir", 150), ], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 2)
+ self.failUnlessEqual(150, actionSet.actionSet[0].index)
+ self.failUnlessEqual("one", actionSet.actionSet[0].name)
+ self.failUnlessEqual(isdir, actionSet.actionSet[0].function)
+ self.failUnlessEqual(300, actionSet.actionSet[1].index)
+ self.failUnlessEqual("store", actionSet.actionSet[1].name)
+ self.failUnlessEqual(executeStore, actionSet.actionSet[1].function)
+
+ def testManagedPeer_019(self):
+ """
+ Test with actions=[ collect, stage, store, purge ], extensions=[],
+ peers=None, managed=True, local=True
+ """
+ actions = [ "collect", "stage", "store", "purge", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 4)
+ self.failUnlessEqual(100, actionSet.actionSet[0].index)
+ self.failUnlessEqual("collect", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeCollect, actionSet.actionSet[0].function)
+ self.failUnlessEqual(200, actionSet.actionSet[1].index)
+ self.failUnlessEqual("stage", actionSet.actionSet[1].name)
+ self.failUnlessEqual(executeStage, actionSet.actionSet[1].function)
+ self.failUnlessEqual(300, actionSet.actionSet[2].index)
+ self.failUnlessEqual("store", actionSet.actionSet[2].name)
+ self.failUnlessEqual(executeStore, actionSet.actionSet[2].function)
+ self.failUnlessEqual(400, actionSet.actionSet[3].index)
+ self.failUnlessEqual("purge", actionSet.actionSet[3].name)
+ self.failUnlessEqual(executePurge, actionSet.actionSet[3].function)
+
+ def testManagedPeer_020(self):
+ """
+ Test with actions=[ collect, stage, store, purge, one, two, three, four,
+ five ], extensions=[ (index 50, 150, 250, 350, 450)], peers=None,
+ managed=True, local=True
+ """
+ actions = [ "collect", "stage", "store", "purge", "one", "two", "three", "four", "five", ]
+ extensions = ExtensionsConfig([ ExtendedAction("one", "os.path", "isdir", 50), ExtendedAction("two", "os.path", "isfile", 150),
+ ExtendedAction("three", "os.path", "islink", 250), ExtendedAction("four", "os.path", "isabs", 350),
+ ExtendedAction("five", "os.path", "exists", 450), ], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 9)
+ self.failUnlessEqual(50, actionSet.actionSet[0].index)
+ self.failUnlessEqual("one", actionSet.actionSet[0].name)
+ self.failUnlessEqual(isdir, actionSet.actionSet[0].function)
+ self.failUnlessEqual(100, actionSet.actionSet[1].index)
+ self.failUnlessEqual("collect", actionSet.actionSet[1].name)
+ self.failUnlessEqual(executeCollect, actionSet.actionSet[1].function)
+ self.failUnlessEqual(150, actionSet.actionSet[2].index)
+ self.failUnlessEqual("two", actionSet.actionSet[2].name)
+ self.failUnlessEqual(isfile, actionSet.actionSet[2].function)
+ self.failUnlessEqual(200, actionSet.actionSet[3].index)
+ self.failUnlessEqual("stage", actionSet.actionSet[3].name)
+ self.failUnlessEqual(executeStage, actionSet.actionSet[3].function)
+ self.failUnlessEqual(250, actionSet.actionSet[4].index)
+ self.failUnlessEqual("three", actionSet.actionSet[4].name)
+ self.failUnlessEqual(islink, actionSet.actionSet[4].function)
+ self.failUnlessEqual(300, actionSet.actionSet[5].index)
+ self.failUnlessEqual("store", actionSet.actionSet[5].name)
+ self.failUnlessEqual(executeStore, actionSet.actionSet[5].function)
+ self.failUnlessEqual(350, actionSet.actionSet[6].index)
+ self.failUnlessEqual("four", actionSet.actionSet[6].name)
+ self.failUnlessEqual(isabs, actionSet.actionSet[6].function)
+ self.failUnlessEqual(400, actionSet.actionSet[7].index)
+ self.failUnlessEqual("purge", actionSet.actionSet[7].name)
+ self.failUnlessEqual(executePurge, actionSet.actionSet[7].function)
+ self.failUnlessEqual(450, actionSet.actionSet[8].index)
+ self.failUnlessEqual("five", actionSet.actionSet[8].name)
+ self.failUnlessEqual(exists, actionSet.actionSet[8].function)
+
+ def testManagedPeer_021(self):
+ """
+ Test with actions=[ one ], extensions=[ (one, index 50) ], peers=None,
+ managed=True, local=True
+ """
+ actions = [ "one", ]
+ extensions = ExtensionsConfig([ ExtendedAction("one", "os.path", "isdir", 50), ], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, None, True, True)
+ self.failUnless(len(actionSet.actionSet) == 1)
+ self.failUnlessEqual(50, actionSet.actionSet[0].index)
+ self.failUnlessEqual("one", actionSet.actionSet[0].name)
+ self.failUnlessEqual(isdir, actionSet.actionSet[0].function)
+
+ def testManagedPeer_022(self):
+ """
+ Test with actions=[ collect ], extensions=[], no peers, managed=True,
+ local=True
+ """
+ actions = [ "collect", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, peers, True, True)
+ self.failIf(actionSet.actionSet is None)
+ self.failUnless(len(actionSet.actionSet) == 1)
+ self.failUnlessEqual(100, actionSet.actionSet[0].index)
+ self.failUnlessEqual("collect", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeCollect, actionSet.actionSet[0].function)
+
+ def testManagedPeer_023(self):
+ """
+ Test with actions=[ stage ], extensions=[], no peers, managed=True,
+ local=True
+ """
+ actions = [ "stage", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.managedActions = [ "collect", "purge", "one", ]
+ peers = PeersConfig()
+ actionSet = _ActionSet(actions, extensions, options, peers, True, True)
+ self.failIf(actionSet.actionSet is None)
+ self.failUnless(len(actionSet.actionSet) == 1)
+ self.failUnlessEqual(200, actionSet.actionSet[0].index)
+ self.failUnlessEqual("stage", actionSet.actionSet[0].name)
+ self.failUnlessEqual(executeStage, actionSet.actionSet[0].function)
+
+ def testManagedPeer_024(self):
+ """
+ Test with actions=[ store ], extensions=[], no peers, managed=True,
+ local=True
+ """
+ actions = [ "store", ]
+ extensions = ExtensionsConfig([], None)
+ options = OptionsConfig()
+ options.ma...
[truncated message content] |
|
From: <pro...@us...> - 2007-12-19 04:07:01
|
Revision: 825
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=825&view=rev
Author: pronovic
Date: 2007-12-18 20:06:57 -0800 (Tue, 18 Dec 2007)
Log Message:
-----------
Fix pychecker warnings
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/cli.py
cedar-backup2/trunk/test/clitests.py
Modified: cedar-backup2/trunk/CedarBackup2/cli.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/cli.py 2007-12-19 04:05:16 UTC (rev 824)
+++ cedar-backup2/trunk/CedarBackup2/cli.py 2007-12-19 04:06:57 UTC (rev 825)
@@ -731,7 +731,6 @@
for peer in peers.remotePeers:
if peer.managed:
remoteUser = _ActionSet._getRemoteUser(options, peer)
- localUser = _ActionSet._getRemoteUser(options, peer)
rshCommand = _ActionSet._getRshCommand(options, peer)
cbackCommand = _ActionSet._getCbackCommand(options, peer)
managedActions = _ActionSet._getManagedActions(options, peer)
Modified: cedar-backup2/trunk/test/clitests.py
===================================================================
--- cedar-backup2/trunk/test/clitests.py 2007-12-19 04:05:16 UTC (rev 824)
+++ cedar-backup2/trunk/test/clitests.py 2007-12-19 04:06:57 UTC (rev 825)
@@ -8992,7 +8992,6 @@
extensions = ExtensionsConfig([ ExtendedAction("one", "os.path", "isdir", 50), ], None)
options = OptionsConfig()
options.managedActions = [ "collect", "purge", "one", ]
- peers = PeersConfig()
actionSet = _ActionSet(actions, extensions, options, None, True, True)
self.failUnless(len(actionSet.actionSet) == 1)
self.failUnlessEqual(50, actionSet.actionSet[0].index)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2007-12-19 04:08:20
|
Revision: 826
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=826&view=rev
Author: pronovic
Date: 2007-12-18 20:08:16 -0800 (Tue, 18 Dec 2007)
Log Message:
-----------
Release 2.15.0
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/release.py
cedar-backup2/trunk/Changelog
Modified: cedar-backup2/trunk/CedarBackup2/release.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/release.py 2007-12-19 04:06:57 UTC (rev 825)
+++ cedar-backup2/trunk/CedarBackup2/release.py 2007-12-19 04:08:16 UTC (rev 826)
@@ -34,7 +34,7 @@
AUTHOR = "Kenneth J. Pronovici"
EMAIL = "pro...@ie..."
COPYRIGHT = "2004-2007"
-VERSION = "2.14.0"
-DATE = "19 Sep 2007"
+VERSION = "2.15.0"
+DATE = "18 Dec 2007"
URL = "http://cedar-solutions.com/software/cedar-backup"
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2007-12-19 04:06:57 UTC (rev 825)
+++ cedar-backup2/trunk/Changelog 2007-12-19 04:08:16 UTC (rev 826)
@@ -1,4 +1,4 @@
-Version 2.15.0 unreleased
+Version 2.15.0 18 Dec 2007
* Minor documentation tweaks discovered during 3.0 development.
* Add support for a new managed backup feature.
@@ -6,7 +6,7 @@
- Change peers configuration in <stage> to just override <peers>
- Modify stage process to take peers list from peers section (if available)
- Add new configuration in options and remote peers to support remote shells
- - Updated user manual to discuss managed backup concept and configuration
+ - Update user manual to discuss managed backup concept and configuration
- Add executeRemoteCommand() and executeManagedAction() on peer.RemotePeer
Version 2.14.0 19 Sep 2007
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2007-12-20 01:42:33
|
Revision: 838
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=838&view=rev
Author: pronovic
Date: 2007-12-19 17:42:31 -0800 (Wed, 19 Dec 2007)
Log Message:
-----------
Improve managed action error handling
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/cli.py
cedar-backup2/trunk/CedarBackup2/peer.py
cedar-backup2/trunk/Changelog
Modified: cedar-backup2/trunk/CedarBackup2/cli.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/cli.py 2007-12-19 16:17:27 UTC (rev 837)
+++ cedar-backup2/trunk/CedarBackup2/cli.py 2007-12-20 01:42:31 UTC (rev 838)
@@ -448,9 +448,14 @@
"""
Executes the managed action associated with an item.
- @note: Only options.full is actually used. The rest of
- the arguments exist to satisfy the ActionItem iterface.
+ @note: Only options.full is actually used. The rest of the arguments
+ exist to satisfy the ActionItem iterface.
+ @note: Errors here result in a message logged to ERROR, but no thrown
+ exception. The analogy is the stage action where a problem with one host
+ should not kill the entire backup. Since we're logging an error, the
+ administrator will get an email.
+
@param configPath: Path to configuration file on disk.
@param options: Command-line options to be passed to action.
@param config: Parsed configuration to be passed to action.
@@ -459,7 +464,10 @@
"""
for peer in self.remotePeers:
logger.debug("Executing managed action [%s] on peer [%s]." % (self.name, peer.name))
- peer.executeManagedAction(self.name, options.full)
+ try:
+ peer.executeManagedAction(self.name, options.full)
+ except IOError, e:
+ logger.error(e) # log the message and go on, so we don't kill the backup
###################
Modified: cedar-backup2/trunk/CedarBackup2/peer.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/peer.py 2007-12-19 16:17:27 UTC (rev 837)
+++ cedar-backup2/trunk/CedarBackup2/peer.py 2007-12-20 01:42:31 UTC (rev 838)
@@ -857,8 +857,12 @@
@raise IOError: If there is an error executing the action on the remote peer.
"""
- command = RemotePeer._buildCbackCommand(self.cbackCommand, action, fullBackup)
- self.executeRemoteCommand(command)
+ try:
+ command = RemotePeer._buildCbackCommand(self.cbackCommand, action, fullBackup)
+ self.executeRemoteCommand(command)
+ except IOError, e:
+ logger.info(e)
+ raise IOError("Failed to execute action [%s] on managed client [%s]." % (action, self.name))
##################
@@ -1160,21 +1164,21 @@
@raise IOError: If there is an error executing the remote command
"""
+ actualCommand = "%s %s@%s '%s'" % (rshCommand, remoteUser, remoteHost, remoteCommand)
if localUser is not None:
try:
if os.getuid() != 0:
raise IOError("Only root can remote shell as another user.")
except AttributeError: pass
command = resolveCommand(SU_COMMAND)
- actualCommand = "%s %s@%s '%s'" % (rshCommand, remoteUser, remoteHost, remoteCommand)
result = executeCommand(command, [localUser, "-c", actualCommand])[0]
if result != 0:
- raise IOError("Error (%d) executing command on remote host as local user [%s]." % (result, localUser))
+ raise IOError("Command failed [su -c %s \"%s\"]" % (localUser, actualCommand))
else:
command = resolveCommand(rshCommandList)
result = executeCommand(command, ["%s@%s" % (remoteUser, remoteHost), "%s" % remoteCommand])[0]
if result != 0:
- raise IOError("Error (%d) executing command on remote host (using no local user)." % result)
+ raise IOError("Command failed [%s]" % (actualCommand))
_executeRemoteCommand = staticmethod(_executeRemoteCommand)
def _buildCbackCommand(cbackCommand, action, fullBackup):
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2007-12-19 16:17:27 UTC (rev 837)
+++ cedar-backup2/trunk/Changelog 2007-12-20 01:42:31 UTC (rev 838)
@@ -1,3 +1,8 @@
+Version 2.15.1 19 Dec 2007
+
+ * Improve error reporting for managed client action failures.
+ * Make sure that managed client failure does not kill entire backup.
+
Version 2.15.0 18 Dec 2007
* Minor documentation tweaks discovered during 3.0 development.
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2008-02-07 15:21:42
|
Revision: 845
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=845&view=rev
Author: pronovic
Date: 2008-02-07 07:21:36 -0800 (Thu, 07 Feb 2008)
Log Message:
-----------
Fix SF bug #1861878
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/testutil.py
cedar-backup2/trunk/Changelog
cedar-backup2/trunk/test/configtests.py
cedar-backup2/trunk/test/splittests.py
Modified: cedar-backup2/trunk/CedarBackup2/testutil.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/testutil.py 2008-02-07 15:21:17 UTC (rev 844)
+++ cedar-backup2/trunk/CedarBackup2/testutil.py 2008-02-07 15:21:36 UTC (rev 845)
@@ -8,7 +8,7 @@
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
-# Copyright (c) 2004-2006 Kenneth J. Pronovici.
+# Copyright (c) 2004-2006,2008 Kenneth J. Pronovici.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
@@ -482,3 +482,28 @@
locales.append(line.rstrip())
return locales
+
+####################################
+# hexFloatLiteralAllowed() function
+####################################
+
+def hexFloatLiteralAllowed():
+ """
+ Indicates whether hex float literals are allowed by the interpreter.
+
+ As far back as 2004, some Python documentation indicated that octal and hex
+ notation applies only to integer literals. However, prior to Python 2.5, it
+ was legal to construct a float with an argument like 0xAC. This check
+ provides a version-based indication of whether the current interpreter
+ supports that behavior.
+
+ This check exists so that unit tests can continue to test the same thing as
+ always for pre-2.5 interpreters (i.e. making sure backwards compatibility
+ doesn't break) while still continuing to work for later interpreters.
+
+ The returned value is True for Python <= 2.5, and False otherwise.
+ """
+ if map(int, [sys.version_info[0], sys.version_info[1]]) < [2, 5]:
+ return True
+ return False
+
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2008-02-07 15:21:17 UTC (rev 844)
+++ cedar-backup2/trunk/Changelog 2008-02-07 15:21:36 UTC (rev 845)
@@ -1,3 +1,14 @@
+Version 2.15.2 07 Feb 2007
+
+ * Fix two unit test failures when using Python 2.5 (SF #1861878).
+ - Add new function testtutil.hexFloatLiteralAllowed()
+ - Fix splittests.TestByteQuantity.testConstructor_004() for 0xAC
+ - Fix configtests.TestBlankBehavior.testConstructor_006() for 0xAC
+ - Octal and hex notations apply only to integer literals. However,
+ Python apparently didn't enforce this before 2.5. These tests
+ actually did their job, which was to make me aware of the change.
+ This was a test-only bug, and there was no functional impact.
+
Version 2.15.1 19 Dec 2007
* Improve error reporting for managed client action failures.
Modified: cedar-backup2/trunk/test/configtests.py
===================================================================
--- cedar-backup2/trunk/test/configtests.py 2008-02-07 15:21:17 UTC (rev 844)
+++ cedar-backup2/trunk/test/configtests.py 2008-02-07 15:21:36 UTC (rev 845)
@@ -9,7 +9,7 @@
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
-# Copyright (c) 2004-2007 Kenneth J. Pronovici.
+# Copyright (c) 2004-2008 Kenneth J. Pronovici.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
@@ -102,6 +102,7 @@
import os
import unittest
from CedarBackup2.testutil import findResources, removedir, failUnlessAssignRaises
+from CedarBackup2.testutil import hexFloatLiteralAllowed
from CedarBackup2.config import ActionHook, PreActionHook, PostActionHook, CommandOverride
from CedarBackup2.config import ExtendedAction, ActionDependencies, BlankBehavior
from CedarBackup2.config import CollectFile, CollectDir, PurgeDir, LocalPeer, RemotePeer
@@ -1084,8 +1085,10 @@
self.failUnlessEqual("1E6", behavior.blankFactor)
behavior.blankFactor = "0.25E2"
self.failUnlessEqual("0.25E2", behavior.blankFactor)
- behavior.blankFactor = "0xAC"
- self.failUnlessEqual("0xAC", behavior.blankFactor)
+ if hexFloatLiteralAllowed():
+ # Some interpreters allow this, some don't
+ behavior.blankFactor = "0xAC"
+ self.failUnlessEqual("0xAC", behavior.blankFactor)
def testConstructor_007(self):
"""
Modified: cedar-backup2/trunk/test/splittests.py
===================================================================
--- cedar-backup2/trunk/test/splittests.py 2008-02-07 15:21:17 UTC (rev 844)
+++ cedar-backup2/trunk/test/splittests.py 2008-02-07 15:21:36 UTC (rev 845)
@@ -9,7 +9,7 @@
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
-# Copyright (c) 2007 Kenneth J. Pronovici.
+# Copyright (c) 2007-2008 Kenneth J. Pronovici.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
@@ -111,6 +111,7 @@
from CedarBackup2.util import UNIT_BYTES, UNIT_KBYTES, UNIT_MBYTES, UNIT_GBYTES
from CedarBackup2.testutil import findResources, buildPath, removedir, extractTar
from CedarBackup2.testutil import failUnlessAssignRaises, platformSupportsLinks, availableLocales
+from CedarBackup2.testutil import hexFloatLiteralAllowed
from CedarBackup2.xmlutil import createOutputDom, serializeDom
from CedarBackup2.extend.split import LocalConfig, SplitConfig, ByteQuantity
from CedarBackup2.extend.split import _splitFile, _splitDailyDir
@@ -221,8 +222,10 @@
self.failUnlessEqual("1E6", quantity.quantity)
quantity.quantity = "0.25E2"
self.failUnlessEqual("0.25E2", quantity.quantity)
- quantity.quantity = "0xAC"
- self.failUnlessEqual("0xAC", quantity.quantity)
+ if hexFloatLiteralAllowed():
+ # Some interpreters allow this, some don't
+ quantity.quantity = "0xAC"
+ self.failUnlessEqual("0xAC", quantity.quantity)
def testConstructor_005(self):
"""
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2008-02-07 15:23:58
|
Revision: 846
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=846&view=rev
Author: pronovic
Date: 2008-02-07 07:23:54 -0800 (Thu, 07 Feb 2008)
Log Message:
-----------
Update copyright statements for year 2008
Modified Paths:
--------------
cedar-backup2/trunk/CREDITS
cedar-backup2/trunk/Changelog
Modified: cedar-backup2/trunk/CREDITS
===================================================================
--- cedar-backup2/trunk/CREDITS 2008-02-07 15:21:36 UTC (rev 845)
+++ cedar-backup2/trunk/CREDITS 2008-02-07 15:23:54 UTC (rev 846)
@@ -23,7 +23,7 @@
software, as indicated in the source code itself.
Unless otherwise indicated, all Cedar Backup source code is copyright
-(c) 2004-2007 Kenneth J. Pronovici and is released under the GNU General
+(c) 2004-2008 Kenneth J. Pronovici and is released under the GNU General
Public License. The contents of the GNU General Public License can be
found in the LICENSE file, or can be downloaded from http://www.gnu.org/.
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2008-02-07 15:21:36 UTC (rev 845)
+++ cedar-backup2/trunk/Changelog 2008-02-07 15:23:54 UTC (rev 846)
@@ -1,5 +1,6 @@
-Version 2.15.2 07 Feb 2007
+Version 2.15.2 07 Feb 2008
+ * Updated copyright statements now that code changed in year 2008.
* Fix two unit test failures when using Python 2.5 (SF #1861878).
- Add new function testtutil.hexFloatLiteralAllowed()
- Fix splittests.TestByteQuantity.testConstructor_004() for 0xAC
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2008-02-07 15:25:35
|
Revision: 847
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=847&view=rev
Author: pronovic
Date: 2008-02-07 07:25:31 -0800 (Thu, 07 Feb 2008)
Log Message:
-----------
Release 2.15.2
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/release.py
cedar-backup2/trunk/Changelog
Modified: cedar-backup2/trunk/CedarBackup2/release.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/release.py 2008-02-07 15:23:54 UTC (rev 846)
+++ cedar-backup2/trunk/CedarBackup2/release.py 2008-02-07 15:25:31 UTC (rev 847)
@@ -33,8 +33,8 @@
AUTHOR = "Kenneth J. Pronovici"
EMAIL = "pro...@ie..."
-COPYRIGHT = "2004-2007"
-VERSION = "2.15.1"
-DATE = "19 Dec 2007"
+COPYRIGHT = "2004-2008"
+VERSION = "2.15.2"
+DATE = "07 Feb 2008"
URL = "http://cedar-solutions.com/software/cedar-backup"
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2008-02-07 15:23:54 UTC (rev 846)
+++ cedar-backup2/trunk/Changelog 2008-02-07 15:25:31 UTC (rev 847)
@@ -5,10 +5,6 @@
- Add new function testtutil.hexFloatLiteralAllowed()
- Fix splittests.TestByteQuantity.testConstructor_004() for 0xAC
- Fix configtests.TestBlankBehavior.testConstructor_006() for 0xAC
- - Octal and hex notations apply only to integer literals. However,
- Python apparently didn't enforce this before 2.5. These tests
- actually did their job, which was to make me aware of the change.
- This was a test-only bug, and there was no functional impact.
Version 2.15.1 19 Dec 2007
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2008-02-27 04:15:24
|
Revision: 848
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=848&view=rev
Author: pronovic
Date: 2008-02-26 20:05:21 -0800 (Tue, 26 Feb 2008)
Log Message:
-----------
Fix testEncodePath_009() to be aware of UTF-8 encoding.
Modified Paths:
--------------
cedar-backup2/trunk/Changelog
cedar-backup2/trunk/test/utiltests.py
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2008-02-07 15:25:31 UTC (rev 847)
+++ cedar-backup2/trunk/Changelog 2008-02-27 04:05:21 UTC (rev 848)
@@ -1,3 +1,7 @@
+Version 2.15.3 unreleased
+
+ * Fix testEncodePath_009() to be aware of "UTF-8" encoding.
+
Version 2.15.2 07 Feb 2008
* Updated copyright statements now that code changed in year 2008.
Modified: cedar-backup2/trunk/test/utiltests.py
===================================================================
--- cedar-backup2/trunk/test/utiltests.py 2008-02-07 15:25:31 UTC (rev 847)
+++ cedar-backup2/trunk/test/utiltests.py 2008-02-27 04:05:21 UTC (rev 848)
@@ -3273,7 +3273,7 @@
path = u"\xe2\x99\xaa\xe2\x99\xac"
safePath = encodePath(path)
self.failUnless(isinstance(safePath, str))
- if encoding == "utf-8":
+ if encoding.upper() == "UTF-8": # apparently, some platforms have "utf-8", some have "UTF-8"
self.failUnlessEqual('\xc3\xa2\xc2\x99\xc2\xaa\xc3\xa2\xc2\x99\xc2\xac', safePath)
else:
self.failUnlessEqual("\xe2\x99\xaa\xe2\x99\xac", safePath)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2008-03-16 21:45:12
|
Revision: 849
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=849&view=rev
Author: pronovic
Date: 2008-03-16 14:44:04 -0700 (Sun, 16 Mar 2008)
Log Message:
-----------
Fix typos in the PostgreSQL extension section of the manual.
Modified Paths:
--------------
cedar-backup2/trunk/Changelog
cedar-backup2/trunk/manual/src/extensions.xml
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2008-02-27 04:05:21 UTC (rev 848)
+++ cedar-backup2/trunk/Changelog 2008-03-16 21:44:04 UTC (rev 849)
@@ -1,6 +1,7 @@
Version 2.15.3 unreleased
* Fix testEncodePath_009() to be aware of "UTF-8" encoding.
+ * Fix typos in the PostgreSQL extension section of the manual.
Version 2.15.2 07 Feb 2008
Modified: cedar-backup2/trunk/manual/src/extensions.xml
===================================================================
--- cedar-backup2/trunk/manual/src/extensions.xml 2008-02-27 04:05:21 UTC (rev 848)
+++ cedar-backup2/trunk/manual/src/extensions.xml 2008-03-16 21:44:04 UTC (rev 849)
@@ -798,11 +798,11 @@
</para>
<programlisting>
-<mysql>
+<postgresql>
<compress_mode>bzip2</compress_mode>
<user>username</user>
<all>Y</all>
-</mysql>
+</postgresql>
</programlisting>
<para>
@@ -811,13 +811,13 @@
</para>
<programlisting>
-<mysql>
+<postgresql>
<compress_mode>bzip2</compress_mode>
<user>username</user>
<all>N</all>
<database>db1</database>
<database>db2</database>
-</mysql>
+</postgresql>
</programlisting>
<para>
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2008-03-16 22:04:05
|
Revision: 850
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=850&view=rev
Author: pronovic
Date: 2008-03-16 15:03:24 -0700 (Sun, 16 Mar 2008)
Log Message:
-----------
Improve logging when stage action fails (closes: #1854635)
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/peer.py
cedar-backup2/trunk/Changelog
Modified: cedar-backup2/trunk/CedarBackup2/peer.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/peer.py 2008-03-16 21:44:04 UTC (rev 849)
+++ cedar-backup2/trunk/CedarBackup2/peer.py 2008-03-16 22:03:24 UTC (rev 850)
@@ -784,10 +784,13 @@
sourceFile, targetFile,
overwrite=False)
if os.path.exists(targetFile):
+ logger.debug("Found collect indicator.")
return True
else:
+ logger.debug("Did not find collect indicator.")
return False
- except:
+ except Exception, e:
+ logger.info("Failed looking for collect indicator: %s" % e)
return False
finally:
if os.path.exists(targetFile):
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2008-03-16 21:44:04 UTC (rev 849)
+++ cedar-backup2/trunk/Changelog 2008-03-16 22:03:24 UTC (rev 850)
@@ -2,6 +2,7 @@
* Fix testEncodePath_009() to be aware of "UTF-8" encoding.
* Fix typos in the PostgreSQL extension section of the manual.
+ * Improve logging when stage action fails (closes: #1854635).
Version 2.15.2 07 Feb 2008
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2008-03-16 23:20:39
|
Revision: 851
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=851&view=rev
Author: pronovic
Date: 2008-03-16 16:20:35 -0700 (Sun, 16 Mar 2008)
Log Message:
-----------
Fix stage action so it works for local users (closes: #1854634).
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/actions/stage.py
cedar-backup2/trunk/CedarBackup2/peer.py
cedar-backup2/trunk/Changelog
Modified: cedar-backup2/trunk/CedarBackup2/actions/stage.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/actions/stage.py 2008-03-16 22:03:24 UTC (rev 850)
+++ cedar-backup2/trunk/CedarBackup2/actions/stage.py 2008-03-16 23:20:35 UTC (rev 851)
@@ -8,7 +8,7 @@
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
-# Copyright (c) 2004-2007 Kenneth J. Pronovici.
+# Copyright (c) 2004-2008 Kenneth J. Pronovici.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
@@ -248,9 +248,10 @@
if configPeers is not None:
for peer in configPeers:
remoteUser = _getRemoteUser(config, peer)
+ localUser = _getLocalUser(config)
rcpCommand = _getRcpCommand(config, peer)
remotePeer = RemotePeer(peer.name, peer.collectDir, config.options.workingDir,
- remoteUser, rcpCommand, config.options.backupUser)
+ remoteUser, rcpCommand, localUser)
remotePeers.append(remotePeer)
logger.debug("Found remote peer: [%s]" % remotePeer.name)
return remotePeers
@@ -273,6 +274,21 @@
return remotePeer.remoteUser
+###########################
+# _getLocalUser() function
+###########################
+
+def _getLocalUser(config):
+ """
+ Gets the remote user associated with a remote peer.
+ @param config: Config object.
+ @return: Name of local user that should be used
+ """
+ if os.getuid() != 0:
+ return None
+ return config.options.backupUser
+
+
############################
# _getRcpCommand() function
############################
Modified: cedar-backup2/trunk/CedarBackup2/peer.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/peer.py 2008-03-16 22:03:24 UTC (rev 850)
+++ cedar-backup2/trunk/CedarBackup2/peer.py 2008-03-16 23:20:35 UTC (rev 851)
@@ -8,7 +8,7 @@
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
-# Copyright (c) 2004-2007 Kenneth J. Pronovici.
+# Copyright (c) 2004-2008 Kenneth J. Pronovici.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
@@ -784,10 +784,8 @@
sourceFile, targetFile,
overwrite=False)
if os.path.exists(targetFile):
- logger.debug("Found collect indicator.")
return True
else:
- logger.debug("Did not find collect indicator.")
return False
except Exception, e:
logger.info("Failed looking for collect indicator: %s" % e)
@@ -976,18 +974,21 @@
command = resolveCommand(rcpCommandList)
result = executeCommand(command, [copySource, targetDir])[0]
if result != 0:
- raise IOError("Error (%d) copying files from remote host (using no local user)." % result)
+ raise IOError("Error (%d) copying files from remote host." % result)
afterSet = RemotePeer._getDirContents(targetDir)
if len(afterSet) == 0:
raise IOError("Did not copy any files from remote peer.")
differenceSet = afterSet.difference(beforeSet) # files we added as part of copy
if len(differenceSet) == 0:
raise IOError("Apparently did not copy any new files from remote peer.")
- for targetFile in differenceSet:
- if ownership is not None:
- os.chown(targetFile, ownership[0], ownership[1])
- if permissions is not None:
- os.chmod(targetFile, permissions)
+ if localUser is None:
+ logger.debug("Not root, so not attempting to change owner on staged files.")
+ else:
+ for targetFile in differenceSet:
+ if ownership is not None:
+ os.chown(targetFile, ownership[0], ownership[1])
+ if permissions is not None:
+ os.chmod(targetFile, permissions)
return len(differenceSet)
_copyRemoteDir = staticmethod(_copyRemoteDir)
@@ -1069,7 +1070,7 @@
command = resolveCommand(rcpCommandList)
result = executeCommand(command, [copySource, targetFile])[0]
if result != 0:
- raise IOError("Error (%d) copying [%s] from remote host (using no local user)." % (result, sourceFile))
+ raise IOError("Error (%d) copying [%s] from remote host." % (result, sourceFile))
if not os.path.exists(targetFile):
raise IOError("Apparently unable to copy file from remote host.")
if ownership is not None:
@@ -1140,7 +1141,7 @@
command = resolveCommand(rcpCommandList)
result = executeCommand(command, [sourceFile.replace(" ", "\\ "), copyTarget])[0]
if result != 0:
- raise IOError("Error (%d) copying [%s] to remote host (using no local user)." % (result, sourceFile))
+ raise IOError("Error (%d) copying [%s] to remote host." % (result, sourceFile))
_pushLocalFile = staticmethod(_pushLocalFile)
def _executeRemoteCommand(remoteUser, localUser, remoteHost, rshCommand, rshCommandList, remoteCommand):
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2008-03-16 22:03:24 UTC (rev 850)
+++ cedar-backup2/trunk/Changelog 2008-03-16 23:20:35 UTC (rev 851)
@@ -3,6 +3,7 @@
* Fix testEncodePath_009() to be aware of "UTF-8" encoding.
* Fix typos in the PostgreSQL extension section of the manual.
* Improve logging when stage action fails (closes: #1854635).
+ * Fix stage action so it works for local users (closes: #1854634).
Version 2.15.2 07 Feb 2008
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2008-03-16 23:35:53
|
Revision: 853
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=853&view=rev
Author: pronovic
Date: 2008-03-16 16:35:43 -0700 (Sun, 16 Mar 2008)
Log Message:
-----------
Release 2.15.3
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/release.py
cedar-backup2/trunk/Changelog
Modified: cedar-backup2/trunk/CedarBackup2/release.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/release.py 2008-03-16 23:34:19 UTC (rev 852)
+++ cedar-backup2/trunk/CedarBackup2/release.py 2008-03-16 23:35:43 UTC (rev 853)
@@ -34,7 +34,7 @@
AUTHOR = "Kenneth J. Pronovici"
EMAIL = "pro...@ie..."
COPYRIGHT = "2004-2008"
-VERSION = "2.15.2"
-DATE = "07 Feb 2008"
+VERSION = "2.15.3"
+DATE = "16 Mar 2008"
URL = "http://cedar-solutions.com/software/cedar-backup"
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2008-03-16 23:34:19 UTC (rev 852)
+++ cedar-backup2/trunk/Changelog 2008-03-16 23:35:43 UTC (rev 853)
@@ -1,4 +1,4 @@
-Version 2.15.3 unreleased
+Version 2.15.3 16 Mar 2008
* Fix testEncodePath_009() to be aware of "UTF-8" encoding.
* Fix typos in the PostgreSQL extension section of the manual.
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2008-03-17 02:04:58
|
Revision: 855
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=855&view=rev
Author: pronovic
Date: 2008-03-16 19:04:53 -0700 (Sun, 16 Mar 2008)
Log Message:
-----------
Add linkDepth parameter to FilesystemList.addDirContents()
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/filesystem.py
cedar-backup2/trunk/Changelog
cedar-backup2/trunk/test/filesystemtests.py
Modified: cedar-backup2/trunk/CedarBackup2/filesystem.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/filesystem.py 2008-03-16 23:35:54 UTC (rev 854)
+++ cedar-backup2/trunk/CedarBackup2/filesystem.py 2008-03-17 02:04:53 UTC (rev 855)
@@ -8,7 +8,7 @@
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
-# Copyright (c) 2004-2007 Kenneth J. Pronovici.
+# Copyright (c) 2004-2008 Kenneth J. Pronovici.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
@@ -357,7 +357,7 @@
logger.debug("Added directory to list: [%s]" % path)
return 1
- def addDirContents(self, path, recursive=True, addSelf=True):
+ def addDirContents(self, path, recursive=True, addSelf=True, linkDepth=0):
"""
Adds the contents of a directory to the list.
@@ -372,11 +372,17 @@
directory and all of its contents will be recursively excluded from the
list.
- @note: If the passed-in directory happens to be a soft link, it will
- still be recursed. However, any soft links I{within} the directory will
- only be added by name, not recursively. Any invalid soft links (i.e.
- soft links that point to non-existent items) will be silently ignored.
+ @note: If the passed-in directory happens to be a soft link, it will be
+ recursed. However, the linkDepth parameter controls whether any soft
+ links I{within} the directory will be recursed. The link depth is
+ maximum depth of the tree at which soft links should be followed. So, a
+ depth of 0 does not follow any soft links, a depth of 1 follows only
+ links within the passed-in directory, a depth of 2 follows the links at
+ the next level down, etc.
+ @note: Any invalid soft links (i.e. soft links that point to
+ non-existent items) will be silently ignored.
+
@note: The L{excludeDirs} flag only controls whether any given directory
path itself is added to the list once it has been discovered. It does
I{not} modify any behavior related to directory recursion.
@@ -390,6 +396,9 @@
@param addSelf: Indicates whether the directory itself should be added to the list.
@type addSelf: Boolean value
+ @param linkDepth: Depth to which soft links should be followed
+ @type linkDepth: Integer value, where zero means not to follow any soft links
+
@return: Number of items recursively added to the list
@raise ValueError: If path is not a directory or does not exist.
@@ -397,9 +406,9 @@
"""
path = encodePath(path)
path = normalizeDir(path)
- return self._addDirContentsInternal(path, recursive=recursive, includePath=addSelf)
+ return self._addDirContentsInternal(path, addSelf, recursive, linkDepth)
- def _addDirContentsInternal(self, path, includePath=True, recursive=True):
+ def _addDirContentsInternal(self, path, includePath=True, recursive=True, linkDepth=0):
"""
Internal implementation of C{addDirContents}.
@@ -411,9 +420,15 @@
interface, C{addDirContents} ends up being wholly implemented in terms
of this method.
+ The linkDepth parameter controls whether soft links are followed when we
+ are adding the contents recursively. Any recursive calls reduce the
+ value by one. If the value zero or less, then soft links will just be
+ added as directories, but will not be followed.
+
@param path: Directory path whose contents should be added to the list.
@param includePath: Indicates whether to include the path as well as contents.
@param recursive: Indicates whether directory contents should be added recursively.
+ @param linkDepth: Depth of soft links that should be followed
@return: Number of items recursively added to the list
@@ -445,10 +460,15 @@
added += self.addFile(entrypath)
elif os.path.isdir(entrypath):
if os.path.islink(entrypath):
- added += self.addDir(entrypath)
+ if recursive and linkDepth > 0:
+ newDepth = linkDepth - 1;
+ added += self._addDirContentsInternal(entrypath, linkDepth=newDepth)
+ else:
+ added += self.addDir(entrypath)
else:
if recursive:
- added += self._addDirContentsInternal(entrypath)
+ newDepth = linkDepth - 1;
+ added += self._addDirContentsInternal(entrypath, linkDepth=newDepth)
else:
added += self.addDir(entrypath)
return added
@@ -1193,7 +1213,7 @@
# Add methods
##############
- def addDirContents(self, path, recursive=True, addSelf=False):
+ def addDirContents(self, path, recursive=True, addSelf=True, linkDepth=0):
"""
Adds the contents of a directory to the list.
@@ -1208,11 +1228,17 @@
directory and all of its contents will be recursively excluded from the
list.
- @note: If the passed-in directory happens to be a soft link, it will
- still be recursed. However, any soft links I{within} the directory will
- only be added by name, not recursively. Any invalid soft links (i.e.
- soft links that point to non-existent items) will be silently ignored.
+ @note: If the passed-in directory happens to be a soft link, it will be
+ recursed. However, the linkDepth parameter controls whether any soft
+ links I{within} the directory will be recursed. The link depth is
+ maximum depth of the tree at which soft links should be followed. So, a
+ depth of 0 does not follow any soft links, a depth of 1 follows only
+ links within the passed-in directory, a depth of 2 follows the links at
+ the next level down, etc.
+ @note: Any invalid soft links (i.e. soft links that point to
+ non-existent items) will be silently ignored.
+
@note: The L{excludeDirs} flag only controls whether any given soft link
path itself is added to the list once it has been discovered. It does
I{not} modify any behavior related to directory recursion.
@@ -1229,6 +1255,9 @@
@param addSelf: Ignored in this subclass.
+ @param linkDepth: Depth of soft links that should be followed
+ @type linkDepth: Integer value, where zero means not to follow any soft links
+
@return: Number of items recursively added to the list
@raise ValueError: If path is not a directory or does not exist.
@@ -1236,7 +1265,7 @@
"""
path = encodePath(path)
path = normalizeDir(path)
- return super(PurgeItemList, self)._addDirContentsInternal(path, includePath=False, recursive=recursive)
+ return super(PurgeItemList, self)._addDirContentsInternal(path, False, recursive, linkDepth)
##################
@@ -1372,6 +1401,8 @@
between the directories containing different files, and containing the same
files with differing content.
+ @note: Symlinks are I{not} followed for the purposes of this comparison.
+
@param path1: First path to compare.
@type path1: String representing a path on disk
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2008-03-16 23:35:54 UTC (rev 854)
+++ cedar-backup2/trunk/Changelog 2008-03-17 02:04:53 UTC (rev 855)
@@ -1,3 +1,7 @@
+Version 2.16.0 unreleased
+
+ * Add linkDepth parameter to FilesystemList.addDirContents().
+
Version 2.15.3 16 Mar 2008
* Fix testEncodePath_009() to be aware of "UTF-8" encoding.
Modified: cedar-backup2/trunk/test/filesystemtests.py
===================================================================
--- cedar-backup2/trunk/test/filesystemtests.py 2008-03-16 23:35:54 UTC (rev 854)
+++ cedar-backup2/trunk/test/filesystemtests.py 2008-03-17 02:04:53 UTC (rev 855)
@@ -9,7 +9,7 @@
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
-# Copyright (c) 2004-2007 Kenneth J. Pronovici.
+# Copyright (c) 2004-2008 Kenneth J. Pronovici.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
@@ -4751,7 +4751,691 @@
self.failUnless(self.buildPath([ "tree6", "link001", ]) in fsList)
self.failUnless(self.buildPath([ "tree6", "link002", ]) in fsList)
+ def testAddDirContents_091(self):
+ """
+ Attempt to add a directory with linkDepth=1.
+ """
+ self.extractTar("tree6")
+ path = self.buildPath(["tree6"])
+ fsList = FilesystemList()
+ count = fsList.addDirContents(path, addSelf=False, linkDepth=1)
+ if not platformSupportsLinks():
+ self.failUnlessEqual(121, count)
+ self.failUnlessEqual(121, len(fsList))
+ self.failUnless(self.buildPath([ "tree6", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "dir003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file006", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file007", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "ignore", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "link003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "link003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "dir003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file006", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file007", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file008", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file009", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "link003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "link004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "dir003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file006", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file007", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file008", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "link005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file006", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file007", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "link004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "link003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "link004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file006", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file007", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file008", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file009", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir002", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir002", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir002", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir002", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir002", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir002", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir002", "file005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir002", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir002", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir002", "link004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "file005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "file006", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "file007", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "file008", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "file009", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "ignore", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "link003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "link005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "link001", ]) in fsList)
+ else:
+ self.failUnlessEqual(165, count)
+ self.failUnlessEqual(165, len(fsList))
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "dir003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file006", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "file007", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "ignore", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir001", "link003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "dir002", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir001", "link003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "dir003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file006", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file007", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file008", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "file009", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "link003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "link004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir001", "link005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "dir003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file006", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file007", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "file008", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "link003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "link004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir002", "link005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file006", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "file007", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "link003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "dir003", "link004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "link002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "link003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "link004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir002", "link005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "dir002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file002", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file003", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file004", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file005", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file006", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file007", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file008", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "file009", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tree6", "dir003", "dir001", "link001", ]) in fsList)
+ self.failUnless(self.buildPath([ "tr...
[truncated message content] |
|
From: <pro...@us...> - 2008-03-17 02:46:48
|
Revision: 858
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=858&view=rev
Author: pronovic
Date: 2008-03-16 19:46:45 -0700 (Sun, 16 Mar 2008)
Log Message:
-----------
Add support for collecting soft links (closes: #1854631)
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/actions/collect.py
cedar-backup2/trunk/CedarBackup2/config.py
cedar-backup2/trunk/CedarBackup2/filesystem.py
cedar-backup2/trunk/Changelog
cedar-backup2/trunk/manual/src/config.xml
cedar-backup2/trunk/test/configtests.py
cedar-backup2/trunk/test/data/cback.conf.15
cedar-backup2/trunk/test/data/cback.conf.20
cedar-backup2/trunk/test/data/cback.conf.21
cedar-backup2/trunk/test/data/cback.conf.8
Modified: cedar-backup2/trunk/CedarBackup2/actions/collect.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/actions/collect.py 2008-03-17 02:44:37 UTC (rev 857)
+++ cedar-backup2/trunk/CedarBackup2/actions/collect.py 2008-03-17 02:46:45 UTC (rev 858)
@@ -8,7 +8,7 @@
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
-# Copyright (c) 2004-2007 Kenneth J. Pronovici.
+# Copyright (c) 2004-2008 Kenneth J. Pronovici.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
@@ -126,14 +126,15 @@
collectMode = _getCollectMode(config, collectDir)
archiveMode = _getArchiveMode(config, collectDir)
ignoreFile = _getIgnoreFile(config, collectDir)
+ linkDepth = _getLinkDepth(collectDir)
digestPath = _getDigestPath(config, collectDir)
tarfilePath = _getTarfilePath(config, collectDir, archiveMode)
(excludePaths, excludePatterns) = _getExclusions(config, collectDir)
if fullBackup or (collectMode in ['daily', 'incr', ]) or (collectMode == 'weekly' and todayIsStart):
logger.debug("Directory meets criteria to be backed up today.")
_collectDirectory(config, collectDir.absolutePath, tarfilePath,
- collectMode, archiveMode, ignoreFile, resetDigest,
- digestPath, excludePaths, excludePatterns)
+ collectMode, archiveMode, ignoreFile, linkDepth,
+ resetDigest, digestPath, excludePaths, excludePatterns)
else:
logger.debug("Directory will not be backed up, per collect mode.")
logger.info("Completed collecting directory [%s]" % collectDir.absolutePath)
@@ -181,7 +182,8 @@
###############################
def _collectDirectory(config, absolutePath, tarfilePath, collectMode, archiveMode,
- ignoreFile, resetDigest, digestPath, excludePaths, excludePatterns):
+ ignoreFile, linkDepth, resetDigest, digestPath, excludePaths,
+ excludePatterns):
"""
Collects a configured collect directory.
@@ -200,6 +202,7 @@
@param collectMode: Collect mode to use.
@param archiveMode: Archive mode to use.
@param ignoreFile: Ignore file to use.
+ @param linkDepth: Link depth value to use.
@param resetDigest: Reset digest flag.
@param digestPath: Path to digest file on disk, if needed.
@param excludePaths: List of absolute paths to exclude.
@@ -209,7 +212,7 @@
backupList.ignoreFile = ignoreFile
backupList.excludePaths = excludePaths
backupList.excludePatterns = excludePatterns
- backupList.addDirContents(absolutePath)
+ backupList.addDirContents(absolutePath, linkDepth=linkDepth)
_executeBackup(config, backupList, absolutePath, tarfilePath, collectMode, archiveMode, resetDigest, digestPath)
@@ -385,6 +388,25 @@
############################
+# _getLinkDepth() function
+############################
+
+def _getLinkDepth(item):
+ """
+ Gets the link depth that should be used for a collect directory.
+ If possible, use the one on the directory, otherwise set a value of 0 (zero).
+ @param item: C{CollectDir} object
+ @return: Ignore file to use.
+ """
+ if item.linkDepth is None:
+ linkDepth = 0
+ else:
+ linkDepth = item.linkDepth
+ logger.debug("Link depth is [%d]" % linkDepth)
+ return linkDepth
+
+
+############################
# _getDigestPath() function
############################
Modified: cedar-backup2/trunk/CedarBackup2/config.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/config.py 2008-03-17 02:44:37 UTC (rev 857)
+++ cedar-backup2/trunk/CedarBackup2/config.py 2008-03-17 02:46:45 UTC (rev 858)
@@ -8,7 +8,7 @@
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
-# Copyright (c) 2004-2007 Kenneth J. Pronovici.
+# Copyright (c) 2004-2008 Kenneth J. Pronovici.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
@@ -1230,12 +1230,13 @@
@note: Lists within this class are "unordered" for equality comparisons.
@sort: __init__, __repr__, __str__, __cmp__, absolutePath, collectMode,
- archiveMode, ignoreFile, absoluteExcludePaths, relativeExcludePaths,
- excludePatterns
+ archiveMode, ignoreFile, linkDepth, absoluteExcludePaths,
+ relativeExcludePaths, excludePatterns
"""
def __init__(self, absolutePath=None, collectMode=None, archiveMode=None, ignoreFile=None,
- absoluteExcludePaths=None, relativeExcludePaths=None, excludePatterns=None):
+ absoluteExcludePaths=None, relativeExcludePaths=None, excludePatterns=None,
+ linkDepth=None):
"""
Constructor for the C{CollectDir} class.
@@ -1243,6 +1244,7 @@
@param collectMode: Overridden collect mode for this directory.
@param archiveMode: Overridden archive mode for this directory.
@param ignoreFile: Overidden ignore file name for this directory.
+ @param linkDepth: Maximum at which soft links should be followed.
@param absoluteExcludePaths: List of absolute paths to exclude.
@param relativeExcludePaths: List of relative paths to exclude.
@param excludePatterns: List of regular expression patterns to exclude.
@@ -1253,6 +1255,7 @@
self._collectMode = None
self._archiveMode = None
self._ignoreFile = None
+ self._linkDepth = None
self._absoluteExcludePaths = None
self._relativeExcludePaths = None
self._excludePatterns = None
@@ -1260,6 +1263,7 @@
self.collectMode = collectMode
self.archiveMode = archiveMode
self.ignoreFile = ignoreFile
+ self.linkDepth = linkDepth
self.absoluteExcludePaths = absoluteExcludePaths
self.relativeExcludePaths = relativeExcludePaths
self.excludePatterns = excludePatterns
@@ -1268,11 +1272,12 @@
"""
Official string representation for class instance.
"""
- return "CollectDir(%s, %s, %s, %s, %s, %s, %s)" % (self.absolutePath, self.collectMode,
- self.archiveMode, self.ignoreFile,
- self.absoluteExcludePaths,
- self.relativeExcludePaths,
- self.excludePatterns)
+ return "CollectDir(%s, %s, %s, %s, %s, %s, %s, %s)" % (self.absolutePath, self.collectMode,
+ self.archiveMode, self.ignoreFile,
+ self.absoluteExcludePaths,
+ self.relativeExcludePaths,
+ self.excludePatterns,
+ self.linkDepth)
def __str__(self):
"""
@@ -1309,6 +1314,11 @@
return -1
else:
return 1
+ if self._linkDepth != other._linkDepth:
+ if self._linkDepth < other._linkDepth:
+ return -1
+ else:
+ return 1
if self._absoluteExcludePaths != other._absoluteExcludePaths:
if self._absoluteExcludePaths < other._absoluteExcludePaths:
return -1
@@ -1396,6 +1406,29 @@
"""
return self._ignoreFile
+ def _setLinkDepth(self, value):
+ """
+ Property target used to set the link depth.
+ The value must be an integer >= 0.
+ @raise 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 _setAbsoluteExcludePaths(self, value):
"""
Property target used to set the absolute exclude paths list.
@@ -1467,6 +1500,7 @@
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.")
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.")
@@ -4550,6 +4584,7 @@
collectMode mode I{or} collect_mode
archiveMode archive_mode
ignoreFile ignore_file
+ linkDepth link_depth
The collect mode is a special case. Just a C{mode} tag is accepted for
backwards compatibility, but we prefer C{collect_mode} for consistency
@@ -4580,6 +4615,7 @@
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.absoluteExcludePaths, cdir.relativeExcludePaths, cdir.excludePatterns) = Config._parseExclusions(entry)
lst.append(cdir)
if lst == []:
@@ -5199,6 +5235,7 @@
collectMode dir/collect_mode
archiveMode dir/archive_mode
ignoreFile dir/ignore_file
+ linkDepth dir/link_depth
Note that an original XML document might have listed the collect mode
using the C{mode} tag, since we accept both C{collect_mode} and C{mode}.
@@ -5226,6 +5263,7 @@
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)
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 != [])):
Modified: cedar-backup2/trunk/CedarBackup2/filesystem.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/filesystem.py 2008-03-17 02:44:37 UTC (rev 857)
+++ cedar-backup2/trunk/CedarBackup2/filesystem.py 2008-03-17 02:46:45 UTC (rev 858)
@@ -396,7 +396,7 @@
@param addSelf: Indicates whether the directory itself should be added to the list.
@type addSelf: Boolean value
- @param linkDepth: Depth to which soft links should be followed
+ @param linkDepth: Maximum depth of the tree at which soft links should be followed
@type linkDepth: Integer value, where zero means not to follow any soft links
@return: Number of items recursively added to the list
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2008-03-17 02:44:37 UTC (rev 857)
+++ cedar-backup2/trunk/Changelog 2008-03-17 02:46:45 UTC (rev 858)
@@ -1,6 +1,11 @@
Version 2.16.0 unreleased
- * Add linkDepth parameter to FilesystemList.addDirContents().
+ * Make name attribute optional in RemotPeer constructor.
+ * Add support for collecting soft links (closes: #1854631).
+ - Add linkDepth parameter to FilesystemList.addDirContents()
+ - Add CollectDir.linkDepth attribute
+ - Modify collect action to obey CollectDir.linkDepth
+ - Update user manual to discuss new attribute
Version 2.15.3 16 Mar 2008
Modified: cedar-backup2/trunk/manual/src/config.xml
===================================================================
--- cedar-backup2/trunk/manual/src/config.xml 2008-03-17 02:44:37 UTC (rev 857)
+++ cedar-backup2/trunk/manual/src/config.xml 2008-03-17 02:46:45 UTC (rev 858)
@@ -1459,8 +1459,8 @@
</para>
<para>
This section can be repeated as many times as is
- necessary. At least one collect directory must be
- configured when the collect action is executed.
+ necessary. At least one collect directory or collect file
+ must be configured when the collect action is executed.
</para>
<para>
The collect directory subsection contains the following
@@ -1585,6 +1585,32 @@
</varlistentry>
<varlistentry>
+ <term><literal>link_depth</literal></term>
+ <listitem>
+ <para>Link depth value to use for this directory.</para>
+ <para>
+ The link depth is maximum depth of the tree at
+ which soft links should be followed. So, a depth
+ of 0 does not follow any soft links within the
+ collect directory, a depth of 1 follows only
+ links immediately within the collect directory, a
+ depth of 2 follows the links at the next level
+ down, etc.
+ </para>
+ <para>
+ This field is optional. If it doesn't exist,
+ the backup will assume a value of zero, meaning
+ that soft links within the collect directory will
+ never be followed.
+ </para>
+ <para>
+ <emphasis>Restrictions:</emphasis> If set, must
+ be an integer ≥ 0.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>exclude</literal></term>
<listitem>
<para>List of paths or patterns to exclude from the backup.</para>
Modified: cedar-backup2/trunk/test/configtests.py
===================================================================
--- cedar-backup2/trunk/test/configtests.py 2008-03-17 02:44:37 UTC (rev 857)
+++ cedar-backup2/trunk/test/configtests.py 2008-03-17 02:46:45 UTC (rev 858)
@@ -2162,6 +2162,7 @@
self.failUnlessEqual(None, collectDir.collectMode)
self.failUnlessEqual(None, collectDir.archiveMode)
self.failUnlessEqual(None, collectDir.ignoreFile)
+ self.failUnlessEqual(None, collectDir.linkDepth)
self.failUnlessEqual(None, collectDir.absoluteExcludePaths)
self.failUnlessEqual(None, collectDir.relativeExcludePaths)
self.failUnlessEqual(None, collectDir.excludePatterns)
@@ -2170,11 +2171,12 @@
"""
Test constructor with all values filled in, with valid values.
"""
- collectDir = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [])
+ collectDir = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [], 2)
self.failUnlessEqual("/etc/whatever", collectDir.absolutePath)
self.failUnlessEqual("incr", collectDir.collectMode)
self.failUnlessEqual("tar", collectDir.archiveMode)
self.failUnlessEqual(".ignore", collectDir.ignoreFile)
+ self.failUnlessEqual(2, collectDir.linkDepth)
self.failUnlessEqual([], collectDir.absoluteExcludePaths)
self.failUnlessEqual([], collectDir.relativeExcludePaths)
self.failUnlessEqual([], collectDir.excludePatterns)
@@ -2502,7 +2504,34 @@
self.failUnlessAssignRaises(ValueError, collectDir, "excludePatterns", ["*.jpg", "valid", ])
self.failUnlessEqual(None, collectDir.excludePatterns)
+ def testConstructor_036(self):
+ """
+ Test assignment of linkDepth attribute, None value.
+ """
+ collectDir = CollectDir(linkDepth=1)
+ self.failUnlessEqual(1, collectDir.linkDepth)
+ collectDir.linkDepth = None
+ self.failUnlessEqual(None, collectDir.linkDepth)
+ def testConstructor_037(self):
+ """
+ Test assignment of linkDepth attribute, valid value.
+ """
+ collectDir = CollectDir()
+ self.failUnlessEqual(None, collectDir.linkDepth)
+ collectDir.linkDepth = 1
+ self.failUnlessEqual(1, collectDir.linkDepth)
+
+ def testConstructor_038(self):
+ """
+ Test assignment of linkDepth attribute, invalid value.
+ """
+ collectDir = CollectDir()
+ self.failUnlessEqual(None, collectDir.linkDepth)
+ self.failUnlessAssignRaises(ValueError, collectDir, "linkDepth", "ken")
+ self.failUnlessEqual(None, collectDir.linkDepth)
+
+
############################
# Test comparison operators
############################
@@ -2526,8 +2555,8 @@
Test comparison of two identical objects, all attributes non-None (empty
lists).
"""
- collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [])
- collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [])
+ collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [], 1)
+ collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [], 1)
self.failUnless(collectDir1 == collectDir2)
self.failUnless(not collectDir1 < collectDir2)
self.failUnless(collectDir1 <= collectDir2)
@@ -2540,8 +2569,8 @@
Test comparison of two identical objects, all attributes non-None
(non-empty lists).
"""
- collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", ["/one",], ["two",], ["three",])
- collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", ["/one",], ["two",], ["three",])
+ collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", ["/one",], ["two",], ["three",], 1)
+ collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", ["/one",], ["two",], ["three",], 1)
self.failUnless(collectDir1 == collectDir2)
self.failUnless(not collectDir1 < collectDir2)
self.failUnless(collectDir1 <= collectDir2)
@@ -2567,8 +2596,8 @@
"""
Test comparison of two differing objects, absolutePath differs.
"""
- collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [])
- collectDir2 = CollectDir("/stuff", "incr", "tar", ".ignore", [], [], [])
+ collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [], 1)
+ collectDir2 = CollectDir("/stuff", "incr", "tar", ".ignore", [], [], [], 1)
self.failIfEqual(collectDir1, collectDir2)
self.failUnless(not collectDir1 == collectDir2)
self.failUnless(collectDir1 < collectDir2)
@@ -2595,8 +2624,8 @@
"""
Test comparison of two differing objects, collectMode differs.
"""
- collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [])
- collectDir2 = CollectDir("/etc/whatever", "daily", "tar", ".ignore", [], [], [])
+ collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [], 1)
+ collectDir2 = CollectDir("/etc/whatever", "daily", "tar", ".ignore", [], [], [], 1)
self.failIfEqual(collectDir1, collectDir2)
self.failUnless(not collectDir1 == collectDir2)
self.failUnless(not collectDir1 < collectDir2)
@@ -2623,8 +2652,8 @@
"""
Test comparison of two differing objects, archiveMode differs.
"""
- collectDir1 = CollectDir("/etc/whatever", "incr", "targz", ".ignore", [], [], [])
- collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [])
+ collectDir1 = CollectDir("/etc/whatever", "incr", "targz", ".ignore", [], [], [], 1)
+ collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [], 1)
self.failIfEqual(collectDir1, collectDir2)
self.failUnless(not collectDir1 == collectDir2)
self.failUnless(not collectDir1 < collectDir2)
@@ -2651,8 +2680,8 @@
"""
Test comparison of two differing objects, ignoreFile differs.
"""
- collectDir1 = CollectDir("/etc/whatever", "incr", "tar", "ignore", [], [], [])
- collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [])
+ collectDir1 = CollectDir("/etc/whatever", "incr", "tar", "ignore", [], [], [], 1)
+ collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [], 1)
self.failIfEqual(collectDir1, collectDir2)
self.failUnless(not collectDir1 == collectDir2)
self.failUnless(not collectDir1 < collectDir2)
@@ -2696,8 +2725,8 @@
Test comparison of two differing objects, absoluteExcludePaths differs
(one empty, one not empty).
"""
- collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [])
- collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", ["/whatever", ], [], [])
+ collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [], 1)
+ collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", ["/whatever", ], [], [], 1)
self.failIfEqual(collectDir1, collectDir2)
self.failUnless(not collectDir1 == collectDir2)
self.failUnless(collectDir1 < collectDir2)
@@ -2711,8 +2740,8 @@
Test comparison of two differing objects, absoluteExcludePaths differs
(both not empty).
"""
- collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", ["/stuff", ], [], [])
- collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", ["/stuff", "/something", ], [], [])
+ collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", ["/stuff", ], [], [], 1)
+ collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", ["/stuff", "/something", ], [], [], 1)
self.failIfEqual(collectDir1, collectDir2)
self.failUnless(not collectDir1 == collectDir2)
self.failUnless(not collectDir1 < collectDir2) # note: different than standard due to unsorted list
@@ -2756,8 +2785,8 @@
Test comparison of two differing objects, relativeExcludePaths differs
(one empty, one not empty).
"""
- collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], ["one", ], [])
- collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [])
+ collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], ["one", ], [], 1)
+ collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [], 1)
self.failIfEqual(collectDir1, collectDir2)
self.failUnless(not collectDir1 == collectDir2)
self.failUnless(not collectDir1 < collectDir2)
@@ -2771,8 +2800,8 @@
Test comparison of two differing objects, relativeExcludePaths differs
(both not empty).
"""
- collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], ["one", ], [])
- collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], ["two", ], [])
+ collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], ["one", ], [], 1)
+ collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], ["two", ], [], 1)
self.failIfEqual(collectDir1, collectDir2)
self.failUnless(not collectDir1 == collectDir2)
self.failUnless(collectDir1 < collectDir2)
@@ -2816,8 +2845,8 @@
Test comparison of two differing objects, excludePatterns differs (one
empty, one not empty).
"""
- collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [])
- collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], ["pattern", ])
+ collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], [], 1)
+ collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], ["pattern", ], 1)
self.failIfEqual(collectDir1, collectDir2)
self.failUnless(not collectDir1 == collectDir2)
self.failUnless(collectDir1 < collectDir2)
@@ -2831,8 +2860,8 @@
Test comparison of two differing objects, excludePatterns differs (both
not empty).
"""
- collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], ["p1", ])
- collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], ["p2", ])
+ collectDir1 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], ["p1", ], 1)
+ collectDir2 = CollectDir("/etc/whatever", "incr", "tar", ".ignore", [], [], ["p2", ], 1)
self.failIfEqual(collectDir1, collectDir2)
self.failUnless(not collectDir1 == collectDir2)
self.failUnless(collectDir1 < collectDir2)
@@ -2841,7 +2870,35 @@
self.failUnless(not collectDir1 >= collectDir2)
self.failUnless(collectDir1 != collectDir2)
+ def testComparison_024(self):
+ """
+ Test comparison of two differing objects, linkDepth differs (one None).
+ """
+ collectDir1 = CollectDir()
+ collectDir2 = CollectDir(linkDepth=1)
+ self.failIfEqual(collectDir1, collectDir2)
+ self.failUnless(not collectDir1 == collectDir2)
+ self.failUnless(collectDir1 < collectDir2)
+ self.failUnless(collectDir1 <= collectDir2)
+ self.failUnless(not collectDir1 > collectDir2)
+ self.failUnless(not collectDir1 >= collectDir2)
+ self.failUnless(collectDir1 != collectDir2)
+ def testComparison_025(self):
+ """
+ Test comparison of two differing objects, linkDepth differs.
+ """
+ collectDir1 = CollectDir("/etc/whatever", "incr", "tar", "ignore", [], [], [], 2)
+ collectDir2 = CollectDir("/etc/whatever", "incr", "tar", "ignore", [], [], [], 1)
+ self.failIfEqual(collectDir1, collectDir2)
+ self.failUnless(not collectDir1 == collectDir2)
+ self.failUnless(not collectDir1 < collectDir2)
+ self.failUnless(not collectDir1 <= collectDir2)
+ self.failUnless(collectDir1 > collectDir2)
+ self.failUnless(collectDir1 >= collectDir2)
+ self.failUnless(collectDir1 != collectDir2)
+
+
#####################
# TestPurgeDir class
#####################
@@ -10605,6 +10662,7 @@
expected.collect.collectFiles.append(CollectFile(absolutePath="/home/root/.aliases",collectMode="daily",archiveMode="tarbz2"))
expected.collect.collectDirs = []
expected.collect.collectDirs.append(CollectDir(absolutePath="/root"))
+ expected.collect.collectDirs.append(CollectDir(absolutePath="/tmp", linkDepth=3))
expected.collect.collectDirs.append(CollectDir(absolutePath="/var/log", collectMode="incr"))
expected.collect.collectDirs.append(CollectDir(absolutePath="/etc",collectMode="incr",archiveMode="tar",ignoreFile=".ignore"))
collectDir = CollectDir(absolutePath="/opt")
@@ -10791,6 +10849,7 @@
expected.collect.collectFiles.append(CollectFile(absolutePath="/home/root/.aliases",collectMode="daily",archiveMode="tarbz2"))
expected.collect.collectDirs = []
expected.collect.collectDirs.append(CollectDir(absolutePath="/root"))
+ expected.collect.collectDirs.append(CollectDir(absolutePath="/tmp", linkDepth=3))
expected.collect.collectDirs.append(CollectDir(absolutePath="/var/log", collectMode="incr"))
expected.collect.collectDirs.append(CollectDir(absolutePath="/etc",collectMode="incr",archiveMode="tar",ignoreFile=".ignore"))
collectDir = CollectDir(absolutePath="/opt")
@@ -10857,6 +10916,7 @@
expected.collect.collectFiles.append(CollectFile(absolutePath="/home/root/.aliases",collectMode="daily",archiveMode="tarbz2"))
expected.collect.collectDirs = []
expected.collect.collectDirs.append(CollectDir(absolutePath="/root"))
+ expected.collect.collectDirs.append(CollectDir(absolutePath="/tmp", linkDepth=3))
expected.collect.collectDirs.append(CollectDir(absolutePath="/var/log", collectMode="incr"))
expected.collect.collectDirs.append(CollectDir(absolutePath="/etc",collectMode="incr",archiveMode="tar",ignoreFile=".ignore"))
collectDir = CollectDir(absolutePath="/opt")
@@ -10920,6 +10980,7 @@
expected.collect.collectFiles.append(CollectFile(ab...
[truncated message content] |
|
From: <pro...@us...> - 2008-03-18 02:55:18
|
Revision: 860
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=860&view=rev
Author: pronovic
Date: 2008-03-17 19:55:08 -0700 (Mon, 17 Mar 2008)
Log Message:
-----------
Refactored ByteQuantity functionality out of split and into config.
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/config.py
cedar-backup2/trunk/CedarBackup2/extend/split.py
cedar-backup2/trunk/Changelog
Modified: cedar-backup2/trunk/CedarBackup2/config.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/config.py 2008-03-18 02:49:50 UTC (rev 859)
+++ cedar-backup2/trunk/CedarBackup2/config.py 2008-03-18 02:55:08 UTC (rev 860)
@@ -246,6 +246,7 @@
from CedarBackup2.writers.util import validateScsiId, validateDriveSpeed
from CedarBackup2.util import UnorderedList, AbsolutePathList, ObjectTypeList
from CedarBackup2.util import RegexMatchList, RegexList, encodePath
+from CedarBackup2.util import UNIT_BYTES, UNIT_KBYTES, UNIT_MBYTES, UNIT_GBYTES
from CedarBackup2.xmlutil import isElement, readChildren, readFirstChild
from CedarBackup2.xmlutil import readStringList, readString, readInteger, readBoolean
from CedarBackup2.xmlutil import addContainerNode, addStringNode, addIntegerNode, addBooleanNode
@@ -270,6 +271,7 @@
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, ]
REWRITABLE_MEDIA_TYPES = [ "cdrw-74", "cdrw-80", "dvd+rw", ]
@@ -277,6 +279,123 @@
########################################################################
+# ByteQuantity class definition
+########################################################################
+
+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.
+
+ 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 want 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.
+
+ @sort: __init__, __repr__, __str__, __cmp__, quantity, units
+ """
+
+ def __init__(self, quantity=None, units=None):
+ """
+ Constructor for the C{ByteQuantity} class.
+
+ @param quantity: Quantity of bytes, as string ("1.25")
+ @param units: Unit of bytes, one of VALID_BYTE_UNITS
+
+ @raise 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 self.__repr__()
+
+ def __cmp__(self, other):
+ """
+ Definition of equals operator for this class.
+ Lists within this class are "unordered" for equality comparisons.
+ @param other: Other object to compare to.
+ @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
+ """
+ if other is None:
+ return 1
+ if self._quantity != other._quantity:
+ if self._quantity < other._quantity:
+ return -1
+ else:
+ return 1
+ if self._units != other._units:
+ if self._units < other._units:
+ return -1
+ else:
+ return 1
+ return 0
+
+ def _setQuantity(self, value):
+ """
+ Property target used to set the quantity
+ The value must be a non-empty string if it is not C{None}.
+ @raise ValueError: If the value is an empty string.
+ @raise ValueError: If the value is not a valid floating point number
+ @raise ValueError: If the value is less than zero
+ """
+ if value is not None:
+ if len(value) < 1:
+ raise ValueError("Quantity must be a non-empty string.")
+ floatValue = float(value)
+ if floatValue < 0.0:
+ raise ValueError("Quantity cannot be negative.")
+ self._quantity = 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 C{None}, the units value must be one of the values in L{VALID_BYTE_UNITS}.
+ @raise ValueError: If the value is not valid.
+ """
+ if value is not None:
+ 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
+
+ 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")
+
+
+########################################################################
# ActionDependencies class definition
########################################################################
@@ -5743,3 +5862,68 @@
raise ValueError("%s %s" % (prefix, duplicates))
_checkUnique = staticmethod(_checkUnique)
+
+########################################################################
+# General utility functions
+########################################################################
+
+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.
+
+ @param parent: Parent node to search beneath.
+ @param name: Name of node to search for.
+
+ @return: 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)
+
+def addByteQuantityNode(xmlDom, parentNode, nodeName, byteQuantity):
+ """
+ Adds a text node as the next child of a parent, to contain a byte size.
+
+ If the C{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").
+
+ @param xmlDom: DOM tree as from C{impl.createDocument()}.
+ @param parentNode: Parent node to create child for.
+ @param nodeName: Name of the new container node.
+ @param byteQuantity: ByteQuantity object to put into the XML document
+
+ @return: 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)
Modified: cedar-backup2/trunk/CedarBackup2/extend/split.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/extend/split.py 2008-03-18 02:49:50 UTC (rev 859)
+++ cedar-backup2/trunk/CedarBackup2/extend/split.py 2008-03-18 02:55:08 UTC (rev 860)
@@ -71,6 +71,7 @@
from CedarBackup2.xmlutil import createInputDom, addContainerNode, addStringNode
from CedarBackup2.xmlutil import readFirstChild, readString
from CedarBackup2.actions.util import findDailyDirs, writeIndicatorFile, getBackupFiles
+from CedarBackup2.config import ByteQuantity, readByteQuantity, addByteQuantityNode
########################################################################
@@ -82,127 +83,8 @@
SPLIT_COMMAND = [ "split", ]
SPLIT_INDICATOR = "cback.split"
-VALID_BYTE_UNITS = [ UNIT_BYTES, UNIT_KBYTES, UNIT_MBYTES, UNIT_GBYTES, ]
-
########################################################################
-# ByteQuantity class definition
-########################################################################
-
-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.
-
- 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 want 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.
-
- @sort: __init__, __repr__, __str__, __cmp__, quantity, units
- """
-
- def __init__(self, quantity=None, units=None):
- """
- Constructor for the C{ByteQuantity} class.
-
- @param quantity: Quantity of bytes, as string ("1.25")
- @param units: Unit of bytes, one of VALID_BYTE_UNITS
-
- @raise 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 self.__repr__()
-
- def __cmp__(self, other):
- """
- Definition of equals operator for this class.
- Lists within this class are "unordered" for equality comparisons.
- @param other: Other object to compare to.
- @return: -1/0/1 depending on whether self is C{<}, C{=} or C{>} other.
- """
- if other is None:
- return 1
- if self._quantity != other._quantity:
- if self._quantity < other._quantity:
- return -1
- else:
- return 1
- if self._units != other._units:
- if self._units < other._units:
- return -1
- else:
- return 1
- return 0
-
- def _setQuantity(self, value):
- """
- Property target used to set the quantity
- The value must be a non-empty string if it is not C{None}.
- @raise ValueError: If the value is an empty string.
- @raise ValueError: If the value is not a valid floating point number
- @raise ValueError: If the value is less than zero
- """
- if value is not None:
- if len(value) < 1:
- raise ValueError("Quantity must be a non-empty string.")
- floatValue = float(value)
- if floatValue < 0.0:
- raise ValueError("Quantity cannot be negative.")
- self._quantity = 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 C{None}, the units value must be one of the values in L{VALID_BYTE_UNITS}.
- @raise ValueError: If the value is not valid.
- """
- if value is not None:
- 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
-
- 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")
-
-
-########################################################################
# SplitConfig class definition
########################################################################
@@ -461,8 +343,8 @@
"""
if self.split is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "split")
- LocalConfig._addByteQuantityNode(xmlDom, sectionNode, "size_limit", self.split.sizeLimit)
- LocalConfig._addByteQuantityNode(xmlDom, sectionNode, "split_size", self.split.splitSize)
+ addByteQuantityNode(xmlDom, sectionNode, "size_limit", self.split.sizeLimit)
+ addByteQuantityNode(xmlDom, sectionNode, "split_size", self.split.splitSize)
def _parseXmlData(self, xmlData):
"""
@@ -497,75 +379,12 @@
section = readFirstChild(parent, "split")
if section is not None:
split = SplitConfig()
- split.sizeLimit = LocalConfig._readByteQuantity(section, "size_limit")
- split.splitSize = LocalConfig._readByteQuantity(section, "split_size")
+ split.sizeLimit = readByteQuantity(section, "size_limit")
+ split.splitSize = readByteQuantity(section, "split_size")
return split
_parseSplit = staticmethod(_parseSplit)
- 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.
-
- @param parent: Parent node to search beneath.
- @param name: Name of node to search for.
-
- @return: 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)
- _readByteQuantity = staticmethod(_readByteQuantity)
-
- def _addByteQuantityNode(xmlDom, parentNode, nodeName, byteQuantity):
- """
- Adds a text node as the next child of a parent, to contain a byte size.
-
- If the C{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").
-
- @param xmlDom: DOM tree as from C{impl.createDocument()}.
- @param parentNode: Parent node to create child for.
- @param nodeName: Name of the new container node.
- @param byteQuantity: ByteQuantity object to put into the XML document
-
- @return: 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)
- _addByteQuantityNode = staticmethod(_addByteQuantityNode)
-
-
########################################################################
# Public functions
########################################################################
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2008-03-18 02:49:50 UTC (rev 859)
+++ cedar-backup2/trunk/Changelog 2008-03-18 02:55:08 UTC (rev 860)
@@ -6,6 +6,7 @@
- Add CollectDir.linkDepth attribute
- Modify collect action to obey CollectDir.linkDepth
- Update user manual to discuss new attribute
+ * Refactored ByteQuantity functionality out of split and into config.
Version 2.15.3 16 Mar 2008
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2008-03-18 22:09:59
|
Revision: 865
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=865&view=rev
Author: pronovic
Date: 2008-03-18 15:09:56 -0700 (Tue, 18 Mar 2008)
Log Message:
-----------
Add initial unit tests for capacity
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/extend/capacity.py
cedar-backup2/trunk/util/test.py
Added Paths:
-----------
cedar-backup2/trunk/test/capacitytests.py
cedar-backup2/trunk/test/data/capacity.conf.1
cedar-backup2/trunk/test/data/capacity.conf.2
cedar-backup2/trunk/test/data/capacity.conf.3
cedar-backup2/trunk/test/data/capacity.conf.4
Modified: cedar-backup2/trunk/CedarBackup2/extend/capacity.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/extend/capacity.py 2008-03-18 21:26:48 UTC (rev 864)
+++ cedar-backup2/trunk/CedarBackup2/extend/capacity.py 2008-03-18 22:09:56 UTC (rev 865)
@@ -52,12 +52,12 @@
########################################################################
# System modules
-import sys
-import os
import logging
# Cedar Backup modules
from CedarBackup2.config import ByteQuantity, readByteQuantity, addByteQuantityNode
+from CedarBackup2.xmlutil import createInputDom, addContainerNode, addStringNode
+from CedarBackup2.xmlutil import readFirstChild, readString
from CedarBackup2.actions.util import createWriter, checkMediaState
@@ -105,7 +105,7 @@
"""
Official string representation for class instance.
"""
- return "Percentage(%s)" % (self.quantity)
+ return "PercentageQuantity(%s)" % (self.quantity)
def __str__(self):
"""
@@ -175,26 +175,26 @@
- The maximum percentage utilized must be a PercentageQuantity
- The minimum bytes remaining must be a ByteQuantity
- @sort: __init__, __repr__, __str__, __cmp__, percentage, bytes
+ @sort: __init__, __repr__, __str__, __cmp__, maxPercentage, minBytes
"""
- def __init__(self, percentage, bytes):
+ def __init__(self, maxPercentage=None, minBytes=None):
"""
Constructor for the C{CapacityConfig} class.
- @param percentage: Maximum percentage of the media that may be utilized
- @param bytes: Minimum number of free bytes that must be available
+ @param maxPercentage: Maximum percentage of the media that may be utilized
+ @param minBytes: Minimum number of free bytes that must be available
"""
- self._percentage = None
- self._bytes = None
- self.percentage = percentage
- self.bytes = bytes
+ self._maxPercentage = None
+ self._minBytes = None
+ self.maxPercentage = maxPercentage
+ self.minBytes = minBytes
def __repr__(self):
"""
Official string representation for class instance.
"""
- return "CapacityConfig(%s, %s)" % (self.percentage, self.bytes)
+ return "CapacityConfig(%s, %s)" % (self.maxPercentage, self.minBytes)
def __str__(self):
"""
@@ -210,58 +210,58 @@
"""
if other is None:
return 1
- if self._percentage != other._percentage:
- if self._percentage < other._percentage:
+ if self._maxPercentage != other._maxPercentage:
+ if self._maxPercentage < other._maxPercentage:
return -1
else:
return 1
- if self._bytes != other._bytes:
- if self._bytes < other._bytes:
+ if self._minBytes != other._minBytes:
+ if self._minBytes < other._minBytes:
return -1
else:
return 1
return 0
- def _setPercentage(self, value):
+ def _setMaxPercentage(self, value):
"""
- Property target used to set the percentage utilized value.
+ Property target used to set the maxPercentage value.
If not C{None}, the value must be a C{PercentageQuantity} object.
@raise ValueError: If the value is not a C{PercentageQuantity}
"""
if value is None:
- self._percentage = None
+ self._maxPercentage = None
else:
if not isinstance(value, PercentageQuantity):
raise ValueError("Value must be a C{PercentageQuantity} object.")
- self._percentage = value
+ self._maxPercentage = value
- def _getPercentage(self):
+ def _getMaxPercentage(self):
"""
- Property target used to get the percentage remaining value
+ Property target used to get the maxPercentage value
"""
- return self._percentage
+ return self._maxPercentage
- def _setBytes(self, value):
+ def _setMinBytes(self, value):
"""
Property target used to set the bytes utilized value.
If not C{None}, the value must be a C{ByteQuantity} object.
@raise ValueError: If the value is not a C{ByteQuantity}
"""
if value is None:
- self._bytes = None
+ self._minBytes = None
else:
if not isinstance(value, ByteQuantity):
raise ValueError("Value must be a C{ByteQuantity} object.")
- self._bytes = value
+ self._minBytes = value
- def _getBytes(self):
+ def _getMinBytes(self):
"""
- Property target used to get the bytes remaining value
+ Property target used to get the bytes remaining value.
"""
- return self._bytes
+ return self._minBytes
- percentage = property(_getPercentage, _setPercentage, None, "Maximum percentage of the media that may be utilized.")
- bytes = property(_getBytes, _setBytes, None, "Minimum number of free bytes that must be available.")
+ maxPercentage = property(_getMaxPercentage, _setMaxPercentage, None, "Maximum percentage of the media that may be utilized.")
+ minBytes = property(_getMinBytes, _setMinBytes, None, "Minimum number of free bytes that must be available.")
########################################################################
@@ -390,10 +390,10 @@
"""
if self.capacity is None:
raise ValueError("Capacity section is required.")
- if self.capacity.percentage is None and self.capacity.bytes is None:
- raise ValueError("Must provide either capacity percentage or capacity bytes.")
- if self.capacity.percentage is not None and self.capacity.bytes is not None:
- raise ValueError("Must provide either capacity percentage or capacity bytes, but not both.")
+ if self.capacity.maxPercentage is None and self.capacity.minBytes is None:
+ raise ValueError("Must provide either max percentage or min bytes.")
+ if self.capacity.maxPercentage is not None and self.capacity.minBytes is not None:
+ raise ValueError("Must provide either max percentage or min bytes, but not both.")
def addConfig(self, xmlDom, parentNode):
"""
@@ -404,17 +404,17 @@
We add the following fields to the document::
- percentage //cb_config/capacity/percentage
- bytes //cb_config/capacity/bytes
+ maxPercentage //cb_config/capacity/max_percentage
+ minBytes //cb_config/capacity/min_bytes
@param xmlDom: DOM tree as from C{impl.createDocument()}.
@param parentNode: Parent that the section should be appended to.
"""
if self.capacity is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "capacity")
- addStringNode(xmlDom, parentNode, "percentage", self.capacity.percentage)
- if self.capacity.bytes is not None: # because utility function fills in empty section on None
- addByteQuantityNode(xmlDom, sectionNode, "bytes", self.capacity.bytes)
+ LocalConfig._addPercentageQuantity(xmlDom, sectionNode, "max_percentage", self.capacity.maxPercentage)
+ if self.capacity.minBytes is not None: # because utility function fills in empty section on None
+ addByteQuantityNode(xmlDom, sectionNode, "min_bytes", self.capacity.minBytes)
def _parseXmlData(self, xmlData):
"""
@@ -437,8 +437,8 @@
We read the following fields::
- percentage //cb_config/capacity/percentage
- bytes //cb_config/capacity/bytes
+ maxPercentage //cb_config/capacity/max_percentage
+ minBytes //cb_config/capacity/min_bytes
@param parentNode: Parent node to search beneath.
@@ -449,8 +449,8 @@
section = readFirstChild(parentNode, "capacity")
if section is not None:
capacity = CapacityConfig()
- capacity.percentage = LocalConfig._readPercentageQuantity(section, "percentage")
- capacity.bytes = readByteQuantity(section, "bytes")
+ capacity.maxPercentage = LocalConfig._readPercentageQuantity(section, "max_percentage")
+ capacity.minBytes = readByteQuantity(section, "min_bytes")
return capacity
_parseCapacity = staticmethod(_parseCapacity)
@@ -462,10 +462,29 @@
@return: Percentage quantity parsed from XML document
"""
quantity = readString(parent, name)
+ if quantity is None:
+ return None
return PercentageQuantity(quantity)
_readPercentageQuantity = staticmethod(_readPercentageQuantity)
+ def _addPercentageQuantity(xmlDom, parentNode, nodeName, percentageQuantity):
+ """
+ Adds a text node as the next child of a parent, to contain a percentage quantity.
+ If the C{percentageQuantity} is None, then no node will be created.
+
+ @param xmlDom: DOM tree as from C{impl.createDocument()}.
+ @param parentNode: Parent node to create child for.
+ @param nodeName: Name of the new container node.
+ @param percentageQuantity: PercentageQuantity object to put into the XML document
+
+ @return: Reference to the newly-created node.
+ """
+ if percentageQuantity is not None:
+ addStringNode(xmlDom, parentNode, nodeName, percentageQuantity.quantity)
+ _addPercentageQuantity = staticmethod(_addPercentageQuantity)
+
+
########################################################################
# Public functions
########################################################################
@@ -498,15 +517,15 @@
checkMediaState(config.store) # raises exception if media is not initialized
writer = createWriter(config)
capacity = writer.retrieveCapacity()
- if local.capacity.percentage is not None:
- if bytesAvailable == 0:
- logger.error("Media has reached capacity limit: media is 100%% utilized." % utilized)
+ if local.capacity.maxPercentage is not None:
+ if capacity.bytesAvailable == 0:
+ logger.error("Media has reached capacity limit: media is 100%% utilized.")
else:
utilized = (float(capacity.bytesUsed) / float(capacity.bytesAvailable)) * 100.0
- if utilized > local.capacity.percentage.percentage:
+ if utilized > local.capacity.maxPercentage.percentage:
logger.error("Media has reached capacity limit: media is %.2f%% utilized." % utilized)
else: # if local.capacity.bytes is not None
- if capacity.bytesAvailable < local.capacity.bytes.bytes:
+ if capacity.bytesAvailable < local.capacity.minBytes.bytes:
logger.error("Media has reached capacity limit: only %d free bytes remain." % capacity.bytesAvailable)
logger.info("Executed the capacity extended action successfully.")
Added: cedar-backup2/trunk/test/capacitytests.py
===================================================================
--- cedar-backup2/trunk/test/capacitytests.py (rev 0)
+++ cedar-backup2/trunk/test/capacitytests.py 2008-03-18 22:09:56 UTC (rev 865)
@@ -0,0 +1,710 @@
+#!/usr/bin/env python
+# -*- 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) 2008 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 <pro...@ie...>
+# Language : Python (>= 2.3)
+# Project : Cedar Backup, release 2
+# Revision : $Id$
+# Purpose : Tests capacity extension functionality.
+#
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+########################################################################
+# Module documentation
+########################################################################
+
+"""
+Unit tests for CedarBackup2/extend/capacity.py.
+
+Code Coverage
+=============
+
+ This module contains individual tests for the the public classes implemented
+ in extend/capacity.py.
+
+Naming Conventions
+==================
+
+ I prefer to avoid large unit tests which validate more than one piece of
+ functionality, and I prefer to avoid using overly descriptive (read: long)
+ test names, as well. Instead, I use lots of very small tests that each
+ validate one specific thing. These small tests are then named with an index
+ number, yielding something like C{testAddDir_001} or C{testValidate_010}.
+ Each method has a docstring describing what it's supposed to accomplish. I
+ feel that this makes it easier to judge how important a given failure is,
+ and also makes it somewhat easier to diagnose and fix individual problems.
+
+Testing XML Extraction
+======================
+
+ It's difficult to validated that generated XML is exactly "right",
+ especially when dealing with pretty-printed XML. We can't just provide a
+ constant string and say "the result must match this". Instead, what we do
+ is extract a node, build some XML from it, and then feed that XML back into
+ another object's constructor. If that parse process succeeds and the old
+ object is equal to the new object, we assume that the extract was
+ successful.
+
+ It would arguably be better if we could do a completely independent check -
+ but implementing that check would be equivalent to re-implementing all of
+ the existing functionality that we're validating here! After all, the most
+ important thing is that data can move seamlessly from object to XML document
+ and back to object.
+
+Full vs. Reduced Tests
+======================
+
+ All of the tests in this module are considered safe to be run in an average
+ build environment. There is a no need to use a CAPACITYTESTS_FULL
+ environment variable to provide a "reduced feature set" test suite as for
+ some of the other test modules.
+
+@author Kenneth J. Pronovici <pro...@ie...>
+"""
+
+
+########################################################################
+# Import modules and do runtime validations
+########################################################################
+
+# System modules
+import unittest
+import os
+
+# Cedar Backup modules
+from CedarBackup2.util import UNIT_BYTES, UNIT_KBYTES, UNIT_MBYTES, UNIT_GBYTES
+from CedarBackup2.testutil import hexFloatLiteralAllowed, findResources, failUnlessAssignRaises
+from CedarBackup2.xmlutil import createOutputDom, serializeDom
+from CedarBackup2.extend.capacity import LocalConfig, CapacityConfig, ByteQuantity, PercentageQuantity
+
+
+#######################################################################
+# Module-wide configuration and constants
+#######################################################################
+
+DATA_DIRS = [ "./data", "./test/data", ]
+RESOURCES = [ "capacity.conf.1", "capacity.conf.2", "capacity.conf.3", "capacity.conf.4", ]
+
+
+#######################################################################
+# Test Case Classes
+#######################################################################
+
+##########################
+# TestCapacityConfig class
+##########################
+
+class TestCapacityConfig(unittest.TestCase):
+
+ """Tests for the CapacityConfig class."""
+
+ ##################
+ # Utility methods
+ ##################
+
+ def failUnlessAssignRaises(self, exception, object, property, value):
+ """Equivalent of L{failUnlessRaises}, but used for property assignments instead."""
+ failUnlessAssignRaises(self, exception, object, property, value)
+
+
+ ############################
+ # Test __repr__ and __str__
+ ############################
+
+ def testStringFuncs_001(self):
+ """
+ Just make sure that the string functions don't have errors (i.e. bad variable names).
+ """
+ obj = CapacityConfig()
+ obj.__repr__()
+ obj.__str__()
+
+
+ ##################################
+ # Test constructor and attributes
+ ##################################
+
+ def testConstructor_001(self):
+ """
+ Test constructor with no values filled in.
+ """
+ capacity = CapacityConfig()
+ self.failUnlessEqual(None, capacity.maxPercentage)
+ self.failUnlessEqual(None, capacity.minBytes)
+
+ def testConstructor_002(self):
+ """
+ Test constructor with all values filled in, with valid values.
+ """
+ capacity = CapacityConfig(PercentageQuantity("63.2"), ByteQuantity("2.0", UNIT_KBYTES))
+ self.failUnlessEqual(PercentageQuantity("63.2"), capacity.maxPercentage)
+ self.failUnlessEqual(ByteQuantity("2.0", UNIT_KBYTES), capacity.minBytes)
+
+ def testConstructor_003(self):
+ """
+ Test assignment of maxPercentage attribute, None value.
+ """
+ capacity = CapacityConfig(maxPercentage=PercentageQuantity("63.2"))
+ self.failUnlessEqual(PercentageQuantity("63.2"), capacity.maxPercentage)
+ capacity.maxPercentage = None
+ self.failUnlessEqual(None, capacity.maxPercentage)
+
+ def testConstructor_004(self):
+ """
+ Test assignment of maxPercentage attribute, valid value.
+ """
+ capacity = CapacityConfig()
+ self.failUnlessEqual(None, capacity.maxPercentage)
+ capacity.maxPercentage = PercentageQuantity("63.2")
+ self.failUnlessEqual(PercentageQuantity("63.2"), capacity.maxPercentage)
+
+ def testConstructor_005(self):
+ """
+ Test assignment of maxPercentage attribute, invalid value (empty).
+ """
+ capacity = CapacityConfig()
+ self.failUnlessEqual(None, capacity.maxPercentage)
+ self.failUnlessAssignRaises(ValueError, capacity, "maxPercentage", "")
+ self.failUnlessEqual(None, capacity.maxPercentage)
+
+ def testConstructor_006(self):
+ """
+ Test assignment of maxPercentage attribute, invalid value (not a PercentageQuantity).
+ """
+ capacity = CapacityConfig()
+ self.failUnlessEqual(None, capacity.maxPercentage)
+ self.failUnlessAssignRaises(ValueError, capacity, "maxPercentage", "1.0 GB")
+ self.failUnlessEqual(None, capacity.maxPercentage)
+
+ def testConstructor_007(self):
+ """
+ Test assignment of minBytes attribute, None value.
+ """
+ capacity = CapacityConfig(minBytes=ByteQuantity("1.00", UNIT_KBYTES))
+ self.failUnlessEqual(ByteQuantity("1.00", UNIT_KBYTES), capacity.minBytes)
+ capacity.minBytes = None
+ self.failUnlessEqual(None, capacity.minBytes)
+
+ def testConstructor_008(self):
+ """
+ Test assignment of minBytes attribute, valid value.
+ """
+ capacity = CapacityConfig()
+ self.failUnlessEqual(None, capacity.minBytes)
+ capacity.minBytes = ByteQuantity("1.00", UNIT_KBYTES)
+ self.failUnlessEqual(ByteQuantity("1.00", UNIT_KBYTES), capacity.minBytes)
+
+ def testConstructor_009(self):
+ """
+ Test assignment of minBytes attribute, invalid value (empty).
+ """
+ capacity = CapacityConfig()
+ self.failUnlessEqual(None, capacity.minBytes)
+ self.failUnlessAssignRaises(ValueError, capacity, "minBytes", "")
+ self.failUnlessEqual(None, capacity.minBytes)
+
+ def testConstructor_010(self):
+ """
+ Test assignment of minBytes attribute, invalid value (not a ByteQuantity).
+ """
+ capacity = CapacityConfig()
+ self.failUnlessEqual(None, capacity.minBytes)
+ self.failUnlessAssignRaises(ValueError, capacity, "minBytes", 12)
+ self.failUnlessEqual(None, capacity.minBytes)
+
+
+ ############################
+ # Test comparison operators
+ ############################
+
+ def testComparison_001(self):
+ """
+ Test comparison of two identical objects, all attributes None.
+ """
+ capacity1 = CapacityConfig()
+ capacity2 = CapacityConfig()
+ self.failUnlessEqual(capacity1, capacity2)
+ self.failUnless(capacity1 == capacity2)
+ self.failUnless(not capacity1 < capacity2)
+ self.failUnless(capacity1 <= capacity2)
+ self.failUnless(not capacity1 > capacity2)
+ self.failUnless(capacity1 >= capacity2)
+ self.failUnless(not capacity1 != capacity2)
+
+ def testComparison_002(self):
+ """
+ Test comparison of two identical objects, all attributes non-None.
+ """
+ capacity1 = CapacityConfig(PercentageQuantity("63.2"), ByteQuantity("1.00", UNIT_MBYTES))
+ capacity2 = CapacityConfig(PercentageQuantity("63.2"), ByteQuantity("1.00", UNIT_MBYTES))
+ self.failUnlessEqual(capacity1, capacity2)
+ self.failUnless(capacity1 == capacity2)
+ self.failUnless(not capacity1 < capacity2)
+ self.failUnless(capacity1 <= capacity2)
+ self.failUnless(not capacity1 > capacity2)
+ self.failUnless(capacity1 >= capacity2)
+ self.failUnless(not capacity1 != capacity2)
+
+ def testComparison_003(self):
+ """
+ Test comparison of two differing objects, maxPercentage differs (one None).
+ """
+ capacity1 = CapacityConfig()
+ capacity2 = CapacityConfig(maxPercentage=PercentageQuantity("63.2"))
+ self.failIfEqual(capacity1, capacity2)
+ self.failUnless(not capacity1 == capacity2)
+ self.failUnless(capacity1 < capacity2)
+ self.failUnless(capacity1 <= capacity2)
+ self.failUnless(not capacity1 > capacity2)
+ self.failUnless(not capacity1 >= capacity2)
+ self.failUnless(capacity1 != capacity2)
+
+ def testComparison_004(self):
+ """
+ Test comparison of two differing objects, maxPercentage differs.
+ """
+ capacity1 = CapacityConfig(PercentageQuantity("15.0"), ByteQuantity("1.00", UNIT_MBYTES))
+ capacity2 = CapacityConfig(PercentageQuantity("63.2"), ByteQuantity("1.00", UNIT_MBYTES))
+ self.failIfEqual(capacity1, capacity2)
+ self.failUnless(not capacity1 == capacity2)
+ self.failUnless(capacity1 < capacity2)
+ self.failUnless(capacity1 <= capacity2)
+ self.failUnless(not capacity1 > capacity2)
+ self.failUnless(not capacity1 >= capacity2)
+ self.failUnless(capacity1 != capacity2)
+
+ def testComparison_005(self):
+ """
+ Test comparison of two differing objects, minBytes differs (one None).
+ """
+ capacity1 = CapacityConfig()
+ capacity2 = CapacityConfig(minBytes=ByteQuantity("1.00", UNIT_MBYTES))
+ self.failIfEqual(capacity1, capacity2)
+ self.failUnless(not capacity1 == capacity2)
+ self.failUnless(capacity1 < capacity2)
+ self.failUnless(capacity1 <= capacity2)
+ self.failUnless(not capacity1 > capacity2)
+ self.failUnless(not capacity1 >= capacity2)
+ self.failUnless(capacity1 != capacity2)
+
+ def testComparison_006(self):
+ """
+ Test comparison of two differing objects, minBytes differs.
+ """
+ capacity1 = CapacityConfig(PercentageQuantity("63.2"), ByteQuantity("0.5", UNIT_MBYTES))
+ capacity2 = CapacityConfig(PercentageQuantity("63.2"), ByteQuantity("1.00", UNIT_MBYTES))
+ self.failIfEqual(capacity1, capacity2)
+ self.failUnless(not capacity1 == capacity2)
+ self.failUnless(capacity1 < capacity2)
+ self.failUnless(capacity1 <= capacity2)
+ self.failUnless(not capacity1 > capacity2)
+ self.failUnless(not capacity1 >= capacity2)
+ self.failUnless(capacity1 != capacity2)
+
+
+########################
+# TestLocalConfig class
+########################
+
+class TestLocalConfig(unittest.TestCase):
+
+ """Tests for the LocalConfig class."""
+
+ ################
+ # Setup methods
+ ################
+
+ def setUp(self):
+ try:
+ self.resources = findResources(RESOURCES, DATA_DIRS)
+ except Exception, e:
+ self.fail(e)
+
+ def tearDown(self):
+ pass
+
+
+ ##################
+ # Utility methods
+ ##################
+
+ def failUnlessAssignRaises(self, exception, object, property, value):
+ """Equivalent of L{failUnlessRaises}, but used for property assignments instead."""
+ failUnlessAssignRaises(self, exception, object, property, value)
+
+ def validateAddConfig(self, origConfig):
+ """
+ Validates that document dumped from C{LocalConfig.addConfig} results in
+ identical object.
+
+ We dump a document containing just the capacity configuration, and then
+ make sure that if we push that document back into the C{LocalConfig}
+ object, that the resulting object matches the original.
+
+ The C{self.failUnlessEqual} method is used for the validation, so if the
+ method call returns normally, everything is OK.
+
+ @param origConfig: Original configuration.
+ """
+ (xmlDom, parentNode) = createOutputDom()
+ origConfig.addConfig(xmlDom, parentNode)
+ xmlData = serializeDom(xmlDom)
+ newConfig = LocalConfig(xmlData=xmlData, validate=False)
+ self.failUnlessEqual(origConfig, newConfig)
+
+
+ ############################
+ # Test __repr__ and __str__
+ ############################
+
+ def testStringFuncs_001(self):
+ """
+ Just make sure that the string functions don't have errors (i.e. bad variable names).
+ """
+ obj = LocalConfig()
+ obj.__repr__()
+ obj.__str__()
+
+
+ #####################################################
+ # Test basic constructor and attribute functionality
+ #####################################################
+
+ def testConstructor_001(self):
+ """
+ Test empty constructor, validate=False.
+ """
+ config = LocalConfig(validate=False)
+ self.failUnlessEqual(None, config.capacity)
+
+ def testConstructor_002(self):
+ """
+ Test empty constructor, validate=True.
+ """
+ config = LocalConfig(validate=True)
+ self.failUnlessEqual(None, config.capacity)
+
+ def testConstructor_003(self):
+ """
+ Test with empty config document as both data and file, validate=False.
+ """
+ path = self.resources["capacity.conf.1"]
+ contents = open(path).read()
+ self.failUnlessRaises(ValueError, LocalConfig, xmlData=contents, xmlPath=path, validate=False)
+
+ def testConstructor_004(self):
+ """
+ Test assignment of capacity attribute, None value.
+ """
+ config = LocalConfig()
+ config.capacity = None
+ self.failUnlessEqual(None, config.capacity)
+
+ def testConstructor_005(self):
+ """
+ Test assignment of capacity attribute, valid value.
+ """
+ config = LocalConfig()
+ config.capacity = CapacityConfig()
+ self.failUnlessEqual(CapacityConfig(), config.capacity)
+
+ def testConstructor_006(self):
+ """
+ Test assignment of capacity attribute, invalid value (not CapacityConfig).
+ """
+ config = LocalConfig()
+ self.failUnlessAssignRaises(ValueError, config, "capacity", "STRING!")
+
+
+ ############################
+ # Test comparison operators
+ ############################
+
+ def testComparison_001(self):
+ """
+ Test comparison of two identical objects, all attributes None.
+ """
+ config1 = LocalConfig()
+ config2 = LocalConfig()
+ self.failUnlessEqual(config1, config2)
+ self.failUnless(config1 == config2)
+ self.failUnless(not config1 < config2)
+ self.failUnless(config1 <= config2)
+ self.failUnless(not config1 > config2)
+ self.failUnless(config1 >= config2)
+ self.failUnless(not config1 != config2)
+
+ def testComparison_002(self):
+ """
+ Test comparison of two identical objects, all attributes non-None.
+ """
+ config1 = LocalConfig()
+ config1.capacity = CapacityConfig()
+
+ config2 = LocalConfig()
+ config2.capacity = CapacityConfig()
+
+ self.failUnlessEqual(config1, config2)
+ self.failUnless(config1 == config2)
+ self.failUnless(not config1 < config2)
+ self.failUnless(config1 <= config2)
+ self.failUnless(not config1 > config2)
+ self.failUnless(config1 >= config2)
+ self.failUnless(not config1 != config2)
+
+ def testComparison_003(self):
+ """
+ Test comparison of two differing objects, capacity differs (one None).
+ """
+ config1 = LocalConfig()
+ config2 = LocalConfig()
+ config2.capacity = CapacityConfig()
+ self.failIfEqual(config1, config2)
+ self.failUnless(not config1 == config2)
+ self.failUnless(config1 < config2)
+ self.failUnless(config1 <= config2)
+ self.failUnless(not config1 > config2)
+ self.failUnless(not config1 >= config2)
+ self.failUnless(config1 != config2)
+
+ def testComparison_004(self):
+ """
+ Test comparison of two differing objects, capacity differs.
+ """
+ config1 = LocalConfig()
+ config1.capacity = CapacityConfig(minBytes=ByteQuantity("0.1", UNIT_MBYTES))
+
+ config2 = LocalConfig()
+ config2.capacity = CapacityConfig(minBytes=ByteQuantity("1.00", UNIT_MBYTES))
+
+ self.failIfEqual(config1, config2)
+ self.failUnless(not ...
[truncated message content] |
|
From: <pro...@us...> - 2008-03-18 23:46:08
|
Revision: 866
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=866&view=rev
Author: pronovic
Date: 2008-03-18 16:45:43 -0700 (Tue, 18 Mar 2008)
Log Message:
-----------
Add unit tests for the new ByteQuantity.bytes attribute
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/config.py
cedar-backup2/trunk/test/configtests.py
Modified: cedar-backup2/trunk/CedarBackup2/config.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/config.py 2008-03-18 22:09:56 UTC (rev 865)
+++ cedar-backup2/trunk/CedarBackup2/config.py 2008-03-18 23:45:43 UTC (rev 866)
@@ -394,8 +394,11 @@
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.
"""
- return float(convertSize(self.quantity, self.units, UNIT_BYTES))
+ 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")
Modified: cedar-backup2/trunk/test/configtests.py
===================================================================
--- cedar-backup2/trunk/test/configtests.py 2008-03-18 22:09:56 UTC (rev 865)
+++ cedar-backup2/trunk/test/configtests.py 2008-03-18 23:45:43 UTC (rev 866)
@@ -170,6 +170,7 @@
quantity = ByteQuantity()
self.failUnlessEqual(None, quantity.quantity)
self.failUnlessEqual(None, quantity.units)
+ self.failUnlessEqual(0.0, quantity.bytes)
def testConstructor_002(self):
"""
@@ -185,33 +186,45 @@
"""
quantity = ByteQuantity(quantity="1.0")
self.failUnlessEqual("1.0", quantity.quantity)
+ self.failUnlessEqual(0.0, quantity.bytes) # because no units are set
quantity.quantity = None
self.failUnlessEqual(None, quantity.quantity)
+ self.failUnlessEqual(0.0, quantity.bytes)
def testConstructor_004(self):
"""
Test assignment of quantity attribute, valid values.
"""
quantity = ByteQuantity()
+ quantity.units = UNIT_BYTES # so we can test the bytes attribute
self.failUnlessEqual(None, quantity.quantity)
+ self.failUnlessEqual(0.0, quantity.bytes)
quantity.quantity = "1.0"
self.failUnlessEqual("1.0", quantity.quantity)
+ self.failUnlessEqual(1.0, quantity.bytes)
quantity.quantity = ".1"
self.failUnlessEqual(".1", quantity.quantity)
+ self.failUnlessEqual(0.1, quantity.bytes)
quantity.quantity = "12"
self.failUnlessEqual("12", quantity.quantity)
+ self.failUnlessEqual(12.0, quantity.bytes)
quantity.quantity = "0.5"
self.failUnlessEqual("0.5", quantity.quantity)
+ self.failUnlessEqual(0.5, quantity.bytes)
quantity.quantity = "181281"
self.failUnlessEqual("181281", quantity.quantity)
+ self.failUnlessEqual(181281.0, quantity.bytes)
quantity.quantity = "1E6"
self.failUnlessEqual("1E6", quantity.quantity)
+ self.failUnlessEqual(1.0e6, quantity.bytes)
quantity.quantity = "0.25E2"
self.failUnlessEqual("0.25E2", quantity.quantity)
+ self.failUnlessEqual(0.25e2, quantity.bytes)
if hexFloatLiteralAllowed():
# Some interpreters allow this, some don't
quantity.quantity = "0xAC"
self.failUnlessEqual("0xAC", quantity.quantity)
+ self.failUnlessEqual(172.0, quantity.bytes)
def testConstructor_005(self):
"""
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|