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
1=== modified file 'bin/swift-account-auditor'
2--- bin/swift-account-auditor 2010-08-31 23:12:59 +0000
3+++ bin/swift-account-auditor 2010-11-19 22:38:08 +0000
4@@ -14,15 +14,10 @@
5 # See the License for the specific language governing permissions and
6 # limitations under the License.
7
8-import sys
9-
10 from swift.account.auditor import AccountAuditor
11-from swift.common import utils
12+from swift.common.utils import parse_options
13+from swift.common.daemon import run_daemon
14
15 if __name__ == '__main__':
16- if len(sys.argv) < 2:
17- print "Usage: swift-account-auditor CONFIG_FILE [once]"
18- sys.exit()
19- once = len(sys.argv) > 2 and sys.argv[2] == 'once'
20- conf = utils.readconf(sys.argv[1], 'account-auditor')
21- auditor = AccountAuditor(conf).run(once)
22+ conf_file, options = parse_options(once=True)
23+ run_daemon(AccountAuditor, conf_file, **options)
24
25=== modified file 'bin/swift-account-reaper'
26--- bin/swift-account-reaper 2010-08-31 23:12:59 +0000
27+++ bin/swift-account-reaper 2010-11-19 22:38:08 +0000
28@@ -14,15 +14,10 @@
29 # See the License for the specific language governing permissions and
30 # limitations under the License.
31
32-import sys
33-
34 from swift.account.reaper import AccountReaper
35-from swift.common import utils
36+from swift.common.utils import parse_options
37+from swift.common.daemon import run_daemon
38
39 if __name__ == '__main__':
40- if len(sys.argv) < 2:
41- print "Usage: account-reaper CONFIG_FILE [once]"
42- sys.exit()
43- once = len(sys.argv) > 2 and sys.argv[2] == 'once'
44- conf = utils.readconf(sys.argv[1], 'account-reaper')
45- reaper = AccountReaper(conf).run(once)
46+ conf_file, options = parse_options(once=True)
47+ run_daemon(AccountReaper, conf_file, **options)
48
49=== modified file 'bin/swift-account-replicator'
50--- bin/swift-account-replicator 2010-08-31 23:12:59 +0000
51+++ bin/swift-account-replicator 2010-11-19 22:38:08 +0000
52@@ -14,15 +14,10 @@
53 # See the License for the specific language governing permissions and
54 # limitations under the License.
55
56-import sys
57-
58-from swift.common import utils
59 from swift.account.replicator import AccountReplicator
60+from swift.common.utils import parse_options
61+from swift.common.daemon import run_daemon
62
63 if __name__ == '__main__':
64- if len(sys.argv) < 2:
65- print "Usage: swift-account-replicator CONFIG_FILE [once]"
66- sys.exit(1)
67- once = len(sys.argv) > 2 and sys.argv[2] == 'once'
68- conf = utils.readconf(sys.argv[1], 'account-replicator')
69- AccountReplicator(conf).run(once)
70+ conf_file, options = parse_options(once=True)
71+ run_daemon(AccountReplicator, conf_file, **options)
72
73=== modified file 'bin/swift-account-server'
74--- bin/swift-account-server 2010-08-24 14:04:44 +0000
75+++ bin/swift-account-server 2010-11-19 22:38:08 +0000
76@@ -14,12 +14,9 @@
77 # See the License for the specific language governing permissions and
78 # limitations under the License.
79
80-import sys
81-
82+from swift.common.utils import parse_options
83 from swift.common.wsgi import run_wsgi
84
85 if __name__ == '__main__':
86- if len(sys.argv) != 2:
87- sys.exit("Usage: %s CONFIG_FILE" % sys.argv[0])
88- run_wsgi(sys.argv[1], 'account-server', default_port=6002)
89-
90+ conf_file, options = parse_options()
91+ run_wsgi(conf_file, 'account-server', default_port=6002, **options)
92
93=== modified file 'bin/swift-account-stats-logger'
94--- bin/swift-account-stats-logger 2010-09-13 16:11:12 +0000
95+++ bin/swift-account-stats-logger 2010-11-19 22:38:08 +0000
96@@ -14,14 +14,13 @@
97 # See the License for the specific language governing permissions and
98 # limitations under the License.
99
100-import sys
101-
102 from swift.stats.account_stats import AccountStat
103-from swift.common import utils
104+from swift.common.utils import parse_options
105+from swift.common.daemon import run_daemon
106
107 if __name__ == '__main__':
108- if len(sys.argv) < 2:
109- print "Usage: swift-account-stats-logger CONFIG_FILE"
110- sys.exit()
111- stats_conf = utils.readconf(sys.argv[1], 'log-processor-stats')
112- stats = AccountStat(stats_conf).run(once=True)
113+ conf_file, options = parse_options()
114+ # currently AccountStat only supports run_once
115+ options['once'] = True
116+ run_daemon(AccountStat, conf_file, section_name='log-processor-stats',
117+ **options)
118
119=== modified file 'bin/swift-auth-server'
120--- bin/swift-auth-server 2010-08-24 14:08:16 +0000
121+++ bin/swift-auth-server 2010-11-19 22:38:08 +0000
122@@ -14,11 +14,9 @@
123 # See the License for the specific language governing permissions and
124 # limitations under the License.
125
126-import sys
127-
128+from swift.common.utils import parse_options
129 from swift.common.wsgi import run_wsgi
130
131 if __name__ == '__main__':
132- if len(sys.argv) != 2:
133- sys.exit("Usage: %s CONFIG_FILE" % sys.argv[0])
134- run_wsgi(sys.argv[1], 'auth-server', default_port=11000)
135+ conf_file, options = parse_options()
136+ run_wsgi(conf_file, 'auth-server', default_port=11000, **options)
137
138=== modified file 'bin/swift-container-auditor'
139--- bin/swift-container-auditor 2010-08-31 23:12:59 +0000
140+++ bin/swift-container-auditor 2010-11-19 22:38:08 +0000
141@@ -14,15 +14,10 @@
142 # See the License for the specific language governing permissions and
143 # limitations under the License.
144
145-import sys
146-
147 from swift.container.auditor import ContainerAuditor
148-from swift.common import utils
149+from swift.common.utils import parse_options
150+from swift.common.daemon import run_daemon
151
152 if __name__ == '__main__':
153- if len(sys.argv) < 2:
154- print "Usage: swift-container-auditor CONFIG_FILE [once]"
155- sys.exit()
156- once = len(sys.argv) > 2 and sys.argv[2] == 'once'
157- conf = utils.readconf(sys.argv[1], 'container-auditor')
158- ContainerAuditor(conf).run(once)
159+ conf_file, options = parse_options(once=True)
160+ run_daemon(ContainerAuditor, conf_file, **options)
161
162=== modified file 'bin/swift-container-replicator'
163--- bin/swift-container-replicator 2010-08-31 23:12:59 +0000
164+++ bin/swift-container-replicator 2010-11-19 22:38:08 +0000
165@@ -14,16 +14,10 @@
166 # See the License for the specific language governing permissions and
167 # limitations under the License.
168
169-import sys
170-
171-from swift.common import db, utils
172 from swift.container.replicator import ContainerReplicator
173+from swift.common.utils import parse_options
174+from swift.common.daemon import run_daemon
175
176 if __name__ == '__main__':
177- if len(sys.argv) < 2:
178- print "Usage: swift-container-replicator CONFIG_FILE [once]"
179- sys.exit(1)
180- once = len(sys.argv) > 2 and sys.argv[2] == 'once'
181- conf = utils.readconf(sys.argv[1], 'container-replicator')
182- ContainerReplicator(conf).run(once)
183-
184+ conf_file, options = parse_options(once=True)
185+ run_daemon(ContainerReplicator, conf_file, **options)
186
187=== modified file 'bin/swift-container-server'
188--- bin/swift-container-server 2010-08-24 14:04:44 +0000
189+++ bin/swift-container-server 2010-11-19 22:38:08 +0000
190@@ -14,11 +14,9 @@
191 # See the License for the specific language governing permissions and
192 # limitations under the License.
193
194-import sys
195-
196+from swift.common.utils import parse_options
197 from swift.common.wsgi import run_wsgi
198
199 if __name__ == '__main__':
200- if len(sys.argv) != 2:
201- sys.exit("Usage: %s CONFIG_FILE" % sys.argv[0])
202- run_wsgi(sys.argv[1], 'container-server', default_port=6001)
203+ conf_file, options = parse_options()
204+ run_wsgi(conf_file, 'container-server', default_port=6001, **options)
205
206=== modified file 'bin/swift-container-updater'
207--- bin/swift-container-updater 2010-08-31 23:12:59 +0000
208+++ bin/swift-container-updater 2010-11-19 22:38:08 +0000
209@@ -14,15 +14,10 @@
210 # See the License for the specific language governing permissions and
211 # limitations under the License.
212
213-import sys
214-
215 from swift.container.updater import ContainerUpdater
216-from swift.common import utils
217+from swift.common.utils import parse_options
218+from swift.common.daemon import run_daemon
219
220 if __name__ == '__main__':
221- if len(sys.argv) < 2:
222- print "Usage: swift-container-updater CONFIG_FILE [once]"
223- sys.exit()
224- once = len(sys.argv) > 2 and sys.argv[2] == 'once'
225- conf = utils.readconf(sys.argv[1], 'container-updater')
226- ContainerUpdater(conf).run(once)
227+ conf_file, options = parse_options(once=True)
228+ run_daemon(ContainerUpdater, conf_file, **options)
229
230=== modified file 'bin/swift-log-stats-collector'
231--- bin/swift-log-stats-collector 2010-09-22 14:32:40 +0000
232+++ bin/swift-log-stats-collector 2010-11-19 22:38:08 +0000
233@@ -14,14 +14,13 @@
234 # See the License for the specific language governing permissions and
235 # limitations under the License.
236
237-import sys
238-
239 from swift.stats.log_processor import LogProcessorDaemon
240-from swift.common import utils
241+from swift.common.utils import parse_options
242+from swift.common.daemon import run_daemon
243
244 if __name__ == '__main__':
245- if len(sys.argv) < 2:
246- print "Usage: swift-log-stats-collector CONFIG_FILE"
247- sys.exit()
248- conf = utils.readconf(sys.argv[1], log_name='log-stats-collector')
249- stats = LogProcessorDaemon(conf).run(once=True)
250+ conf_file, options = parse_options()
251+ # currently the LogProcessorDaemon only supports run_once
252+ options['once'] = True
253+ run_daemon(LogProcessorDaemon, conf_file, section_name=None,
254+ log_name='log-stats-collector', **options)
255
256=== modified file 'bin/swift-log-uploader'
257--- bin/swift-log-uploader 2010-09-10 20:08:06 +0000
258+++ bin/swift-log-uploader 2010-11-19 22:38:08 +0000
259@@ -17,15 +17,25 @@
260 import sys
261
262 from swift.stats.log_uploader import LogUploader
263+from swift.common.utils import parse_options
264 from swift.common import utils
265
266 if __name__ == '__main__':
267- if len(sys.argv) < 3:
268- print "Usage: swift-log-uploader CONFIG_FILE plugin"
269- sys.exit()
270- uploader_conf = utils.readconf(sys.argv[1], 'log-processor')
271- plugin = sys.argv[2]
272+ conf_file, options = parse_options(usage="Usage: %prog CONFIG_FILE PLUGIN")
273+ try:
274+ plugin = options['extra_args'][0]
275+ except IndexError:
276+ print "Error: missing plugin name"
277+ sys.exit(1)
278+
279+ uploader_conf = utils.readconf(conf_file, 'log-processor')
280 section_name = 'log-processor-%s' % plugin
281- plugin_conf = utils.readconf(sys.argv[1], section_name)
282+ plugin_conf = utils.readconf(conf_file, section_name)
283 uploader_conf.update(plugin_conf)
284- uploader = LogUploader(uploader_conf, plugin).run(once=True)
285+
286+ # pre-configure logger
287+ logger = utils.get_logger(uploader_conf, plugin,
288+ log_to_console=options.get('verbose', False))
289+ # currently LogUploader only supports run_once
290+ options['once'] = True
291+ uploader = LogUploader(uploader_conf, plugin).run(**options)
292
293=== modified file 'bin/swift-object-auditor'
294--- bin/swift-object-auditor 2010-08-31 23:12:59 +0000
295+++ bin/swift-object-auditor 2010-11-19 22:38:08 +0000
296@@ -14,16 +14,10 @@
297 # See the License for the specific language governing permissions and
298 # limitations under the License.
299
300-import sys
301-
302 from swift.obj.auditor import ObjectAuditor
303-from swift.common import utils
304+from swift.common.utils import parse_options
305+from swift.common.daemon import run_daemon
306
307 if __name__ == '__main__':
308- if len(sys.argv) < 2:
309- print "Usage: swift-object-auditor CONFIG_FILE [once]"
310- sys.exit()
311-
312- once = len(sys.argv) > 2 and sys.argv[2] == 'once'
313- conf = utils.readconf(sys.argv[1], 'object-auditor')
314- ObjectAuditor(conf).run(once)
315+ conf_file, options = parse_options(once=True)
316+ run_daemon(ObjectAuditor, conf_file, **options)
317
318=== modified file 'bin/swift-object-replicator'
319--- bin/swift-object-replicator 2010-08-31 23:12:59 +0000
320+++ bin/swift-object-replicator 2010-11-19 22:38:08 +0000
321@@ -14,16 +14,10 @@
322 # See the License for the specific language governing permissions and
323 # limitations under the License.
324
325-import sys
326-
327 from swift.obj.replicator import ObjectReplicator
328-from swift.common import utils
329+from swift.common.utils import parse_options
330+from swift.common.daemon import run_daemon
331
332 if __name__ == '__main__':
333- if len(sys.argv) < 2:
334- print "Usage: swift-object-replicator CONFIG_FILE [once]"
335- sys.exit()
336- conf = utils.readconf(sys.argv[1], "object-replicator")
337- once = (len(sys.argv) > 2 and sys.argv[2] == 'once') or \
338- conf.get('daemonize', 'true') not in utils.TRUE_VALUES
339- ObjectReplicator(conf).run(once)
340+ conf_file, options = parse_options(once=True)
341+ run_daemon(ObjectReplicator, conf_file, **options)
342
343=== modified file 'bin/swift-object-server'
344--- bin/swift-object-server 2010-08-24 14:04:44 +0000
345+++ bin/swift-object-server 2010-11-19 22:38:08 +0000
346@@ -14,11 +14,9 @@
347 # See the License for the specific language governing permissions and
348 # limitations under the License.
349
350-import sys
351-
352+from swift.common.utils import parse_options
353 from swift.common.wsgi import run_wsgi
354
355 if __name__ == '__main__':
356- if len(sys.argv) != 2:
357- sys.exit("Usage: %s CONFIG_FILE" % sys.argv[0])
358- run_wsgi(sys.argv[1], 'object-server', default_port=6000)
359+ conf_file, options = parse_options()
360+ run_wsgi(conf_file, 'object-server', default_port=6000, **options)
361
362=== modified file 'bin/swift-object-updater'
363--- bin/swift-object-updater 2010-08-31 23:12:59 +0000
364+++ bin/swift-object-updater 2010-11-19 22:38:08 +0000
365@@ -14,15 +14,10 @@
366 # See the License for the specific language governing permissions and
367 # limitations under the License.
368
369-import sys
370-
371 from swift.obj.updater import ObjectUpdater
372-from swift.common import utils
373+from swift.common.utils import parse_options
374+from swift.common.daemon import run_daemon
375
376 if __name__ == '__main__':
377- if len(sys.argv) < 2:
378- print "Usage: swift-object-updater CONFIG_FILE [once]"
379- sys.exit(1)
380- once = len(sys.argv) > 2 and sys.argv[2] == 'once'
381- conf = utils.readconf(sys.argv[1], 'object-updater')
382- ObjectUpdater(conf).run(once)
383+ conf_file, options = parse_options(once=True)
384+ run_daemon(ObjectUpdater, conf_file, **options)
385
386=== modified file 'bin/swift-proxy-server'
387--- bin/swift-proxy-server 2010-08-24 14:04:44 +0000
388+++ bin/swift-proxy-server 2010-11-19 22:38:08 +0000
389@@ -14,11 +14,9 @@
390 # See the License for the specific language governing permissions and
391 # limitations under the License.
392
393-import sys
394-
395+from swift.common.utils import parse_options
396 from swift.common.wsgi import run_wsgi
397
398 if __name__ == '__main__':
399- if len(sys.argv) != 2:
400- sys.exit("Usage: %s CONFIG_FILE" % sys.argv[0])
401- run_wsgi(sys.argv[1], 'proxy-server', default_port=8080)
402+ conf_file, options = parse_options()
403+ run_wsgi(conf_file, 'proxy-server', default_port=8080, **options)
404
405=== modified file 'swift/common/daemon.py'
406--- swift/common/daemon.py 2010-10-15 19:28:38 +0000
407+++ swift/common/daemon.py 2010-11-19 22:38:08 +0000
408@@ -16,6 +16,7 @@
409 import os
410 import sys
411 import signal
412+from re import sub
413 from swift.common import utils
414
415
416@@ -34,23 +35,11 @@
417 """Override this to run forever"""
418 raise NotImplementedError('run_forever not implemented')
419
420- def run(self, once=False, capture_stdout=True, capture_stderr=True):
421+ def run(self, once=False, **kwargs):
422 """Run the daemon"""
423- # log uncaught exceptions
424- sys.excepthook = lambda *exc_info: \
425- self.logger.critical('UNCAUGHT EXCEPTION', exc_info=exc_info)
426- if capture_stdout:
427- sys.stdout = utils.LoggerFileObject(self.logger)
428- if capture_stderr:
429- sys.stderr = utils.LoggerFileObject(self.logger)
430-
431+ utils.validate_configuration()
432+ utils.capture_stdio(self.logger, **kwargs)
433 utils.drop_privileges(self.conf.get('user', 'swift'))
434- utils.validate_configuration()
435-
436- try:
437- os.setsid()
438- except OSError:
439- pass
440
441 def kill_children(*args):
442 signal.signal(signal.SIGTERM, signal.SIG_IGN)
443@@ -63,3 +52,40 @@
444 self.run_once()
445 else:
446 self.run_forever()
447+
448+
449+def run_daemon(klass, conf_file, section_name='',
450+ once=False, **kwargs):
451+ """
452+ Loads settings from conf, then instantiates daemon "klass" and runs the
453+ daemon with the specified once kwarg. The section_name will be derived
454+ from the daemon "klass" if not provided (e.g. ObjectReplicator =>
455+ object-replicator).
456+
457+ :param klass: Class to instantiate, subclass of common.daemon.Daemon
458+ :param conf_file: Path to configuration file
459+ :param section_name: Section name from conf file to load config from
460+ :param once: Passed to daemon run method
461+ """
462+ # very often the config section_name is based on the class name
463+ # the None singleton will be passed through to readconf as is
464+ if section_name is '':
465+ section_name = sub(r'([a-z])([A-Z])', r'\1-\2',
466+ klass.__name__).lower()
467+ conf = utils.readconf(conf_file, section_name,
468+ log_name=kwargs.get('log_name'))
469+
470+ # once on command line (i.e. daemonize=false) will over-ride config
471+ once = once or conf.get('daemonize', 'true') not in utils.TRUE_VALUES
472+
473+ # pre-configure logger
474+ if 'logger' in kwargs:
475+ logger = kwargs.pop('logger')
476+ else:
477+ logger = utils.get_logger(conf, conf.get('log_name', section_name),
478+ log_to_console=kwargs.pop('verbose', False))
479+ try:
480+ klass(conf).run(once=once, **kwargs)
481+ except KeyboardInterrupt:
482+ logger.info('User quit')
483+ logger.info('Exited')
484
485=== modified file 'swift/common/db_replicator.py'
486--- swift/common/db_replicator.py 2010-09-01 15:56:37 +0000
487+++ swift/common/db_replicator.py 2010-11-19 22:38:08 +0000
488@@ -93,10 +93,6 @@
489 def __init__(self, conf):
490 self.conf = conf
491 self.logger = get_logger(conf)
492- # log uncaught exceptions
493- sys.excepthook = lambda * exc_info: \
494- self.logger.critical('UNCAUGHT EXCEPTION', exc_info=exc_info)
495- sys.stdout = sys.stderr = LoggerFileObject(self.logger)
496 self.root = conf.get('devices', '/srv/node')
497 self.mount_check = conf.get('mount_check', 'true').lower() in \
498 ('true', 't', '1', 'on', 'yes', 'y')
499
500=== modified file 'swift/common/utils.py'
501--- swift/common/utils.py 2010-11-16 19:52:18 +0000
502+++ swift/common/utils.py 2010-11-19 22:38:08 +0000
503@@ -31,6 +31,7 @@
504 import ctypes.util
505 import struct
506 from ConfigParser import ConfigParser, NoSectionError, NoOptionError
507+from optparse import OptionParser
508 from tempfile import mkstemp
509 import cPickle as pickle
510
511@@ -283,17 +284,6 @@
512 return self
513
514
515-def drop_privileges(user):
516- """
517- Sets the userid of the current process
518-
519- :param user: User id to change privileges to
520- """
521- user = pwd.getpwnam(user)
522- os.setgid(user[3])
523- os.setuid(user[2])
524-
525-
526 class NamedLogger(object):
527 """Cheesy version of the LoggerAdapter available in Python 3"""
528
529@@ -343,7 +333,7 @@
530 call('%s %s: %s' % (self.server, msg, emsg), *args)
531
532
533-def get_logger(conf, name=None):
534+def get_logger(conf, name=None, log_to_console=False):
535 """
536 Get the current system logger using config settings.
537
538@@ -355,11 +345,18 @@
539
540 :param conf: Configuration dict to read settings from
541 :param name: Name of the logger
542+ :param log_to_console: Add handler which writes to console on stderr
543 """
544 root_logger = logging.getLogger()
545 if hasattr(get_logger, 'handler') and get_logger.handler:
546 root_logger.removeHandler(get_logger.handler)
547 get_logger.handler = None
548+ if log_to_console:
549+ # check if a previous call to get_logger already added a console logger
550+ if hasattr(get_logger, 'console') and get_logger.console:
551+ root_logger.removeHandler(get_logger.console)
552+ get_logger.console = logging.StreamHandler(sys.__stderr__)
553+ root_logger.addHandler(get_logger.console)
554 if conf is None:
555 root_logger.setLevel(logging.INFO)
556 return NamedLogger(root_logger, name)
557@@ -375,6 +372,99 @@
558 return NamedLogger(root_logger, name)
559
560
561+def drop_privileges(user):
562+ """
563+ Sets the userid/groupid of the current process, get session leader, etc.
564+
565+ :param user: User name to change privileges to
566+ """
567+ user = pwd.getpwnam(user)
568+ os.setgid(user[3])
569+ os.setuid(user[2])
570+ try:
571+ os.setsid()
572+ except OSError:
573+ pass
574+ os.chdir('/') # in case you need to rmdir on where you started the daemon
575+ os.umask(0) # ensure files are created with the correct privileges
576+
577+
578+def capture_stdio(logger, **kwargs):
579+ """
580+ Log unhandled exceptions, close stdio, capture stdout and stderr.
581+
582+ param logger: Logger object to use
583+ """
584+ # log uncaught exceptions
585+ sys.excepthook = lambda * exc_info: \
586+ logger.critical('UNCAUGHT EXCEPTION', exc_info=exc_info)
587+
588+ # collect stdio file desc not in use for logging
589+ stdio_fds = [0, 1, 2]
590+ if hasattr(get_logger, 'console'):
591+ stdio_fds.remove(get_logger.console.stream.fileno())
592+
593+ with open(os.devnull, 'r+b') as nullfile:
594+ # close stdio (excludes fds open for logging)
595+ for desc in stdio_fds:
596+ try:
597+ os.dup2(nullfile.fileno(), desc)
598+ except OSError:
599+ pass
600+
601+ # redirect stdio
602+ if kwargs.pop('capture_stdout', True):
603+ sys.stdout = LoggerFileObject(logger)
604+ if kwargs.pop('capture_stderr', True):
605+ sys.stderr = LoggerFileObject(logger)
606+
607+
608+def parse_options(usage="%prog CONFIG [options]", once=False, test_args=None):
609+ """
610+ Parse standard swift server/daemon options with optparse.OptionParser.
611+
612+ :param usage: String describing usage
613+ :param once: Boolean indicating the "once" option is available
614+ :param test_args: Override sys.argv; used in testing
615+
616+ :returns : Tuple of (config, options); config is an absolute path to the
617+ config file, options is the parser options as a dictionary.
618+
619+ :raises SystemExit: First arg (CONFIG) is required, file must exist
620+ """
621+ parser = OptionParser(usage)
622+ parser.add_option("-v", "--verbose", default=False, action="store_true",
623+ help="log to console")
624+ if once:
625+ parser.add_option("-o", "--once", default=False, action="store_true",
626+ help="only run one pass of daemon")
627+
628+ # if test_args is None, optparse will use sys.argv[:1]
629+ options, args = parser.parse_args(args=test_args)
630+
631+ if not args:
632+ parser.print_usage()
633+ print "Error: missing config file argument"
634+ sys.exit(1)
635+ config = os.path.abspath(args.pop(0))
636+ if not os.path.exists(config):
637+ parser.print_usage()
638+ print "Error: unable to locate %s" % config
639+ sys.exit(1)
640+
641+ extra_args = []
642+ # if any named options appear in remaining args, set the option to True
643+ for arg in args:
644+ if arg in options.__dict__:
645+ setattr(options, arg, True)
646+ else:
647+ extra_args.append(arg)
648+
649+ options = vars(options)
650+ options['extra_args'] = extra_args
651+ return config, options
652+
653+
654 def whataremyips():
655 """
656 Get the machine's ip addresses using ifconfig
657
658=== modified file 'swift/common/wsgi.py'
659--- swift/common/wsgi.py 2010-10-15 19:28:38 +0000
660+++ swift/common/wsgi.py 2010-11-19 22:38:08 +0000
661@@ -34,7 +34,7 @@
662 from eventlet.green import socket, ssl
663
664 from swift.common.utils import get_logger, drop_privileges, \
665- validate_configuration, LoggerFileObject, NullLogger
666+ validate_configuration, capture_stdio, NullLogger
667
668
669 def monkey_patch_mimetools():
670@@ -56,41 +56,17 @@
671
672 mimetools.Message.parsetype = parsetype
673
674-
675-# We might be able to pull pieces of this out to test, but right now it seems
676-# like more work than it's worth.
677-def run_wsgi(conf_file, app_section, *args, **kwargs): # pragma: no cover
678- """
679- Loads common settings from conf, then instantiates app and runs
680- the server using the specified number of workers.
681-
682- :param conf_file: Path to paste.deploy style configuration file
683- :param app_section: App name from conf file to load config from
684- """
685-
686- try:
687- conf = appconfig('config:%s' % conf_file, name=app_section)
688- log_name = conf.get('log_name', app_section)
689- app = loadapp('config:%s' % conf_file,
690- global_conf={'log_name': log_name})
691- except Exception, e:
692- print "Error trying to load config %s: %s" % (conf_file, e)
693- return
694- if 'logger' in kwargs:
695- logger = kwargs['logger']
696- else:
697- logger = get_logger(conf, log_name)
698- # log uncaught exceptions
699- sys.excepthook = lambda * exc_info: \
700- logger.critical('UNCAUGHT EXCEPTION', exc_info=exc_info)
701- sys.stdout = sys.stderr = LoggerFileObject(logger)
702-
703- try:
704- os.setsid()
705- except OSError:
706- no_cover = True # pass
707+def get_socket(conf, default_port=8080):
708+ """Bind socket to bind ip:port in conf
709+
710+ :param conf: Configuration dict to read settings from
711+ :param default_port: port to use if not specified in conf
712+
713+ :returns : a socket object as returned from socket.listen or ssl.wrap_socket
714+ if conf specifies cert_file
715+ """
716 bind_addr = (conf.get('bind_ip', '0.0.0.0'),
717- int(conf.get('bind_port', kwargs.get('default_port', 8080))))
718+ int(conf.get('bind_port', default_port)))
719 sock = None
720 retry_until = time.time() + 30
721 while not sock and time.time() < retry_until:
722@@ -110,9 +86,43 @@
723 # in my experience, sockets can hang around forever without keepalive
724 sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
725 sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 600)
726- worker_count = int(conf.get('workers', '1'))
727+ return sock
728+
729+
730+# TODO: pull pieces of this out to test
731+def run_wsgi(conf_file, app_section, *args, **kwargs):
732+ """
733+ Loads common settings from conf, then instantiates app and runs
734+ the server using the specified number of workers.
735+
736+ :param conf_file: Path to paste.deploy style configuration file
737+ :param app_section: App name from conf file to load config from
738+ """
739+
740+ try:
741+ conf = appconfig('config:%s' % conf_file, name=app_section)
742+ except Exception, e:
743+ print "Error trying to load config %s: %s" % (conf_file, e)
744+ return
745+ validate_configuration()
746+
747+ # pre-configure logger
748+ log_name = conf.get('log_name', app_section)
749+ if 'logger' in kwargs:
750+ logger = kwargs.pop('logger')
751+ else:
752+ logger = get_logger(conf, log_name,
753+ log_to_console=kwargs.pop('verbose', False))
754+
755+ # redirect errors to logger and close stdio
756+ capture_stdio(logger)
757+ # bind to address and port
758+ sock = get_socket(conf, default_port=kwargs.get('default_port', 8080))
759+ # remaining tasks should not require elevated privileges
760 drop_privileges(conf.get('user', 'swift'))
761- validate_configuration()
762+
763+ # finally after binding to ports and privilege drop, run app __init__ code
764+ app = loadapp('config:%s' % conf_file, global_conf={'log_name': log_name})
765
766 def run_server():
767 wsgi.HttpProtocol.default_request_version = "HTTP/1.0"
768@@ -127,6 +137,7 @@
769 raise
770 pool.waitall()
771
772+ worker_count = int(conf.get('workers', '1'))
773 # Useful for profiling [no forks].
774 if worker_count == 0:
775 run_server()
776@@ -169,6 +180,9 @@
777 except OSError, err:
778 if err.errno not in (errno.EINTR, errno.ECHILD):
779 raise
780+ except KeyboardInterrupt:
781+ logger.info('User quit')
782+ break
783 greenio.shutdown_safe(sock)
784 sock.close()
785 logger.info('Exited')
786
787=== modified file 'test/unit/__init__.py'
788--- test/unit/__init__.py 2010-07-29 20:06:01 +0000
789+++ test/unit/__init__.py 2010-11-19 22:38:08 +0000
790@@ -1,5 +1,8 @@
791 """ Swift tests """
792
793+import os
794+from contextlib import contextmanager
795+from tempfile import NamedTemporaryFile
796 from eventlet.green import socket
797
798
799@@ -23,6 +26,18 @@
800 rv.connect(hostport)
801 return rv
802
803+
804+@contextmanager
805+def tmpfile(content):
806+ with NamedTemporaryFile('w', delete=False) as f:
807+ file_name = f.name
808+ f.write(str(content))
809+ try:
810+ yield file_name
811+ finally:
812+ os.unlink(file_name)
813+
814+
815 class MockTrue(object):
816 """
817 Instances of MockTrue evaluate like True
818
819=== modified file 'test/unit/common/test_daemon.py'
820--- test/unit/common/test_daemon.py 2010-10-07 15:23:17 +0000
821+++ test/unit/common/test_daemon.py 2010-11-19 22:38:08 +0000
822@@ -13,16 +13,95 @@
823 # See the License for the specific language governing permissions and
824 # limitations under the License.
825
826-# TODO: Tests
827+# TODO: Test kill_children signal handlers
828
829 import unittest
830-from swift.common import daemon
831+from getpass import getuser
832+import logging
833+from StringIO import StringIO
834+from test.unit import tmpfile
835+
836+from swift.common import daemon, utils
837+
838+
839+class MyDaemon(daemon.Daemon):
840+
841+ def __init__(self, conf):
842+ self.conf = conf
843+ self.logger = utils.get_logger(None)
844+ MyDaemon.forever_called = False
845+ MyDaemon.once_called = False
846+
847+ def run_forever(self):
848+ MyDaemon.forever_called = True
849+
850+ def run_once(self):
851+ MyDaemon.once_called = True
852+
853+ def run_raise(self):
854+ raise OSError
855+
856+ def run_quit(self):
857+ raise KeyboardInterrupt
858
859
860 class TestDaemon(unittest.TestCase):
861
862- def test_placeholder(self):
863- pass
864+ def test_create(self):
865+ d = daemon.Daemon({})
866+ self.assertEquals(d.conf, {})
867+ self.assert_(isinstance(d.logger, utils.NamedLogger))
868+
869+ def test_stubs(self):
870+ d = daemon.Daemon({})
871+ self.assertRaises(NotImplementedError, d.run_once)
872+ self.assertRaises(NotImplementedError, d.run_forever)
873+
874+
875+class TestRunDaemon(unittest.TestCase):
876+
877+ def setUp(self):
878+ utils.HASH_PATH_SUFFIX = 'endcap'
879+ utils.drop_privileges = lambda *args: None
880+ utils.capture_stdio = lambda *args: None
881+
882+ def tearDown(self):
883+ reload(utils)
884+
885+ def test_run(self):
886+ d = MyDaemon({})
887+ self.assertFalse(MyDaemon.forever_called)
888+ self.assertFalse(MyDaemon.once_called)
889+ # test default
890+ d.run()
891+ self.assertEquals(d.forever_called, True)
892+ # test once
893+ d.run(once=True)
894+ self.assertEquals(d.once_called, True)
895+
896+ def test_run_daemon(self):
897+ sample_conf = """[my-daemon]
898+user = %s
899+""" % getuser()
900+ with tmpfile(sample_conf) as conf_file:
901+ daemon.run_daemon(MyDaemon, conf_file)
902+ self.assertEquals(MyDaemon.forever_called, True)
903+ daemon.run_daemon(MyDaemon, conf_file, once=True)
904+ self.assertEquals(MyDaemon.once_called, True)
905+
906+ # test raise in daemon code
907+ MyDaemon.run_once = MyDaemon.run_raise
908+ self.assertRaises(OSError, daemon.run_daemon, MyDaemon,
909+ conf_file, once=True)
910+
911+ # test user quit
912+ MyDaemon.run_forever = MyDaemon.run_quit
913+ sio = StringIO()
914+ logger = logging.getLogger()
915+ logger.addHandler(logging.StreamHandler(sio))
916+ logger = utils.get_logger(None, 'server')
917+ daemon.run_daemon(MyDaemon, conf_file, logger=logger)
918+ self.assert_('user quit' in sio.getvalue().lower())
919
920
921 if __name__ == '__main__':
922
923=== modified file 'test/unit/common/test_utils.py'
924--- test/unit/common/test_utils.py 2010-11-01 21:47:48 +0000
925+++ test/unit/common/test_utils.py 2010-11-19 22:38:08 +0000
926@@ -25,12 +25,55 @@
927 from getpass import getuser
928 from shutil import rmtree
929 from StringIO import StringIO
930+from functools import partial
931+from tempfile import NamedTemporaryFile
932
933 from eventlet import sleep
934
935 from swift.common import utils
936
937
938+class MockOs():
939+ def __init__(self, pass_funcs=[], called_funcs=[], raise_funcs=[]):
940+ self.closed_fds = []
941+ for func in pass_funcs:
942+ setattr(self, func, self.pass_func)
943+ self.called_funcs = {}
944+ for func in called_funcs:
945+ c_func = partial(self.called_func, func)
946+ setattr(self, func, c_func)
947+ for func in raise_funcs:
948+ r_func = partial(self.raise_func, func)
949+ setattr(self, func, r_func)
950+
951+ def pass_func(self, *args, **kwargs):
952+ pass
953+
954+ chdir = setsid = setgid = setuid = umask = pass_func
955+
956+ def called_func(self, name, *args, **kwargs):
957+ self.called_funcs[name] = True
958+
959+ def raise_func(self, name, *args, **kwargs):
960+ self.called_funcs[name] = True
961+ raise OSError()
962+
963+ def dup2(self, source, target):
964+ self.closed_fds.append(target)
965+
966+ def __getattr__(self, name):
967+ # I only over-ride portions of the os module
968+ try:
969+ return object.__getattr__(self, name)
970+ except AttributeError:
971+ return getattr(os, name)
972+
973+
974+class MockSys():
975+
976+ __stderr__ = sys.__stderr__
977+
978+
979 class TestUtils(unittest.TestCase):
980 """ Tests for swift.common.utils """
981
982@@ -182,10 +225,63 @@
983 self.assertRaises(IOError, lfo.readline, 1024)
984 lfo.tell()
985
986- def test_drop_privileges(self):
987- # Note that this doesn't really drop privileges as it just sets them to
988- # what they already are; but it exercises the code at least.
989- utils.drop_privileges(getuser())
990+ def test_parse_options(self):
991+ # use mkstemp to get a file that is definately on disk
992+ with NamedTemporaryFile() as f:
993+ conf_file = f.name
994+ conf, options = utils.parse_options(test_args=[conf_file])
995+ self.assertEquals(conf, conf_file)
996+ # assert defaults
997+ self.assertEquals(options['verbose'], False)
998+ self.assert_('once' not in options)
999+ # assert verbose as option
1000+ conf, options = utils.parse_options(test_args=[conf_file, '-v'])
1001+ self.assertEquals(options['verbose'], True)
1002+ # check once option
1003+ conf, options = utils.parse_options(test_args=[conf_file],
1004+ once=True)
1005+ self.assertEquals(options['once'], False)
1006+ test_args = [conf_file, '--once']
1007+ conf, options = utils.parse_options(test_args=test_args, once=True)
1008+ self.assertEquals(options['once'], True)
1009+ # check options as arg parsing
1010+ test_args = [conf_file, 'once', 'plugin_name', 'verbose']
1011+ conf, options = utils.parse_options(test_args=test_args, once=True)
1012+ self.assertEquals(options['verbose'], True)
1013+ self.assertEquals(options['once'], True)
1014+ self.assertEquals(options['extra_args'], ['plugin_name'])
1015+
1016+ def test_parse_options_errors(self):
1017+ orig_stdout = sys.stdout
1018+ orig_stderr = sys.stderr
1019+ stdo = StringIO()
1020+ stde = StringIO()
1021+ utils.sys.stdout = stdo
1022+ utils.sys.stderr = stde
1023+ err_msg = """Usage: test usage
1024+
1025+Error: missing config file argument
1026+"""
1027+ test_args = []
1028+ self.assertRaises(SystemExit, utils.parse_options, 'test usage', True,
1029+ test_args)
1030+ self.assertEquals(stdo.getvalue(), err_msg)
1031+
1032+ # verify conf file must exist, context manager will delete temp file
1033+ with NamedTemporaryFile() as f:
1034+ conf_file = f.name
1035+ err_msg += """Usage: test usage
1036+
1037+Error: unable to locate %s
1038+""" % conf_file
1039+ test_args = [conf_file]
1040+ self.assertRaises(SystemExit, utils.parse_options, 'test usage', True,
1041+ test_args)
1042+ self.assertEquals(stdo.getvalue(), err_msg)
1043+
1044+ # reset stdio
1045+ utils.sys.stdout = orig_stdout
1046+ utils.sys.stderr = orig_stderr
1047
1048 def test_NamedLogger(self):
1049 sio = StringIO()
1050@@ -275,5 +371,80 @@
1051 self.assertEquals(result, expected)
1052 os.unlink('/tmp/test')
1053
1054+ def test_drop_privileges(self):
1055+ user = getuser()
1056+ # over-ride os with mock
1057+ required_func_calls = ('setgid', 'setuid', 'setsid', 'chdir', 'umask')
1058+ utils.os = MockOs(called_funcs=required_func_calls)
1059+ # exercise the code
1060+ utils.drop_privileges(user)
1061+ for func in required_func_calls:
1062+ self.assert_(utils.os.called_funcs[func])
1063+
1064+ # reset; test same args, OSError trying to get session leader
1065+ utils.os = MockOs(called_funcs=required_func_calls,
1066+ raise_funcs=('setsid',))
1067+ for func in required_func_calls:
1068+ self.assertFalse(utils.os.called_funcs.get(func, False))
1069+ utils.drop_privileges(user)
1070+ for func in required_func_calls:
1071+ self.assert_(utils.os.called_funcs[func])
1072+
1073+ def test_capture_stdio(self):
1074+ # stubs
1075+ logger = utils.get_logger(None, 'dummy')
1076+
1077+ # mock utils system modules
1078+ utils.sys = MockSys()
1079+ utils.os = MockOs()
1080+
1081+ # basic test
1082+ utils.capture_stdio(logger)
1083+ self.assert_(utils.sys.excepthook is not None)
1084+ self.assertEquals(utils.os.closed_fds, [0, 1, 2])
1085+ self.assert_(utils.sys.stdout is not None)
1086+ self.assert_(utils.sys.stderr is not None)
1087+
1088+ # reset; test same args, but exc when trying to close stdio
1089+ utils.os = MockOs(raise_funcs=('dup2',))
1090+ utils.sys = MockSys()
1091+
1092+ # test unable to close stdio
1093+ utils.capture_stdio(logger)
1094+ self.assert_(utils.sys.excepthook is not None)
1095+ self.assertEquals(utils.os.closed_fds, [])
1096+ self.assert_(utils.sys.stdout is not None)
1097+ self.assert_(utils.sys.stderr is not None)
1098+
1099+ # reset; test some other args
1100+ logger = utils.get_logger(None, log_to_console=True)
1101+ utils.os = MockOs()
1102+ utils.sys = MockSys()
1103+
1104+ # test console log
1105+ utils.capture_stdio(logger, capture_stdout=False,
1106+ capture_stderr=False)
1107+ self.assert_(utils.sys.excepthook is not None)
1108+ # when logging to console, stderr remains open
1109+ self.assertEquals(utils.os.closed_fds, [0, 1])
1110+ logger.logger.removeHandler(utils.get_logger.console)
1111+ # stdio not captured
1112+ self.assertFalse(hasattr(utils.sys, 'stdout'))
1113+ self.assertFalse(hasattr(utils.sys, 'stderr'))
1114+
1115+ def test_get_logger_console(self):
1116+ reload(utils) # reset get_logger attrs
1117+ logger = utils.get_logger(None)
1118+ self.assertFalse(hasattr(utils.get_logger, 'console'))
1119+ logger = utils.get_logger(None, log_to_console=True)
1120+ self.assert_(hasattr(utils.get_logger, 'console'))
1121+ self.assert_(isinstance(utils.get_logger.console,
1122+ logging.StreamHandler))
1123+ # make sure you can't have two console handlers
1124+ old_handler = utils.get_logger.console
1125+ logger = utils.get_logger(None, log_to_console=True)
1126+ self.assertNotEquals(utils.get_logger.console, old_handler)
1127+ logger.logger.removeHandler(utils.get_logger.console)
1128+
1129 if __name__ == '__main__':
1130 unittest.main()
1131
1132=== modified file 'test/unit/common/test_wsgi.py'
1133--- test/unit/common/test_wsgi.py 2010-07-19 16:25:18 +0000
1134+++ test/unit/common/test_wsgi.py 2010-11-19 22:38:08 +0000
1135@@ -25,12 +25,12 @@
1136 from getpass import getuser
1137 from shutil import rmtree
1138 from StringIO import StringIO
1139+from collections import defaultdict
1140
1141 from eventlet import sleep
1142
1143 from swift.common import wsgi
1144
1145-
1146 class TestWSGI(unittest.TestCase):
1147 """ Tests for swift.common.wsgi """
1148
1149@@ -72,5 +72,107 @@
1150 sio = StringIO('Content-Type: text/html; charset=ISO-8859-4')
1151 self.assertEquals(mimetools.Message(sio).subtype, 'html')
1152
1153+ def test_get_socket(self):
1154+ # stubs
1155+ conf = {}
1156+ ssl_conf = {
1157+ 'cert_file': '',
1158+ 'key_file': '',
1159+ }
1160+
1161+ # mocks
1162+ class MockSocket():
1163+ def __init__(self):
1164+ self.opts = defaultdict(dict)
1165+
1166+ def setsockopt(self, level, optname, value):
1167+ self.opts[level][optname] = value
1168+
1169+ def mock_listen(*args, **kwargs):
1170+ return MockSocket()
1171+
1172+ class MockSsl():
1173+ def __init__(self):
1174+ self.wrap_socket_called = []
1175+
1176+ def wrap_socket(self, sock, **kwargs):
1177+ self.wrap_socket_called.append(kwargs)
1178+ return sock
1179+
1180+ # patch
1181+ old_listen = wsgi.listen
1182+ old_ssl = wsgi.ssl
1183+ try:
1184+ wsgi.listen = mock_listen
1185+ wsgi.ssl = MockSsl()
1186+ # test
1187+ sock = wsgi.get_socket(conf)
1188+ # assert
1189+ self.assert_(isinstance(sock, MockSocket))
1190+ expected_socket_opts = {
1191+ socket.SOL_SOCKET: {
1192+ socket.SO_REUSEADDR: 1,
1193+ socket.SO_KEEPALIVE: 1,
1194+ },
1195+ socket.IPPROTO_TCP: {
1196+ socket.TCP_KEEPIDLE: 600,
1197+ },
1198+ }
1199+ self.assertEquals(sock.opts, expected_socket_opts)
1200+ # test ssl
1201+ sock = wsgi.get_socket(ssl_conf)
1202+ expected_kwargs = {
1203+ 'certfile': '',
1204+ 'keyfile': '',
1205+ }
1206+ self.assertEquals(wsgi.ssl.wrap_socket_called, [expected_kwargs])
1207+ finally:
1208+ wsgi.listen = old_listen
1209+ wsgi.ssl = old_ssl
1210+
1211+ def test_address_in_use(self):
1212+ # stubs
1213+ conf = {}
1214+
1215+ # mocks
1216+ def mock_listen(*args, **kwargs):
1217+ raise socket.error(errno.EADDRINUSE)
1218+
1219+ def value_error_listen(*args, **kwargs):
1220+ raise ValueError('fake')
1221+
1222+ def mock_sleep(*args):
1223+ pass
1224+
1225+ class MockTime():
1226+ """Fast clock advances 10 seconds after every call to time
1227+ """
1228+ def __init__(self):
1229+ self.current_time = old_time.time()
1230+
1231+ def time(self, *args, **kwargs):
1232+ rv = self.current_time
1233+ # advance for next call
1234+ self.current_time += 10
1235+ return rv
1236+
1237+ old_listen = wsgi.listen
1238+ old_sleep = wsgi.sleep
1239+ old_time = wsgi.time
1240+ try:
1241+ wsgi.listen = mock_listen
1242+ wsgi.sleep = mock_sleep
1243+ wsgi.time = MockTime()
1244+ # test error
1245+ self.assertRaises(Exception, wsgi.get_socket, conf)
1246+ # different error
1247+ wsgi.listen = value_error_listen
1248+ self.assertRaises(ValueError, wsgi.get_socket, conf)
1249+ finally:
1250+ wsgi.listen = old_listen
1251+ wsgi.sleep = old_sleep
1252+ wsgi.time = old_time
1253+
1254+
1255 if __name__ == '__main__':
1256 unittest.main()
1257
1258=== modified file 'test/unit/stats/test_log_processor.py'
1259--- test/unit/stats/test_log_processor.py 2010-11-17 15:36:21 +0000
1260+++ test/unit/stats/test_log_processor.py 2010-11-19 22:38:08 +0000
1261@@ -14,25 +14,12 @@
1262 # limitations under the License.
1263
1264 import unittest
1265-import os
1266-from contextlib import contextmanager
1267-from tempfile import NamedTemporaryFile
1268+from test.unit import tmpfile
1269
1270 from swift.common import internal_proxy
1271 from swift.stats import log_processor
1272
1273
1274-@contextmanager
1275-def tmpfile(content):
1276- with NamedTemporaryFile('w', delete=False) as f:
1277- file_name = f.name
1278- f.write(str(content))
1279- try:
1280- yield file_name
1281- finally:
1282- os.unlink(file_name)
1283-
1284-
1285 class FakeUploadApp(object):
1286 def __init__(self, *args, **kwargs):
1287 pass