Merge lp:~clay-gerrard/swift/run_daemon into lp:~hudson-openstack/swift/trunk

Proposed by clayg
Status: Merged
Approved by: John Dickinson
Approved revision: 134
Merged at revision: 132
Proposed branch: lp:~clay-gerrard/swift/run_daemon
Merge into: lp:~hudson-openstack/swift/trunk
Diff against target: 1287 lines (+653/-224)
26 files modified
bin/swift-account-auditor (+4/-9)
bin/swift-account-reaper (+4/-9)
bin/swift-account-replicator (+4/-9)
bin/swift-account-server (+3/-6)
bin/swift-account-stats-logger (+7/-8)
bin/swift-auth-server (+3/-5)
bin/swift-container-auditor (+4/-9)
bin/swift-container-replicator (+4/-10)
bin/swift-container-server (+3/-5)
bin/swift-container-updater (+4/-9)
bin/swift-log-stats-collector (+7/-8)
bin/swift-log-uploader (+17/-7)
bin/swift-object-auditor (+4/-10)
bin/swift-object-replicator (+4/-10)
bin/swift-object-server (+3/-5)
bin/swift-object-updater (+4/-9)
bin/swift-proxy-server (+3/-5)
swift/common/daemon.py (+41/-15)
swift/common/db_replicator.py (+0/-4)
swift/common/utils.py (+102/-12)
swift/common/wsgi.py (+51/-37)
test/unit/__init__.py (+15/-0)
test/unit/common/test_daemon.py (+83/-4)
test/unit/common/test_utils.py (+175/-4)
test/unit/common/test_wsgi.py (+103/-1)
test/unit/stats/test_log_processor.py (+1/-14)
To merge this branch: bzr merge lp:~clay-gerrard/swift/run_daemon
Reviewer Review Type Date Requested Status
John Dickinson Approve
gholt (community) Approve
Review via email: mp+40678@code.launchpad.net

Commit message

updated daemonize process, added option for servers/daemons to log to console

Description of the change

added helper/util to parse command line args
removed some duplicated code in server/daemon bin scripts
more standardized python/linux daemonization procedures
fixed lp:666957 "devauth server creates auth.db with the wrong
 privileges"
new run_daemon helper based on run_wsgi simplifies daemon
 launching/testing
new - all servers/daemons support verbose option when started
 interactivity which will log to the console
fixed lp:667839 "can't start servers with relative paths to configs"
added tests

To post a comment you must log in.
Revision history for this message
gholt (gholt) :
review: Approve
Revision history for this message
gholt (gholt) wrote :

This one's a wee bit hairy so another should take a peek as well.

review: Approve
Revision history for this message
Chuck Thier (cthier) wrote :

The only question I have is why the daemonize function ended up in swift.common.utils rather than swift.common.daemon? Otherwise, I think everything else looks good.

Revision history for this message
clayg (clay-gerrard) wrote :

thanks!

heh, funny story. When redbo and I talked about it - the plan was to expand utils.drop_privileges to also handle the capture_stdio stuff and rename it daemonize (for use in both run_wsgi and my new run_daemon) - but when I got into run_wsgi I decided that I want to redirect errors before the the port bindings... which needs elevated privileges for reserved ports. So I had to split up utils.daemonize back into utils.drop_privileges and the new utils.capture_stdio, which left utils.daemonize as pretty thin wrapper that was only called in common.daemon and the tests which I had already wrote in test.unit.common.test_utils :P

tl;dr - daemonize is dumb, run_daemon can call drop_privileges and capture_stdio same as run_wsgi; good catch - I'll remove it and rework the tests.

Revision history for this message
John Dickinson (notmyname) wrote :

run_daemon looks good, and it all works well with the stats processing code

however, the unit tests seem to print something out that needs to be removed (see test_utils.py:180)

review: Needs Fixing
Revision history for this message
clayg (clay-gerrard) wrote :

awesome, works great on nose 0.11.4 - it's way worse on 0.11.1 :P

Revision history for this message
clayg (clay-gerrard) wrote :

John, gholt - if you guys could pull down the latest changes and see if the unittest output got cleaned up?

lp:~clay-gerrard/swift/run_daemon updated
134. By clayg

remove capture_io stuff from db_replicator.__init__

Revision history for this message
clayg (clay-gerrard) wrote :

after discussion with redbo and gholt - we decided to pull out the stdio redirection from common.db_replicator.Replicator.__init__

Revision history for this message
John Dickinson (notmyname) wrote :

