Merge lp:~jaypipes/glance/use-paste-deploy into lp:~glance-coresec/glance/cactus-trunk

Proposed by Jay Pipes
Status: Merged
Approved by: Jay Pipes
Approved revision: 79
Merged at revision: 69
Proposed branch: lp:~jaypipes/glance/use-paste-deploy
Merge into: lp:~glance-coresec/glance/cactus-trunk
Prerequisite: lp:~jaypipes/glance/use-config-parser
Diff against target: 1592 lines (+717/-584)
19 files modified
.bzrignore (+5/-0)
bin/glance-api (+7/-38)
bin/glance-combined (+9/-43)
bin/glance-control (+216/-0)
bin/glance-registry (+7/-28)
doc/source/configuring.rst (+20/-0)
doc/source/controllingservers.rst (+165/-0)
doc/source/gettingstarted.rst (+3/-45)
doc/source/index.rst (+3/-0)
doc/source/installing.rst (+100/-0)
etc/glance.conf.sample (+32/-8)
glance/common/config.py (+103/-115)
glance/common/server.py (+0/-129)
glance/common/wsgi.py (+27/-51)
glance/registry/server.py (+10/-0)
glance/server.py (+7/-0)
setup.py (+1/-0)
tests/unit/test_config.py (+1/-125)
tools/pip-requires (+1/-2)
To merge this branch: bzr merge lp:~jaypipes/glance/use-paste-deploy
Reviewer Review Type Date Requested Status
Rick Harris (community) Approve
Devin Carlen (community) Approve
Chuck Thier (community) Approve
Review via email: mp+48489@code.launchpad.net

Commit message

Removes lockfile and custom python-daemon server initialization
in favour of paste.deploy.

Description of the change

Removes lockfile and custom python-daemon server initialization
in favour of paste.deploy.

We use a solution that is a hybrid of the code in Nova and Swift:

* We continue to use the object-based WSGI Server/Router objects
  in glance.common.wsgi.
* We load options from a configuration file like Swift does, merging
  in the typed options returned from glance.common.config.parse_options()
* We use a module-level app_factory method instead of @classmethods on
  the wsgi.Router class, so this is more like Swift than Nova.

NOTE: Due to http://trac.pythonpaste.org/pythonpaste/ticket/379, we
removed the CLI option --log-format and use a hard-coded DEFAULT_LOG_FORMAT.
You are still able to adjust the log format using the --log-config-file
option, however, and setting the format string in the log config file.

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

As far as I can tell, the paste.deploy stuff looks fine to me.

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

Setting to In Progress while I:

