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

Proposed by Jay Pipes
Status: Merged
Approved by: Devin Carlen
Approved revision: 62
Merged at revision: 58
Proposed branch: lp:~jaypipes/glance/use-optparse
Merge into: lp:~glance-coresec/glance/cactus-trunk
Prerequisite: lp:~jaypipes/glance/versioning
Diff against target: 1727 lines (+402/-683)
21 files modified
bin/glance-api (+73/-10)
bin/glance-registry (+53/-6)
glance/common/config.py (+54/-0)
glance/common/db/sqlalchemy/__init__.py (+0/-16)
glance/common/db/sqlalchemy/session.py (+0/-49)
glance/common/flags.py (+0/-177)
glance/common/server.py (+38/-51)
glance/common/utils.py (+0/-17)
glance/registry/__init__.py (+18/-24)
glance/registry/db/__init__.py (+0/-9)
glance/registry/db/api.py (+45/-2)
glance/registry/db/models.py (+2/-15)
glance/registry/server.py (+10/-5)
glance/server.py (+34/-20)
glance/store/__init__.py (+23/-0)
glance/store/filesystem.py (+19/-11)
tests/stubs.py (+7/-2)
tests/unit/test_api.py (+26/-35)
tests/unit/test_clients.py (+0/-6)
tests/unit/test_registry_api.py (+0/-227)
tools/pip-requires (+0/-1)
To merge this branch: bzr merge lp:~jaypipes/glance/use-optparse
Reviewer Review Type Date Requested Status
Rick Harris (community) Approve
Devin Carlen (community) Approve
Review via email: mp+47918@code.launchpad.net

Commit message

Replaces gflags with optparse

Description of the change

Replaces gflags with optparse

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

I recommend you removing optparse and use argparse. optparse is marked deprecated as of python 2.7.

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

And how does that go towards our goal of supporting Python 2.6? :)

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

It doesn't, of course. :)

It's just one of those things - if it is easy to use argparse over optparse, then we get some future proofing which is always good.

http://argparse.googlecode.com/svn/trunk/doc/argparse-vs-optparse.html

It looks like it would be pretty trivial to make the change, but I'll leave it up to you at the end of the day.

Either way I'll approve this for now, if you decide to go with argparse it can be a separate merge.

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

I'll throw in my 2c here.

I personally agree with Devin, argparse is a great library, does some things that optparse can't, and is easy enough to include as a dependency. So I'd be a +1 on moving to argparse.

That said, I'm totally cool with going with optparse for now and then moving to argparse when 2.7 becomes more standard.

No biggie.

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

I like argparse too :)

We'll move to argparse fully eventually, prolly sooner than later. I'll bring it up at the next design summit and let's see if we can get consensus on the ML and at the summit on it.

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

Discovered a bug this morning while working on a separate issue but having branched from my local use-optparse tree:

(.glance-venv)jpipes@serialcoder:~/repos/glance/use-optparse$ ./bin/glance-api --daemonize start
(.glance-venv)jpipes@serialcoder:~/repos/glance/use-optparse$ ./bin/glance-api stop
Traceback (most recent call last):
  File "./bin/glance-api", line 116, in <module>
    server.serve('glance-api', main, options, args)
  File "/home/jpipes/repos/glance/use-optparse/glance/common/server.py", line 82, in serve
    daemonize(args, name, main, options)
  File "/home/jpipes/repos/glance/use-optparse/glance/common/server.py", line 136, in daemonize
    files_preserve=files_to_keep):
  File "/home/jpipes/repos/glance/use-optparse/.glance-venv/lib/python2.6/site-packages/daemon/daemon.py", line 352, in __enter__
    self.open()
  File "/home/jpipes/repos/glance/use-optparse/.glance-venv/lib/python2.6/site-packages/daemon/daemon.py", line 344, in open
    self.pidfile.__enter__()
  File "/home/jpipes/repos/glance/use-optparse/.glance-venv/lib/python2.6/site-packages/lockfile.py", line 223, in __enter__
    self.acquire()
  File "/home/jpipes/repos/glance/use-optparse/.glance-venv/lib/python2.6/site-packages/daemon/pidlockfile.py", line 109, in acquire
    super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs)
  File "/home/jpipes/repos/glance/use-optparse/.glance-venv/lib/python2.6/site-packages/daemon/pidlockfile.py", line 59, in acquire
    super(PIDLockFile, self).acquire(*args, **kwargs)
  File "/home/jpipes/repos/glance/use-optparse/.glance-venv/lib/python2.6/site-packages/lockfile.py", line 261, in acquire
    raise LockTimeout
lockfile.LockTimeout

This doesn't happen on Bexar's trunk, so it's a bug introduced by this patch...

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

lgtm

yotta-nit:

70 if len(args):

Could just be `if args`, no need to compute actual number of arguments since it's truthiness suffices.

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

approvage

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

Just noticed this in /glance/common/db/sqlalchemy/session.py:

283 - _ENGINE = create_engine(FLAGS.sql_connection, echo=echo)
284 + _ENGINE = create_engine('sqlite://', echo=echo)

Oops.

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

Oops, I totally missed that too :)

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

Alrighty, fixed the issue with a hard-coded sqlite:// sql_connection setting...

Also removed the glance.common.db.sqlalchemy stuff

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

ok, -really- approving this time ;)

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

