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
=== modified file 'bin/glance-api'
--- bin/glance-api 2011-01-27 04:04:29 +0000
+++ bin/glance-api 2011-01-31 19:42:44 +0000
@@ -3,6 +3,7 @@
33
4# Copyright 2010 United States Government as represented by the4# Copyright 2010 United States Government as represented by the
5# Administrator of the National Aeronautics and Space Administration.5# Administrator of the National Aeronautics and Space Administration.
6# Copyright 2011 OpenStack LLC.
6# All Rights Reserved.7# All Rights Reserved.
7#8#
8# Licensed under the Apache License, Version 2.0 (the "License"); you may9# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -21,6 +22,7 @@
21Glance API Server22Glance API Server
22"""23"""
2324
25import optparse
24import os26import os
25import sys27import sys
2628
@@ -28,14 +30,72 @@
2830
29sys.path.append(ROOT_DIR)31sys.path.append(ROOT_DIR)
3032
31from glance.common import flags33from glance import version
32from glance.common import utils34from glance.common import config
33from glance.common import server35from glance.common import server
3436import glance.store
3537
36FLAGS = flags.FLAGS38
37flags.DEFINE_string('api_host', '0.0.0.0', 'API server lives at this address')39DEFAULT_STORE_CHOICES = ['file', 'swift', 's3']
38flags.DEFINE_integer('api_port', 9292, 'API server listens on this port')40
41
42def create_options(parser):
43 """
44 Sets up the CLI and config-file options that may be
45 parsed and program commands.
46
47 :param parser: The option parser
48 """
49 parser.add_option('-v', '--verbose', default=False, dest="verbose",
50 action="store_true",
51 help="Print more verbose output")
52 parser.add_option('-H', '--host',
53 dest="host", metavar="ADDRESS",
54 default="0.0.0.0",
55 help="Address of Glance API server. "
56 "Default: %default")
57 parser.add_option('-p', '--port',
58 dest="port", metavar="PORT", type=int,
59 default=9292,
60 help="Port the Glance API server listens on. "
61 "Default: %default")
62 parser.add_option('--daemonize', default=False, action="store_true",
63 help="Daemonize this process")
64 parser.add_option('--use-syslog', default=True, action="store_true",
65 help="Output to syslog when daemonizing. "
66 "Default: %default")
67 parser.add_option('--logfile', default=None,
68 metavar="PATH",
69 help="(Optional) Name of log file to output to.")
70 parser.add_option("--logdir", default=None,
71 help="(Optional) The directory to keep log files in "
72 "(will be prepended to --logfile)")
73 parser.add_option("--pidfile", default=None,
74 help="(Optional) Name of pid file for the server")
75 parser.add_option('--working-directory', '--working-dir',
76 default=os.path.abspath(os.getcwd()),
77 help="The working directory. Default: %default")
78 parser.add_option("--uid", type=int, default=os.getuid(),
79 help="uid under which to run. Default: %default")
80 parser.add_option("--gid", type=int, default=os.getgid(),
81 help="gid under which to run. Default: %default")
82 parser.add_option('--registry-host',
83 dest="registry_host", metavar="ADDRESS",
84 default="0.0.0.0",
85 help="Address of a Glance Registry server. "
86 "Default: %default")
87 parser.add_option('--registry-port',
88 dest="registry_port", metavar="PORT", type=int,
89 default=9191,
90 help="Port a Glance Registry server listens on. "
91 "Default: %default")
92 parser.add_option('--default-store', metavar="STORE",
93 default="file",
94 choices=DEFAULT_STORE_CHOICES,
95 help="The backend store that Glance will use to store "
96 "virtual machine images to. Choices: ('%s') "
97 "Default: %%default" % "','".join(DEFAULT_STORE_CHOICES))
98 glance.store.add_options(parser)
3999
40100
41def main(_args):101def main(_args):
@@ -44,10 +104,13 @@
44 from glance.common import wsgi104 from glance.common import wsgi
45 from glance.server import API105 from glance.server import API
46 server = wsgi.Server()106 server = wsgi.Server()
47 server.start(API(), FLAGS.api_port, host=FLAGS.api_host)107 server.start(API(options), options['port'], host=options['host'])
48 server.wait()108 server.wait()
49109
50110
51if __name__ == '__main__':111if __name__ == '__main__':
52 utils.default_flagfile()112 oparser = optparse.OptionParser(version='%%prog %s'
53 server.serve('glance-api', main)113 % version.version_string())
114 create_options(oparser)
115 (options, args) = config.parse_options(oparser)
116 server.serve('glance-api', main, options, args)
54117
=== modified file 'bin/glance-registry'
--- bin/glance-registry 2011-01-27 04:04:29 +0000
+++ bin/glance-registry 2011-01-31 19:42:44 +0000
@@ -22,6 +22,7 @@
22Reference implementation server for Glance Registry22Reference implementation server for Glance Registry
23"""23"""
2424
25import optparse
25import os26import os
26import sys27import sys
2728
@@ -29,12 +30,55 @@
2930
30sys.path.append(ROOT_DIR)31sys.path.append(ROOT_DIR)
3132
32from glance.common import flags33from glance import version
33from glance.common import utils34from glance.common import config
34from glance.common import server35from glance.common import server
3536
3637
37FLAGS = flags.FLAGS38def create_options(parser):
39 """
40 Sets up the CLI and config-file options that may be
41 parsed and program commands.
42
43 :param parser: The option parser
44 """
45 parser.add_option('-v', '--verbose', default=False, dest="verbose",
46 action="store_true",
47 help="Print more verbose output")
48 parser.add_option('-H', '--host',
49 dest="host", metavar="ADDRESS",
50 default="0.0.0.0",
51 help="Address of Glance API server. "
52 "Default: %default")
53 parser.add_option('-p', '--port',
54 dest="port", metavar="PORT", type=int,
55 default=9191,
56 help="Port the Glance Registry server listens on. "
57 "Default: %default")
58 parser.add_option('--daemonize', default=False, action="store_true",
59 help="Daemonize this process")
60 parser.add_option('--use-syslog', default=True, action="store_true",
61 help="Output to syslog when daemonizing. "
62 "Default: %default")
63 parser.add_option('--logfile', default=None,
64 metavar="PATH",
65 help="(Optional) Name of log file to output to.")
66 parser.add_option("--logdir", default=None,
67 help="(Optional) The directory to keep log files in "
68 "(will be prepended to --logfile)")
69 parser.add_option("--pidfile", default=None,
70 help="(Optional) Name of pid file for the server")
71 parser.add_option('--working-directory', '--working-dir',
72 default=os.path.abspath(os.getcwd()),
73 help="The working directory. Default: %default")
74 parser.add_option("--uid", type=int, default=os.getuid(),
75 help="uid under which to run. Default: %default")
76 parser.add_option("--gid", type=int, default=os.getgid(),
77 help="gid under which to run. Default: %default")
78 parser.add_option('--sql-connection', metavar="CONNECTION",
79 default='sqlite:///glance.sqlite',
80 help="A valid SQLAlchemy connection string for the "
81 "registry database. Default: %default")
3882
3983
40def main(_args):84def main(_args):
@@ -43,10 +87,13 @@
43 from glance.common import wsgi87 from glance.common import wsgi
44 from glance.registry.server import API88 from glance.registry.server import API
45 server = wsgi.Server()89 server = wsgi.Server()
46 server.start(API(), FLAGS.registry_port, host=FLAGS.registry_host)90 server.start(API(options), int(options['port']), host=options['host'])
47 server.wait()91 server.wait()
4892
4993
50if __name__ == '__main__':94if __name__ == '__main__':
51 utils.default_flagfile()95 oparser = optparse.OptionParser(version='%%prog %s'
52 server.serve('glance-registry', main)96 % version.version_string())
97 create_options(oparser)
98 (options, args) = config.parse_options(oparser)
99 server.serve('glance-registry', main, options, args)
53100
=== added file 'glance/common/config.py'
--- glance/common/config.py 1970-01-01 00:00:00 +0000
+++ glance/common/config.py 2011-01-31 19:42:44 +0000
@@ -0,0 +1,54 @@
1#!/usr/bin/env python
2# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
4# Copyright 2011 OpenStack LLC.
5# All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License.
18
19
20def parse_options(parser, cli_args=None):
21 """
22 Returns the parsed CLI options, command to run and its arguments, merged
23 with any same-named options found in a configuration file.
24
25 The function returns a tuple of (options, args), where options is a
26 mapping of option key/str(value) pairs, and args is the set of arguments
27 (not options) supplied on the command-line.
28
29 The reason that the option values are returned as strings only is that
30 ConfigParser and paste.deploy only accept string values...
31
32 :param parser: The option parser
33 :param cli_args: (Optional) Set of arguments to process. If not present,
34 sys.argv[1:] is used.
35 :retval tuple of (options, args)
36 """
37
38 (options, args) = parser.parse_args(cli_args)
39
40 return (vars(options), args)
41
42
43def options_to_conf(options):
44 """
45 Converts a mapping of options having typed values into
46 a mapping of configuration options having only stringified values.
47
48 This method is used to convert the return of parse_options()[0]
49 into the configuration mapping that is expected by ConfigParser
50 and paste.deploy.
51
52 :params options: Mapping of typed option key/values
53 """
54 return dict([(k, str(v)) for k, v in options.items()])
055
=== removed directory 'glance/common/db/sqlalchemy'
=== removed file 'glance/common/db/sqlalchemy/__init__.py'
--- glance/common/db/sqlalchemy/__init__.py 2011-01-26 17:26:54 +0000
+++ glance/common/db/sqlalchemy/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,16 +0,0 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010-2011 OpenStack LLC.
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
170
=== removed file 'glance/common/db/sqlalchemy/session.py'
--- glance/common/db/sqlalchemy/session.py 2011-01-26 17:26:54 +0000
+++ glance/common/db/sqlalchemy/session.py 1970-01-01 00:00:00 +0000
@@ -1,49 +0,0 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010 United States Government as represented by the
4# Administrator of the National Aeronautics and Space Administration.
5# All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License.
18
19"""
20Session Handling for SQLAlchemy backend
21"""
22
23from sqlalchemy import create_engine
24from sqlalchemy.orm import sessionmaker
25
26from glance.common import flags
27
28FLAGS = flags.FLAGS
29
30_ENGINE = None
31_MAKER = None
32
33
34def get_engine(echo=False):
35 global _ENGINE
36 if not _ENGINE:
37 _ENGINE = create_engine(FLAGS.sql_connection, echo=echo)
38 return _ENGINE
39
40
41def get_session(autocommit=True, expire_on_commit=False):
42 """Helper method to grab session"""
43 global _MAKER
44 if not _MAKER:
45 engine = get_engine()
46 _MAKER = sessionmaker(bind=engine,
47 autocommit=autocommit,
48 expire_on_commit=expire_on_commit)
49 return _MAKER()
500
=== removed file 'glance/common/flags.py'
--- glance/common/flags.py 2011-01-26 17:26:54 +0000
+++ glance/common/flags.py 1970-01-01 00:00:00 +0000
@@ -1,177 +0,0 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010 United States Government as represented by the
4# Administrator of the National Aeronautics and Space Administration.
5# All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License.
18
19"""
20Package-level global flags are defined here, the rest are defined
21where they're used.
22"""
23
24import getopt
25import os
26import socket
27import sys
28
29import gflags
30
31
32class FlagValues(gflags.FlagValues):
33 """Extension of gflags.FlagValues that allows undefined and runtime flags.
34
35 Unknown flags will be ignored when parsing the command line, but the
36 command line will be kept so that it can be replayed if new flags are
37 defined after the initial parsing.
38
39 """
40
41 def __init__(self):
42 gflags.FlagValues.__init__(self)
43 self.__dict__['__dirty'] = []
44 self.__dict__['__was_already_parsed'] = False
45 self.__dict__['__stored_argv'] = []
46
47 def __call__(self, argv):
48 # We're doing some hacky stuff here so that we don't have to copy
49 # out all the code of the original verbatim and then tweak a few lines.
50 # We're hijacking the output of getopt so we can still return the
51 # leftover args at the end
52 sneaky_unparsed_args = {"value": None}
53 original_argv = list(argv)
54
55 if self.IsGnuGetOpt():
56 orig_getopt = getattr(getopt, 'gnu_getopt')
57 orig_name = 'gnu_getopt'
58 else:
59 orig_getopt = getattr(getopt, 'getopt')
60 orig_name = 'getopt'
61
62 def _sneaky(*args, **kw):
63 optlist, unparsed_args = orig_getopt(*args, **kw)
64 sneaky_unparsed_args['value'] = unparsed_args
65 return optlist, unparsed_args
66
67 try:
68 setattr(getopt, orig_name, _sneaky)
69 args = gflags.FlagValues.__call__(self, argv)
70 except gflags.UnrecognizedFlagError:
71 # Undefined args were found, for now we don't care so just
72 # act like everything went well
73 # (these three lines are copied pretty much verbatim from the end
74 # of the __call__ function we are wrapping)
75 unparsed_args = sneaky_unparsed_args['value']
76 if unparsed_args:
77 if self.IsGnuGetOpt():
78 args = argv[:1] + unparsed_args
79 else:
80 args = argv[:1] + original_argv[-len(unparsed_args):]
81 else:
82 args = argv[:1]
83 finally:
84 setattr(getopt, orig_name, orig_getopt)
85
86 # Store the arguments for later, we'll need them for new flags
87 # added at runtime
88 self.__dict__['__stored_argv'] = original_argv
89 self.__dict__['__was_already_parsed'] = True
90 self.ClearDirty()
91 return args
92
93 def SetDirty(self, name):
94 """Mark a flag as dirty so that accessing it will case a reparse."""
95 self.__dict__['__dirty'].append(name)
96
97 def IsDirty(self, name):
98 return name in self.__dict__['__dirty']
99
100 def ClearDirty(self):
101 self.__dict__['__is_dirty'] = []
102
103 def WasAlreadyParsed(self):
104 return self.__dict__['__was_already_parsed']
105
106 def ParseNewFlags(self):
107 if '__stored_argv' not in self.__dict__:
108 return
109 new_flags = FlagValues()
110 for k in self.__dict__['__dirty']:
111 new_flags[k] = gflags.FlagValues.__getitem__(self, k)
112
113 new_flags(self.__dict__['__stored_argv'])
114 for k in self.__dict__['__dirty']:
115 setattr(self, k, getattr(new_flags, k))
116 self.ClearDirty()
117
118 def __setitem__(self, name, flag):
119 gflags.FlagValues.__setitem__(self, name, flag)
120 if self.WasAlreadyParsed():
121 self.SetDirty(name)
122
123 def __getitem__(self, name):
124 if self.IsDirty(name):
125 self.ParseNewFlags()
126 return gflags.FlagValues.__getitem__(self, name)
127
128 def __getattr__(self, name):
129 if self.IsDirty(name):
130 self.ParseNewFlags()
131 return gflags.FlagValues.__getattr__(self, name)
132
133
134FLAGS = FlagValues()
135
136
137def _wrapper(func):
138 def _wrapped(*args, **kw):
139 kw.setdefault('flag_values', FLAGS)
140 func(*args, **kw)
141 _wrapped.func_name = func.func_name
142 return _wrapped
143
144
145DEFINE = _wrapper(gflags.DEFINE)
146DEFINE_string = _wrapper(gflags.DEFINE_string)
147DEFINE_integer = _wrapper(gflags.DEFINE_integer)
148DEFINE_bool = _wrapper(gflags.DEFINE_bool)
149DEFINE_boolean = _wrapper(gflags.DEFINE_boolean)
150DEFINE_float = _wrapper(gflags.DEFINE_float)
151DEFINE_enum = _wrapper(gflags.DEFINE_enum)
152DEFINE_list = _wrapper(gflags.DEFINE_list)
153DEFINE_spaceseplist = _wrapper(gflags.DEFINE_spaceseplist)
154DEFINE_multistring = _wrapper(gflags.DEFINE_multistring)
155DEFINE_multi_int = _wrapper(gflags.DEFINE_multi_int)
156
157
158def DECLARE(name, module_string, flag_values=FLAGS):
159 if module_string not in sys.modules:
160 __import__(module_string, globals(), locals())
161 if name not in flag_values:
162 raise gflags.UnrecognizedFlag(
163 "%s not defined by %s" % (name, module_string))
164
165
166# __GLOBAL FLAGS ONLY__
167# Define any app-specific flags in their own files, docs at:
168# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39
169
170# TODO(sirp): move this out to an application specific setting when we create
171# Nova/Glance common library
172DEFINE_string('sql_connection',
173 'sqlite:///%s/glance.sqlite' % os.path.abspath("./"),
174 'connection string for sql database')
175DEFINE_bool('verbose', False, 'show debug output')
176DEFINE_string('default_store', 'file',
177 'Default storage backend. Default: "file"')
1780
=== modified file 'glance/common/server.py'
--- glance/common/server.py 2011-01-26 17:26:54 +0000
+++ glance/common/server.py 2011-01-31 19:42:44 +0000
@@ -25,30 +25,11 @@
25import logging25import logging
26import logging.handlers26import logging.handlers
27import os27import os
28import pprint
28import signal29import signal
29import sys30import sys
30import time31import time
3132
32from glance.common import flags
33
34
35FLAGS = flags.FLAGS
36flags.DEFINE_bool('daemonize', False, 'daemonize this process')
37# NOTE(termie): right now I am defaulting to using syslog when we daemonize
38# it may be better to do something else -shrug-
39# NOTE(Devin): I think we should let each process have its own log file
40# and put it in /var/logs/nova/(appname).log
41# This makes debugging much easier and cuts down on sys log
42# clutter.
43flags.DEFINE_bool('use_syslog', True, 'output to syslog when daemonizing')
44flags.DEFINE_string('logfile', None, 'log file to output to')
45flags.DEFINE_string('logdir', None, 'directory to keep log files in '
46 '(will be prepended to $logfile)')
47flags.DEFINE_string('pidfile', None, 'pid file to output to')
48flags.DEFINE_string('working_directory', './', 'working directory...')
49flags.DEFINE_integer('uid', os.getuid(), 'uid under which to run')
50flags.DEFINE_integer('gid', os.getgid(), 'gid under which to run')
51
5233
53def stop(pidfile):34def stop(pidfile):
54 """35 """
@@ -64,6 +45,7 @@
6445
65 # Try killing the daemon process46 # Try killing the daemon process
66 try:47 try:
48 print "Killing process from pidfile %s" % pidfile
67 while 1:49 while 1:
68 os.kill(pid, signal.SIGTERM)50 os.kill(pid, signal.SIGTERM)
69 time.sleep(0.1)51 time.sleep(0.1)
@@ -77,53 +59,58 @@
77 sys.exit(1)59 sys.exit(1)
7860
7961
80def serve(name, main):62def serve(name, main, options, args):
81 """Controller for server"""63 """Controller for server"""
82 argv = FLAGS(sys.argv)64
8365 pidfile = options['pidfile']
84 if not FLAGS.pidfile:66 if not pidfile:
85 FLAGS.pidfile = '%s.pid' % name67 options['pidfile'] = '%s.pid' % name
86
87 logging.debug("Full set of FLAGS: \n\n\n")
88 for flag in FLAGS:
89 logging.debug("%s : %s", flag, FLAGS.get(flag, None))
9068
91 action = 'start'69 action = 'start'
92 if len(argv) > 1:70 if len(args):
93 action = argv.pop()71 action = args.pop()
9472
95 if action == 'stop':73 if action == 'stop':
96 stop(FLAGS.pidfile)74 stop(options['pidfile'])
97 sys.exit()75 sys.exit()
98 elif action == 'restart':76 elif action == 'restart':
99 stop(FLAGS.pidfile)77 stop(options['pidfile'])
100 elif action == 'start':78 elif action == 'start':
101 pass79 pass
102 else:80 else:
103 print 'usage: %s [options] [start|stop|restart]' % argv[0]81 print 'usage: %s [options] [start|stop|restart]' % name
104 sys.exit(1)82 sys.exit(1)
105 daemonize(argv, name, main)83 daemonize(args, name, main, options)
10684
10785
108def daemonize(args, name, main):86def daemonize(args, name, main, options):
109 """Does the work of daemonizing the process"""87 """Does the work of daemonizing the process"""
110 logging.getLogger('amqplib').setLevel(logging.WARN)88 logging.getLogger('amqplib').setLevel(logging.WARN)
89 pidfile = options['pidfile']
90 logfile = options['logfile']
91 if not logfile:
92 logfile = None
93 logdir = options['logdir']
94 if not logdir:
95 logdir = None
96 daemonize = options['daemonize']
97 use_syslog = options['use_syslog']
111 files_to_keep = []98 files_to_keep = []
112 if FLAGS.daemonize:99 if daemonize:
113 logger = logging.getLogger()100 logger = logging.getLogger()
114 formatter = logging.Formatter(101 formatter = logging.Formatter(
115 name + '(%(name)s): %(levelname)s %(message)s')102 name + '(%(name)s): %(levelname)s %(message)s')
116 if FLAGS.use_syslog and not FLAGS.logfile:103 if use_syslog and not logfile:
117 syslog = logging.handlers.SysLogHandler(address='/dev/log')104 syslog = logging.handlers.SysLogHandler(address='/dev/log')
118 syslog.setFormatter(formatter)105 syslog.setFormatter(formatter)
119 logger.addHandler(syslog)106 logger.addHandler(syslog)
120 files_to_keep.append(syslog.socket)107 files_to_keep.append(syslog.socket)
121 else:108 else:
122 if not FLAGS.logfile:109 if not logfile:
123 FLAGS.logfile = '%s.log' % name110 logfile = '%s.log' % name
124 if FLAGS.logdir:111 if logdir:
125 FLAGS.logfile = os.path.join(FLAGS.logdir, FLAGS.logfile)112 logfile = os.path.join(logdir, logfile)
126 logfile = logging.FileHandler(FLAGS.logfile)113 logfile = logging.FileHandler(logfile)
127 logfile.setFormatter(formatter)114 logfile.setFormatter(formatter)
128 logger.addHandler(logfile)115 logger.addHandler(logfile)
129 files_to_keep.append(logfile.stream)116 files_to_keep.append(logfile.stream)
@@ -131,21 +118,21 @@
131 else:118 else:
132 stdin, stdout, stderr = sys.stdin, sys.stdout, sys.stderr119 stdin, stdout, stderr = sys.stdin, sys.stdout, sys.stderr
133120
134 if FLAGS.verbose:121 if options['verbose']:
135 logging.getLogger().setLevel(logging.DEBUG)122 logging.getLogger().setLevel(logging.DEBUG)
136 else:123 else:
137 logging.getLogger().setLevel(logging.WARNING)124 logging.getLogger().setLevel(logging.WARNING)
138125
139 with daemon.DaemonContext(126 with daemon.DaemonContext(
140 detach_process=FLAGS.daemonize,127 detach_process=daemonize,
141 working_directory=FLAGS.working_directory,128 working_directory=options['working_directory'],
142 pidfile=pidlockfile.TimeoutPIDLockFile(FLAGS.pidfile,129 pidfile=pidlockfile.TimeoutPIDLockFile(pidfile,
143 acquire_timeout=1,130 acquire_timeout=1,
144 threaded=False),131 threaded=False),
145 stdin=stdin,132 stdin=stdin,
146 stdout=stdout,133 stdout=stdout,
147 stderr=stderr,134 stderr=stderr,
148 uid=FLAGS.uid,135 uid=options['uid'],
149 gid=FLAGS.gid,136 gid=options['gid'],
150 files_preserve=files_to_keep):137 files_preserve=files_to_keep):
151 main(args)138 main(args)
152139
=== modified file 'glance/common/utils.py'
--- glance/common/utils.py 2011-01-26 17:26:54 +0000
+++ glance/common/utils.py 2011-01-31 19:42:44 +0000
@@ -30,11 +30,9 @@
30import sys30import sys
3131
32from glance.common import exception32from glance.common import exception
33from glance.common import flags
34from glance.common.exception import ProcessExecutionError33from glance.common.exception import ProcessExecutionError
3534
3635
37FLAGS = flags.FLAGS
38TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"36TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
3937
4038
@@ -147,21 +145,6 @@
147 return int(address.split(".")[-1])145 return int(address.split(".")[-1])
148146
149147
150def get_my_ip():
151 """Returns the actual ip of the local machine."""
152 if getattr(FLAGS, 'fake_tests', None):
153 return '127.0.0.1'
154 try:
155 csock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
156 csock.connect(('www.google.com', 80))
157 (addr, port) = csock.getsockname()
158 csock.close()
159 return addr
160 except socket.gaierror as ex:
161 logging.warn("Couldn't get IP, using 127.0.0.1 %s", ex)
162 return "127.0.0.1"
163
164
165def isotime(at=None):148def isotime(at=None):
166 if not at:149 if not at:
167 at = datetime.datetime.utcnow()150 at = datetime.datetime.utcnow()
168151
=== modified file 'glance/registry/__init__.py'
--- glance/registry/__init__.py 2011-01-26 20:44:36 +0000
+++ glance/registry/__init__.py 2011-01-31 19:42:44 +0000
@@ -20,45 +20,39 @@
20Registry API20Registry API
21"""21"""
2222
23from glance.common import flags
24from glance.registry import client23from glance.registry import client
2524
26FLAGS = flags.FLAGS25
2726def get_registry_client(options):
28# TODO(jaypipes): Separate server flags from client flags27 return client.RegistryClient(options['registry_host'],
29# and allow a list of client host/port28 int(options['registry_port']))
30# combinations29
31flags.DEFINE_string('registry_host', '0.0.0.0',30
32 'Registry server lives at this address')31def get_images_list(options):
33flags.DEFINE_integer('registry_port', 9191,32 c = get_registry_client(options)
34 'Registry server listens on this port')
35
36
37def get_images_list():
38 c = client.RegistryClient(FLAGS.registry_host, FLAGS.registry_port)
39 return c.get_images()33 return c.get_images()
4034
4135
42def get_images_detail():36def get_images_detail(options):
43 c = client.RegistryClient(FLAGS.registry_host, FLAGS.registry_port)37 c = get_registry_client(options)
44 return c.get_images_detailed()38 return c.get_images_detailed()
4539
4640
47def get_image_metadata(image_id):41def get_image_metadata(options, image_id):
48 c = client.RegistryClient(FLAGS.registry_host, FLAGS.registry_port)42 c = get_registry_client(options)
49 return c.get_image(image_id)43 return c.get_image(image_id)
5044
5145
52def add_image_metadata(image_data):46def add_image_metadata(options, image_data):
53 c = client.RegistryClient(FLAGS.registry_host, FLAGS.registry_port)47 c = get_registry_client(options)
54 return c.add_image(image_data)48 return c.add_image(image_data)
5549
5650
57def update_image_metadata(image_id, image_data):51def update_image_metadata(options, image_id, image_data):
58 c = client.RegistryClient(FLAGS.registry_host, FLAGS.registry_port)52 c = get_registry_client(options)
59 return c.update_image(image_id, image_data)53 return c.update_image(image_id, image_data)
6054
6155
62def delete_image_metadata(image_id):56def delete_image_metadata(options, image_id):
63 c = client.RegistryClient(FLAGS.registry_host, FLAGS.registry_port)57 c = get_registry_client(options)
64 return c.delete_image(image_id)58 return c.delete_image(image_id)
6559
=== modified file 'glance/registry/db/__init__.py'
--- glance/registry/db/__init__.py 2011-01-28 19:39:19 +0000
+++ glance/registry/db/__init__.py 2011-01-31 19:42:44 +0000
@@ -16,12 +16,3 @@
16# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the16# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17# License for the specific language governing permissions and limitations17# License for the specific language governing permissions and limitations
18# under the License.18# under the License.
19
20"""
21DB abstraction for Nova and Glance
22"""
23
24from glance.registry.db import models
25
26
27models.register_models()
2819
=== modified file 'glance/registry/db/api.py'
--- glance/registry/db/api.py 2011-01-28 19:54:10 +0000
+++ glance/registry/db/api.py 2011-01-31 19:42:44 +0000
@@ -21,13 +21,19 @@
21Defines interface for DB access21Defines interface for DB access
22"""22"""
2323
24
25from sqlalchemy import create_engine
26from sqlalchemy.ext.declarative import declarative_base
24from sqlalchemy.orm import joinedload27from sqlalchemy.orm import joinedload
28from sqlalchemy.orm import sessionmaker
2529
26from glance.common import exception30from glance.common import exception
27from glance.common import utils31from glance.common import utils
28from glance.common.db.sqlalchemy.session import get_session
29from glance.registry.db import models32from glance.registry.db import models
3033
34_ENGINE = None
35_MAKER = None
36BASE = declarative_base()
3137
32# attributes common to all models38# attributes common to all models
33BASE_MODEL_ATTRS = set(['id', 'created_at', 'updated_at', 'deleted_at',39BASE_MODEL_ATTRS = set(['id', 'created_at', 'updated_at', 'deleted_at',
@@ -36,7 +42,44 @@
36IMAGE_ATTRS = BASE_MODEL_ATTRS | set(['name', 'type', 'status', 'size',42IMAGE_ATTRS = BASE_MODEL_ATTRS | set(['name', 'type', 'status', 'size',
37 'is_public', 'location'])43 'is_public', 'location'])
3844
39###################45
46def configure_db(options):
47 """
48 Establish the database, create an engine if needed, and
49 register the models.
50
51 :param options: Mapping of configuration options
52 """
53 global _ENGINE
54 if not _ENGINE:
55 _ENGINE = create_engine(options['sql_connection'],
56 echo=options['verbose'])
57 register_models()
58
59
60def get_session(autocommit=True, expire_on_commit=False):
61 """Helper method to grab session"""
62 global _MAKER, _ENGINE
63 if not _MAKER:
64 assert _ENGINE
65 _MAKER = sessionmaker(bind=_ENGINE,
66 autocommit=autocommit,
67 expire_on_commit=expire_on_commit)
68 return _MAKER()
69
70
71def register_models():
72 """Register Models and create properties"""
73 global _ENGINE
74 assert _ENGINE
75 BASE.metadata.create_all(_ENGINE)
76
77
78def unregister_models():
79 """Unregister Models, useful clearing out data before testing"""
80 global _ENGINE
81 assert _ENGINE
82 BASE.metadata.drop_all(engine)
4083
4184
42def image_create(context, values):85def image_create(context, values):
4386
=== modified file 'glance/registry/db/models.py'
--- glance/registry/db/models.py 2011-01-28 19:39:19 +0000
+++ glance/registry/db/models.py 2011-01-31 19:42:44 +0000
@@ -29,10 +29,9 @@
29from sqlalchemy import UniqueConstraint29from sqlalchemy import UniqueConstraint
30from sqlalchemy.ext.declarative import declarative_base30from sqlalchemy.ext.declarative import declarative_base
3131
32from glance.common.db.sqlalchemy.session import get_session, get_engine32import glance.registry.db.api
33from glance.common import exception33from glance.common import exception
3434
35
36BASE = declarative_base()35BASE = declarative_base()
3736
3837
@@ -50,7 +49,7 @@
5049
51 def save(self, session=None):50 def save(self, session=None):
52 """Save this object"""51 """Save this object"""
53 session = session or get_session()52 session = session or glance.registry.db.api.get_session()
54 session.add(self)53 session.add(self)
55 session.flush()54 session.flush()
5655
@@ -126,15 +125,3 @@
126125
127 key = Column(String(255), index=True)126 key = Column(String(255), index=True)
128 value = Column(Text)127 value = Column(Text)
129
130
131def register_models():
132 """Register Models and create properties"""
133 engine = get_engine()
134 BASE.metadata.create_all(engine)
135
136
137def unregister_models():
138 """Unregister Models, useful clearing out data before testing"""
139 engine = get_engine()
140 BASE.metadata.drop_all(engine)
141128
=== modified file 'glance/registry/server.py'
--- glance/registry/server.py 2011-01-28 19:39:19 +0000
+++ glance/registry/server.py 2011-01-31 19:42:44 +0000
@@ -27,8 +27,12 @@
27from glance.registry.db import api as db_api27from glance.registry.db import api as db_api
2828
2929
30class ImageController(wsgi.Controller):30class Controller(wsgi.Controller):
31 """Image Controller """31 """Controller for the reference implementation registry server"""
32
33 def __init__(self, options):
34 self.options = options
35 db_api.configure_db(options)
3236
33 def index(self, req):37 def index(self, req):
34 """Return basic information for all public, non-deleted images38 """Return basic information for all public, non-deleted images
@@ -141,11 +145,12 @@
141class API(wsgi.Router):145class API(wsgi.Router):
142 """WSGI entry point for all Registry requests."""146 """WSGI entry point for all Registry requests."""
143147
144 def __init__(self):148 def __init__(self, options):
145 mapper = routes.Mapper()149 mapper = routes.Mapper()
146 mapper.resource("image", "images", controller=ImageController(),150 controller = Controller(options)
151 mapper.resource("image", "images", controller=controller,
147 collection={'detail': 'GET'})152 collection={'detail': 'GET'})
148 mapper.connect("/", controller=ImageController(), action="index")153 mapper.connect("/", controller=controller, action="index")
149 super(API, self).__init__(mapper)154 super(API, self).__init__(mapper)
150155
151156
152157
=== modified file 'glance/server.py'
--- glance/server.py 2011-01-27 04:19:13 +0000
+++ glance/server.py 2011-01-31 19:42:44 +0000
@@ -39,7 +39,6 @@
39 HTTPBadRequest)39 HTTPBadRequest)
4040
41from glance.common import exception41from glance.common import exception
42from glance.common import flags
43from glance.common import wsgi42from glance.common import wsgi
44from glance.store import (get_from_backend,43from glance.store import (get_from_backend,
45 delete_from_backend,44 delete_from_backend,
@@ -50,9 +49,6 @@
50from glance import util49from glance import util
5150
5251
53FLAGS = flags.FLAGS
54
55
56class Controller(wsgi.Controller):52class Controller(wsgi.Controller):
5753
58 """54 """
@@ -73,6 +69,9 @@
73 DELETE /images/<ID> -- Delete the image with id <ID>69 DELETE /images/<ID> -- Delete the image with id <ID>
74 """70 """
7571
72 def __init__(self, options):
73 self.options = options
74
76 def index(self, req):75 def index(self, req):
77 """76 """
78 Returns the following information for all public, available images:77 Returns the following information for all public, available images:
@@ -92,7 +91,7 @@
92 'type': <TYPE>}, ...91 'type': <TYPE>}, ...
93 ]}92 ]}
94 """93 """
95 images = registry.get_images_list()94 images = registry.get_images_list(self.options)
96 return dict(images=images)95 return dict(images=images)
9796
98 def detail(self, req):97 def detail(self, req):
@@ -115,7 +114,7 @@
115 'properties': {'distro': 'Ubuntu 10.04 LTS', ...}}, ...114 'properties': {'distro': 'Ubuntu 10.04 LTS', ...}}, ...
116 ]}115 ]}
117 """116 """
118 images = registry.get_images_detail()117 images = registry.get_images_detail(self.options)
119 return dict(images=images)118 return dict(images=images)
120119
121 def meta(self, req, id):120 def meta(self, req, id):
@@ -183,7 +182,8 @@
183 image_meta['size'] = image_meta.get('size', 0)182 image_meta['size'] = image_meta.get('size', 0)
184183
185 try:184 try:
186 image_meta = registry.add_image_metadata(image_meta)185 image_meta = registry.add_image_metadata(self.options,
186 image_meta)
187 return image_meta187 return image_meta
188 except exception.Duplicate:188 except exception.Duplicate:
189 msg = "An image with identifier %s already exists"\189 msg = "An image with identifier %s already exists"\
@@ -212,21 +212,27 @@
212 "Content-Type must be application/octet-stream")212 "Content-Type must be application/octet-stream")
213213
214 image_store = req.headers.get(214 image_store = req.headers.get(
215 'x-image-meta-store', FLAGS.default_store)215 'x-image-meta-store', self.options['default_store'])
216216
217 store = self.get_store_or_400(req, image_store)217 store = self.get_store_or_400(req, image_store)
218218
219 image_meta['status'] = 'saving'219 image_meta['status'] = 'saving'
220 registry.update_image_metadata(image_meta['id'], image_meta)220 registry.update_image_metadata(self.options,
221 image_meta['id'],
222 image_meta)
221223
222 try:224 try:
223 location, size = store.add(image_meta['id'], req.body_file)225 location, size = store.add(image_meta['id'],
226 req.body_file,
227 self.options)
224 # If size returned from store is different from size228 # If size returned from store is different from size
225 # already stored in registry, update the registry with229 # already stored in registry, update the registry with
226 # the new size of the image230 # the new size of the image
227 if image_meta.get('size', 0) != size:231 if image_meta.get('size', 0) != size:
228 image_meta['size'] = size232 image_meta['size'] = size
229 registry.update_image_metadata(image_meta['id'], image_meta)233 registry.update_image_metadata(self.options,
234 image_meta['id'],
235 image_meta)
230 return location236 return location
231 except exception.Duplicate, e:237 except exception.Duplicate, e:
232 logging.error("Error adding image to store: %s", str(e))238 logging.error("Error adding image to store: %s", str(e))
@@ -243,7 +249,9 @@
243 """249 """
244 image_meta['location'] = location250 image_meta['location'] = location
245 image_meta['status'] = 'active'251 image_meta['status'] = 'active'
246 registry.update_image_metadata(image_meta['id'], image_meta)252 registry.update_image_metadata(self.options,
253 image_meta['id'],
254 image_meta)
247255
248 def _kill(self, req, image_meta):256 def _kill(self, req, image_meta):
249 """257 """
@@ -253,7 +261,9 @@
253 :param image_meta: Mapping of metadata about image261 :param image_meta: Mapping of metadata about image
254 """262 """
255 image_meta['status'] = 'killed'263 image_meta['status'] = 'killed'
256 registry.update_image_metadata(image_meta['id'], image_meta)264 registry.update_image_metadata(self.options,
265 image_meta['id'],
266 image_meta)
257267
258 def _safe_kill(self, req, image_meta):268 def _safe_kill(self, req, image_meta):
259 """269 """
@@ -351,7 +361,9 @@
351 raise HTTPConflict("Cannot upload to an unqueued image")361 raise HTTPConflict("Cannot upload to an unqueued image")
352362
353 new_image_meta = util.get_image_meta_from_headers(req)363 new_image_meta = util.get_image_meta_from_headers(req)
354 image_meta = registry.update_image_metadata(id, new_image_meta)364 image_meta = registry.update_image_metadata(self.options,
365 id,
366 new_image_meta)
355367
356 if has_body:368 if has_body:
357 self._upload_and_activate(req, image_meta)369 self._upload_and_activate(req, image_meta)
@@ -374,7 +386,7 @@
374386
375 delete_from_backend(image['location'])387 delete_from_backend(image['location'])
376388
377 registry.delete_image_metadata(id)389 registry.delete_image_metadata(self.options, id)
378390
379 def get_image_meta_or_404(self, request, id):391 def get_image_meta_or_404(self, request, id):
380 """392 """
@@ -387,7 +399,7 @@
387 :raises HTTPNotFound if image does not exist399 :raises HTTPNotFound if image does not exist
388 """400 """
389 try:401 try:
390 return registry.get_image_metadata(id)402 return registry.get_image_metadata(self.options, id)
391 except exception.NotFound:403 except exception.NotFound:
392 raise HTTPNotFound(body='Image not found',404 raise HTTPNotFound(body='Image not found',
393 request=request,405 request=request,
@@ -417,11 +429,13 @@
417429
418 """WSGI entry point for all Glance API requests."""430 """WSGI entry point for all Glance API requests."""
419431
420 def __init__(self):432 def __init__(self, options):
433 self.options = options
421 mapper = routes.Mapper()434 mapper = routes.Mapper()
422 mapper.resource("image", "images", controller=Controller(),435 controller = Controller(options)
436 mapper.resource("image", "images", controller=controller,
423 collection={'detail': 'GET'})437 collection={'detail': 'GET'})
424 mapper.connect("/", controller=Controller(), action="index")438 mapper.connect("/", controller=controller, action="index")
425 mapper.connect("/images/{id}", controller=Controller(), action="meta",439 mapper.connect("/images/{id}", controller=controller, action="meta",
426 conditions=dict(method=["HEAD"]))440 conditions=dict(method=["HEAD"]))
427 super(API, self).__init__(mapper)441 super(API, self).__init__(mapper)
428442
=== modified file 'glance/store/__init__.py'
--- glance/store/__init__.py 2011-01-27 04:19:13 +0000
+++ glance/store/__init__.py 2011-01-31 19:42:44 +0000
@@ -139,3 +139,26 @@
139 authurl = "https://%s" % '/'.join(path_parts)139 authurl = "https://%s" % '/'.join(path_parts)
140140
141 return user, key, authurl, container, obj141 return user, key, authurl, container, obj
142
143
144def add_options(parser):
145 """
146 Adds any configuration options that each store might
147 have.
148
149 :param parser: An optparse.OptionParser object
150 :retval None
151 """
152 # TODO(jaypipes): Remove these imports...
153 from glance.store.http import HTTPBackend
154 from glance.store.s3 import S3Backend
155 from glance.store.swift import SwiftBackend
156 from glance.store.filesystem import FilesystemBackend
157
158 backend_classes = [FilesystemBackend,
159 HTTPBackend,
160 SwiftBackend,
161 S3Backend]
162 for b in backend_classes:
163 if hasattr(b, 'add_options'):
164 b.add_options(parser)
142165
=== modified file 'glance/store/filesystem.py'
--- glance/store/filesystem.py 2011-01-27 04:19:13 +0000
+++ glance/store/filesystem.py 2011-01-31 19:42:44 +0000
@@ -23,17 +23,9 @@
23import urlparse23import urlparse
2424
25from glance.common import exception25from glance.common import exception
26from glance.common import flags
27import glance.store26import glance.store
2827
2928
30flags.DEFINE_string('filesystem_store_datadir', '/var/lib/glance/images/',
31 'Location to write image data. '
32 'Default: /var/lib/glance/images/')
33
34FLAGS = flags.FLAGS
35
36
37class ChunkedFile(object):29class ChunkedFile(object):
3830
39 """31 """
@@ -102,21 +94,22 @@
102 raise exception.NotFound("Image file %s does not exist" % fn)94 raise exception.NotFound("Image file %s does not exist" % fn)
10395
104 @classmethod96 @classmethod
105 def add(cls, id, data):97 def add(cls, id, data, options):
106 """98 """
107 Stores image data to disk and returns a location that the image was99 Stores image data to disk and returns a location that the image was
108 written to. By default, the backend writes the image data to a file100 written to. By default, the backend writes the image data to a file
109 `/<DATADIR>/<ID>`, where <DATADIR> is the value of101 `/<DATADIR>/<ID>`, where <DATADIR> is the value of
110 FLAGS.filesystem_store_datadir and <ID> is the supplied image ID.102 options['filesystem_store_datadir'] and <ID> is the supplied image ID.
111103
112 :param id: The opaque image identifier104 :param id: The opaque image identifier
113 :param data: The image data to write, as a file-like object105 :param data: The image data to write, as a file-like object
106 :param options: Conf mapping
114107
115 :retval Tuple with (location, size)108 :retval Tuple with (location, size)
116 The location that was written, with file:// scheme prepended109 The location that was written, with file:// scheme prepended
117 and the size in bytes of the data written110 and the size in bytes of the data written
118 """111 """
119 datadir = FLAGS.filesystem_store_datadir112 datadir = options['filesystem_store_datadir']
120113
121 if not os.path.exists(datadir):114 if not os.path.exists(datadir):
122 os.makedirs(datadir)115 os.makedirs(datadir)
@@ -137,3 +130,18 @@
137 f.write(buf)130 f.write(buf)
138131
139 return ('file://%s' % filepath, bytes_written)132 return ('file://%s' % filepath, bytes_written)
133
134 @classmethod
135 def add_options(cls, parser):
136 """
137 Adds specific configuration options for this store
138
139 :param parser: An optparse.OptionParser object
140 :retval None
141 """
142
143 parser.add_option('--filesystem-store-datadir', metavar="DIR",
144 default="/var/lib/glance/images/",
145 help="Location to write image data. This directory "
146 "should be writeable by the user that runs the "
147 "glance-api program. Default: %default")
140148
=== modified file 'tests/stubs.py'
--- tests/stubs.py 2011-01-28 19:39:19 +0000
+++ tests/stubs.py 2011-01-31 19:42:44 +0000
@@ -239,7 +239,8 @@
239 self.req.body = body239 self.req.body = body
240240
241 def getresponse(self):241 def getresponse(self):
242 res = self.req.get_response(rserver.API())242 res = self.req.get_response(rserver.API({'sql_connection': 'sqlite://',
243 'verbose': True}))
243244
244 # httplib.Response has a read() method...fake it out245 # httplib.Response has a read() method...fake it out
245 def fake_reader():246 def fake_reader():
@@ -284,7 +285,11 @@
284 self.req.body = body285 self.req.body = body
285286
286 def getresponse(self):287 def getresponse(self):
287 res = self.req.get_response(server.API())288 res = self.req.get_response(server.API({'verbose': True,
289 'registry_host': '0.0.0.0',
290 'registry_port': '9191',
291 'default_store': 'file',
292 'filesystem_store_datadir': FAKE_FILESYSTEM_ROOTDIR}))
288293
289 # httplib.Response has a read() method...fake it out294 # httplib.Response has a read() method...fake it out
290 def fake_reader():295 def fake_reader():
291296
=== modified file 'tests/unit/test_api.py'
--- tests/unit/test_api.py 2011-01-26 17:26:54 +0000
+++ tests/unit/test_api.py 2011-01-31 19:42:44 +0000
@@ -23,12 +23,9 @@
23import webob23import webob
2424
25from glance import server25from glance import server
26from glance.common import flags
27from glance.registry import server as rserver26from glance.registry import server as rserver
28from tests import stubs27from tests import stubs
2928
30FLAGS = flags.FLAGS
31
3229
33class TestRegistryAPI(unittest.TestCase):30class TestRegistryAPI(unittest.TestCase):
34 def setUp(self):31 def setUp(self):
@@ -37,6 +34,7 @@
37 stubs.stub_out_registry_and_store_server(self.stubs)34 stubs.stub_out_registry_and_store_server(self.stubs)
38 stubs.stub_out_registry_db_image_api(self.stubs)35 stubs.stub_out_registry_db_image_api(self.stubs)
39 stubs.stub_out_filesystem_backend()36 stubs.stub_out_filesystem_backend()
37 self.api = rserver.API({})
4038
41 def tearDown(self):39 def tearDown(self):
42 """Clear the test environment"""40 """Clear the test environment"""
@@ -51,7 +49,7 @@
51 fixture = {'id': 2,49 fixture = {'id': 2,
52 'name': 'fake image #2'}50 'name': 'fake image #2'}
53 req = webob.Request.blank('/')51 req = webob.Request.blank('/')
54 res = req.get_response(rserver.API())52 res = req.get_response(self.api)
55 res_dict = json.loads(res.body)53 res_dict = json.loads(res.body)
56 self.assertEquals(res.status_int, 200)54 self.assertEquals(res.status_int, 200)
5755
@@ -69,7 +67,7 @@
69 fixture = {'id': 2,67 fixture = {'id': 2,
70 'name': 'fake image #2'}68 'name': 'fake image #2'}
71 req = webob.Request.blank('/images')69 req = webob.Request.blank('/images')
72 res = req.get_response(rserver.API())70 res = req.get_response(self.api)
73 res_dict = json.loads(res.body)71 res_dict = json.loads(res.body)
74 self.assertEquals(res.status_int, 200)72 self.assertEquals(res.status_int, 200)
7573
@@ -91,7 +89,7 @@
91 'status': 'active'}89 'status': 'active'}
9290
93 req = webob.Request.blank('/images/detail')91 req = webob.Request.blank('/images/detail')
94 res = req.get_response(rserver.API())92 res = req.get_response(self.api)
95 res_dict = json.loads(res.body)93 res_dict = json.loads(res.body)
96 self.assertEquals(res.status_int, 200)94 self.assertEquals(res.status_int, 200)
9795
@@ -112,7 +110,7 @@
112 req.method = 'POST'110 req.method = 'POST'
113 req.body = json.dumps(dict(image=fixture))111 req.body = json.dumps(dict(image=fixture))
114112
115 res = req.get_response(rserver.API())113 res = req.get_response(self.api)
116114
117 self.assertEquals(res.status_int, 200)115 self.assertEquals(res.status_int, 200)
118116
@@ -140,10 +138,7 @@
140 req.method = 'POST'138 req.method = 'POST'
141 req.body = json.dumps(dict(image=fixture))139 req.body = json.dumps(dict(image=fixture))
142140
143 # TODO(jaypipes): Port Nova's Fault infrastructure141 res = req.get_response(self.api)
144 # over to Glance to support exception catching into
145 # standard HTTP errors.
146 res = req.get_response(rserver.API())
147 self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)142 self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
148143
149 def test_update_image(self):144 def test_update_image(self):
@@ -156,7 +151,7 @@
156 req.method = 'PUT'151 req.method = 'PUT'
157 req.body = json.dumps(dict(image=fixture))152 req.body = json.dumps(dict(image=fixture))
158153
159 res = req.get_response(rserver.API())154 res = req.get_response(self.api)
160155
161 self.assertEquals(res.status_int, 200)156 self.assertEquals(res.status_int, 200)
162157
@@ -179,10 +174,7 @@
179 req.method = 'PUT'174 req.method = 'PUT'
180 req.body = json.dumps(dict(image=fixture))175 req.body = json.dumps(dict(image=fixture))
181176
182 # TODO(jaypipes): Port Nova's Fault infrastructure177 res = req.get_response(self.api)
183 # over to Glance to support exception catching into
184 # standard HTTP errors.
185 res = req.get_response(rserver.API())
186 self.assertEquals(res.status_int,178 self.assertEquals(res.status_int,
187 webob.exc.HTTPNotFound.code)179 webob.exc.HTTPNotFound.code)
188180
@@ -191,7 +183,7 @@
191183
192 # Grab the original number of images184 # Grab the original number of images
193 req = webob.Request.blank('/images')185 req = webob.Request.blank('/images')
194 res = req.get_response(rserver.API())186 res = req.get_response(self.api)
195 res_dict = json.loads(res.body)187 res_dict = json.loads(res.body)
196 self.assertEquals(res.status_int, 200)188 self.assertEquals(res.status_int, 200)
197189
@@ -202,13 +194,13 @@
202194
203 req.method = 'DELETE'195 req.method = 'DELETE'
204196
205 res = req.get_response(rserver.API())197 res = req.get_response(self.api)
206198
207 self.assertEquals(res.status_int, 200)199 self.assertEquals(res.status_int, 200)
208200
209 # Verify one less image201 # Verify one less image
210 req = webob.Request.blank('/images')202 req = webob.Request.blank('/images')
211 res = req.get_response(rserver.API())203 res = req.get_response(self.api)
212 res_dict = json.loads(res.body)204 res_dict = json.loads(res.body)
213 self.assertEquals(res.status_int, 200)205 self.assertEquals(res.status_int, 200)
214206
@@ -223,10 +215,7 @@
223215
224 req.method = 'DELETE'216 req.method = 'DELETE'
225217
226 # TODO(jaypipes): Port Nova's Fault infrastructure218 res = req.get_response(self.api)
227 # over to Glance to support exception catching into
228 # standard HTTP errors.
229 res = req.get_response(rserver.API())
230 self.assertEquals(res.status_int,219 self.assertEquals(res.status_int,
231 webob.exc.HTTPNotFound.code)220 webob.exc.HTTPNotFound.code)
232221
@@ -238,12 +227,14 @@
238 stubs.stub_out_registry_and_store_server(self.stubs)227 stubs.stub_out_registry_and_store_server(self.stubs)
239 stubs.stub_out_registry_db_image_api(self.stubs)228 stubs.stub_out_registry_db_image_api(self.stubs)
240 stubs.stub_out_filesystem_backend()229 stubs.stub_out_filesystem_backend()
241 self.orig_filesystem_store_datadir = FLAGS.filesystem_store_datadir230 self.api = server.API({'registry_host': '0.0.0.0',
242 FLAGS.filesystem_store_datadir = stubs.FAKE_FILESYSTEM_ROOTDIR231 'registry_port': '9191',
232 'sql_connection': 'sqlite://',
233 'default_store': 'file',
234 'filesystem_store_datadir': stubs.FAKE_FILESYSTEM_ROOTDIR})
243235
244 def tearDown(self):236 def tearDown(self):
245 """Clear the test environment"""237 """Clear the test environment"""
246 FLAGS.filesystem_store_datadir = self.orig_filesystem_store_datadir
247 stubs.clean_out_fake_filesystem_backend()238 stubs.clean_out_fake_filesystem_backend()
248 self.stubs.UnsetAll()239 self.stubs.UnsetAll()
249240
@@ -256,7 +247,7 @@
256 req.method = 'POST'247 req.method = 'POST'
257 for k, v in fixture_headers.iteritems():248 for k, v in fixture_headers.iteritems():
258 req.headers[k] = v249 req.headers[k] = v
259 res = req.get_response(server.API())250 res = req.get_response(self.api)
260 self.assertEquals(res.status_int, httplib.OK)251 self.assertEquals(res.status_int, httplib.OK)
261252
262 res_body = json.loads(res.body)['image']253 res_body = json.loads(res.body)['image']
@@ -274,7 +265,7 @@
274265
275 req.headers['Content-Type'] = 'application/octet-stream'266 req.headers['Content-Type'] = 'application/octet-stream'
276 req.body = "chunk00000remainder"267 req.body = "chunk00000remainder"
277 res = req.get_response(server.API())268 res = req.get_response(self.api)
278 self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)269 self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
279270
280 def test_add_image_basic_file_store(self):271 def test_add_image_basic_file_store(self):
@@ -289,7 +280,7 @@
289280
290 req.headers['Content-Type'] = 'application/octet-stream'281 req.headers['Content-Type'] = 'application/octet-stream'
291 req.body = "chunk00000remainder"282 req.body = "chunk00000remainder"
292 res = req.get_response(server.API())283 res = req.get_response(self.api)
293 self.assertEquals(res.status_int, 200)284 self.assertEquals(res.status_int, 200)
294285
295 res_body = json.loads(res.body)['image']286 res_body = json.loads(res.body)['image']
@@ -302,7 +293,7 @@
302 'x-image-meta-name': 'fake image #2'}293 'x-image-meta-name': 'fake image #2'}
303 req = webob.Request.blank("/images/2")294 req = webob.Request.blank("/images/2")
304 req.method = 'HEAD'295 req.method = 'HEAD'
305 res = req.get_response(server.API())296 res = req.get_response(self.api)
306 self.assertEquals(res.status_int, 200)297 self.assertEquals(res.status_int, 200)
307298
308 for key, value in expected_headers.iteritems():299 for key, value in expected_headers.iteritems():
@@ -310,28 +301,28 @@
310301
311 def test_show_image_basic(self):302 def test_show_image_basic(self):
312 req = webob.Request.blank("/images/2")303 req = webob.Request.blank("/images/2")
313 res = req.get_response(server.API())304 res = req.get_response(self.api)
314 self.assertEqual('chunk00000remainder', res.body)305 self.assertEqual('chunk00000remainder', res.body)
315306
316 def test_show_non_exists_image(self):307 def test_show_non_exists_image(self):
317 req = webob.Request.blank("/images/42")308 req = webob.Request.blank("/images/42")
318 res = req.get_response(server.API())309 res = req.get_response(self.api)
319 self.assertEquals(res.status_int, webob.exc.HTTPNotFound.code)310 self.assertEquals(res.status_int, webob.exc.HTTPNotFound.code)
320311
321 def test_delete_image(self):312 def test_delete_image(self):
322 req = webob.Request.blank("/images/2")313 req = webob.Request.blank("/images/2")
323 req.method = 'DELETE'314 req.method = 'DELETE'
324 res = req.get_response(server.API())315 res = req.get_response(self.api)
325 self.assertEquals(res.status_int, 200)316 self.assertEquals(res.status_int, 200)
326317
327 req = webob.Request.blank("/images/2")318 req = webob.Request.blank("/images/2")
328 req.method = 'GET'319 req.method = 'GET'
329 res = req.get_response(server.API())320 res = req.get_response(self.api)
330 self.assertEquals(res.status_int, webob.exc.HTTPNotFound.code,321 self.assertEquals(res.status_int, webob.exc.HTTPNotFound.code,
331 res.body)322 res.body)
332323
333 def test_delete_non_exists_image(self):324 def test_delete_non_exists_image(self):
334 req = webob.Request.blank("/images/42")325 req = webob.Request.blank("/images/42")
335 req.method = 'DELETE'326 req.method = 'DELETE'
336 res = req.get_response(server.API())327 res = req.get_response(self.api)
337 self.assertEquals(res.status_int, webob.exc.HTTPNotFound.code)328 self.assertEquals(res.status_int, webob.exc.HTTPNotFound.code)
338329
=== modified file 'tests/unit/test_clients.py'
--- tests/unit/test_clients.py 2011-01-26 20:47:01 +0000
+++ tests/unit/test_clients.py 2011-01-31 19:42:44 +0000
@@ -25,12 +25,9 @@
2525
26from glance import client26from glance import client
27from glance.registry import client as rclient27from glance.registry import client as rclient
28from glance.common import flags
29from glance.common import exception28from glance.common import exception
30from tests import stubs29from tests import stubs
3130
32FLAGS = flags.FLAGS
33
3431
35class TestBadClients(unittest.TestCase):32class TestBadClients(unittest.TestCase):
3633
@@ -273,13 +270,10 @@
273 stubs.stub_out_registry_db_image_api(self.stubs)270 stubs.stub_out_registry_db_image_api(self.stubs)
274 stubs.stub_out_registry_and_store_server(self.stubs)271 stubs.stub_out_registry_and_store_server(self.stubs)
275 stubs.stub_out_filesystem_backend()272 stubs.stub_out_filesystem_backend()
276 self.orig_filesystem_store_datadir = FLAGS.filesystem_store_datadir
277 FLAGS.filesystem_store_datadir = stubs.FAKE_FILESYSTEM_ROOTDIR
278 self.client = client.Client("0.0.0.0")273 self.client = client.Client("0.0.0.0")
279274
280 def tearDown(self):275 def tearDown(self):
281 """Clear the test environment"""276 """Clear the test environment"""
282 FLAGS.filesystem_store_datadir = self.orig_filesystem_store_datadir
283 stubs.clean_out_fake_filesystem_backend()277 stubs.clean_out_fake_filesystem_backend()
284 self.stubs.UnsetAll()278 self.stubs.UnsetAll()
285279
286280
=== removed file 'tests/unit/test_registry_api.py'
--- tests/unit/test_registry_api.py 2011-01-26 17:26:54 +0000
+++ tests/unit/test_registry_api.py 1970-01-01 00:00:00 +0000
@@ -1,227 +0,0 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010-2011 OpenStack, LLC
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18import json
19import stubout
20import unittest
21import webob
22
23from glance.common import exception
24from glance.registry import server
25from tests import stubs
26
27
28class TestImageController(unittest.TestCase):
29 def setUp(self):
30 """Establish a clean test environment"""
31 self.stubs = stubout.StubOutForTesting()
32 stubs.stub_out_registry_db_image_api(self.stubs)
33
34 def tearDown(self):
35 """Clear the test environment"""
36 self.stubs.UnsetAll()
37
38 def test_get_root(self):
39 """Tests that the root registry API returns "index",
40 which is a list of public images
41
42 """
43 fixture = {'id': 2,
44 'name': 'fake image #2'}
45 req = webob.Request.blank('/')
46 res = req.get_response(server.API())
47 res_dict = json.loads(res.body)
48 self.assertEquals(res.status_int, 200)
49
50 images = res_dict['images']
51 self.assertEquals(len(images), 1)
52
53 for k, v in fixture.iteritems():
54 self.assertEquals(v, images[0][k])
55
56 def test_get_index(self):
57 """Tests that the /images registry API returns list of
58 public images
59
60 """
61 fixture = {'id': 2,
62 'name': 'fake image #2'}
63 req = webob.Request.blank('/images')
64 res = req.get_response(server.API())
65 res_dict = json.loads(res.body)
66 self.assertEquals(res.status_int, 200)
67
68 images = res_dict['images']
69 self.assertEquals(len(images), 1)
70
71 for k, v in fixture.iteritems():
72 self.assertEquals(v, images[0][k])
73
74 def test_get_details(self):
75 """Tests that the /images/detail registry API returns
76 a mapping containing a list of detailed image information
77
78 """
79 fixture = {'id': 2,
80 'name': 'fake image #2',
81 'is_public': True,
82 'type': 'kernel',
83 'status': 'active',
84 }
85 req = webob.Request.blank('/images/detail')
86 res = req.get_response(server.API())
87 res_dict = json.loads(res.body)
88 self.assertEquals(res.status_int, 200)
89
90 images = res_dict['images']
91 self.assertEquals(len(images), 1)
92
93 for k, v in fixture.iteritems():
94 self.assertEquals(v, images[0][k])
95
96 def test_create_image(self):
97 """Tests that the /images POST registry API creates the image"""
98 fixture = {'name': 'fake public image',
99 'is_public': True,
100 'type': 'kernel',
101 }
102
103 req = webob.Request.blank('/images')
104
105 req.method = 'POST'
106 req.body = json.dumps(dict(image=fixture))
107
108 res = req.get_response(server.API())
109
110 self.assertEquals(res.status_int, 200)
111
112 res_dict = json.loads(res.body)
113
114 for k, v in fixture.iteritems():
115 self.assertEquals(v, res_dict['image'][k])
116
117 # Test ID auto-assigned properly
118 self.assertEquals(3, res_dict['image']['id'])
119
120 # Test status was updated properly
121 self.assertEquals('active', res_dict['image']['status'])
122
123 def test_create_image_with_bad_status(self):
124 """Tests proper exception is raised if a bad status is set"""
125 fixture = {'id': 3,
126 'name': 'fake public image',
127 'is_public': True,
128 'type': 'kernel',
129 'status': 'bad status',
130 }
131
132 req = webob.Request.blank('/images')
133
134 req.method = 'POST'
135 req.body = json.dumps(dict(image=fixture))
136
137 # TODO(jaypipes): Port Nova's Fault infrastructure
138 # over to Glance to support exception catching into
139 # standard HTTP errors.
140 res = req.get_response(server.API())
141 self.assertEquals(res.status_int, webob.exc.HTTPBadRequest.code)
142
143 def test_update_image(self):
144 """Tests that the /images PUT registry API updates the image"""
145 fixture = {'name': 'fake public image #2',
146 'type': 'ramdisk',
147 }
148
149 req = webob.Request.blank('/images/2')
150
151 req.method = 'PUT'
152 req.body = json.dumps(dict(image=fixture))
153
154 res = req.get_response(server.API())
155
156 self.assertEquals(res.status_int, 200)
157
158 res_dict = json.loads(res.body)
159
160 for k, v in fixture.iteritems():
161 self.assertEquals(v, res_dict['image'][k])
162
163 def test_update_image_not_existing(self):
164 """Tests proper exception is raised if attempt to update non-existing
165 image"""
166 fixture = {'id': 3,
167 'name': 'fake public image',
168 'is_public': True,
169 'type': 'kernel',
170 'status': 'bad status',
171 }
172
173 req = webob.Request.blank('/images/3')
174
175 req.method = 'PUT'
176 req.body = json.dumps(dict(image=fixture))
177
178 # TODO(jaypipes): Port Nova's Fault infrastructure
179 # over to Glance to support exception catching into
180 # standard HTTP errors.
181 res = req.get_response(server.API())
182 self.assertEquals(res.status_int,
183 webob.exc.HTTPNotFound.code)
184
185 def test_delete_image(self):
186 """Tests that the /images DELETE registry API deletes the image"""
187
188 # Grab the original number of images
189 req = webob.Request.blank('/images')
190 res = req.get_response(server.API())
191 res_dict = json.loads(res.body)
192 self.assertEquals(res.status_int, 200)
193
194 orig_num_images = len(res_dict['images'])
195
196 # Delete image #2
197 req = webob.Request.blank('/images/2')
198
199 req.method = 'DELETE'
200
201 res = req.get_response(server.API())
202
203 self.assertEquals(res.status_int, 200)
204
205 # Verify one less image
206 req = webob.Request.blank('/images')
207 res = req.get_response(server.API())
208 res_dict = json.loads(res.body)
209 self.assertEquals(res.status_int, 200)
210
211 new_num_images = len(res_dict['images'])
212 self.assertEquals(new_num_images, orig_num_images - 1)
213
214 def test_delete_image_not_existing(self):
215 """Tests proper exception is raised if attempt to delete non-existing
216 image"""
217
218 req = webob.Request.blank('/images/3')
219
220 req.method = 'DELETE'
221
222 # TODO(jaypipes): Port Nova's Fault infrastructure
223 # over to Glance to support exception catching into
224 # standard HTTP errors.
225 res = req.get_response(server.API())
226 self.assertEquals(res.status_int,
227 webob.exc.HTTPNotFound.code)
2280
=== modified file 'tools/pip-requires'
--- tools/pip-requires 2011-01-26 20:44:36 +0000
+++ tools/pip-requires 2011-01-31 19:42:44 +0000
@@ -6,7 +6,6 @@
6eventlet>=0.9.126eventlet>=0.9.12
7lockfile==0.87lockfile==0.8
8python-daemon==1.5.58python-daemon==1.5.5
9python-gflags>=1.3
10routes9routes
11webob10webob
12wsgiref11wsgiref

Subscribers

People subscribed via source and target branches