works now! yay!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/swift-account-auditor'
--- bin/swift-account-auditor 2010-08-31 23:12:59 +0000
+++ bin/swift-account-auditor 2010-11-19 22:38:08 +0000
@@ -14,15 +14,10 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys
18
19from swift.account.auditor import AccountAuditor17from swift.account.auditor import AccountAuditor
20from swift.common import utils18from swift.common.utils import parse_options
19from swift.common.daemon import run_daemon
2120
22if __name__ == '__main__':21if __name__ == '__main__':
23 if len(sys.argv) < 2:22 conf_file, options = parse_options(once=True)
24 print "Usage: swift-account-auditor CONFIG_FILE [once]"23 run_daemon(AccountAuditor, conf_file, **options)
25 sys.exit()
26 once = len(sys.argv) > 2 and sys.argv[2] == 'once'
27 conf = utils.readconf(sys.argv[1], 'account-auditor')
28 auditor = AccountAuditor(conf).run(once)
2924
=== modified file 'bin/swift-account-reaper'
--- bin/swift-account-reaper 2010-08-31 23:12:59 +0000
+++ bin/swift-account-reaper 2010-11-19 22:38:08 +0000
@@ -14,15 +14,10 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys
18
19from swift.account.reaper import AccountReaper17from swift.account.reaper import AccountReaper
20from swift.common import utils18from swift.common.utils import parse_options
19from swift.common.daemon import run_daemon
2120
22if __name__ == '__main__':21if __name__ == '__main__':
23 if len(sys.argv) < 2:22 conf_file, options = parse_options(once=True)
24 print "Usage: account-reaper CONFIG_FILE [once]"23 run_daemon(AccountReaper, conf_file, **options)
25 sys.exit()
26 once = len(sys.argv) > 2 and sys.argv[2] == 'once'
27 conf = utils.readconf(sys.argv[1], 'account-reaper')
28 reaper = AccountReaper(conf).run(once)
2924
=== modified file 'bin/swift-account-replicator'
--- bin/swift-account-replicator 2010-08-31 23:12:59 +0000
+++ bin/swift-account-replicator 2010-11-19 22:38:08 +0000
@@ -14,15 +14,10 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys
18
19from swift.common import utils
20from swift.account.replicator import AccountReplicator17from swift.account.replicator import AccountReplicator
18from swift.common.utils import parse_options
19from swift.common.daemon import run_daemon
2120
22if __name__ == '__main__':21if __name__ == '__main__':
23 if len(sys.argv) < 2:22 conf_file, options = parse_options(once=True)
24 print "Usage: swift-account-replicator CONFIG_FILE [once]"23 run_daemon(AccountReplicator, conf_file, **options)
25 sys.exit(1)
26 once = len(sys.argv) > 2 and sys.argv[2] == 'once'
27 conf = utils.readconf(sys.argv[1], 'account-replicator')
28 AccountReplicator(conf).run(once)
2924
=== modified file 'bin/swift-account-server'
--- bin/swift-account-server 2010-08-24 14:04:44 +0000
+++ bin/swift-account-server 2010-11-19 22:38:08 +0000
@@ -14,12 +14,9 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys17from swift.common.utils import parse_options
18
19from swift.common.wsgi import run_wsgi18from swift.common.wsgi import run_wsgi
2019
21if __name__ == '__main__':20if __name__ == '__main__':
22 if len(sys.argv) != 2:21 conf_file, options = parse_options()
23 sys.exit("Usage: %s CONFIG_FILE" % sys.argv[0])22 run_wsgi(conf_file, 'account-server', default_port=6002, **options)
24 run_wsgi(sys.argv[1], 'account-server', default_port=6002)
25
2623
=== modified file 'bin/swift-account-stats-logger'
--- bin/swift-account-stats-logger 2010-09-13 16:11:12 +0000
+++ bin/swift-account-stats-logger 2010-11-19 22:38:08 +0000
@@ -14,14 +14,13 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys
18
19from swift.stats.account_stats import AccountStat17from swift.stats.account_stats import AccountStat
20from swift.common import utils18from swift.common.utils import parse_options
19from swift.common.daemon import run_daemon
2120
22if __name__ == '__main__':21if __name__ == '__main__':
23 if len(sys.argv) < 2:22 conf_file, options = parse_options()
24 print "Usage: swift-account-stats-logger CONFIG_FILE"23 # currently AccountStat only supports run_once
25 sys.exit()24 options['once'] = True
26 stats_conf = utils.readconf(sys.argv[1], 'log-processor-stats')25 run_daemon(AccountStat, conf_file, section_name='log-processor-stats',
27 stats = AccountStat(stats_conf).run(once=True)26 **options)
2827
=== modified file 'bin/swift-auth-server'
--- bin/swift-auth-server 2010-08-24 14:08:16 +0000
+++ bin/swift-auth-server 2010-11-19 22:38:08 +0000
@@ -14,11 +14,9 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys17from swift.common.utils import parse_options
18
19from swift.common.wsgi import run_wsgi18from swift.common.wsgi import run_wsgi
2019
21if __name__ == '__main__':20if __name__ == '__main__':
22 if len(sys.argv) != 2:21 conf_file, options = parse_options()
23 sys.exit("Usage: %s CONFIG_FILE" % sys.argv[0])22 run_wsgi(conf_file, 'auth-server', default_port=11000, **options)
24 run_wsgi(sys.argv[1], 'auth-server', default_port=11000)
2523
=== modified file 'bin/swift-container-auditor'
--- bin/swift-container-auditor 2010-08-31 23:12:59 +0000
+++ bin/swift-container-auditor 2010-11-19 22:38:08 +0000
@@ -14,15 +14,10 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys
18
19from swift.container.auditor import ContainerAuditor17from swift.container.auditor import ContainerAuditor
20from swift.common import utils18from swift.common.utils import parse_options
19from swift.common.daemon import run_daemon
2120
22if __name__ == '__main__':21if __name__ == '__main__':
23 if len(sys.argv) < 2:22 conf_file, options = parse_options(once=True)
24 print "Usage: swift-container-auditor CONFIG_FILE [once]"23 run_daemon(ContainerAuditor, conf_file, **options)
25 sys.exit()
26 once = len(sys.argv) > 2 and sys.argv[2] == 'once'
27 conf = utils.readconf(sys.argv[1], 'container-auditor')
28 ContainerAuditor(conf).run(once)
2924
=== modified file 'bin/swift-container-replicator'
--- bin/swift-container-replicator 2010-08-31 23:12:59 +0000
+++ bin/swift-container-replicator 2010-11-19 22:38:08 +0000
@@ -14,16 +14,10 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys
18
19from swift.common import db, utils
20from swift.container.replicator import ContainerReplicator17from swift.container.replicator import ContainerReplicator
18from swift.common.utils import parse_options
19from swift.common.daemon import run_daemon
2120
22if __name__ == '__main__':21if __name__ == '__main__':
23 if len(sys.argv) < 2:22 conf_file, options = parse_options(once=True)
24 print "Usage: swift-container-replicator CONFIG_FILE [once]"23 run_daemon(ContainerReplicator, conf_file, **options)
25 sys.exit(1)
26 once = len(sys.argv) > 2 and sys.argv[2] == 'once'
27 conf = utils.readconf(sys.argv[1], 'container-replicator')
28 ContainerReplicator(conf).run(once)
29
3024
=== modified file 'bin/swift-container-server'
--- bin/swift-container-server 2010-08-24 14:04:44 +0000
+++ bin/swift-container-server 2010-11-19 22:38:08 +0000
@@ -14,11 +14,9 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys17from swift.common.utils import parse_options
18
19from swift.common.wsgi import run_wsgi18from swift.common.wsgi import run_wsgi
2019
21if __name__ == '__main__':20if __name__ == '__main__':
22 if len(sys.argv) != 2:21 conf_file, options = parse_options()
23 sys.exit("Usage: %s CONFIG_FILE" % sys.argv[0])22 run_wsgi(conf_file, 'container-server', default_port=6001, **options)
24 run_wsgi(sys.argv[1], 'container-server', default_port=6001)
2523
=== modified file 'bin/swift-container-updater'
--- bin/swift-container-updater 2010-08-31 23:12:59 +0000
+++ bin/swift-container-updater 2010-11-19 22:38:08 +0000
@@ -14,15 +14,10 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys
18
19from swift.container.updater import ContainerUpdater17from swift.container.updater import ContainerUpdater
20from swift.common import utils18from swift.common.utils import parse_options
19from swift.common.daemon import run_daemon
2120
22if __name__ == '__main__':21if __name__ == '__main__':
23 if len(sys.argv) < 2:22 conf_file, options = parse_options(once=True)
24 print "Usage: swift-container-updater CONFIG_FILE [once]"23 run_daemon(ContainerUpdater, conf_file, **options)
25 sys.exit()
26 once = len(sys.argv) > 2 and sys.argv[2] == 'once'
27 conf = utils.readconf(sys.argv[1], 'container-updater')
28 ContainerUpdater(conf).run(once)
2924
=== modified file 'bin/swift-log-stats-collector'
--- bin/swift-log-stats-collector 2010-09-22 14:32:40 +0000
+++ bin/swift-log-stats-collector 2010-11-19 22:38:08 +0000
@@ -14,14 +14,13 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys
18
19from swift.stats.log_processor import LogProcessorDaemon17from swift.stats.log_processor import LogProcessorDaemon
20from swift.common import utils18from swift.common.utils import parse_options
19from swift.common.daemon import run_daemon
2120
22if __name__ == '__main__':21if __name__ == '__main__':
23 if len(sys.argv) < 2:22 conf_file, options = parse_options()
24 print "Usage: swift-log-stats-collector CONFIG_FILE"23 # currently the LogProcessorDaemon only supports run_once
25 sys.exit()24 options['once'] = True
26 conf = utils.readconf(sys.argv[1], log_name='log-stats-collector')25 run_daemon(LogProcessorDaemon, conf_file, section_name=None,
27 stats = LogProcessorDaemon(conf).run(once=True)26 log_name='log-stats-collector', **options)
2827
=== modified file 'bin/swift-log-uploader'
--- bin/swift-log-uploader 2010-09-10 20:08:06 +0000
+++ bin/swift-log-uploader 2010-11-19 22:38:08 +0000
@@ -17,15 +17,25 @@
17import sys17import sys
1818
19from swift.stats.log_uploader import LogUploader19from swift.stats.log_uploader import LogUploader
20from swift.common.utils import parse_options
20from swift.common import utils21from swift.common import utils
2122
22if __name__ == '__main__':23if __name__ == '__main__':
23 if len(sys.argv) < 3:24 conf_file, options = parse_options(usage="Usage: %prog CONFIG_FILE PLUGIN")
24 print "Usage: swift-log-uploader CONFIG_FILE plugin"25 try:
25 sys.exit()26 plugin = options['extra_args'][0]
26 uploader_conf = utils.readconf(sys.argv[1], 'log-processor')27 except IndexError:
27 plugin = sys.argv[2]28 print "Error: missing plugin name"
29 sys.exit(1)
30
31 uploader_conf = utils.readconf(conf_file, 'log-processor')
28 section_name = 'log-processor-%s' % plugin32 section_name = 'log-processor-%s' % plugin
29 plugin_conf = utils.readconf(sys.argv[1], section_name)33 plugin_conf = utils.readconf(conf_file, section_name)
30 uploader_conf.update(plugin_conf)34 uploader_conf.update(plugin_conf)
31 uploader = LogUploader(uploader_conf, plugin).run(once=True)35
36 # pre-configure logger
37 logger = utils.get_logger(uploader_conf, plugin,
38 log_to_console=options.get('verbose', False))
39 # currently LogUploader only supports run_once
40 options['once'] = True
41 uploader = LogUploader(uploader_conf, plugin).run(**options)
3242
=== modified file 'bin/swift-object-auditor'
--- bin/swift-object-auditor 2010-08-31 23:12:59 +0000
+++ bin/swift-object-auditor 2010-11-19 22:38:08 +0000
@@ -14,16 +14,10 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys
18
19from swift.obj.auditor import ObjectAuditor17from swift.obj.auditor import ObjectAuditor
20from swift.common import utils18from swift.common.utils import parse_options
19from swift.common.daemon import run_daemon
2120
22if __name__ == '__main__':21if __name__ == '__main__':
23 if len(sys.argv) < 2:22 conf_file, options = parse_options(once=True)
24 print "Usage: swift-object-auditor CONFIG_FILE [once]"23 run_daemon(ObjectAuditor, conf_file, **options)
25 sys.exit()
26
27 once = len(sys.argv) > 2 and sys.argv[2] == 'once'
28 conf = utils.readconf(sys.argv[1], 'object-auditor')
29 ObjectAuditor(conf).run(once)
3024
=== modified file 'bin/swift-object-replicator'
--- bin/swift-object-replicator 2010-08-31 23:12:59 +0000
+++ bin/swift-object-replicator 2010-11-19 22:38:08 +0000
@@ -14,16 +14,10 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys
18
19from swift.obj.replicator import ObjectReplicator17from swift.obj.replicator import ObjectReplicator
20from swift.common import utils18from swift.common.utils import parse_options
19from swift.common.daemon import run_daemon
2120
22if __name__ == '__main__':21if __name__ == '__main__':
23 if len(sys.argv) < 2:22 conf_file, options = parse_options(once=True)
24 print "Usage: swift-object-replicator CONFIG_FILE [once]"23 run_daemon(ObjectReplicator, conf_file, **options)
25 sys.exit()
26 conf = utils.readconf(sys.argv[1], "object-replicator")
27 once = (len(sys.argv) > 2 and sys.argv[2] == 'once') or \
28 conf.get('daemonize', 'true') not in utils.TRUE_VALUES
29 ObjectReplicator(conf).run(once)
3024
=== modified file 'bin/swift-object-server'
--- bin/swift-object-server 2010-08-24 14:04:44 +0000
+++ bin/swift-object-server 2010-11-19 22:38:08 +0000
@@ -14,11 +14,9 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys17from swift.common.utils import parse_options
18
19from swift.common.wsgi import run_wsgi18from swift.common.wsgi import run_wsgi
2019
21if __name__ == '__main__':20if __name__ == '__main__':
22 if len(sys.argv) != 2:21 conf_file, options = parse_options()
23 sys.exit("Usage: %s CONFIG_FILE" % sys.argv[0])22 run_wsgi(conf_file, 'object-server', default_port=6000, **options)
24 run_wsgi(sys.argv[1], 'object-server', default_port=6000)
2523
=== modified file 'bin/swift-object-updater'
--- bin/swift-object-updater 2010-08-31 23:12:59 +0000
+++ bin/swift-object-updater 2010-11-19 22:38:08 +0000
@@ -14,15 +14,10 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys
18
19from swift.obj.updater import ObjectUpdater17from swift.obj.updater import ObjectUpdater
20from swift.common import utils18from swift.common.utils import parse_options
19from swift.common.daemon import run_daemon
2120
22if __name__ == '__main__':21if __name__ == '__main__':
23 if len(sys.argv) < 2:22 conf_file, options = parse_options(once=True)
24 print "Usage: swift-object-updater CONFIG_FILE [once]"23 run_daemon(ObjectUpdater, conf_file, **options)
25 sys.exit(1)
26 once = len(sys.argv) > 2 and sys.argv[2] == 'once'
27 conf = utils.readconf(sys.argv[1], 'object-updater')
28 ObjectUpdater(conf).run(once)
2924
=== modified file 'bin/swift-proxy-server'
--- bin/swift-proxy-server 2010-08-24 14:04:44 +0000
+++ bin/swift-proxy-server 2010-11-19 22:38:08 +0000
@@ -14,11 +14,9 @@
14# See the License for the specific language governing permissions and14# See the License for the specific language governing permissions and
15# limitations under the License.15# limitations under the License.
1616
17import sys17from swift.common.utils import parse_options
18
19from swift.common.wsgi import run_wsgi18from swift.common.wsgi import run_wsgi
2019
21if __name__ == '__main__':20if __name__ == '__main__':
22 if len(sys.argv) != 2:21 conf_file, options = parse_options()
23 sys.exit("Usage: %s CONFIG_FILE" % sys.argv[0])22 run_wsgi(conf_file, 'proxy-server', default_port=8080, **options)
24 run_wsgi(sys.argv[1], 'proxy-server', default_port=8080)
2523
=== modified file 'swift/common/daemon.py'
--- swift/common/daemon.py 2010-10-15 19:28:38 +0000
+++ swift/common/daemon.py 2010-11-19 22:38:08 +0000
@@ -16,6 +16,7 @@
16import os16import os
17import sys17import sys
18import signal18import signal
19from re import sub
19from swift.common import utils20from swift.common import utils
2021
2122
@@ -34,23 +35,11 @@
34 """Override this to run forever"""35 """Override this to run forever"""
35 raise NotImplementedError('run_forever not implemented')36 raise NotImplementedError('run_forever not implemented')
3637
37 def run(self, once=False, capture_stdout=True, capture_stderr=True):38 def run(self, once=False, **kwargs):
38 """Run the daemon"""39 """Run the daemon"""
39 # log uncaught exceptions40 utils.validate_configuration()
40 sys.excepthook = lambda *exc_info: \41 utils.capture_stdio(self.logger, **kwargs)
41 self.logger.critical('UNCAUGHT EXCEPTION', exc_info=exc_info)
42 if capture_stdout:
43 sys.stdout = utils.LoggerFileObject(self.logger)
44 if capture_stderr:
45 sys.stderr = utils.LoggerFileObject(self.logger)
46
47 utils.drop_privileges(self.conf.get('user', 'swift'))42 utils.drop_privileges(self.conf.get('user', 'swift'))
48 utils.validate_configuration()
49
50 try:
51 os.setsid()
52 except OSError:
53 pass
5443
55 def kill_children(*args):44 def kill_children(*args):
56 signal.signal(signal.SIGTERM, signal.SIG_IGN)45 signal.signal(signal.SIGTERM, signal.SIG_IGN)
@@ -63,3 +52,40 @@
63 self.run_once()52 self.run_once()
64 else:53 else:
65 self.run_forever()54 self.run_forever()
55
56
57def run_daemon(klass, conf_file, section_name='',
58 once=False, **kwargs):
59 """
60 Loads settings from conf, then instantiates daemon "klass" and runs the
61 daemon with the specified once kwarg. The section_name will be derived
62 from the daemon "klass" if not provided (e.g. ObjectReplicator =>
63 object-replicator).
64
65 :param klass: Class to instantiate, subclass of common.daemon.Daemon
66 :param conf_file: Path to configuration file
67 :param section_name: Section name from conf file to load config from
68 :param once: Passed to daemon run method
69 """
70 # very often the config section_name is based on the class name
71 # the None singleton will be passed through to readconf as is
72 if section_name is '':
73 section_name = sub(r'([a-z])([A-Z])', r'\1-\2',
74 klass.__name__).lower()
75 conf = utils.readconf(conf_file, section_name,
76 log_name=kwargs.get('log_name'))
77
78 # once on command line (i.e. daemonize=false) will over-ride config
79 once = once or conf.get('daemonize', 'true') not in utils.TRUE_VALUES
80
81 # pre-configure logger
82 if 'logger' in kwargs:
83 logger = kwargs.pop('logger')
84 else:
85 logger = utils.get_logger(conf, conf.get('log_name', section_name),
86 log_to_console=kwargs.pop('verbose', False))
87 try:
88 klass(conf).run(once=once, **kwargs)
89 except KeyboardInterrupt:
90 logger.info('User quit')
91 logger.info('Exited')
6692
=== modified file 'swift/common/db_replicator.py'
--- swift/common/db_replicator.py 2010-09-01 15:56:37 +0000
+++ swift/common/db_replicator.py 2010-11-19 22:38:08 +0000
@@ -93,10 +93,6 @@
93 def __init__(self, conf):93 def __init__(self, conf):
94 self.conf = conf94 self.conf = conf
95 self.logger = get_logger(conf)95 self.logger = get_logger(conf)
96 # log uncaught exceptions
97 sys.excepthook = lambda * exc_info: \
98 self.logger.critical('UNCAUGHT EXCEPTION', exc_info=exc_info)
99 sys.stdout = sys.stderr = LoggerFileObject(self.logger)
100 self.root = conf.get('devices', '/srv/node')96 self.root = conf.get('devices', '/srv/node')
101 self.mount_check = conf.get('mount_check', 'true').lower() in \97 self.mount_check = conf.get('mount_check', 'true').lower() in \
102 ('true', 't', '1', 'on', 'yes', 'y')98 ('true', 't', '1', 'on', 'yes', 'y')
10399
=== modified file 'swift/common/utils.py'
--- swift/common/utils.py 2010-11-16 19:52:18 +0000
+++ swift/common/utils.py 2010-11-19 22:38:08 +0000
@@ -31,6 +31,7 @@
31import ctypes.util31import ctypes.util
32import struct32import struct
33from ConfigParser import ConfigParser, NoSectionError, NoOptionError33from ConfigParser import ConfigParser, NoSectionError, NoOptionError
34from optparse import OptionParser
34from tempfile import mkstemp35from tempfile import mkstemp
35import cPickle as pickle36import cPickle as pickle
3637
@@ -283,17 +284,6 @@
283 return self284 return self
284285
285286
286def drop_privileges(user):
287 """
288 Sets the userid of the current process
289
290 :param user: User id to change privileges to
291 """
292 user = pwd.getpwnam(user)
293 os.setgid(user[3])
294 os.setuid(user[2])
295
296
297class NamedLogger(object):287class NamedLogger(object):
298 """Cheesy version of the LoggerAdapter available in Python 3"""288 """Cheesy version of the LoggerAdapter available in Python 3"""
299289
@@ -343,7 +333,7 @@
343 call('%s %s: %s' % (self.server, msg, emsg), *args)333 call('%s %s: %s' % (self.server, msg, emsg), *args)
344334
345335
346def get_logger(conf, name=None):336def get_logger(conf, name=None, log_to_console=False):
347 """337 """
348 Get the current system logger using config settings.338 Get the current system logger using config settings.
349339
@@ -355,11 +345,18 @@
355345
356 :param conf: Configuration dict to read settings from346 :param conf: Configuration dict to read settings from
357 :param name: Name of the logger347 :param name: Name of the logger
348 :param log_to_console: Add handler which writes to console on stderr
358 """349 """
359 root_logger = logging.getLogger()350 root_logger = logging.getLogger()
360 if hasattr(get_logger, 'handler') and get_logger.handler:351 if hasattr(get_logger, 'handler') and get_logger.handler:
361 root_logger.removeHandler(get_logger.handler)352 root_logger.removeHandler(get_logger.handler)
362 get_logger.handler = None353 get_logger.handler = None
354 if log_to_console:
355 # check if a previous call to get_logger already added a console logger
356 if hasattr(get_logger, 'console') and get_logger.console:
357 root_logger.removeHandler(get_logger.console)
358 get_logger.console = logging.StreamHandler(sys.__stderr__)
359 root_logger.addHandler(get_logger.console)
363 if conf is None:360 if conf is None:
364 root_logger.setLevel(logging.INFO)361 root_logger.setLevel(logging.INFO)
365 return NamedLogger(root_logger, name)362 return NamedLogger(root_logger, name)
@@ -375,6 +372,99 @@
375 return NamedLogger(root_logger, name)372 return NamedLogger(root_logger, name)
376373
377374
375def drop_privileges(user):
376 """
377 Sets the userid/groupid of the current process, get session leader, etc.
378
379 :param user: User name to change privileges to
380 """
381 user = pwd.getpwnam(user)
382 os.setgid(user[3])
383 os.setuid(user[2])
384 try:
385 os.setsid()
386 except OSError:
387 pass
388 os.chdir('/') # in case you need to rmdir on where you started the daemon
389 os.umask(0) # ensure files are created with the correct privileges
390
391
392def capture_stdio(logger, **kwargs):
393 """
394 Log unhandled exceptions, close stdio, capture stdout and stderr.
395
396 param logger: Logger object to use
397 """
398 # log uncaught exceptions
399 sys.excepthook = lambda * exc_info: \
400 logger.critical('UNCAUGHT EXCEPTION', exc_info=exc_info)
401
402 # collect stdio file desc not in use for logging
403 stdio_fds = [0, 1, 2]
404 if hasattr(get_logger, 'console'):
405 stdio_fds.remove(get_logger.console.stream.fileno())
406
407 with open(os.devnull, 'r+b') as nullfile:
408 # close stdio (excludes fds open for logging)
409 for desc in stdio_fds:
410 try:
411 os.dup2(nullfile.fileno(), desc)
412 except OSError:
413 pass
414
415 # redirect stdio
416 if kwargs.pop('capture_stdout', True):
417 sys.stdout = LoggerFileObject(logger)
418 if kwargs.pop('capture_stderr', True):
419 sys.stderr = LoggerFileObject(logger)
420
421
422def parse_options(usage="%prog CONFIG [options]", once=False, test_args=None):
423 """
424 Parse standard swift server/daemon options with optparse.OptionParser.
425
426 :param usage: String describing usage
427 :param once: Boolean indicating the "once" option is available
428 :param test_args: Override sys.argv; used in testing
429
430 :returns : Tuple of (config, options); config is an absolute path to the
431 config file, options is the parser options as a dictionary.
432
433 :raises SystemExit: First arg (CONFIG) is required, file must exist
434 """
435 parser = OptionParser(usage)
436 parser.add_option("-v", "--verbose", default=False, action="store_true",
437 help="log to console")
438 if once:
439 parser.add_option("-o", "--once", default=False, action="store_true",
440 help="only run one pass of daemon")
441
442 # if test_args is None, optparse will use sys.argv[:1]
443 options, args = parser.parse_args(args=test_args)
444
445 if not args:
446 parser.print_usage()
447 print "Error: missing config file argument"
448 sys.exit(1)
449 config = os.path.abspath(args.pop(0))
450 if not os.path.exists(config):
451 parser.print_usage()
452 print "Error: unable to locate %s" % config
453 sys.exit(1)
454
455 extra_args = []
456 # if any named options appear in remaining args, set the option to True
457 for arg in args:
458 if arg in options.__dict__:
459 setattr(options, arg, True)
460 else:
461 extra_args.append(arg)
462
463 options = vars(options)
464 options['extra_args'] = extra_args
465 return config, options
466
467
378def whataremyips():468def whataremyips():
379 """469 """
380 Get the machine's ip addresses using ifconfig470 Get the machine's ip addresses using ifconfig
381471
=== modified file 'swift/common/wsgi.py'
--- swift/common/wsgi.py 2010-10-15 19:28:38 +0000
+++ swift/common/wsgi.py 2010-11-19 22:38:08 +0000
@@ -34,7 +34,7 @@
34from eventlet.green import socket, ssl34from eventlet.green import socket, ssl
3535
36from swift.common.utils import get_logger, drop_privileges, \36from swift.common.utils import get_logger, drop_privileges, \
37 validate_configuration, LoggerFileObject, NullLogger37 validate_configuration, capture_stdio, NullLogger
3838
3939
40def monkey_patch_mimetools():40def monkey_patch_mimetools():
@@ -56,41 +56,17 @@
5656
57 mimetools.Message.parsetype = parsetype57 mimetools.Message.parsetype = parsetype
5858
5959def get_socket(conf, default_port=8080):
60# We might be able to pull pieces of this out to test, but right now it seems60 """Bind socket to bind ip:port in conf
61# like more work than it's worth.61
62def run_wsgi(conf_file, app_section, *args, **kwargs): # pragma: no cover62 :param conf: Configuration dict to read settings from
63 """63 :param default_port: port to use if not specified in conf
64 Loads common settings from conf, then instantiates app and runs64
65 the server using the specified number of workers.65 :returns : a socket object as returned from socket.listen or ssl.wrap_socket
6666 if conf specifies cert_file
67 :param conf_file: Path to paste.deploy style configuration file67 """
68 :param app_section: App name from conf file to load config from
69 """
70
71 try:
72 conf = appconfig('config:%s' % conf_file, name=app_section)
73 log_name = conf.get('log_name', app_section)
74 app = loadapp('config:%s' % conf_file,
75 global_conf={'log_name': log_name})
76 except Exception, e:
77 print "Error trying to load config %s: %s" % (conf_file, e)
78 return
79 if 'logger' in kwargs:
80 logger = kwargs['logger']
81 else:
82 logger = get_logger(conf, log_name)
83 # log uncaught exceptions
84 sys.excepthook = lambda * exc_info: \
85 logger.critical('UNCAUGHT EXCEPTION', exc_info=exc_info)
86 sys.stdout = sys.stderr = LoggerFileObject(logger)
87
88 try:
89 os.setsid()
90 except OSError:
91 no_cover = True # pass
92 bind_addr = (conf.get('bind_ip', '0.0.0.0'),68 bind_addr = (conf.get('bind_ip', '0.0.0.0'),
93 int(conf.get('bind_port', kwargs.get('default_port', 8080))))69 int(conf.get('bind_port', default_port)))
94 sock = None70 sock = None
95 retry_until = time.time() + 3071 retry_until = time.time() + 30
96 while not sock and time.time() < retry_until:72 while not sock and time.time() < retry_until:
@@ -110,9 +86,43 @@
110 # in my experience, sockets can hang around forever without keepalive86 # in my experience, sockets can hang around forever without keepalive
111 sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)87 sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
112 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 600)88 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 600)
113 worker_count = int(conf.get('workers', '1'))89 return sock
90
91
92# TODO: pull pieces of this out to test
93def run_wsgi(conf_file, app_section, *args, **kwargs):
94 """
95 Loads common settings from conf, then instantiates app and runs
96 the server using the specified number of workers.
97
98 :param conf_file: Path to paste.deploy style configuration file
99 :param app_section: App name from conf file to load config from
100 """
101
102 try:
103 conf = appconfig('config:%s' % conf_file, name=app_section)
104 except Exception, e:
105 print "Error trying to load config %s: %s" % (conf_file, e)
106 return
107 validate_configuration()
108
109 # pre-configure logger
110 log_name = conf.get('log_name', app_section)
111 if 'logger' in kwargs:
112 logger = kwargs.pop('logger')
113 else:
114 logger = get_logger(conf, log_name,
115 log_to_console=kwargs.pop('verbose', False))
116
117 # redirect errors to logger and close stdio
118 capture_stdio(logger)
119 # bind to address and port
120 sock = get_socket(conf, default_port=kwargs.get('default_port', 8080))
121 # remaining tasks should not require elevated privileges
114 drop_privileges(conf.get('user', 'swift'))122 drop_privileges(conf.get('user', 'swift'))
115 validate_configuration()123
124 # finally after binding to ports and privilege drop, run app __init__ code
125 app = loadapp('config:%s' % conf_file, global_conf={'log_name': log_name})
116126
117 def run_server():127 def run_server():
118 wsgi.HttpProtocol.default_request_version = "HTTP/1.0"128 wsgi.HttpProtocol.default_request_version = "HTTP/1.0"
@@ -127,6 +137,7 @@
127 raise137 raise
128 pool.waitall()138 pool.waitall()
129139
140 worker_count = int(conf.get('workers', '1'))
130 # Useful for profiling [no forks].141 # Useful for profiling [no forks].
131 if worker_count == 0:142 if worker_count == 0:
132 run_server()143 run_server()
@@ -169,6 +180,9 @@
169 except OSError, err:180 except OSError, err:
170 if err.errno not in (errno.EINTR, errno.ECHILD):181 if err.errno not in (errno.EINTR, errno.ECHILD):
171 raise182 raise
183 except KeyboardInterrupt:
184 logger.info('User quit')
185 break
172 greenio.shutdown_safe(sock)186 greenio.shutdown_safe(sock)
173 sock.close()187 sock.close()
174 logger.info('Exited')188 logger.info('Exited')
175189
=== modified file 'test/unit/__init__.py'
--- test/unit/__init__.py 2010-07-29 20:06:01 +0000
+++ test/unit/__init__.py 2010-11-19 22:38:08 +0000
@@ -1,5 +1,8 @@
1""" Swift tests """1""" Swift tests """
22
3import os
4from contextlib import contextmanager
5from tempfile import NamedTemporaryFile
3from eventlet.green import socket6from eventlet.green import socket
47
58
@@ -23,6 +26,18 @@
23 rv.connect(hostport)26 rv.connect(hostport)
24 return rv27 return rv
2528
29
30@contextmanager
31def tmpfile(content):
32 with NamedTemporaryFile('w', delete=False) as f:
33 file_name = f.name
34 f.write(str(content))
35 try:
36 yield file_name
37 finally:
38 os.unlink(file_name)
39
40
26class MockTrue(object):41class MockTrue(object):
27 """42 """
28 Instances of MockTrue evaluate like True43 Instances of MockTrue evaluate like True
2944
=== modified file 'test/unit/common/test_daemon.py'
--- test/unit/common/test_daemon.py 2010-10-07 15:23:17 +0000
+++ test/unit/common/test_daemon.py 2010-11-19 22:38:08 +0000
@@ -13,16 +13,95 @@
13# See the License for the specific language governing permissions and13# See the License for the specific language governing permissions and
14# limitations under the License.14# limitations under the License.
1515
16# TODO: Tests16# TODO: Test kill_children signal handlers
1717
18import unittest18import unittest
19from swift.common import daemon19from getpass import getuser
20import logging
21from StringIO import StringIO
22from test.unit import tmpfile
23
24from swift.common import daemon, utils
25
26
27class MyDaemon(daemon.Daemon):
28
29 def __init__(self, conf):
30 self.conf = conf
31 self.logger = utils.get_logger(None)
32 MyDaemon.forever_called = False
33 MyDaemon.once_called = False
34
35 def run_forever(self):
36 MyDaemon.forever_called = True
37
38 def run_once(self):
39 MyDaemon.once_called = True
40
41 def run_raise(self):
42 raise OSError
43
44 def run_quit(self):
45 raise KeyboardInterrupt
2046
2147
22class TestDaemon(unittest.TestCase):48class TestDaemon(unittest.TestCase):
2349
24 def test_placeholder(self):50 def test_create(self):
25 pass51 d = daemon.Daemon({})
52 self.assertEquals(d.conf, {})
53 self.assert_(isinstance(d.logger, utils.NamedLogger))
54
55 def test_stubs(self):
56 d = daemon.Daemon({})
57 self.assertRaises(NotImplementedError, d.run_once)
58 self.assertRaises(NotImplementedError, d.run_forever)
59
60
61class TestRunDaemon(unittest.TestCase):
62
63 def setUp(self):
64 utils.HASH_PATH_SUFFIX = 'endcap'
65 utils.drop_privileges = lambda *args: None
66 utils.capture_stdio = lambda *args: None
67
68 def tearDown(self):
69 reload(utils)
70
71 def test_run(self):
72 d = MyDaemon({})
73 self.assertFalse(MyDaemon.forever_called)
74 self.assertFalse(MyDaemon.once_called)
75 # test default
76 d.run()
77 self.assertEquals(d.forever_called, True)
78 # test once
79 d.run(once=True)
80 self.assertEquals(d.once_called, True)
81
82 def test_run_daemon(self):
83 sample_conf = """[my-daemon]
84user = %s
85""" % getuser()
86 with tmpfile(sample_conf) as conf_file:
87 daemon.run_daemon(MyDaemon, conf_file)
88 self.assertEquals(MyDaemon.forever_called, True)
89 daemon.run_daemon(MyDaemon, conf_file, once=True)
90 self.assertEquals(MyDaemon.once_called, True)
91
92 # test raise in daemon code
93 MyDaemon.run_once = MyDaemon.run_raise
94 self.assertRaises(OSError, daemon.run_daemon, MyDaemon,
95 conf_file, once=True)
96
97 # test user quit
98 MyDaemon.run_forever = MyDaemon.run_quit
99 sio = StringIO()
100 logger = logging.getLogger()
101 logger.addHandler(logging.StreamHandler(sio))
102 logger = utils.get_logger(None, 'server')
103 daemon.run_daemon(MyDaemon, conf_file, logger=logger)
104 self.assert_('user quit' in sio.getvalue().lower())
26105
27106
28if __name__ == '__main__':107if __name__ == '__main__':
29108
=== modified file 'test/unit/common/test_utils.py'
--- test/unit/common/test_utils.py 2010-11-01 21:47:48 +0000
+++ test/unit/common/test_utils.py 2010-11-19 22:38:08 +0000
@@ -25,12 +25,55 @@
25from getpass import getuser25from getpass import getuser
26from shutil import rmtree26from shutil import rmtree
27from StringIO import StringIO27from StringIO import StringIO
28from functools import partial
29from tempfile import NamedTemporaryFile
2830
29from eventlet import sleep31from eventlet import sleep
3032
31from swift.common import utils33from swift.common import utils
3234
3335
36class MockOs():
37 def __init__(self, pass_funcs=[], called_funcs=[], raise_funcs=[]):
38 self.closed_fds = []
39 for func in pass_funcs:
40 setattr(self, func, self.pass_func)
41 self.called_funcs = {}
42 for func in called_funcs:
43 c_func = partial(self.called_func, func)
44 setattr(self, func, c_func)
45 for func in raise_funcs:
46 r_func = partial(self.raise_func, func)
47 setattr(self, func, r_func)
48
49 def pass_func(self, *args, **kwargs):
50 pass
51
52 chdir = setsid = setgid = setuid = umask = pass_func
53
54 def called_func(self, name, *args, **kwargs):
55 self.called_funcs[name] = True
56
57 def raise_func(self, name, *args, **kwargs):
58 self.called_funcs[name] = True
59 raise OSError()
60
61 def dup2(self, source, target):
62 self.closed_fds.append(target)
63
64 def __getattr__(self, name):
65 # I only over-ride portions of the os module
66 try:
67 return object.__getattr__(self, name)
68 except AttributeError:
69 return getattr(os, name)
70
71
72class MockSys():
73
74 __stderr__ = sys.__stderr__
75
76
34class TestUtils(unittest.TestCase):77class TestUtils(unittest.TestCase):
35 """ Tests for swift.common.utils """78 """ Tests for swift.common.utils """
3679
@@ -182,10 +225,63 @@
182 self.assertRaises(IOError, lfo.readline, 1024)225 self.assertRaises(IOError, lfo.readline, 1024)
183 lfo.tell()226 lfo.tell()
184227
185 def test_drop_privileges(self):228 def test_parse_options(self):
186 # Note that this doesn't really drop privileges as it just sets them to229 # use mkstemp to get a file that is definately on disk
187 # what they already are; but it exercises the code at least.230 with NamedTemporaryFile() as f:
188 utils.drop_privileges(getuser())231 conf_file = f.name
232 conf, options = utils.parse_options(test_args=[conf_file])
233 self.assertEquals(conf, conf_file)
234 # assert defaults
235 self.assertEquals(options['verbose'], False)
236 self.assert_('once' not in options)
237 # assert verbose as option
238 conf, options = utils.parse_options(test_args=[conf_file, '-v'])
239 self.assertEquals(options['verbose'], True)
240 # check once option
241 conf, options = utils.parse_options(test_args=[conf_file],
242 once=True)
243 self.assertEquals(options['once'], False)
244 test_args = [conf_file, '--once']
245 conf, options = utils.parse_options(test_args=test_args, once=True)
246 self.assertEquals(options['once'], True)
247 # check options as arg parsing
248 test_args = [conf_file, 'once', 'plugin_name', 'verbose']
249 conf, options = utils.parse_options(test_args=test_args, once=True)
250 self.assertEquals(options['verbose'], True)
251 self.assertEquals(options['once'], True)
252 self.assertEquals(options['extra_args'], ['plugin_name'])
253
254 def test_parse_options_errors(self):
255 orig_stdout = sys.stdout
256 orig_stderr = sys.stderr
257 stdo = StringIO()
258 stde = StringIO()
259 utils.sys.stdout = stdo
260 utils.sys.stderr = stde
261 err_msg = """Usage: test usage
262
263Error: missing config file argument
264"""
265 test_args = []
266 self.assertRaises(SystemExit, utils.parse_options, 'test usage', True,
267 test_args)
268 self.assertEquals(stdo.getvalue(), err_msg)
269
270 # verify conf file must exist, context manager will delete temp file
271 with NamedTemporaryFile() as f:
272 conf_file = f.name
273 err_msg += """Usage: test usage
274
275Error: unable to locate %s
276""" % conf_file
277 test_args = [conf_file]
278 self.assertRaises(SystemExit, utils.parse_options, 'test usage', True,
279 test_args)
280 self.assertEquals(stdo.getvalue(), err_msg)
281
282 # reset stdio
283 utils.sys.stdout = orig_stdout
284 utils.sys.stderr = orig_stderr
189285
190 def test_NamedLogger(self):286 def test_NamedLogger(self):
191 sio = StringIO()287 sio = StringIO()
@@ -275,5 +371,80 @@
275 self.assertEquals(result, expected)371 self.assertEquals(result, expected)
276 os.unlink('/tmp/test')372 os.unlink('/tmp/test')
277373
374 def test_drop_privileges(self):
375 user = getuser()
376 # over-ride os with mock
377 required_func_calls = ('setgid', 'setuid', 'setsid', 'chdir', 'umask')
378 utils.os = MockOs(called_funcs=required_func_calls)
379 # exercise the code
380 utils.drop_privileges(user)
381 for func in required_func_calls:
382 self.assert_(utils.os.called_funcs[func])
383
384 # reset; test same args, OSError trying to get session leader
385 utils.os = MockOs(called_funcs=required_func_calls,
386 raise_funcs=('setsid',))
387 for func in required_func_calls:
388 self.assertFalse(utils.os.called_funcs.get(func, False))
389 utils.drop_privileges(user)
390 for func in required_func_calls:
391 self.assert_(utils.os.called_funcs[func])
392
393 def test_capture_stdio(self):
394 # stubs
395 logger = utils.get_logger(None, 'dummy')
396
397 # mock utils system modules
398 utils.sys = MockSys()
399 utils.os = MockOs()
400
401 # basic test
402 utils.capture_stdio(logger)
403 self.assert_(utils.sys.excepthook is not None)
404 self.assertEquals(utils.os.closed_fds, [0, 1, 2])
405 self.assert_(utils.sys.stdout is not None)
406 self.assert_(utils.sys.stderr is not None)
407
408 # reset; test same args, but exc when trying to close stdio
409 utils.os = MockOs(raise_funcs=('dup2',))
410 utils.sys = MockSys()
411
412 # test unable to close stdio
413 utils.capture_stdio(logger)
414 self.assert_(utils.sys.excepthook is not None)
415 self.assertEquals(utils.os.closed_fds, [])
416 self.assert_(utils.sys.stdout is not None)
417 self.assert_(utils.sys.stderr is not None)
418
419 # reset; test some other args
420 logger = utils.get_logger(None, log_to_console=True)
421 utils.os = MockOs()
422 utils.sys = MockSys()
423
424 # test console log
425 utils.capture_stdio(logger, capture_stdout=False,
426 capture_stderr=False)
427 self.assert_(utils.sys.excepthook is not None)
428 # when logging to console, stderr remains open
429 self.assertEquals(utils.os.closed_fds, [0, 1])
430 logger.logger.removeHandler(utils.get_logger.console)
431 # stdio not captured
432 self.assertFalse(hasattr(utils.sys, 'stdout'))
433 self.assertFalse(hasattr(utils.sys, 'stderr'))
434
435 def test_get_logger_console(self):
436 reload(utils) # reset get_logger attrs
437 logger = utils.get_logger(None)
438 self.assertFalse(hasattr(utils.get_logger, 'console'))
439 logger = utils.get_logger(None, log_to_console=True)
440 self.assert_(hasattr(utils.get_logger, 'console'))
441 self.assert_(isinstance(utils.get_logger.console,
442 logging.StreamHandler))
443 # make sure you can't have two console handlers
444 old_handler = utils.get_logger.console
445 logger = utils.get_logger(None, log_to_console=True)
446 self.assertNotEquals(utils.get_logger.console, old_handler)
447 logger.logger.removeHandler(utils.get_logger.console)
448
278if __name__ == '__main__':449if __name__ == '__main__':
279 unittest.main()450 unittest.main()
280451
=== modified file 'test/unit/common/test_wsgi.py'
--- test/unit/common/test_wsgi.py 2010-07-19 16:25:18 +0000
+++ test/unit/common/test_wsgi.py 2010-11-19 22:38:08 +0000
@@ -25,12 +25,12 @@
25from getpass import getuser25from getpass import getuser
26from shutil import rmtree26from shutil import rmtree
27from StringIO import StringIO27from StringIO import StringIO
28from collections import defaultdict
2829
29from eventlet import sleep30from eventlet import sleep
3031
31from swift.common import wsgi32from swift.common import wsgi
3233
33
34class TestWSGI(unittest.TestCase):34class TestWSGI(unittest.TestCase):
35 """ Tests for swift.common.wsgi """35 """ Tests for swift.common.wsgi """
3636
@@ -72,5 +72,107 @@
72 sio = StringIO('Content-Type: text/html; charset=ISO-8859-4')72 sio = StringIO('Content-Type: text/html; charset=ISO-8859-4')
73 self.assertEquals(mimetools.Message(sio).subtype, 'html')73 self.assertEquals(mimetools.Message(sio).subtype, 'html')
7474
75 def test_get_socket(self):
76 # stubs
77 conf = {}
78 ssl_conf = {
79 'cert_file': '',
80 'key_file': '',
81 }
82
83 # mocks
84 class MockSocket():
85 def __init__(self):
86 self.opts = defaultdict(dict)
87
88 def setsockopt(self, level, optname, value):
89 self.opts[level][optname] = value
90
91 def mock_listen(*args, **kwargs):
92 return MockSocket()
93
94 class MockSsl():
95 def __init__(self):
96 self.wrap_socket_called = []
97
98 def wrap_socket(self, sock, **kwargs):
99 self.wrap_socket_called.append(kwargs)
100 return sock
101
102 # patch
103 old_listen = wsgi.listen
104 old_ssl = wsgi.ssl
105 try:
106 wsgi.listen = mock_listen
107 wsgi.ssl = MockSsl()
108 # test
109 sock = wsgi.get_socket(conf)
110 # assert
111 self.assert_(isinstance(sock, MockSocket))
112 expected_socket_opts = {
113 socket.SOL_SOCKET: {
114 socket.SO_REUSEADDR: 1,
115 socket.SO_KEEPALIVE: 1,
116 },
117 socket.IPPROTO_TCP: {
118 socket.TCP_KEEPIDLE: 600,
119 },
120 }
121 self.assertEquals(sock.opts, expected_socket_opts)
122 # test ssl
123 sock = wsgi.get_socket(ssl_conf)
124 expected_kwargs = {
125 'certfile': '',
126 'keyfile': '',
127 }
128 self.assertEquals(wsgi.ssl.wrap_socket_called, [expected_kwargs])
129 finally:
130 wsgi.listen = old_listen
131 wsgi.ssl = old_ssl
132
133 def test_address_in_use(self):
134 # stubs
135 conf = {}
136
137 # mocks
138 def mock_listen(*args, **kwargs):
139 raise socket.error(errno.EADDRINUSE)
140
141 def value_error_listen(*args, **kwargs):
142 raise ValueError('fake')
143
144 def mock_sleep(*args):
145 pass
146
147 class MockTime():
148 """Fast clock advances 10 seconds after every call to time
149 """
150 def __init__(self):
151 self.current_time = old_time.time()
152
153 def time(self, *args, **kwargs):
154 rv = self.current_time
155 # advance for next call
156 self.current_time += 10
157 return rv
158
159 old_listen = wsgi.listen
160 old_sleep = wsgi.sleep
161 old_time = wsgi.time
162 try:
163 wsgi.listen = mock_listen
164 wsgi.sleep = mock_sleep
165 wsgi.time = MockTime()
166 # test error
167 self.assertRaises(Exception, wsgi.get_socket, conf)
168 # different error
169 wsgi.listen = value_error_listen
170 self.assertRaises(ValueError, wsgi.get_socket, conf)
171 finally:
172 wsgi.listen = old_listen
173 wsgi.sleep = old_sleep
174 wsgi.time = old_time
175
176
75if __name__ == '__main__':177if __name__ == '__main__':
76 unittest.main()178 unittest.main()
77179
=== modified file 'test/unit/stats/test_log_processor.py'
--- test/unit/stats/test_log_processor.py 2010-11-17 15:36:21 +0000
+++ test/unit/stats/test_log_processor.py 2010-11-19 22:38:08 +0000
@@ -14,25 +14,12 @@
14# limitations under the License.14# limitations under the License.
1515
16import unittest16import unittest
17import os17from test.unit import tmpfile
18from contextlib import contextmanager
19from tempfile import NamedTemporaryFile
2018
21from swift.common import internal_proxy19from swift.common import internal_proxy
22from swift.stats import log_processor20from swift.stats import log_processor
2321
2422
25@contextmanager
26def tmpfile(content):
27 with NamedTemporaryFile('w', delete=False) as f:
28 file_name = f.name
29 f.write(str(content))
30 try:
31 yield file_name
32 finally:
33 os.unlink(file_name)
34
35
36class FakeUploadApp(object):23class FakeUploadApp(object):
37 def __init__(self, *args, **kwargs):24 def __init__(self, *args, **kwargs):
38 pass25 pass