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

Proposed by clayg
Status: Merged
Approved by: Chuck Thier
Approved revision: 223
Merged at revision: 218
Proposed branch: lp:~clay-gerrard/swift/swift_init_ng
Merge into: lp:~hudson-openstack/swift/trunk
Diff against target: 2813 lines (+2473/-193)
10 files modified
bin/swift-init (+55/-175)
doc/source/development_saio.rst (+2/-14)
doc/source/misc.rst (+7/-0)
swift/common/daemon.py (+1/-1)
swift/common/manager.py (+605/-0)
swift/common/utils.py (+59/-1)
swift/common/wsgi.py (+3/-2)
test/unit/__init__.py (+23/-0)
test/unit/common/test_manager.py (+1636/-0)
test/unit/common/test_utils.py (+82/-0)
To merge this branch: bzr merge lp:~clay-gerrard/swift/swift_init_ng
Reviewer Review Type Date Requested Status
Chuck Thier (community) Approve
gholt (community) Approve
Review via email: mp+49492@code.launchpad.net

This proposal supersedes a proposal from 2010-11-18.

Description of the change

I rewrote swift-init cause I wanted it to do somethings it didn't, and I
tried to make some of the error conditions a little better too. It has
some new features, but overall it's behavior is mostly compatible with
the old swift-init (input, return codes) - but if someone had some weird
greps on the output they're hozed.

swift-init --help is a good place to start

some highlights might be:
 $swift-init start main --wait # wait for all "main" services to start,
printing any errors to console
 $swift-init rest wait # same as above but for replicators, updaters & auditors
 $swift-init stop object -c1 # kill the first object-server "node"
 $swift-init object-server status # make sure it's dead - yay status!
 $swift-init proxy auth reload # devauth? srsly?
 $swift-init *-replicator once -n # run the object, container,
and account replicators in "once mode" and watch them log to console
until they're finished

... probably some other stuff too. Looking forward to feedback...

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

First, thanks! This should greatly increase quality of life. :)