* Remove the daemon options leftover from before paste.deploy
* Write documentation on starting the bin/* servers with paste.script and manually

Revision history for this message
Rick Harris (rconradharris) wrote :

Just recapping here for posterity:

1. Paste.deploy fails on my machine because it cannot find an app-factory config line in the configuration file even though it's there. Basically:

* We parse config_file ourselves and flatten it into a simple dict
* We pass this options dict to paste.deploy as global_confs
* paste.deploy re-parses the config file (to gather loaders, like "use" and "app-factory" clauses)
* Since we passed in this global_conf that contained some of this info, paste.deploy gets confused, thinks we already defined the app_factory, and won't redefine it
* Since app_factory is not *actually* defined, paste.deploy explodes

Proposed solution:

Not exactly sure why we're passing in global_confs. Since paste.deploy parses the config file itself, it will pull out what it's interested in on it's own.

2. It was mentioned that this patch is using the "swift-style" of passing the config file as the first argument. However, I'm not seeing where the args param is parsed and treated as a config_filename.

Looks like paste.deploy is getting confused by out config options. We store options a flat-dictionary which we pass to paste.deploy as global_confs. paste.deploy attempts to read config-file again and parse out options, however, since global_confs already contains some keys (erroneously since we flattened the section info out), paste.deploy

review: Needs Information
Revision history for this message
Jay Pipes (jaypipes) wrote :

> Just recapping here for posterity:
>
> 1. Paste.deploy fails on my machine because it cannot find an app-factory
> config line in the configuration file even though it's there. Basically:
>
> * We parse config_file ourselves and flatten it into a simple dict
> * We pass this options dict to paste.deploy as global_confs
> * paste.deploy re-parses the config file (to gather loaders, like "use" and
> "app-factory" clauses)
> * Since we passed in this global_conf that contained some of this info,
> paste.deploy gets confused, thinks we already defined the app_factory, and
> won't redefine it
> * Since app_factory is not *actually* defined, paste.deploy explodes
>
> Proposed solution:
>
> Not exactly sure why we're passing in global_confs. Since paste.deploy parses
> the config file itself, it will pull out what it's interested in on it's own.

As discussed, pulling the section config processing from common.config.get_config_file_options().

> 2. It was mentioned that this patch is using the "swift-style" of passing the
> config file as the first argument. However, I'm not seeing where the args
> param is parsed and treated as a config_filename.

Lines 339-341 in glance.common.config.py

> Looks like paste.deploy is getting confused by out config options. We store
> options a flat-dictionary which we pass to paste.deploy as global_confs.
> paste.deploy attempts to read config-file again and parse out options,
> however, since global_confs already contains some keys (erroneously since we
> flattened the section info out), paste.deploy

Ya. I'm just going to remove the ability to override config files with CLI options, since ConfigParser/paste.deploy and a dict of CLI options just don't seem to play nicely together.

-jay

Revision history for this message
Jay Pipes (jaypipes) wrote :

    Changes the server daemon programs to be configured only via
    paste.deploy configuration files. Removed ability to configure
    server options from CLI options when starting the servers with
    the exception of --verbose and --debug, which are useful during
    debugging.

    Updated the documentation controllingservers.rst.

Please re-review.

Revision history for this message
Rick Harris (rconradharris) wrote :

Nice, it's working for me now.

Small tweaks/suggestions:

* setup.py will need glance-control

* Looks like get_config_file_options is now only referenced in glance-control and tests; it *looks* like we may be able to remove it from glance-control as well as the tests, meaning we could cleanup common/config somewhat. Thoughts?

* Was mentioned earlier as a femto-nit, do we want to change cnf -> conf to match Nova and Swift?

review: Needs Fixing
Revision history for this message
Jay Pipes (jaypipes) wrote :

Working on fixes now... thx Rick.

Revision history for this message
Jay Pipes (jaypipes) wrote :

ok, fixes pushed. pls review for third time :) thx.

Revision history for this message
Devin Carlen (devcamcar) wrote :

lgtm!

review: Approve
Revision history for this message
Rick Harris (rconradharris) wrote :

Great cleanups Jay, thanks!

I'm marking Approved b/c what follows are nits that shouldn't necessarily hold up patch. I'll leave to your discretion whether they should be fixed with this merge:

* options_to_conf is no longer used, so we can strike that as well
* the defaults regex code is no longer used (outside of tests) so we can strike that as well

I think with both of those removed, common.config will be pretty clean.

review: Approve
lp:~jaypipes/glance/use-paste-deploy updated
79. By Jay Pipes

Review 3 fixups.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2010-10-15 19:46:21 +0000
3+++ .bzrignore 2011-02-10 01:05:12 +0000
4@@ -2,3 +2,8 @@
5 glance.egg-info
6 glance.sqlite
7 *.glance-venv
8+dist/
9+ChangeLog
10+*.pid
11+*.log
12+glance/vcsversion.py
13
14=== modified file 'bin/glance-api'
15--- bin/glance-api 2011-02-03 02:17:38 +0000
16+++ bin/glance-api 2011-02-10 01:05:12 +0000
17@@ -32,8 +32,7 @@
18
19 from glance import version
20 from glance.common import config
21-from glance.common import server
22-import glance.store
23+from glance.common import wsgi
24
25
26 def create_options(parser):
27@@ -43,52 +42,22 @@
28
29 :param parser: The option parser
30 """
31- parser.add_option('-H', '--host',
32- dest="host", metavar="ADDRESS",
33- default="0.0.0.0",
34- help="Address of Glance API server. "
35- "Default: %default")
36- parser.add_option('-p', '--port',
37- dest="port", metavar="PORT", type=int,
38- default=9292,
39- help="Port the Glance API server listens on. "
40- "Default: %default")
41- parser.add_option('--registry-host',
42- dest="registry_host", metavar="ADDRESS",
43- default="0.0.0.0",
44- help="Address of a Glance Registry server. "
45- "Default: %default")
46- parser.add_option('--registry-port',
47- dest="registry_port", metavar="PORT", type=int,
48- default=9191,
49- help="Port a Glance Registry server listens on. "
50- "Default: %default")
51-
52- glance.store.add_options(parser)
53 config.add_common_options(parser)
54- config.add_daemon_options(parser)
55 config.add_log_options('glance-api', parser)
56
57
58-def main(_args):
59- # NOTE(sirp): importing in main so that eventlet is imported AFTER
60- # daemonization. See https://bugs.launchpad.net/bugs/687661
61- from glance.common import wsgi
62- from glance.server import API
63- server = wsgi.Server()
64- server.start(API(options), options['port'], host=options['host'])
65- server.wait()
66-
67-
68 if __name__ == '__main__':
69 oparser = optparse.OptionParser(version='%%prog %s'
70 % version.version_string())
71 create_options(oparser)
72- conf_options = config.get_config_file_options()
73- (options, args) = config.parse_options(oparser, defaults=conf_options)
74+ (options, args) = config.parse_options(oparser)
75
76 try:
77 config.setup_logging(options)
78- server.serve('glance-api', main, options, args)
79+ conf, app = config.load_paste_app('glance-api', options, args)
80+
81+ server = wsgi.Server()
82+ server.start(app, int(conf['bind_port']), conf['bind_host'])
83+ server.wait()
84 except RuntimeError, e:
85 sys.exit("ERROR: %s" % e)
86
87=== modified file 'bin/glance-combined'
88--- bin/glance-combined 2011-02-03 02:17:38 +0000
89+++ bin/glance-combined 2011-02-10 01:05:12 +0000
90@@ -34,9 +34,7 @@
91
92 from glance import version
93 from glance.common import config
94-from glance.common import server
95-import glance.registry.db
96-import glance.store
97+from glance.common import wsgi
98
99
100 def create_options(parser):
101@@ -46,56 +44,24 @@
102
103 :param parser: The option parser
104 """
105- parser.add_option('--api-host',
106- dest="api_host", metavar="ADDRESS",
107- default="0.0.0.0",
108- help="Address of Glance API server. "
109- "Default: %default")
110- parser.add_option('--api-port',
111- dest="api_port", metavar="PORT", type=int,
112- default=9292,
113- help="Port the Glance API server listens on. "
114- "Default: %default")
115- parser.add_option('--registry-host',
116- dest="registry_host", metavar="ADDRESS",
117- default="0.0.0.0",
118- help="Address of a Glance Registry server. "
119- "Default: %default")
120- parser.add_option('--registry-port',
121- dest="registry_port", metavar="PORT", type=int,
122- default=9191,
123- help="Port a Glance Registry server listens on. "
124- "Default: %default")
125-
126- glance.store.add_options(parser)
127- glance.registry.db.add_options(parser)
128 config.add_common_options(parser)
129- config.add_daemon_options(parser)
130 config.add_log_options('glance-combined', parser)
131
132
133-def main(_args):
134- # NOTE(sirp): importing in main so that eventlet is imported AFTER
135- # daemonization. See https://bugs.launchpad.net/bugs/687661
136- from glance.common import wsgi
137- from glance.server import API
138- from glance.registry.server import API as rAPI
139- server = wsgi.Server()
140- server.start(API(options), options['api_port'], host=options['api_host'])
141- server.start(rAPI(options), options['registry_port'],
142- host=options['registry_host'])
143- server.wait()
144-
145-
146 if __name__ == '__main__':
147 oparser = optparse.OptionParser(version='%%prog %s'
148 % version.version_string())
149 create_options(oparser)
150- conf_options = config.get_config_file_options()
151- (options, args) = config.parse_options(oparser, defaults=conf_options)
152+ (options, args) = config.parse_options(oparser)
153
154 try:
155 config.setup_logging(options)
156- server.serve('glance-combined', main, options, args)
157+
158+ server = wsgi.Server()
159+ conf, app = config.load_paste_app('glance-api', options, args)
160+ server.start(app, int(conf['bind_port']), conf['bind_host'])
161+ conf, app = config.load_paste_app('glance-registry', options, args)
162+ server.start(app, int(conf['bind_port']), conf['bind_host'])
163+ server.wait()
164 except RuntimeError, e:
165 sys.exit("ERROR: %s" % e)
166
167=== added file 'bin/glance-control'
168--- bin/glance-control 1970-01-01 00:00:00 +0000
169+++ bin/glance-control 2011-02-10 01:05:12 +0000
170@@ -0,0 +1,216 @@
171+#!/usr/bin/python
172+# vim: tabstop=4 shiftwidth=4 softtabstop=4
173+
174+# Copyright (c) 2011 OpenStack, LLC.
175+#
176+# Licensed under the Apache License, Version 2.0 (the "License");
177+# you may not use this file except in compliance with the License.
178+# You may obtain a copy of the License at
179+#
180+# http://www.apache.org/licenses/LICENSE-2.0
181+#
182+# Unless required by applicable law or agreed to in writing, software
183+# distributed under the License is distributed on an "AS IS" BASIS,
184+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
185+# implied.
186+# See the License for the specific language governing permissions and
187+# limitations under the License.
188+
189+"""
190+Helper script for starting/stopping/reloading Glance server programs.
191+Thanks for some of the code, Swifties ;)
192+"""
193+
194+from __future__ import with_statement
195+
196+import errno
197+import glob
198+import os
199+import optparse
200+import resource
201+import signal
202+import sys
203+import time
204+
205+from glance import version
206+from glance.common import config
207+
208+ALL_COMMANDS = ['start', 'stop', 'shutdown', 'restart',
209+ 'reload', 'force-reload']
210+ALL_SERVERS = ['glance-api', 'glance-registry']
211+GRACEFUL_SHUTDOWN_SERVERS = ['glance-api', 'glance-registry']
212+MAX_DESCRIPTORS = 32768
213+MAX_MEMORY = (1024 * 1024 * 1024) * 2 # 2 GB
214+USAGE = """%prog [options] <SERVER> <COMMAND> [CONFPATH]
215+
216+Where <SERVER> is one of:
217+
218+ all, api, registry
219+
220+And command is one of:
221+
222+ start, stop, shutdown, restart, reload, force-reload
223+
224+And CONFPATH is the optional configuration file to use."""
225+
226+
227+def pid_files(server):
228+ if os.path.exists('/var/run/glance/%s.pid' % server):
229+ pid_files = ['/var/run/glance/%s.pid' % server]
230+ else:
231+ pid_files = glob.glob('/var/run/glance/%s/*.pid' % server)
232+ for pid_file in pid_files:
233+ pid = int(open(pid_file).read().strip())
234+ yield pid_file, pid
235+
236+
237+def do_start(server, options, args):
238+ server_type = '-'.join(server.split('-')[:-1])
239+
240+ for pid_file, pid in pid_files(server):
241+ if os.path.exists('/proc/%s' % pid):
242+ print "%s appears to already be running: %s" % (server, pid_file)
243+ return
244+ else:
245+ print "Removing stale pid file %s" % pid_file
246+ os.unlink(pid_file)
247+
248+ try:
249+ resource.setrlimit(resource.RLIMIT_NOFILE,
250+ (MAX_DESCRIPTORS, MAX_DESCRIPTORS))
251+ resource.setrlimit(resource.RLIMIT_DATA,
252+ (MAX_MEMORY, MAX_MEMORY))
253+ except ValueError:
254+ print "Unable to increase file descriptor limit. Running as non-root?"
255+ os.environ['PYTHON_EGG_CACHE'] = '/tmp'
256+
257+ def write_pid_file(pid_file, pid):
258+ dir, file = os.path.split(pid_file)
259+ if not os.path.exists(dir):
260+ try:
261+ os.makedirs(dir)
262+ except OSError, err:
263+ if err.errno == errno.EACCES:
264+ sys.exit('Unable to create %s. Running as non-root?'
265+ % dir)
266+ fp = open(pid_file, 'w')
267+ fp.write('%d\n' % pid)
268+ fp.close()
269+
270+ def launch(ini_file, pid_file):
271+ args = [server, ini_file]
272+ print 'Starting %s with %s' % (server, ini_file)
273+
274+ pid = os.fork()
275+ if pid == 0:
276+ os.setsid()
277+ with open(os.devnull, 'r+b') as nullfile:
278+ for desc in (0, 1, 2): # close stdio
279+ try:
280+ os.dup2(nullfile.fileno(), desc)
281+ except OSError:
282+ pass
283+ try:
284+ os.execlp('%s' % server, server, ini_file)
285+ except OSError, e:
286+ sys.exit('unable to launch %s. Got error: %s'
287+ % (server, str(e)))
288+ sys.exit(0)
289+ else:
290+ write_pid_file(pid_file, pid)
291+
292+ pid_file = '/var/run/glance/%s.pid' % server
293+ conf_file = config.find_config_file(options, args)
294+ if not conf_file:
295+ sys.exit("Could not find any configuration file to use!")
296+ launch_args = [(conf_file, pid_file)]
297+
298+ # start all servers
299+ for conf_file, pid_file in launch_args:
300+ launch(conf_file, pid_file)
301+
302+
303+def do_stop(server, options, args, graceful=False):
304+ if graceful and server in GRACEFUL_SHUTDOWN_SERVERS:
305+ sig = signal.SIGHUP
306+ else:
307+ sig = signal.SIGTERM
308+
309+ did_anything = False
310+ pfiles = pid_files(server)
311+ for pid_file, pid in pfiles:
312+ did_anything = True
313+ try:
314+ print 'Stopping %s pid: %s signal: %s' % (server, pid, sig)
315+ os.kill(pid, sig)
316+ except OSError:
317+ print "Process %d not running" % pid
318+ try:
319+ os.unlink(pid_file)
320+ except OSError:
321+ pass
322+ for pid_file, pid in pfiles:
323+ for _junk in xrange(150): # 15 seconds
324+ if not os.path.exists('/proc/%s' % pid):
325+ break
326+ time.sleep(0.1)
327+ else:
328+ print 'Waited 15 seconds for pid %s (%s) to die; giving up' % \
329+ (pid, pid_file)
330+ if not did_anything:
331+ print 'No %s running' % server
332+
333+
334+if __name__ == '__main__':
335+ oparser = optparse.OptionParser(usage=USAGE, version='%%prog %s'
336+ % version.version_string())
337+ config.add_common_options(oparser)
338+ (options, args) = config.parse_options(oparser)
339+
340+ if len(args) < 2:
341+ oparser.print_usage()
342+ sys.exit(1)
343+
344+ server = args.pop(0).lower()
345+ if server == 'all':
346+ servers = ALL_SERVERS
347+ else:
348+ if not server.startswith('glance-'):
349+ server = 'glance-%s' % server
350+ if server not in ALL_SERVERS:
351+ server_list = ", ".join([s.replace('glance-', '')
352+ for s in ALL_SERVERS])
353+ msg = ("Unknown server '%(server)s' specified. Please specify "
354+ "all, or one of the servers: %(server_list)s" % locals())
355+ sys.exit(msg)
356+ servers = [server]
357+
358+ command = args.pop(0).lower()
359+ if command not in ALL_COMMANDS:
360+ command_list = ", ".join(ALL_COMMANDS)
361+ msg = ("Unknown command %(command)s specified. Please specify a "
362+ "command in this list: %(command_list)s" % locals())
363+ sys.exit(msg)
364+
365+ if command == 'start':
366+ for server in servers:
367+ do_start(server, options, args)
368+
369+ if command == 'stop':
370+ for server in servers:
371+ do_stop(server, options, args)
372+
373+ if command == 'shutdown':
374+ for server in servers:
375+ do_stop(server, options, args, graceful=True)
376+
377+ if command == 'restart':
378+ for server in servers:
379+ do_stop(server, options, args)
380+ for server in servers:
381+ do_start(server, options, args)
382+
383+ if command == 'reload' or command == 'force-reload':
384+ for server in servers:
385+ do_stop(server, options, args, graceful=True)
386+ do_start(server, options, args)
387
388=== modified file 'bin/glance-registry'
389--- bin/glance-registry 2011-02-03 02:17:38 +0000
390+++ bin/glance-registry 2011-02-10 01:05:12 +0000
391@@ -30,10 +30,9 @@
392
393 sys.path.append(ROOT_DIR)
394
395-import glance.registry.db
396 from glance import version
397 from glance.common import config
398-from glance.common import server
399+from glance.common import wsgi
400
401
402 def create_options(parser):
403@@ -43,42 +42,22 @@
404
405 :param parser: The option parser
406 """
407- parser.add_option('-H', '--host',
408- dest="host", metavar="ADDRESS",
409- default="0.0.0.0",
410- help="Address of Glance API server. "
411- "Default: %default")
412- parser.add_option('-p', '--port',
413- dest="port", metavar="PORT", type=int,
414- default=9191,
415- help="Port the Glance Registry server listens on. "
416- "Default: %default")
417-
418- glance.registry.db.add_options(parser)
419 config.add_common_options(parser)
420- config.add_daemon_options(parser)
421 config.add_log_options('glance-registry', parser)
422
423
424-def main(_args):
425- # NOTE(sirp): importing in main so that eventlet is imported AFTER
426- # daemonization. See https://bugs.launchpad.net/bugs/687661
427- from glance.common import wsgi
428- from glance.registry.server import API
429- server = wsgi.Server()
430- server.start(API(options), int(options['port']), host=options['host'])
431- server.wait()
432-
433-
434 if __name__ == '__main__':
435 oparser = optparse.OptionParser(version='%%prog %s'
436 % version.version_string())
437 create_options(oparser)
438- conf_options = config.get_config_file_options()
439- (options, args) = config.parse_options(oparser, defaults=conf_options)
440+ (options, args) = config.parse_options(oparser)
441
442 try:
443 config.setup_logging(options)
444- server.serve('glance-registry', main, options, args)
445+ conf, app = config.load_paste_app('glance-registry', options, args)
446+
447+ server = wsgi.Server()
448+ server.start(app, int(conf['bind_port']), conf['bind_host'])
449+ server.wait()
450 except RuntimeError, e:
451 sys.exit("ERROR: %s" % e)
452
453=== added file 'doc/source/configuring.rst'
454--- doc/source/configuring.rst 1970-01-01 00:00:00 +0000
455+++ doc/source/configuring.rst 2011-02-10 01:05:12 +0000
456@@ -0,0 +1,20 @@
457+..
458+ Copyright 2011 OpenStack, LLC
459+ All Rights Reserved.
460+
461+ Licensed under the Apache License, Version 2.0 (the "License"); you may
462+ not use this file except in compliance with the License. You may obtain
463+ a copy of the License at
464+
465+ http://www.apache.org/licenses/LICENSE-2.0
466+
467+ Unless required by applicable law or agreed to in writing, software
468+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
469+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
470+ License for the specific language governing permissions and limitations
471+ under the License.
472+
473+Configuring Glance
474+==================
475+
476+.. todo:: Complete details of configuration with paste.deploy config files
477
478=== added file 'doc/source/controllingservers.rst'
479--- doc/source/controllingservers.rst 1970-01-01 00:00:00 +0000
480+++ doc/source/controllingservers.rst 2011-02-10 01:05:12 +0000
481@@ -0,0 +1,165 @@
482+..
483+ Copyright 2011 OpenStack, LLC
484+ All Rights Reserved.
485+
486+ Licensed under the Apache License, Version 2.0 (the "License"); you may
487+ not use this file except in compliance with the License. You may obtain
488+ a copy of the License at
489+
490+ http://www.apache.org/licenses/LICENSE-2.0
491+
492+ Unless required by applicable law or agreed to in writing, software
493+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
494+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
495+ License for the specific language governing permissions and limitations
496+ under the License.
497+
498+Controlling Glance Servers
499+==========================
500+
501+This section describes the ways to start, stop, and reload Glance's server
502+programs.
503+
504+Starting a server
505+-----------------
506+
507+There are two ways to start a Glance server (either the API server or the
508+reference implementation registry server that ships with Glance):
509+
510+* Manually calling the server program
511+
512+* Using the ``glance-control`` server daemon wrapper program
513+
514+Manually starting the server
515+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
516+
517+The first is by directly calling the server program, passing in command-line
518+options and a single argument for the ``paste.deploy`` configuration file to
519+use when configuring the server application.
520+
521+.. note::
522+
523+ Glance ships with an ``etc/`` directory that contains sample ``paste.deploy``
524+ configuration files that you can copy to a standard configuation directory and
525+ adapt for your own uses.
526+
527+If you do `not` specifiy a configuration file on the command line, Glance will
528+do its best to locate a ``glance.conf`` configuration file in one of the
529+following directories, stopping at the first config file it finds:
530+
531+* .
532+
533+* ~/.glance
534+
535+* ~/
536+
537+* /etc/glance/
538+
539+* /etc
540+
541+If no configuration file is found, you will see any error, like so::
542+
543+ $> glance-api
544+ ERROR: Unable to locate any configuration file. Cannot load application glance-api
545+
546+Here is an example showing how you can manually start the ``glance-api`` server
547+in a shell.::
548+
549+ $> sudo glance-api etc/glance.conf.sample --debug
550+ 2011-02-09 14:58:29 DEBUG [glance-api] ********************************************************************************
551+ 2011-02-09 14:58:29 DEBUG [glance-api] Configuration options gathered from config file:
552+ 2011-02-09 14:58:29 DEBUG [glance-api] /home/jpipes/repos/glance/trunk/etc/glance.conf.sample
553+ 2011-02-09 14:58:29 DEBUG [glance-api] ================================================
554+ 2011-02-09 14:58:29 DEBUG [glance-api] bind_host 0.0.0.0
555+ 2011-02-09 14:58:29 DEBUG [glance-api] bind_port 9292
556+ 2011-02-09 14:58:29 DEBUG [glance-api] debug True
557+ 2011-02-09 14:58:29 DEBUG [glance-api] default_store file
558+ 2011-02-09 14:58:29 DEBUG [glance-api] filesystem_store_datadir /var/lib/glance/images/
559+ 2011-02-09 14:58:29 DEBUG [glance-api] registry_host 0.0.0.0
560+ 2011-02-09 14:58:29 DEBUG [glance-api] registry_port 9191
561+ 2011-02-09 14:58:29 DEBUG [glance-api] verbose False
562+ 2011-02-09 14:58:29 DEBUG [glance-api] ********************************************************************************
563+ 2011-02-09 14:58:29 DEBUG [routes.middleware] Initialized with method overriding = True, and path info altering = True
564+ (16333) wsgi starting up on http://0.0.0.0:9292/
565+
566+Simply supply the configuration file as the first argument
567+(``etc/glance.conf.sample`` in the above example) and then any common options
568+you want to use (``--debug`` was used above to show some of the debugging
569+output that the server shows when starting up. Call the server program
570+with ``--help`` to see all available options you can specify on the
571+command line.)
572+
573+For more information on configuring the server via the ``paste.deploy``
574+configuration files, see the section entitled
575+:doc:`Configuring Glance servers <configuring>`
576+
577+Note that the server does not `daemonize` itself when run manually
578+from the terminal. You can force the server to daemonize using the standard
579+shell backgrounding indicator, ``&``. However, for most use cases, we recommend
580+using the ``glance-control`` server daemon wrapper for daemonizing. See below
581+for more details on daemonization with ``glance-control``.
582+
583+Using the ``glance-control`` program to start the server
584+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
585+
586+The second way to start up a Glance server is to use the ``glance-control``
587+program. ``glance-control`` is a wrapper script that allows the user to
588+start, stop, restart, and reload the other Glance server programs in
589+a fashion that is more conducive to automation and scripting.
590+
591+Servers started via the ``glance-control`` program are always `daemonized`,
592+meaning that the server program process runs in the background.
593+
594+To start a Glance server with ``glance-control``, simply call
595+``glance-control`` with a server and the word "start", followed by
596+any command-line options you wish to provide. Start the server with ``glance-control``
597+in the following way::
598+
599+ $> sudo glance-control <SERVER> start [CONFPATH]
600+
601+.. note::
602+
603+ You must use the ``sudo`` program to run ``glance-control`` currently, as the
604+ pid files for the server programs are written to /var/run/glance/
605+
606+Here is an example that shows how to start the ``glance-registry`` server
607+with the ``glance-control`` wrapper script. ::
608+
609+ $> sudo glance-control registry start etc/glance.conf.sample
610+ Starting glance-registry with /home/jpipes/repos/glance/trunk/etc/glance.conf.sample
611+
612+The same ``paste.deploy`` configuration files are used by ``glance-control``
613+to start the Glance server programs, and you can specify (as the example above
614+shows) a configuration file when starting the server.
615+
616+.. note::
617+
618+ To start all the Glance servers (currently the glance-api and glance-registry
619+ programs) at once, you can specify "all" for the <SERVER>
620+
621+Stopping a server
622+-----------------
623+
624+If you started a Glance server manually and did not use the ``&`` backgrounding
625+function, simply send a terminate signal to the server process by typing
626+``Ctrl-C``
627+
628+If you started the Glance server using the ``glance-control`` program, you can
629+use the ``glance-control`` program to stop it. Simply do the following::
630+
631+ $> sudo glance-control <SERVER> stop
632+
633+as this example shows::
634+
635+ $> sudo glance-control registry stop
636+ Stopping glance-registry pid: 17602 signal: 15
637+
638+Restarting a server
639+-------------------
640+
641+You can restart a server with the ``glance-control`` program, as demonstrated
642+here::
643+
644+ $> sudo glance-control registry restart etc/glance.conf.sample
645+ Stopping glance-registry pid: 17611 signal: 15
646+ Starting glance-registry with /home/jpipes/repos/glance/trunk/etc/glance.conf.sample
647
648=== modified file 'doc/source/gettingstarted.rst'
649--- doc/source/gettingstarted.rst 2011-01-26 17:26:54 +0000
650+++ doc/source/gettingstarted.rst 2011-02-10 01:05:12 +0000
651@@ -76,48 +76,6 @@
652
653 Glance registry servers are servers that conform to the Glance Registry API.
654 Glance ships with a reference implementation of a registry server that
655-complies with this API (``bin/glance-registry``).
656-
657-
658-Starting Up Glance's Servers
659-----------------------------
660-
661-To get started using Glance, you must first start the Glance API server.
662-After installing Glance, starting up the Glance API server is easy. Simply
663-start the ``glance-api`` program, like so::
664-
665- $> glance-api
666-
667-Configuring the Glance API server
668-*********************************
669-
670-There are a few options that can be supplied to the API server when
671-starting it up:
672-
673-* ``verbose``
674-
675- Show more verbose/debugging output
676-
677-* ``api_host``
678-
679- Address of the host the registry runs on. Defaults to 0.0.0.0.
680-
681-* ``api_port``
682-
683- Port the registry server listens on. Defaults to 9292.
684-
685-* ``default_store``
686-
687- The store that the Glance API server will use by default to store
688- images that are added to it. The default value is `filesystem`, and
689- possible choices are: `filesystem`, `swift`, and `s3`.
690-
691-* ``filesystem_store_datadir``
692-
693- Directory where the filesystem store can write images to. This directory
694- must be writeable by the user that runs ``glance-api``
695-
696-.. todo::
697-
698-Link to docs on the different stores when the documentation on Glance
699-stores is complete.
700+complies with this API (``glance-registry``).
701+
702+For more details on Glance's architecture see :doc:`here <architecture>`
703
704=== modified file 'doc/source/index.rst'
705--- doc/source/index.rst 2011-01-26 20:47:01 +0000
706+++ doc/source/index.rst 2011-02-10 01:05:12 +0000
707@@ -58,6 +58,9 @@
708 :maxdepth: 1
709
710 gettingstarted
711+ installing
712+ controllingservers
713+ configuring
714 glanceapi
715 client
716
717
718=== added file 'doc/source/installing.rst'
719--- doc/source/installing.rst 1970-01-01 00:00:00 +0000
720+++ doc/source/installing.rst 2011-02-10 01:05:12 +0000
721@@ -0,0 +1,100 @@
722+..
723+ Copyright 2011 OpenStack, LLC
724+ All Rights Reserved.
725+
726+ Licensed under the Apache License, Version 2.0 (the "License"); you may
727+ not use this file except in compliance with the License. You may obtain
728+ a copy of the License at
729+
730+ http://www.apache.org/licenses/LICENSE-2.0
731+
732+ Unless required by applicable law or agreed to in writing, software
733+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
734+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
735+ License for the specific language governing permissions and limitations
736+ under the License.
737+
738+Installing Glance
739+=================
740+
741+Installing from packages
742+~~~~~~~~~~~~~~~~~~~~~~~~
743+
744+To install the latest version of Glance from the Launchpad Bazaar repositories,
745+following the following instructions.
746+
747+Debian/Ubuntu
748+#############
749+
750+1. Add the Glance PPA to your sources.lst::
751+
752+ $> sudo add-apt-repository ppa:glance-core/trunk
753+ $> sudo apt-get update
754+
755+2. Install Glance::
756+
757+ $> sudo apt-get install glance
758+
759+RedHat/Fedora
760+#############
761+
762+.. todo:: Need some help on this one...
763+
764+Mac OSX
765+#######
766+
767+.. todo:: No idea how to do install on Mac OSX. Somebody with a Mac should complete this section
768+
769+Installing from source tarballs
770+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
771+
772+To install the latest version of Glance from the Launchpad Bazaar repositories,
773+following the following instructions.
774+
775+1. Grab the source tarball from `Launchpad <http://launchpad.net/glance/+download>`_
776+
777+2. Untar the source tarball::
778+
779+ $> tar -xzf <FILE>
780+
781+3. Change into the package directory and build/install::
782+
783+ $> cd glance-<RELEASE>
784+ $> sudo python setup.py install
785+
786+Installing from a Bazaar Branch
787+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
788+
789+To install the latest version of Glance from the Launchpad Bazaar repositories,
790+following the following instructions.
791+
792+Debian/Ubuntu
793+#############
794+
795+1. Install Bazaar and build dependencies::
796+
797+ $> sudo apt-get install bzr python-eventlet python-routes python-greenlet
798+ $> sudo apt-get install python-argparse python-sqlalchemy python-wsgiref python-pastedeploy
799+
800+.. note::
801+
802+ If you want to build the Glance documentation locally, you will also want
803+ to install the python-sphinx package
804+
805+1. Branch Glance's trunk branch::
806+
807+ $> bzr branch lp:glance
808+
809+1. Install Glance::
810+
811+ $> sudo python setup.py install
812+
813+RedHat/Fedora
814+#############
815+
816+.. todo:: Need some help on this one...
817+
818+Mac OSX
819+#######
820+
821+.. todo:: No idea how to do install on Mac OSX. Somebody with a Mac should complete this section
822
823=== renamed file 'etc/glance.cnf.sample' => 'etc/glance.conf.sample'
824--- etc/glance.cnf.sample 2011-02-03 02:17:38 +0000
825+++ etc/glance.conf.sample 2011-02-10 01:05:12 +0000
826@@ -1,20 +1,44 @@
827 [DEFAULT]
828 # Show more verbose log output (sets INFO log level output)
829-# verbose = True
830+verbose = True
831
832 # Show debugging output in logs (sets DEBUG log level output)
833-# debug = True
834+debug = False
835+
836+[app:glance-api]
837+paste.app_factory = glance.server:app_factory
838+
839+# Directory that the Filesystem backend store
840+# writes image data to
841+filesystem_store_datadir=/var/lib/glance/images/
842
843 # Which backend store should Glance use by default is not specified
844 # in a request to add a new image to Glance? Default: 'file'
845 # Available choices are 'file', 'swift', and 's3'
846-# default_store = file
847+default_store = file
848+
849+# Address to bind the API server
850+bind_host = 0.0.0.0
851+
852+# Port the bind the API server to
853+bind_port = 9292
854+
855+# Address to find the registry server
856+registry_host = 0.0.0.0
857+
858+# Port the registry server is listening on
859+registry_port = 9191
860+
861+[app:glance-registry]
862+paste.app_factory = glance.registry.server:app_factory
863+
864+# Address to bind the registry server
865+bind_host = 0.0.0.0
866+
867+# Port the bind the registry server to
868+bind_port = 9191
869
870 # SQLAlchemy connection string for the reference implementation
871 # registry server. Any valid SQLAlchemy connection string is fine.
872 # See: http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html#sqlalchemy.create_engine
873-# sql_connection = sqlite://glance.sqlite
874-
875-# The directory that the Filesystem backend store will write disk
876-# images to. Default: /var/lib/glance/images
877-# filesystem_store_datadir = /var/lib/glance/images
878+sql_connection = sqlite:///glance.sqlite
879
880=== modified file 'glance/common/config.py'
881--- glance/common/config.py 2011-02-05 20:26:46 +0000
882+++ glance/common/config.py 2011-02-10 01:05:12 +0000
883@@ -29,6 +29,8 @@
884 import re
885 import sys
886
887+from paste import deploy
888+
889 import glance.common.exception as exception
890
891 DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"
892@@ -37,7 +39,7 @@
893 LOGGING_HANDLER_CHOICES = ['syslog', 'file', 'stream']
894
895
896-def parse_options(parser, cli_args=None, defaults=None):
897+def parse_options(parser, cli_args=None):
898 """
899 Returns the parsed CLI options, command to run and its arguments, merged
900 with any same-named options found in a configuration file.
901@@ -52,47 +54,14 @@
902 :param parser: The option parser
903 :param cli_args: (Optional) Set of arguments to process. If not present,
904 sys.argv[1:] is used.
905- :param defaults: (optional) mapping of default values for options
906 :retval tuple of (options, args)
907 """
908
909- if defaults:
910- int_re = re.compile(r'^\d+$')
911- float_re = re.compile(r'^([+-]?(((\d+(\.)?)|(\d*\.\d+))'
912- '([eE][+-]?\d+)?))$')
913- for key, value in defaults.items():
914- # Do our best to figure out what the actual option
915- # type is underneath...
916- if value.lower() in ('true', 'on'):
917- value = True
918- elif value.lower() in ('false', 'off'):
919- value = False
920- elif int_re.match(value):
921- value = int(value)
922- elif float_re.match(value):
923- value = float(value)
924- defaults[key] = value
925-
926- parser.set_defaults(**defaults)
927 (options, args) = parser.parse_args(cli_args)
928
929 return (vars(options), args)
930
931
932-def options_to_conf(options):
933- """
934- Converts a mapping of options having typed values into
935- a mapping of configuration options having only stringified values.
936-
937- This method is used to convert the return of parse_options()[0]
938- into the configuration mapping that is expected by ConfigParser
939- and paste.deploy.
940-
941- :params options: Mapping of typed option key/values
942- """
943- return dict([(k, str(v)) for k, v in options.items()])
944-
945-
946 def add_common_options(parser):
947 """
948 Given a supplied optparse.OptionParser, adds an OptionGroup that
949@@ -124,10 +93,16 @@
950 "the daemonizing of this program."
951
952 group = optparse.OptionGroup(parser, "Daemon Options", help_text)
953- group.add_option('--daemonize', default=False, action="store_true",
954- help="Daemonize this process")
955- group.add_option("--pidfile", default=None,
956- help="(Optional) Name of pid file for the server")
957+ group.add_option('--config', default=None,
958+ help="Configuration file to read when loading "
959+ "application. If missing, the first argument is "
960+ "used. If no arguments are found, then a set of "
961+ "standard directories are searched for a config "
962+ "file.")
963+ group.add_option("--pid-file", default=None, metavar="PATH",
964+ help="(Optional) Name of pid file for the server. "
965+ "If not specified, the pid file will be named "
966+ "/var/run/glance/<SERVER>.pid.")
967 group.add_option("--uid", type=int, default=os.getuid(),
968 help="uid under which to run. Default: %default")
969 group.add_option("--gid", type=int, default=os.getgid(),
970@@ -160,9 +135,6 @@
971 choices=LOGGING_HANDLER_CHOICES,
972 help="What logging handler to use? "
973 "Default: %default")
974- group.add_option('--log-format', metavar="FORMAT",
975- default=DEFAULT_LOG_FORMAT,
976- help="Format string for log records. Default: %default")
977 group.add_option('--log-date-format', metavar="FORMAT",
978 default=DEFAULT_LOG_DATE_FORMAT,
979 help="Format string for %(asctime)s in log records. "
980@@ -203,6 +175,9 @@
981 root_logger.setLevel(logging.WARNING)
982
983 # Set log configuration from options...
984+ # Note that we use a hard-coded log format in the options
985+ # because of Paste.Deploy bug #379
986+ # http://trac.pythonpaste.org/pythonpaste/ticket/379
987 log_format = options.get('log_format', DEFAULT_LOG_FORMAT)
988 log_date_format = options.get('log_date_format', DEFAULT_LOG_DATE_FORMAT)
989 formatter = logging.Formatter(log_format, log_date_format)
990@@ -229,79 +204,92 @@
991 raise exception.BadInputError(
992 "unrecognized log handler '%(log_handler)s'" % locals())
993
994- # Log the options used when starting if we're in debug mode...
995- if debug:
996- root_logger.debug("*" * 80)
997- root_logger.debug("Options:")
998- root_logger.debug("========")
999- for key, value in sorted(options.items()):
1000- root_logger.debug("%(key)-30s %(value)s" % locals())
1001- root_logger.debug("*" * 80)
1002-
1003-
1004-def get_config_file_options(conf_file=None, conf_dirs=None, app_name=None):
1005- """
1006- Look for configuration files in a number of standard directories and
1007- return a mapping of options found in the files.
1008-
1009- The files that are searched for are in the following order, with
1010- options found in later files overriding options found in earlier
1011- files::
1012-
1013- /etc/glance.cnf
1014- /etc/glance/glance.cnf
1015- ~/glance.cnf
1016- ~/.glance/glance.cnf
1017- ./glance.cnf
1018- supplied conf_file param, if any.
1019-
1020- :param conf_file: (optional) config file to read options from. Options
1021- from this config file override all others
1022- :param conf_dirs: (optional) sequence of directory paths to search for
1023- config files. Generally just used in testing
1024- :param app_name: (optional) name of application we're interested in.
1025- Supplying this will ensure that only the [DEFAULT]
1026- section and the [app_name] sections of the config
1027- files will be read. If not supplied (the default), all
1028- sections are read for configuration options.
1029-
1030- :retval Mapping of configuration options read from config files
1031- """
1032-
1033- # Note that we do this in reverse priority order because
1034- # later configs overwrite the values of previously-read
1035- # configuration options
1036-
1037- fixup_path = lambda p: os.path.abspath(os.path.expanduser(p))
1038- config_file_dirs = conf_dirs or \
1039- ['/etc',
1040- '/etc/glance/',
1041- fixup_path('~'),
1042- fixup_path(os.path.join('~', '.glance')),
1043- fixup_path(os.getcwd())]
1044-
1045- config_files = []
1046- results = {}
1047+
1048+def find_config_file(options, args):
1049+ """
1050+ Return the first config file found.
1051+
1052+ We search for the paste config file in the following order:
1053+ * If --config-file option is used, use that
1054+ * If args[0] is a file, use that
1055+ * Search for glance.conf in standard directories:
1056+ * .
1057+ * ~.glance/
1058+ * ~
1059+ * /etc/glance
1060+ * /etc
1061+
1062+ :retval Full path to config file, or None if no config file found
1063+ """
1064+
1065+ fix_path = lambda p: os.path.abspath(os.path.expanduser(p))
1066+ if getattr(options, 'config', None):
1067+ if os.path.exists(options.config_file):
1068+ return fix_path(getattr(options, 'config'))
1069+ elif args:
1070+ if os.path.exists(args[0]):
1071+ return fix_path(args[0])
1072+
1073+ # Handle standard directory search for glance.conf
1074+ config_file_dirs = [fix_path(os.getcwd()),
1075+ fix_path(os.path.join('~', '.glance')),
1076+ fix_path('~'),
1077+ '/etc/glance/',
1078+ '/etc']
1079+
1080 for cfg_dir in config_file_dirs:
1081- cfg_file = os.path.join(cfg_dir, 'glance.cnf')
1082+ cfg_file = os.path.join(cfg_dir, 'glance.conf')
1083 if os.path.exists(cfg_file):
1084- config_files.append(cfg_file)
1085-
1086- if conf_file:
1087- config_files.append(fixup_path(conf_file))
1088-
1089- cp = ConfigParser.ConfigParser()
1090- for config_file in config_files:
1091- if not cp.read(config_file):
1092- msg = 'Unable to read config file: %s' % config_file
1093- raise RuntimeError(msg)
1094-
1095- results.update(cp.defaults())
1096- # Add any sections we have in the configuration file, too...
1097- for section in cp.sections():
1098- section_option_keys = cp.options(section)
1099- if not app_name or (app_name == section):
1100- for k in section_option_keys:
1101- results[k] = cp.get(section, k)
1102-
1103- return results
1104+ return cfg_file
1105+
1106+
1107+def load_paste_app(app_name, options, args):
1108+ """
1109+ Builds and returns a WSGI app from a paste config file.
1110+
1111+ We search for the paste config file in the following order:
1112+ * If --config-file option is used, use that
1113+ * If args[0] is a file, use that
1114+ * Search for glance.conf in standard directories:
1115+ * .
1116+ * ~.glance/
1117+ * ~
1118+ * /etc/glance
1119+ * /etc
1120+
1121+ :param app_name: Name of the application to load
1122+ :param options: Set of typed options returned from parse_options()
1123+ :param args: Command line arguments from argv[1:]
1124+
1125+ :raises RuntimeError when config file cannot be located or application
1126+ cannot be loaded from config file
1127+ """
1128+ conf_file = find_config_file(options, args)
1129+ if not conf_file:
1130+ raise RuntimeError("Unable to locate any configuration file. "
1131+ "Cannot load application %s" % app_name)
1132+ try:
1133+ conf = deploy.appconfig("config:%s" % conf_file, name=app_name)
1134+ # We only update the conf dict for the verbose and debug
1135+ # flags. Everything else must be set up in the conf file...
1136+ conf['verbose'] = options['verbose']
1137+ conf['debug'] = options['debug']
1138+
1139+ # Log the options used when starting if we're in debug mode...
1140+ if conf['debug']:
1141+ logger = logging.getLogger(app_name)
1142+ logger.debug("*" * 80)
1143+ logger.debug("Configuration options gathered from config file:")
1144+ logger.debug(conf_file)
1145+ logger.debug("================================================")
1146+ items = dict([(k, v) for k, v in conf.items()
1147+ if k not in ('__file__', 'here')])
1148+ for key, value in sorted(items.items()):
1149+ logger.debug("%(key)-30s %(value)s" % locals())
1150+ logger.debug("*" * 80)
1151+ app = deploy.loadapp("config:%s" % conf_file, name=app_name)
1152+ except (LookupError, ImportError), e:
1153+ raise RuntimeError("Unable to load %(app_name)s from "
1154+ "configuration file %(conf_file)s."
1155+ "\nGot: %(e)r" % locals())
1156+ return conf, app
1157
1158=== removed file 'glance/common/server.py'
1159--- glance/common/server.py 2011-02-01 21:22:49 +0000
1160+++ glance/common/server.py 1970-01-01 00:00:00 +0000
1161@@ -1,129 +0,0 @@
1162-# vim: tabstop=4 shiftwidth=4 softtabstop=4
1163-
1164-# Copyright 2010 United States Government as represented by the
1165-# Administrator of the National Aeronautics and Space Administration.
1166-# All Rights Reserved.
1167-#
1168-# Licensed under the Apache License, Version 2.0 (the "License"); you may
1169-# not use this file except in compliance with the License. You may obtain
1170-# a copy of the License at
1171-#
1172-# http://www.apache.org/licenses/LICENSE-2.0
1173-#
1174-# Unless required by applicable law or agreed to in writing, software
1175-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1176-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1177-# License for the specific language governing permissions and limitations
1178-# under the License.
1179-
1180-"""
1181-Base functionality for nova daemons - gradually being replaced with twistd.py.
1182-"""
1183-
1184-import daemon
1185-from daemon import pidlockfile
1186-import logging
1187-import logging.handlers
1188-import os
1189-import pprint
1190-import signal
1191-import sys
1192-import time
1193-
1194-
1195-def stop(pidfile):
1196- """
1197- Stop the daemon
1198- """
1199- # Get the pid from the pidfile
1200- try:
1201- pid = int(open(pidfile, 'r').read().strip())
1202- except IOError:
1203- message = "pidfile %s does not exist. Daemon not running?\n"
1204- sys.stderr.write(message % pidfile)
1205- return
1206-
1207- # Try killing the daemon process
1208- try:
1209- print "Killing process from pidfile %s" % pidfile
1210- while 1:
1211- os.kill(pid, signal.SIGTERM)
1212- time.sleep(0.1)
1213- except OSError, err:
1214- err = str(err)
1215- if err.find("No such process") > 0:
1216- if os.path.exists(pidfile):
1217- os.remove(pidfile)
1218- else:
1219- print str(err)
1220- sys.exit(1)
1221-
1222-
1223-def serve(name, main, options, args):
1224- """Controller for server"""
1225-
1226- pidfile = options['pidfile']
1227- if not pidfile:
1228- options['pidfile'] = '%s.pid' % name
1229-
1230- action = 'start'
1231- if len(args):
1232- action = args.pop()
1233-
1234- if action == 'stop':
1235- stop(options['pidfile'])
1236- sys.exit()
1237- elif action == 'restart':
1238- stop(options['pidfile'])
1239- elif action == 'start':
1240- pass
1241- else:
1242- print 'usage: %s [options] [start|stop|restart]' % name
1243- sys.exit(1)
1244- daemonize(args, name, main, options)
1245-
1246-
1247-def daemonize(args, name, main, options):
1248- """Does the work of daemonizing the process"""
1249- logging.getLogger('amqplib').setLevel(logging.WARN)
1250- pidfile = options['pidfile']
1251- logfile = options['log_file']
1252- logdir = options['log_dir']
1253- daemonize = options['daemonize']
1254- use_syslog = options['log_handler'] == 'syslog'
1255- files_to_keep = []
1256- if daemonize:
1257- logger = logging.getLogger(name)
1258- formatter = logging.Formatter(
1259- name + '(%(name)s): %(levelname)s %(message)s')
1260- if use_syslog and not logfile:
1261- syslog = logging.handlers.SysLogHandler(address='/dev/log')
1262- syslog.setFormatter(formatter)
1263- logger.addHandler(syslog)
1264- files_to_keep.append(syslog.socket)
1265- elif options['log_handler'] == 'file':
1266- if not logfile:
1267- logfile = '%s.log' % name
1268- if logdir:
1269- logfile = os.path.join(logdir, logfile)
1270- logfile = logging.FileHandler(logfile)
1271- logfile.setFormatter(formatter)
1272- logger.addHandler(logfile)
1273- files_to_keep.append(logfile.stream)
1274- stdin, stdout, stderr = None, None, None
1275- else:
1276- stdin, stdout, stderr = sys.stdin, sys.stdout, sys.stderr
1277-
1278- with daemon.DaemonContext(
1279- detach_process=daemonize,
1280- working_directory=options['working_directory'],
1281- pidfile=pidlockfile.TimeoutPIDLockFile(pidfile,
1282- acquire_timeout=1,
1283- threaded=False),
1284- stdin=stdin,
1285- stdout=stdout,
1286- stderr=stderr,
1287- uid=options['uid'],
1288- gid=options['gid'],
1289- files_preserve=files_to_keep):
1290- main(args)
1291
1292=== modified file 'glance/common/wsgi.py'
1293--- glance/common/wsgi.py 2011-01-27 04:19:13 +0000
1294+++ glance/common/wsgi.py 2011-02-10 01:05:12 +0000
1295@@ -35,9 +35,6 @@
1296 import webob.exc
1297
1298
1299-logging.getLogger("routes.middleware").addHandler(logging.StreamHandler())
1300-
1301-
1302 def run_server(application, port):
1303 """Run a WSGI server with the given application."""
1304 sock = eventlet.listen(('0.0.0.0', port))
1305@@ -67,48 +64,7 @@
1306 eventlet.wsgi.server(socket, application, custom_pool=self.pool)
1307
1308
1309-class Application(object):
1310-# TODO(gundlach): I think we should toss this class, now that it has no
1311-# purpose.
1312- """Base WSGI application wrapper. Subclasses need to implement __call__."""
1313-
1314- def __call__(self, environ, start_response):
1315- r"""Subclasses will probably want to implement __call__ like this:
1316-
1317- @webob.dec.wsgify
1318- def __call__(self, req):
1319- # Any of the following objects work as responses:
1320-
1321- # Option 1: simple string
1322- res = 'message\n'
1323-
1324- # Option 2: a nicely formatted HTTP exception page
1325- res = exc.HTTPForbidden(detail='Nice try')
1326-
1327- # Option 3: a webob Response object (in case you need to play with
1328- # headers, or you want to be treated like an iterable, or or or)
1329- res = Response();
1330- res.app_iter = open('somefile')
1331-
1332- # Option 4: any wsgi app to be run next
1333- res = self.application
1334-
1335- # Option 5: you can get a Response object for a wsgi app, too, to
1336- # play with headers etc
1337- res = req.get_response(self.application)
1338-
1339- # You can then just return your response...
1340- return res
1341- # ... or set req.response and return None.
1342- req.response = res
1343-
1344- See the end of http://pythonpaste.org/webob/modules/dec.html
1345- for more info.
1346- """
1347- raise NotImplementedError("You must implement __call__")
1348-
1349-
1350-class Middleware(Application):
1351+class Middleware(object):
1352 """
1353 Base WSGI middleware wrapper. These classes require an application to be
1354 initialized that will be called next. By default the middleware will
1355@@ -116,18 +72,38 @@
1356 behavior.
1357 """
1358
1359- def __init__(self, application): # pylint: disable-msg=W0231
1360+ def __init__(self, application):
1361 self.application = application
1362
1363+ def process_request(self, req):
1364+ """
1365+ Called on each request.
1366+
1367+ If this returns None, the next application down the stack will be
1368+ executed. If it returns a response then that response will be returned
1369+ and execution will stop here.
1370+
1371+ """
1372+ return None
1373+
1374+ def process_response(self, response):
1375+ """Do whatever you'd like to the response."""
1376+ return response
1377+
1378 @webob.dec.wsgify
1379- def __call__(self, req): # pylint: disable-msg=W0221
1380- """Override to implement middleware behavior."""
1381- return self.application
1382+ def __call__(self, req):
1383+ response = self.process_request(req)
1384+ if response:
1385+ return response
1386+ response = req.get_response(self.application)
1387+ return self.process_response(response)
1388
1389
1390 class Debug(Middleware):
1391- """Helper class that can be inserted into any WSGI application chain
1392- to get information about the request and response."""
1393+ """
1394+ Helper class that can be inserted into any WSGI application chain
1395+ to get information about the request and response.
1396+ """
1397
1398 @webob.dec.wsgify
1399 def __call__(self, req):
1400
1401=== modified file 'glance/registry/server.py'
1402--- glance/registry/server.py 2011-02-01 21:38:31 +0000
1403+++ glance/registry/server.py 2011-02-10 01:05:12 +0000
1404@@ -175,3 +175,13 @@
1405
1406 image_dict['properties'] = properties
1407 return image_dict
1408+
1409+
1410+def app_factory(global_conf, **local_conf):
1411+ """
1412+ paste.deploy app factory for creating Glance reference implementation
1413+ registry server apps
1414+ """
1415+ conf = global_conf.copy()
1416+ conf.update(local_conf)
1417+ return API(conf)
1418
1419=== modified file 'glance/server.py'
1420--- glance/server.py 2011-02-05 02:32:31 +0000
1421+++ glance/server.py 2011-02-10 01:05:12 +0000
1422@@ -439,3 +439,10 @@
1423 mapper.connect("/images/{id}", controller=controller, action="meta",
1424 conditions=dict(method=["HEAD"]))
1425 super(API, self).__init__(mapper)
1426+
1427+
1428+def app_factory(global_conf, **local_conf):
1429+ """paste.deploy app factory for creating Glance API server apps"""
1430+ conf = global_conf.copy()
1431+ conf.update(local_conf)
1432+ return API(conf)
1433
1434=== modified file 'setup.py'
1435--- setup.py 2011-02-08 18:07:42 +0000
1436+++ setup.py 2011-02-10 01:05:12 +0000
1437@@ -87,6 +87,7 @@
1438 ],
1439 scripts=['bin/glance-api',
1440 'bin/glance-combined',
1441+ 'bin/glance-control',
1442 'bin/glance-manage',
1443 'bin/glance-registry',
1444 'bin/glance-upload'])
1445
1446=== modified file 'tests/unit/test_config.py'
1447--- tests/unit/test_config.py 2011-02-03 02:17:38 +0000
1448+++ tests/unit/test_config.py 2011-02-10 01:05:12 +0000
1449@@ -62,128 +62,4 @@
1450 parser = optparse.OptionParser()
1451 config.add_common_options(parser)
1452 self.assertRaises(SystemExit, config.parse_options,
1453- parser,['--unknown'])
1454-
1455- def test_options_to_conf(self):
1456- parser = optparse.OptionParser()
1457- config.add_common_options(parser)
1458- parsed_options, args = config.parse_options(parser)
1459- conf_options = config.options_to_conf(parsed_options)
1460-
1461- expected_options = {'verbose': 'False', 'debug': 'False'}
1462- self.assertEquals(expected_options, conf_options)
1463-
1464- def test_get_config_file_options(self):
1465-
1466- # Test when no conf files are found...
1467- expected_options = {}
1468- conf_options = config.get_config_file_options(conf_dirs=['tests'])
1469- self.assertEquals(expected_options, conf_options)
1470-
1471- # Test when a conf file is supplied and only DEFAULT
1472- # section is present
1473- with tempfile.NamedTemporaryFile() as f:
1474- contents = """[DEFAULT]
1475-verbose = True
1476-"""
1477- f.write(contents)
1478- f.flush()
1479- conf_file = f.name
1480-
1481- expected_options = {'verbose': 'True'}
1482- conf_options = config.get_config_file_options(conf_file)
1483- self.assertEquals(expected_options, conf_options)
1484-
1485- # Test when a conf file is supplied and it has a DEFAULT
1486- # section and another section called glance-api, with
1487- # no specified app_name when calling get_config_file_options()
1488- with tempfile.NamedTemporaryFile() as f:
1489- contents = """[DEFAULT]
1490-verbose = True
1491-
1492-[glance-api]
1493-default_store = swift
1494-"""
1495- f.write(contents)
1496- f.flush()
1497- conf_file = f.name
1498-
1499- expected_options = {'verbose': 'True',
1500- 'default_store': 'swift'}
1501- conf_options = config.get_config_file_options(conf_file)
1502- self.assertEquals(expected_options, conf_options)
1503-
1504- # Test when a conf file is supplied and it has a DEFAULT
1505- # section and another section called glance-api, with
1506- # specified app_name is NOT glance-api
1507- with tempfile.NamedTemporaryFile() as f:
1508- contents = """[DEFAULT]
1509-verbose = True
1510-
1511-[glance-api]
1512-default_store = swift
1513-"""
1514- f.write(contents)
1515- f.flush()
1516- conf_file = f.name
1517-
1518- expected_options = {'verbose': 'True'}
1519- app_name = 'glance-registry'
1520- conf_options = config.get_config_file_options(conf_file,
1521- app_name=app_name)
1522- self.assertEquals(expected_options, conf_options)
1523-
1524- # Test when a conf file is supplied and it has a DEFAULT
1525- # section and two other sections. Check that the later section
1526- # overrides the value of the former section...
1527- with tempfile.NamedTemporaryFile() as f:
1528- contents = """[DEFAULT]
1529-verbose = True
1530-
1531-[glance-api]
1532-default_store = swift
1533-
1534-[glance-combined]
1535-default_store = s3
1536-"""
1537- f.write(contents)
1538- f.flush()
1539- conf_file = f.name
1540-
1541- expected_options = {'verbose': 'True',
1542- 'default_store': 's3'}
1543- conf_options = config.get_config_file_options(conf_file)
1544- self.assertEquals(expected_options, conf_options)
1545-
1546- def test_parse_options_with_defaults(self):
1547- # Test the integration of parse_options() with a set
1548- # of defaults. These defaults generally come from a
1549- # configuration file
1550- defaults = {'verbose': 'on'}
1551- parser = optparse.OptionParser()
1552- config.add_common_options(parser)
1553- parsed_options, args = config.parse_options(parser, defaults=defaults)
1554-
1555- expected_options = {'verbose': True, 'debug': False}
1556- self.assertEquals(expected_options, parsed_options)
1557-
1558- # Write a sample conf file and merge the conf file defaults
1559- # with the parsed options.
1560- with tempfile.NamedTemporaryFile() as f:
1561- contents = """[DEFAULT]
1562-verbose = True
1563-debug = off
1564-"""
1565- f.write(contents)
1566- f.flush()
1567- conf_file = f.name
1568-
1569- expected_options = {'verbose': True,
1570- 'debug': False}
1571- conf_options = config.get_config_file_options(conf_file)
1572- parser = optparse.OptionParser()
1573- config.add_common_options(parser)
1574- parsed_options, args = config.parse_options(parser,
1575- defaults=conf_options)
1576-
1577- self.assertEquals(expected_options, parsed_options)
1578+ parser, ['--unknown'])
1579
1580=== modified file 'tools/pip-requires'
1581--- tools/pip-requires 2011-02-02 01:43:16 +0000
1582+++ tools/pip-requires 2011-02-10 01:05:12 +0000
1583@@ -4,8 +4,7 @@
1584 pylint==0.19
1585 anyjson
1586 eventlet>=0.9.12
1587-lockfile==0.8
1588-python-daemon==1.5.5
1589+PasteDeploy
1590 routes
1591 webob
1592 wsgiref

Subscribers

People subscribed via source and target branches