lgtm

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/glance-api'
2--- bin/glance-api 2011-01-27 04:04:29 +0000
3+++ bin/glance-api 2011-01-31 19:42:44 +0000
4@@ -3,6 +3,7 @@
5
6 # Copyright 2010 United States Government as represented by the
7 # Administrator of the National Aeronautics and Space Administration.
8+# Copyright 2011 OpenStack LLC.
9 # All Rights Reserved.
10 #
11 # Licensed under the Apache License, Version 2.0 (the "License"); you may
12@@ -21,6 +22,7 @@
13 Glance API Server
14 """
15
16+import optparse
17 import os
18 import sys
19
20@@ -28,14 +30,72 @@
21
22 sys.path.append(ROOT_DIR)
23
24-from glance.common import flags
25-from glance.common import utils
26+from glance import version
27+from glance.common import config
28 from glance.common import server
29-
30-
31-FLAGS = flags.FLAGS
32-flags.DEFINE_string('api_host', '0.0.0.0', 'API server lives at this address')
33-flags.DEFINE_integer('api_port', 9292, 'API server listens on this port')
34+import glance.store
35+
36+
37+DEFAULT_STORE_CHOICES = ['file', 'swift', 's3']
38+
39+
40+def create_options(parser):
41+ """
42+ Sets up the CLI and config-file options that may be
43+ parsed and program commands.
44+
45+ :param parser: The option parser
46+ """
47+ parser.add_option('-v', '--verbose', default=False, dest="verbose",
48+ action="store_true",
49+ help="Print more verbose output")
50+ parser.add_option('-H', '--host',
51+ dest="host", metavar="ADDRESS",
52+ default="0.0.0.0",
53+ help="Address of Glance API server. "
54+ "Default: %default")
55+ parser.add_option('-p', '--port',
56+ dest="port", metavar="PORT", type=int,
57+ default=9292,
58+ help="Port the Glance API server listens on. "
59+ "Default: %default")
60+ parser.add_option('--daemonize', default=False, action="store_true",
61+ help="Daemonize this process")
62+ parser.add_option('--use-syslog', default=True, action="store_true",
63+ help="Output to syslog when daemonizing. "
64+ "Default: %default")
65+ parser.add_option('--logfile', default=None,
66+ metavar="PATH",
67+ help="(Optional) Name of log file to output to.")
68+ parser.add_option("--logdir", default=None,
69+ help="(Optional) The directory to keep log files in "
70+ "(will be prepended to --logfile)")
71+ parser.add_option("--pidfile", default=None,
72+ help="(Optional) Name of pid file for the server")
73+ parser.add_option('--working-directory', '--working-dir',
74+ default=os.path.abspath(os.getcwd()),
75+ help="The working directory. Default: %default")
76+ parser.add_option("--uid", type=int, default=os.getuid(),
77+ help="uid under which to run. Default: %default")
78+ parser.add_option("--gid", type=int, default=os.getgid(),
79+ help="gid under which to run. Default: %default")
80+ parser.add_option('--registry-host',
81+ dest="registry_host", metavar="ADDRESS",
82+ default="0.0.0.0",
83+ help="Address of a Glance Registry server. "
84+ "Default: %default")
85+ parser.add_option('--registry-port',
86+ dest="registry_port", metavar="PORT", type=int,
87+ default=9191,
88+ help="Port a Glance Registry server listens on. "
89+ "Default: %default")
90+ parser.add_option('--default-store', metavar="STORE",
91+ default="file",
92+ choices=DEFAULT_STORE_CHOICES,
93+ help="The backend store that Glance will use to store "
94+ "virtual machine images to. Choices: ('%s') "
95+ "Default: %%default" % "','".join(DEFAULT_STORE_CHOICES))
96+ glance.store.add_options(parser)
97
98
99 def main(_args):
100@@ -44,10 +104,13 @@
101 from glance.common import wsgi
102 from glance.server import API
103 server = wsgi.Server()
104- server.start(API(), FLAGS.api_port, host=FLAGS.api_host)
105+ server.start(API(options), options['port'], host=options['host'])
106 server.wait()
107
108
109 if __name__ == '__main__':
110- utils.default_flagfile()
111- server.serve('glance-api', main)
112+ oparser = optparse.OptionParser(version='%%prog %s'
113+ % version.version_string())
114+ create_options(oparser)
115+ (options, args) = config.parse_options(oparser)
116+ server.serve('glance-api', main, options, args)
117
118=== modified file 'bin/glance-registry'
119--- bin/glance-registry 2011-01-27 04:04:29 +0000
120+++ bin/glance-registry 2011-01-31 19:42:44 +0000
121@@ -22,6 +22,7 @@
122 Reference implementation server for Glance Registry
123 """
124
125+import optparse
126 import os
127 import sys
128
129@@ -29,12 +30,55 @@
130
131 sys.path.append(ROOT_DIR)
132
133-from glance.common import flags
134-from glance.common import utils
135+from glance import version
136+from glance.common import config
137 from glance.common import server
138
139
140-FLAGS = flags.FLAGS
141+def create_options(parser):
142+ """
143+ Sets up the CLI and config-file options that may be
144+ parsed and program commands.
145+
146+ :param parser: The option parser
147+ """
148+ parser.add_option('-v', '--verbose', default=False, dest="verbose",
149+ action="store_true",
150+ help="Print more verbose output")
151+ parser.add_option('-H', '--host',
152+ dest="host", metavar="ADDRESS",
153+ default="0.0.0.0",
154+ help="Address of Glance API server. "
155+ "Default: %default")
156+ parser.add_option('-p', '--port',
157+ dest="port", metavar="PORT", type=int,
158+ default=9191,
159+ help="Port the Glance Registry server listens on. "
160+ "Default: %default")
161+ parser.add_option('--daemonize', default=False, action="store_true",
162+ help="Daemonize this process")
163+ parser.add_option('--use-syslog', default=True, action="store_true",
164+ help="Output to syslog when daemonizing. "
165+ "Default: %default")
166+ parser.add_option('--logfile', default=None,
167+ metavar="PATH",
168+ help="(Optional) Name of log file to output to.")
169+ parser.add_option("--logdir", default=None,
170+ help="(Optional) The directory to keep log files in "
171+ "(will be prepended to --logfile)")
172+ parser.add_option("--pidfile", default=None,
173+ help="(Optional) Name of pid file for the server")
174+ parser.add_option('--working-directory', '--working-dir',
175+ default=os.path.abspath(os.getcwd()),
176+ help="The working directory. Default: %default")
177+ parser.add_option("--uid", type=int, default=os.getuid(),
178+ help="uid under which to run. Default: %default")
179+ parser.add_option("--gid", type=int, default=os.getgid(),
180+ help="gid under which to run. Default: %default")
181+ parser.add_option('--sql-connection', metavar="CONNECTION",
182+ default='sqlite:///glance.sqlite',
183+ help="A valid SQLAlchemy connection string for the "
184+ "registry database. Default: %default")
185
186
187 def main(_args):
188@@ -43,10 +87,13 @@
189 from glance.common import wsgi
190 from glance.registry.server import API
191 server = wsgi.Server()
192- server.start(API(), FLAGS.registry_port, host=FLAGS.registry_host)
193+ server.start(API(options), int(options['port']), host=options['host'])
194 server.wait()
195
196
197 if __name__ == '__main__':
198- utils.default_flagfile()
199- server.serve('glance-registry', main)
200+ oparser = optparse.OptionParser(version='%%prog %s'
201+ % version.version_string())
202+ create_options(oparser)
203+ (options, args) = config.parse_options(oparser)
204+ server.serve('glance-registry', main, options, args)
205
206=== added file 'glance/common/config.py'
207--- glance/common/config.py 1970-01-01 00:00:00 +0000
208+++ glance/common/config.py 2011-01-31 19:42:44 +0000
209@@ -0,0 +1,54 @@
210+#!/usr/bin/env python
211+# vim: tabstop=4 shiftwidth=4 softtabstop=4
212+
213+# Copyright 2011 OpenStack LLC.
214+# All Rights Reserved.
215+#
216+# Licensed under the Apache License, Version 2.0 (the "License"); you may
217+# not use this file except in compliance with the License. You may obtain
218+# a copy of the License at
219+#
220+# http://www.apache.org/licenses/LICENSE-2.0
221+#
222+# Unless required by applicable law or agreed to in writing, software
223+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
224+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
225+# License for the specific language governing permissions and limitations
226+# under the License.
227+
228+
229+def parse_options(parser, cli_args=None):
230+ """
231+ Returns the parsed CLI options, command to run and its arguments, merged
232+ with any same-named options found in a configuration file.
233+
234+ The function returns a tuple of (options, args), where options is a
235+ mapping of option key/str(value) pairs, and args is the set of arguments
236+ (not options) supplied on the command-line.
237+
238+ The reason that the option values are returned as strings only is that
239+ ConfigParser and paste.deploy only accept string values...
240+
241+ :param parser: The option parser
242+ :param cli_args: (Optional) Set of arguments to process. If not present,
243+ sys.argv[1:] is used.
244+ :retval tuple of (options, args)
245+ """
246+
247+ (options, args) = parser.parse_args(cli_args)
248+
249+ return (vars(options), args)
250+
251+
252+def options_to_conf(options):
253+ """
254+ Converts a mapping of options having typed values into
255+ a mapping of configuration options having only stringified values.
256+
257+ This method is used to convert the return of parse_options()[0]
258+ into the configuration mapping that is expected by ConfigParser
259+ and paste.deploy.
260+
261+ :params options: Mapping of typed option key/values
262+ """
263+ return dict([(k, str(v)) for k, v in options.items()])
264
265=== removed directory 'glance/common/db/sqlalchemy'
266=== removed file 'glance/common/db/sqlalchemy/__init__.py'
267--- glance/common/db/sqlalchemy/__init__.py 2011-01-26 17:26:54 +0000
268+++ glance/common/db/sqlalchemy/__init__.py 1970-01-01 00:00:00 +0000
269@@ -1,16 +0,0 @@
270-# vim: tabstop=4 shiftwidth=4 softtabstop=4
271-
272-# Copyright 2010-2011 OpenStack LLC.
273-# All Rights Reserved.
274-#
275-# Licensed under the Apache License, Version 2.0 (the "License"); you may
276-# not use this file except in compliance with the License. You may obtain
277-# a copy of the License at
278-#
279-# http://www.apache.org/licenses/LICENSE-2.0
280-#
281-# Unless required by applicable law or agreed to in writing, software
282-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
283-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
284-# License for the specific language governing permissions and limitations
285-# under the License.
286
287=== removed file 'glance/common/db/sqlalchemy/session.py'
288--- glance/common/db/sqlalchemy/session.py 2011-01-26 17:26:54 +0000
289+++ glance/common/db/sqlalchemy/session.py 1970-01-01 00:00:00 +0000
290@@ -1,49 +0,0 @@
291-# vim: tabstop=4 shiftwidth=4 softtabstop=4
292-
293-# Copyright 2010 United States Government as represented by the
294-# Administrator of the National Aeronautics and Space Administration.
295-# All Rights Reserved.
296-#
297-# Licensed under the Apache License, Version 2.0 (the "License"); you may
298-# not use this file except in compliance with the License. You may obtain
299-# a copy of the License at
300-#
301-# http://www.apache.org/licenses/LICENSE-2.0
302-#
303-# Unless required by applicable law or agreed to in writing, software
304-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
305-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
306-# License for the specific language governing permissions and limitations
307-# under the License.
308-
309-"""
310-Session Handling for SQLAlchemy backend
311-"""
312-
313-from sqlalchemy import create_engine
314-from sqlalchemy.orm import sessionmaker
315-
316-from glance.common import flags
317-
318-FLAGS = flags.FLAGS
319-
320-_ENGINE = None
321-_MAKER = None
322-
323-
324-def get_engine(echo=False):
325- global _ENGINE
326- if not _ENGINE:
327- _ENGINE = create_engine(FLAGS.sql_connection, echo=echo)
328- return _ENGINE
329-
330-
331-def get_session(autocommit=True, expire_on_commit=False):
332- """Helper method to grab session"""
333- global _MAKER
334- if not _MAKER:
335- engine = get_engine()
336- _MAKER = sessionmaker(bind=engine,
337- autocommit=autocommit,
338- expire_on_commit=expire_on_commit)
339- return _MAKER()
340
341=== removed file 'glance/common/flags.py'
342--- glance/common/flags.py 2011-01-26 17:26:54 +0000
343+++ glance/common/flags.py 1970-01-01 00:00:00 +0000
344@@ -1,177 +0,0 @@
345-# vim: tabstop=4 shiftwidth=4 softtabstop=4
346-
347-# Copyright 2010 United States Government as represented by the
348-# Administrator of the National Aeronautics and Space Administration.
349-# All Rights Reserved.
350-#
351-# Licensed under the Apache License, Version 2.0 (the "License"); you may
352-# not use this file except in compliance with the License. You may obtain
353-# a copy of the License at
354-#
355-# http://www.apache.org/licenses/LICENSE-2.0
356-#
357-# Unless required by applicable law or agreed to in writing, software
358-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
359-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
360-# License for the specific language governing permissions and limitations
361-# under the License.
362-
363-"""
364-Package-level global flags are defined here, the rest are defined
365-where they're used.
366-"""
367-
368-import getopt
369-import os
370-import socket
371-import sys
372-
373-import gflags
374-
375-
376-class FlagValues(gflags.FlagValues):
377- """Extension of gflags.FlagValues that allows undefined and runtime flags.
378-
379- Unknown flags will be ignored when parsing the command line, but the
380- command line will be kept so that it can be replayed if new flags are
381- defined after the initial parsing.
382-
383- """
384-
385- def __init__(self):
386- gflags.FlagValues.__init__(self)
387- self.__dict__['__dirty'] = []
388- self.__dict__['__was_already_parsed'] = False
389- self.__dict__['__stored_argv'] = []
390-
391- def __call__(self, argv):
392- # We're doing some hacky stuff here so that we don't have to copy
393- # out all the code of the original verbatim and then tweak a few lines.
394- # We're hijacking the output of getopt so we can still return the
395- # leftover args at the end
396- sneaky_unparsed_args = {"value": None}
397- original_argv = list(argv)
398-
399- if self.IsGnuGetOpt():
400- orig_getopt = getattr(getopt, 'gnu_getopt')
401- orig_name = 'gnu_getopt'
402- else:
403- orig_getopt = getattr(getopt, 'getopt')
404- orig_name = 'getopt'
405-
406- def _sneaky(*args, **kw):
407- optlist, unparsed_args = orig_getopt(*args, **kw)
408- sneaky_unparsed_args['value'] = unparsed_args
409- return optlist, unparsed_args
410-
411- try:
412- setattr(getopt, orig_name, _sneaky)
413- args = gflags.FlagValues.__call__(self, argv)
414- except gflags.UnrecognizedFlagError:
415- # Undefined args were found, for now we don't care so just
416- # act like everything went well
417- # (these three lines are copied pretty much verbatim from the end
418- # of the __call__ function we are wrapping)
419- unparsed_args = sneaky_unparsed_args['value']
420- if unparsed_args:
421- if self.IsGnuGetOpt():
422- args = argv[:1] + unparsed_args
423- else:
424- args = argv[:1] + original_argv[-len(unparsed_args):]
425- else:
426- args = argv[:1]
427- finally:
428- setattr(getopt, orig_name, orig_getopt)
429-
430- # Store the arguments for later, we'll need them for new flags
431- # added at runtime
432- self.__dict__['__stored_argv'] = original_argv
433- self.__dict__['__was_already_parsed'] = True
434- self.ClearDirty()
435- return args
436-
437- def SetDirty(self, name):
438- """Mark a flag as dirty so that accessing it will case a reparse."""
439- self.__dict__['__dirty'].append(name)
440-
441- def IsDirty(self, name):
442- return name in self.__dict__['__dirty']
443-
444- def ClearDirty(self):
445- self.__dict__['__is_dirty'] = []
446-
447- def WasAlreadyParsed(self):
448- return self.__dict__['__was_already_parsed']
449-
450- def ParseNewFlags(self):
451- if '__stored_argv' not in self.__dict__:
452- return
453- new_flags = FlagValues()
454- for k in self.__dict__['__dirty']:
455- new_flags[k] = gflags.FlagValues.__getitem__(self, k)
456-
457- new_flags(self.__dict__['__stored_argv'])
458- for k in self.__dict__['__dirty']:
459- setattr(self, k, getattr(new_flags, k))
460- self.ClearDirty()
461-
462- def __setitem__(self, name, flag):
463- gflags.FlagValues.__setitem__(self, name, flag)
464- if self.WasAlreadyParsed():
465- self.SetDirty(name)
466-
467- def __getitem__(self, name):
468- if self.IsDirty(name):
469- self.ParseNewFlags()
470- return gflags.FlagValues.__getitem__(self, name)
471-
472- def __getattr__(self, name):
473- if self.IsDirty(name):
474- self.ParseNewFlags()
475- return gflags.FlagValues.__getattr__(self, name)
476-
477-
478-FLAGS = FlagValues()
479-
480-
481-def _wrapper(func):
482- def _wrapped(*args, **kw):
483- kw.setdefault('flag_values', FLAGS)
484- func(*args, **kw)
485- _wrapped.func_name = func.func_name
486- return _wrapped
487-
488-
489-DEFINE = _wrapper(gflags.DEFINE)
490-DEFINE_string = _wrapper(gflags.DEFINE_string)
491-DEFINE_integer = _wrapper(gflags.DEFINE_integer)
492-DEFINE_bool = _wrapper(gflags.DEFINE_bool)
493-DEFINE_boolean = _wrapper(gflags.DEFINE_boolean)
494-DEFINE_float = _wrapper(gflags.DEFINE_float)
495-DEFINE_enum = _wrapper(gflags.DEFINE_enum)
496-DEFINE_list = _wrapper(gflags.DEFINE_list)
497-DEFINE_spaceseplist = _wrapper(gflags.DEFINE_spaceseplist)
498-DEFINE_multistring = _wrapper(gflags.DEFINE_multistring)
499-DEFINE_multi_int = _wrapper(gflags.DEFINE_multi_int)
500-
501-
502-def DECLARE(name, module_string, flag_values=FLAGS):
503- if module_string not in sys.modules:
504- __import__(module_string, globals(), locals())
505- if name not in flag_values:
506- raise gflags.UnrecognizedFlag(
507- "%s not defined by %s" % (name, module_string))
508-
509-
510-# __GLOBAL FLAGS ONLY__
511-# Define any app-specific flags in their own files, docs at:
512-# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39
513-
514-# TODO(sirp): move this out to an application specific setting when we create
515-# Nova/Glance common library
516-DEFINE_string('sql_connection',
517- 'sqlite:///%s/glance.sqlite' % os.path.abspath("./"),
518- 'connection string for sql database')
519-DEFINE_bool('verbose', False, 'show debug output')
520-DEFINE_string('default_store', 'file',
521- 'Default storage backend. Default: "file"')
522
523=== modified file 'glance/common/server.py'
524--- glance/common/server.py 2011-01-26 17:26:54 +0000
525+++ glance/common/server.py 2011-01-31 19:42:44 +0000
526@@ -25,30 +25,11 @@
527 import logging
528 import logging.handlers
529 import os
530+import pprint
531 import signal
532 import sys
533 import time
534
535-from glance.common import flags
536-
537-
538-FLAGS = flags.FLAGS
539-flags.DEFINE_bool('daemonize', False, 'daemonize this process')
540-# NOTE(termie): right now I am defaulting to using syslog when we daemonize
541-# it may be better to do something else -shrug-
542-# NOTE(Devin): I think we should let each process have its own log file
543-# and put it in /var/logs/nova/(appname).log
544-# This makes debugging much easier and cuts down on sys log
545-# clutter.
546-flags.DEFINE_bool('use_syslog', True, 'output to syslog when daemonizing')
547-flags.DEFINE_string('logfile', None, 'log file to output to')
548-flags.DEFINE_string('logdir', None, 'directory to keep log files in '
549- '(will be prepended to $logfile)')
550-flags.DEFINE_string('pidfile', None, 'pid file to output to')
551-flags.DEFINE_string('working_directory', './', 'working directory...')
552-flags.DEFINE_integer('uid', os.getuid(), 'uid under which to run')
553-flags.DEFINE_integer('gid', os.getgid(), 'gid under which to run')
554-
555
556 def stop(pidfile):
557 """
558@@ -64,6 +45,7 @@
559
560 # Try killing the daemon process
561 try:
562+ print "Killing process from pidfile %s" % pidfile
563 while 1:
564 os.kill(pid, signal.SIGTERM)
565 time.sleep(0.1)
566@@ -77,53 +59,58 @@
567 sys.exit(1)
568
569
570-def serve(name, main):
571+def serve(name, main, options, args):
572 """Controller for server"""
573- argv = FLAGS(sys.argv)
574-
575- if not FLAGS.pidfile:
576- FLAGS.pidfile = '%s.pid' % name
577-
578- logging.debug("Full set of FLAGS: \n\n\n")
579- for flag in FLAGS:
580- logging.debug("%s : %s", flag, FLAGS.get(flag, None))
581+
582+ pidfile = options['pidfile']
583+ if not pidfile:
584+ options['pidfile'] = '%s.pid' % name
585
586 action = 'start'
587- if len(argv) > 1:
588- action = argv.pop()
589+ if len(args):
590+ action = args.pop()
591
592 if action == 'stop':
593- stop(FLAGS.pidfile)
594+ stop(options['pidfile'])
595 sys.exit()
596 elif action == 'restart':
597- stop(FLAGS.pidfile)
598+ stop(options['pidfile'])
599 elif action == 'start':
600 pass
601 else:
602- print 'usage: %s [options] [start|stop|restart]' % argv[0]
603+ print 'usage: %s [options] [start|stop|restart]' % name
604 sys.exit(1)
605- daemonize(argv, name, main)
606-
607-
608-def daemonize(args, name, main):
609+ daemonize(args, name, main, options)
610+
611+
612+def daemonize(args, name, main, options):
613 """Does the work of daemonizing the process"""
614 logging.getLogger('amqplib').setLevel(logging.WARN)
615+ pidfile = options['pidfile']
616+ logfile = options['logfile']
617+ if not logfile:
618+ logfile = None
619+ logdir = options['logdir']
620+ if not logdir:
621+ logdir = None
622+ daemonize = options['daemonize']
623+ use_syslog = options['use_syslog']
624 files_to_keep = []
625- if FLAGS.daemonize:
626+ if daemonize:
627 logger = logging.getLogger()
628 formatter = logging.Formatter(
629 name + '(%(name)s): %(levelname)s %(message)s')
630- if FLAGS.use_syslog and not FLAGS.logfile:
631+ if use_syslog and not logfile:
632 syslog = logging.handlers.SysLogHandler(address='/dev/log')
633 syslog.setFormatter(formatter)
634 logger.addHandler(syslog)
635 files_to_keep.append(syslog.socket)
636 else:
637- if not FLAGS.logfile:
638- FLAGS.logfile = '%s.log' % name
639- if FLAGS.logdir:
640- FLAGS.logfile = os.path.join(FLAGS.logdir, FLAGS.logfile)
641- logfile = logging.FileHandler(FLAGS.logfile)
642+ if not logfile:
643+ logfile = '%s.log' % name
644+ if logdir:
645+ logfile = os.path.join(logdir, logfile)
646+ logfile = logging.FileHandler(logfile)
647 logfile.setFormatter(formatter)
648 logger.addHandler(logfile)
649 files_to_keep.append(logfile.stream)
650@@ -131,21 +118,21 @@
651 else:
652 stdin, stdout, stderr = sys.stdin, sys.stdout, sys.stderr
653
654- if FLAGS.verbose:
655+ if options['verbose']:
656 logging.getLogger().setLevel(logging.DEBUG)
657 else:
658 logging.getLogger().setLevel(logging.WARNING)
659
660 with daemon.DaemonContext(
661- detach_process=FLAGS.daemonize,
662- working_directory=FLAGS.working_directory,
663- pidfile=pidlockfile.TimeoutPIDLockFile(FLAGS.pidfile,
664+ detach_process=daemonize,
665+ working_directory=options['working_directory'],
666+ pidfile=pidlockfile.TimeoutPIDLockFile(pidfile,
667 acquire_timeout=1,
668 threaded=False),
669 stdin=stdin,
670 stdout=stdout,
671 stderr=stderr,
672- uid=FLAGS.uid,
673- gid=FLAGS.gid,
674+ uid=options['uid'],
675+ gid=options['gid'],
676 files_preserve=files_to_keep):
677 main(args)
678
679=== modified file 'glance/common/utils.py'
680--- glance/common/utils.py 2011-01-26 17:26:54 +0000
681+++ glance/common/utils.py 2011-01-31 19:42:44 +0000
682@@ -30,11 +30,9 @@
683 import sys
684
685 from glance.common import exception
686-from glance.common import flags
687 from glance.common.exception import ProcessExecutionError
688
689
690-FLAGS = flags.FLAGS
691 TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
692
693
694@@ -147,21 +145,6 @@
695 return int(address.split(".")[-1])
696
697
698-def get_my_ip():
699- """Returns the actual ip of the local machine."""
700- if getattr(FLAGS, 'fake_tests', None):
701- return '127.0.0.1'
702- try:
703- csock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
704- csock.connect(('www.google.com', 80))
705- (addr, port) = csock.getsockname()
706- csock.close()
707- return addr
708- except socket.gaierror as ex:
709- logging.warn("Couldn't get IP, using 127.0.0.1 %s", ex)
710- return "127.0.0.1"
711-
712-
713 def isotime(at=None):
714 if not at:
715 at = datetime.datetime.utcnow()
716
717=== modified file 'glance/registry/__init__.py'
718--- glance/registry/__init__.py 2011-01-26 20:44:36 +0000
719+++ glance/registry/__init__.py 2011-01-31 19:42:44 +0000
720@@ -20,45 +20,39 @@
721 Registry API
722 """
723
724-from glance.common import flags
725 from glance.registry import client
726
727-FLAGS = flags.FLAGS
728-
729-# TODO(jaypipes): Separate server flags from client flags
730-# and allow a list of client host/port
731-# combinations
732-flags.DEFINE_string('registry_host', '0.0.0.0',
733- 'Registry server lives at this address')
734-flags.DEFINE_integer('registry_port', 9191,
735- 'Registry server listens on this port')
736-
737-
738-def get_images_list():
739- c = client.RegistryClient(FLAGS.registry_host, FLAGS.registry_port)
740+
741+def get_registry_client(options):
742+ return client.RegistryClient(options['registry_host'],
743+ int(options['registry_port']))
744+
745+
746+def get_images_list(options):
747+ c = get_registry_client(options)
748 return c.get_images()
749
750
751-def get_images_detail():
752- c = client.RegistryClient(FLAGS.registry_host, FLAGS.registry_port)
753+def get_images_detail(options):
754+ c = get_registry_client(options)
755 return c.get_images_detailed()
756
757
758-def get_image_metadata(image_id):
759- c = client.RegistryClient(FLAGS.registry_host, FLAGS.registry_port)
760+def get_image_metadata(options, image_id):
761+ c = get_registry_client(options)
762 return c.get_image(image_id)
763
764
765-def add_image_metadata(image_data):
766- c = client.RegistryClient(FLAGS.registry_host, FLAGS.registry_port)
767+def add_image_metadata(options, image_data):
768+ c = get_registry_client(options)
769 return c.add_image(image_data)
770
771
772-def update_image_metadata(image_id, image_data):
773- c = client.RegistryClient(FLAGS.registry_host, FLAGS.registry_port)
774+def update_image_metadata(options, image_id, image_data):
775+ c = get_registry_client(options)
776 return c.update_image(image_id, image_data)
777
778
779-def delete_image_metadata(image_id):
780- c = client.RegistryClient(FLAGS.registry_host, FLAGS.registry_port)
781+def delete_image_metadata(options, image_id):
782+ c = get_registry_client(options)
783 return c.delete_image(image_id)
784
785=== modified file 'glance/registry/db/__init__.py'
786--- glance/registry/db/__init__.py 2011-01-28 19:39:19 +0000
787+++ glance/registry/db/__init__.py 2011-01-31 19:42:44 +0000
788@@ -16,12 +16,3 @@
789 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
790 # License for the specific language governing permissions and limitations
791 # under the License.
792-
793-"""
794-DB abstraction for Nova and Glance
795-"""
796-
797-from glance.registry.db import models
798-
799-
800-models.register_models()
801
802=== modified file 'glance/registry/db/api.py'
803--- glance/registry/db/api.py 2011-01-28 19:54:10 +0000
804+++ glance/registry/db/api.py 2011-01-31 19:42:44 +0000
805@@ -21,13 +21,19 @@
806 Defines interface for DB access
807 """
808
809+
810+from sqlalchemy import create_engine
811+from sqlalchemy.ext.declarative import declarative_base
812 from sqlalchemy.orm import joinedload
813+from sqlalchemy.orm import sessionmaker
814
815 from glance.common import exception
816 from glance.common import utils
817-from glance.common.db.sqlalchemy.session import get_session
818 from glance.registry.db import models
819
820+_ENGINE = None
821+_MAKER = None
822+BASE = declarative_base()
823
824 # attributes common to all models
825 BASE_MODEL_ATTRS = set(['id', 'created_at', 'updated_at', 'deleted_at',
826@@ -36,7 +42,44 @@
827 IMAGE_ATTRS = BASE_MODEL_ATTRS | set(['name', 'type', 'status', 'size',
828 'is_public', 'location'])
829
830-###################
831+
832+def configure_db(options):
833+ """
834+ Establish the database, create an engine if needed, and
835+ register the models.
836+
837+ :param options: Mapping of configuration options
838+ """
839+ global _ENGINE
840+ if not _ENGINE:
841+ _ENGINE = create_engine(options['sql_connection'],
842+ echo=options['verbose'])
843+ register_models()
844+
845+
846+def get_session(autocommit=True, expire_on_commit=False):
847+ """Helper method to grab session"""
848+ global _MAKER, _ENGINE
849+ if not _MAKER:
850+ assert _ENGINE
851+ _MAKER = sessionmaker(bind=_ENGINE,
852+ autocommit=autocommit,
853+ expire_on_commit=expire_on_commit)
854+ return _MAKER()
855+
856+
857+def register_models():
858+ """Register Models and create properties"""
859+ global _ENGINE
860+ assert _ENGINE
861+ BASE.metadata.create_all(_ENGINE)
862+
863+
864+def unregister_models():
865+ """Unregister Models, useful clearing out data before testing"""
866+ global _ENGINE
867+ assert _ENGINE
868+ BASE.metadata.drop_all(engine)
869
870
871 def image_create(context, values):
872
873=== modified file 'glance/registry/db/models.py'
874--- glance/registry/db/models.py 2011-01-28 19:39:19 +0000
875+++ glance/registry/db/models.py 2011-01-31 19:42:44 +0000
876@@ -29,10 +29,9 @@
877 from sqlalchemy import UniqueConstraint
878 from sqlalchemy.ext.declarative import declarative_base
879
880-from glance.common.db.sqlalchemy.session import get_session, get_engine
881+import glance.registry.db.api
882 from glance.common import exception
883
884-
885 BASE = declarative_base()
886
887
888@@ -50,7 +49,7 @@
889
890 def save(self, session=None):
891 """Save this object"""
892- session = session or get_session()
893+ session = session or glance.registry.db.api.get_session()
894 session.add(self)
895 session.flush()
896
897@@ -126,15 +125,3 @@
898
899 key = Column(String(255), index=True)
900 value = Column(Text)
901-
902-
903-def register_models():
904- """Register Models and create properties"""
905- engine = get_engine()
906- BASE.metadata.create_all(engine)
907-
908-
909-def unregister_models():
910- """Unregister Models, useful clearing out data before testing"""
911- engine = get_engine()
912- BASE.metadata.drop_all(engine)
913
914=== modified file 'glance/registry/server.py'
915--- glance/registry/server.py 2011-01-28 19:39:19 +0000
916+++ glance/registry/server.py 2011-01-31 19:42:44 +0000
917@@ -27,8 +27,12 @@
918 from glance.registry.db import api as db_api
919
920
921-class ImageController(wsgi.Controller):
922- """Image Controller """
923+class Controller(wsgi.Controller):
924+ """Controller for the reference implementation registry server"""
925+
926+ def __init__(self, options):
927+ self.options = options
928+ db_api.configure_db(options)
929
930 def index(self, req):
931 """Return basic information for all public, non-deleted images
932@@ -141,11 +145,12 @@
933 class API(wsgi.Router):
934 """WSGI entry point for all Registry requests."""
935
936- def __init__(self):
937+ def __init__(self, options):
938 mapper = routes.Mapper()
939- mapper.resource("image", "images", controller=ImageController(),
940+ controller = Controller(options)
941+ mapper.resource("image", "images", controller=controller,
942 collection={'detail': 'GET'})
943- mapper.connect("/", controller=ImageController(), action="index")
944+ mapper.connect("/", controller=controller, action="index")
945 super(API, self).__init__(mapper)
946
947
948
949=== modified file 'glance/server.py'
950--- glance/server.py 2011-01-27 04:19:13 +0000
951+++ glance/server.py 2011-01-31 19:42:44 +0000
952@@ -39,7 +39,6 @@
953 HTTPBadRequest)
954
955 from glance.common import exception
956-from glance.common import flags
957 from glance.common import wsgi
958 from glance.store import (get_from_backend,
959 delete_from_backend,
960@@ -50,9 +49,6 @@
961 from glance import util
962
963
964-FLAGS = flags.FLAGS
965-
966-
967 class Controller(wsgi.Controller):
968
969 """
970@@ -73,6 +69,9 @@
971 DELETE /images/<ID> -- Delete the image with id <ID>
972 """
973
974+ def __init__(self, options):
975+ self.options = options
976+
977 def index(self, req):
978 """
979 Returns the following information for all public, available images:
980@@ -92,7 +91,7 @@
981 'type': <TYPE>}, ...
982 ]}
983 """
984- images = registry.get_images_list()
985+ images = registry.get_images_list(self.options)
986 return dict(images=images)
987
988 def detail(self, req):
989@@ -115,7 +114,7 @@
990 'properties': {'distro': 'Ubuntu 10.04 LTS', ...}}, ...
991 ]}
992 """
993- images = registry.get_images_detail()
994+ images = registry.get_images_detail(self.options)
995 return dict(images=images)
996
997 def meta(self, req, id):
998@@ -183,7 +182,8 @@
999 image_meta['size'] = image_meta.get('size', 0)
1000
1001 try:
1002- image_meta = registry.add_image_metadata(image_meta)
1003+ image_meta = registry.add_image_metadata(self.options,
1004+ image_meta)
1005 return image_meta
1006 except exception.Duplicate:
1007 msg = "An image with identifier %s already exists"\
1008@@ -212,21 +212,27 @@
1009 "Content-Type must be application/octet-stream")
1010
1011 image_store = req.headers.get(
1012- 'x-image-meta-store', FLAGS.default_store)
1013+ 'x-image-meta-store', self.options['default_store'])
1014
1015 store = self.get_store_or_400(req, image_store)
1016
1017 image_meta['status'] = 'saving'
1018- registry.update_image_metadata(image_meta['id'], image_meta)
1019+ registry.update_image_metadata(self.options,
1020+ image_meta['id'],
1021+ image_meta)
1022
1023 try:
1024- location, size = store.add(image_meta['id'], req.body_file)
1025+ location, size = store.add(image_meta['id'],
1026+ req.body_file,
1027+ self.options)
1028 # If size returned from store is different from size
1029 # already stored in registry, update the registry with
1030 # the new size of the image
1031 if image_meta.get('size', 0) != size:
1032 image_meta['size'] = size
1033- registry.update_image_metadata(image_meta['id'], image_meta)
1034+ registry.update_image_metadata(self.options,
1035+ image_meta['id'],
1036+ image_meta)
1037 return location
1038 except exception.Duplicate, e:
1039 logging.error("Error adding image to store: %s", str(e))
1040@@ -243,7 +249,9 @@
1041 """
1042 image_meta['location'] = location
1043 image_meta['status'] = 'active'
1044- registry.update_image_metadata(image_meta['id'], image_meta)
1045+ registry.update_image_metadata(self.options,
1046+ image_meta['id'],
1047+ image_meta)
1048
1049 def _kill(self, req, image_meta):
1050 """
1051@@ -253,7 +261,9 @@
1052 :param image_meta: Mapping of metadata about image
1053 """
1054 image_meta['status'] = 'killed'
1055- registry.update_image_metadata(image_meta['id'], image_meta)
1056+ registry.update_image_metadata(self.options,
1057+ image_meta['id'],
1058+ image_meta)
1059
1060 def _safe_kill(self, req, image_meta):
1061 """
1062@@ -351,7 +361,9 @@
1063 raise HTTPConflict("Cannot upload to an unqueued image")
1064
1065 new_image_meta = util.get_image_meta_from_headers(req)
1066- image_meta = registry.update_image_metadata(id, new_image_meta)
1067+ image_meta = registry.update_image_metadata(self.options,
1068+ id,
1069+ new_image_meta)
1070
1071 if has_body:
1072 self._upload_and_activate(req, image_meta)
1073@@ -374,7 +386,7 @@
1074
1075 delete_from_backend(image['location'])
1076
1077- registry.delete_image_metadata(id)
1078+ registry.delete_image_metadata(self.options, id)
1079
1080 def get_image_meta_or_404(self, request, id):
1081 """
1082@@ -387,7 +399,7 @@
1083 :raises HTTPNotFound if image does not exist
1084 """
1085 try:
1086- return registry.get_image_metadata(id)
1087+ return registry.get_image_metadata(self.options, id)
1088 except exception.NotFound:
1089 raise HTTPNotFound(body='Image not found',
1090 request=request,
1091@@ -417,11 +429,13 @@
1092
1093 """WSGI entry point for all Glance API requests."""
1094
1095- def __init__(self):
1096+ def __init__(self, options):
1097+ self.options = options
1098 mapper = routes.Mapper()
1099- mapper.resource("image", "images", controller=Controller(),
1100+ controller = Controller(options)
1101+ mapper.resource("image", "images", controller=controller,
1102 collection={'detail': 'GET'})
1103- mapper.connect("/", controller=Controller(), action="index")
1104- mapper.connect("/images/{id}", controller=Controller(), action="meta",
1105+ mapper.connect("/", controller=controller, action="index")
1106+ mapper.connect("/images/{id}", controller=controller, action="meta",
1107 conditions=dict(method=["HEAD"]))
1108 super(API, self).__init__(mapper)
1109
1110=== modified file 'glance/store/__init__.py'
1111--- glance/store/__init__.py 2011-01-27 04:19:13 +0000
1112+++ glance/store/__init__.py 2011-01-31 19:42:44 +0000
1113@@ -139,3 +139,26 @@
1114 authurl = "https://%s" % '/'.join(path_parts)
1115
1116 return user, key, authurl, container, obj
1117+
1118+
1119+def add_options(parser):
1120+ """
1121+ Adds any configuration options that each store might
1122+ have.
1123+
1124+ :param parser: An optparse.OptionParser object
1125+ :retval None
1126+ """
1127+ # TODO(jaypipes): Remove these imports...
1128+ from glance.store.http import HTTPBackend
1129+ from glance.store.s3 import S3Backend
1130+ from glance.store.swift import SwiftBackend
1131+ from glance.store.filesystem import FilesystemBackend
1132+
1133+ backend_classes = [FilesystemBackend,
1134+ HTTPBackend,
1135+ SwiftBackend,
1136+ S3Backend]
1137+ for b in backend_classes:
1138+ if hasattr(b, 'add_options'):
1139+ b.add_options(parser)
1140
1141=== modified file 'glance/store/filesystem.py'
1142--- glance/store/filesystem.py 2011-01-27 04:19:13 +0000
1143+++ glance/store/filesystem.py 2011-01-31 19:42:44 +0000
1144@@ -23,17 +23,9 @@
1145 import urlparse
1146
1147 from glance.common import exception
1148-from glance.common import flags
1149 import glance.store
1150
1151
1152-flags.DEFINE_string('filesystem_store_datadir', '/var/lib/glance/images/',
1153- 'Location to write image data. '
1154- 'Default: /var/lib/glance/images/')
1155-
1156-FLAGS = flags.FLAGS
1157-
1158-
1159 class ChunkedFile(object):
1160
1161 """
1162@@ -102,21 +94,22 @@
1163 raise exception.NotFound("Image file %s does not exist" % fn)
1164
1165 @classmethod
1166- def add(cls, id, data):
1167+ def add(cls, id, data, options):
1168 """
1169 Stores image data to disk and returns a location that the image was
1170 written to. By default, the backend writes the image data to a file
1171 `/<DATADIR>/<ID>`, where <DATADIR> is the value of
1172- FLAGS.filesystem_store_datadir and <ID> is the supplied image ID.
1173+ options['filesystem_store_datadir'] and <ID> is the supplied image ID.
1174
1175 :param id: The opaque image identifier
1176 :param data: The image data to write, as a file-like object
1177+ :param options: Conf mapping
1178
1179 :retval Tuple with (location, size)
1180 The location that was written, with file:// scheme prepended
1181 and the size in bytes of the data written
1182 """
1183- datadir = FLAGS.filesystem_store_datadir
1184+ datadir = options['filesystem_store_datadir']
1185
1186 if not os.path.exists(datadir):
1187 os.makedirs(datadir)
1188@@ -137,3 +130,18 @@
1189 f.write(buf)
1190
1191 return ('file://%s' % filepath, bytes_written)
1192+
1193+ @classmethod
1194+ def add_options(cls, parser):
1195+ """
1196+ Adds specific configuration options for this store
1197+
1198+ :param parser: An optparse.OptionParser object
1199+ :retval None
1200+ """
1201+
1202+ parser.add_option('--filesystem-store-datadir', metavar="DIR",
1203+ default="/var/lib/glance/images/",
1204+ help="Location to write image data. This directory "
1205+ "should be writeable by the user that runs the "
1206+ "glance-api program. Default: %default")
1207
1208=== modified file 'tests/stubs.py'
1209--- tests/stubs.py 2011-01-28 19:39:19 +0000
1210+++ tests/stubs.py 2011-01-31 19:42:44 +0000
1211@@ -239,7 +239,8 @@
1212 self.req.body = body
1213
1214 def getresponse(self):
1215- res = self.req.get_response(rserver.API())
1216+ res = self.req.get_response(rserver.API({'sql_connection': 'sqlite://',
1217+ 'verbose': True}))
1218
1219 # httplib.Response has a read() method...fake it out
1220 def fake_reader():
1221@@ -284,7 +285,11 @@
1222 self.req.body = body
1223
1224 def getresponse(self):
1225- res = self.req.get_response(server.API())
1226+ res = self.req.get_response(server.API({'verbose': True,
1227+ 'registry_host': '0.0.0.0',
1228+ 'registry_port': '9191',
1229+ 'default_store': 'file',
1230+ 'filesystem_store_datadir': FAKE_FILESYSTEM_ROOTDIR}))
1231
1232 # httplib.Response has a read() method...fake it out
1233 def fake_reader():
1234
1235=== modified file 'tests/unit/test_api.py'
1236--- tests/unit/test_api.py 2011-01-26 17:26:54 +0000
1237+++ tests/unit/test_api.py 2011-01-31 19:42:44 +0000
1238@@ -23,12 +23,9 @@
1239 import webob
1240
1241 from glance import server
1242-from glance.common import flags
1243 from glance.registry import server as rserver
1244 from tests import stubs
1245
1246-FLAGS = flags.FLAGS
1247-
1248
1249 class TestRegistryAPI(unittest.TestCase):
1250 def setUp(self):
1251@@ -37,6 +34,7 @@
1252 stubs.stub_out_registry_and_store_server(self.stubs)
1253 stubs.stub_out_registry_db_image_api(self.stubs)
1254 stubs.stub_out_filesystem_backend()
1255+ self.api = rserver.API({})
1256
1257 def tearDown(self):
1258 """Clear the test environment"""
1259@@ -51,7 +49,7 @@
1260 fixture = {'id': 2,
1261 'name': 'fake image #2'}
1262 req = webob.Request.blank('/')
1263- res = req.get_response(rserver.API())
1264+ res = req.get_response(self.api)
1265 res_dict = json.loads(res.body)
1266 self.assertEquals(res.status_int, 200)
1267
1268@@ -69,7 +67,7 @@
1269 fixture = {'id': 2,
1270 'name': 'fake image #2'}
1271 req = webob.Request.blank('/images')
1272- res = req.get_response(rserver.API())
1273+ res = req.get_response(self.api)
1274 res_dict = json.loads(res.body)
1275 self.assertEquals(res.status_int, 200)
1276
1277@@ -91,7 +89,7 @@
1278 'status': 'active'}
1279
1280 req = webob.Request.blank('/images/detail')
1281- res = req.get_response(rserver.API())
1282+ res = req.get_response(self.api)
1283 res_dict = json.loads(res.body)
1284 self.assertEquals(res.status_int, 200)
1285
1286@@ -112,7 +110,7 @@
1287 req.method = 'POST'
1288 req.body = json.dumps(dict(image=fixture))
1289
1290- res = req.get_response(rserver.API())
1291+ res = req.get_response(self.api)
1292
1293 self.assertEquals(res.status_int, 200)
1294
1295@@ -140,10 +138,7 @@
1296 req.method = 'POST'
1297 req.body = json.dumps(dict(image=fixture))
1298
1299- # TODO(jaypipes): Port Nova's Fault infrastructure
1300- # over to Glance to support exception catching into
1301- # standard HTTP errors.
1302- res = req.get_response(rserver.API())
1303+ res = req.get_response(self.api)
1304 self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
1305
1306 def test_update_image(self):
1307@@ -156,7 +151,7 @@
1308 req.method = 'PUT'
1309 req.body = json.dumps(dict(image=fixture))
1310
1311- res = req.get_response(rserver.API())
1312+ res = req.get_response(self.api)
1313
1314 self.assertEquals(res.status_int, 200)
1315
1316@@ -179,10 +174,7 @@
1317 req.method = 'PUT'
1318 req.body = json.dumps(dict(image=fixture))
1319
1320- # TODO(jaypipes): Port Nova's Fault infrastructure
1321- # over to Glance to support exception catching into
1322- # standard HTTP errors.
1323- res = req.get_response(rserver.API())
1324+ res = req.get_response(self.api)
1325 self.assertEquals(res.status_int,
1326 webob.exc.HTTPNotFound.code)
1327
1328@@ -191,7 +183,7 @@
1329
1330 # Grab the original number of images
1331 req = webob.Request.blank('/images')
1332- res = req.get_response(rserver.API())
1333+ res = req.get_response(self.api)
1334 res_dict = json.loads(res.body)
1335 self.assertEquals(res.status_int, 200)
1336
1337@@ -202,13 +194,13 @@
1338
1339 req.method = 'DELETE'
1340
1341- res = req.get_response(rserver.API())
1342+ res = req.get_response(self.api)
1343
1344 self.assertEquals(res.status_int, 200)
1345
1346 # Verify one less image
1347 req = webob.Request.blank('/images')
1348- res = req.get_response(rserver.API())
1349+ res = req.get_response(self.api)
1350 res_dict = json.loads(res.body)
1351 self.assertEquals(res.status_int, 200)
1352
1353@@ -223,10 +215,7 @@
1354
1355 req.method = 'DELETE'
1356
1357- # TODO(jaypipes): Port Nova's Fault infrastructure
1358- # over to Glance to support exception catching into
1359- # standard HTTP errors.
1360- res = req.get_response(rserver.API())
1361+ res = req.get_response(self.api)
1362 self.assertEquals(res.status_int,
1363 webob.exc.HTTPNotFound.code)
1364
1365@@ -238,12 +227,14 @@
1366 stubs.stub_out_registry_and_store_server(self.stubs)
1367 stubs.stub_out_registry_db_image_api(self.stubs)
1368 stubs.stub_out_filesystem_backend()
1369- self.orig_filesystem_store_datadir = FLAGS.filesystem_store_datadir
1370- FLAGS.filesystem_store_datadir = stubs.FAKE_FILESYSTEM_ROOTDIR
1371+ self.api = server.API({'registry_host': '0.0.0.0',
1372+ 'registry_port': '9191',
1373+ 'sql_connection': 'sqlite://',
1374+ 'default_store': 'file',
1375+ 'filesystem_store_datadir': stubs.FAKE_FILESYSTEM_ROOTDIR})
1376
1377 def tearDown(self):
1378 """Clear the test environment"""
1379- FLAGS.filesystem_store_datadir = self.orig_filesystem_store_datadir
1380 stubs.clean_out_fake_filesystem_backend()
1381 self.stubs.UnsetAll()
1382
1383@@ -256,7 +247,7 @@
1384 req.method = 'POST'
1385 for k, v in fixture_headers.iteritems():
1386 req.headers[k] = v
1387- res = req.get_response(server.API())
1388+ res = req.get_response(self.api)
1389 self.assertEquals(res.status_int, httplib.OK)
1390
1391 res_body = json.loads(res.body)['image']
1392@@ -274,7 +265,7 @@
1393
1394 req.headers['Content-Type'] = 'application/octet-stream'
1395 req.body = "chunk00000remainder"
1396- res = req.get_response(server.API())
1397+ res = req.get_response(self.api)
1398 self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
1399
1400 def test_add_image_basic_file_store(self):
1401@@ -289,7 +280,7 @@
1402
1403 req.headers['Content-Type'] = 'application/octet-stream'
1404 req.body = "chunk00000remainder"
1405- res = req.get_response(server.API())
1406+ res = req.get_response(self.api)
1407 self.assertEquals(res.status_int, 200)
1408
1409 res_body = json.loads(res.body)['image']
1410@@ -302,7 +293,7 @@
1411 'x-image-meta-name': 'fake image #2'}
1412 req = webob.Request.blank("/images/2")
1413 req.method = 'HEAD'
1414- res = req.get_response(server.API())
1415+ res = req.get_response(self.api)
1416 self.assertEquals(res.status_int, 200)
1417
1418 for key, value in expected_headers.iteritems():
1419@@ -310,28 +301,28 @@
1420
1421 def test_show_image_basic(self):
1422 req = webob.Request.blank("/images/2")
1423- res = req.get_response(server.API())
1424+ res = req.get_response(self.api)
1425 self.assertEqual('chunk00000remainder', res.body)
1426
1427 def test_show_non_exists_image(self):
1428 req = webob.Request.blank("/images/42")
1429- res = req.get_response(server.API())
1430+ res = req.get_response(self.api)
1431 self.assertEquals(res.status_int, webob.exc.HTTPNotFound.code)
1432
1433 def test_delete_image(self):
1434 req = webob.Request.blank("/images/2")
1435 req.method = 'DELETE'
1436- res = req.get_response(server.API())
1437+ res = req.get_response(self.api)
1438 self.assertEquals(res.status_int, 200)
1439
1440 req = webob.Request.blank("/images/2")
1441 req.method = 'GET'
1442- res = req.get_response(server.API())
1443+ res = req.get_response(self.api)
1444 self.assertEquals(res.status_int, webob.exc.HTTPNotFound.code,
1445 res.body)
1446
1447 def test_delete_non_exists_image(self):
1448 req = webob.Request.blank("/images/42")
1449 req.method = 'DELETE'
1450- res = req.get_response(server.API())
1451+ res = req.get_response(self.api)
1452 self.assertEquals(res.status_int, webob.exc.HTTPNotFound.code)
1453
1454=== modified file 'tests/unit/test_clients.py'
1455--- tests/unit/test_clients.py 2011-01-26 20:47:01 +0000
1456+++ tests/unit/test_clients.py 2011-01-31 19:42:44 +0000
1457@@ -25,12 +25,9 @@
1458
1459 from glance import client
1460 from glance.registry import client as rclient
1461-from glance.common import flags
1462 from glance.common import exception
1463 from tests import stubs
1464
1465-FLAGS = flags.FLAGS
1466-
1467
1468 class TestBadClients(unittest.TestCase):
1469
1470@@ -273,13 +270,10 @@
1471 stubs.stub_out_registry_db_image_api(self.stubs)
1472 stubs.stub_out_registry_and_store_server(self.stubs)
1473 stubs.stub_out_filesystem_backend()
1474- self.orig_filesystem_store_datadir = FLAGS.filesystem_store_datadir
1475- FLAGS.filesystem_store_datadir = stubs.FAKE_FILESYSTEM_ROOTDIR
1476 self.client = client.Client("0.0.0.0")
1477
1478 def tearDown(self):
1479 """Clear the test environment"""
1480- FLAGS.filesystem_store_datadir = self.orig_filesystem_store_datadir
1481 stubs.clean_out_fake_filesystem_backend()
1482 self.stubs.UnsetAll()
1483
1484
1485=== removed file 'tests/unit/test_registry_api.py'
1486--- tests/unit/test_registry_api.py 2011-01-26 17:26:54 +0000
1487+++ tests/unit/test_registry_api.py 1970-01-01 00:00:00 +0000
1488@@ -1,227 +0,0 @@
1489-# vim: tabstop=4 shiftwidth=4 softtabstop=4
1490-
1491-# Copyright 2010-2011 OpenStack, LLC
1492-# All Rights Reserved.
1493-#
1494-# Licensed under the Apache License, Version 2.0 (the "License"); you may
1495-# not use this file except in compliance with the License. You may obtain
1496-# a copy of the License at
1497-#
1498-# http://www.apache.org/licenses/LICENSE-2.0
1499-#
1500-# Unless required by applicable law or agreed to in writing, software
1501-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1502-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1503-# License for the specific language governing permissions and limitations
1504-# under the License.
1505-
1506-import json
1507-import stubout
1508-import unittest
1509-import webob
1510-
1511-from glance.common import exception
1512-from glance.registry import server
1513-from tests import stubs
1514-
1515-
1516-class TestImageController(unittest.TestCase):
1517- def setUp(self):
1518- """Establish a clean test environment"""
1519- self.stubs = stubout.StubOutForTesting()
1520- stubs.stub_out_registry_db_image_api(self.stubs)
1521-
1522- def tearDown(self):
1523- """Clear the test environment"""
1524- self.stubs.UnsetAll()
1525-
1526- def test_get_root(self):
1527- """Tests that the root registry API returns "index",
1528- which is a list of public images
1529-
1530- """
1531- fixture = {'id': 2,
1532- 'name': 'fake image #2'}
1533- req = webob.Request.blank('/')
1534- res = req.get_response(server.API())
1535- res_dict = json.loads(res.body)
1536- self.assertEquals(res.status_int, 200)
1537-
1538- images = res_dict['images']
1539- self.assertEquals(len(images), 1)
1540-
1541- for k, v in fixture.iteritems():
1542- self.assertEquals(v, images[0][k])
1543-
1544- def test_get_index(self):
1545- """Tests that the /images registry API returns list of
1546- public images
1547-
1548- """
1549- fixture = {'id': 2,
1550- 'name': 'fake image #2'}
1551- req = webob.Request.blank('/images')
1552- res = req.get_response(server.API())
1553- res_dict = json.loads(res.body)
1554- self.assertEquals(res.status_int, 200)
1555-
1556- images = res_dict['images']
1557- self.assertEquals(len(images), 1)
1558-
1559- for k, v in fixture.iteritems():
1560- self.assertEquals(v, images[0][k])
1561-
1562- def test_get_details(self):
1563- """Tests that the /images/detail registry API returns
1564- a mapping containing a list of detailed image information
1565-
1566- """
1567- fixture = {'id': 2,
1568- 'name': 'fake image #2',
1569- 'is_public': True,
1570- 'type': 'kernel',
1571- 'status': 'active',
1572- }
1573- req = webob.Request.blank('/images/detail')
1574- res = req.get_response(server.API())
1575- res_dict = json.loads(res.body)
1576- self.assertEquals(res.status_int, 200)
1577-
1578- images = res_dict['images']
1579- self.assertEquals(len(images), 1)
1580-
1581- for k, v in fixture.iteritems():
1582- self.assertEquals(v, images[0][k])
1583-
1584- def test_create_image(self):
1585- """Tests that the /images POST registry API creates the image"""
1586- fixture = {'name': 'fake public image',
1587- 'is_public': True,
1588- 'type': 'kernel',
1589- }
1590-
1591- req = webob.Request.blank('/images')
1592-
1593- req.method = 'POST'
1594- req.body = json.dumps(dict(image=fixture))
1595-
1596- res = req.get_response(server.API())
1597-
1598- self.assertEquals(res.status_int, 200)
1599-
1600- res_dict = json.loads(res.body)
1601-
1602- for k, v in fixture.iteritems():
1603- self.assertEquals(v, res_dict['image'][k])
1604-
1605- # Test ID auto-assigned properly
1606- self.assertEquals(3, res_dict['image']['id'])
1607-
1608- # Test status was updated properly
1609- self.assertEquals('active', res_dict['image']['status'])
1610-
1611- def test_create_image_with_bad_status(self):
1612- """Tests proper exception is raised if a bad status is set"""
1613- fixture = {'id': 3,
1614- 'name': 'fake public image',
1615- 'is_public': True,
1616- 'type': 'kernel',
1617- 'status': 'bad status',
1618- }
1619-
1620- req = webob.Request.blank('/images')
1621-
1622- req.method = 'POST'
1623- req.body = json.dumps(dict(image=fixture))
1624-
1625- # TODO(jaypipes): Port Nova's Fault infrastructure
1626- # over to Glance to support exception catching into
1627- # standard HTTP errors.
1628- res = req.get_response(server.API())
1629- self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
1630-
1631- def test_update_image(self):
1632- """Tests that the /images PUT registry API updates the image"""
1633- fixture = {'name': 'fake public image #2',
1634- 'type': 'ramdisk',
1635- }
1636-
1637- req = webob.Request.blank('/images/2')
1638-
1639- req.method = 'PUT'
1640- req.body = json.dumps(dict(image=fixture))
1641-
1642- res = req.get_response(server.API())
1643-
1644- self.assertEquals(res.status_int, 200)
1645-
1646- res_dict = json.loads(res.body)
1647-
1648- for k, v in fixture.iteritems():
1649- self.assertEquals(v, res_dict['image'][k])
1650-
1651- def test_update_image_not_existing(self):
1652- """Tests proper exception is raised if attempt to update non-existing
1653- image"""
1654- fixture = {'id': 3,
1655- 'name': 'fake public image',
1656- 'is_public': True,
1657- 'type': 'kernel',
1658- 'status': 'bad status',
1659- }
1660-
1661- req = webob.Request.blank('/images/3')
1662-
1663- req.method = 'PUT'
1664- req.body = json.dumps(dict(image=fixture))
1665-
1666- # TODO(jaypipes): Port Nova's Fault infrastructure
1667- # over to Glance to support exception catching into
1668- # standard HTTP errors.
1669- res = req.get_response(server.API())
1670- self.assertEquals(res.status_int,
1671- webob.exc.HTTPNotFound.code)
1672-
1673- def test_delete_image(self):
1674- """Tests that the /images DELETE registry API deletes the image"""
1675-
1676- # Grab the original number of images
1677- req = webob.Request.blank('/images')
1678- res = req.get_response(server.API())
1679- res_dict = json.loads(res.body)
1680- self.assertEquals(res.status_int, 200)
1681-
1682- orig_num_images = len(res_dict['images'])
1683-
1684- # Delete image #2
1685- req = webob.Request.blank('/images/2')
1686-
1687- req.method = 'DELETE'
1688-
1689- res = req.get_response(server.API())
1690-
1691- self.assertEquals(res.status_int, 200)
1692-
1693- # Verify one less image
1694- req = webob.Request.blank('/images')
1695- res = req.get_response(server.API())
1696- res_dict = json.loads(res.body)
1697- self.assertEquals(res.status_int, 200)
1698-
1699- new_num_images = len(res_dict['images'])
1700- self.assertEquals(new_num_images, orig_num_images - 1)
1701-
1702- def test_delete_image_not_existing(self):
1703- """Tests proper exception is raised if attempt to delete non-existing
1704- image"""
1705-
1706- req = webob.Request.blank('/images/3')
1707-
1708- req.method = 'DELETE'
1709-
1710- # TODO(jaypipes): Port Nova's Fault infrastructure
1711- # over to Glance to support exception catching into
1712- # standard HTTP errors.
1713- res = req.get_response(server.API())
1714- self.assertEquals(res.status_int,
1715- webob.exc.HTTPNotFound.code)
1716
1717=== modified file 'tools/pip-requires'
1718--- tools/pip-requires 2011-01-26 20:44:36 +0000
1719+++ tools/pip-requires 2011-01-31 19:42:44 +0000
1720@@ -6,7 +6,6 @@
1721 eventlet>=0.9.12
1722 lockfile==0.8
1723 python-daemon==1.5.5
1724-python-gflags>=1.3
1725 routes
1726 webob
1727 wsgiref

Subscribers

People subscribed via source and target branches