Here are my comments so far (just a once over, haven't read the tests):

General:
Internationalization?
I think you have to update the doc/source stuff some more so that the new swift.common.manager appears.
With your "wait" mode stuff, I think we can maybe drop the "sleep 5" in resetswift? [doc/source/development_saio.rst line 534]

swift/common/manager.py:
24: Commonly imports are: stdlib imports, blank line, local swift imports
60: Is there a reason for this explicit return?
90: Why "while interval" why not just "while True" or maybe better "while time.time() < end" and clean up the bottom of the loop?
106: Is there a possibility of a problem modifying the dict you're iterating over?
114: Is there a reason for this explicit return?
213: E501 line too long (81 characters)
214: Typo "yeiled"
228: Hardcoded "15" instead of KILL_WAIT
315: Maybe use self.type = server.rsplit('-', 1)[0] instead?
361: Reference to "ini files"
365: Reference to "ini files"
401: E501 line too long (80 characters)
401: typo, grammar: "limt" and sentence is rambling
430: 3 should probably be errno.ESRCH instead
552: Reference to "ini files"
582: Isn't this TODO done?

swift/common/utils.py:
80: Not sure why you changed this since [from pydoc sys.exit] "If it is another kind of object, it will be printed and the system exit status will be one (i.e., failure)."
825: I heard that "dir" as a variable name is frowned upon by somebody somewhere.

swift/common/wsgi.py
124: Honestly? This was your change here? :P
127: Well, I mean, doesn't this one deserve some space? :)

I haven't yet begun the unit tests. Figured I'd post this and do those later.

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

yes, that all seems reasonable, thanks.

Revision history for this message
gholt (gholt) :
review: Approve
lp:~clay-gerrard/swift/swift_init_ng updated
222. By clayg

added [options] to command usage

223. By clayg

mermged upstream changes

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

Looks great clay. Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/swift-init' (properties changed: +x to -x)
2--- bin/swift-init 2011-01-19 23:21:57 +0000
3+++ bin/swift-init 2011-02-16 21:01:55 +0000
4@@ -14,180 +14,60 @@
5 # See the License for the specific language governing permissions and
6 # limitations under the License.
7
8-from __future__ import with_statement
9-import errno
10-import glob
11-import os
12-import resource
13-import signal
14 import sys
15-import time
16-
17-ALL_SERVERS = ['account-auditor', 'account-server', 'container-auditor',
18- 'container-replicator', 'container-server', 'container-updater',
19- 'object-auditor', 'object-server', 'object-replicator', 'object-updater',
20- 'proxy-server', 'account-replicator', 'auth-server', 'account-reaper']
21-GRACEFUL_SHUTDOWN_SERVERS = ['account-server', 'container-server',
22- 'object-server', 'proxy-server', 'auth-server']
23-MAX_DESCRIPTORS = 32768
24-MAX_MEMORY = (1024 * 1024 * 1024) * 2 # 2 GB
25-
26-_junk, server, command = sys.argv
27-if server == 'all':
28- servers = ALL_SERVERS
29-else:
30- if '-' not in server:
31- server = '%s-server' % server
32- servers = [server]
33-command = command.lower()
34-
35-def pid_files(server):
36- if os.path.exists('/var/run/swift/%s.pid' % server):
37- pid_files = ['/var/run/swift/%s.pid' % server]
38- else:
39- pid_files = glob.glob('/var/run/swift/%s/*.pid' % server)
40- for pid_file in pid_files:
41- pid = int(open(pid_file).read().strip())
42- yield pid_file, pid
43-
44-def do_start(server, once=False):
45- server_type = '-'.join(server.split('-')[:-1])
46-
47- for pid_file, pid in pid_files(server):
48- if os.path.exists('/proc/%s' % pid):
49- print "%s appears to already be running: %s" % (server, pid_file)
50- return
51- else:
52- print "Removing stale pid file %s" % pid_file
53- os.unlink(pid_file)
54-
55+from optparse import OptionParser
56+
57+from swift.common.manager import Server, Manager, UnknownCommandError
58+
59+USAGE = """%prog <server> [<server> ...] <command> [options]
60+
61+Commands:
62+""" + '\n'.join(["%16s: %s" % x for x in Manager.list_commands()])
63+
64+
65+def main():
66+ parser = OptionParser(USAGE)
67+ parser.add_option('-v', '--verbose', action="store_true",
68+ default=False, help="display verbose output")
69+ parser.add_option('-w', '--no-wait', action="store_false", dest="wait",
70+ default=True, help="won't wait for server to start "
71+ "before returning")
72+ parser.add_option('-o', '--once', action="store_true",
73+ default=False, help="only run one pass of daemon")
74+ # this is a negative option, default is options.daemon = True
75+ parser.add_option('-n', '--no-daemon', action="store_false", dest="daemon",
76+ default=True, help="start server interactively")
77+ parser.add_option('-g', '--graceful', action="store_true",
78+ default=False, help="send SIGHUP to supporting servers")
79+ parser.add_option('-c', '--config-num', metavar="N", type="int",
80+ dest="number", default=0,
81+ help="send command to the Nth server only")
82+ options, args = parser.parse_args()
83+
84+ if len(args) < 2:
85+ parser.print_help()
86+ print 'ERROR: specify server(s) and command'
87+ return 1
88+
89+ command = args[-1]
90+ servers = args[:-1]
91+
92+ # this is just a silly swap for me cause I always try to "start main"
93+ commands = dict(Manager.list_commands()).keys()
94+ if command not in commands and servers[0] in commands:
95+ servers.append(command)
96+ command = servers.pop(0)
97+
98+ manager = Manager(servers)
99 try:
100- resource.setrlimit(resource.RLIMIT_NOFILE,
101- (MAX_DESCRIPTORS, MAX_DESCRIPTORS))
102- resource.setrlimit(resource.RLIMIT_DATA,
103- (MAX_MEMORY, MAX_MEMORY))
104- except ValueError:
105- print "Unable to increase file descriptor limit. Running as non-root?"
106- os.environ['PYTHON_EGG_CACHE'] = '/tmp'
107-
108- def write_pid_file(pid_file, pid):
109- dir, file = os.path.split(pid_file)
110- if not os.path.exists(dir):
111- try:
112- os.makedirs(dir)
113- except OSError, err:
114- if err.errno == errno.EACCES:
115- sys.exit('Unable to create %s. Running as non-root?' % dir)
116- fp = open(pid_file, 'w')
117- fp.write('%d\n' % pid)
118- fp.close()
119-
120- def launch(ini_file, pid_file):
121- cmd = 'swift-%s' % server
122- args = [server, ini_file]
123- if once:
124- print 'Running %s once' % server
125- args.append('once')
126- else:
127- print 'Starting %s' % server
128-
129- pid = os.fork()
130- if pid == 0:
131- os.setsid()
132- with open(os.devnull, 'r+b') as nullfile:
133- for desc in (0, 1, 2): # close stdio
134- try:
135- os.dup2(nullfile.fileno(), desc)
136- except OSError:
137- pass
138- try:
139- if once:
140- os.execlp('swift-%s' % server, server,
141- ini_file, 'once')
142- else:
143- os.execlp('swift-%s' % server, server, ini_file)
144- except OSError:
145- print 'unable to launch %s' % server
146- sys.exit(0)
147- else:
148- write_pid_file(pid_file, pid)
149-
150- ini_file = '/etc/swift/%s-server.conf' % server_type
151- if os.path.exists(ini_file):
152- # single config file over-rides config dirs
153- pid_file = '/var/run/swift/%s.pid' % server
154- launch_args = [(ini_file, pid_file)]
155- elif os.path.exists('/etc/swift/%s-server/' % server_type):
156- # found config directory, searching for config file(s)
157- launch_args = []
158- for num, ini_file in enumerate(glob.glob('/etc/swift/%s-server/*.conf' \
159- % server_type)):
160- pid_file = '/var/run/swift/%s/%d.pid' % (server, num)
161- # start a server for each ini_file found
162- launch_args.append((ini_file, pid_file))
163- else:
164- # maybe there's a config file(s) out there, but I couldn't find it!
165- print 'Unable to locate config file for %s. %s does not exist?' % \
166- (server, ini_file)
167- return
168-
169- # start all servers
170- for ini_file, pid_file in launch_args:
171- launch(ini_file, pid_file)
172-
173-def do_stop(server, graceful=False):
174- if graceful and server in GRACEFUL_SHUTDOWN_SERVERS:
175- sig = signal.SIGHUP
176- else:
177- sig = signal.SIGTERM
178-
179- did_anything = False
180- pfiles = pid_files(server)
181- for pid_file, pid in pfiles:
182- did_anything = True
183- try:
184- print 'Stopping %s pid: %s signal: %s' % (server, pid, sig)
185- os.kill(pid, sig)
186- except OSError:
187- print "Process %d not running" % pid
188- try:
189- os.unlink(pid_file)
190- except OSError:
191- pass
192- for pid_file, pid in pfiles:
193- for _junk in xrange(150): # 15 seconds
194- if not os.path.exists('/proc/%s' % pid):
195- break
196- time.sleep(0.1)
197- else:
198- print 'Waited 15 seconds for pid %s (%s) to die; giving up' % \
199- (pid, pid_file)
200- if not did_anything:
201- print 'No %s running' % server
202-
203-if command == 'start':
204- for server in servers:
205- do_start(server)
206-
207-if command == 'stop':
208- for server in servers:
209- do_stop(server)
210-
211-if command == 'shutdown':
212- for server in servers:
213- do_stop(server, graceful=True)
214-
215-if command == 'restart':
216- for server in servers:
217- do_stop(server)
218- for server in servers:
219- do_start(server)
220-
221-if command == 'reload' or command == 'force-reload':
222- for server in servers:
223- do_stop(server, graceful=True)
224- do_start(server)
225-
226-if command == 'once':
227- for server in servers:
228- do_start(server, once=True)
229+ status = manager.run_command(command, **options.__dict__)
230+ except UnknownCommandError:
231+ parser.print_help()
232+ print 'ERROR: unknown command, %s' % command
233+ status = 1
234+
235+ return 1 if status else 0
236+
237+
238+if __name__ == "__main__":
239+ sys.exit(main())
240
241=== modified file 'doc/source/development_saio.rst'
242--- doc/source/development_saio.rst 2011-01-19 22:05:22 +0000
243+++ doc/source/development_saio.rst 2011-02-16 21:01:55 +0000
244@@ -531,7 +531,6 @@
245 #!/bin/bash
246
247 swift-init all stop
248- sleep 5
249 sudo umount /mnt/sdb1
250 sudo mkfs.xfs -f -i size=1024 /dev/sdb1
251 sudo mount /mnt/sdb1
252@@ -573,12 +572,9 @@
253
254 #!/bin/bash
255
256+ swift-init main start
257 # The auth-server line is only needed for DevAuth:
258 swift-init auth-server start
259- swift-init proxy-server start
260- swift-init account-server start
261- swift-init container-server start
262- swift-init object-server start
263
264 #. For Swauth (not needed for DevAuth), create `~/bin/recreateaccounts`::
265
266@@ -600,15 +596,7 @@
267 # /etc/swift/auth-server.conf). This swift-auth-recreate-accounts line
268 # is only needed for DevAuth:
269 swift-auth-recreate-accounts -K devauth
270- swift-init object-updater start
271- swift-init container-updater start
272- swift-init object-replicator start
273- swift-init container-replicator start
274- swift-init account-replicator start
275- swift-init object-auditor start
276- swift-init container-auditor start
277- swift-init account-auditor start
278- swift-init account-reaper start
279+ swift-init rest start
280
281 #. `chmod +x ~/bin/*`
282 #. `remakerings`
283
284=== modified file 'doc/source/misc.rst'
285--- doc/source/misc.rst 2011-01-14 19:49:05 +0000
286+++ doc/source/misc.rst 2011-02-16 21:01:55 +0000
287@@ -116,6 +116,13 @@
288 :members:
289 :show-inheritance:
290
291+Manager
292+=========
293+
294+.. automodule:: swift.common.manager
295+ :members:
296+ :show-inheritance:
297+
298 Ratelimit
299 =========
300
301
302=== modified file 'swift/common/daemon.py'
303--- swift/common/daemon.py 2011-02-02 21:39:08 +0000
304+++ swift/common/daemon.py 2011-02-16 21:01:55 +0000
305@@ -39,8 +39,8 @@
306 def run(self, once=False, **kwargs):
307 """Run the daemon"""
308 utils.validate_configuration()
309+ utils.drop_privileges(self.conf.get('user', 'swift'))
310 utils.capture_stdio(self.logger, **kwargs)
311- utils.drop_privileges(self.conf.get('user', 'swift'))
312
313 def kill_children(*args):
314 signal.signal(signal.SIGTERM, signal.SIG_IGN)
315
316=== added file 'swift/common/manager.py'
317--- swift/common/manager.py 1970-01-01 00:00:00 +0000
318+++ swift/common/manager.py 2011-02-16 21:01:55 +0000
319@@ -0,0 +1,605 @@
320+# Copyright (c) 2010-2011 OpenStack, LLC.
321+#
322+# Licensed under the Apache License, Version 2.0 (the "License");
323+# you may not use this file except in compliance with the License.
324+# You may obtain a copy of the License at
325+#
326+# http://www.apache.org/licenses/LICENSE-2.0
327+#
328+# Unless required by applicable law or agreed to in writing, software
329+# distributed under the License is distributed on an "AS IS" BASIS,
330+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
331+# implied.
332+# See the License for the specific language governing permissions and
333+# limitations under the License.
334+
335+from __future__ import with_statement
336+import functools
337+import errno
338+import os
339+import resource
340+import signal
341+import sys
342+import time
343+import subprocess
344+import re
345+
346+from swift.common.utils import search_tree, remove_file, write_file
347+
348+SWIFT_DIR = '/etc/swift'
349+RUN_DIR = '/var/run/swift'
350+
351+# auth-server has been removed from ALL_SERVERS, start it explicitly
352+ALL_SERVERS = ['account-auditor', 'account-server', 'container-auditor',
353+ 'container-replicator', 'container-server', 'container-updater',
354+ 'object-auditor', 'object-server', 'object-replicator', 'object-updater',
355+ 'proxy-server', 'account-replicator', 'account-reaper']
356+MAIN_SERVERS = ['proxy-server', 'account-server', 'container-server',
357+ 'object-server']
358+REST_SERVERS = [s for s in ALL_SERVERS if s not in MAIN_SERVERS]
359+GRACEFUL_SHUTDOWN_SERVERS = MAIN_SERVERS + ['auth-server']
360+START_ONCE_SERVERS = REST_SERVERS
361+
362+KILL_WAIT = 15 # seconds to wait for servers to die
363+
364+MAX_DESCRIPTORS = 32768
365+MAX_MEMORY = (1024 * 1024 * 1024) * 2 # 2 GB
366+
367+
368+def setup_env():
369+ """Try to increase resource limits of the OS. Move PYTHON_EGG_CACHE to /tmp
370+ """
371+ try:
372+ resource.setrlimit(resource.RLIMIT_NOFILE,
373+ (MAX_DESCRIPTORS, MAX_DESCRIPTORS))
374+ resource.setrlimit(resource.RLIMIT_DATA,
375+ (MAX_MEMORY, MAX_MEMORY))
376+ except ValueError:
377+ print _("WARNING: Unable to increase file descriptor limit. "
378+ "Running as non-root?")
379+
380+ os.environ['PYTHON_EGG_CACHE'] = '/tmp'
381+
382+
383+def command(func):
384+ """
385+ Decorator to declare which methods are accessible as commands, commands
386+ always return 1 or 0, where 0 should indicate success.
387+
388+ :param func: function to make public
389+ """
390+ func.publicly_accessible = True
391+
392+ @functools.wraps(func)
393+ def wrapped(*a, **kw):
394+ rv = func(*a, **kw)
395+ return 1 if rv else 0
396+ return wrapped
397+
398+
399+def watch_server_pids(server_pids, interval=1, **kwargs):
400+ """Monitor a collection of server pids yeilding back those pids that
401+ aren't responding to signals.
402+
403+ :param server_pids: a dict, lists of pids [int,...] keyed on
404+ Server objects
405+ """
406+ status = {}
407+ start = time.time()
408+ end = start + interval
409+ server_pids = dict(server_pids) # make a copy
410+ while True:
411+ for server, pids in server_pids.items():
412+ for pid in pids:
413+ try:
414+ # let pid stop if it wants to
415+ os.waitpid(pid, os.WNOHANG)
416+ except OSError, e:
417+ if e.errno not in (errno.ECHILD, errno.ESRCH):
418+ raise # else no such child/process
419+ # check running pids for server
420+ status[server] = server.get_running_pids(**kwargs)
421+ for pid in pids:
422+ # original pids no longer in running pids!
423+ if pid not in status[server]:
424+ yield server, pid
425+ # update active pids list using running_pids
426+ server_pids[server] = status[server]
427+ if not [p for server, pids in status.items() for p in pids]:
428+ # no more running pids
429+ break
430+ if time.time() > end:
431+ break
432+ else:
433+ time.sleep(0.1)
434+
435+
436+class UnknownCommandError(Exception):
437+ pass
438+
439+
440+class Manager():
441+ """Main class for performing commands on groups of servers.
442+
443+ :param servers: list of server names as strings
444+
445+ """
446+
447+ def __init__(self, servers):
448+ server_names = set()
449+ for server in servers:
450+ if server == 'all':
451+ server_names.update(ALL_SERVERS)
452+ elif server == 'main':
453+ server_names.update(MAIN_SERVERS)
454+ elif server == 'rest':
455+ server_names.update(REST_SERVERS)
456+ elif '*' in server:
457+ # convert glob to regex
458+ server_names.update([s for s in ALL_SERVERS if
459+ re.match(server.replace('*', '.*'), s)])
460+ else:
461+ server_names.add(server)
462+
463+ self.servers = set()
464+ for name in server_names:
465+ self.servers.add(Server(name))
466+
467+ @command
468+ def status(self, **kwargs):
469+ """display status of tracked pids for server
470+ """
471+ status = 0
472+ for server in self.servers:
473+ status += server.status(**kwargs)
474+ return status
475+
476+ @command
477+ def start(self, **kwargs):
478+ """starts a server
479+ """
480+ setup_env()
481+ status = 0
482+
483+ for server in self.servers:
484+ server.launch(**kwargs)
485+ if not kwargs.get('daemon', True):
486+ for server in self.servers:
487+ try:
488+ status += server.interact(**kwargs)
489+ except KeyboardInterrupt:
490+ print _('\nuser quit')
491+ self.stop(**kwargs)
492+ break
493+ elif kwargs.get('wait', True):
494+ for server in self.servers:
495+ status += server.wait(**kwargs)
496+ return status
497+
498+ @command
499+ def no_wait(self, **kwargs):
500+ """spawn server and return immediately
501+ """
502+ kwargs['wait'] = False
503+ return self.start(**kwargs)
504+
505+ @command
506+ def no_daemon(self, **kwargs):
507+ """start a server interactively
508+ """
509+ kwargs['daemon'] = False
510+ return self.start(**kwargs)
511+
512+ @command
513+ def once(self, **kwargs):
514+ """start server and run one pass on supporting daemons
515+ """
516+ kwargs['once'] = True
517+ return self.start(**kwargs)
518+
519+ @command
520+ def stop(self, **kwargs):
521+ """stops a server
522+ """
523+ server_pids = {}
524+ for server in self.servers:
525+ signaled_pids = server.stop(**kwargs)
526+ if not signaled_pids:
527+ print _('No %s running') % server
528+ else:
529+ server_pids[server] = signaled_pids
530+
531+ # all signaled_pids, i.e. list(itertools.chain(*server_pids.values()))
532+ signaled_pids = [p for server, pids in server_pids.items()
533+ for p in pids]
534+ # keep track of the pids yeiled back as killed for all servers
535+ killed_pids = set()
536+ for server, killed_pid in watch_server_pids(server_pids,
537+ interval=KILL_WAIT, **kwargs):
538+ print _("%s (%s) appears to have stopped") % (server, killed_pid)
539+ killed_pids.add(killed_pid)
540+ if not killed_pids.symmetric_difference(signaled_pids):
541+ # all proccesses have been stopped
542+ return 0
543+
544+ # reached interval n watch_pids w/o killing all servers
545+ for server, pids in server_pids.items():
546+ if not killed_pids.issuperset(pids):
547+ # some pids of this server were not killed
548+ print _('Waited %s seconds for %s to die; giving up') % (
549+ KILL_WAIT, server)
550+ return 1
551+
552+ @command
553+ def shutdown(self, **kwargs):
554+ """allow current requests to finish on supporting servers
555+ """
556+ kwargs['graceful'] = True
557+ status = 0
558+ status += self.stop(**kwargs)
559+ return status
560+
561+ @command
562+ def restart(self, **kwargs):
563+ """stops then restarts server
564+ """
565+ status = 0
566+ status += self.stop(**kwargs)
567+ status += self.start(**kwargs)
568+ return status
569+
570+ @command
571+ def reload(self, **kwargs):
572+ """graceful shutdown then restart on supporting servers
573+ """
574+ kwargs['graceful'] = True
575+ status = 0
576+ for server in self.servers:
577+ m = Manager([server.server])
578+ status += m.stop(**kwargs)
579+ status += m.start(**kwargs)
580+ return status
581+
582+ @command
583+ def force_reload(self, **kwargs):
584+ """alias for reload
585+ """
586+ return self.reload(**kwargs)
587+
588+ def get_command(self, cmd):
589+ """Find and return the decorated method named like cmd
590+
591+ :param cmd: the command to get, a string, if not found raises
592+ UnknownCommandError
593+
594+ """
595+ cmd = cmd.lower().replace('-', '_')
596+ try:
597+ f = getattr(self, cmd)
598+ except AttributeError:
599+ raise UnknownCommandError(cmd)
600+ if not hasattr(f, 'publicly_accessible'):
601+ raise UnknownCommandError(cmd)
602+ return f
603+
604+ @classmethod
605+ def list_commands(cls):
606+ """Get all publicly accessible commands
607+
608+ :returns: a list of string tuples (cmd, help), the method names who are
609+ decorated as commands
610+ """
611+ get_method = lambda cmd: getattr(cls, cmd)
612+ return sorted([(x.replace('_', '-'), get_method(x).__doc__.strip())
613+ for x in dir(cls) if
614+ getattr(get_method(x), 'publicly_accessible', False)])
615+
616+ def run_command(self, cmd, **kwargs):
617+ """Find the named command and run it
618+
619+ :param cmd: the command name to run
620+
621+ """
622+ f = self.get_command(cmd)
623+ return f(**kwargs)
624+
625+
626+class Server():
627+ """Manage operations on a server or group of servers of similar type
628+
629+ :param server: name of server
630+ """
631+
632+ def __init__(self, server):
633+ if '-' not in server:
634+ server = '%s-server' % server
635+ self.server = server.lower()
636+ self.type = server.rsplit('-', 1)[0]
637+ self.cmd = 'swift-%s' % server
638+ self.procs = []
639+
640+ def __str__(self):
641+ return self.server
642+
643+ def __repr__(self):
644+ return "%s(%s)" % (self.__class__.__name__, repr(str(self)))
645+
646+ def __hash__(self):
647+ return hash(str(self))
648+
649+ def __eq__(self, other):
650+ try:
651+ return self.server == other.server
652+ except AttributeError:
653+ return False
654+
655+ def get_pid_file_name(self, conf_file):
656+ """Translate conf_file to a corresponding pid_file
657+
658+ :param conf_file: an conf_file for this server, a string
659+
660+ :returns: the pid_file for this conf_file
661+
662+ """
663+ return conf_file.replace(
664+ os.path.normpath(SWIFT_DIR), RUN_DIR, 1).replace(
665+ '%s-server' % self.type, self.server, 1).rsplit(
666+ '.conf', 1)[0] + '.pid'
667+
668+ def get_conf_file_name(self, pid_file):
669+ """Translate pid_file to a corresponding conf_file
670+
671+ :param pid_file: a pid_file for this server, a string
672+
673+ :returns: the conf_file for this pid_file
674+
675+ """
676+ return pid_file.replace(
677+ os.path.normpath(RUN_DIR), SWIFT_DIR, 1).replace(
678+ self.server, '%s-server' % self.type, 1).rsplit(
679+ '.pid', 1)[0] + '.conf'
680+
681+ def conf_files(self, **kwargs):
682+ """Get conf files for this server
683+
684+ :param: number, if supplied will only lookup the nth server
685+
686+ :returns: list of conf files
687+ """
688+ found_conf_files = search_tree(SWIFT_DIR, '%s-server*' % self.type,
689+ '.conf')
690+ number = kwargs.get('number')
691+ if number:
692+ try:
693+ conf_files = [found_conf_files[number - 1]]
694+ except IndexError:
695+ conf_files = []
696+ else:
697+ conf_files = found_conf_files
698+ if not conf_files:
699+ # maybe there's a config file(s) out there, but I couldn't find it!
700+ if not kwargs.get('quiet'):
701+ print _('Unable to locate config %sfor %s') % (
702+ ('number %s ' % number if number else ''), self.server)
703+ if kwargs.get('verbose') and not kwargs.get('quiet'):
704+ if found_conf_files:
705+ print _('Found configs:')
706+ for i, conf_file in enumerate(found_conf_files):
707+ print ' %d) %s' % (i + 1, conf_file)
708+
709+ return conf_files
710+
711+ def pid_files(self, **kwargs):
712+ """Get pid files for this server
713+
714+ :param: number, if supplied will only lookup the nth server
715+
716+ :returns: list of pid files
717+ """
718+ pid_files = search_tree(RUN_DIR, '%s*' % self.server, '.pid')
719+ if kwargs.get('number', 0):
720+ conf_files = self.conf_files(**kwargs)
721+ # filter pid_files to match the index of numbered conf_file
722+ pid_files = [pid_file for pid_file in pid_files if
723+ self.get_conf_file_name(pid_file) in conf_files]
724+ return pid_files
725+
726+ def iter_pid_files(self, **kwargs):
727+ """Generator, yields (pid_file, pids)
728+ """
729+ for pid_file in self.pid_files(**kwargs):
730+ yield pid_file, int(open(pid_file).read().strip())
731+
732+ def signal_pids(self, sig, **kwargs):
733+ """Send a signal to pids for this server
734+
735+ :param sig: signal to send
736+
737+ :returns: a dict mapping pids (ints) to pid_files (paths)
738+
739+ """
740+ pids = {}
741+ for pid_file, pid in self.iter_pid_files(**kwargs):
742+ try:
743+ if sig != signal.SIG_DFL:
744+ print _('Signal %s pid: %s signal: %s') % (self.server,
745+ pid, sig)
746+ os.kill(pid, sig)
747+ except OSError, e:
748+ if e.errno == errno.ESRCH:
749+ # pid does not exist
750+ if kwargs.get('verbose'):
751+ print _("Removing stale pid file %s") % pid_file
752+ remove_file(pid_file)
753+ else:
754+ # process exists
755+ pids[pid] = pid_file
756+ return pids
757+
758+ def get_running_pids(self, **kwargs):
759+ """Get running pids
760+
761+ :returns: a dict mapping pids (ints) to pid_files (paths)
762+
763+ """
764+ return self.signal_pids(signal.SIG_DFL, **kwargs) # send noop
765+
766+ def kill_running_pids(self, **kwargs):
767+ """Kill running pids
768+
769+ :param graceful: if True, attempt SIGHUP on supporting servers
770+
771+ :returns: a dict mapping pids (ints) to pid_files (paths)
772+
773+ """
774+ graceful = kwargs.get('graceful')
775+ if graceful and self.server in GRACEFUL_SHUTDOWN_SERVERS:
776+ sig = signal.SIGHUP
777+ else:
778+ sig = signal.SIGTERM
779+ return self.signal_pids(sig, **kwargs)
780+
781+ def status(self, pids=None, **kwargs):
782+ """Display status of server
783+
784+ :param: pids, if not supplied pids will be populated automatically
785+ :param: number, if supplied will only lookup the nth server
786+
787+ :returns: 1 if server is not running, 0 otherwise
788+ """
789+ if pids is None:
790+ pids = self.get_running_pids(**kwargs)
791+ if not pids:
792+ number = kwargs.get('number', 0)
793+ if number:
794+ kwargs['quiet'] = True
795+ conf_files = self.conf_files(**kwargs)
796+ if conf_files:
797+ print _("%s #%d not running (%s)") % (self.server, number,
798+ conf_files[0])
799+ else:
800+ print _("No %s running") % self.server
801+ return 1
802+ for pid, pid_file in pids.items():
803+ conf_file = self.get_conf_file_name(pid_file)
804+ print _("%s running (%s - %s)") % (self.server, pid, conf_file)
805+ return 0
806+
807+ def spawn(self, conf_file, once=False, wait=True, daemon=True, **kwargs):
808+ """Launch a subprocess for this server.
809+
810+ :param conf_file: path to conf_file to use as first arg
811+ :param once: boolean, add once argument to command
812+ :param wait: boolean, if true capture stdout with a pipe
813+ :param daemon: boolean, if true ask server to log to console
814+
815+ :returns : the pid of the spawned process
816+ """
817+ args = [self.cmd, conf_file]
818+ if once:
819+ args.append('once')
820+ if not daemon:
821+ # ask the server to log to console
822+ args.append('verbose')
823+
824+ # figure out what we're going to do with stdio
825+ if not daemon:
826+ # do nothing, this process is open until the spawns close anyway
827+ re_out = None
828+ re_err = None
829+ else:
830+ re_err = subprocess.STDOUT
831+ if wait:
832+ # we're going to need to block on this...
833+ re_out = subprocess.PIPE
834+ else:
835+ re_out = open(os.devnull, 'w+b')
836+ proc = subprocess.Popen(args, stdout=re_out, stderr=re_err)
837+ pid_file = self.get_pid_file_name(conf_file)
838+ write_file(pid_file, proc.pid)
839+ self.procs.append(proc)
840+ return proc.pid
841+
842+ def wait(self, **kwargs):
843+ """
844+ wait on spawned procs to start
845+ """
846+ status = 0
847+ for proc in self.procs:
848+ # wait for process to close it's stdout
849+ output = proc.stdout.read()
850+ if output:
851+ print output
852+ proc.communicate()
853+ if proc.returncode:
854+ status += 1
855+ return status
856+
857+ def interact(self, **kwargs):
858+ """
859+ wait on spawned procs to terminate
860+ """
861+ status = 0
862+ for proc in self.procs:
863+ # wait for process to terminate
864+ proc.communicate()
865+ if proc.returncode:
866+ status += 1
867+ return status
868+
869+ def launch(self, **kwargs):
870+ """
871+ Collect conf files and attempt to spawn the processes for this server
872+ """
873+ conf_files = self.conf_files(**kwargs)
874+ if not conf_files:
875+ return []
876+
877+ pids = self.get_running_pids(**kwargs)
878+
879+ already_started = False
880+ for pid, pid_file in pids.items():
881+ conf_file = self.get_conf_file_name(pid_file)
882+ # for legacy compat you can't start other servers if one server is
883+ # already running (unless -n specifies which one you want), this
884+ # restriction could potentially be lifted, and launch could start
885+ # any unstarted instances
886+ if conf_file in conf_files:
887+ already_started = True
888+ print _("%s running (%s - %s)") % (self.server, pid, conf_file)
889+ elif not kwargs.get('number', 0):
890+ already_started = True
891+ print _("%s running (%s - %s)") % (self.server, pid, pid_file)
892+
893+ if already_started:
894+ print _("%s already started...") % self.server
895+ return []
896+
897+ if self.server not in START_ONCE_SERVERS:
898+ kwargs['once'] = False
899+
900+ pids = {}
901+ for conf_file in conf_files:
902+ if kwargs.get('once'):
903+ msg = _('Running %s once') % self.server
904+ else:
905+ msg = _('Starting %s') % self.server
906+ print '%s...(%s)' % (msg, conf_file)
907+ try:
908+ pid = self.spawn(conf_file, **kwargs)
909+ except OSError, e:
910+ if e.errno == errno.ENOENT:
911+ # TODO: should I check if self.cmd exists earlier?
912+ print _("%s does not exist") % self.cmd
913+ break
914+ pids[pid] = conf_file
915+
916+ return pids
917+
918+ def stop(self, **kwargs):
919+ """Send stop signals to pids for this server
920+
921+ :returns: a dict mapping pids (ints) to pid_files (paths)
922+
923+ """
924+ return self.kill_running_pids(**kwargs)
925
926=== modified file 'swift/common/utils.py'
927--- swift/common/utils.py 2011-02-11 17:27:05 +0000
928+++ swift/common/utils.py 2011-02-16 21:01:55 +0000
929@@ -34,6 +34,7 @@
930 from optparse import OptionParser
931 from tempfile import mkstemp
932 import cPickle as pickle
933+import glob
934 from urlparse import urlparse as stdlib_urlparse, ParseResult
935
936 import eventlet
937@@ -471,7 +472,10 @@
938 stdio_fds = [0, 1, 2]
939 for _junk, handler in getattr(get_logger,
940 'console_handler4logger', {}).items():
941- stdio_fds.remove(handler.stream.fileno())
942+ try:
943+ stdio_fds.remove(handler.stream.fileno())
944+ except ValueError:
945+ pass # fd not in list
946
947 with open(os.devnull, 'r+b') as nullfile:
948 # close stdio (excludes fds open for logging)
949@@ -786,6 +790,60 @@
950 renamer(tmppath, dest)
951
952
953+def search_tree(root, glob_match, ext):
954+ """Look in root, for any files/dirs matching glob, recurively traversing
955+ any found directories looking for files ending with ext
956+
957+ :param root: start of search path
958+ :param glob_match: glob to match in root, matching dirs are traversed with
959+ os.walk
960+ :param ext: only files that end in ext will be returned
961+
962+ :returns: list of full paths to matching files, sorted
963+
964+ """
965+ found_files = []
966+ for path in glob.glob(os.path.join(root, glob_match)):
967+ if path.endswith(ext):
968+ found_files.append(path)
969+ else:
970+ for root, dirs, files in os.walk(path):
971+ for file in files:
972+ if file.endswith(ext):
973+ found_files.append(os.path.join(root, file))
974+ return sorted(found_files)
975+
976+
977+def write_file(path, contents):
978+ """Write contents to file at path
979+
980+ :param path: any path, subdirs will be created as needed
981+ :param contents: data to write to file, will be converted to string
982+
983+ """
984+ dirname, name = os.path.split(path)
985+ if not os.path.exists(dirname):
986+ try:
987+ os.makedirs(dirname)
988+ except OSError, err:
989+ if err.errno == errno.EACCES:
990+ sys.exit('Unable to create %s. Running as '
991+ 'non-root?' % dirname)
992+ with open(path, 'w') as f:
993+ f.write('%s' % contents)
994+
995+
996+def remove_file(path):
997+ """Quiet wrapper for os.unlink, OSErrors are suppressed
998+
999+ :param path: first and only argument passed to os.unlink
1000+ """
1001+ try:
1002+ os.unlink(path)
1003+ except OSError:
1004+ pass
1005+
1006+
1007 def audit_location_generator(devices, datadir, mount_check=True, logger=None):
1008 '''
1009 Given a devices path and a data directory, yield (path, device,
1010
1011=== modified file 'swift/common/wsgi.py'
1012--- swift/common/wsgi.py 2011-02-11 17:27:05 +0000
1013+++ swift/common/wsgi.py 2011-02-16 21:01:55 +0000
1014@@ -119,8 +119,6 @@
1015 logger = get_logger(conf, log_name,
1016 log_to_console=kwargs.pop('verbose', False), log_route='wsgi')
1017
1018- # redirect errors to logger and close stdio
1019- capture_stdio(logger)
1020 # bind to address and port
1021 sock = get_socket(conf, default_port=kwargs.get('default_port', 8080))
1022 # remaining tasks should not require elevated privileges
1023@@ -129,6 +127,9 @@
1024 # finally after binding to ports and privilege drop, run app __init__ code
1025 app = loadapp('config:%s' % conf_file, global_conf={'log_name': log_name})
1026
1027+ # redirect errors to logger and close stdio
1028+ capture_stdio(logger)
1029+
1030 def run_server():
1031 wsgi.HttpProtocol.default_request_version = "HTTP/1.0"
1032 eventlet.hubs.use_hub('poll')
1033
1034=== modified file 'test/unit/__init__.py'
1035--- test/unit/__init__.py 2011-01-19 22:19:43 +0000
1036+++ test/unit/__init__.py 2011-02-16 21:01:55 +0000
1037@@ -4,6 +4,8 @@
1038 from contextlib import contextmanager
1039 from tempfile import NamedTemporaryFile
1040 from eventlet.green import socket
1041+from tempfile import mkdtemp
1042+from shutil import rmtree
1043
1044
1045 def readuntil2crlfs(fd):
1046@@ -68,6 +70,27 @@
1047 xattr.getxattr = _getxattr
1048
1049
1050+@contextmanager
1051+def temptree(files, contents=''):
1052+ # generate enough contents to fill the files
1053+ c = len(files)
1054+ contents = (list(contents) + [''] * c)[:c]
1055+ tempdir = mkdtemp()
1056+ for path, content in zip(files, contents):
1057+ if os.path.isabs(path):
1058+ path = '.' + path
1059+ new_path = os.path.join(tempdir, path)
1060+ subdir = os.path.dirname(new_path)
1061+ if not os.path.exists(subdir):
1062+ os.makedirs(subdir)
1063+ with open(new_path, 'w') as f:
1064+ f.write(str(content))
1065+ try:
1066+ yield tempdir
1067+ finally:
1068+ rmtree(tempdir)
1069+
1070+
1071 class MockTrue(object):
1072 """
1073 Instances of MockTrue evaluate like True
1074
1075=== added file 'test/unit/common/test_manager.py'
1076--- test/unit/common/test_manager.py 1970-01-01 00:00:00 +0000
1077+++ test/unit/common/test_manager.py 2011-02-16 21:01:55 +0000
1078@@ -0,0 +1,1636 @@
1079+# Copyright (c) 2010-2011 OpenStack, LLC.
1080+#
1081+# Licensed under the Apache License, Version 2.0 (the "License");
1082+# you may not use this file except in compliance with the License.
1083+# You may obtain a copy of the License at
1084+#
1085+# http://www.apache.org/licenses/LICENSE-2.0
1086+#
1087+# Unless required by applicable law or agreed to in writing, software
1088+# distributed under the License is distributed on an "AS IS" BASIS,
1089+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
1090+# implied.
1091+# See the License for the specific language governing permissions and
1092+# limitations under the License.
1093+
1094+import unittest
1095+from nose import SkipTest
1096+from test.unit import temptree
1097+
1098+import os
1099+import sys
1100+import resource
1101+import signal
1102+import errno
1103+from contextlib import contextmanager
1104+from collections import defaultdict
1105+from threading import Thread
1106+from time import sleep, time
1107+
1108+from swift.common import manager
1109+
1110+DUMMY_SIG = 1
1111+
1112+
1113+class MockOs():
1114+
1115+ def __init__(self, pids):
1116+ self.running_pids = pids
1117+ self.pid_sigs = defaultdict(list)
1118+ self.closed_fds = []
1119+ self.child_pid = 9999 # fork defaults to test parent process path
1120+ self.execlp_called = False
1121+
1122+ def kill(self, pid, sig):
1123+ if pid not in self.running_pids:
1124+ raise OSError(3, 'No such process')
1125+ self.pid_sigs[pid].append(sig)
1126+
1127+ def __getattr__(self, name):
1128+ # I only over-ride portions of the os module
1129+ try:
1130+ return object.__getattr__(self, name)
1131+ except AttributeError:
1132+ return getattr(os, name)
1133+
1134+
1135+def pop_stream(f):
1136+ """read everything out of file from the top and clear it out
1137+ """
1138+ f.flush()
1139+ f.seek(0)
1140+ output = f.read()
1141+ f.seek(0)
1142+ f.truncate()
1143+ #print >> sys.stderr, output
1144+ return output
1145+
1146+
1147+class TestManagerModule(unittest.TestCase):
1148+
1149+ def test_servers(self):
1150+ main_plus_rest = set(manager.MAIN_SERVERS + manager.REST_SERVERS)
1151+ self.assertEquals(set(manager.ALL_SERVERS), main_plus_rest)
1152+ # make sure there's no server listed in both
1153+ self.assertEquals(len(main_plus_rest), len(manager.MAIN_SERVERS) +
1154+ len(manager.REST_SERVERS))
1155+
1156+ def test_setup_env(self):
1157+ class MockResource():
1158+ def __init__(self, error=None):
1159+ self.error = error
1160+ self.called_with_args = []
1161+
1162+ def setrlimit(self, resource, limits):
1163+ if self.error:
1164+ raise self.error
1165+ self.called_with_args.append((resource, limits))
1166+
1167+ def __getattr__(self, name):
1168+ # I only over-ride portions of the resource module
1169+ try:
1170+ return object.__getattr__(self, name)
1171+ except AttributeError:
1172+ return getattr(resource, name)
1173+
1174+ _orig_resource = manager.resource
1175+ _orig_environ = os.environ
1176+ try:
1177+ manager.resource = MockResource()
1178+ manager.os.environ = {}
1179+ manager.setup_env()
1180+ expected = [
1181+ (resource.RLIMIT_NOFILE, (manager.MAX_DESCRIPTORS,
1182+ manager.MAX_DESCRIPTORS)),
1183+ (resource.RLIMIT_DATA, (manager.MAX_MEMORY,
1184+ manager.MAX_MEMORY)),
1185+ ]
1186+ self.assertEquals(manager.resource.called_with_args, expected)
1187+ self.assertEquals(manager.os.environ['PYTHON_EGG_CACHE'], '/tmp')
1188+
1189+ # test error condition
1190+ manager.resource = MockResource(error=ValueError())
1191+ manager.os.environ = {}
1192+ manager.setup_env()
1193+ self.assertEquals(manager.resource.called_with_args, [])
1194+ self.assertEquals(manager.os.environ['PYTHON_EGG_CACHE'], '/tmp')
1195+
1196+ manager.resource = MockResource(error=OSError())
1197+ manager.os.environ = {}
1198+ self.assertRaises(OSError, manager.setup_env)
1199+ self.assertEquals(manager.os.environ.get('PYTHON_EGG_CACHE'), None)
1200+ finally:
1201+ manager.resource = _orig_resource
1202+ os.environ = _orig_environ
1203+
1204+ def test_command_wrapper(self):
1205+ @manager.command
1206+ def myfunc(arg1):
1207+ """test doc
1208+ """
1209+ return arg1
1210+
1211+ self.assertEquals(myfunc.__doc__.strip(), 'test doc')
1212+ self.assertEquals(myfunc(1), 1)
1213+ self.assertEquals(myfunc(0), 0)
1214+ self.assertEquals(myfunc(True), 1)
1215+ self.assertEquals(myfunc(False), 0)
1216+ self.assert_(hasattr(myfunc, 'publicly_accessible'))
1217+ self.assert_(myfunc.publicly_accessible)
1218+
1219+ def test_watch_server_pids(self):
1220+ class MockOs():
1221+ WNOHANG = os.WNOHANG
1222+
1223+ def __init__(self, pid_map={}):
1224+ self.pid_map = {}
1225+ for pid, v in pid_map.items():
1226+ self.pid_map[pid] = (x for x in v)
1227+
1228+ def waitpid(self, pid, options):
1229+ try:
1230+ rv = self.pid_map[pid].next()
1231+ except StopIteration:
1232+ raise OSError(errno.ECHILD, os.strerror(errno.ECHILD))
1233+ except KeyError:
1234+ raise OSError(errno.ESRCH, os.strerror(errno.ESRCH))
1235+ if isinstance(rv, Exception):
1236+ raise rv
1237+ else:
1238+ return rv
1239+
1240+ class MockTime():
1241+ def __init__(self, ticks=None):
1242+ self.tock = time()
1243+ if not ticks:
1244+ ticks = []
1245+
1246+ self.ticks = (t for t in ticks)
1247+
1248+ def time(self):
1249+ try:
1250+ self.tock += self.ticks.next()
1251+ except StopIteration:
1252+ self.tock += 1
1253+ return self.tock
1254+
1255+ def sleep(*args):
1256+ return
1257+
1258+ class MockServer():
1259+
1260+ def __init__(self, pids, zombie=0):
1261+ self.heartbeat = (pids for _ in range(zombie))
1262+
1263+ def get_running_pids(self):
1264+ try:
1265+ rv = self.heartbeat.next()
1266+ return rv
1267+ except StopIteration:
1268+ return {}
1269+
1270+ _orig_os = manager.os
1271+ _orig_time = manager.time
1272+ _orig_server = manager.Server
1273+ try:
1274+ manager.time = MockTime()
1275+ manager.os = MockOs()
1276+ # this server always says it's dead when you ask for running pids
1277+ server = MockServer([1])
1278+ # list of pids keyed on servers to watch
1279+ server_pids = {
1280+ server: [1],
1281+ }
1282+ # basic test, server dies
1283+ gen = manager.watch_server_pids(server_pids)
1284+ expected = [(server, 1)]
1285+ self.assertEquals([x for x in gen], expected)
1286+ # start long running server and short interval
1287+ server = MockServer([1], zombie=15)
1288+ server_pids = {
1289+ server: [1],
1290+ }
1291+ gen = manager.watch_server_pids(server_pids)
1292+ self.assertEquals([x for x in gen], [])
1293+ # wait a little longer
1294+ gen = manager.watch_server_pids(server_pids, interval=15)
1295+ self.assertEquals([x for x in gen], [(server, 1)])
1296+ # zombie process
1297+ server = MockServer([1], zombie=200)
1298+ server_pids = {
1299+ server: [1],
1300+ }
1301+ # test weird os error
1302+ manager.os = MockOs({1: [OSError()]})
1303+ gen = manager.watch_server_pids(server_pids)
1304+ self.assertRaises(OSError, lambda: [x for x in gen])
1305+ # test multi-server
1306+ server1 = MockServer([1, 10], zombie=200)
1307+ server2 = MockServer([2, 20], zombie=8)
1308+ server_pids = {
1309+ server1: [1, 10],
1310+ server2: [2, 20],
1311+ }
1312+ pid_map = {
1313+ 1: [None for _ in range(10)],
1314+ 2: [None for _ in range(8)],
1315+ 20: [None for _ in range(4)],
1316+ }
1317+ manager.os = MockOs(pid_map)
1318+ gen = manager.watch_server_pids(server_pids,
1319+ interval=manager.KILL_WAIT)
1320+ expected = [
1321+ (server2, 2),
1322+ (server2, 20),
1323+ ]
1324+ self.assertEquals([x for x in gen], expected)
1325+
1326+ finally:
1327+ manager.os = _orig_os
1328+ manager.time = _orig_time
1329+ manager.Server = _orig_server
1330+
1331+ def test_exc(self):
1332+ self.assert_(issubclass(manager.UnknownCommandError, Exception))
1333+
1334+
1335+class TestServer(unittest.TestCase):
1336+
1337+ def tearDown(self):
1338+ reload(manager)
1339+
1340+ def join_swift_dir(self, path):
1341+ return os.path.join(manager.SWIFT_DIR, path)
1342+
1343+ def join_run_dir(self, path):
1344+ return os.path.join(manager.RUN_DIR, path)
1345+
1346+ def test_create_server(self):
1347+ server = manager.Server('proxy')
1348+ self.assertEquals(server.server, 'proxy-server')
1349+ self.assertEquals(server.type, 'proxy')
1350+ self.assertEquals(server.cmd, 'swift-proxy-server')
1351+ server = manager.Server('object-replicator')
1352+ self.assertEquals(server.server, 'object-replicator')
1353+ self.assertEquals(server.type, 'object')
1354+ self.assertEquals(server.cmd, 'swift-object-replicator')
1355+
1356+ def test_server_to_string(self):
1357+ server = manager.Server('Proxy')
1358+ self.assertEquals(str(server), 'proxy-server')
1359+ server = manager.Server('object-replicator')
1360+ self.assertEquals(str(server), 'object-replicator')
1361+
1362+ def test_server_repr(self):
1363+ server = manager.Server('proxy')
1364+ self.assert_(server.__class__.__name__ in repr(server))
1365+ self.assert_(str(server) in repr(server))
1366+
1367+ def test_server_equality(self):
1368+ server1 = manager.Server('Proxy')
1369+ server2 = manager.Server('proxy-server')
1370+ self.assertEquals(server1, server2)
1371+ # it is NOT a string
1372+ self.assertNotEquals(server1, 'proxy-server')
1373+
1374+ def test_get_pid_file_name(self):
1375+ server = manager.Server('proxy')
1376+ conf_file = self.join_swift_dir('proxy-server.conf')
1377+ pid_file = self.join_run_dir('proxy-server.pid')
1378+ self.assertEquals(pid_file, server.get_pid_file_name(conf_file))
1379+ server = manager.Server('object-replicator')
1380+ conf_file = self.join_swift_dir('object-server/1.conf')
1381+ pid_file = self.join_run_dir('object-replicator/1.pid')
1382+ self.assertEquals(pid_file, server.get_pid_file_name(conf_file))
1383+ server = manager.Server('container-auditor')
1384+ conf_file = self.join_swift_dir(
1385+ 'container-server/1/container-auditor.conf')
1386+ pid_file = self.join_run_dir(
1387+ 'container-auditor/1/container-auditor.pid')
1388+ self.assertEquals(pid_file, server.get_pid_file_name(conf_file))
1389+
1390+ def test_get_conf_file_name(self):
1391+ server = manager.Server('proxy')
1392+ conf_file = self.join_swift_dir('proxy-server.conf')
1393+ pid_file = self.join_run_dir('proxy-server.pid')
1394+ self.assertEquals(conf_file, server.get_conf_file_name(pid_file))
1395+ server = manager.Server('object-replicator')
1396+ conf_file = self.join_swift_dir('object-server/1.conf')
1397+ pid_file = self.join_run_dir('object-replicator/1.pid')
1398+ self.assertEquals(conf_file, server.get_conf_file_name(pid_file))
1399+ server = manager.Server('container-auditor')
1400+ conf_file = self.join_swift_dir(
1401+ 'container-server/1/container-auditor.conf')
1402+ pid_file = self.join_run_dir(
1403+ 'container-auditor/1/container-auditor.pid')
1404+ self.assertEquals(conf_file, server.get_conf_file_name(pid_file))
1405+
1406+ def test_conf_files(self):
1407+ # test get single conf file
1408+ conf_files = (
1409+ 'proxy-server.conf',
1410+ 'proxy-server.ini',
1411+ 'auth-server.conf',
1412+ )
1413+ with temptree(conf_files) as t:
1414+ manager.SWIFT_DIR = t
1415+ server = manager.Server('proxy')
1416+ conf_files = server.conf_files()
1417+ self.assertEquals(len(conf_files), 1)
1418+ conf_file = conf_files[0]
1419+ proxy_conf = self.join_swift_dir('proxy-server.conf')
1420+ self.assertEquals(conf_file, proxy_conf)
1421+
1422+ # test multi server conf files & grouping of server-type config
1423+ conf_files = (
1424+ 'object-server1.conf',
1425+ 'object-server/2.conf',
1426+ 'object-server/object3.conf',
1427+ 'object-server/conf/server4.conf',
1428+ 'object-server.txt',
1429+ 'proxy-server.conf',
1430+ )
1431+ with temptree(conf_files) as t:
1432+ manager.SWIFT_DIR = t
1433+ server = manager.Server('object-replicator')
1434+ conf_files = server.conf_files()
1435+ self.assertEquals(len(conf_files), 4)
1436+ c1 = self.join_swift_dir('object-server1.conf')
1437+ c2 = self.join_swift_dir('object-server/2.conf')
1438+ c3 = self.join_swift_dir('object-server/object3.conf')
1439+ c4 = self.join_swift_dir('object-server/conf/server4.conf')
1440+ for c in [c1, c2, c3, c4]:
1441+ self.assert_(c in conf_files)
1442+ # test configs returned sorted
1443+ sorted_confs = sorted([c1, c2, c3, c4])
1444+ self.assertEquals(conf_files, sorted_confs)
1445+
1446+ # test get single numbered conf
1447+ conf_files = (
1448+ 'account-server/1.conf',
1449+ 'account-server/2.conf',
1450+ 'account-server/3.conf',
1451+ 'account-server/4.conf',
1452+ )
1453+ with temptree(conf_files) as t:
1454+ manager.SWIFT_DIR = t
1455+ server = manager.Server('account')
1456+ conf_files = server.conf_files(number=2)
1457+ self.assertEquals(len(conf_files), 1)
1458+ conf_file = conf_files[0]
1459+ self.assertEquals(conf_file,
1460+ self.join_swift_dir('account-server/2.conf'))
1461+ # test missing config number
1462+ conf_files = server.conf_files(number=5)
1463+ self.assertFalse(conf_files)
1464+
1465+ # test verbose & quiet
1466+ conf_files = (
1467+ 'auth-server.ini',
1468+ 'container-server/1.conf',
1469+ )
1470+ with temptree(conf_files) as t:
1471+ manager.SWIFT_DIR = t
1472+ old_stdout = sys.stdout
1473+ try:
1474+ with open(os.path.join(t, 'output'), 'w+') as f:
1475+ sys.stdout = f
1476+ server = manager.Server('auth')
1477+ # check warn "unable to locate"
1478+ conf_files = server.conf_files()
1479+ self.assertFalse(conf_files)
1480+ self.assert_('unable to locate' in pop_stream(f).lower())
1481+ # check quiet will silence warning
1482+ conf_files = server.conf_files(verbose=True, quiet=True)
1483+ self.assertEquals(pop_stream(f), '')
1484+ # check found config no warning
1485+ server = manager.Server('container-auditor')
1486+ conf_files = server.conf_files()
1487+ self.assertEquals(pop_stream(f), '')
1488+ # check missing config number warn "unable to locate"
1489+ conf_files = server.conf_files(number=2)
1490+ self.assert_('unable to locate' in pop_stream(f).lower())
1491+ # check verbose lists configs
1492+ conf_files = server.conf_files(number=2, verbose=True)
1493+ c1 = self.join_swift_dir('container-server/1.conf')
1494+ self.assert_(c1 in pop_stream(f))
1495+ finally:
1496+ sys.stdout = old_stdout
1497+
1498+ def test_iter_pid_files(self):
1499+ """
1500+ Server.iter_pid_files is kinda boring, test the
1501+ Server.pid_files stuff here as well
1502+ """
1503+ pid_files = (
1504+ ('proxy-server.pid', 1),
1505+ ('auth-server.pid', 'blah'),
1506+ ('object-replicator/1.pid', 11),
1507+ ('object-replicator/2.pid', 12),
1508+ )
1509+ files, contents = zip(*pid_files)
1510+ with temptree(files, contents) as t:
1511+ manager.RUN_DIR = t
1512+ server = manager.Server('proxy')
1513+ # test get one file
1514+ iter = server.iter_pid_files()
1515+ pid_file, pid = iter.next()
1516+ self.assertEquals(pid_file, self.join_run_dir('proxy-server.pid'))
1517+ self.assertEquals(pid, 1)
1518+ # ... and only one file
1519+ self.assertRaises(StopIteration, iter.next)
1520+ # test invalid value in pid file
1521+ server = manager.Server('auth')
1522+ self.assertRaises(ValueError, server.iter_pid_files().next)
1523+ # test object-server doesn't steal pids from object-replicator
1524+ server = manager.Server('object')
1525+ self.assertRaises(StopIteration, server.iter_pid_files().next)
1526+ # test multi-pid iter
1527+ server = manager.Server('object-replicator')
1528+ real_map = {
1529+ 11: self.join_run_dir('object-replicator/1.pid'),
1530+ 12: self.join_run_dir('object-replicator/2.pid'),
1531+ }
1532+ pid_map = {}
1533+ for pid_file, pid in server.iter_pid_files():
1534+ pid_map[pid] = pid_file
1535+ self.assertEquals(pid_map, real_map)
1536+
1537+ # test get pid_files by number
1538+ conf_files = (
1539+ 'object-server/1.conf',
1540+ 'object-server/2.conf',
1541+ 'object-server/3.conf',
1542+ 'object-server/4.conf',
1543+ )
1544+
1545+ pid_files = (
1546+ ('object-server/1.pid', 1),
1547+ ('object-server/2.pid', 2),
1548+ ('object-server/5.pid', 5),
1549+ )
1550+
1551+ with temptree(conf_files) as swift_dir:
1552+ manager.SWIFT_DIR = swift_dir
1553+ files, pids = zip(*pid_files)
1554+ with temptree(files, pids) as t:
1555+ manager.RUN_DIR = t
1556+ server = manager.Server('object')
1557+ # test get all pid files
1558+ real_map = {
1559+ 1: self.join_run_dir('object-server/1.pid'),
1560+ 2: self.join_run_dir('object-server/2.pid'),
1561+ 5: self.join_run_dir('object-server/5.pid'),
1562+ }
1563+ pid_map = {}
1564+ for pid_file, pid in server.iter_pid_files():
1565+ pid_map[pid] = pid_file
1566+ self.assertEquals(pid_map, real_map)
1567+ # test get pid with matching conf
1568+ pids = list(server.iter_pid_files(number=2))
1569+ self.assertEquals(len(pids), 1)
1570+ pid_file, pid = pids[0]
1571+ self.assertEquals(pid, 2)
1572+ pid_two = self.join_run_dir('object-server/2.pid')
1573+ self.assertEquals(pid_file, pid_two)
1574+ # try to iter on a pid number with a matching conf but no pid
1575+ pids = list(server.iter_pid_files(number=3))
1576+ self.assertFalse(pids)
1577+ # test get pids w/o matching conf
1578+ pids = list(server.iter_pid_files(number=5))
1579+ self.assertFalse(pids)
1580+
1581+ def test_signal_pids(self):
1582+ pid_files = (
1583+ ('proxy-server.pid', 1),
1584+ ('auth-server.pid', 2),
1585+ )
1586+ files, pids = zip(*pid_files)
1587+ with temptree(files, pids) as t:
1588+ manager.RUN_DIR = t
1589+ # mock os with both pids running
1590+ manager.os = MockOs([1, 2])
1591+ server = manager.Server('proxy')
1592+ pids = server.signal_pids(DUMMY_SIG)
1593+ self.assertEquals(len(pids), 1)
1594+ self.assert_(1 in pids)
1595+ self.assertEquals(manager.os.pid_sigs[1], [DUMMY_SIG])
1596+ # make sure other process not signaled
1597+ self.assertFalse(2 in pids)
1598+ self.assertFalse(2 in manager.os.pid_sigs)
1599+ # capture stdio
1600+ old_stdout = sys.stdout
1601+ try:
1602+ with open(os.path.join(t, 'output'), 'w+') as f:
1603+ sys.stdout = f
1604+ #test print details
1605+ pids = server.signal_pids(DUMMY_SIG)
1606+ output = pop_stream(f)
1607+ self.assert_('pid: %s' % 1 in output)
1608+ self.assert_('signal: %s' % DUMMY_SIG in output)
1609+ # test no details on signal.SIG_DFL
1610+ pids = server.signal_pids(signal.SIG_DFL)
1611+ self.assertEquals(pop_stream(f), '')
1612+ # reset mock os so only the other server is running
1613+ manager.os = MockOs([2])
1614+ # test pid not running
1615+ pids = server.signal_pids(signal.SIG_DFL)
1616+ self.assert_(1 not in pids)
1617+ self.assert_(1 not in manager.os.pid_sigs)
1618+ # test remove stale pid file
1619+ self.assertFalse(os.path.exists(
1620+ self.join_run_dir('proxy-server.pid')))
1621+ # reset mock os with no running pids
1622+ manager.os = MockOs([])
1623+ server = manager.Server('auth')
1624+ # test verbose warns on removing pid file
1625+ pids = server.signal_pids(signal.SIG_DFL, verbose=True)
1626+ output = pop_stream(f)
1627+ self.assert_('stale pid' in output.lower())
1628+ auth_pid = self.join_run_dir('auth-server.pid')
1629+ self.assert_(auth_pid in output)
1630+ finally:
1631+ sys.stdout = old_stdout
1632+
1633+ def test_get_running_pids(self):
1634+ # test only gets running pids
1635+ pid_files = (
1636+ ('test-server1.pid', 1),
1637+ ('test-server2.pid', 2),
1638+ )
1639+ with temptree(*zip(*pid_files)) as t:
1640+ manager.RUN_DIR = t
1641+ server = manager.Server('test-server')
1642+ # mock os, only pid '1' is running
1643+ manager.os = MockOs([1])
1644+ running_pids = server.get_running_pids()
1645+ self.assertEquals(len(running_pids), 1)
1646+ self.assert_(1 in running_pids)
1647+ self.assert_(2 not in running_pids)
1648+ # test persistant running pid files
1649+ self.assert_(os.path.exists(os.path.join(t, 'test-server1.pid')))
1650+ # test clean up stale pids
1651+ pid_two = self.join_swift_dir('test-server2.pid')
1652+ self.assertFalse(os.path.exists(pid_two))
1653+ # reset mock os, no pids running
1654+ manager.os = MockOs([])
1655+ running_pids = server.get_running_pids()
1656+ self.assertFalse(running_pids)
1657+ # and now all pid files are cleaned out
1658+ pid_one = self.join_run_dir('test-server1.pid')
1659+ self.assertFalse(os.path.exists(pid_one))
1660+ all_pids = os.listdir(t)
1661+ self.assertEquals(len(all_pids), 0)
1662+
1663+ # test only get pids for right server
1664+ pid_files = (
1665+ ('thing-doer.pid', 1),
1666+ ('thing-sayer.pid', 2),
1667+ ('other-doer.pid', 3),
1668+ ('other-sayer.pid', 4),
1669+ )
1670+ files, pids = zip(*pid_files)
1671+ with temptree(files, pids) as t:
1672+ manager.RUN_DIR = t
1673+ # all pids are running
1674+ manager.os = MockOs(pids)
1675+ server = manager.Server('thing-doer')
1676+ running_pids = server.get_running_pids()
1677+ # only thing-doer.pid, 1
1678+ self.assertEquals(len(running_pids), 1)
1679+ self.assert_(1 in running_pids)
1680+ # no other pids returned
1681+ for n in (2, 3, 4):
1682+ self.assert_(n not in running_pids)
1683+ # assert stale pids for other servers ignored
1684+ manager.os = MockOs([1]) # only thing-doer is running
1685+ running_pids = server.get_running_pids()
1686+ for f in ('thing-sayer.pid', 'other-doer.pid', 'other-sayer.pid'):
1687+ # other server pid files persist
1688+ self.assert_(os.path.exists, os.path.join(t, f))
1689+ # verify that servers are in fact not running
1690+ for server_name in ('thing-sayer', 'other-doer', 'other-sayer'):
1691+ server = manager.Server(server_name)
1692+ running_pids = server.get_running_pids()
1693+ self.assertFalse(running_pids)
1694+ # and now all OTHER pid files are cleaned out
1695+ all_pids = os.listdir(t)
1696+ self.assertEquals(len(all_pids), 1)
1697+ self.assert_(os.path.exists(os.path.join(t, 'thing-doer.pid')))
1698+
1699+ def test_kill_running_pids(self):
1700+ pid_files = (
1701+ ('object-server.pid', 1),
1702+ ('object-replicator1.pid', 11),
1703+ ('object-replicator2.pid', 12),
1704+ )
1705+ files, running_pids = zip(*pid_files)
1706+ with temptree(files, running_pids) as t:
1707+ manager.RUN_DIR = t
1708+ server = manager.Server('object')
1709+ # test no servers running
1710+ manager.os = MockOs([])
1711+ pids = server.kill_running_pids()
1712+ self.assertFalse(pids, pids)
1713+ files, running_pids = zip(*pid_files)
1714+ with temptree(files, running_pids) as t:
1715+ manager.RUN_DIR = t
1716+ # start up pid
1717+ manager.os = MockOs([1])
1718+ # test kill one pid
1719+ pids = server.kill_running_pids()
1720+ self.assertEquals(len(pids), 1)
1721+ self.assert_(1 in pids)
1722+ self.assertEquals(manager.os.pid_sigs[1], [signal.SIGTERM])
1723+ # reset os mock
1724+ manager.os = MockOs([1])
1725+ # test shutdown
1726+ self.assert_('object-server' in
1727+ manager.GRACEFUL_SHUTDOWN_SERVERS)
1728+ pids = server.kill_running_pids(graceful=True)
1729+ self.assertEquals(len(pids), 1)
1730+ self.assert_(1 in pids)
1731+ self.assertEquals(manager.os.pid_sigs[1], [signal.SIGHUP])
1732+ # start up other servers
1733+ manager.os = MockOs([11, 12])
1734+ # test multi server kill & ignore graceful on unsupported server
1735+ self.assertFalse('object-replicator' in
1736+ manager.GRACEFUL_SHUTDOWN_SERVERS)
1737+ server = manager.Server('object-replicator')
1738+ pids = server.kill_running_pids(graceful=True)
1739+ self.assertEquals(len(pids), 2)
1740+ for pid in (11, 12):
1741+ self.assert_(pid in pids)
1742+ self.assertEquals(manager.os.pid_sigs[pid],
1743+ [signal.SIGTERM])
1744+ # and the other pid is of course not signaled
1745+ self.assert_(1 not in manager.os.pid_sigs)
1746+
1747+ def test_status(self):
1748+ conf_files = (
1749+ 'test-server/1.conf',
1750+ 'test-server/2.conf',
1751+ 'test-server/3.conf',
1752+ 'test-server/4.conf',
1753+ )
1754+
1755+ pid_files = (
1756+ ('test-server/1.pid', 1),
1757+ ('test-server/2.pid', 2),
1758+ ('test-server/3.pid', 3),
1759+ ('test-server/4.pid', 4),
1760+ )
1761+
1762+ with temptree(conf_files) as swift_dir:
1763+ manager.SWIFT_DIR = swift_dir
1764+ files, pids = zip(*pid_files)
1765+ with temptree(files, pids) as t:
1766+ manager.RUN_DIR = t
1767+ # setup running servers
1768+ server = manager.Server('test')
1769+ # capture stdio
1770+ old_stdout = sys.stdout
1771+ try:
1772+ with open(os.path.join(t, 'output'), 'w+') as f:
1773+ sys.stdout = f
1774+ # test status for all running
1775+ manager.os = MockOs(pids)
1776+ self.assertEquals(server.status(), 0)
1777+ output = pop_stream(f).strip().splitlines()
1778+ self.assertEquals(len(output), 4)
1779+ for line in output:
1780+ self.assert_('test-server running' in line)
1781+ # test get single server by number
1782+ self.assertEquals(server.status(number=4), 0)
1783+ output = pop_stream(f).strip().splitlines()
1784+ self.assertEquals(len(output), 1)
1785+ line = output[0]
1786+ self.assert_('test-server running' in line)
1787+ conf_four = self.join_swift_dir(conf_files[3])
1788+ self.assert_('4 - %s' % conf_four in line)
1789+ # test some servers not running
1790+ manager.os = MockOs([1, 2, 3])
1791+ self.assertEquals(server.status(), 0)
1792+ output = pop_stream(f).strip().splitlines()
1793+ self.assertEquals(len(output), 3)
1794+ for line in output:
1795+ self.assert_('test-server running' in line)
1796+ # test single server not running
1797+ manager.os = MockOs([1, 2])
1798+ self.assertEquals(server.status(number=3), 1)
1799+ output = pop_stream(f).strip().splitlines()
1800+ self.assertEquals(len(output), 1)
1801+ line = output[0]
1802+ self.assert_('not running' in line)
1803+ conf_three = self.join_swift_dir(conf_files[2])
1804+ self.assert_(conf_three in line)
1805+ # test no running pids
1806+ manager.os = MockOs([])
1807+ self.assertEquals(server.status(), 1)
1808+ output = pop_stream(f).lower()
1809+ self.assert_('no test-server running' in output)
1810+ # test use provided pids
1811+ pids = {
1812+ 1: '1.pid',
1813+ 2: '2.pid',
1814+ }
1815+ # shouldn't call get_running_pids
1816+ called = []
1817+
1818+ def mock(*args, **kwargs):
1819+ called.append(True)
1820+ server.get_running_pids = mock
1821+ status = server.status(pids=pids)
1822+ self.assertEquals(status, 0)
1823+ self.assertFalse(called)
1824+ output = pop_stream(f).strip().splitlines()
1825+ self.assertEquals(len(output), 2)
1826+ for line in output:
1827+ self.assert_('test-server running' in line)
1828+ finally:
1829+ sys.stdout = old_stdout
1830+
1831+ def test_spawn(self):
1832+
1833+ # mocks
1834+ class MockProcess():
1835+
1836+ NOTHING = 'default besides None'
1837+ STDOUT = 'stdout'
1838+ PIPE = 'pipe'
1839+
1840+ def __init__(self, pids=None):
1841+ if pids is None:
1842+ pids = []
1843+ self.pids = (p for p in pids)
1844+
1845+ def Popen(self, args, **kwargs):
1846+ return MockProc(self.pids.next(), args, **kwargs)
1847+
1848+ class MockProc():
1849+
1850+ def __init__(self, pid, args, stdout=MockProcess.NOTHING,
1851+ stderr=MockProcess.NOTHING):
1852+ self.pid = pid
1853+ self.args = args
1854+ self.stdout = stdout
1855+ if stderr == MockProcess.STDOUT:
1856+ self.stderr = self.stdout
1857+ else:
1858+ self.stderr = stderr
1859+
1860+ # setup running servers
1861+ server = manager.Server('test')
1862+
1863+ with temptree(['test-server.conf']) as swift_dir:
1864+ manager.SWIFT_DIR = swift_dir
1865+ with temptree([]) as t:
1866+ manager.RUN_DIR = t
1867+ old_subprocess = manager.subprocess
1868+ try:
1869+ # test single server process calls spawn once
1870+ manager.subprocess = MockProcess([1])
1871+ conf_file = self.join_swift_dir('test-server.conf')
1872+ # spawn server no kwargs
1873+ server.spawn(conf_file)
1874+ # test pid file
1875+ pid_file = self.join_run_dir('test-server.pid')
1876+ self.assert_(os.path.exists(pid_file))
1877+ pid_on_disk = int(open(pid_file).read().strip())
1878+ self.assertEquals(pid_on_disk, 1)
1879+ # assert procs args
1880+ self.assert_(server.procs)
1881+ self.assertEquals(len(server.procs), 1)
1882+ proc = server.procs[0]
1883+ expected_args = [
1884+ 'swift-test-server',
1885+ conf_file,
1886+ ]
1887+ self.assertEquals(proc.args, expected_args)
1888+ # assert stdout is piped
1889+ self.assertEquals(proc.stdout, MockProcess.PIPE)
1890+ self.assertEquals(proc.stderr, proc.stdout)
1891+ # test multi server process calls spawn multiple times
1892+ manager.subprocess = MockProcess([11, 12, 13, 14])
1893+ conf1 = self.join_swift_dir('test-server/1.conf')
1894+ conf2 = self.join_swift_dir('test-server/2.conf')
1895+ conf3 = self.join_swift_dir('test-server/3.conf')
1896+ conf4 = self.join_swift_dir('test-server/4.conf')
1897+ server = manager.Server('test')
1898+ # test server run once
1899+ server.spawn(conf1, once=True)
1900+ self.assert_(server.procs)
1901+ self.assertEquals(len(server.procs), 1)
1902+ proc = server.procs[0]
1903+ expected_args = ['swift-test-server', conf1, 'once']
1904+ # assert stdout is piped
1905+ self.assertEquals(proc.stdout, MockProcess.PIPE)
1906+ self.assertEquals(proc.stderr, proc.stdout)
1907+ # test server not daemon
1908+ server.spawn(conf2, daemon=False)
1909+ self.assert_(server.procs)
1910+ self.assertEquals(len(server.procs), 2)
1911+ proc = server.procs[1]
1912+ expected_args = ['swift-test-server', conf2, 'verbose']
1913+ self.assertEquals(proc.args, expected_args)
1914+ # assert stdout is not changed
1915+ self.assertEquals(proc.stdout, None)
1916+ self.assertEquals(proc.stderr, None)
1917+ # test server wait
1918+ server.spawn(conf3, wait=False)
1919+ self.assert_(server.procs)
1920+ self.assertEquals(len(server.procs), 3)
1921+ proc = server.procs[2]
1922+ # assert stdout is /dev/null
1923+ self.assert_(isinstance(proc.stdout, file))
1924+ self.assertEquals(proc.stdout.name, os.devnull)
1925+ self.assertEquals(proc.stdout.mode, 'w+b')
1926+ self.assertEquals(proc.stderr, proc.stdout)
1927+ # test not daemon over-rides wait
1928+ server.spawn(conf4, wait=False, daemon=False, once=True)
1929+ self.assert_(server.procs)
1930+ self.assertEquals(len(server.procs), 4)
1931+ proc = server.procs[3]
1932+ expected_args = ['swift-test-server', conf4, 'once',
1933+ 'verbose']
1934+ self.assertEquals(proc.args, expected_args)
1935+ # daemon behavior should trump wait, once shouldn't matter
1936+ self.assertEquals(proc.stdout, None)
1937+ self.assertEquals(proc.stderr, None)
1938+ # assert pids
1939+ for i, proc in enumerate(server.procs):
1940+ pid_file = self.join_run_dir('test-server/%d.pid' %
1941+ (i + 1))
1942+ pid_on_disk = int(open(pid_file).read().strip())
1943+ self.assertEquals(pid_on_disk, proc.pid)
1944+ finally:
1945+ manager.subprocess = old_subprocess
1946+
1947+ def test_wait(self):
1948+ server = manager.Server('test')
1949+ self.assertEquals(server.wait(), 0)
1950+
1951+ class MockProcess(Thread):
1952+ def __init__(self, delay=0.1, fail_to_start=False):
1953+ Thread.__init__(self)
1954+ # setup pipe
1955+ rfd, wfd = os.pipe()
1956+ # subprocess connection to read stdout
1957+ self.stdout = os.fdopen(rfd)
1958+ # real process connection to write stdout
1959+ self._stdout = os.fdopen(wfd, 'w')
1960+ self.delay = delay
1961+ self.finished = False
1962+ self.returncode = None
1963+ if fail_to_start:
1964+ self._returncode = 1
1965+ self.run = self.fail
1966+ else:
1967+ self._returncode = 0
1968+
1969+ def __enter__(self):
1970+ self.start()
1971+ return self
1972+
1973+ def __exit__(self, *args):
1974+ if self.isAlive():
1975+ self.join()
1976+
1977+ def close_stdout(self):
1978+ self._stdout.flush()
1979+ with open(os.devnull, 'wb') as nullfile:
1980+ try:
1981+ os.dup2(nullfile.fileno(), self._stdout.fileno())
1982+ except OSError:
1983+ pass
1984+
1985+ def fail(self):
1986+ print >>self._stdout, 'mock process started'
1987+ sleep(self.delay) # perform setup processing
1988+ print >>self._stdout, 'mock process failed to start'
1989+ self.close_stdout()
1990+
1991+ def communicate(self):
1992+ self.returncode = self._returncode
1993+
1994+ def run(self):
1995+ print >>self._stdout, 'mock process started'
1996+ sleep(self.delay) # perform setup processing
1997+ print >>self._stdout, 'setup complete!'
1998+ self.close_stdout()
1999+ sleep(self.delay) # do some more processing
2000+ print >>self._stdout, 'mock process finished'
2001+ self.finished = True
2002+
2003+ with temptree([]) as t:
2004+ old_stdout = sys.stdout
2005+ try:
2006+ with open(os.path.join(t, 'output'), 'w+') as f:
2007+ # acctually capture the read stdout (for prints)
2008+ sys.stdout = f
2009+ # test closing pipe in subprocess unblocks read
2010+ with MockProcess() as proc:
2011+ server.procs = [proc]
2012+ status = server.wait()
2013+ self.assertEquals(status, 0)
2014+ # wait should return as soon as stdout is closed
2015+ self.assert_(proc.isAlive())
2016+ self.assertFalse(proc.finished)
2017+ self.assert_(proc.finished) # make sure it did finish...
2018+ # test output kwarg prints subprocess output
2019+ with MockProcess() as proc:
2020+ server.procs = [proc]
2021+ status = server.wait(output=True)
2022+ output = pop_stream(f)
2023+ self.assert_('mock process started' in output)
2024+ self.assert_('setup complete' in output)
2025+ # make sure we don't get prints after stdout was closed
2026+ self.assert_('mock process finished' not in output)
2027+ # test process which fails to start
2028+ with MockProcess(fail_to_start=True) as proc:
2029+ server.procs = [proc]
2030+ status = server.wait()
2031+ self.assertEquals(status, 1)
2032+ self.assert_('failed' in pop_stream(f))
2033+ # test multiple procs
2034+ procs = [MockProcess() for i in range(3)]
2035+ for proc in procs:
2036+ proc.start()
2037+ server.procs = procs
2038+ status = server.wait()
2039+ self.assertEquals(status, 0)
2040+ for proc in procs:
2041+ self.assert_(proc.isAlive())
2042+ for proc in procs:
2043+ proc.join()
2044+ finally:
2045+ sys.stdout = old_stdout
2046+
2047+ def test_interact(self):
2048+ class MockProcess():
2049+
2050+ def __init__(self, fail=False):
2051+ self.returncode = None
2052+ if fail:
2053+ self._returncode = 1
2054+ else:
2055+ self._returncode = 0
2056+
2057+ def communicate(self):
2058+ self.returncode = self._returncode
2059+ return '', ''
2060+
2061+ server = manager.Server('test')
2062+ server.procs = [MockProcess()]
2063+ self.assertEquals(server.interact(), 0)
2064+ server.procs = [MockProcess(fail=True)]
2065+ self.assertEquals(server.interact(), 1)
2066+ procs = []
2067+ for fail in (False, True, True):
2068+ procs.append(MockProcess(fail=fail))
2069+ server.procs = procs
2070+ self.assert_(server.interact() > 0)
2071+
2072+ def test_launch(self):
2073+ # stubs
2074+ conf_files = (
2075+ 'proxy-server.conf',
2076+ 'auth-server.conf',
2077+ 'object-server/1.conf',
2078+ 'object-server/2.conf',
2079+ 'object-server/3.conf',
2080+ 'object-server/4.conf',
2081+ )
2082+ pid_files = (
2083+ ('proxy-server.pid', 1),
2084+ ('proxy-server/2.pid', 2),
2085+ )
2086+
2087+ #mocks
2088+ class MockSpawn():
2089+
2090+ def __init__(self, pids=None):
2091+ self.conf_files = []
2092+ self.kwargs = []
2093+ if not pids:
2094+ def one_forever():
2095+ while True:
2096+ yield 1
2097+ self.pids = one_forever()
2098+ else:
2099+ self.pids = (x for x in pids)
2100+
2101+ def __call__(self, conf_file, **kwargs):
2102+ self.conf_files.append(conf_file)
2103+ self.kwargs.append(kwargs)
2104+ rv = self.pids.next()
2105+ if isinstance(rv, Exception):
2106+ raise rv
2107+ else:
2108+ return rv
2109+
2110+ with temptree(conf_files) as swift_dir:
2111+ manager.SWIFT_DIR = swift_dir
2112+ files, pids = zip(*pid_files)
2113+ with temptree(files, pids) as t:
2114+ manager.RUN_DIR = t
2115+ old_stdout = sys.stdout
2116+ try:
2117+ with open(os.path.join(t, 'output'), 'w+') as f:
2118+ sys.stdout = f
2119+ # can't start server w/o an conf
2120+ server = manager.Server('test')
2121+ self.assertFalse(server.launch())
2122+ # start mock os running all pids
2123+ manager.os = MockOs(pids)
2124+ server = manager.Server('proxy')
2125+ # can't start server if it's already running
2126+ self.assertFalse(server.launch())
2127+ output = pop_stream(f)
2128+ self.assert_('running' in output)
2129+ conf_file = self.join_swift_dir('proxy-server.conf')
2130+ self.assert_(conf_file in output)
2131+ pid_file = self.join_run_dir('proxy-server/2.pid')
2132+ self.assert_(pid_file in output)
2133+ self.assert_('already started' in output)
2134+ # no running pids
2135+ manager.os = MockOs([])
2136+ # test ignore once for non-start-once server
2137+ mock_spawn = MockSpawn([1])
2138+ server.spawn = mock_spawn
2139+ conf_file = self.join_swift_dir('proxy-server.conf')
2140+ expected = {
2141+ 1: conf_file,
2142+ }
2143+ self.assertEquals(server.launch(once=True), expected)
2144+ self.assertEquals(mock_spawn.conf_files, [conf_file])
2145+ expected = {
2146+ 'once': False,
2147+ }
2148+ self.assertEquals(mock_spawn.kwargs, [expected])
2149+ output = pop_stream(f)
2150+ self.assert_('Starting' in output)
2151+ self.assert_('once' not in output)
2152+ # test multi-server kwarg once
2153+ server = manager.Server('object-replicator')
2154+ mock_spawn = MockSpawn([1, 2, 3, 4])
2155+ server.spawn = mock_spawn
2156+ conf1 = self.join_swift_dir('object-server/1.conf')
2157+ conf2 = self.join_swift_dir('object-server/2.conf')
2158+ conf3 = self.join_swift_dir('object-server/3.conf')
2159+ conf4 = self.join_swift_dir('object-server/4.conf')
2160+ expected = {
2161+ 1: conf1,
2162+ 2: conf2,
2163+ 3: conf3,
2164+ 4: conf4,
2165+ }
2166+ self.assertEquals(server.launch(once=True), expected)
2167+ self.assertEquals(mock_spawn.conf_files, [conf1, conf2,
2168+ conf3, conf4])
2169+ expected = {
2170+ 'once': True,
2171+ }
2172+ self.assertEquals(len(mock_spawn.kwargs), 4)
2173+ for kwargs in mock_spawn.kwargs:
2174+ self.assertEquals(kwargs, expected)
2175+ # test number kwarg
2176+ mock_spawn = MockSpawn([4])
2177+ server.spawn = mock_spawn
2178+ expected = {
2179+ 4: conf4,
2180+ }
2181+ self.assertEquals(server.launch(number=4), expected)
2182+ self.assertEquals(mock_spawn.conf_files, [conf4])
2183+ expected = {
2184+ 'number': 4
2185+ }
2186+ self.assertEquals(mock_spawn.kwargs, [expected])
2187+ # test cmd does not exist
2188+ server = manager.Server('auth')
2189+ mock_spawn = MockSpawn([OSError(errno.ENOENT, 'blah')])
2190+ server.spawn = mock_spawn
2191+ self.assertEquals(server.launch(), {})
2192+ self.assert_('swift-auth-server does not exist' in
2193+ pop_stream(f))
2194+ finally:
2195+ sys.stdout = old_stdout
2196+
2197+ def test_stop(self):
2198+ conf_files = (
2199+ 'account-server/1.conf',
2200+ 'account-server/2.conf',
2201+ 'account-server/3.conf',
2202+ 'account-server/4.conf',
2203+ )
2204+ pid_files = (
2205+ ('account-reaper/1.pid', 1),
2206+ ('account-reaper/2.pid', 2),
2207+ ('account-reaper/3.pid', 3),
2208+ ('account-reaper/4.pid', 4),
2209+ )
2210+
2211+ with temptree(conf_files) as swift_dir:
2212+ manager.SWIFT_DIR = swift_dir
2213+ files, pids = zip(*pid_files)
2214+ with temptree(files, pids) as t:
2215+ manager.RUN_DIR = t
2216+ # start all pids in mock os
2217+ manager.os = MockOs(pids)
2218+ server = manager.Server('account-reaper')
2219+ # test kill all running pids
2220+ pids = server.stop()
2221+ self.assertEquals(len(pids), 4)
2222+ for pid in (1, 2, 3, 4):
2223+ self.assert_(pid in pids)
2224+ self.assertEquals(manager.os.pid_sigs[pid],
2225+ [signal.SIGTERM])
2226+ conf1 = self.join_swift_dir('account-reaper/1.conf')
2227+ conf2 = self.join_swift_dir('account-reaper/2.conf')
2228+ conf3 = self.join_swift_dir('account-reaper/3.conf')
2229+ conf4 = self.join_swift_dir('account-reaper/4.conf')
2230+ # reset mock os with only 2 running pids
2231+ manager.os = MockOs([3, 4])
2232+ pids = server.stop()
2233+ self.assertEquals(len(pids), 2)
2234+ for pid in (3, 4):
2235+ self.assert_(pid in pids)
2236+ self.assertEquals(manager.os.pid_sigs[pid],
2237+ [signal.SIGTERM])
2238+ self.assertFalse(os.path.exists(conf1))
2239+ self.assertFalse(os.path.exists(conf2))
2240+ # test number kwarg
2241+ manager.os = MockOs([3, 4])
2242+ pids = server.stop(number=3)
2243+ self.assertEquals(len(pids), 1)
2244+ expected = {
2245+ 3: conf3,
2246+ }
2247+ self.assert_(pids, expected)
2248+ self.assertEquals(manager.os.pid_sigs[3], [signal.SIGTERM])
2249+ self.assertFalse(os.path.exists(conf4))
2250+ self.assertFalse(os.path.exists(conf3))
2251+
2252+
2253+class TestManager(unittest.TestCase):
2254+
2255+ def test_create(self):
2256+ m = manager.Manager(['test'])
2257+ self.assertEquals(len(m.servers), 1)
2258+ server = m.servers.pop()
2259+ self.assert_(isinstance(server, manager.Server))
2260+ self.assertEquals(server.server, 'test-server')
2261+ # test multi-server and simple dedupe
2262+ servers = ['object-replicator', 'object-auditor', 'object-replicator']
2263+ m = manager.Manager(servers)
2264+ self.assertEquals(len(m.servers), 2)
2265+ for server in m.servers:
2266+ self.assert_(server.server in servers)
2267+ # test all
2268+ m = manager.Manager(['all'])
2269+ self.assertEquals(len(m.servers), len(manager.ALL_SERVERS))
2270+ for server in m.servers:
2271+ self.assert_(server.server in manager.ALL_SERVERS)
2272+ # test main
2273+ m = manager.Manager(['main'])
2274+ self.assertEquals(len(m.servers), len(manager.MAIN_SERVERS))
2275+ for server in m.servers:
2276+ self.assert_(server.server in manager.MAIN_SERVERS)
2277+ # test rest
2278+ m = manager.Manager(['rest'])
2279+ self.assertEquals(len(m.servers), len(manager.REST_SERVERS))
2280+ for server in m.servers:
2281+ self.assert_(server.server in manager.REST_SERVERS)
2282+ # test main + rest == all
2283+ m = manager.Manager(['main', 'rest'])
2284+ self.assertEquals(len(m.servers), len(manager.ALL_SERVERS))
2285+ for server in m.servers:
2286+ self.assert_(server.server in manager.ALL_SERVERS)
2287+ # test dedupe
2288+ m = manager.Manager(['main', 'rest', 'proxy', 'object',
2289+ 'container', 'account'])
2290+ self.assertEquals(len(m.servers), len(manager.ALL_SERVERS))
2291+ for server in m.servers:
2292+ self.assert_(server.server in manager.ALL_SERVERS)
2293+ # test glob
2294+ m = manager.Manager(['object-*'])
2295+ object_servers = [s for s in manager.ALL_SERVERS if
2296+ s.startswith('object')]
2297+ self.assertEquals(len(m.servers), len(object_servers))
2298+ for s in m.servers:
2299+ self.assert_(str(s) in object_servers)
2300+ m = manager.Manager(['*-replicator'])
2301+ replicators = [s for s in manager.ALL_SERVERS if
2302+ s.endswith('replicator')]
2303+ for s in m.servers:
2304+ self.assert_(str(s) in replicators)
2305+
2306+ def test_status(self):
2307+ class MockServer():
2308+
2309+ def __init__(self, server):
2310+ self.server = server
2311+ self.called_kwargs = []
2312+
2313+ def status(self, **kwargs):
2314+ self.called_kwargs.append(kwargs)
2315+ if 'error' in self.server:
2316+ return 1
2317+ else:
2318+ return 0
2319+
2320+ old_server_class = manager.Server
2321+ try:
2322+ manager.Server = MockServer
2323+ m = manager.Manager(['test'])
2324+ status = m.status()
2325+ self.assertEquals(status, 0)
2326+ m = manager.Manager(['error'])
2327+ status = m.status()
2328+ self.assertEquals(status, 1)
2329+ # test multi-server
2330+ m = manager.Manager(['test', 'error'])
2331+ kwargs = {'key': 'value'}
2332+ status = m.status(**kwargs)
2333+ self.assertEquals(status, 1)
2334+ for server in m.servers:
2335+ self.assertEquals(server.called_kwargs, [kwargs])
2336+ finally:
2337+ manager.Server = old_server_class
2338+
2339+ def test_start(self):
2340+ def mock_setup_env():
2341+ getattr(mock_setup_env, 'called', []).append(True)
2342+
2343+ class MockServer():
2344+ def __init__(self, server):
2345+ self.server = server
2346+ self.called = defaultdict(list)
2347+
2348+ def launch(self, **kwargs):
2349+ self.called['launch'].append(kwargs)
2350+
2351+ def wait(self, **kwargs):
2352+ self.called['wait'].append(kwargs)
2353+ return int('error' in self.server)
2354+
2355+ def stop(self, **kwargs):
2356+ self.called['stop'].append(kwargs)
2357+
2358+ def interact(self, **kwargs):
2359+ self.called['interact'].append(kwargs)
2360+ if 'raise' in self.server:
2361+ raise KeyboardInterrupt
2362+ elif 'error' in self.server:
2363+ return 1
2364+ else:
2365+ return 0
2366+
2367+ old_setup_env = manager.setup_env
2368+ old_swift_server = manager.Server
2369+ try:
2370+ manager.setup_env = mock_setup_env
2371+ manager.Server = MockServer
2372+
2373+ # test no errors on launch
2374+ m = manager.Manager(['proxy'])
2375+ status = m.start()
2376+ self.assertEquals(status, 0)
2377+ for server in m.servers:
2378+ self.assertEquals(server.called['launch'], [{}])
2379+
2380+ # test error on launch
2381+ m = manager.Manager(['proxy', 'error'])
2382+ status = m.start()
2383+ self.assertEquals(status, 1)
2384+ for server in m.servers:
2385+ self.assertEquals(server.called['launch'], [{}])
2386+ self.assertEquals(server.called['wait'], [{}])
2387+
2388+ # test interact
2389+ m = manager.Manager(['proxy', 'error'])
2390+ kwargs = {'daemon': False}
2391+ status = m.start(**kwargs)
2392+ self.assertEquals(status, 1)
2393+ for server in m.servers:
2394+ self.assertEquals(server.called['launch'], [kwargs])
2395+ self.assertEquals(server.called['interact'], [kwargs])
2396+ m = manager.Manager(['raise'])
2397+ kwargs = {'daemon': False}
2398+ status = m.start(**kwargs)
2399+
2400+ finally:
2401+ manager.setup_env = old_setup_env
2402+ manager.Server = old_swift_server
2403+
2404+ def test_no_wait(self):
2405+ class MockServer():
2406+ def __init__(self, server):
2407+ self.server = server
2408+ self.called = defaultdict(list)
2409+
2410+ def launch(self, **kwargs):
2411+ self.called['launch'].append(kwargs)
2412+
2413+ def wait(self, **kwargs):
2414+ self.called['wait'].append(kwargs)
2415+ return int('error' in self.server)
2416+
2417+ orig_swift_server = manager.Server
2418+ try:
2419+ manager.Server = MockServer
2420+ # test success
2421+ init = manager.Manager(['proxy'])
2422+ status = init.no_wait()
2423+ self.assertEquals(status, 0)
2424+ for server in init.servers:
2425+ self.assertEquals(len(server.called['launch']), 1)
2426+ called_kwargs = server.called['launch'][0]
2427+ self.assertFalse(called_kwargs['wait'])
2428+ self.assertFalse(server.called['wait'])
2429+ # test no errocode status even on error
2430+ init = manager.Manager(['error'])
2431+ status = init.no_wait()
2432+ self.assertEquals(status, 0)
2433+ for server in init.servers:
2434+ self.assertEquals(len(server.called['launch']), 1)
2435+ called_kwargs = server.called['launch'][0]
2436+ self.assert_('wait' in called_kwargs)
2437+ self.assertFalse(called_kwargs['wait'])
2438+ self.assertFalse(server.called['wait'])
2439+ # test wait with once option
2440+ init = manager.Manager(['updater', 'replicator-error'])
2441+ status = init.no_wait(once=True)
2442+ self.assertEquals(status, 0)
2443+ for server in init.servers:
2444+ self.assertEquals(len(server.called['launch']), 1)
2445+ called_kwargs = server.called['launch'][0]
2446+ self.assert_('wait' in called_kwargs)
2447+ self.assertFalse(called_kwargs['wait'])
2448+ self.assert_('once' in called_kwargs)
2449+ self.assert_(called_kwargs['once'])
2450+ self.assertFalse(server.called['wait'])
2451+ finally:
2452+ manager.Server = orig_swift_server
2453+
2454+ def test_no_daemon(self):
2455+ class MockServer():
2456+
2457+ def __init__(self, server):
2458+ self.server = server
2459+ self.called = defaultdict(list)
2460+
2461+ def launch(self, **kwargs):
2462+ self.called['launch'].append(kwargs)
2463+
2464+ def interact(self, **kwargs):
2465+ self.called['interact'].append(kwargs)
2466+ return int('error' in self.server)
2467+
2468+ orig_swift_server = manager.Server
2469+ try:
2470+ manager.Server = MockServer
2471+ # test success
2472+ init = manager.Manager(['proxy'])
2473+ stats = init.no_daemon()
2474+ self.assertEquals(stats, 0)
2475+ # test error
2476+ init = manager.Manager(['proxy', 'object-error'])
2477+ stats = init.no_daemon()
2478+ self.assertEquals(stats, 1)
2479+ # test once
2480+ init = manager.Manager(['proxy', 'object-error'])
2481+ stats = init.no_daemon()
2482+ for server in init.servers:
2483+ self.assertEquals(len(server.called['launch']), 1)
2484+ self.assertEquals(len(server.called['wait']), 0)
2485+ self.assertEquals(len(server.called['interact']), 1)
2486+ finally:
2487+ manager.Server = orig_swift_server
2488+
2489+ def test_once(self):
2490+ class MockServer():
2491+
2492+ def __init__(self, server):
2493+ self.server = server
2494+ self.called = defaultdict(list)
2495+
2496+ def wait(self, **kwargs):
2497+ self.called['wait'].append(kwargs)
2498+ if 'error' in self.server:
2499+ return 1
2500+ else:
2501+ return 0
2502+
2503+ def launch(self, **kwargs):
2504+ return self.called['launch'].append(kwargs)
2505+
2506+ orig_swift_server = manager.Server
2507+ try:
2508+ manager.Server = MockServer
2509+ # test no errors
2510+ init = manager.Manager(['account-reaper'])
2511+ status = init.once()
2512+ self.assertEquals(status, 0)
2513+ # test error code on error
2514+ init = manager.Manager(['error-reaper'])
2515+ status = init.once()
2516+ self.assertEquals(status, 1)
2517+ for server in init.servers:
2518+ self.assertEquals(len(server.called['launch']), 1)
2519+ called_kwargs = server.called['launch'][0]
2520+ self.assertEquals(called_kwargs, {'once': True})
2521+ self.assertEquals(len(server.called['wait']), 1)
2522+ self.assertEquals(len(server.called['interact']), 0)
2523+ finally:
2524+ manager.Server = orig_swift_server
2525+
2526+ def test_stop(self):
2527+ class MockServerFactory():
2528+ class MockServer():
2529+ def __init__(self, pids):
2530+ self.pids = pids
2531+
2532+ def stop(self, **kwargs):
2533+ return self.pids
2534+
2535+ def __init__(self, server_pids):
2536+ self.server_pids = server_pids
2537+
2538+ def __call__(self, server):
2539+ return MockServerFactory.MockServer(self.server_pids[server])
2540+
2541+ def mock_watch_server_pids(server_pids, **kwargs):
2542+ for server, pids in server_pids.items():
2543+ for pid in pids:
2544+ if pid is None:
2545+ continue
2546+ yield server, pid
2547+
2548+ _orig_server = manager.Server
2549+ _orig_watch_server_pids = manager.watch_server_pids
2550+ try:
2551+ manager.watch_server_pids = mock_watch_server_pids
2552+ # test stop one server
2553+ server_pids = {
2554+ 'test': [1]
2555+ }
2556+ manager.Server = MockServerFactory(server_pids)
2557+ m = manager.Manager(['test'])
2558+ status = m.stop()
2559+ self.assertEquals(status, 0)
2560+ # test not running
2561+ server_pids = {
2562+ 'test': []
2563+ }
2564+ manager.Server = MockServerFactory(server_pids)
2565+ m = manager.Manager(['test'])
2566+ status = m.stop()
2567+ self.assertEquals(status, 1)
2568+ # test won't die
2569+ server_pids = {
2570+ 'test': [None]
2571+ }
2572+ manager.Server = MockServerFactory(server_pids)
2573+ m = manager.Manager(['test'])
2574+ status = m.stop()
2575+ self.assertEquals(status, 1)
2576+
2577+ finally:
2578+ manager.Server = _orig_server
2579+ manager.watch_server_pids = _orig_watch_server_pids
2580+
2581+ # TODO: more tests
2582+ def test_shutdown(self):
2583+ m = manager.Manager(['test'])
2584+ m.stop_was_called = False
2585+
2586+ def mock_stop(*args, **kwargs):
2587+ m.stop_was_called = True
2588+ expected = {'graceful': True}
2589+ self.assertEquals(kwargs, expected)
2590+ return 0
2591+ m.stop = mock_stop
2592+ status = m.shutdown()
2593+ self.assertEquals(status, 0)
2594+ self.assertEquals(m.stop_was_called, True)
2595+
2596+ def test_restart(self):
2597+ m = manager.Manager(['test'])
2598+ m.stop_was_called = False
2599+
2600+ def mock_stop(*args, **kwargs):
2601+ m.stop_was_called = True
2602+ return 0
2603+ m.start_was_called = False
2604+
2605+ def mock_start(*args, **kwargs):
2606+ m.start_was_called = True
2607+ return 0
2608+ m.stop = mock_stop
2609+ m.start = mock_start
2610+ status = m.restart()
2611+ self.assertEquals(status, 0)
2612+ self.assertEquals(m.stop_was_called, True)
2613+ self.assertEquals(m.start_was_called, True)
2614+
2615+ def test_reload(self):
2616+ class MockManager():
2617+ called = defaultdict(list)
2618+
2619+ def __init__(self, servers):
2620+ pass
2621+
2622+ @classmethod
2623+ def reset_called(cls):
2624+ cls.called = defaultdict(list)
2625+
2626+ def stop(self, **kwargs):
2627+ MockManager.called['stop'].append(kwargs)
2628+ return 0
2629+
2630+ def start(self, **kwargs):
2631+ MockManager.called['start'].append(kwargs)
2632+ return 0
2633+
2634+ _orig_manager = manager.Manager
2635+ try:
2636+ m = _orig_manager(['auth'])
2637+ for server in m.servers:
2638+ self.assert_(server.server in
2639+ manager.GRACEFUL_SHUTDOWN_SERVERS)
2640+ manager.Manager = MockManager
2641+ status = m.reload()
2642+ self.assertEquals(status, 0)
2643+ expected = {
2644+ 'start': [{'graceful': True}],
2645+ 'stop': [{'graceful': True}],
2646+ }
2647+ self.assertEquals(MockManager.called, expected)
2648+ # test force graceful
2649+ MockManager.reset_called()
2650+ m = _orig_manager(['*-server'])
2651+ self.assert_(len(m.servers), 4)
2652+ for server in m.servers:
2653+ self.assert_(server.server in
2654+ manager.GRACEFUL_SHUTDOWN_SERVERS)
2655+ manager.Manager = MockManager
2656+ status = m.reload(graceful=False)
2657+ self.assertEquals(status, 0)
2658+ expected = {
2659+ 'start': [{'graceful': True}] * 4,
2660+ 'stop': [{'graceful': True}] * 4,
2661+ }
2662+ self.assertEquals(MockManager.called, expected)
2663+
2664+ finally:
2665+ manager.Manager = _orig_manager
2666+
2667+ def test_force_reload(self):
2668+ m = manager.Manager(['test'])
2669+ m.reload_was_called = False
2670+
2671+ def mock_reload(*args, **kwargs):
2672+ m.reload_was_called = True
2673+ return 0
2674+ m.reload = mock_reload
2675+ status = m.force_reload()
2676+ self.assertEquals(status, 0)
2677+ self.assertEquals(m.reload_was_called, True)
2678+
2679+ def test_get_command(self):
2680+ m = manager.Manager(['test'])
2681+ self.assertEquals(m.start, m.get_command('start'))
2682+ self.assertEquals(m.force_reload, m.get_command('force-reload'))
2683+ self.assertEquals(m.get_command('force-reload'),
2684+ m.get_command('force_reload'))
2685+ self.assertRaises(manager.UnknownCommandError, m.get_command,
2686+ 'no_command')
2687+ self.assertRaises(manager.UnknownCommandError, m.get_command,
2688+ '__init__')
2689+
2690+ def test_list_commands(self):
2691+ for cmd, help in manager.Manager.list_commands():
2692+ method = getattr(manager.Manager, cmd.replace('-', '_'), None)
2693+ self.assert_(method, '%s is not a command' % cmd)
2694+ self.assert_(getattr(method, 'publicly_accessible', False))
2695+ self.assertEquals(method.__doc__.strip(), help)
2696+
2697+ def test_run_command(self):
2698+ m = manager.Manager(['test'])
2699+ m.cmd_was_called = False
2700+
2701+ def mock_cmd(*args, **kwargs):
2702+ m.cmd_was_called = True
2703+ expected = {'kw1': True, 'kw2': False}
2704+ self.assertEquals(kwargs, expected)
2705+ return 0
2706+ mock_cmd.publicly_accessible = True
2707+ m.mock_cmd = mock_cmd
2708+ kwargs = {'kw1': True, 'kw2': False}
2709+ status = m.run_command('mock_cmd', **kwargs)
2710+ self.assertEquals(status, 0)
2711+ self.assertEquals(m.cmd_was_called, True)
2712+
2713+if __name__ == '__main__':
2714+ unittest.main()
2715
2716=== modified file 'test/unit/common/test_utils.py'
2717--- test/unit/common/test_utils.py 2011-02-11 17:27:05 +0000
2718+++ test/unit/common/test_utils.py 2011-02-16 21:01:55 +0000
2719@@ -16,6 +16,7 @@
2720 """ Tests for swift.common.utils """
2721
2722 from __future__ import with_statement
2723+from test.unit import temptree
2724 import logging
2725 import mimetools
2726 import os
2727@@ -660,5 +661,86 @@
2728 self.assertTrue(abs(100 - (time.time() - start) * 100) < 10)
2729
2730
2731+ def test_search_tree(self):
2732+ # file match & ext miss
2733+ with temptree(['asdf.conf', 'blarg.conf', 'asdf.cfg']) as t:
2734+ asdf = utils.search_tree(t, 'a*', '.conf')
2735+ self.assertEquals(len(asdf), 1)
2736+ self.assertEquals(asdf[0],
2737+ os.path.join(t, 'asdf.conf'))
2738+
2739+ # multi-file match & glob miss & sort
2740+ with temptree(['application.bin', 'apple.bin', 'apropos.bin']) as t:
2741+ app_bins = utils.search_tree(t, 'app*', 'bin')
2742+ self.assertEquals(len(app_bins), 2)
2743+ self.assertEquals(app_bins[0],
2744+ os.path.join(t, 'apple.bin'))
2745+ self.assertEquals(app_bins[1],
2746+ os.path.join(t, 'application.bin'))
2747+
2748+ # test file in folder & ext miss & glob miss
2749+ files = (
2750+ 'sub/file1.ini',
2751+ 'sub/file2.conf',
2752+ 'sub.bin',
2753+ 'bus.ini',
2754+ 'bus/file3.ini',
2755+ )
2756+ with temptree(files) as t:
2757+ sub_ini = utils.search_tree(t, 'sub*', '.ini')
2758+ self.assertEquals(len(sub_ini), 1)
2759+ self.assertEquals(sub_ini[0],
2760+ os.path.join(t, 'sub/file1.ini'))
2761+
2762+ # test multi-file in folder & sub-folder & ext miss & glob miss
2763+ files = (
2764+ 'folder_file.txt',
2765+ 'folder/1.txt',
2766+ 'folder/sub/2.txt',
2767+ 'folder2/3.txt',
2768+ 'Folder3/4.txt'
2769+ 'folder.rc',
2770+ )
2771+ with temptree(files) as t:
2772+ folder_texts = utils.search_tree(t, 'folder*', '.txt')
2773+ self.assertEquals(len(folder_texts), 4)
2774+ f1 = os.path.join(t, 'folder_file.txt')
2775+ f2 = os.path.join(t, 'folder/1.txt')
2776+ f3 = os.path.join(t, 'folder/sub/2.txt')
2777+ f4 = os.path.join(t, 'folder2/3.txt')
2778+ for f in [f1, f2, f3, f4]:
2779+ self.assert_(f in folder_texts)
2780+
2781+ def test_write_file(self):
2782+ with temptree([]) as t:
2783+ file_name = os.path.join(t, 'test')
2784+ utils.write_file(file_name, 'test')
2785+ with open(file_name, 'r') as f:
2786+ contents = f.read()
2787+ self.assertEquals(contents, 'test')
2788+ # and also subdirs
2789+ file_name = os.path.join(t, 'subdir/test2')
2790+ utils.write_file(file_name, 'test2')
2791+ with open(file_name, 'r') as f:
2792+ contents = f.read()
2793+ self.assertEquals(contents, 'test2')
2794+ # but can't over-write files
2795+ file_name = os.path.join(t, 'subdir/test2/test3')
2796+ self.assertRaises(IOError, utils.write_file, file_name,
2797+ 'test3')
2798+
2799+ def test_remove_file(self):
2800+ with temptree([]) as t:
2801+ file_name = os.path.join(t, 'blah.pid')
2802+ # assert no raise
2803+ self.assertEquals(os.path.exists(file_name), False)
2804+ self.assertEquals(utils.remove_file(file_name), None)
2805+ with open(file_name, 'w') as f:
2806+ f.write('1')
2807+ self.assert_(os.path.exists(file_name))
2808+ self.assertEquals(utils.remove_file(file_name), None)
2809+ self.assertFalse(os.path.exists(file_name))
2810+
2811+
2812 if __name__ == '__main__':
2813 unittest.main()