cedar-backup-svn Mailing List for Cedar Backup (Page 3)
Brought to you by:
pronovic
You can subscribe to this list here.
| 2007 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
(5) |
Dec
(50) |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2008 |
Jan
|
Feb
(5) |
Mar
(55) |
Apr
(13) |
May
(6) |
Jun
|
Jul
|
Aug
|
Sep
|
Oct
(5) |
Nov
(3) |
Dec
(3) |
| 2009 |
Jan
|
Feb
|
Mar
(11) |
Apr
|
May
|
Jun
|
Jul
|
Aug
(7) |
Sep
|
Oct
|
Nov
|
Dec
|
| 2010 |
Jan
(14) |
Feb
(2) |
Mar
|
Apr
|
May
(7) |
Jun
(8) |
Jul
(30) |
Aug
|
Sep
|
Oct
(4) |
Nov
|
Dec
|
| 2011 |
Jan
|
Feb
|
Mar
(1) |
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
(9) |
Nov
|
Dec
|
| 2013 |
Jan
|
Feb
|
Mar
(3) |
Apr
(10) |
May
(6) |
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
| 2014 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
(48) |
Nov
(1) |
Dec
|
| 2015 |
Jan
(5) |
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
(3) |
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
|
From: <pro...@us...> - 2014-10-01 20:30:23
|
Revision: 1052
http://sourceforge.net/p/cedar-backup/code/1052
Author: pronovic
Date: 2014-10-01 20:30:20 +0000 (Wed, 01 Oct 2014)
Log Message:
-----------
Fixes from debugging on daystrom
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/extend/amazons3.py
Modified: cedar-backup2/trunk/CedarBackup2/extend/amazons3.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/extend/amazons3.py 2014-10-01 20:16:04 UTC (rev 1051)
+++ cedar-backup2/trunk/CedarBackup2/extend/amazons3.py 2014-10-01 20:30:20 UTC (rev 1052)
@@ -549,7 +549,7 @@
try:
suCommand = resolveCommand(SU_COMMAND)
s3CmdCommand = resolveCommand(S3CMD_COMMAND)
- actualCommand = "%s sync --no-encrypt --recursive --delete-removed %s/ %s/" % (s3CmdCommand, emptyDir, s3BucketUrl)
+ actualCommand = "%s sync --no-encrypt --recursive --delete-removed %s/ %s/" % (s3CmdCommand[0], emptyDir, s3BucketUrl)
result = executeCommand(suCommand, [config.options.backupUser, "-c", actualCommand])[0]
if result != 0:
raise IOError("Error [%d] calling s3Cmd to clear existing backup [%s]." % (result, s3BucketUrl))
@@ -571,7 +571,7 @@
"""
suCommand = resolveCommand(SU_COMMAND)
s3CmdCommand = resolveCommand(S3CMD_COMMAND)
- actualCommand = "%s put --recursive %s/ %s/" % (s3CmdCommand, stagingDir, s3BucketUrl)
+ actualCommand = "%s put --recursive %s/ %s/" % (s3CmdCommand[0], stagingDir, s3BucketUrl)
result = executeCommand(suCommand, [config.options.backupUser, "-c", actualCommand])[0]
if result != 0:
raise IOError("Error [%d] calling s3Cmd to store staging directory [%s]." % (result, s3BucketUrl))
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2014-10-01 20:16:07
|
Revision: 1051
http://sourceforge.net/p/cedar-backup/code/1051
Author: pronovic
Date: 2014-10-01 20:16:04 +0000 (Wed, 01 Oct 2014)
Log Message:
-----------
Add tests
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/extend/amazons3.py
cedar-backup2/trunk/testcase/amazons3tests.py
cedar-backup2/trunk/testcase/data/amazons3.conf.2
Modified: cedar-backup2/trunk/CedarBackup2/extend/amazons3.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/extend/amazons3.py 2014-10-01 19:39:24 UTC (rev 1050)
+++ cedar-backup2/trunk/CedarBackup2/extend/amazons3.py 2014-10-01 20:16:04 UTC (rev 1051)
@@ -82,8 +82,8 @@
# Cedar Backup modules
from CedarBackup2.util import resolveCommand, executeCommand, isRunningAsRoot
-from CedarBackup2.xmlutil import createInputDom, addContainerNode, addStringNode
-from CedarBackup2.xmlutil import readFirstChild, readString
+from CedarBackup2.xmlutil import createInputDom, addContainerNode, addBooleanNode, addStringNode
+from CedarBackup2.xmlutil import readFirstChild, readString, readBoolean
from CedarBackup2.actions.util import writeIndicatorFile
from CedarBackup2.actions.constants import DIR_TIME_FORMAT, STAGE_INDICATOR
@@ -340,6 +340,7 @@
We add the following fields to the document::
+ warnMidnite //cb_config/amazons3/warn_midnite
s3Bucket //cb_config/amazons3/s3_bucket
@param xmlDom: DOM tree as from C{impl.createDocument()}.
@@ -347,6 +348,7 @@
"""
if self.amazons3 is not None:
sectionNode = addContainerNode(xmlDom, parentNode, "amazons3")
+ addBooleanNode(xmlDom, sectionNode, "warn_midnite", self.amazons3.warnMidnite)
addStringNode(xmlDom, sectionNode, "s3_bucket", self.amazons3.s3Bucket)
def _parseXmlData(self, xmlData):
@@ -371,6 +373,7 @@
We read the following individual fields::
+ warnMidnite //cb_config/amazons3/warn_midnite
s3Bucket //cb_config/amazons3/s3_bucket
@param parent: Parent node to search beneath.
@@ -382,6 +385,7 @@
section = readFirstChild(parent, "amazons3")
if section is not None:
amazons3 = AmazonS3Config()
+ amazons3.warnMidnite = readBoolean(section, "warn_midnite")
amazons3.s3Bucket = readString(section, "s3_bucket")
return amazons3
Modified: cedar-backup2/trunk/testcase/amazons3tests.py
===================================================================
--- cedar-backup2/trunk/testcase/amazons3tests.py 2014-10-01 19:39:24 UTC (rev 1050)
+++ cedar-backup2/trunk/testcase/amazons3tests.py 2014-10-01 20:16:04 UTC (rev 1051)
@@ -149,20 +149,23 @@
Test constructor with no values filled in.
"""
amazons3 = AmazonS3Config()
+ self.failUnlessEqual(False, amazons3.warnMidnite)
self.failUnlessEqual(None, amazons3.s3Bucket)
def testConstructor_002(self):
"""
Test constructor with all values filled in, with valid values.
"""
- amazons3 = AmazonS3Config("bucket")
+ amazons3 = AmazonS3Config(True, "bucket")
+ self.failUnlessEqual(True, amazons3.warnMidnite)
self.failUnlessEqual("bucket", amazons3.s3Bucket)
def testConstructor_003(self):
"""
Test assignment of s3Bucket attribute, None value.
"""
- amazons3 = AmazonS3Config(s3Bucket="bucket")
+ amazons3 = AmazonS3Config(warnMidnite=True, s3Bucket="bucket")
+ self.failUnlessEqual(True, amazons3.warnMidnite)
self.failUnlessEqual("bucket", amazons3.s3Bucket)
amazons3.s3Bucket = None
self.failUnlessEqual(None, amazons3.s3Bucket)
@@ -185,7 +188,35 @@
self.failUnlessAssignRaises(ValueError, amazons3, "s3Bucket", "")
self.failUnlessEqual(None, amazons3.s3Bucket)
+ def testConstructor_006(self):
+ """
+ Test assignment of warnMidnite attribute, valid value (real boolean).
+ """
+ amazons3 = AmazonS3Config()
+ self.failUnlessEqual(False, amazons3.warnMidnite)
+ amazons3.warnMidnite = True
+ self.failUnlessEqual(True, amazons3.warnMidnite)
+ amazons3.warnMidnite = False
+ self.failUnlessEqual(False, amazons3.warnMidnite)
+ def testConstructor_007(self):
+ """
+ Test assignment of warnMidnite attribute, valid value (expression).
+ """
+ amazons3 = AmazonS3Config()
+ self.failUnlessEqual(False, amazons3.warnMidnite)
+ amazons3.warnMidnite = 0
+ self.failUnlessEqual(False, amazons3.warnMidnite)
+ amazons3.warnMidnite = []
+ self.failUnlessEqual(False, amazons3.warnMidnite)
+ amazons3.warnMidnite = None
+ self.failUnlessEqual(False, amazons3.warnMidnite)
+ amazons3.warnMidnite = ['a']
+ self.failUnlessEqual(True, amazons3.warnMidnite)
+ amazons3.warnMidnite = 3
+ self.failUnlessEqual(True, amazons3.warnMidnite)
+
+
############################
# Test comparison operators
############################
@@ -236,8 +267,8 @@
"""
Test comparison of two differing objects, s3Bucket differs.
"""
- amazons31 = AmazonS3Config("bucket1")
- amazons32 = AmazonS3Config("bucket2")
+ amazons31 = AmazonS3Config(True, "bucket1")
+ amazons32 = AmazonS3Config(True, "bucket2")
self.failIfEqual(amazons31, amazons32)
self.failUnless(not amazons31 == amazons32)
self.failUnless(amazons31 < amazons32)
@@ -246,7 +277,21 @@
self.failUnless(not amazons31 >= amazons32)
self.failUnless(amazons31 != amazons32)
+ def testComparison_005(self):
+ """
+ Test comparison of two differing objects, warnMidnite differs.
+ """
+ amazons31 = AmazonS3Config(warnMidnite=False)
+ amazons32 = AmazonS3Config(warnMidnite=True)
+ self.failIfEqual(amazons31, amazons32)
+ self.failUnless(not amazons31 == amazons32)
+ self.failUnless(amazons31 < amazons32)
+ self.failUnless(amazons31 <= amazons32)
+ self.failUnless(not amazons31 > amazons32)
+ self.failUnless(not amazons31 >= amazons32)
+ self.failUnless(amazons31 != amazons32)
+
########################
# TestLocalConfig class
########################
@@ -414,13 +459,13 @@
def testComparison_004(self):
"""
- Test comparison of two differing objects, s3Bucket differs.
+ Test comparison of two differing objects, s3Bucket differs.
"""
config1 = LocalConfig()
- config1.amazons3 = AmazonS3Config("bucket1")
+ config1.amazons3 = AmazonS3Config(True, "bucket1")
config2 = LocalConfig()
- config2.amazons3 = AmazonS3Config("bucket2")
+ config2.amazons3 = AmazonS3Config(True, "bucket2")
self.failIfEqual(config1, config2)
self.failUnless(not config1 == config2)
@@ -464,7 +509,7 @@
Test validate on a non-empty amazons3 section with valid values filled in.
"""
config = LocalConfig()
- config.amazons3 = AmazonS3Config("bucket")
+ config.amazons3 = AmazonS3Config(True, "bucket")
config.validate()
@@ -493,9 +538,11 @@
contents = open(path).read()
config = LocalConfig(xmlPath=path, validate=False)
self.failIfEqual(None, config.amazons3)
+ self.failUnlessEqual(True, config.amazons3.warnMidnite)
self.failUnlessEqual("mybucket", config.amazons3.s3Bucket)
config = LocalConfig(xmlData=contents, validate=False)
self.failIfEqual(None, config.amazons3)
+ self.failUnlessEqual(True, config.amazons3.warnMidnite)
self.failUnlessEqual("mybucket", config.amazons3.s3Bucket)
@@ -516,7 +563,7 @@
"""
Test with values set.
"""
- amazons3 = AmazonS3Config("bucket")
+ amazons3 = AmazonS3Config(True, "bucket")
config = LocalConfig()
config.amazons3 = amazons3
self.validateAddConfig(config)
Modified: cedar-backup2/trunk/testcase/data/amazons3.conf.2
===================================================================
--- cedar-backup2/trunk/testcase/data/amazons3.conf.2 2014-10-01 19:39:24 UTC (rev 1050)
+++ cedar-backup2/trunk/testcase/data/amazons3.conf.2 2014-10-01 20:16:04 UTC (rev 1051)
@@ -2,6 +2,7 @@
<!-- Valid document -->
<cb_config>
<amazons3>
+ <warn_midnite>Y</warn_midnite>
<s3_bucket>mybucket</s3_bucket>
</amazons3>
</cb_config>
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2014-10-01 19:39:35
|
Revision: 1050
http://sourceforge.net/p/cedar-backup/code/1050
Author: pronovic
Date: 2014-10-01 19:39:24 +0000 (Wed, 01 Oct 2014)
Log Message:
-----------
Continued development on amazons3
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/extend/amazons3.py
Modified: cedar-backup2/trunk/CedarBackup2/extend/amazons3.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/extend/amazons3.py 2014-10-01 02:08:09 UTC (rev 1049)
+++ cedar-backup2/trunk/CedarBackup2/extend/amazons3.py 2014-10-01 19:39:24 UTC (rev 1050)
@@ -43,25 +43,28 @@
to be run immediately after the standard stage action, replacing the standard
store action. Aside from its own configuration, it requires the options and
staging configuration sections in the standard Cedar Backup configuration file.
+Since it is intended to replace the store action, it does not rely on any store
+configuration.
-This extension relies on the U{{Amazon S3Tools} <http://s3tools.org/>} package.
-It is a very thin wrapper around the C{s3cmd put} command. Before you use this
-extension, you need to set up your Amazon S3 account and configure C{s3cmd} as
-detailed in the U{{HOWTO} <http://s3tools.org/s3cmd-howto>}. The configured
-backup user will run the C{s3cmd} program, so make sure you configure S3 Tools
-as that user, and not root.
+The underlying functionality relies on the U{{Amazon S3Tools} <http://s3tools.org/>}
+package. It is a very thin wrapper around the C{s3cmd put} command. Before
+you use this extension, you need to set up your Amazon S3 account and configure
+C{s3cmd} as detailed in the U{{HOWTO} <http://s3tools.org/s3cmd-howto>}. The
+extension assumes that the backup is being executed as root, and switches over
+to the configured backup user to run the C{s3cmd} program. So, make sure you
+configure S3 Tools as the backup user and not root.
It's up to you how to configure the S3 Tools connection to Amazon, but I
-recommend that you configure GPG encrpytion using a strong passphrase. One way
-to generate a strong passphrase is using your random number generator, i.e.
-C{dd if=/dev/urandom count=20 bs=1 | xxd -ps}. (See U{{StackExchange}
+recommend that you configure GPG encryption using a strong passphrase. One way
+to generate a strong passphrase is using your system random number generator,
+i.e. C{dd if=/dev/urandom count=20 bs=1 | xxd -ps}. (See U{{StackExchange}
<http://security.stackexchange.com/questions/14867/gpg-encryption-security>}
-for more details about that advice.) If decide to use encryption, make sure you
-save off the passphrase in a safe place, so you can get at your backup data
+for more details about that advice.) If you decide to use encryption, make sure
+you save off the passphrase in a safe place, so you can get at your backup data
later if you need to.
-This extension was written for and tested on Linux. I do not expect it to
-work on non-UNIX platforms.
+This extension was written for and tested on Linux. It will throw an exception
+if run on Windows.
@author: Kenneth J. Pronovici <pro...@ie...>
"""
@@ -71,15 +74,18 @@
########################################################################
# System modules
+import sys
import os
import logging
import tempfile
+import datetime
# Cedar Backup modules
-from CedarBackup2.util import resolveCommand, executeCommand
+from CedarBackup2.util import resolveCommand, executeCommand, isRunningAsRoot
from CedarBackup2.xmlutil import createInputDom, addContainerNode, addStringNode
from CedarBackup2.xmlutil import readFirstChild, readString
-from CedarBackup2.actions.util import findDailyDirs, writeIndicatorFile
+from CedarBackup2.actions.util import writeIndicatorFile
+from CedarBackup2.actions.constants import DIR_TIME_FORMAT, STAGE_INDICATOR
########################################################################
@@ -88,7 +94,9 @@
logger = logging.getLogger("CedarBackup2.log.extend.amazons3")
+SU_COMMAND = [ "su" ]
S3CMD_COMMAND = [ "s3cmd", ]
+
STORE_INDICATOR = "cback.amazons3"
@@ -101,32 +109,35 @@
"""
Class representing Amazon S3 configuration.
- Amazon S3 configuration is used for storing staging directories
- in Amazon's cloud storage using the C{s3cmd} tool.
+ Amazon S3 configuration is used for storing backup data in Amazon's S3 cloud
+ storage using the C{s3cmd} tool.
The following restrictions exist on data in this class:
- The s3Bucket value must be a non-empty string
- @sort: __init__, __repr__, __str__, __cmp__, s3Bucket
+ @sort: __init__, __repr__, __str__, __cmp__, warnMidnite, s3Bucket
"""
- def __init__(self, s3Bucket=None):
+ def __init__(self, warnMidnite=None, s3Bucket=None):
"""
Constructor for the C{AmazonS3Config} class.
@param s3Bucket: Name of the Amazon S3 bucket in which to store the data
+ @param warnMidnite: Whether to generate warnings for crossing midnite.
@raise ValueError: If one of the values is invalid.
"""
+ self._warnMidnite = None
self._s3Bucket = None
+ self.warnMidnite = warnMidnite
self.s3Bucket = s3Bucket
def __repr__(self):
"""
Official string representation for class instance.
"""
- return "AmazonS3Config(%s)" % (self.s3Bucket)
+ return "AmazonS3Config(%s, %s)" % (self.warnMidnite, self.s3Bucket)
def __str__(self):
"""
@@ -137,12 +148,16 @@
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.warnMidnite != other.warnMidnite:
+ if self.warnMidnite < other.warnMidnite:
+ return -1
+ else:
+ return 1
if self.s3Bucket != other.s3Bucket:
if self.s3Bucket < other.s3Bucket:
return -1
@@ -150,6 +165,22 @@
return 1
return 0
+ def _setWarnMidnite(self, value):
+ """
+ Property target used to set the midnite warning flag.
+ No validations, but we normalize the value to C{True} or C{False}.
+ """
+ if value:
+ self._warnMidnite = True
+ else:
+ self._warnMidnite = False
+
+ def _getWarnMidnite(self):
+ """
+ Property target used to get the midnite warning flag.
+ """
+ return self._warnMidnite
+
def _setS3Bucket(self, value):
"""
Property target used to set the S3 bucket.
@@ -165,7 +196,8 @@
"""
return self._s3Bucket
- s3Bucket = property(_getS3Bucket, _setS3Bucket, None, doc="Amazon S3 Bucket")
+ warnMidnite = property(_getWarnMidnite, _setWarnMidnite, None, "Whether to generate warnings for crossing midnite.")
+ s3Bucket = property(_getS3Bucket, _setS3Bucket, None, doc="Amazon S3 Bucket in which to store data")
########################################################################
@@ -379,88 +411,165 @@
@raise IOError: If there are I/O problems reading or writing files
"""
logger.debug("Executing amazons3 extended action.")
+ if not isRunningAsRoot():
+ logger.error("Error: the amazons3 extended action must be run as root.")
+ raise ValueError("The amazons3 extended action must be run as root.")
+ if sys.platform == "win32":
+ logger.error("Error: the amazons3 extended action is not supported on Windows.")
+ raise ValueError("The amazons3 extended action is not supported on Windows.")
if config.options is None or config.stage is None:
raise ValueError("Cedar Backup configuration is not properly filled in.")
local = LocalConfig(xmlPath=configPath)
- dailyDirs = findDailyDirs(config.stage.targetDir, STORE_INDICATOR)
- for dailyDir in dailyDirs:
- _storeDailyDir(config.stage.targetDir, dailyDir, local.amazons3.s3Bucket)
- writeIndicatorFile(dailyDir, STORE_INDICATOR, config.options.backupUser, config.options.backupGroup)
+ stagingDirs = _findCorrectDailyDir(options, config, local)
+ _writeToAmazonS3(config, local, stagingDirs)
+ _writeStoreIndicator(config, stagingDirs)
logger.info("Executed the amazons3 extended action successfully.")
########################################################################
-# Utility functions
+# Private utility functions
########################################################################
-############################
-# _storeDailyDir() function
-############################
+#########################
+# _findCorrectDailyDir()
+#########################
-def _storeDailyDir(stagingDir, dailyDir, s3Bucket):
+def _findCorrectDailyDir(options, config, local):
"""
- Store the contents of a daily staging directory to a bucket in the Amazon S3 cloud.
- @param stagingDir: Configured staging directory (config.targetDir)
- @param dailyDir: Daily directory to store in the cloud
- @param s3Bucket: The Amazon S3 bucket to use as the target
+ Finds the correct daily staging directory to be written to Amazon S3.
+
+ This is substantially similar to the same function in store.py. The
+ main difference is that it doesn't rely on store configuration at all.
+
+ @param options: Options object.
+ @param config: Config object.
+ @param local: Local config object.
+
+ @return: Correct staging dir, as a dict mapping directory to date suffix.
+ @raise IOError: If the staging directory cannot be found.
"""
- s3BucketUrl = _deriveS3BucketUrl(stagingDir, dailyDir, s3Bucket)
- _clearExistingBackup(s3BucketUrl)
- _writeDailyDir(dailyDir, s3BucketUrl)
+ oneDay = datetime.timedelta(days=1)
+ today = datetime.date.today()
+ yesterday = today - oneDay
+ tomorrow = today + oneDay
+ todayDate = today.strftime(DIR_TIME_FORMAT)
+ yesterdayDate = yesterday.strftime(DIR_TIME_FORMAT)
+ tomorrowDate = tomorrow.strftime(DIR_TIME_FORMAT)
+ todayPath = os.path.join(config.stage.targetDir, todayDate)
+ yesterdayPath = os.path.join(config.stage.targetDir, yesterdayDate)
+ tomorrowPath = os.path.join(config.stage.targetDir, tomorrowDate)
+ todayStageInd = os.path.join(todayPath, STAGE_INDICATOR)
+ yesterdayStageInd = os.path.join(yesterdayPath, STAGE_INDICATOR)
+ tomorrowStageInd = os.path.join(tomorrowPath, STAGE_INDICATOR)
+ todayStoreInd = os.path.join(todayPath, STORE_INDICATOR)
+ yesterdayStoreInd = os.path.join(yesterdayPath, STORE_INDICATOR)
+ tomorrowStoreInd = os.path.join(tomorrowPath, STORE_INDICATOR)
+ if options.full:
+ if os.path.isdir(todayPath) and os.path.exists(todayStageInd):
+ logger.info("Amazon S3 process will use current day's staging directory [%s]" % todayPath)
+ return { todayPath:todayDate }
+ raise IOError("Unable to find staging directory to process (only tried today due to full option).")
+ else:
+ if os.path.isdir(todayPath) and os.path.exists(todayStageInd) and not os.path.exists(todayStoreInd):
+ logger.info("Amazon S3 process will use current day's staging directory [%s]" % todayPath)
+ return { todayPath:todayDate }
+ elif os.path.isdir(yesterdayPath) and os.path.exists(yesterdayStageInd) and not os.path.exists(yesterdayStoreInd):
+ logger.info("Amazon S3 process will use previous day's staging directory [%s]" % yesterdayPath)
+ if local.amazons3.warnMidnite:
+ logger.warn("Warning: Amazon S3 process crossed midnite boundary to find data.")
+ return { yesterdayPath:yesterdayDate }
+ elif os.path.isdir(tomorrowPath) and os.path.exists(tomorrowStageInd) and not os.path.exists(tomorrowStoreInd):
+ logger.info("Amazon S3 process will use next day's staging directory [%s]" % tomorrowPath)
+ if local.amazons3.warnMidnite:
+ logger.warn("Warning: Amazon S3 process crossed midnite boundary to find data.")
+ return { tomorrowPath:tomorrowDate }
+ raise IOError("Unable to find unused staging directory to process (tried today, yesterday, tomorrow).")
##############################
-# _deriveBucketUrl() function
+# _writeToAmazonS3() function
##############################
-def _deriveS3BucketUrl(stagingDir, dailyDir, s3Bucket):
+def _writeToAmazonS3(config, local, stagingDirs):
"""
- Derive the correct bucket URL for a daily directory.
- @param stagingDir: Configured staging directory (config.targetDir)
- @param dailyDir: Daily directory to store
- @param s3Bucket: The Amazon S3 bucket to use as the target
- @return: S3 bucket URL, with no trailing slash
+ Writes the indicated staging directories to an Amazon S3 bucket.
+
+ Each of the staging directories listed in C{stagingDirs} will be written to
+ the configured Amazon S3 bucket from local configuration. The directories
+ will be placed into the image at the root by date, so staging directory
+ C{/opt/stage/2005/02/10} will be placed into the S3 bucket at C{/2005/02/10}.
+
+ @param config: Config object.
+ @param local: Local config object.
+ @param stagingDirs: Dictionary mapping directory path to date suffix.
+
+ @raise ValueError: Under many generic error conditions
+ @raise IOError: If there is a problem writing to Amazon S3
"""
- subdir = dailyDir.replace(stagingDir, "")
- if subdir.startswith("/"):
- subdir = subdir[1:]
- return "s3://%s/%s" % (s3Bucket, dailyDir)
+ for stagingDir in stagingDirs.keys():
+ logger.debug("Storing stage directory to Amazon S3 [%s]." % stagingDir)
+ dateSuffix = stagingDirs[stagingDir]
+ s3BucketUrl = "s3://%s/%s" % (local.amazons3.s3Bucket, dateSuffix)
+ logger.debug("S3 bucket URL is [%s]" % s3BucketUrl)
+ _clearExistingBackup(config, s3BucketUrl)
+ _writeStagingDir(config, stagingDir, s3BucketUrl)
##################################
+# _writeStoreIndicator() function
+##################################
+
+def _writeStoreIndicator(config, stagingDirs):
+ """
+ Writes a store indicator file into staging directories.
+ @param config: Config object.
+ @param stagingDirs: Dictionary mapping directory path to date suffix.
+ """
+ for stagingDir in stagingDirs.keys():
+ writeIndicatorFile(stagingDir, STORE_INDICATOR,
+ config.options.backupUser,
+ config.options.backupGroup)
+
+
+##################################
# _clearExistingBackup() function
##################################
-def _clearExistingBackup(s3BucketUrl):
+def _clearExistingBackup(config, s3BucketUrl):
"""
- Clear any existing backup files for a daily directory.
- @param s3BucketUrl: S3 bucket URL derived for the daily directory
+ Clear any existing backup files for an S3 bucket URL.
+ @param config: Config object.
+ @param s3BucketUrl: S3 bucket URL derived for the staging directory
"""
- emptydir = tempfile.mkdtemp()
+ emptyDir = tempfile.mkdtemp()
try:
- command = resolveCommand(S3CMD_COMMAND)
- args = [ "sync", "--no-encrypt", "--recursive", "--delete-removed", emptydir + "/", s3BucketUrl + "/", ]
- result = executeCommand(command, args)[0]
+ suCommand = resolveCommand(SU_COMMAND)
+ s3CmdCommand = resolveCommand(S3CMD_COMMAND)
+ actualCommand = "%s sync --no-encrypt --recursive --delete-removed %s/ %s/" % (s3CmdCommand, emptyDir, s3BucketUrl)
+ result = executeCommand(suCommand, [config.options.backupUser, "-c", actualCommand])[0]
if result != 0:
raise IOError("Error [%d] calling s3Cmd to clear existing backup [%s]." % (result, s3BucketUrl))
finally:
- if os.path.exists(emptydir):
- os.rmdir(emptydir)
+ if os.path.exists(emptyDir):
+ os.rmdir(emptyDir)
-############################
-# _writeDailyDir() function
-############################
+###########################
+# _writeStaging() function
+###########################
-def _writeDailyDir(dailyDir, s3BucketUrl):
+def _writeStagingDir(config, stagingDir, s3BucketUrl):
"""
- Write the daily directory out to the Amazon S3 cloud.
- @param dailyDir: Daily directory to store
- @param s3BucketUrl: S3 bucket URL derived for the daily directory
+ Write a staging directory out to the Amazon S3 cloud.
+ @param config: Config object.
+ @param stagingDir: Staging directory to write
+ @param s3BucketUrl: S3 bucket URL derived for the staging directory
"""
- command = resolveCommand(S3CMD_COMMAND)
- args = [ "put", "--recursive", dailyDir + "/", s3BucketUrl + "/", ]
- result = executeCommand(command, args)[0]
+ suCommand = resolveCommand(SU_COMMAND)
+ s3CmdCommand = resolveCommand(S3CMD_COMMAND)
+ actualCommand = "%s put --recursive %s/ %s/" % (s3CmdCommand, stagingDir, s3BucketUrl)
+ result = executeCommand(suCommand, [config.options.backupUser, "-c", actualCommand])[0]
if result != 0:
- raise IOError("Error [%d] calling s3Cmd to store daily directory [%s]." % (result, s3BucketUrl))
+ raise IOError("Error [%d] calling s3Cmd to store staging directory [%s]." % (result, s3BucketUrl))
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2014-10-01 02:08:17
|
Revision: 1049
http://sourceforge.net/p/cedar-backup/code/1049
Author: pronovic
Date: 2014-10-01 02:08:09 +0000 (Wed, 01 Oct 2014)
Log Message:
-----------
Fix warnings from 'make check'
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/extend/amazons3.py
Modified: cedar-backup2/trunk/CedarBackup2/extend/amazons3.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/extend/amazons3.py 2014-10-01 01:59:59 UTC (rev 1048)
+++ cedar-backup2/trunk/CedarBackup2/extend/amazons3.py 2014-10-01 02:08:09 UTC (rev 1049)
@@ -79,7 +79,7 @@
from CedarBackup2.util import resolveCommand, executeCommand
from CedarBackup2.xmlutil import createInputDom, addContainerNode, addStringNode
from CedarBackup2.xmlutil import readFirstChild, readString
-from CedarBackup2.actions.util import findDailyDirs, writeIndicatorFile, getBackupFiles
+from CedarBackup2.actions.util import findDailyDirs, writeIndicatorFile
########################################################################
@@ -384,7 +384,7 @@
local = LocalConfig(xmlPath=configPath)
dailyDirs = findDailyDirs(config.stage.targetDir, STORE_INDICATOR)
for dailyDir in dailyDirs:
- _storeDailyDir(dailyDir, local.amazons3.s3Bucket, config.options.backupUser, config.options.backupGroup)
+ _storeDailyDir(config.stage.targetDir, dailyDir, local.amazons3.s3Bucket)
writeIndicatorFile(dailyDir, STORE_INDICATOR, config.options.backupUser, config.options.backupGroup)
logger.info("Executed the amazons3 extended action successfully.")
@@ -397,14 +397,12 @@
# _storeDailyDir() function
############################
-def _storeDailyDir(stagingDir, dailyDir, s3Bucket, backupUser, backupGroup):
+def _storeDailyDir(stagingDir, dailyDir, s3Bucket):
"""
Store the contents of a daily staging directory to a bucket in the Amazon S3 cloud.
@param stagingDir: Configured staging directory (config.targetDir)
@param dailyDir: Daily directory to store in the cloud
@param s3Bucket: The Amazon S3 bucket to use as the target
- @param backupUser: User that target files should be owned by
- @param backupGroup: Group that target files should be owned by
"""
s3BucketUrl = _deriveS3BucketUrl(stagingDir, dailyDir, s3Bucket)
_clearExistingBackup(s3BucketUrl)
@@ -441,19 +439,20 @@
emptydir = tempfile.mkdtemp()
try:
command = resolveCommand(S3CMD_COMMAND)
- args = [ "sync", "--no-encrypt", "--recursive", "--delete-removed", emptyDir + "/", s3BucketUrl + "/", ]
+ args = [ "sync", "--no-encrypt", "--recursive", "--delete-removed", emptydir + "/", s3BucketUrl + "/", ]
result = executeCommand(command, args)[0]
if result != 0:
raise IOError("Error [%d] calling s3Cmd to clear existing backup [%s]." % (result, s3BucketUrl))
finally:
- os.rmdir(emptydir)
+ if os.path.exists(emptydir):
+ os.rmdir(emptydir)
############################
# _writeDailyDir() function
############################
-def __writeDailyDir(dailyDir, s3BucketUrl):
+def _writeDailyDir(dailyDir, s3BucketUrl):
"""
Write the daily directory out to the Amazon S3 cloud.
@param dailyDir: Daily directory to store
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2014-10-01 02:00:08
|
Revision: 1048
http://sourceforge.net/p/cedar-backup/code/1048
Author: pronovic
Date: 2014-10-01 01:59:59 +0000 (Wed, 01 Oct 2014)
Log Message:
-----------
Debugging
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/extend/amazons3.py
Modified: cedar-backup2/trunk/CedarBackup2/extend/amazons3.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/extend/amazons3.py 2014-10-01 01:44:17 UTC (rev 1047)
+++ cedar-backup2/trunk/CedarBackup2/extend/amazons3.py 2014-10-01 01:59:59 UTC (rev 1048)
@@ -423,10 +423,10 @@
@param s3Bucket: The Amazon S3 bucket to use as the target
@return: S3 bucket URL, with no trailing slash
"""
- subdir = dailyDir.replace("/opt/backup/staging", "")
+ subdir = dailyDir.replace(stagingDir, "")
if subdir.startswith("/"):
subdir = subdir[1:]
- return "s3://%s/staging/%s" % (s3Bucket, dailyDir)
+ return "s3://%s/%s" % (s3Bucket, dailyDir)
##################################
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2014-10-01 01:44:21
|
Revision: 1047
http://sourceforge.net/p/cedar-backup/code/1047
Author: pronovic
Date: 2014-10-01 01:44:17 +0000 (Wed, 01 Oct 2014)
Log Message:
-----------
Start implementing a new Amazon S3 extension
Modified Paths:
--------------
cedar-backup2/trunk/CREDITS
cedar-backup2/trunk/CedarBackup2/extend/__init__.py
cedar-backup2/trunk/CedarBackup2/release.py
cedar-backup2/trunk/Changelog
cedar-backup2/trunk/testcase/configtests.py
cedar-backup2/trunk/testcase/data/cback.conf.19
cedar-backup2/trunk/util/test.py
Added Paths:
-----------
cedar-backup2/trunk/CedarBackup2/extend/amazons3.py
cedar-backup2/trunk/testcase/amazons3tests.py
cedar-backup2/trunk/testcase/data/amazons3.conf.1
cedar-backup2/trunk/testcase/data/amazons3.conf.2
Modified: cedar-backup2/trunk/CREDITS
===================================================================
--- cedar-backup2/trunk/CREDITS 2014-10-01 01:42:33 UTC (rev 1046)
+++ cedar-backup2/trunk/CREDITS 2014-10-01 01:44:17 UTC (rev 1047)
@@ -22,10 +22,11 @@
Pronovici. Some portions have been based on other pieces of open-source
software, as indicated in the source code itself.
-Unless otherwise indicated, all Cedar Backup source code is Copyright (c)
-2004-2011,2013 Kenneth J. Pronovici and is released under the GNU General
-Public License, version 2. The contents of the GNU General Public License
-can be found in the LICENSE file, or can be downloaded from http://www.gnu.org/.
+Unless otherwise indicated, all Cedar Backup source code is Copyright
+(c) 2004-2011,2013,2014 Kenneth J. Pronovici and is released under the GNU
+General Public License, version 2. The contents of the GNU General Public
+License can be found in the LICENSE file, or can be downloaded from
+http://www.gnu.org/.
Various patches have been contributed to the Cedar Backup codebase by
Dmitry Rutsky. Major contributions include the initial implementation for
Modified: cedar-backup2/trunk/CedarBackup2/extend/__init__.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/extend/__init__.py 2014-10-01 01:42:33 UTC (rev 1046)
+++ cedar-backup2/trunk/CedarBackup2/extend/__init__.py 2014-10-01 01:44:17 UTC (rev 1047)
@@ -38,5 +38,5 @@
# Using 'from CedarBackup2.extend import *' will just import the modules listed
# in the __all__ variable.
-__all__ = [ 'encrypt', 'mbox', 'mysql', 'postgresql', 'split', 'subversion', 'sysinfo', ]
+__all__ = [ 'amazons3', 'encrypt', 'mbox', 'mysql', 'postgresql', 'split', 'subversion', 'sysinfo', ]
Added: cedar-backup2/trunk/CedarBackup2/extend/amazons3.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/extend/amazons3.py (rev 0)
+++ cedar-backup2/trunk/CedarBackup2/extend/amazons3.py 2014-10-01 01:44:17 UTC (rev 1047)
@@ -0,0 +1,467 @@
+# -*- 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) 2014 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.5)
+# Project : Official Cedar Backup Extensions
+# Revision : $Id$
+# Purpose : "Store" type extension that writes data to Amazon S3.
+#
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+########################################################################
+# Module documentation
+########################################################################
+
+"""
+Store-type extension that writes data to Amazon S3.
+
+This extension requires a new configuration section <amazons3> and is intended
+to be run immediately after the standard stage action, replacing the standard
+store action. Aside from its own configuration, it requires the options and
+staging configuration sections in the standard Cedar Backup configuration file.
+
+This extension relies on the U{{Amazon S3Tools} <http://s3tools.org/>} package.
+It is a very thin wrapper around the C{s3cmd put} command. Before you use this
+extension, you need to set up your Amazon S3 account and configure C{s3cmd} as
+detailed in the U{{HOWTO} <http://s3tools.org/s3cmd-howto>}. The configured
+backup user will run the C{s3cmd} program, so make sure you configure S3 Tools
+as that user, and not root.
+
+It's up to you how to configure the S3 Tools connection to Amazon, but I
+recommend that you configure GPG encrpytion using a strong passphrase. One way
+to generate a strong passphrase is using your random number generator, i.e.
+C{dd if=/dev/urandom count=20 bs=1 | xxd -ps}. (See U{{StackExchange}
+<http://security.stackexchange.com/questions/14867/gpg-encryption-security>}
+for more details about that advice.) If decide to use encryption, make sure you
+save off the passphrase in a safe place, so you can get at your backup data
+later if you need to.
+
+This extension was written for and tested on Linux. I do not expect it to
+work on non-UNIX platforms.
+
+@author: Kenneth J. Pronovici <pro...@ie...>
+"""
+
+########################################################################
+# Imported modules
+########################################################################
+
+# System modules
+import os
+import logging
+import tempfile
+
+# Cedar Backup modules
+from CedarBackup2.util import resolveCommand, executeCommand
+from CedarBackup2.xmlutil import createInputDom, addContainerNode, addStringNode
+from CedarBackup2.xmlutil import readFirstChild, readString
+from CedarBackup2.actions.util import findDailyDirs, writeIndicatorFile, getBackupFiles
+
+
+########################################################################
+# Module-wide constants and variables
+########################################################################
+
+logger = logging.getLogger("CedarBackup2.log.extend.amazons3")
+
+S3CMD_COMMAND = [ "s3cmd", ]
+STORE_INDICATOR = "cback.amazons3"
+
+
+########################################################################
+# AmazonS3Config class definition
+########################################################################
+
+class AmazonS3Config(object):
+
+ """
+ Class representing Amazon S3 configuration.
+
+ Amazon S3 configuration is used for storing staging directories
+ in Amazon's cloud storage using the C{s3cmd} tool.
+
+ The following restrictions exist on data in this class:
+
+ - The s3Bucket value must be a non-empty string
+
+ @sort: __init__, __repr__, __str__, __cmp__, s3Bucket
+ """
+
+ def __init__(self, s3Bucket=None):
+ """
+ Constructor for the C{AmazonS3Config} class.
+
+ @param s3Bucket: Name of the Amazon S3 bucket in which to store the data
+
+ @raise ValueError: If one of the values is invalid.
+ """
+ self._s3Bucket = None
+ self.s3Bucket = s3Bucket
+
+ def __repr__(self):
+ """
+ Official string representation for class instance.
+ """
+ return "AmazonS3Config(%s)" % (self.s3Bucket)
+
+ 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.s3Bucket != other.s3Bucket:
+ if self.s3Bucket < other.s3Bucket:
+ return -1
+ else:
+ return 1
+ return 0
+
+ def _setS3Bucket(self, value):
+ """
+ Property target used to set the S3 bucket.
+ """
+ if value is not None:
+ if len(value) < 1:
+ raise ValueError("S3 bucket must be non-empty string.")
+ self._s3Bucket = value
+
+ def _getS3Bucket(self):
+ """
+ Property target used to get the S3 bucket.
+ """
+ return self._s3Bucket
+
+ s3Bucket = property(_getS3Bucket, _setS3Bucket, None, doc="Amazon S3 Bucket")
+
+
+########################################################################
+# LocalConfig class definition
+########################################################################
+
+class LocalConfig(object):
+
+ """
+ Class representing this extension's configuration document.
+
+ This is not a general-purpose configuration object like the main Cedar
+ Backup configuration object. Instead, it just knows how to parse and emit
+ amazons3-specific configuration values. Third parties who need to read and
+ write configuration related to this extension should access it through the
+ constructor, C{validate} and C{addConfig} methods.
+
+ @note: Lists within this class are "unordered" for equality comparisons.
+
+ @sort: __init__, __repr__, __str__, __cmp__, amazons3, validate, addConfig
+ """
+
+ def __init__(self, xmlData=None, xmlPath=None, validate=True):
+ """
+ Initializes a configuration object.
+
+ If you initialize the object without passing either C{xmlData} or
+ C{xmlPath} then configuration will be empty and will be invalid until it
+ is filled in properly.
+
+ No reference to the original XML data or original path is saved off by
+ this class. Once the data has been parsed (successfully or not) this
+ original information is discarded.
+
+ Unless the C{validate} argument is C{False}, the L{LocalConfig.validate}
+ method will be called (with its default arguments) against configuration
+ after successfully parsing any passed-in XML. Keep in mind that even if
+ C{validate} is C{False}, it might not be possible to parse the passed-in
+ XML document if lower-level validations fail.
+
+ @note: It is strongly suggested that the C{validate} option always be set
+ to C{True} (the default) unless there is a specific need to read in
+ invalid configuration from disk.
+
+ @param xmlData: XML data representing configuration.
+ @type xmlData: String data.
+
+ @param xmlPath: Path to an XML file on disk.
+ @type xmlPath: Absolute path to a file on disk.
+
+ @param validate: Validate the document after parsing it.
+ @type validate: Boolean true/false.
+
+ @raise ValueError: If both C{xmlData} and C{xmlPath} are passed-in.
+ @raise ValueError: If the XML data in C{xmlData} or C{xmlPath} cannot be parsed.
+ @raise ValueError: If the parsed configuration document is not valid.
+ """
+ self._amazons3 = None
+ self.amazons3 = None
+ if xmlData is not None and xmlPath is not None:
+ raise ValueError("Use either xmlData or xmlPath, but not both.")
+ if xmlData is not None:
+ self._parseXmlData(xmlData)
+ if validate:
+ self.validate()
+ elif xmlPath is not None:
+ xmlData = open(xmlPath).read()
+ self._parseXmlData(xmlData)
+ if validate:
+ self.validate()
+
+ def __repr__(self):
+ """
+ Official string representation for class instance.
+ """
+ return "LocalConfig(%s)" % (self.amazons3)
+
+ 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.amazons3 != other.amazons3:
+ if self.amazons3 < other.amazons3:
+ return -1
+ else:
+ return 1
+ return 0
+
+ def _setAmazonS3(self, value):
+ """
+ Property target used to set the amazons3 configuration value.
+ If not C{None}, the value must be a C{AmazonS3Config} object.
+ @raise ValueError: If the value is not a C{AmazonS3Config}
+ """
+ if value is None:
+ self._amazons3 = None
+ else:
+ if not isinstance(value, AmazonS3Config):
+ raise ValueError("Value must be a C{AmazonS3Config} object.")
+ self._amazons3 = value
+
+ def _getAmazonS3(self):
+ """
+ Property target used to get the amazons3 configuration value.
+ """
+ return self._amazons3
+
+ amazons3 = property(_getAmazonS3, _setAmazonS3, None, "AmazonS3 configuration in terms of a C{AmazonS3Config} object.")
+
+ def validate(self):
+ """
+ Validates configuration represented by the object.
+
+ AmazonS3 configuration must be filled in. Within that, the s3Bucket target must be filled in
+
+ @raise ValueError: If one of the validations fails.
+ """
+ if self.amazons3 is None:
+ raise ValueError("AmazonS3 section is required.")
+ if self.amazons3.s3Bucket is None:
+ raise ValueError("AmazonS3 s3Bucket must be set.")
+
+ def addConfig(self, xmlDom, parentNode):
+ """
+ Adds an <amazons3> configuration section as the next child of a parent.
+
+ Third parties should use this function to write configuration related to
+ this extension.
+
+ We add the following fields to the document::
+
+ s3Bucket //cb_config/amazons3/s3_bucket
+
+ @param xmlDom: DOM tree as from C{impl.createDocument()}.
+ @param parentNode: Parent that the section should be appended to.
+ """
+ if self.amazons3 is not None:
+ sectionNode = addContainerNode(xmlDom, parentNode, "amazons3")
+ addStringNode(xmlDom, sectionNode, "s3_bucket", self.amazons3.s3Bucket)
+
+ def _parseXmlData(self, xmlData):
+ """
+ Internal method to parse an XML string into the object.
+
+ This method parses the XML document into a DOM tree (C{xmlDom}) and then
+ calls a static method to parse the amazons3 configuration section.
+
+ @param xmlData: XML data to be parsed
+ @type xmlData: String data
+
+ @raise ValueError: If the XML cannot be successfully parsed.
+ """
+ (xmlDom, parentNode) = createInputDom(xmlData)
+ self._amazons3 = LocalConfig._parseAmazonS3(parentNode)
+
+ @staticmethod
+ def _parseAmazonS3(parent):
+ """
+ Parses an amazons3 configuration section.
+
+ We read the following individual fields::
+
+ s3Bucket //cb_config/amazons3/s3_bucket
+
+ @param parent: Parent node to search beneath.
+
+ @return: C{AmazonS3Config} object or C{None} if the section does not exist.
+ @raise ValueError: If some filled-in value is invalid.
+ """
+ amazons3 = None
+ section = readFirstChild(parent, "amazons3")
+ if section is not None:
+ amazons3 = AmazonS3Config()
+ amazons3.s3Bucket = readString(section, "s3_bucket")
+ return amazons3
+
+
+########################################################################
+# Public functions
+########################################################################
+
+###########################
+# executeAction() function
+###########################
+
+def executeAction(configPath, options, config):
+ """
+ Executes the amazons3 backup action.
+
+ @param configPath: Path to configuration file on disk.
+ @type configPath: String representing a path on disk.
+
+ @param options: Program command-line options.
+ @type options: Options object.
+
+ @param config: Program configuration.
+ @type config: Config object.
+
+ @raise ValueError: Under many generic error conditions
+ @raise IOError: If there are I/O problems reading or writing files
+ """
+ logger.debug("Executing amazons3 extended action.")
+ if config.options is None or config.stage is None:
+ raise ValueError("Cedar Backup configuration is not properly filled in.")
+ local = LocalConfig(xmlPath=configPath)
+ dailyDirs = findDailyDirs(config.stage.targetDir, STORE_INDICATOR)
+ for dailyDir in dailyDirs:
+ _storeDailyDir(dailyDir, local.amazons3.s3Bucket, config.options.backupUser, config.options.backupGroup)
+ writeIndicatorFile(dailyDir, STORE_INDICATOR, config.options.backupUser, config.options.backupGroup)
+ logger.info("Executed the amazons3 extended action successfully.")
+
+
+########################################################################
+# Utility functions
+########################################################################
+
+############################
+# _storeDailyDir() function
+############################
+
+def _storeDailyDir(stagingDir, dailyDir, s3Bucket, backupUser, backupGroup):
+ """
+ Store the contents of a daily staging directory to a bucket in the Amazon S3 cloud.
+ @param stagingDir: Configured staging directory (config.targetDir)
+ @param dailyDir: Daily directory to store in the cloud
+ @param s3Bucket: The Amazon S3 bucket to use as the target
+ @param backupUser: User that target files should be owned by
+ @param backupGroup: Group that target files should be owned by
+ """
+ s3BucketUrl = _deriveS3BucketUrl(stagingDir, dailyDir, s3Bucket)
+ _clearExistingBackup(s3BucketUrl)
+ _writeDailyDir(dailyDir, s3BucketUrl)
+
+
+##############################
+# _deriveBucketUrl() function
+##############################
+
+def _deriveS3BucketUrl(stagingDir, dailyDir, s3Bucket):
+ """
+ Derive the correct bucket URL for a daily directory.
+ @param stagingDir: Configured staging directory (config.targetDir)
+ @param dailyDir: Daily directory to store
+ @param s3Bucket: The Amazon S3 bucket to use as the target
+ @return: S3 bucket URL, with no trailing slash
+ """
+ subdir = dailyDir.replace("/opt/backup/staging", "")
+ if subdir.startswith("/"):
+ subdir = subdir[1:]
+ return "s3://%s/staging/%s" % (s3Bucket, dailyDir)
+
+
+##################################
+# _clearExistingBackup() function
+##################################
+
+def _clearExistingBackup(s3BucketUrl):
+ """
+ Clear any existing backup files for a daily directory.
+ @param s3BucketUrl: S3 bucket URL derived for the daily directory
+ """
+ emptydir = tempfile.mkdtemp()
+ try:
+ command = resolveCommand(S3CMD_COMMAND)
+ args = [ "sync", "--no-encrypt", "--recursive", "--delete-removed", emptyDir + "/", s3BucketUrl + "/", ]
+ result = executeCommand(command, args)[0]
+ if result != 0:
+ raise IOError("Error [%d] calling s3Cmd to clear existing backup [%s]." % (result, s3BucketUrl))
+ finally:
+ os.rmdir(emptydir)
+
+
+############################
+# _writeDailyDir() function
+############################
+
+def __writeDailyDir(dailyDir, s3BucketUrl):
+ """
+ Write the daily directory out to the Amazon S3 cloud.
+ @param dailyDir: Daily directory to store
+ @param s3BucketUrl: S3 bucket URL derived for the daily directory
+ """
+ command = resolveCommand(S3CMD_COMMAND)
+ args = [ "put", "--recursive", dailyDir + "/", s3BucketUrl + "/", ]
+ result = executeCommand(command, args)[0]
+ if result != 0:
+ raise IOError("Error [%d] calling s3Cmd to store daily directory [%s]." % (result, s3BucketUrl))
+
Property changes on: cedar-backup2/trunk/CedarBackup2/extend/amazons3.py
___________________________________________________________________
Added: svn:keywords
## -0,0 +1 ##
+Id
\ No newline at end of property
Modified: cedar-backup2/trunk/CedarBackup2/release.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/release.py 2014-10-01 01:42:33 UTC (rev 1046)
+++ cedar-backup2/trunk/CedarBackup2/release.py 2014-10-01 01:44:17 UTC (rev 1047)
@@ -33,8 +33,8 @@
AUTHOR = "Kenneth J. Pronovici"
EMAIL = "pro...@ie..."
-COPYRIGHT = "2004-2011,2013"
-VERSION = "2.22.0"
-DATE = "09 May 2013"
+COPYRIGHT = "2004-2011,2013,2014"
+VERSION = "2.23.0"
+DATE = "unreleased"
URL = "http://cedar-backup.sourceforge.net/"
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2014-10-01 01:42:33 UTC (rev 1046)
+++ cedar-backup2/trunk/Changelog 2014-10-01 01:44:17 UTC (rev 1047)
@@ -1,14 +1,18 @@
+Version 2.23.0 unreleased
+
+ * Add new extension amazons3, as a new store-type action.
+
Version 2.22.0 09 May 2013
* Add eject-related kludges to work around observed behavior.
- * New config option eject_delay, to slow down open/close
- * Unlock tray with 'eject -i off' to handle potential problems
+ * New config option eject_delay, to slow down open/close
+ * Unlock tray with 'eject -i off' to handle potential problems
Version 2.21.1 21 Mar 2013
* Apply patches provided by Jan Medlock as Debian bugs.
- * Fix typo in manpage (showed -s instead of -D)
- * Support output from latest /usr/bin/split (' vs. `)
+ * Fix typo in manpage (showed -s instead of -D)
+ * Support output from latest /usr/bin/split (' vs. `)
Version 2.21.0 12 Oct 2011
Added: cedar-backup2/trunk/testcase/amazons3tests.py
===================================================================
--- cedar-backup2/trunk/testcase/amazons3tests.py (rev 0)
+++ cedar-backup2/trunk/testcase/amazons3tests.py 2014-10-01 01:44:17 UTC (rev 1047)
@@ -0,0 +1,584 @@
+#!/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) 2014 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.5)
+# Project : Cedar Backup, release 2
+# Revision : $Id$
+# Purpose : Tests amazons3 extension functionality.
+#
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+########################################################################
+# Module documentation
+########################################################################
+
+"""
+Unit tests for CedarBackup2/extend/amazons3.py.
+
+Code Coverage
+=============
+
+ This module contains individual tests for the the public classes implemented
+ in extend/amazons3.py. There are also tests for some of the private
+ functions.
+
+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.
+
+@author Kenneth J. Pronovici <pro...@ie...>
+"""
+
+
+########################################################################
+# Import modules and do runtime validations
+########################################################################
+
+# System modules
+import unittest
+import os
+import tempfile
+
+# Cedar Backup modules
+from CedarBackup2.filesystem import FilesystemList
+from CedarBackup2.testutil import findResources, buildPath, removedir, extractTar, failUnlessAssignRaises, platformSupportsLinks
+from CedarBackup2.xmlutil import createOutputDom, serializeDom
+from CedarBackup2.extend.amazons3 import LocalConfig, AmazonS3Config
+
+
+#######################################################################
+# Module-wide configuration and constants
+#######################################################################
+
+DATA_DIRS = [ "./data", "./testcase/data", ]
+RESOURCES = [ "amazons3.conf.1", "amazons3.conf.2", "tree1.tar.gz", "tree2.tar.gz",
+ "tree8.tar.gz", "tree15.tar.gz", "tree16.tar.gz", "tree17.tar.gz",
+ "tree18.tar.gz", "tree19.tar.gz", "tree20.tar.gz", ]
+
+
+#######################################################################
+# Test Case Classes
+#######################################################################
+
+##########################
+# TestAmazonS3Config class
+##########################
+
+class TestAmazonS3Config(unittest.TestCase):
+
+ """Tests for the AmazonS3Config class."""
+
+ ##################
+ # Utility methods
+ ##################
+
+ def failUnlessAssignRaises(self, exception, obj, prop, value):
+ """Equivalent of L{failUnlessRaises}, but used for property assignments instead."""
+ failUnlessAssignRaises(self, exception, obj, prop, 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 = AmazonS3Config()
+ obj.__repr__()
+ obj.__str__()
+
+
+ ##################################
+ # Test constructor and attributes
+ ##################################
+
+ def testConstructor_001(self):
+ """
+ Test constructor with no values filled in.
+ """
+ amazons3 = AmazonS3Config()
+ self.failUnlessEqual(None, amazons3.s3Bucket)
+
+ def testConstructor_002(self):
+ """
+ Test constructor with all values filled in, with valid values.
+ """
+ amazons3 = AmazonS3Config("bucket")
+ self.failUnlessEqual("bucket", amazons3.s3Bucket)
+
+ def testConstructor_003(self):
+ """
+ Test assignment of s3Bucket attribute, None value.
+ """
+ amazons3 = AmazonS3Config(s3Bucket="bucket")
+ self.failUnlessEqual("bucket", amazons3.s3Bucket)
+ amazons3.s3Bucket = None
+ self.failUnlessEqual(None, amazons3.s3Bucket)
+
+ def testConstructor_004(self):
+ """
+ Test assignment of s3Bucket attribute, valid value.
+ """
+ amazons3 = AmazonS3Config()
+ self.failUnlessEqual(None, amazons3.s3Bucket)
+ amazons3.s3Bucket = "bucket"
+ self.failUnlessEqual("bucket", amazons3.s3Bucket)
+
+ def testConstructor_005(self):
+ """
+ Test assignment of s3Bucket attribute, invalid value (empty).
+ """
+ amazons3 = AmazonS3Config()
+ self.failUnlessEqual(None, amazons3.s3Bucket)
+ self.failUnlessAssignRaises(ValueError, amazons3, "s3Bucket", "")
+ self.failUnlessEqual(None, amazons3.s3Bucket)
+
+
+ ############################
+ # Test comparison operators
+ ############################
+
+ def testComparison_001(self):
+ """
+ Test comparison of two identical objects, all attributes None.
+ """
+ amazons31 = AmazonS3Config()
+ amazons32 = AmazonS3Config()
+ self.failUnlessEqual(amazons31, amazons32)
+ self.failUnless(amazons31 == amazons32)
+ self.failUnless(not amazons31 < amazons32)
+ self.failUnless(amazons31 <= amazons32)
+ self.failUnless(not amazons31 > amazons32)
+ self.failUnless(amazons31 >= amazons32)
+ self.failUnless(not amazons31 != amazons32)
+
+ def testComparison_002(self):
+ """
+ Test comparison of two identical objects, all attributes non-None.
+ """
+ amazons31 = AmazonS3Config("bucket")
+ amazons32 = AmazonS3Config("bucket")
+ self.failUnlessEqual(amazons31, amazons32)
+ self.failUnless(amazons31 == amazons32)
+ self.failUnless(not amazons31 < amazons32)
+ self.failUnless(amazons31 <= amazons32)
+ self.failUnless(not amazons31 > amazons32)
+ self.failUnless(amazons31 >= amazons32)
+ self.failUnless(not amazons31 != amazons32)
+
+ def testComparison_003(self):
+ """
+ Test comparison of two differing objects, s3Bucket differs (one None).
+ """
+ amazons31 = AmazonS3Config()
+ amazons32 = AmazonS3Config(s3Bucket="bucket")
+ self.failIfEqual(amazons31, amazons32)
+ self.failUnless(not amazons31 == amazons32)
+ self.failUnless(amazons31 < amazons32)
+ self.failUnless(amazons31 <= amazons32)
+ self.failUnless(not amazons31 > amazons32)
+ self.failUnless(not amazons31 >= amazons32)
+ self.failUnless(amazons31 != amazons32)
+
+ def testComparison_004(self):
+ """
+ Test comparison of two differing objects, s3Bucket differs.
+ """
+ amazons31 = AmazonS3Config("bucket1")
+ amazons32 = AmazonS3Config("bucket2")
+ self.failIfEqual(amazons31, amazons32)
+ self.failUnless(not amazons31 == amazons32)
+ self.failUnless(amazons31 < amazons32)
+ self.failUnless(amazons31 <= amazons32)
+ self.failUnless(not amazons31 > amazons32)
+ self.failUnless(not amazons31 >= amazons32)
+ self.failUnless(amazons31 != amazons32)
+
+
+########################
+# 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, obj, prop, value):
+ """Equivalent of L{failUnlessRaises}, but used for property assignments instead."""
+ failUnlessAssignRaises(self, exception, obj, prop, value)
+
+ def validateAddConfig(self, origConfig):
+ """
+ Validates that document dumped from C{LocalConfig.addConfig} results in
+ identical object.
+
+ We dump a document containing just the amazons3 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.amazons3)
+
+ def testConstructor_002(self):
+ """
+ Test empty constructor, validate=True.
+ """
+ config = LocalConfig(validate=True)
+ self.failUnlessEqual(None, config.amazons3)
+
+ def testConstructor_003(self):
+ """
+ Test with empty config document as both data and file, validate=False.
+ """
+ path = self.resources["amazons3.conf.1"]
+ contents = open(path).read()
+ self.failUnlessRaises(ValueError, LocalConfig, xmlData=contents, xmlPath=path, validate=False)
+
+ def testConstructor_004(self):
+ """
+ Test assignment of amazons3 attribute, None value.
+ """
+ config = LocalConfig()
+ config.amazons3 = None
+ self.failUnlessEqual(None, config.amazons3)
+
+ def testConstructor_005(self):
+ """
+ Test assignment of amazons3 attribute, valid value.
+ """
+ config = LocalConfig()
+ config.amazons3 = AmazonS3Config()
+ self.failUnlessEqual(AmazonS3Config(), config.amazons3)
+
+ def testConstructor_006(self):
+ """
+ Test assignment of amazons3 attribute, invalid value (not AmazonS3Config).
+ """
+ config = LocalConfig()
+ self.failUnlessAssignRaises(ValueError, config, "amazons3", "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.amazons3 = AmazonS3Config()
+
+ config2 = LocalConfig()
+ config2.amazons3 = AmazonS3Config()
+
+ 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, amazons3 differs (one None).
+ """
+ config1 = LocalConfig()
+ config2 = LocalConfig()
+ config2.amazons3 = AmazonS3Config()
+ 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, s3Bucket differs.
+ """
+ config1 = LocalConfig()
+ config1.amazons3 = AmazonS3Config("bucket1")
+
+ config2 = LocalConfig()
+ config2.amazons3 = AmazonS3Config("bucket2")
+
+ 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)
+
+
+ ######################
+ # Test validate logic
+ ######################
+
+ def testValidate_001(self):
+ """
+ Test validate on a None amazons3 section.
+ """
+ config = LocalConfig()
+ config.amazons3 = None
+ self.failUnlessRaises(ValueError, config.validate)
+
+ def testValidate_002(self):
+ """
+ Test validate on an empty amazons3 section.
+ """
+ config = LocalConfig()
+ config.amazons3 = AmazonS3Config()
+ self.failUnlessRaises(ValueError, config.validate)
+
+ def testValidate_003(self):
+ """
+ Test validate on a non-empty amazons3 section with no values filled in.
+ """
+ config = LocalConfig()
+ config.amazons3 = AmazonS3Config(None)
+ self.failUnlessRaises(ValueError, config.validate)
+
+ def testValidate_005(self):
+ """
+ Test validate on a non-empty amazons3 section with valid values filled in.
+ """
+ config = LocalConfig()
+ config.amazons3 = AmazonS3Config("bucket")
+ config.validate()
+
+
+ ############################
+ # Test parsing of documents
+ ############################
+
+ def testParse_001(self):
+ """
+ Parse empty config document.
+ """
+ path = self.resources["amazons3.conf.1"]
+ contents = open(path).read()
+ self.failUnlessRaises(ValueError, LocalConfig, xmlPath=path, validate=True)
+ self.failUnlessRaises(ValueError, LocalConfig, xmlData=contents, validate=True)
+ config = LocalConfig(xmlPath=path, validate=False)
+ self.failUnlessEqual(None, config.amazons3)
+ config = LocalConfig(xmlData=contents, validate=False)
+ self.failUnlessEqual(None, config.amazons3)
+
+ def testParse_002(self):
+ """
+ Parse config document with filled-in values.
+ """
+ path = self.resources["amazons3.conf.2"]
+ contents = open(path).read()
+ config = LocalConfig(xmlPath=path, validate=False)
+ self.failIfEqual(None, config.amazons3)
+ self.failUnlessEqual("mybucket", config.amazons3.s3Bucket)
+ config = LocalConfig(xmlData=contents, validate=False)
+ self.failIfEqual(None, config.amazons3)
+ self.failUnlessEqual("mybucket", config.amazons3.s3Bucket)
+
+
+ ###################
+ # Test addConfig()
+ ###################
+
+ def testAddConfig_001(self):
+ """
+ Test with empty config document.
+ """
+ amazons3 = AmazonS3Config()
+ config = LocalConfig()
+ config.amazons3 = amazons3
+ self.validateAddConfig(config)
+
+ def testAddConfig_002(self):
+ """
+ Test with values set.
+ """
+ amazons3 = AmazonS3Config("bucket")
+ config = LocalConfig()
+ config.amazons3 = amazons3
+ self.validateAddConfig(config)
+
+
+######################
+# TestFunctions class
+######################
+
+class TestFunctions(unittest.TestCase):
+
+ """Tests for the functions in amazons3.py."""
+
+ ################
+ # Setup methods
+ ################
+
+ def setUp(self):
+ try:
+ self.tmpdir = tempfile.mkdtemp()
+ self.resources = findResources(RESOURCES, DATA_DIRS)
+ except Exception, e:
+ self.fail(e)
+
+ def tearDown(self):
+ try:
+ removedir(self.tmpdir)
+ except: pass
+
+
+ ##################
+ # Utility methods
+ ##################
+
+ def extractTar(self, tarname):
+ """Extracts a tarfile with a particular name."""
+ extractTar(self.tmpdir, self.resources['%s.tar.gz' % tarname])
+
+ def buildPath(self, components):
+ """Builds a complete search path from a list of components."""
+ components.insert(0, self.tmpdir)
+ return buildPath(components)
+
+
+#######################################################################
+# Suite definition
+#######################################################################
+
+def suite():
+ """Returns a suite containing all the test cases in this module."""
+ return unittest.TestSuite((
+ unittest.makeSuite(TestAmazonS3Config, 'test'),
+ unittest.makeSuite(TestLocalConfig, 'test'),
+ unittest.makeSuite(TestFunctions, 'test'),
+ ))
+
+
+########################################################################
+# Module entry point
+########################################################################
+
+# When this module is executed from the command-line, run its tests
+if __name__ == '__main__':
+ unittest.main()
+
Property changes on: cedar-backup2/trunk/testcase/amazons3tests.py
___________________________________________________________________
Added: svn:keywords
## -0,0 +1 ##
+Id
\ No newline at end of property
Modified: cedar-backup2/trunk/testcase/configtests.py
===================================================================
--- cedar-backup2/trunk/testcase/configtests.py 2014-10-01 01:42:33 UTC (rev 1046)
+++ cedar-backup2/trunk/testcase/configtests.py 2014-10-01 01:44:17 UTC (rev 1047)
@@ -11271,6 +11271,9 @@
afterList=["one", "two", "three",
"four", "five", "six",
"seven", "eight", ])))
+ expected.extensions.actions.append(ExtendedAction("amazons3", "CedarBackup2.extend.amazons3", "executeAction",
+ index=None,
+ dependencies=ActionDependencies()))
self.failUnlessEqual(expected, config)
def testParse_010(self):
Added: cedar-backup2/trunk/testcase/data/amazons3.conf.1
===================================================================
--- cedar-backup2/trunk/testcase/data/amazons3.conf.1 (rev 0)
+++ cedar-backup2/trunk/testcase/data/amazons3.conf.1 2014-10-01 01:44:17 UTC (rev 1047)
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<!-- Empty document -->
+<cb_config/>
Added: cedar-backup2/trunk/testcase/data/amazons3.conf.2
===================================================================
--- cedar-backup2/trunk/testcase/data/amazons3.conf.2 (rev 0)
+++ cedar-backup2/trunk/testcase/data/amazons3.conf.2 2014-10-01 01:44:17 UTC (rev 1047)
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<!-- Valid document -->
+<cb_config>
+ <amazons3>
+ <s3_bucket>mybucket</s3_bucket>
+ </amazons3>
+</cb_config>
Modified: cedar-backup2/trunk/testcase/data/cback.conf.19
===================================================================
--- cedar-backup2/trunk/testcase/data/cback.conf.19 2014-10-01 01:42:33 UTC (rev 1046)
+++ cedar-backup2/trunk/testcase/data/cback.conf.19 2014-10-01 01:44:17 UTC (rev 1047)
@@ -50,5 +50,12 @@
<run_after> one, two,three, four , five , six, seven,,eight ,</run_after>
</depends>
</action>
+ <action>
+ <name>amazons3</name>
+ <module>CedarBackup2.extend.amazons3</module>
+ <function>executeAction</function>
+ <depends>
+ </depends>
+ </action>
</extensions>
</cb_config>
Modified: cedar-backup2/trunk/util/test.py
===================================================================
--- cedar-backup2/trunk/util/test.py 2014-10-01 01:42:33 UTC (rev 1046)
+++ cedar-backup2/trunk/util/test.py 2014-10-01 01:44:17 UTC (rev 1047)
@@ -164,6 +164,7 @@
from testcase import subversiontests
from testcase import mboxtests
from testcase import encrypttests
+ from testcase import amazons3tests
from testcase import splittests
from testcase import spantests
from testcase import capacitytests
@@ -223,6 +224,7 @@
if args == [] or "mbox" in args: unittests["mbox"] = mboxtests.suite()
if args == [] or "split" in args: unittests["split"] = splittests.suite()
if args == [] or "encrypt" in args: unittests["encrypt"] = encrypttests.suite()
+ if args == [] or "amazons3" in args: unittests["amazons3"] = amazons3tests.suite()
if args == [] or "span" in args: unittests["span"] = spantests.suite()
if args == [] or "capacity" in args: unittests["capacity"] = capacitytests.suite()
if args == [] or "customize" in args: unittests["customize"] = customizetests.suite()
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2014-10-01 01:42:41
|
Revision: 1046
http://sourceforge.net/p/cedar-backup/code/1046
Author: pronovic
Date: 2014-10-01 01:42:33 +0000 (Wed, 01 Oct 2014)
Log Message:
-----------
Fix test so it works now that I have two GPG gets
Modified Paths:
--------------
cedar-backup2/trunk/testcase/encrypttests.py
Modified: cedar-backup2/trunk/testcase/encrypttests.py
===================================================================
--- cedar-backup2/trunk/testcase/encrypttests.py 2013-05-10 02:18:52 UTC (rev 1045)
+++ cedar-backup2/trunk/testcase/encrypttests.py 2014-10-01 01:42:33 UTC (rev 1046)
@@ -87,9 +87,9 @@
want to run all of the tests, set ENCRYPTTESTS_FULL to "Y" in the environment.
In this module, the primary dependency is that for some tests, GPG must have
- access to the public key for "Kenneth J. Pronovici". There is also an
- assumption that GPG does I{not} have access to a public key for anyone named
- "Bogus J. User" (so we can test failure scenarios).
+ access to the public key EFD75934. There is also an assumption that GPG
+ does I{not} have access to a public key for anyone named "Bogus J. User" (so
+ we can test failure scenarios).
@author Kenneth J. Pronovici <pro...@ie...>
"""
@@ -121,7 +121,7 @@
"tree8.tar.gz", "tree15.tar.gz", "tree16.tar.gz", "tree17.tar.gz",
"tree18.tar.gz", "tree19.tar.gz", "tree20.tar.gz", ]
-VALID_GPG_RECIPIENT = "Kenneth J. Pronovici"
+VALID_GPG_RECIPIENT = "EFD75934"
INVALID_GPG_RECIPIENT = "Bogus J. User"
INVALID_PATH = "bogus" # This path name should never exist
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-05-10 02:18:55
|
Revision: 1045
http://sourceforge.net/p/cedar-backup/code/1045
Author: pronovic
Date: 2013-05-10 02:18:52 +0000 (Fri, 10 May 2013)
Log Message:
-----------
Tagging the 2.22.0 release of Cedar Backup.
Added Paths:
-----------
cedar-backup2/tags/CEDAR_BACKUP2_V2.22.0/
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-05-10 02:16:14
|
Revision: 1044
http://sourceforge.net/p/cedar-backup/code/1044
Author: pronovic
Date: 2013-05-10 02:16:12 +0000 (Fri, 10 May 2013)
Log Message:
-----------
Prepare to release 2.22.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 2013-05-10 02:10:25 UTC (rev 1043)
+++ cedar-backup2/trunk/CedarBackup2/release.py 2013-05-10 02:16:12 UTC (rev 1044)
@@ -34,7 +34,7 @@
AUTHOR = "Kenneth J. Pronovici"
EMAIL = "pro...@ie..."
COPYRIGHT = "2004-2011,2013"
-VERSION = "2.21.1"
-DATE = "21 Mar 2013"
+VERSION = "2.22.0"
+DATE = "09 May 2013"
URL = "http://cedar-backup.sourceforge.net/"
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2013-05-10 02:10:25 UTC (rev 1043)
+++ cedar-backup2/trunk/Changelog 2013-05-10 02:16:12 UTC (rev 1044)
@@ -1,8 +1,9 @@
-Version 2.22.0 unreleased
+Version 2.22.0 09 May 2013
* Add eject-related kludges to work around observed behavior.
+ * New config option eject_delay, to slow down open/close
+ * Unlock tray with 'eject -i off' to handle potential problems
-
Version 2.21.1 21 Mar 2013
* Apply patches provided by Jan Medlock as Debian bugs.
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-05-10 02:10:27
|
Revision: 1043
http://sourceforge.net/p/cedar-backup/code/1043
Author: pronovic
Date: 2013-05-10 02:10:25 +0000 (Fri, 10 May 2013)
Log Message:
-----------
Document eject_delay option
Modified Paths:
--------------
cedar-backup2/trunk/manual/src/config.xml
Modified: cedar-backup2/trunk/manual/src/config.xml
===================================================================
--- cedar-backup2/trunk/manual/src/config.xml 2013-05-10 02:10:00 UTC (rev 1042)
+++ cedar-backup2/trunk/manual/src/config.xml 2013-05-10 02:10:25 UTC (rev 1043)
@@ -2245,7 +2245,8 @@
<check_media>Y</check_media>
<warn_midnite>Y</warn_midnite>
<no_eject>N</no_eject>
- <refresh_media_delay>5</refresh_media_delay>
+ <refresh_media_delay>15</refresh_media_delay>
+ <eject_delay>2</eject_delay>
<blank_behavior>
<mode>weekly</mode>
<factor>1.3</factor>
@@ -2563,7 +2564,7 @@
refreshing the media (i.e. closing and opening the tray).
During this period, operations on the media may fail. If
your device behaves like this, you can try setting a delay
- of a few seconds to remedy the problem.
+ of 10-15 seconds.
</para>
<para>
<emphasis>Restrictions:</emphasis> If set, must be an integer ≥ 1.
@@ -2572,6 +2573,27 @@
</varlistentry>
<varlistentry>
+ <term><literal>eject_delay</literal></term>
+ <listitem>
+ <para>Number of seconds to delay after ejecting the tray</para>
+ <para>
+ This field is optional. If it doesn't exist, no delay
+ will occur.
+ </para>
+ <para>
+ If your system seems to have problems opening and closing the tray,
+ one possibility is that the open/close sequence is happening too
+ quickly — either the tray isn't fully open when Cedar Backup
+ tries to close it, or it doesn't report being open. To work around
+ that problem, set an eject delay of a few seconds.
+ </para>
+ <para>
+ <emphasis>Restrictions:</emphasis> If set, must be an integer ≥ 1.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><literal>blank_behavior</literal></term>
<listitem>
<para>Optimized blanking strategy.</para>
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-05-10 02:10:03
|
Revision: 1042
http://sourceforge.net/p/cedar-backup/code/1042
Author: pronovic
Date: 2013-05-10 02:10:00 +0000 (Fri, 10 May 2013)
Log Message:
-----------
Fix typo
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/util.py
Modified: cedar-backup2/trunk/CedarBackup2/util.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/util.py 2013-05-10 02:05:13 UTC (rev 1041)
+++ cedar-backup2/trunk/CedarBackup2/util.py 2013-05-10 02:10:00 UTC (rev 1042)
@@ -1454,7 +1454,7 @@
@type returnOutput: Boolean C{True} or C{False}
@param ignoreStderr: Whether stderr should be discarded
- @type ignoreSdderr: Boolean True or False
+ @type ignoreStderr: Boolean True or False
@param doNotLog: Indicates that output should not be logged.
@type doNotLog: Boolean C{True} or C{False}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-05-10 02:05:17
|
Revision: 1041
http://sourceforge.net/p/cedar-backup/code/1041
Author: pronovic
Date: 2013-05-10 02:05:13 +0000 (Fri, 10 May 2013)
Log Message:
-----------
Eject-related kludges
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/actions/util.py
cedar-backup2/trunk/CedarBackup2/config.py
cedar-backup2/trunk/CedarBackup2/writers/cdwriter.py
cedar-backup2/trunk/CedarBackup2/writers/dvdwriter.py
cedar-backup2/trunk/Changelog
cedar-backup2/trunk/testcase/configtests.py
cedar-backup2/trunk/testcase/data/cback.conf.12
Modified: cedar-backup2/trunk/CedarBackup2/actions/util.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/actions/util.py 2013-05-10 02:01:14 UTC (rev 1040)
+++ cedar-backup2/trunk/CedarBackup2/actions/util.py 2013-05-10 02:05:13 UTC (rev 1041)
@@ -148,14 +148,15 @@
driveSpeed = config.store.driveSpeed
noEject = config.store.noEject
refreshMediaDelay = config.store.refreshMediaDelay
+ ejectDelay = config.store.ejectDelay
deviceType = _getDeviceType(config)
mediaType = _getMediaType(config)
if deviceMounted(devicePath):
raise IOError("Device [%s] is currently mounted." % (devicePath))
if deviceType == "cdwriter":
- return CdWriter(devicePath, deviceScsiId, driveSpeed, mediaType, noEject, refreshMediaDelay)
+ return CdWriter(devicePath, deviceScsiId, driveSpeed, mediaType, noEject, refreshMediaDelay, ejectDelay)
elif deviceType == "dvdwriter":
- return DvdWriter(devicePath, deviceScsiId, driveSpeed, mediaType, noEject, refreshMediaDelay)
+ return DvdWriter(devicePath, deviceScsiId, driveSpeed, mediaType, noEject, refreshMediaDelay, ejectDelay)
else:
raise ValueError("Device type [%s] is invalid." % deviceType)
@@ -270,6 +271,7 @@
raise ValueError("Only rewritable media types can be initialized.")
mediaLabel = buildMediaLabel()
writer = createWriter(config)
+ writer.refreshMedia()
writer.initializeImage(True, config.options.workingDir, mediaLabel) # always create a new disc
tempdir = tempfile.mkdtemp(dir=config.options.workingDir)
try:
Modified: cedar-backup2/trunk/CedarBackup2/config.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/config.py 2013-05-10 02:01:14 UTC (rev 1040)
+++ cedar-backup2/trunk/CedarBackup2/config.py 2013-05-10 02:05:13 UTC (rev 1041)
@@ -3410,6 +3410,7 @@
- The drive speed must be an integer >= 1
- The blanking behavior must be a C{BlankBehavior} object
- The refresh media delay must be an integer >= 0
+ - The eject delay must be an integer >= 0
Note that although the blanking factor must be a positive floating point
number, it is stored as a string. This is done so that we can losslessly go
@@ -3418,13 +3419,14 @@
@sort: __init__, __repr__, __str__, __cmp__, sourceDir,
mediaType, deviceType, devicePath, deviceScsiId,
driveSpeed, checkData, checkMedia, warnMidnite, noEject,
- blankBehavior, refreshMediaDelay
+ blankBehavior, refreshMediaDelay, ejectDelay
"""
def __init__(self, sourceDir=None, mediaType=None, deviceType=None,
devicePath=None, deviceScsiId=None, driveSpeed=None,
checkData=False, warnMidnite=False, noEject=False,
- checkMedia=False, blankBehavior=None, refreshMediaDelay=None):
+ checkMedia=False, blankBehavior=None, refreshMediaDelay=None,
+ ejectDelay=None):
"""
Constructor for the C{StoreConfig} class.
@@ -3440,6 +3442,7 @@
@param noEject: Indicates that the writer device should not be ejected.
@param blankBehavior: Controls optimized blanking behavior.
@param refreshMediaDelay: Delay, in seconds, to add after refreshing media
+ @param ejectDelay: Delay, in seconds, to add after ejecting media before closing the tray
@raise ValueError: If one of the values is invalid.
"""
@@ -3455,6 +3458,7 @@
self._noEject = None
self._blankBehavior = None
self._refreshMediaDelay = None
+ self._ejectDelay = None
self.sourceDir = sourceDir
self.mediaType = mediaType
self.deviceType = deviceType
@@ -3467,16 +3471,18 @@
self.noEject = noEject
self.blankBehavior = blankBehavior
self.refreshMediaDelay = refreshMediaDelay
+ self.ejectDelay = ejectDelay
def __repr__(self):
"""
Official string representation for class instance.
"""
- return "StoreConfig(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (
+ return "StoreConfig(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (
self.sourceDir, self.mediaType, self.deviceType,
self.devicePath, self.deviceScsiId, self.driveSpeed,
self.checkData, self.warnMidnite, self.noEject,
- self.checkMedia, self.blankBehavior, self.refreshMediaDelay)
+ self.checkMedia, self.blankBehavior, self.refreshMediaDelay,
+ self.ejectDelay)
def __str__(self):
"""
@@ -3552,6 +3558,11 @@
return -1
else:
return 1
+ if self.ejectDelay != other.ejectDelay:
+ if self.ejectDelay < other.ejectDelay:
+ return -1
+ else:
+ return 1
return 0
def _setSourceDir(self, value):
@@ -3765,6 +3776,31 @@
"""
return self._refreshMediaDelay
+ def _setEjectDelay(self, value):
+ """
+ Property target used to set the ejectDelay.
+ The value must be an integer >= 0.
+ @raise ValueError: If the value is not valid.
+ """
+ if value is None:
+ self._ejectDelay = None
+ else:
+ try:
+ value = int(value)
+ except TypeError:
+ raise ValueError("Action ejectDelay value must be an integer >= 0.")
+ if value < 0:
+ raise ValueError("Action ejectDelay value must be an integer >= 0.")
+ if value == 0:
+ value = None # normalize this out, since it's the default
+ self._ejectDelay = value
+
+ def _getEjectDelay(self):
+ """
+ Property target used to get the action ejectDelay.
+ """
+ return self._ejectDelay
+
sourceDir = property(_getSourceDir, _setSourceDir, None, "Directory whose contents should be written to media.")
mediaType = property(_getMediaType, _setMediaType, None, "Type of the media (see notes above).")
deviceType = property(_getDeviceType, _setDeviceType, None, "Type of the device (optional, see notes above).")
@@ -3777,6 +3813,7 @@
noEject = property(_getNoEject, _setNoEject, None, "Indicates that the writer device should not be ejected.")
blankBehavior = property(_getBlankBehavior, _setBlankBehavior, None, "Controls optimized blanking behavior.")
refreshMediaDelay = property(_getRefreshMediaDelay, _setRefreshMediaDelay, None, "Delay, in seconds, to add after refreshing media.")
+ ejectDelay = property(_getEjectDelay, _setEjectDelay, None, "Delay, in seconds, to add after ejecting media before closing the tray")
########################################################################
@@ -4587,6 +4624,7 @@
store.noEject = readBoolean(sectionNode, "no_eject")
store.blankBehavior = Config._parseBlankBehavior(sectionNode)
store.refreshMediaDelay = readInteger(sectionNode, "refresh_media_delay")
+ store.ejectDelay = readInteger(sectionNode, "eject_delay")
return store
@staticmethod
@@ -5234,6 +5272,8 @@
checkMedia //cb_config/store/check_media
warnMidnite //cb_config/store/warn_midnite
noEject //cb_config/store/no_eject
+ refreshMediaDelay //cb_config/store/refresh_media_delay
+ ejectDelay //cb_config/store/eject_delay
Blanking behavior configuration is added by the L{_addBlankBehavior}
method.
@@ -5257,6 +5297,7 @@
addBooleanNode(xmlDom, sectionNode, "warn_midnite", storeConfig.warnMidnite)
addBooleanNode(xmlDom, sectionNode, "no_eject", storeConfig.noEject)
addIntegerNode(xmlDom, sectionNode, "refresh_media_delay", storeConfig.refreshMediaDelay)
+ addIntegerNode(xmlDom, sectionNode, "eject_delay", storeConfig.ejectDelay)
Config._addBlankBehavior(xmlDom, sectionNode, storeConfig.blankBehavior)
@staticmethod
Modified: cedar-backup2/trunk/CedarBackup2/writers/cdwriter.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/writers/cdwriter.py 2013-05-10 02:01:14 UTC (rev 1040)
+++ cedar-backup2/trunk/CedarBackup2/writers/cdwriter.py 2013-05-10 02:05:13 UTC (rev 1041)
@@ -414,7 +414,7 @@
def __init__(self, device, scsiId=None, driveSpeed=None,
mediaType=MEDIA_CDRW_74, noEject=False,
- refreshMediaDelay=0, unittest=False):
+ refreshMediaDelay=0, ejectDelay=0, unittest=False):
"""
Initializes a CD writer object.
@@ -458,6 +458,9 @@
@param refreshMediaDelay: Refresh media delay to use, if any
@type refreshMediaDelay: Number of seconds, an integer >= 0
+ @param ejectDelay: Eject delay to use, if any
+ @type ejectDelay: Number of seconds, an integer >= 0
+
@param unittest: Turns off certain validations, for use in unit testing.
@type unittest: Boolean true/false
@@ -473,6 +476,7 @@
self._media = MediaDefinition(mediaType)
self._noEject = noEject
self._refreshMediaDelay = refreshMediaDelay
+ self._ejectDelay = ejectDelay
if not unittest:
(self._deviceType,
self._deviceVendor,
@@ -567,6 +571,12 @@
"""
return self._refreshMediaDelay
+ def _getEjectDelay(self):
+ """
+ Property target used to get the configured eject delay, in seconds.
+ """
+ return self._ejectDelay
+
device = property(_getDevice, None, None, doc="Filesystem device name for this writer.")
scsiId = property(_getScsiId, None, None, doc="SCSI id for the device, in the form C{[<method>:]scsibus,target,lun}.")
hardwareId = property(_getHardwareId, None, None, doc="Hardware id for this writer, either SCSI id or device path.")
@@ -580,6 +590,7 @@
deviceHasTray = property(_getDeviceHasTray, None, None, doc="Indicates whether the device has a media tray.")
deviceCanEject = property(_getDeviceCanEject, None, None, doc="Indicates whether the device supports ejecting its media.")
refreshMediaDelay = property(_getRefreshMediaDelay, None, None, doc="Refresh media delay, in seconds.")
+ ejectDelay = property(_getEjectDelay, None, None, doc="Eject delay, in seconds.")
#################################################
@@ -808,16 +819,48 @@
If the writer was constructed with C{noEject=True}, then this is a no-op.
+ Starting with Debian wheezy on my backup hardware, I started seeing
+ consistent problems with the eject command. I couldn't tell whether
+ these problems were due to the device management system or to the new
+ kernel (3.2.0). Initially, I saw simple eject failures, possibly because
+ I was opening and closing the tray too quickly. I worked around that
+ behavior with the new ejectDelay flag.
+
+ Later, I sometimes ran into issues after writing an image to a disc:
+ eject would give errors like "unable to eject, last error: Inappropriate
+ ioctl for device". Various sources online (like Ubuntu bug #875543)
+ suggested that the drive was being locked somehow, and that the
+ workaround was to run 'eject -i off' to unlock it. Sure enough, that
+ fixed the problem for me, so now it's a normal error-handling strategy.
+
@raise IOError: If there is an error talking to the device.
"""
if not self._noEject:
if self._deviceHasTray and self._deviceCanEject:
args = CdWriter._buildOpenTrayArgs(self._device)
- command = resolveCommand(EJECT_COMMAND)
- result = executeCommand(command, args)[0]
+ result = executeCommand(EJECT_COMMAND, args)[0]
if result != 0:
- raise IOError("Error (%d) executing eject command to open tray." % result)
+ logger.debug("Eject failed; attempting kludge of unlocking the tray before retrying.")
+ self.unlockTray()
+ result = executeCommand(EJECT_COMMAND, args)[0]
+ if result != 0:
+ raise IOError("Error (%d) executing eject command to open tray (failed even after unlocking tray)." % result)
+ logger.debug("Kludge was apparently successful.")
+ if self.ejectDelay is not None:
+ logger.debug("Per configuration, sleeping %d seconds after opening tray." % self.ejectDelay)
+ time.sleep(self.ejectDelay)
+ def unlockTray(self):
+ """
+ Unlocks the device's tray.
+ @raise IOError: If there is an error talking to the device.
+ """
+ args = CdWriter._buildUnlockTrayArgs(self._device)
+ command = resolveCommand(EJECT_COMMAND)
+ result = executeCommand(command, args)[0]
+ if result != 0:
+ raise IOError("Error (%d) executing eject command to unlock tray." % result)
+
def closeTray(self):
"""
Closes the device's tray.
@@ -847,24 +890,25 @@
Sometimes, a device gets confused about the state of its media. Often,
all it takes to solve the problem is to eject the media and then
- immediately reload it. (There is also a configurable refresh media delay
- which can be applied after the tray is closed, for situations where this
- makes a difference.)
+ immediately reload it. (There are also configurable eject and refresh
+ media delays which can be applied, for situations where this makes a
+ difference.)
This only works if the device has a tray and supports ejecting its media.
We have no way to know if the tray is currently open or closed, so we
just send the appropriate command and hope for the best. If the device
does not have a tray or does not support ejecting its media, then we do
- nothing. The configured delay still applies, though.
+ nothing. The configured delays still apply, though.
@raise IOError: If there is an error talking to the device.
"""
self.openTray()
self.closeTray()
+ self.unlockTray() # on some systems, writing a disc leaves the tray locked, yikes!
if self.refreshMediaDelay is not None:
logger.debug("Per configuration, sleeping %d seconds to stabilize media state." % self.refreshMediaDelay)
time.sleep(self.refreshMediaDelay)
- logger.debug("Sleep is complete; hopefully media state is stable now.")
+ logger.debug("Media refresh complete; hopefully media state is stable now.")
def writeImage(self, imagePath=None, newDisc=False, writeMulti=True):
"""
@@ -1127,6 +1171,23 @@
return args
@staticmethod
+ def _buildUnlockTrayArgs(device):
+ """
+ Builds a list of arguments to be passed to a C{eject} command.
+
+ The arguments will cause the C{eject} command to unlock the tray.
+
+ @param device: Filesystem device name for this writer, i.e. C{/dev/cdrw}.
+
+ @return: List suitable for passing to L{util.executeCommand} as C{args}.
+ """
+ args = []
+ args.append("-i")
+ args.append("off")
+ args.append(device)
+ return args
+
+ @staticmethod
def _buildCloseTrayArgs(device):
"""
Builds a list of arguments to be passed to a C{eject} command.
Modified: cedar-backup2/trunk/CedarBackup2/writers/dvdwriter.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/writers/dvdwriter.py 2013-05-10 02:01:14 UTC (rev 1040)
+++ cedar-backup2/trunk/CedarBackup2/writers/dvdwriter.py 2013-05-10 02:05:13 UTC (rev 1041)
@@ -362,7 +362,7 @@
def __init__(self, device, scsiId=None, driveSpeed=None,
mediaType=MEDIA_DVDPLUSRW, noEject=False,
- refreshMediaDelay=0, unittest=False):
+ refreshMediaDelay=0, ejectDelay=0, unittest=False):
"""
Initializes a DVD writer object.
@@ -398,6 +398,9 @@
@param refreshMediaDelay: Refresh media delay to use, if any
@type refreshMediaDelay: Number of seconds, an integer >= 0
+ @param ejectDelay: Eject delay to use, if any
+ @type ejectDelay: Number of seconds, an integer >= 0
+
@param unittest: Turns off certain validations, for use in unit testing.
@type unittest: Boolean true/false
@@ -413,6 +416,7 @@
self._driveSpeed = validateDriveSpeed(driveSpeed)
self._media = MediaDefinition(mediaType)
self._refreshMediaDelay = refreshMediaDelay
+ self._ejectDelay = ejectDelay
if noEject:
self._deviceHasTray = False
self._deviceCanEject = False
@@ -473,6 +477,12 @@
"""
return self._refreshMediaDelay
+ def _getEjectDelay(self):
+ """
+ Property target used to get the configured eject delay, in seconds.
+ """
+ return self._ejectDelay
+
device = property(_getDevice, None, None, doc="Filesystem device name for this writer.")
scsiId = property(_getScsiId, None, None, doc="SCSI id for the device (saved for reference only).")
hardwareId = property(_getHardwareId, None, None, doc="Hardware id for this writer (always the device path).")
@@ -481,6 +491,7 @@
deviceHasTray = property(_getDeviceHasTray, None, None, doc="Indicates whether the device has a media tray.")
deviceCanEject = property(_getDeviceCanEject, None, None, doc="Indicates whether the device supports ejecting its media.")
refreshMediaDelay = property(_getRefreshMediaDelay, None, None, doc="Refresh media delay, in seconds.")
+ ejectDelay = property(_getEjectDelay, None, None, doc="Eject delay, in seconds.")
#################################################
@@ -612,6 +623,20 @@
does not have a tray or does not support ejecting its media, then we do
nothing.
+ Starting with Debian wheezy on my backup hardware, I started seeing
+ consistent problems with the eject command. I couldn't tell whether
+ these problems were due to the device management system or to the new
+ kernel (3.2.0). Initially, I saw simple eject failures, possibly because
+ I was opening and closing the tray too quickly. I worked around that
+ behavior with the new ejectDelay flag.
+
+ Later, I sometimes ran into issues after writing an image to a disc:
+ eject would give errors like "unable to eject, last error: Inappropriate
+ ioctl for device". Various sources online (like Ubuntu bug #875543)
+ suggested that the drive was being locked somehow, and that the
+ workaround was to run 'eject -i off' to unlock it. Sure enough, that
+ fixed the problem for me, so now it's a normal error-handling strategy.
+
@raise IOError: If there is an error talking to the device.
"""
if self._deviceHasTray and self._deviceCanEject:
@@ -619,8 +644,27 @@
args = [ self.device, ]
result = executeCommand(command, args)[0]
if result != 0:
- raise IOError("Error (%d) executing eject command to open tray." % result)
+ logger.debug("Eject failed; attempting kludge of unlocking the tray before retrying.")
+ self.unlockTray()
+ result = executeCommand(command, args)[0]
+ if result != 0:
+ raise IOError("Error (%d) executing eject command to open tray (failed even after unlocking tray)." % result)
+ logger.debug("Kludge was apparently successful.")
+ if self.ejectDelay is not None:
+ logger.debug("Per configuration, sleeping %d seconds after opening tray." % self.ejectDelay)
+ time.sleep(self.ejectDelay)
+ def unlockTray(self):
+ """
+ Unlocks the device's tray via 'eject -i off'.
+ @raise IOError: If there is an error talking to the device.
+ """
+ command = resolveCommand(EJECT_COMMAND)
+ args = [ "-i", "off", self.device, ]
+ result = executeCommand(command, args)[0]
+ if result != 0:
+ raise IOError("Error (%d) executing eject command to unlock tray." % result)
+
def closeTray(self):
"""
Closes the device's tray.
@@ -647,24 +691,25 @@
Sometimes, a device gets confused about the state of its media. Often,
all it takes to solve the problem is to eject the media and then
- immediately reload it. (There is also a configurable refresh media delay
- which can be applied after the tray is closed, for situations where this
- makes a difference.)
+ immediately reload it. (There are also configurable eject and refresh
+ media delays which can be applied, for situations where this makes a
+ difference.)
This only works if the device has a tray and supports ejecting its media.
We have no way to know if the tray is currently open or closed, so we
just send the appropriate command and hope for the best. If the device
does not have a tray or does not support ejecting its media, then we do
- nothing. The configured delay still applies, though.
+ nothing. The configured delays still apply, though.
@raise IOError: If there is an error talking to the device.
"""
self.openTray()
self.closeTray()
+ self.unlockTray() # on some systems, writing a disc leaves the tray locked, yikes!
if self.refreshMediaDelay is not None:
logger.debug("Per configuration, sleeping %d seconds to stabilize media state." % self.refreshMediaDelay)
time.sleep(self.refreshMediaDelay)
- logger.debug("Sleep is complete; hopefully media state is stable now.")
+ logger.debug("Media refresh complete; hopefully media state is stable now.")
def writeImage(self, imagePath=None, newDisc=False, writeMulti=True):
"""
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2013-05-10 02:01:14 UTC (rev 1040)
+++ cedar-backup2/trunk/Changelog 2013-05-10 02:05:13 UTC (rev 1041)
@@ -1,3 +1,8 @@
+Version 2.22.0 unreleased
+
+ * Add eject-related kludges to work around observed behavior.
+
+
Version 2.21.1 21 Mar 2013
* Apply patches provided by Jan Medlock as Debian bugs.
Modified: cedar-backup2/trunk/testcase/configtests.py
===================================================================
--- cedar-backup2/trunk/testcase/configtests.py 2013-05-10 02:01:14 UTC (rev 1040)
+++ cedar-backup2/trunk/testcase/configtests.py 2013-05-10 02:05:13 UTC (rev 1041)
@@ -7851,13 +7851,14 @@
self.failUnlessEqual(False, store.noEject)
self.failUnlessEqual(None, store.blankBehavior)
self.failUnlessEqual(None, store.refreshMediaDelay)
+ self.failUnlessEqual(None, store.ejectDelay)
def testConstructor_002(self):
"""
Test constructor with all values filled in, with valid values.
"""
behavior = BlankBehavior("weekly", "1.3")
- store = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior, 12)
+ store = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior, 12, 13)
self.failUnlessEqual("/source", store.sourceDir)
self.failUnlessEqual("cdr-74", store.mediaType)
self.failUnlessEqual("cdwriter", store.deviceType)
@@ -7870,6 +7871,7 @@
self.failUnlessEqual(True, store.noEject)
self.failUnlessEqual(behavior, store.blankBehavior)
self.failUnlessEqual(12, store.refreshMediaDelay)
+ self.failUnlessEqual(13, store.ejectDelay)
def testConstructor_003(self):
"""
@@ -8306,8 +8308,42 @@
self.failUnlessAssignRaises(ValueError, store, "refreshMediaDelay", CollectDir())
self.failUnlessEqual(None, store.refreshMediaDelay)
+ def testConstructor_044(self):
+ """
+ Test assignment of ejectDelay attribute, None value.
+ """
+ store = StoreConfig(ejectDelay=4)
+ self.failUnlessEqual(4, store.ejectDelay)
+ store.ejectDelay = None
+ self.failUnlessEqual(None, store.ejectDelay)
+ def testConstructor_045(self):
+ """
+ Test assignment of ejectDelay attribute, valid value.
+ """
+ store = StoreConfig()
+ self.failUnlessEqual(None, store.ejectDelay)
+ store.ejectDelay = 4
+ self.failUnlessEqual(4, store.ejectDelay)
+ store.ejectDelay = "12"
+ self.failUnlessEqual(12, store.ejectDelay)
+ store.ejectDelay = "0"
+ self.failUnlessEqual(None, store.ejectDelay)
+ store.ejectDelay = 0
+ self.failUnlessEqual(None, store.ejectDelay)
+ def testConstructor_046(self):
+ """
+ Test assignment of ejectDelay attribute, invalid value (not an integer).
+ """
+ store = StoreConfig()
+ self.failUnlessEqual(None, store.ejectDelay)
+ self.failUnlessAssignRaises(ValueError, store, "ejectDelay", "blech")
+ self.failUnlessEqual(None, store.ejectDelay)
+ self.failUnlessAssignRaises(ValueError, store, "ejectDelay", CollectDir())
+ self.failUnlessEqual(None, store.ejectDelay)
+
+
############################
# Test comparison operators
############################
@@ -8332,8 +8368,8 @@
"""
behavior1 = BlankBehavior("weekly", "1.3")
behavior2 = BlankBehavior("weekly", "1.3")
- store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior1, 4)
- store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4)
+ store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior1, 4, 5)
+ store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4, 5)
self.failUnlessEqual(store1, store2)
self.failUnless(store1 == store2)
self.failUnless(not store1 < store2)
@@ -8362,8 +8398,8 @@
"""
behavior1 = BlankBehavior("weekly", "1.3")
behavior2 = BlankBehavior("weekly", "1.3")
- store1 = StoreConfig("/source1", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior1, 4)
- store2 = StoreConfig("/source2", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4)
+ store1 = StoreConfig("/source1", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior1, 4, 5)
+ store2 = StoreConfig("/source2", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4, 5)
self.failIfEqual(store1, store2)
self.failUnless(not store1 == store2)
self.failUnless(store1 < store2)
@@ -8392,8 +8428,8 @@
"""
behavior1 = BlankBehavior("weekly", "1.3")
behavior2 = BlankBehavior("weekly", "1.3")
- store1 = StoreConfig("/source", "cdrw-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior1, 4)
- store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4)
+ store1 = StoreConfig("/source", "cdrw-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior1, 4, 5)
+ store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4, 5)
self.failIfEqual(store1, store2)
self.failUnless(not store1 == store2)
self.failUnless(not store1 < store2)
@@ -8436,8 +8472,8 @@
"""
behavior1 = BlankBehavior("weekly", "1.3")
behavior2 = BlankBehavior("weekly", "1.3")
- store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior1, 4)
- store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/hdd", "0,0,0", 4, True, True, True, True, behavior2, 4)
+ store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior1, 4, 5)
+ store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/hdd", "0,0,0", 4, True, True, True, True, behavior2, 4, 5)
self.failIfEqual(store1, store2)
self.failUnless(not store1 == store2)
self.failUnless(store1 < store2)
@@ -8466,8 +8502,8 @@
"""
behavior1 = BlankBehavior("weekly", "1.3")
behavior2 = BlankBehavior("weekly", "1.3")
- store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior1, 4)
- store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "ATA:0,0,0", 4, True, True, True, True, behavior2, 4)
+ store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior1, 4, 5)
+ store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "ATA:0,0,0", 4, True, True, True, True, behavior2, 4, 5)
self.failIfEqual(store1, store2)
self.failUnless(not store1 == store2)
self.failUnless(store1 < store2)
@@ -8496,8 +8532,8 @@
"""
behavior1 = BlankBehavior("weekly", "1.3")
behavior2 = BlankBehavior("weekly", "1.3")
- store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 1, True, True, True, True, behavior1, 4)
- store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4)
+ store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 1, True, True, True, True, behavior1, 4, 5)
+ store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4, 5)
self.failIfEqual(store1, store2)
self.failUnless(not store1 == store2)
self.failUnless(store1 < store2)
@@ -8512,8 +8548,8 @@
"""
behavior1 = BlankBehavior("weekly", "1.3")
behavior2 = BlankBehavior("weekly", "1.3")
- store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, False, True, True, True, behavior1, 4)
- store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4)
+ store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, False, True, True, True, behavior1, 4, 5)
+ store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4, 5)
self.failIfEqual(store1, store2)
self.failUnless(not store1 == store2)
self.failUnless(store1 < store2)
@@ -8528,8 +8564,8 @@
"""
behavior1 = BlankBehavior("weekly", "1.3")
behavior2 = BlankBehavior("weekly", "1.3")
- store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, False, True, True, behavior1, 4)
- store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4)
+ store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, False, True, True, behavior1, 4, 5)
+ store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4, 5)
self.failIfEqual(store1, store2)
self.failUnless(not store1 == store2)
self.failUnless(store1 < store2)
@@ -8544,8 +8580,8 @@
"""
behavior1 = BlankBehavior("weekly", "1.3")
behavior2 = BlankBehavior("weekly", "1.3")
- store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, False, True, behavior1, 4)
- store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4)
+ store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, False, True, behavior1, 4, 5)
+ store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4, 5)
self.failIfEqual(store1, store2)
self.failUnless(not store1 == store2)
self.failUnless(store1 < store2)
@@ -8560,8 +8596,8 @@
"""
behavior1 = BlankBehavior("weekly", "1.3")
behavior2 = BlankBehavior("weekly", "1.3")
- store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, False, behavior1, 4)
- store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4)
+ store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, False, behavior1, 4, 5)
+ store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4, 5)
self.failIfEqual(store1, store2)
self.failUnless(not store1 == store2)
self.failUnless(store1 < store2)
@@ -8591,8 +8627,8 @@
"""
behavior1 = BlankBehavior("daily", "1.3")
behavior2 = BlankBehavior("weekly", "1.3")
- store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior1, 4)
- store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4)
+ store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior1, 4, 5)
+ store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 4, True, True, True, True, behavior2, 4, 5)
self.failIfEqual(store1, store2)
self.failUnless(not store1 == store2)
self.failUnless(store1 < store2)
@@ -8621,8 +8657,8 @@
"""
behavior1 = BlankBehavior("weekly", "1.3")
behavior2 = BlankBehavior("weekly", "1.3")
- store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 1, True, True, True, True, behavior1, 1)
- store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 1, True, True, True, True, behavior2, 4)
+ store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 1, True, True, True, True, behavior1, 1, 5)
+ store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 1, True, True, True, True, behavior2, 4, 5)
self.failIfEqual(store1, store2)
self.failUnless(not store1 == store2)
self.failUnless(store1 < store2)
@@ -8631,7 +8667,37 @@
self.failUnless(not store1 >= store2)
self.failUnless(store1 != store2)
+ def testComparison_022(self):
+ """
+ Test comparison of two differing objects, ejectDelay differs (one None).
+ """
+ store1 = StoreConfig()
+ store2 = StoreConfig(ejectDelay=3)
+ self.failIfEqual(store1, store2)
+ self.failUnless(not store1 == store2)
+ self.failUnless(store1 < store2)
+ self.failUnless(store1 <= store2)
+ self.failUnless(not store1 > store2)
+ self.failUnless(not store1 >= store2)
+ self.failUnless(store1 != store2)
+ def testComparison_023(self):
+ """
+ Test comparison of two differing objects, ejectDelay differs.
+ """
+ behavior1 = BlankBehavior("weekly", "1.3")
+ behavior2 = BlankBehavior("weekly", "1.3")
+ store1 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 1, True, True, True, True, behavior1, 4, 1)
+ store2 = StoreConfig("/source", "cdr-74", "cdwriter", "/dev/cdrw", "0,0,0", 1, True, True, True, True, behavior2, 4, 5)
+ self.failIfEqual(store1, store2)
+ self.failUnless(not store1 == store2)
+ self.failUnless(store1 < store2)
+ self.failUnless(store1 <= store2)
+ self.failUnless(not store1 > store2)
+ self.failUnless(not store1 >= store2)
+ self.failUnless(store1 != store2)
+
+
########################
# TestPurgeConfig class
########################
@@ -11427,6 +11493,7 @@
expected.store.warnMidnite = True
expected.store.noEject = True
expected.store.refreshMediaDelay = 12
+ expected.store.ejectDelay = 13
expected.store.blankBehavior = BlankBehavior()
expected.store.blankBehavior.blankMode = "weekly"
expected.store.blankBehavior.blankFactor = "1.3"
@@ -12416,6 +12483,7 @@
before.store.warnMidnite = True
before.store.noEject = True
before.store.refreshMediaDelay = 12
+ before.store.ejectDelay = 13
self.failUnlessRaises(ValueError, before.extractXml, validate=True)
def testExtractXml_028(self):
@@ -12434,6 +12502,7 @@
before.store.warnMidnite = True
before.store.noEject = True
before.store.refreshMediaDelay = 12
+ before.store.ejectDelay = 13
beforeXml = before.extractXml(validate=False)
after = Config(xmlData=beforeXml, validate=False)
self.failUnlessEqual(before, after)
Modified: cedar-backup2/trunk/testcase/data/cback.conf.12
===================================================================
--- cedar-backup2/trunk/testcase/data/cback.conf.12 2013-05-10 02:01:14 UTC (rev 1040)
+++ cedar-backup2/trunk/testcase/data/cback.conf.12 2013-05-10 02:05:13 UTC (rev 1041)
@@ -13,6 +13,7 @@
<warn_midnite>Y</warn_midnite>
<no_eject>Y</no_eject>
<refresh_media_delay>12</refresh_media_delay>
+ <eject_delay>13</eject_delay>
<blank_behavior>
<mode>weekly</mode>
<factor>1.3</factor>
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-05-10 02:01:18
|
Revision: 1040
http://sourceforge.net/p/cedar-backup/code/1040
Author: pronovic
Date: 2013-05-10 02:01:14 +0000 (Fri, 10 May 2013)
Log Message:
-----------
Add comment
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/util.py
Modified: cedar-backup2/trunk/CedarBackup2/util.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/util.py 2013-04-15 16:22:39 UTC (rev 1039)
+++ cedar-backup2/trunk/CedarBackup2/util.py 2013-05-10 02:01:14 UTC (rev 1040)
@@ -1453,6 +1453,9 @@
@param returnOutput: Indicates whether to return the output of the command
@type returnOutput: Boolean C{True} or C{False}
+ @param ignoreStderr: Whether stderr should be discarded
+ @type ignoreSdderr: Boolean True or False
+
@param doNotLog: Indicates that output should not be logged.
@type doNotLog: Boolean C{True} or C{False}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-04-15 16:22:42
|
Revision: 1039
http://sourceforge.net/p/cedar-backup/code/1039
Author: pronovic
Date: 2013-04-15 16:22:39 +0000 (Mon, 15 Apr 2013)
Log Message:
-----------
Changes for new SourceForge project structure
Modified Paths:
--------------
cedar-backup2/trunk/util/release
Modified: cedar-backup2/trunk/util/release
===================================================================
--- cedar-backup2/trunk/util/release 2013-04-15 16:21:58 UTC (rev 1038)
+++ cedar-backup2/trunk/util/release 2013-04-15 16:22:39 UTC (rev 1039)
@@ -1,8 +1,10 @@
#!/bin/ksh
# Sensibly creates a release label in Subversion.
-REPO=https://cedar-backup.svn.sourceforge.net/svnroot/cedar-backup/cedar-backup2
+echo "You'll have to enter your SF password a lot of times, sorry"
+REPO=svn+ssh://pronovic@svn.code.sf.net/p/cedar-backup/code/cedar-backup2
+
if [[ $# != 1 || ${1} == "--help" ]]
then
print "Usage: release <version>"
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-04-15 16:22:00
|
Revision: 1038
http://sourceforge.net/p/cedar-backup/code/1038
Author: pronovic
Date: 2013-04-15 16:21:58 +0000 (Mon, 15 Apr 2013)
Log Message:
-----------
Tagging the 2.21.1 release of Cedar Backup.
Added Paths:
-----------
cedar-backup2/tags/CEDAR_BACKUP2_V2.21.1/
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-04-15 16:21:52
|
Revision: 1037
http://sourceforge.net/p/cedar-backup/code/1037
Author: pronovic
Date: 2013-04-15 16:21:51 +0000 (Mon, 15 Apr 2013)
Log Message:
-----------
Tagging new version of 2.21.1 release of Cedar Backup.
Removed Paths:
-------------
cedar-backup2/tags/CEDAR_BACKUP2_V2.21.1/
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-04-15 16:20:51
|
Revision: 1036
http://sourceforge.net/p/cedar-backup/code/1036
Author: pronovic
Date: 2013-04-15 16:20:49 +0000 (Mon, 15 Apr 2013)
Log Message:
-----------
Tagging the 2.21.1 release of Cedar Backup.
Added Paths:
-----------
cedar-backup2/tags/CEDAR_BACKUP2_V2.21.1/
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-04-15 16:17:16
|
Revision: 1035
http://sourceforge.net/p/cedar-backup/code/1035
Author: pronovic
Date: 2013-04-15 16:17:13 +0000 (Mon, 15 Apr 2013)
Log Message:
-----------
Fix procedure so I don't forget to label the code
Modified Paths:
--------------
cedar-backup2/trunk/doc/procedure.txt
Modified: cedar-backup2/trunk/doc/procedure.txt
===================================================================
--- cedar-backup2/trunk/doc/procedure.txt 2013-04-15 16:10:51 UTC (rev 1034)
+++ cedar-backup2/trunk/doc/procedure.txt 2013-04-15 16:17:13 UTC (rev 1035)
@@ -6,6 +6,7 @@
- Run unit tests one last time (make test)
- Run pychecker tests one last time (make check)
- Build the source distributions (make distrib)
+- Run the util/release script for the right version
- Copy source package to hcoop and install it
- Copy source package to shell.sourceforge.net and install it
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-04-15 16:10:55
|
Revision: 1034
http://sourceforge.net/p/cedar-backup/code/1034
Author: pronovic
Date: 2013-04-15 16:10:51 +0000 (Mon, 15 Apr 2013)
Log Message:
-----------
Point to software.cedar-solutions.com
Modified Paths:
--------------
web/trunk/cedar-backup/config/site-wide.gth
Modified: web/trunk/cedar-backup/config/site-wide.gth
===================================================================
--- web/trunk/cedar-backup/config/site-wide.gth 2013-04-15 16:08:02 UTC (rev 1033)
+++ web/trunk/cedar-backup/config/site-wide.gth 2013-04-15 16:10:51 UTC (rev 1034)
@@ -27,7 +27,7 @@
#define V2_DOC_DIR_URL <<DOC_DIR_URL>>/cedar-backup2
#define V2_MANUAL_URL <<V2_DOC_DIR_URL>>/manual/index.html
-#define CEDAR_URL http://cedar-solutions.com
+#define CEDAR_URL http://software.cedar-solutions.com/
#define CEDAR_DEBIAN_URL <<CEDAR_URL>>/debian.html
#define DEBIAN_URL http://debian.org/
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-04-15 16:08:05
|
Revision: 1033
http://sourceforge.net/p/cedar-backup/code/1033
Author: pronovic
Date: 2013-04-15 16:08:02 +0000 (Mon, 15 Apr 2013)
Log Message:
-----------
Change sidebar
Modified Paths:
--------------
web/trunk/cedar-backup/config/footer.gth
Modified: web/trunk/cedar-backup/config/footer.gth
===================================================================
--- web/trunk/cedar-backup/config/footer.gth 2013-04-15 16:01:13 UTC (rev 1032)
+++ web/trunk/cedar-backup/config/footer.gth 2013-04-15 16:08:02 UTC (rev 1033)
@@ -11,12 +11,12 @@
<ul>
<li><a href="<<HOME_URL>>" accesskey="h">Cedar Backup <span class="accesskey">H</span>ome</a></li>
<li><a href="<<V2_MANUAL_URL>>"accesskey="u">Software <span class="accesskey">U</span>ser Manual</a></li>
+ <li><a href="<<SF_LATEST_URL>>"accesskey="r">Latest <span class="accesskey">R</span>elease</a></li>
<li><a href="<<SF_PROJECT_URL>>"accesskey="p">SF <span class="accesskey">P</span>roject Page</a></li>
<li><a href="<<SF_DOWNLOADS_URL>>"accesskey="d">SF <span class="accesskey">D</span>ownloads</a></li>
<li><a href="<<SF_LISTS_URL>>"accesskey="l">SF Mailing <span class="accesskey">L</span>ists</a></li>
<li><a href="<<SF_BTS_URL>>" accesskey="b">SF <span class="accesskey">B</span>ug Tracking</a></li>
<li><a href="<<SF_CODE_URL>>" accesskey="c">SF Source <span class="accesskey">C</span>ode</a></li>
- <li><a href="<<SF_LATEST_URL>>"accesskey="a">Latest P<span class="accesskey">a</span>ckage</a></li>
<li><a href="<<TERMS_URL>>" accesskey="t"><span class="accesskey">T</span>erms of Use</a></li>
<li><a href="mailto:<<WEBMASTER_EMAIL>>" accesskey="m">Contact Web<span class="accesskey">m</span>aster</a></li>
</div>
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-04-15 16:01:16
|
Revision: 1032
http://sourceforge.net/p/cedar-backup/code/1032
Author: pronovic
Date: 2013-04-15 16:01:13 +0000 (Mon, 15 Apr 2013)
Log Message:
-----------
Remove deploy task
Modified Paths:
--------------
web/trunk/cedar-backup/Makefile
Modified: web/trunk/cedar-backup/Makefile
===================================================================
--- web/trunk/cedar-backup/Makefile 2013-04-15 15:59:30 UTC (rev 1031)
+++ web/trunk/cedar-backup/Makefile 2013-04-15 16:01:13 UTC (rev 1032)
@@ -46,16 +46,6 @@
@tar --exclude=.svn -cf content.tar css/ && \
cd source && tar --exclude=.svn -rf ../content.tar `find . -type f -name "*.html"`
-deploy: tarball
- # ====================================================================
- # If you have removed items, you must propogate those changes by hand.
- # ====================================================================
- cp $(TARBALL) $(CACHE) && \
- cd $(TARGET) && \
- tar -xf $(CACHE)/$(TARBALL) && \
- rm -f $(CACHE)/$(TARBALL) && \
- echo "Deployment complete."
-
distclean: allclean
allclean: clean
rm -f content.tar
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-04-15 15:59:33
|
Revision: 1031
http://sourceforge.net/p/cedar-backup/code/1031
Author: pronovic
Date: 2013-04-15 15:59:30 +0000 (Mon, 15 Apr 2013)
Log Message:
-----------
Update Makefile to work properly on SF shell server
Modified Paths:
--------------
web/trunk/cedar-backup/Makefile
Modified: web/trunk/cedar-backup/Makefile
===================================================================
--- web/trunk/cedar-backup/Makefile 2013-04-15 15:49:48 UTC (rev 1030)
+++ web/trunk/cedar-backup/Makefile 2013-04-15 15:59:30 UTC (rev 1031)
@@ -40,7 +40,7 @@
TARBALL=content.tar
CACHE=/home/users/p/pr/pronovic/tmp
-TARGET=/home/groups/c/ce/cedar-backup/htdocs
+TARGET=/home/project-web/cedar-backup/htdocs
tarball: $(OUTPUT_FILES)
@tar --exclude=.svn -cf content.tar css/ && \
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-04-15 15:49:51
|
Revision: 1030
http://sourceforge.net/p/cedar-backup/code/1030
Author: pronovic
Date: 2013-04-15 15:49:48 +0000 (Mon, 15 Apr 2013)
Log Message:
-----------
Update links to match upgraded SourceForge platform
Modified Paths:
--------------
web/trunk/cedar-backup/config/footer.gth
web/trunk/cedar-backup/config/site-wide.gth
Modified: web/trunk/cedar-backup/config/footer.gth
===================================================================
--- web/trunk/cedar-backup/config/footer.gth 2013-03-21 14:38:17 UTC (rev 1029)
+++ web/trunk/cedar-backup/config/footer.gth 2013-04-15 15:49:48 UTC (rev 1030)
@@ -16,6 +16,7 @@
<li><a href="<<SF_LISTS_URL>>"accesskey="l">SF Mailing <span class="accesskey">L</span>ists</a></li>
<li><a href="<<SF_BTS_URL>>" accesskey="b">SF <span class="accesskey">B</span>ug Tracking</a></li>
<li><a href="<<SF_CODE_URL>>" accesskey="c">SF Source <span class="accesskey">C</span>ode</a></li>
+ <li><a href="<<SF_LATEST_URL>>"accesskey="a">Latest P<span class="accesskey">a</span>ckage</a></li>
<li><a href="<<TERMS_URL>>" accesskey="t"><span class="accesskey">T</span>erms of Use</a></li>
<li><a href="mailto:<<WEBMASTER_EMAIL>>" accesskey="m">Contact Web<span class="accesskey">m</span>aster</a></li>
</div>
Modified: web/trunk/cedar-backup/config/site-wide.gth
===================================================================
--- web/trunk/cedar-backup/config/site-wide.gth 2013-03-21 14:38:17 UTC (rev 1029)
+++ web/trunk/cedar-backup/config/site-wide.gth 2013-04-15 15:49:48 UTC (rev 1030)
@@ -2,7 +2,7 @@
#define CONTACT_EMAIL pro...@ie...
#define SUPPORT_EMAIL su...@ce...
#define WEBMASTER_EMAIL pro...@ie...
-#define COPYRIGHT_DATE 2007
+#define COPYRIGHT_DATE 2007,2013
#define SITE_NAME Cedar Backup
@@ -14,16 +14,17 @@
#define DOC_DIR_URL <<HTTP_URL>>/docs
#define TERMS_URL <<HTTP_URL>>/terms.html
#define GPL_URL <<HTTP_URL>>/gpl.html
-#define V2_DOC_DIR_URL <<DOC_DIR_URL>>/cedar-backup2
#define SF_URL http://sourceforge.net
#define SF_PROJECT_URL <<SF_URL>>/projects/cedar-backup
-#define SF_DOWNLOADS_URL http://sourceforge.net/project/platformdownload.php?group_id=210468
-#define SF_LISTS_URL http://sourceforge.net/mail/?group_id=210468
-#define SF_BTS_URL http://sourceforge.net/tracker/?group_id=210468
-#define SF_CODE_URL https://sourceforge.net/svn/?group_id=210468
+#define SF_LATEST_URL <<SF_URL>>/projects/cedar-backup/files/latest/download
+#define SF_DOWNLOADS_URL <<SF_URL>>/projects/cedar-backup/files/cedar-backup2/
+#define SF_LISTS_URL <<SF_URL>>/p/cedar-backup/mailman/
+#define SF_BTS_URL <<SF_URL>>/p/cedar-backup/bugs/
+#define SF_CODE_URL <<SF_URL>>/p/cedar-backup/code/HEAD/tree/cedar-backup2/
#define SF_SVN_BROWSE_URL http://cedar-backup.svn.sourceforge.net/viewvc/cedar-backup/
+#define V2_DOC_DIR_URL <<DOC_DIR_URL>>/cedar-backup2
#define V2_MANUAL_URL <<V2_DOC_DIR_URL>>/manual/index.html
#define CEDAR_URL http://cedar-solutions.com
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-03-21 14:38:24
|
Revision: 1029
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=1029&view=rev
Author: pronovic
Date: 2013-03-21 14:38:17 +0000 (Thu, 21 Mar 2013)
Log Message:
-----------
Release 2.21.1
Modified Paths:
--------------
cedar-backup2/trunk/CREDITS
cedar-backup2/trunk/CedarBackup2/release.py
cedar-backup2/trunk/Changelog
Modified: cedar-backup2/trunk/CREDITS
===================================================================
--- cedar-backup2/trunk/CREDITS 2013-03-21 14:33:51 UTC (rev 1028)
+++ cedar-backup2/trunk/CREDITS 2013-03-21 14:38:17 UTC (rev 1029)
@@ -22,10 +22,10 @@
Pronovici. Some portions have been based on other pieces of open-source
software, as indicated in the source code itself.
-Unless otherwise indicated, all Cedar Backup source code is Copyright
-(c) 2004-2011 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/.
+Unless otherwise indicated, all Cedar Backup source code is Copyright (c)
+2004-2011,2013 Kenneth J. Pronovici and is released under the GNU General
+Public License, version 2. The contents of the GNU General Public License
+can be found in the LICENSE file, or can be downloaded from http://www.gnu.org/.
Various patches have been contributed to the Cedar Backup codebase by
Dmitry Rutsky. Major contributions include the initial implementation for
@@ -41,6 +41,9 @@
Zoran Bosnjak contributed changes to collect.py to implement recursive
collect behavior based on recursion level.
+Jan Medlock contributed patches to improve the manpage and to support
+recent versions of the /usr/bin/split command.
+
Minor code snippets derived from newsgroup and mailing list postings are
not generally attributed unless I used someone else's source code verbatim.
Modified: cedar-backup2/trunk/CedarBackup2/release.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/release.py 2013-03-21 14:33:51 UTC (rev 1028)
+++ cedar-backup2/trunk/CedarBackup2/release.py 2013-03-21 14:38:17 UTC (rev 1029)
@@ -33,8 +33,8 @@
AUTHOR = "Kenneth J. Pronovici"
EMAIL = "pro...@ie..."
-COPYRIGHT = "2004-2011"
-VERSION = "2.21.0"
-DATE = "12 Oct 2011"
+COPYRIGHT = "2004-2011,2013"
+VERSION = "2.21.1"
+DATE = "21 Mar 2013"
URL = "http://cedar-backup.sourceforge.net/"
Modified: cedar-backup2/trunk/Changelog
===================================================================
--- cedar-backup2/trunk/Changelog 2013-03-21 14:33:51 UTC (rev 1028)
+++ cedar-backup2/trunk/Changelog 2013-03-21 14:38:17 UTC (rev 1029)
@@ -1,4 +1,4 @@
-Version 2.21.1 unreleased
+Version 2.21.1 21 Mar 2013
* Apply patches provided by Jan Medlock as Debian bugs.
* Fix typo in manpage (showed -s instead of -D)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <pro...@us...> - 2013-03-21 14:33:57
|
Revision: 1028
http://cedar-backup.svn.sourceforge.net/cedar-backup/?rev=1028&view=rev
Author: pronovic
Date: 2013-03-21 14:33:51 +0000 (Thu, 21 Mar 2013)
Log Message:
-----------
Update copyright
Modified Paths:
--------------
cedar-backup2/trunk/CedarBackup2/extend/split.py
Modified: cedar-backup2/trunk/CedarBackup2/extend/split.py
===================================================================
--- cedar-backup2/trunk/CedarBackup2/extend/split.py 2013-03-21 14:15:05 UTC (rev 1027)
+++ cedar-backup2/trunk/CedarBackup2/extend/split.py 2013-03-21 14:33:51 UTC (rev 1028)
@@ -8,7 +8,7 @@
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
-# Copyright (c) 2007,2010 Kenneth J. Pronovici.
+# Copyright (c) 2007,2010,2013 Kenneth J. Pronovici.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|