[cedar-backup-svn] SF.net SVN: cedar-backup: [818] cedar-backup2/trunk
Brought to you by:
pronovic
|
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.
|