Merge lp:~clay-gerrard/swift/run_daemon into lp:~hudson-openstack/swift/trunk
- run_daemon
- Merge into trunk
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 | ||||||||
Related bugs: |
|
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
gholt (gholt) : | # |
Chuck Thier (cthier) wrote : | # |
The only question I have is why the daemonize function ended up in swift.common.utils rather than swift.common.
clayg (clay-gerrard) wrote : | # |
thanks!
heh, funny story. When redbo and I talked about it - the plan was to expand utils.drop_
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.
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)
clayg (clay-gerrard) wrote : | # |
awesome, works great on nose 0.11.4 - it's way worse on 0.11.1 :P
clayg (clay-gerrard) wrote : | # |
John, gholt - if you guys could pull down the latest changes and see if the unittest output got cleaned up?
clayg (clay-gerrard) wrote : | # |
after discussion with redbo and gholt - we decided to pull out the stdio redirection from common.
John Dickinson (notmyname) wrote : | # |
works now! yay!
Preview Diff
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 |
This one's a wee bit hairy so another should take a peek as well.