From c540d6d7fef1da68a42c16ac1f9a44337661bb0d Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Thu, 10 Mar 2011 20:12:58 -0500 Subject: Added version attribute to RequestContext class. Set the version in the nova.context object at the middleware level. Prototyped how we can serialize ip addresses based on the version. --- nova/api/openstack/auth.py | 3 ++- nova/api/openstack/servers.py | 38 ++++++++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 13 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index de8905f46..320443935 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -57,7 +57,8 @@ class AuthMiddleware(wsgi.Middleware): return faults.Fault(webob.exc.HTTPUnauthorized()) project = self.auth.get_project(FLAGS.default_project) - req.environ['nova.context'] = context.RequestContext(user, project) + req.environ['nova.context'] = context.RequestContext(user, project, + version=req.script_name.replace('/v', '')) return self.application def has_authentication(self, req): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index dc28a0782..ec542bc92 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -39,7 +39,7 @@ LOG = logging.getLogger('server') FLAGS = flags.FLAGS -def _translate_detail_keys(inst): +def _translate_detail_keys(req, inst): """ Coerces into dictionary format, mapping everything to Rackspace-like attributes for return""" power_mapping = { @@ -54,6 +54,7 @@ def _translate_detail_keys(inst): power_state.CRASHED: 'error', power_state.FAILED: 'error'} inst_dict = {} + version = req.environ['nova.context'].version mapped_keys = dict(status='state', imageId='image_id', flavorId='instance_type', name='display_name', id='id') @@ -62,15 +63,7 @@ def _translate_detail_keys(inst): inst_dict[k] = inst[v] inst_dict['status'] = power_mapping[inst_dict['status']] - inst_dict['addresses'] = dict(public=[], private=[]) - - # grab single private fixed ip - private_ips = utils.get_from_path(inst, 'fixed_ip/address') - inst_dict['addresses']['private'] = private_ips - - # grab all public floating ips - public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') - inst_dict['addresses']['public'] = public_ips + inst_dict['addresses'] = _addresses_generator(version)(inst) # Return the metadata as a dictionary metadata = {} @@ -91,6 +84,27 @@ def _translate_keys(inst): return dict(server=dict(id=inst['id'], name=inst['display_name'])) +def _addresses_generator(version): + + def _gen_addresses_1_0(inst): + private_ips = utils.get_from_path(inst, 'fixed_ip/address') + public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + return dict(public=public_ips, private=private_ips) + + def _gen_addresses_1_1(inst): + private_ips = utils.get_from_path(inst, 'fixed_ip/address') + private_ips = [dict(version=4, addr=a) for a in private_ips] + public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + public_ips = [dict(version=4, addr=a) for a in public_ips] + return dict(public=public_ips, private=private_ips) + + dispatch_table = { + '1.0': _gen_addresses_1_0, + '1.1': _gen_addresses_1_1, + } + + return dispatch_table[version] + class Controller(wsgi.Controller): """ The Server API controller for the OpenStack API """ @@ -120,14 +134,14 @@ class Controller(wsgi.Controller): """ instance_list = self.compute_api.get_all(req.environ['nova.context']) limited_list = common.limited(instance_list, req) - res = [entity_maker(inst)['server'] for inst in limited_list] + res = [entity_maker(req, inst)['server'] for inst in limited_list] return dict(servers=res) def show(self, req, id): """ Returns server details by server id """ try: instance = self.compute_api.get(req.environ['nova.context'], id) - return _translate_detail_keys(instance) + return _translate_detail_keys(req, instance) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) -- cgit From 76b5871adbb1bfc2e3d2a1151a00fa654c45953d Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Thu, 10 Mar 2011 20:35:37 -0500 Subject: _translate_keys now needs one more argument, the request object. --- nova/api/openstack/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index ec542bc92..bd317f995 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -78,7 +78,7 @@ def _translate_detail_keys(req, inst): return dict(server=inst_dict) -def _translate_keys(inst): +def _translate_keys(req, inst): """ Coerces into dictionary format, excluding all model attributes save for id and name """ return dict(server=dict(id=inst['id'], name=inst['display_name'])) @@ -193,7 +193,7 @@ class Controller(wsgi.Controller): metadata=metadata, onset_files=env.get('onset_files', [])) - server = _translate_keys(instances[0]) + server = _translate_keys(req, instances[0]) password = "%s%s" % (server['server']['name'][:4], utils.generate_password(12)) server['server']['adminPass'] = password -- cgit From a5415e8fc40eaa82761532e5ba83c3800cf9ed78 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Fri, 11 Mar 2011 12:45:53 -0500 Subject: Need to set version to '1.0' in the nova.context in test code for tests to be happy. --- nova/api/openstack/auth.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 320443935..8461a8059 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -57,8 +57,9 @@ class AuthMiddleware(wsgi.Middleware): return faults.Fault(webob.exc.HTTPUnauthorized()) project = self.auth.get_project(FLAGS.default_project) + version = req.path.split('/')[1].replace('v', '') req.environ['nova.context'] = context.RequestContext(user, project, - version=req.script_name.replace('/v', '')) + version=version) return self.application def has_authentication(self, req): -- cgit From d03b169e2343fc13f37324f0136835ae54f85569 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Fri, 11 Mar 2011 16:04:54 -0500 Subject: Added support for ips resource: /servers/1/ips Refactored implmentation of how the servers response model is generated. --- nova/api/openstack/servers.py | 85 +++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 36 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index bd317f995..b486dfebb 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -39,9 +39,41 @@ LOG = logging.getLogger('server') FLAGS = flags.FLAGS -def _translate_detail_keys(req, inst): +def _translate_keys(req, inst): + """ Coerces into dictionary format, excluding all model attributes + save for id and name """ + return dict(server=dict(id=inst['id'], name=inst['display_name'])) + + +def _build_addresses_10(inst): + private_ips = utils.get_from_path(inst, 'fixed_ip/address') + public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + return dict(public=public_ips, private=private_ips) + + +def _build_addresses_11(inst): + private_ips = utils.get_from_path(inst, 'fixed_ip/address') + private_ips = [dict(version=4, addr=a) for a in private_ips] + public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + public_ips = [dict(version=4, addr=a) for a in public_ips] + return dict(public=public_ips, private=private_ips) + + +def addresses_builder(req): + version = req.environ['nova.context'].version + if version == '1.1': + return _build_addresses_11 + else: + return _build_addresses_10 + + +def build_server(req, inst, is_detail): """ Coerces into dictionary format, mapping everything to Rackspace-like attributes for return""" + + if not is_detail: + return dict(server=dict(id=inst['id'], name=inst['display_name'])) + power_mapping = { None: 'build', power_state.NOSTATE: 'build', @@ -63,7 +95,7 @@ def _translate_detail_keys(req, inst): inst_dict[k] = inst[v] inst_dict['status'] = power_mapping[inst_dict['status']] - inst_dict['addresses'] = _addresses_generator(version)(inst) + inst_dict['addresses'] = addresses_builder(req)(inst) # Return the metadata as a dictionary metadata = {} @@ -78,33 +110,6 @@ def _translate_detail_keys(req, inst): return dict(server=inst_dict) -def _translate_keys(req, inst): - """ Coerces into dictionary format, excluding all model attributes - save for id and name """ - return dict(server=dict(id=inst['id'], name=inst['display_name'])) - - -def _addresses_generator(version): - - def _gen_addresses_1_0(inst): - private_ips = utils.get_from_path(inst, 'fixed_ip/address') - public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') - return dict(public=public_ips, private=private_ips) - - def _gen_addresses_1_1(inst): - private_ips = utils.get_from_path(inst, 'fixed_ip/address') - private_ips = [dict(version=4, addr=a) for a in private_ips] - public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') - public_ips = [dict(version=4, addr=a) for a in public_ips] - return dict(public=public_ips, private=private_ips) - - dispatch_table = { - '1.0': _gen_addresses_1_0, - '1.1': _gen_addresses_1_1, - } - - return dispatch_table[version] - class Controller(wsgi.Controller): """ The Server API controller for the OpenStack API """ @@ -119,29 +124,37 @@ class Controller(wsgi.Controller): self._image_service = utils.import_object(FLAGS.image_service) super(Controller, self).__init__() + def ips(self, req, id): + try: + instance = self.compute_api.get(req.environ['nova.context'], id) + return addresses_builder(req)(instance) + except exception.NotFound: + return faults.Fault(exc.HTTPNotFound()) + def index(self, req): """ Returns a list of server names and ids for a given user """ - return self._items(req, entity_maker=_translate_keys) + return self._items(req, is_detail=False) def detail(self, req): """ Returns a list of server details for a given user """ - return self._items(req, entity_maker=_translate_detail_keys) + return self._items(req, is_detail=True) - def _items(self, req, entity_maker): + def _items(self, req, is_detail): """Returns a list of servers for a given user. - entity_maker - either _translate_detail_keys or _translate_keys + builder - the response model builder """ instance_list = self.compute_api.get_all(req.environ['nova.context']) limited_list = common.limited(instance_list, req) - res = [entity_maker(req, inst)['server'] for inst in limited_list] + res = [build_server(req, inst, is_detail)['server'] + for inst in limited_list] return dict(servers=res) def show(self, req, id): """ Returns server details by server id """ try: instance = self.compute_api.get(req.environ['nova.context'], id) - return _translate_detail_keys(req, instance) + return build_server(req, instance, is_detail=True) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -193,7 +206,7 @@ class Controller(wsgi.Controller): metadata=metadata, onset_files=env.get('onset_files', [])) - server = _translate_keys(req, instances[0]) + server = build_server(req, instances[0], is_detail=False) password = "%s%s" % (server['server']['name'][:4], utils.generate_password(12)) server['server']['adminPass'] = password -- cgit From 87f7356e98dbb4d01305785ed8209f44b525ff2c Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Fri, 11 Mar 2011 19:21:34 -0500 Subject: Removed _translate_keys() functions since it is no longer used. Moved private top level functions to bottom of module. --- nova/api/openstack/servers.py | 148 ++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 77 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index b486dfebb..940c2c47e 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -34,82 +34,9 @@ import nova.api.openstack LOG = logging.getLogger('server') - - FLAGS = flags.FLAGS -def _translate_keys(req, inst): - """ Coerces into dictionary format, excluding all model attributes - save for id and name """ - return dict(server=dict(id=inst['id'], name=inst['display_name'])) - - -def _build_addresses_10(inst): - private_ips = utils.get_from_path(inst, 'fixed_ip/address') - public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') - return dict(public=public_ips, private=private_ips) - - -def _build_addresses_11(inst): - private_ips = utils.get_from_path(inst, 'fixed_ip/address') - private_ips = [dict(version=4, addr=a) for a in private_ips] - public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') - public_ips = [dict(version=4, addr=a) for a in public_ips] - return dict(public=public_ips, private=private_ips) - - -def addresses_builder(req): - version = req.environ['nova.context'].version - if version == '1.1': - return _build_addresses_11 - else: - return _build_addresses_10 - - -def build_server(req, inst, is_detail): - """ Coerces into dictionary format, mapping everything to Rackspace-like - attributes for return""" - - if not is_detail: - return dict(server=dict(id=inst['id'], name=inst['display_name'])) - - power_mapping = { - None: 'build', - power_state.NOSTATE: 'build', - power_state.RUNNING: 'active', - power_state.BLOCKED: 'active', - power_state.SUSPENDED: 'suspended', - power_state.PAUSED: 'paused', - power_state.SHUTDOWN: 'active', - power_state.SHUTOFF: 'active', - power_state.CRASHED: 'error', - power_state.FAILED: 'error'} - inst_dict = {} - version = req.environ['nova.context'].version - - mapped_keys = dict(status='state', imageId='image_id', - flavorId='instance_type', name='display_name', id='id') - - for k, v in mapped_keys.iteritems(): - inst_dict[k] = inst[v] - - inst_dict['status'] = power_mapping[inst_dict['status']] - inst_dict['addresses'] = addresses_builder(req)(inst) - - # Return the metadata as a dictionary - metadata = {} - for item in inst['metadata']: - metadata[item['key']] = item['value'] - inst_dict['metadata'] = metadata - - inst_dict['hostId'] = '' - if inst['host']: - inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest() - - return dict(server=inst_dict) - - class Controller(wsgi.Controller): """ The Server API controller for the OpenStack API """ @@ -127,7 +54,7 @@ class Controller(wsgi.Controller): def ips(self, req, id): try: instance = self.compute_api.get(req.environ['nova.context'], id) - return addresses_builder(req)(instance) + return _addresses_builder(req)(instance) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -146,7 +73,7 @@ class Controller(wsgi.Controller): """ instance_list = self.compute_api.get_all(req.environ['nova.context']) limited_list = common.limited(instance_list, req) - res = [build_server(req, inst, is_detail)['server'] + res = [_build_server(req, inst, is_detail)['server'] for inst in limited_list] return dict(servers=res) @@ -154,7 +81,7 @@ class Controller(wsgi.Controller): """ Returns server details by server id """ try: instance = self.compute_api.get(req.environ['nova.context'], id) - return build_server(req, instance, is_detail=True) + return _build_server(req, instance, is_detail=True) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -206,7 +133,7 @@ class Controller(wsgi.Controller): metadata=metadata, onset_files=env.get('onset_files', [])) - server = build_server(req, instances[0], is_detail=False) + server = _build_server(req, instances[0], is_detail=False) password = "%s%s" % (server['server']['name'][:4], utils.generate_password(12)) server['server']['adminPass'] = password @@ -503,3 +430,70 @@ class Controller(wsgi.Controller): _("Ramdisk not found for image %(image_id)s") % locals()) return kernel_id, ramdisk_id + + +def _build_server(req, inst, is_detail): + """ Coerces into dictionary format, mapping everything to Rackspace-like + attributes for return""" + + if not is_detail: + return dict(server=dict(id=inst['id'], name=inst['display_name'])) + + power_mapping = { + None: 'build', + power_state.NOSTATE: 'build', + power_state.RUNNING: 'active', + power_state.BLOCKED: 'active', + power_state.SUSPENDED: 'suspended', + power_state.PAUSED: 'paused', + power_state.SHUTDOWN: 'active', + power_state.SHUTOFF: 'active', + power_state.CRASHED: 'error', + power_state.FAILED: 'error'} + inst_dict = {} + version = req.environ['nova.context'].version + + mapped_keys = dict(status='state', imageId='image_id', + flavorId='instance_type', name='display_name', id='id') + + for k, v in mapped_keys.iteritems(): + inst_dict[k] = inst[v] + + inst_dict['status'] = power_mapping[inst_dict['status']] + inst_dict['addresses'] = _addresses_builder(req)(inst) + + # Return the metadata as a dictionary + metadata = {} + for item in inst['metadata']: + metadata[item['key']] = item['value'] + inst_dict['metadata'] = metadata + + inst_dict['hostId'] = '' + if inst['host']: + inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest() + + return dict(server=inst_dict) + + +def _addresses_builder(req): + version = req.environ['nova.context'].version + if version == '1.1': + return _build_addresses_11 + else: + return _build_addresses_10 + + +def _build_addresses_10(inst): + private_ips = utils.get_from_path(inst, 'fixed_ip/address') + public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + return dict(public=public_ips, private=private_ips) + + +def _build_addresses_11(inst): + private_ips = utils.get_from_path(inst, 'fixed_ip/address') + private_ips = [dict(version=4, addr=a) for a in private_ips] + public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + public_ips = [dict(version=4, addr=a) for a in public_ips] + return dict(public=public_ips, private=private_ips) + + -- cgit From 266ea0bdd1da014a3cf23c7003f7fc932f447d35 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Mon, 14 Mar 2011 15:02:59 -0400 Subject: Create v1_0 and v1_1 packages for the openstack api. Added a servers module to each. Added tests to validate the structure of ip addresses for a 1.1 request. --- nova/api/openstack/servers.py | 28 +++++++--------------------- nova/api/openstack/v1_0/__init__.py | 0 nova/api/openstack/v1_0/servers.py | 6 ++++++ nova/api/openstack/v1_1/__init__.py | 0 nova/api/openstack/v1_1/servers.py | 8 ++++++++ 5 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 nova/api/openstack/v1_0/__init__.py create mode 100644 nova/api/openstack/v1_0/servers.py create mode 100644 nova/api/openstack/v1_1/__init__.py create mode 100644 nova/api/openstack/v1_1/servers.py (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 940c2c47e..0d36546d7 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -27,6 +27,8 @@ from nova import wsgi from nova import utils from nova.api.openstack import common from nova.api.openstack import faults +from nova.api.openstack.v1_0 import servers as v1_0 +from nova.api.openstack.v1_1 import servers as v1_1 from nova.auth import manager as auth_manager from nova.compute import instance_types from nova.compute import power_state @@ -54,7 +56,7 @@ class Controller(wsgi.Controller): def ips(self, req, id): try: instance = self.compute_api.get(req.environ['nova.context'], id) - return _addresses_builder(req)(instance) + return _get_addresses_builder(req)(instance) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -460,7 +462,7 @@ def _build_server(req, inst, is_detail): inst_dict[k] = inst[v] inst_dict['status'] = power_mapping[inst_dict['status']] - inst_dict['addresses'] = _addresses_builder(req)(inst) + inst_dict['addresses'] = _get_addresses_builder(req)(inst) # Return the metadata as a dictionary metadata = {} @@ -475,25 +477,9 @@ def _build_server(req, inst, is_detail): return dict(server=inst_dict) -def _addresses_builder(req): +def _get_addresses_builder(req): version = req.environ['nova.context'].version if version == '1.1': - return _build_addresses_11 + return v1_1.build_addresses else: - return _build_addresses_10 - - -def _build_addresses_10(inst): - private_ips = utils.get_from_path(inst, 'fixed_ip/address') - public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') - return dict(public=public_ips, private=private_ips) - - -def _build_addresses_11(inst): - private_ips = utils.get_from_path(inst, 'fixed_ip/address') - private_ips = [dict(version=4, addr=a) for a in private_ips] - public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') - public_ips = [dict(version=4, addr=a) for a in public_ips] - return dict(public=public_ips, private=private_ips) - - + return v1_0.build_addresses diff --git a/nova/api/openstack/v1_0/__init__.py b/nova/api/openstack/v1_0/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nova/api/openstack/v1_0/servers.py b/nova/api/openstack/v1_0/servers.py new file mode 100644 index 000000000..d332b1378 --- /dev/null +++ b/nova/api/openstack/v1_0/servers.py @@ -0,0 +1,6 @@ +from nova import utils + +def build_addresses(inst): + private_ips = utils.get_from_path(inst, 'fixed_ip/address') + public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + return dict(public=public_ips, private=private_ips) diff --git a/nova/api/openstack/v1_1/__init__.py b/nova/api/openstack/v1_1/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nova/api/openstack/v1_1/servers.py b/nova/api/openstack/v1_1/servers.py new file mode 100644 index 000000000..012fc3e5c --- /dev/null +++ b/nova/api/openstack/v1_1/servers.py @@ -0,0 +1,8 @@ +from nova import utils + +def build_addresses(inst): + private_ips = utils.get_from_path(inst, 'fixed_ip/address') + private_ips = [dict(version=4, addr=a) for a in private_ips] + public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + public_ips = [dict(version=4, addr=a) for a in public_ips] + return dict(public=public_ips, private=private_ips) -- cgit From d5b9391e2911ba2210a045a2af380dfc85d16919 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Mon, 14 Mar 2011 23:14:59 -0400 Subject: Added a views package and a views.servers module. For representing the response object before it is serialized. --- nova/api/openstack/servers.py | 65 +++++------------------------- nova/api/openstack/v1_0/__init__.py | 0 nova/api/openstack/v1_0/servers.py | 6 --- nova/api/openstack/v1_1/__init__.py | 0 nova/api/openstack/v1_1/servers.py | 8 ---- nova/api/openstack/views/__init__.py | 0 nova/api/openstack/views/servers.py | 76 ++++++++++++++++++++++++++++++++++++ 7 files changed, 86 insertions(+), 69 deletions(-) delete mode 100644 nova/api/openstack/v1_0/__init__.py delete mode 100644 nova/api/openstack/v1_0/servers.py delete mode 100644 nova/api/openstack/v1_1/__init__.py delete mode 100644 nova/api/openstack/v1_1/servers.py create mode 100644 nova/api/openstack/views/__init__.py create mode 100644 nova/api/openstack/views/servers.py (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 0d36546d7..ea8321e8d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -27,8 +27,7 @@ from nova import wsgi from nova import utils from nova.api.openstack import common from nova.api.openstack import faults -from nova.api.openstack.v1_0 import servers as v1_0 -from nova.api.openstack.v1_1 import servers as v1_1 +from nova.api.openstack.views.servers import get_view_builder from nova.auth import manager as auth_manager from nova.compute import instance_types from nova.compute import power_state @@ -56,7 +55,8 @@ class Controller(wsgi.Controller): def ips(self, req, id): try: instance = self.compute_api.get(req.environ['nova.context'], id) - return _get_addresses_builder(req)(instance) + builder = get_view_builder(req) + return builder._build_addresses(instance) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -75,15 +75,17 @@ class Controller(wsgi.Controller): """ instance_list = self.compute_api.get_all(req.environ['nova.context']) limited_list = common.limited(instance_list, req) - res = [_build_server(req, inst, is_detail)['server'] + builder = get_view_builder(req) + servers = [builder.build(inst, is_detail)['server'] for inst in limited_list] - return dict(servers=res) + return dict(servers=servers) def show(self, req, id): """ Returns server details by server id """ try: instance = self.compute_api.get(req.environ['nova.context'], id) - return _build_server(req, instance, is_detail=True) + builder = get_view_builder(req) + return builder.build(instance, is_detail=True) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -135,7 +137,8 @@ class Controller(wsgi.Controller): metadata=metadata, onset_files=env.get('onset_files', [])) - server = _build_server(req, instances[0], is_detail=False) + builder = get_view_builder(req) + server = builder.build(instances[0], is_detail=False) password = "%s%s" % (server['server']['name'][:4], utils.generate_password(12)) server['server']['adminPass'] = password @@ -434,52 +437,4 @@ class Controller(wsgi.Controller): return kernel_id, ramdisk_id -def _build_server(req, inst, is_detail): - """ Coerces into dictionary format, mapping everything to Rackspace-like - attributes for return""" - if not is_detail: - return dict(server=dict(id=inst['id'], name=inst['display_name'])) - - power_mapping = { - None: 'build', - power_state.NOSTATE: 'build', - power_state.RUNNING: 'active', - power_state.BLOCKED: 'active', - power_state.SUSPENDED: 'suspended', - power_state.PAUSED: 'paused', - power_state.SHUTDOWN: 'active', - power_state.SHUTOFF: 'active', - power_state.CRASHED: 'error', - power_state.FAILED: 'error'} - inst_dict = {} - version = req.environ['nova.context'].version - - mapped_keys = dict(status='state', imageId='image_id', - flavorId='instance_type', name='display_name', id='id') - - for k, v in mapped_keys.iteritems(): - inst_dict[k] = inst[v] - - inst_dict['status'] = power_mapping[inst_dict['status']] - inst_dict['addresses'] = _get_addresses_builder(req)(inst) - - # Return the metadata as a dictionary - metadata = {} - for item in inst['metadata']: - metadata[item['key']] = item['value'] - inst_dict['metadata'] = metadata - - inst_dict['hostId'] = '' - if inst['host']: - inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest() - - return dict(server=inst_dict) - - -def _get_addresses_builder(req): - version = req.environ['nova.context'].version - if version == '1.1': - return v1_1.build_addresses - else: - return v1_0.build_addresses diff --git a/nova/api/openstack/v1_0/__init__.py b/nova/api/openstack/v1_0/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/nova/api/openstack/v1_0/servers.py b/nova/api/openstack/v1_0/servers.py deleted file mode 100644 index d332b1378..000000000 --- a/nova/api/openstack/v1_0/servers.py +++ /dev/null @@ -1,6 +0,0 @@ -from nova import utils - -def build_addresses(inst): - private_ips = utils.get_from_path(inst, 'fixed_ip/address') - public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') - return dict(public=public_ips, private=private_ips) diff --git a/nova/api/openstack/v1_1/__init__.py b/nova/api/openstack/v1_1/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/nova/api/openstack/v1_1/servers.py b/nova/api/openstack/v1_1/servers.py deleted file mode 100644 index 012fc3e5c..000000000 --- a/nova/api/openstack/v1_1/servers.py +++ /dev/null @@ -1,8 +0,0 @@ -from nova import utils - -def build_addresses(inst): - private_ips = utils.get_from_path(inst, 'fixed_ip/address') - private_ips = [dict(version=4, addr=a) for a in private_ips] - public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') - public_ips = [dict(version=4, addr=a) for a in public_ips] - return dict(public=public_ips, private=private_ips) diff --git a/nova/api/openstack/views/__init__.py b/nova/api/openstack/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py new file mode 100644 index 000000000..120cae4d4 --- /dev/null +++ b/nova/api/openstack/views/servers.py @@ -0,0 +1,76 @@ +import hashlib +from nova.compute import power_state +from nova import utils + +def get_view_builder(req): + ''' + A factory method that returns the correct builder based on the version of + the api requested. + ''' + version = req.environ['nova.context'].version + if version == '1.1': + return DataViewBuilder_1_1() + else: + return DataViewBuilder_1_0() + + +class DataViewBuilder(object): + ''' Models a server response as a python dictionary. ''' + + def build(self, inst, is_detail): + """ Coerces into dictionary format, mapping everything to Rackspace-like + attributes for return""" + + if not is_detail: + return dict(server=dict(id=inst['id'], name=inst['display_name'])) + + power_mapping = { + None: 'build', + power_state.NOSTATE: 'build', + power_state.RUNNING: 'active', + power_state.BLOCKED: 'active', + power_state.SUSPENDED: 'suspended', + power_state.PAUSED: 'paused', + power_state.SHUTDOWN: 'active', + power_state.SHUTOFF: 'active', + power_state.CRASHED: 'error', + power_state.FAILED: 'error'} + inst_dict = {} + + mapped_keys = dict(status='state', imageId='image_id', + flavorId='instance_type', name='display_name', id='id') + + for k, v in mapped_keys.iteritems(): + inst_dict[k] = inst[v] + + inst_dict['status'] = power_mapping[inst_dict['status']] + inst_dict['addresses'] = self._build_addresses(inst) + + # Return the metadata as a dictionary + metadata = {} + for item in inst['metadata']: + metadata[item['key']] = item['value'] + inst_dict['metadata'] = metadata + + inst_dict['hostId'] = '' + if inst['host']: + inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest() + + return dict(server=inst_dict) + + +class DataViewBuilder_1_0(DataViewBuilder): + def _build_addresses(self, inst): + private_ips = utils.get_from_path(inst, 'fixed_ip/address') + public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + return dict(public=public_ips, private=private_ips) + + +class DataViewBuilder_1_1(DataViewBuilder): + def _build_addresses(self, inst): + private_ips = utils.get_from_path(inst, 'fixed_ip/address') + private_ips = [dict(version=4, addr=a) for a in private_ips] + public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + public_ips = [dict(version=4, addr=a) for a in public_ips] + return dict(public=public_ips, private=private_ips) + -- cgit From c70e3777a488a63062c030e9949e9c16f2269f9c Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Mon, 14 Mar 2011 23:55:44 -0400 Subject: moving addresses views to new module; removing 'Data' from 'DataViewBuilder' --- nova/api/openstack/views/servers.py | 39 ++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 18 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 120cae4d4..3ccfd8dba 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -1,29 +1,40 @@ import hashlib from nova.compute import power_state +from nova.api.openstack.views import addresses as addresses_view from nova import utils + def get_view_builder(req): ''' A factory method that returns the correct builder based on the version of the api requested. ''' version = req.environ['nova.context'].version + addresses_builder = addresses_view.get_view_builder(req) if version == '1.1': - return DataViewBuilder_1_1() + return ViewBuilder_1_1(addresses_builder) else: - return DataViewBuilder_1_0() + return ViewBuilder_1_0(addresses_builder) + +class ViewBuilder(object): + ''' Models a server response as a python dictionary.''' -class DataViewBuilder(object): - ''' Models a server response as a python dictionary. ''' + def __init__(self, addresses_builder): + self.addresses_builder = addresses_builder def build(self, inst, is_detail): """ Coerces into dictionary format, mapping everything to Rackspace-like attributes for return""" + if is_detail: + return self._build_detail(inst) + else: + return self._build_simple(inst) - if not is_detail: + def _build_simple(self, inst): return dict(server=dict(id=inst['id'], name=inst['display_name'])) + def _build_detail(self, inst): power_mapping = { None: 'build', power_state.NOSTATE: 'build', @@ -44,7 +55,7 @@ class DataViewBuilder(object): inst_dict[k] = inst[v] inst_dict['status'] = power_mapping[inst_dict['status']] - inst_dict['addresses'] = self._build_addresses(inst) + inst_dict['addresses'] = self.addresses_builder.build(inst) # Return the metadata as a dictionary metadata = {} @@ -59,18 +70,10 @@ class DataViewBuilder(object): return dict(server=inst_dict) -class DataViewBuilder_1_0(DataViewBuilder): - def _build_addresses(self, inst): - private_ips = utils.get_from_path(inst, 'fixed_ip/address') - public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') - return dict(public=public_ips, private=private_ips) +class ViewBuilder_1_0(ViewBuilder): + pass -class DataViewBuilder_1_1(DataViewBuilder): - def _build_addresses(self, inst): - private_ips = utils.get_from_path(inst, 'fixed_ip/address') - private_ips = [dict(version=4, addr=a) for a in private_ips] - public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') - public_ips = [dict(version=4, addr=a) for a in public_ips] - return dict(public=public_ips, private=private_ips) +class ViewBuilder_1_1(ViewBuilder): + pass -- cgit From 18cd549ba8d7aa4c688a7f7a5e940acbaaa03acc Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 15 Mar 2011 00:49:20 -0400 Subject: adding flavors and images barebones view code; adding flavorRef and imageRef to v1.1 servers --- nova/api/openstack/servers.py | 13 ++++++------ nova/api/openstack/views/servers.py | 42 ++++++++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 11 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index ea8321e8d..7bb7250ba 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -27,7 +27,8 @@ from nova import wsgi from nova import utils from nova.api.openstack import common from nova.api.openstack import faults -from nova.api.openstack.views.servers import get_view_builder +from nova.api.openstack.views import servers as servers_views +from nova.api.openstack.views import addresses as addresses_views from nova.auth import manager as auth_manager from nova.compute import instance_types from nova.compute import power_state @@ -55,8 +56,8 @@ class Controller(wsgi.Controller): def ips(self, req, id): try: instance = self.compute_api.get(req.environ['nova.context'], id) - builder = get_view_builder(req) - return builder._build_addresses(instance) + builder = addresses_views.get_view_builder(req) + return builder.build(instance) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -75,7 +76,7 @@ class Controller(wsgi.Controller): """ instance_list = self.compute_api.get_all(req.environ['nova.context']) limited_list = common.limited(instance_list, req) - builder = get_view_builder(req) + builder = servers_views.get_view_builder(req) servers = [builder.build(inst, is_detail)['server'] for inst in limited_list] return dict(servers=servers) @@ -84,7 +85,7 @@ class Controller(wsgi.Controller): """ Returns server details by server id """ try: instance = self.compute_api.get(req.environ['nova.context'], id) - builder = get_view_builder(req) + builder = servers_views.get_view_builder(req) return builder.build(instance, is_detail=True) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -137,7 +138,7 @@ class Controller(wsgi.Controller): metadata=metadata, onset_files=env.get('onset_files', [])) - builder = get_view_builder(req) + builder = servers_views.get_view_builder(req) server = builder.build(instances[0], is_detail=False) password = "%s%s" % (server['server']['name'][:4], utils.generate_password(12)) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 3ccfd8dba..950662747 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -1,6 +1,8 @@ import hashlib from nova.compute import power_state from nova.api.openstack.views import addresses as addresses_view +from nova.api.openstack.views import flavors as flavors_view +from nova.api.openstack.views import images as images_view from nova import utils @@ -12,7 +14,9 @@ def get_view_builder(req): version = req.environ['nova.context'].version addresses_builder = addresses_view.get_view_builder(req) if version == '1.1': - return ViewBuilder_1_1(addresses_builder) + flavor_builder = flavors_view.get_view_builder(req) + image_builder = images_view.get_view_builder(req) + return ViewBuilder_1_1(addresses_builder, flavor_builder, image_builder) else: return ViewBuilder_1_0(addresses_builder) @@ -48,8 +52,10 @@ class ViewBuilder(object): power_state.FAILED: 'error'} inst_dict = {} - mapped_keys = dict(status='state', imageId='image_id', - flavorId='instance_type', name='display_name', id='id') + #mapped_keys = dict(status='state', imageId='image_id', + # flavorId='instance_type', name='display_name', id='id') + + mapped_keys = dict(status='state', name='display_name', id='id') for k, v in mapped_keys.iteritems(): inst_dict[k] = inst[v] @@ -67,13 +73,39 @@ class ViewBuilder(object): if inst['host']: inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest() + inst_dict = self._decorate_response(inst_dict, inst) + return dict(server=inst_dict) + def _build_image_data(self, response, inst): + raise NotImplementedError() + class ViewBuilder_1_0(ViewBuilder): - pass + def _decorate_response(self, response, inst): + response["imageId"] = inst["image_id"] + response["flavorId"] = inst["instance_type"] + return response class ViewBuilder_1_1(ViewBuilder): - pass + def __init__(self, addresses_builder, flavor_builder, image_builder): + ViewBuilder.__init__(self, addresses_builder) + self.flavor_builder = flavor_builder + self.image_builder = image_builder + + def _decorate_response(self, response, inst): + response = self._build_image_ref(response, inst) + response = self._build_flavor_ref(response, inst) + return response + + def _build_image_ref(self, response, inst): + image_id = inst["image_id"] + response["imageRef"] = self.image_builder.generate_href(image_id) + return response + + def _build_flavor_ref(self, response, inst): + flavor_id = inst["instance_type"] + response["flavorRef"]= self.flavor_builder.generate_href(flavor_id) + return response -- cgit From 354f5e61c4bfb32ad8c2bc3389678f19db5fdb56 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 15 Mar 2011 00:55:52 -0400 Subject: pep8 fixes --- nova/api/openstack/servers.py | 3 --- nova/api/openstack/views/servers.py | 12 +++++++----- 2 files changed, 7 insertions(+), 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 7bb7250ba..de67cbc4a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -436,6 +436,3 @@ class Controller(wsgi.Controller): _("Ramdisk not found for image %(image_id)s") % locals()) return kernel_id, ramdisk_id - - - diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 950662747..15ac9964c 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -16,7 +16,8 @@ def get_view_builder(req): if version == '1.1': flavor_builder = flavors_view.get_view_builder(req) image_builder = images_view.get_view_builder(req) - return ViewBuilder_1_1(addresses_builder, flavor_builder, image_builder) + return ViewBuilder_1_1(addresses_builder, flavor_builder, + image_builder) else: return ViewBuilder_1_0(addresses_builder) @@ -28,8 +29,10 @@ class ViewBuilder(object): self.addresses_builder = addresses_builder def build(self, inst, is_detail): - """ Coerces into dictionary format, mapping everything to Rackspace-like - attributes for return""" + """ + Coerces into dictionary format, mapping everything to + Rackspace-like attributes for return + """ if is_detail: return self._build_detail(inst) else: @@ -106,6 +109,5 @@ class ViewBuilder_1_1(ViewBuilder): def _build_flavor_ref(self, response, inst): flavor_id = inst["instance_type"] - response["flavorRef"]= self.flavor_builder.generate_href(flavor_id) + response["flavorRef"] = self.flavor_builder.generate_href(flavor_id) return response - -- cgit From e161b00349a7478ac9f51f087c9f16cd345bc2d2 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 15 Mar 2011 13:23:42 -0400 Subject: adding missing view modules; modifying a couple of servers tests to use enumerate --- nova/api/openstack/views/addresses.py | 38 ++++++++++++++++++++++++++++++++++ nova/api/openstack/views/flavors.py | 39 +++++++++++++++++++++++++++++++++++ nova/api/openstack/views/images.py | 39 +++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 nova/api/openstack/views/addresses.py create mode 100644 nova/api/openstack/views/flavors.py create mode 100644 nova/api/openstack/views/images.py (limited to 'nova/api') diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py new file mode 100644 index 000000000..d764e5229 --- /dev/null +++ b/nova/api/openstack/views/addresses.py @@ -0,0 +1,38 @@ +import hashlib +from nova.compute import power_state +from nova import utils + + +def get_view_builder(req): + ''' + A factory method that returns the correct builder based on the version of + the api requested. + ''' + version = req.environ['nova.context'].version + if version == '1.1': + return ViewBuilder_1_1() + else: + return ViewBuilder_1_0() + + +class ViewBuilder(object): + ''' Models a server addresses response as a python dictionary.''' + + def build(self, inst): + raise NotImplementedError() + + +class ViewBuilder_1_0(ViewBuilder): + def build(self, inst): + private_ips = utils.get_from_path(inst, 'fixed_ip/address') + public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + return dict(public=public_ips, private=private_ips) + + +class ViewBuilder_1_1(ViewBuilder): + def build(self, inst): + private_ips = utils.get_from_path(inst, 'fixed_ip/address') + private_ips = [dict(version=4, addr=a) for a in private_ips] + public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address') + public_ips = [dict(version=4, addr=a) for a in public_ips] + return dict(public=public_ips, private=private_ips) diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py new file mode 100644 index 000000000..c6b6c10bb --- /dev/null +++ b/nova/api/openstack/views/flavors.py @@ -0,0 +1,39 @@ + + +def get_view_builder(req): + ''' + A factory method that returns the correct builder based on the version of + the api requested. + ''' + version = req.environ['nova.context'].version + base_url = req.application_url + if version == '1.1': + return ViewBuilder_1_1(base_url) + else: + return ViewBuilder_1_0() + + +class ViewBuilder(object): + def __init__(self): + pass + + def build(self, flavor_obj): + raise NotImplementedError() + + def _decorate_response(self, response, flavor_obj): + return response + + +class ViewBuilder_1_1(ViewBuilder): + def __init__(self, base_url): + self.base_url = base_url + + def _decorate_response(self, response, flavor_obj): + raise NotImplementedError() + + def generate_href(self, flavor_id): + return "{0}/flavors/{1}".format(self.base_url, flavor_id) + + +class ViewBuilder_1_0(ViewBuilder): + pass diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py new file mode 100644 index 000000000..c80713250 --- /dev/null +++ b/nova/api/openstack/views/images.py @@ -0,0 +1,39 @@ + + +def get_view_builder(req): + ''' + A factory method that returns the correct builder based on the version of + the api requested. + ''' + version = req.environ['nova.context'].version + base_url = req.application_url + if version == '1.1': + return ViewBuilder_1_1(base_url) + else: + return ViewBuilder_1_0() + + +class ViewBuilder(object): + def __init__(self): + pass + + def build(self, image_obj): + raise NotImplementedError() + + def _decorate_response(self, response, image_obj): + return response + + +class ViewBuilder_1_1(ViewBuilder): + def __init__(self, base_url): + self.base_url = base_url + + def _decorate_response(self, response, image_obj): + raise NotImplementedError() + + def generate_href(self, image_id): + return "{0}/images/{1}".format(self.base_url, image_id) + + +class ViewBuilder_1_0(ViewBuilder): + pass -- cgit From 937c135ec0c8b557b22ad30c400c75c713f660e1 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Tue, 15 Mar 2011 14:31:48 -0400 Subject: Code clean up. Removing _decorate_response methods. Replaced them with more explicit methods, _build_image, and _build_flavor. --- nova/api/openstack/views/flavors.py | 6 ------ nova/api/openstack/views/images.py | 6 ------ nova/api/openstack/views/servers.py | 30 ++++++++++++++++-------------- 3 files changed, 16 insertions(+), 26 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index c6b6c10bb..dfcc2644c 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -20,17 +20,11 @@ class ViewBuilder(object): def build(self, flavor_obj): raise NotImplementedError() - def _decorate_response(self, response, flavor_obj): - return response - class ViewBuilder_1_1(ViewBuilder): def __init__(self, base_url): self.base_url = base_url - def _decorate_response(self, response, flavor_obj): - raise NotImplementedError() - def generate_href(self, flavor_id): return "{0}/flavors/{1}".format(self.base_url, flavor_id) diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index c80713250..cd61ed656 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -20,17 +20,11 @@ class ViewBuilder(object): def build(self, image_obj): raise NotImplementedError() - def _decorate_response(self, response, image_obj): - return response - class ViewBuilder_1_1(ViewBuilder): def __init__(self, base_url): self.base_url = base_url - def _decorate_response(self, response, image_obj): - raise NotImplementedError() - def generate_href(self, image_id): return "{0}/images/{1}".format(self.base_url, image_id) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 15ac9964c..708c74b4e 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -23,7 +23,10 @@ def get_view_builder(req): class ViewBuilder(object): - ''' Models a server response as a python dictionary.''' + ''' + Models a server response as a python dictionary. + Abstract methods: _build_image, _build_flavor + ''' def __init__(self, addresses_builder): self.addresses_builder = addresses_builder @@ -76,19 +79,24 @@ class ViewBuilder(object): if inst['host']: inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest() - inst_dict = self._decorate_response(inst_dict, inst) + self._build_image(inst_dict, inst) + self._build_flavor(inst_dict, inst) return dict(server=inst_dict) - def _build_image_data(self, response, inst): + def _build_image(self, response, inst): + raise NotImplementedError() + + def _build_flavor(self, response, inst): raise NotImplementedError() class ViewBuilder_1_0(ViewBuilder): - def _decorate_response(self, response, inst): + def _build_image(self, response, inst): response["imageId"] = inst["image_id"] + + def _build_flavor(self, response, inst): response["flavorId"] = inst["instance_type"] - return response class ViewBuilder_1_1(ViewBuilder): @@ -97,17 +105,11 @@ class ViewBuilder_1_1(ViewBuilder): self.flavor_builder = flavor_builder self.image_builder = image_builder - def _decorate_response(self, response, inst): - response = self._build_image_ref(response, inst) - response = self._build_flavor_ref(response, inst) - return response - - def _build_image_ref(self, response, inst): + def _build_image(self, response, inst): image_id = inst["image_id"] response["imageRef"] = self.image_builder.generate_href(image_id) - return response - def _build_flavor_ref(self, response, inst): + def _build_flavor(self, response, inst): flavor_id = inst["instance_type"] response["flavorRef"] = self.flavor_builder.generate_href(flavor_id) - return response + -- cgit From cc25d277755f0e103ff09144d1d490536ab9acec Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 15 Mar 2011 15:56:54 -0400 Subject: modifying paste config to support v1.1; adding v1.1 entry in versions resource ( GET /) --- nova/api/openstack/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index ce3cff337..0244bc93c 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -128,8 +128,11 @@ class Versions(wsgi.Application): def __call__(self, req): """Respond to a request for all OpenStack API versions.""" response = { - "versions": [ - dict(status="CURRENT", id="v1.0")]} + "versions": [ + dict(status="DEPRECATED", id="v1.0"), + dict(status="CURRENT", id="v1.1"), + ], + } metadata = { "application/xml": { "attributes": dict(version=["status", "id"])}} -- cgit From 4b49df7e7232dfd3e187faac52b9eb72773be360 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 15 Mar 2011 16:49:19 -0400 Subject: Major cosmetic changes to limits, but little-to-no functional changes. MUCH better testability now, no more relying on system time to tick by for limit testing. --- nova/api/openstack/__init__.py | 6 + nova/api/openstack/faults.py | 21 +++ nova/api/openstack/limits.py | 342 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 369 insertions(+) create mode 100644 nova/api/openstack/limits.py (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index ce3cff337..db2dff8d5 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -33,6 +33,7 @@ from nova.api.openstack import backup_schedules from nova.api.openstack import consoles from nova.api.openstack import flavors from nova.api.openstack import images +from nova.api.openstack import limits from nova.api.openstack import servers from nova.api.openstack import shared_ip_groups from nova.api.openstack import users @@ -114,12 +115,17 @@ class APIRouter(wsgi.Router): mapper.resource("image", "images", controller=images.Controller(), collection={'detail': 'GET'}) + mapper.resource("flavor", "flavors", controller=flavors.Controller(), collection={'detail': 'GET'}) + mapper.resource("shared_ip_group", "shared_ip_groups", collection={'detail': 'GET'}, controller=shared_ip_groups.Controller()) + _limits = limits.LimitsController() + mapper.resource("limit", "limits", controller=_limits) + super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index 2fd733299..6ed9322de 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -61,3 +61,24 @@ class Fault(webob.exc.HTTPException): content_type = req.best_match_content_type() self.wrapped_exc.body = serializer.serialize(fault_data, content_type) return self.wrapped_exc + + +class OverLimitFault(webob.exc.HTTPException): + """ + Rate-limited request response. + """ + + wrapped_exc = webob.exc.HTTPForbidden() + + def __init__(self, message, details, retry_time): + """ + Initialize new `OverLimitFault` with relevant information. + """ + self.message = message + self.details = details + self.retry_time = retry_time + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, request): + """Currently just return the wrapped exception.""" + return self.wrapped_exc diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py new file mode 100644 index 000000000..4f64cab3c --- /dev/null +++ b/nova/api/openstack/limits.py @@ -0,0 +1,342 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License.import datetime + +""" +Module dedicated functions/classes dealing with rate limiting requests. +""" + +import copy +import httplib +import json +import math +import re +import time +import urllib +import webob.exc + +from collections import defaultdict + +from webob.dec import wsgify + +from nova import wsgi +from nova.api.openstack import faults +from nova.wsgi import Controller +from nova.wsgi import Middleware + + +# Convenience constants for the limits dictionary passed to Limiter(). +PER_SECOND = 1 +PER_MINUTE = 60 +PER_HOUR = 60 * 60 +PER_DAY = 60 * 60 * 24 + + +class LimitsController(Controller): + """ + Controller for accessing limits in the OpenStack API. + """ + + def index(self, req): + """ + Return all global and rate limit information. + """ + abs_limits = {} + rate_limits = req.environ.get("nova.limits", {}) + + return { + "limits" : { + "rate" : rate_limits, + "absolute" : abs_limits, + }, + } + + +class Limit(object): + """ + Stores information about a limit for HTTP requets. + """ + + UNITS = { + 1 : "SECOND", + 60 : "MINUTE", + 60 * 60 : "HOUR", + 60 * 60 * 24 : "DAY", + } + + def __init__(self, verb, uri, regex, value, unit): + """ + Initialize a new `Limit`. + + @param verb: HTTP verb (POST, PUT, etc.) + @param uri: Human-readable URI + @param regex: Regular expression format for this limit + @param value: Integer number of requests which can be made + @param unit: Unit of measure for the value parameter + """ + self.verb = verb + self.uri = uri + self.regex = regex + self.value = int(value) + self.unit = unit + self.remaining = int(value) + + if value <= 0: + raise ValueError("Limit value must be > 0") + + self.last_request = None + self.next_request = None + + self.water_level = 0 + self.capacity = float(self.unit) + self.request_value = float(self.capacity) / float(self.value) + + def __call__(self, verb, url): + """ + Represents a call to this limit from a relevant request. + + @param verb: string http verb (POST, GET, etc.) + @param url: string URL + """ + if self.verb != verb or not re.match(self.regex, url): + return + + now = self._get_time() + + if self.last_request is None: + self.last_request = now + + leak_value = now - self.last_request + + self.water_level -= leak_value + self.water_level = max(self.water_level, 0) + self.water_level += self.request_value + + difference = self.water_level - self.capacity + + self.last_request = now + + if difference > 0: + self.water_level -= self.request_value + self.next_request = now + difference + return difference + + cap = self.capacity + water = self.water_level + val = self.value + + self.remaining = math.floor((cap - water) / cap * val) + print "Remaining:", self.remaining + self.next_request = now + + def _get_time(self): + """Retrieve the current time. Broken out for testability.""" + return time.time() + + def display_unit(self): + """Display the string name of the unit.""" + return self.UNITS.get(self.unit, "UNKNOWN") + + def display(self): + """Return a useful representation of this class.""" + return { + "verb" : self.verb, + "uri" : self.uri, + "regex" : self.regex, + "value" : self.value, + "remaining" : int(self.remaining), + "unit" : self.display_unit(), + "resetTime" : int(self.next_request or self._get_time()), + } + + + +# "Limit" format is a dictionary with the HTTP verb, human-readable URI, +# a regular-expression to match, value and unit of measure (PER_DAY, etc.) + +DEFAULT_LIMITS = [ + Limit("POST", "*", ".*", 10, PER_MINUTE), + Limit("POST", "*/servers", "^/servers", 50, PER_DAY), + Limit("PUT", "*", ".*", 10, PER_MINUTE), + Limit("GET", "*changes-since*", ".*changes-since.*", 3, PER_MINUTE), + Limit("DELETE", "*", ".*", 100, PER_MINUTE), +] + + +class RateLimitingMiddleware(Middleware): + """ + Rate-limits requests passing through this middleware. All limit information + is stored in memory for this implementation. + """ + + def __init__(self, application, limits=None): + """ + Initialize new `RateLimitingMiddleware`, which wraps the given WSGI + application and sets up the given limits. + + @param application: WSGI application to wrap + @param limits: List of dictionaries describing limits + """ + Middleware.__init__(self, application) + self._limiter = Limiter(limits or DEFAULT_LIMITS) + + @wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + """ + Represents a single call through this middleware. We should record the + request if we have a limit relevant to it. If no limit is relevant to + the request, ignore it. + + If the request should be rate limited, return a fault telling the user + they are over the limit and need to retry later. + """ + verb = req.method + url = req.url + username = req.environ["nova.context"].user_id + + delay = self._limiter.check_for_delay(verb, url, username) + + if delay: + msg = "This request was rate-limited." + details = "Error details." + retry = time.time() + delay + return faults.OverLimitFault(msg, details, retry) + + req.environ["nova.limits"] = self._limiter.get_limits(username) + + return self.application + + +class Limiter(object): + """ + Rate-limit checking class which handles limits in memory. + """ + + def __init__(self, limits): + """ + Initialize the new `Limiter`. + + @param limits: List of `Limit` objects + """ + self.limits = copy.deepcopy(limits) + self.levels = defaultdict(lambda: copy.deepcopy(limits)) + + def get_limits(self, username=None): + """ + Return the limits for a given user. + """ + return [limit.display() for limit in self.levels[username]] + + def check_for_delay(self, verb, url, username=None): + """ + Check the given verb/user/user triplet for limit. + """ + def _get_delay_list(): + """Yield limit delays.""" + for limit in self.levels[username]: + delay = limit(verb, url) + if delay: + yield delay + + delays = list(_get_delay_list()) + + if delays: + delays.sort() + return delays[0] + + +class WsgiLimiter(object): + """ + Rate-limit checking from a WSGI application. Uses an in-memory `Limiter`. + + To use: + POST / with JSON data such as: + { + "verb" : GET, + "path" : "/servers" + } + + and receive a 204 No Content, or a 403 Forbidden with an X-Wait-Seconds + header containing the number of seconds to wait before the action would + succeed. + """ + + def __init__(self, limits=None): + """ + Initialize the new `WsgiLimiter`. + + @param limits: List of `Limit` objects + """ + self._limiter = Limiter(limits or DEFAULT_LIMITS) + + @wsgify(RequestClass=wsgi.Request) + def __call__(self, request): + """ + Handles a call to this application. Returns 204 if the request is + acceptable to the limiter, else a 403 is returned with a relevant + header indicating when the request *will* succeed. + """ + if request.method != "POST": + raise webob.exc.HTTPMethodNotAllowed() + + try: + info = dict(json.loads(request.body)) + except ValueError: + raise webob.exc.HTTPBadRequest() + + username = request.path_info_pop() + verb = info.get("verb") + path = info.get("path") + + delay = self._limiter.check_for_delay(verb, path, username) + + if delay: + headers = {"X-Wait-Seconds": "%.2f" % delay} + return webob.exc.HTTPForbidden(headers=headers) + else: + return webob.exc.HTTPNoContent() + + +class WsgiLimiterProxy(object): + """ + Rate-limit requests based on answers from a remote source. + """ + + def __init__(self, limiter_address): + """ + Initialize the new `WsgiLimiterProxy`. + + @param limiter_address: IP/port combination of where to request limit + """ + self.limiter_address = limiter_address + + def check_for_delay(self, verb, path, username=None): + body = json.dumps({"verb":verb,"path":path}) + headers = {"Content-Type" : "application/json"} + + conn = httplib.HTTPConnection(self.limiter_address) + + if username: + conn.request("POST", "/%s" % (username), body, headers) + else: + conn.request("POST", "/", body, headers) + + resp = conn.getresponse() + + if 200 >= resp.status < 300: + return None + + return resp.getheader("X-Wait-Seconds") -- cgit From 1b477c2225816ea8f05595a8812932d516828e01 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 15 Mar 2011 17:47:34 -0400 Subject: pep8 fixes --- nova/api/openstack/limits.py | 43 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 23 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index 4f64cab3c..b1e633330 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -58,12 +58,12 @@ class LimitsController(Controller): rate_limits = req.environ.get("nova.limits", {}) return { - "limits" : { - "rate" : rate_limits, - "absolute" : abs_limits, + "limits": { + "rate": rate_limits, + "absolute": abs_limits, }, } - + class Limit(object): """ @@ -71,10 +71,10 @@ class Limit(object): """ UNITS = { - 1 : "SECOND", - 60 : "MINUTE", - 60 * 60 : "HOUR", - 60 * 60 * 24 : "DAY", + 1: "SECOND", + 60: "MINUTE", + 60 * 60: "HOUR", + 60 * 60 * 24: "DAY", } def __init__(self, verb, uri, regex, value, unit): @@ -137,14 +137,13 @@ class Limit(object): cap = self.capacity water = self.water_level val = self.value - + self.remaining = math.floor((cap - water) / cap * val) - print "Remaining:", self.remaining self.next_request = now def _get_time(self): """Retrieve the current time. Broken out for testability.""" - return time.time() + return time.time() def display_unit(self): """Display the string name of the unit.""" @@ -153,17 +152,15 @@ class Limit(object): def display(self): """Return a useful representation of this class.""" return { - "verb" : self.verb, - "uri" : self.uri, - "regex" : self.regex, - "value" : self.value, - "remaining" : int(self.remaining), - "unit" : self.display_unit(), - "resetTime" : int(self.next_request or self._get_time()), + "verb": self.verb, + "uri": self.uri, + "regex": self.regex, + "value": self.value, + "remaining": int(self.remaining), + "unit": self.display_unit(), + "resetTime": int(self.next_request or self._get_time()), } - - # "Limit" format is a dictionary with the HTTP verb, human-readable URI, # a regular-expression to match, value and unit of measure (PER_DAY, etc.) @@ -324,11 +321,11 @@ class WsgiLimiterProxy(object): self.limiter_address = limiter_address def check_for_delay(self, verb, path, username=None): - body = json.dumps({"verb":verb,"path":path}) - headers = {"Content-Type" : "application/json"} + body = json.dumps({"verb": verb, "path": path}) + headers = {"Content-Type": "application/json"} conn = httplib.HTTPConnection(self.limiter_address) - + if username: conn.request("POST", "/%s" % (username), body, headers) else: -- cgit From 74068a7b504a95dc8e0339faa04c8c5520417f32 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Tue, 15 Mar 2011 18:10:25 -0400 Subject: Per Eric Day's suggest, the verson is not store in the request environ instead of the nova.context. --- nova/api/openstack/auth.py | 4 ++-- nova/api/openstack/views/addresses.py | 2 +- nova/api/openstack/views/flavors.py | 2 +- nova/api/openstack/views/images.py | 2 +- nova/api/openstack/views/servers.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index e33a9faf5..c820a5963 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -68,9 +68,9 @@ class AuthMiddleware(wsgi.Middleware): not self.auth.is_project_member(user, account): return faults.Fault(webob.exc.HTTPUnauthorized()) + req.environ['nova.context'] = context.RequestContext(user, account) version = req.path.split('/')[1].replace('v', '') - req.environ['nova.context'] = context.RequestContext(user, account, - version=version) + req.environ['version'] = version return self.application def has_authentication(self, req): diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index d764e5229..65c24dbd7 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -8,7 +8,7 @@ def get_view_builder(req): A factory method that returns the correct builder based on the version of the api requested. ''' - version = req.environ['nova.context'].version + version = req.environ['version'] if version == '1.1': return ViewBuilder_1_1() else: diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index dfcc2644c..f945f9f8f 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -5,7 +5,7 @@ def get_view_builder(req): A factory method that returns the correct builder based on the version of the api requested. ''' - version = req.environ['nova.context'].version + version = req.environ['version'] base_url = req.application_url if version == '1.1': return ViewBuilder_1_1(base_url) diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index cd61ed656..a59d4a557 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -5,7 +5,7 @@ def get_view_builder(req): A factory method that returns the correct builder based on the version of the api requested. ''' - version = req.environ['nova.context'].version + version = req.environ['version'] base_url = req.application_url if version == '1.1': return ViewBuilder_1_1(base_url) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 7ca2b2427..2549cc11c 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -11,7 +11,7 @@ def get_view_builder(req): A factory method that returns the correct builder based on the version of the api requested. ''' - version = req.environ['nova.context'].version + version = req.environ['version'] addresses_builder = addresses_view.get_view_builder(req) if version == '1.1': flavor_builder = flavors_view.get_view_builder(req) -- cgit From bee1951ac78688e49939aee4e2285ef0ff89adb2 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Tue, 15 Mar 2011 19:55:13 -0400 Subject: As suggested by Eric Day: * changed request.environ version key to more descriptive 'api.version' * removed python3 string formatting * added licenses to headers on new files --- nova/api/openstack/auth.py | 2 +- nova/api/openstack/common.py | 3 +++ nova/api/openstack/views/addresses.py | 22 +++++++++++++++++++--- nova/api/openstack/views/flavors.py | 23 ++++++++++++++++++++--- nova/api/openstack/views/images.py | 23 ++++++++++++++++++++--- nova/api/openstack/views/servers.py | 20 +++++++++++++++++++- 6 files changed, 82 insertions(+), 11 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index c820a5963..7ae285019 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -70,7 +70,7 @@ class AuthMiddleware(wsgi.Middleware): req.environ['nova.context'] = context.RequestContext(user, account) version = req.path.split('/')[1].replace('v', '') - req.environ['version'] = version + req.environ['nova.api.openstack.version'] = version return self.application def has_authentication(self, req): diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 74ac21024..d94969ff5 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -74,3 +74,6 @@ def get_image_id_from_image_hash(image_service, context, image_hash): if abs(hash(image_id)) == int(image_hash): return image_id raise exception.NotFound(image_hash) + +def get_api_version(req): + return req.environ.get('api.version') diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index 65c24dbd7..9d392aace 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -1,6 +1,22 @@ -import hashlib -from nova.compute import power_state +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010-2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + from nova import utils +from nova.api.openstack import common def get_view_builder(req): @@ -8,7 +24,7 @@ def get_view_builder(req): A factory method that returns the correct builder based on the version of the api requested. ''' - version = req.environ['version'] + version = common.get_api_version(req) if version == '1.1': return ViewBuilder_1_1() else: diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index f945f9f8f..aa3c2aeb2 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -1,11 +1,28 @@ - +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010-2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova.api.openstack import common def get_view_builder(req): ''' A factory method that returns the correct builder based on the version of the api requested. ''' - version = req.environ['version'] + version = common.get_api_version(req) base_url = req.application_url if version == '1.1': return ViewBuilder_1_1(base_url) @@ -26,7 +43,7 @@ class ViewBuilder_1_1(ViewBuilder): self.base_url = base_url def generate_href(self, flavor_id): - return "{0}/flavors/{1}".format(self.base_url, flavor_id) + return "%s/flavors/%s" % (self.base_url, flavor_id) class ViewBuilder_1_0(ViewBuilder): diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index a59d4a557..930b464b0 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -1,11 +1,28 @@ - +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010-2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from nova.api.openstack import common def get_view_builder(req): ''' A factory method that returns the correct builder based on the version of the api requested. ''' - version = req.environ['version'] + version = common.get_api_version(req) base_url = req.application_url if version == '1.1': return ViewBuilder_1_1(base_url) @@ -26,7 +43,7 @@ class ViewBuilder_1_1(ViewBuilder): self.base_url = base_url def generate_href(self, image_id): - return "{0}/images/{1}".format(self.base_url, image_id) + return "%s/images/%s" % (self.base_url, image_id) class ViewBuilder_1_0(ViewBuilder): diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 2549cc11c..261acfed0 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -1,5 +1,23 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010-2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + import hashlib from nova.compute import power_state +from nova.api.openstack import common from nova.api.openstack.views import addresses as addresses_view from nova.api.openstack.views import flavors as flavors_view from nova.api.openstack.views import images as images_view @@ -11,7 +29,7 @@ def get_view_builder(req): A factory method that returns the correct builder based on the version of the api requested. ''' - version = req.environ['version'] + version = common.get_api_version(req) addresses_builder = addresses_view.get_view_builder(req) if version == '1.1': flavor_builder = flavors_view.get_view_builder(req) -- cgit From c42d79b58eccaebab14274adf09128d890e920f7 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Tue, 15 Mar 2011 20:37:37 -0400 Subject: adding imageRef and flavorRef attributes to servers serialization metadata --- nova/api/openstack/servers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index de67cbc4a..dc62882eb 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -46,7 +46,8 @@ class Controller(wsgi.Controller): 'application/xml': { "attributes": { "server": ["id", "imageId", "name", "flavorId", "hostId", - "status", "progress", "adminPass"]}}} + "status", "progress", "adminPass", "flavorRef", + "imageRef"]}}} def __init__(self): self.compute_api = compute.API() -- cgit From 5e45d0ba921566e98817cb9e62e383f84c30c5f6 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 15 Mar 2011 20:51:17 -0400 Subject: Limits controller and testing with XML and JSON serialization. --- nova/api/openstack/limits.py | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index b1e633330..57e6bfcc2 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -50,12 +50,24 @@ class LimitsController(Controller): Controller for accessing limits in the OpenStack API. """ + _serialization_metadata = { + "application/xml": { + "attributes": { + "limit": ["verb", "URI", "regex", "value", "unit", + "resetTime", "remaining", "name"], + }, + "plurals" : { + "rate" : "limit", + }, + }, + } + def index(self, req): """ Return all global and rate limit information. """ abs_limits = {} - rate_limits = req.environ.get("nova.limits", {}) + rate_limits = req.environ.get("nova.limits", []) return { "limits": { @@ -92,6 +104,7 @@ class Limit(object): self.regex = regex self.value = int(value) self.unit = unit + self.unit_string = self.display_unit().lower() self.remaining = int(value) if value <= 0: @@ -101,8 +114,10 @@ class Limit(object): self.next_request = None self.water_level = 0 - self.capacity = float(self.unit) + self.capacity = self.unit self.request_value = float(self.capacity) / float(self.value) + self.error_message = _("Only %(value)s %(verb)s request(s) can be "\ + "made to %(uri)s every %(unit_string)s." % self.__dict__) def __call__(self, verb, url): """ @@ -153,7 +168,7 @@ class Limit(object): """Return a useful representation of this class.""" return { "verb": self.verb, - "uri": self.uri, + "URI": self.uri, "regex": self.regex, "value": self.value, "remaining": int(self.remaining), @@ -204,13 +219,12 @@ class RateLimitingMiddleware(Middleware): url = req.url username = req.environ["nova.context"].user_id - delay = self._limiter.check_for_delay(verb, url, username) + delay, error = self._limiter.check_for_delay(verb, url, username) if delay: msg = "This request was rate-limited." - details = "Error details." retry = time.time() + delay - return faults.OverLimitFault(msg, details, retry) + return faults.OverLimitFault(msg, error, retry) req.environ["nova.limits"] = self._limiter.get_limits(username) @@ -240,13 +254,15 @@ class Limiter(object): def check_for_delay(self, verb, url, username=None): """ Check the given verb/user/user triplet for limit. + + @return: Tuple of delay (in seconds) and error message (or None, None) """ def _get_delay_list(): """Yield limit delays.""" for limit in self.levels[username]: delay = limit(verb, url) if delay: - yield delay + yield delay, limit.error_message delays = list(_get_delay_list()) @@ -254,6 +270,8 @@ class Limiter(object): delays.sort() return delays[0] + return None, None + class WsgiLimiter(object): """ @@ -298,11 +316,11 @@ class WsgiLimiter(object): verb = info.get("verb") path = info.get("path") - delay = self._limiter.check_for_delay(verb, path, username) + delay, error = self._limiter.check_for_delay(verb, path, username) if delay: headers = {"X-Wait-Seconds": "%.2f" % delay} - return webob.exc.HTTPForbidden(headers=headers) + return webob.exc.HTTPForbidden(headers=headers, explanation=error) else: return webob.exc.HTTPNoContent() @@ -333,7 +351,9 @@ class WsgiLimiterProxy(object): resp = conn.getresponse() + print resp + if 200 >= resp.status < 300: - return None + return None, None - return resp.getheader("X-Wait-Seconds") + return resp.getheader("X-Wait-Seconds"), resp.read() or None -- cgit From 5ba3e21875d3cf3b71082477311902891706eee4 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 15 Mar 2011 21:09:26 -0400 Subject: Removed VIM specific stuff and changed copyright from 2010 to 2011. --- nova/api/openstack/limits.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index 57e6bfcc2..3ecd46377 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -1,6 +1,4 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack LLC. +# Copyright 2011 OpenStack LLC. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may -- cgit From 6911123fda88c9793a70ea4b03d0352c9c38f938 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Tue, 15 Mar 2011 21:26:45 -0400 Subject: Adding newlines for pep8. --- nova/api/openstack/common.py | 1 + nova/api/openstack/views/flavors.py | 1 + nova/api/openstack/views/images.py | 1 + 3 files changed, 3 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index d94969ff5..d6679de01 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -75,5 +75,6 @@ def get_image_id_from_image_hash(image_service, context, image_hash): return image_id raise exception.NotFound(image_hash) + def get_api_version(req): return req.environ.get('api.version') diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index aa3c2aeb2..dd2e75a7a 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -17,6 +17,7 @@ from nova.api.openstack import common + def get_view_builder(req): ''' A factory method that returns the correct builder based on the version of diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 930b464b0..2369a8f9d 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -17,6 +17,7 @@ from nova.api.openstack import common + def get_view_builder(req): ''' A factory method that returns the correct builder based on the version of -- cgit From be9a218e2e4b01fe19722fb0073731d8ae6a7eea Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Tue, 15 Mar 2011 23:13:05 -0400 Subject: Added tests back for RateLimitingMiddleware which now throw correctly serialized errors with correct error codes. Removed some error printing, and simplified some other parts of the code with suggestions from teammates. --- nova/api/openstack/faults.py | 22 ++++++++++++++++++---- nova/api/openstack/limits.py | 25 +++++++++++++------------ 2 files changed, 31 insertions(+), 16 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index 6ed9322de..d05c61fc7 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -68,17 +68,31 @@ class OverLimitFault(webob.exc.HTTPException): Rate-limited request response. """ - wrapped_exc = webob.exc.HTTPForbidden() + _serialization_metadata = { + "application/xml": { + "attributes": { + "overLimitFault": "code" + } + } + } def __init__(self, message, details, retry_time): """ Initialize new `OverLimitFault` with relevant information. """ - self.message = message - self.details = details - self.retry_time = retry_time + self.wrapped_exc = webob.exc.HTTPForbidden() + self.content = { + "overLimitFault": { + "code": self.wrapped_exc.status_int, + "message": message, + "details": details, + }, + } @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, request): """Currently just return the wrapped exception.""" + serializer = wsgi.Serializer(self._serialization_metadata) + content_type = request.best_match_content_type() + self.wrapped_exc.body = serializer.serialize(self.content, content_type) return self.wrapped_exc diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index 3ecd46377..c4e04e9d9 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -54,8 +54,8 @@ class LimitsController(Controller): "limit": ["verb", "URI", "regex", "value", "unit", "resetTime", "remaining", "name"], }, - "plurals" : { - "rate" : "limit", + "plurals": { + "rate": "limit", }, }, } @@ -215,7 +215,12 @@ class RateLimitingMiddleware(Middleware): """ verb = req.method url = req.url - username = req.environ["nova.context"].user_id + context = req.environ.get("nova.context") + + if context: + username = context.user_id + else: + username = None delay, error = self._limiter.check_for_delay(verb, url, username) @@ -255,14 +260,12 @@ class Limiter(object): @return: Tuple of delay (in seconds) and error message (or None, None) """ - def _get_delay_list(): - """Yield limit delays.""" - for limit in self.levels[username]: - delay = limit(verb, url) - if delay: - yield delay, limit.error_message + delays = [] - delays = list(_get_delay_list()) + for limit in self.levels[username]: + delay = limit(verb, url) + if delay: + delays.append((delay, limit.error_message)) if delays: delays.sort() @@ -349,8 +352,6 @@ class WsgiLimiterProxy(object): resp = conn.getresponse() - print resp - if 200 >= resp.status < 300: return None, None -- cgit From 78542ad1de6476a8962fa0c3b273c4a272410a83 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Wed, 16 Mar 2011 00:38:47 -0400 Subject: req envirom param 'nova.api.openstack.version' should be 'api.version' --- nova/api/openstack/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 7ae285019..6f1cf5e63 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -70,7 +70,7 @@ class AuthMiddleware(wsgi.Middleware): req.environ['nova.context'] = context.RequestContext(user, account) version = req.path.split('/')[1].replace('v', '') - req.environ['nova.api.openstack.version'] = version + req.environ['api.version'] = version return self.application def has_authentication(self, req): -- cgit From a586714557e38116b6b4f473aa21ac54ff0223e7 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Wed, 16 Mar 2011 10:10:58 -0400 Subject: Added i18n to error message. --- nova/api/openstack/limits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index c4e04e9d9..1fe519f8a 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -225,7 +225,7 @@ class RateLimitingMiddleware(Middleware): delay, error = self._limiter.check_for_delay(verb, url, username) if delay: - msg = "This request was rate-limited." + msg = _("This request was rate-limited.") retry = time.time() + delay return faults.OverLimitFault(msg, error, retry) -- cgit From 5473f3a47c1b11c6625960e1ed73c28c7b061fcb Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Wed, 16 Mar 2011 13:41:00 -0400 Subject: moving code out of try/except that would never trigger NotFound --- nova/api/openstack/servers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index dc62882eb..818dd825f 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -57,11 +57,12 @@ class Controller(wsgi.Controller): def ips(self, req, id): try: instance = self.compute_api.get(req.environ['nova.context'], id) - builder = addresses_views.get_view_builder(req) - return builder.build(instance) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) + builder = addresses_views.get_view_builder(req) + return builder.build(instance) + def index(self, req): """ Returns a list of server names and ids for a given user """ return self._items(req, is_detail=False) -- cgit From 7de1ef791296d547c2691454d5cb5451087cd76b Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Wed, 16 Mar 2011 12:15:57 -0700 Subject: User ids are strings, and are not necessarily == name. Also fix so that non-existent user gives a 404, not a 500. --- nova/api/openstack/users.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py index ebd0f4512..d3ab3d553 100644 --- a/nova/api/openstack/users.py +++ b/nova/api/openstack/users.py @@ -13,13 +13,14 @@ # License for the specific language governing permissions and limitations # under the License. -import common +from webob import exc from nova import exception from nova import flags from nova import log as logging from nova import wsgi - +from nova.api.openstack import common +from nova.api.openstack import faults from nova.auth import manager FLAGS = flags.FLAGS @@ -63,7 +64,17 @@ class Controller(wsgi.Controller): def show(self, req, id): """Return data about the given user id""" - user = self.manager.get_user(id) + + #NOTE(justinsb): The drivers are a little inconsistent in how they + # deal with "NotFound" - some throw, some return None. + try: + user = self.manager.get_user(id) + except exception.NotFound: + user = None + + if user is None: + raise faults.Fault(exc.HTTPNotFound()) + return dict(user=_translate_keys(user)) def delete(self, req, id): -- cgit From 85bae497aa803914d329f2872d343a9982dc370e Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 16 Mar 2011 14:35:04 -0500 Subject: Changes --- nova/api/openstack/flavors.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index f3d040ba3..b9e40371d 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -36,7 +36,7 @@ class Controller(wsgi.Controller): def index(self, req): """Return all flavors in brief.""" - return dict(flavors=[dict(id=flavor['id'], name=flavor['name']) + return dict(flavors=[dict(id=flavor['flavorid'], name=flavor['name']) for flavor in self.detail(req)['flavors']]) def detail(self, req): @@ -48,6 +48,7 @@ class Controller(wsgi.Controller): """Return data about the given flavor id.""" ctxt = req.environ['nova.context'] values = db.instance_type_get_by_flavor_id(ctxt, id) + values.update({'id': values['flavorid']}) return dict(flavor=values) raise faults.Fault(exc.HTTPNotFound()) -- cgit From f17fb9370d4af42267837a36c937f213669b0291 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Wed, 16 Mar 2011 14:43:57 -0500 Subject: Dumb --- nova/api/openstack/flavors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index b9e40371d..1c440b3a9 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -48,7 +48,7 @@ class Controller(wsgi.Controller): """Return data about the given flavor id.""" ctxt = req.environ['nova.context'] values = db.instance_type_get_by_flavor_id(ctxt, id) - values.update({'id': values['flavorid']}) + values['id'] = values['flavorid'] return dict(flavor=values) raise faults.Fault(exc.HTTPNotFound()) -- cgit From a766b4111addad804e47b8be3e6dedb5f80a83c4 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Thu, 17 Mar 2011 02:20:18 +0000 Subject: added in network qos support for xenserver. Pull qos settings from flavor, use when creating instance. --- nova/api/openstack/servers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3ecd4fb01..2f26fa873 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -32,6 +32,7 @@ from nova.api.openstack import faults from nova.auth import manager as auth_manager from nova.compute import instance_types from nova.compute import power_state +from nova.quota import QuotaError import nova.api.openstack @@ -189,7 +190,7 @@ class Controller(wsgi.Controller): metadata=metadata, injected_files=injected_files) except QuotaError as error: - self._handle_quota_error(error) + self._handle_quota_errors(error) server = _translate_keys(instances[0]) password = "%s%s" % (server['server']['name'][:4], -- cgit From 732633c93f8d8cf71875d2caf096c9efbcf9dbce Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 17 Mar 2011 09:55:41 -0400 Subject: Update the Openstack API to handle case where personality is set but null in the request to create a server. --- nova/api/openstack/servers.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3ecd4fb01..bf21ed17f 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -220,6 +220,11 @@ class Controller(wsgi.Controller): underlying compute service. """ injected_files = [] + + # NOTE(dprince): handle case where 'personality: null' is in JSON req + if not personality: + return injected_files + for item in personality: try: path = item['path'] -- cgit From f8aa9485fe2048ff916d9dd40478ef0b1486077f Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 17 Mar 2011 10:45:46 -0400 Subject: Switch back to 'is not None' for personality_files check. (makes mark happy) --- nova/api/openstack/servers.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index bf21ed17f..6dd66a9a5 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -221,7 +221,6 @@ class Controller(wsgi.Controller): """ injected_files = [] - # NOTE(dprince): handle case where 'personality: null' is in JSON req if not personality: return injected_files -- cgit From 1d64d0a3d7f25448361ce54e32bba3de68c7afd1 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Thu, 17 Mar 2011 16:14:59 +0100 Subject: Remove unconditional raise, probably left over from debugging. --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 40a9da0e7..e257e44e7 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -959,7 +959,7 @@ class CloudController(object): raise exception.NotFound(_('Image %s not found') % image_id) internal_id = image['id'] del(image['id']) - raise Exception(image) + image['properties']['is_public'] = (operation_type == 'add') return self.image_service.update(context, internal_id, image) -- cgit From d31e0f0ad048fbd0374170ea76968859a4c6df34 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 17 Mar 2011 12:39:09 -0400 Subject: Fixed pep8 violation. --- nova/api/openstack/faults.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index d05c61fc7..56f5b8e7e 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -94,5 +94,6 @@ class OverLimitFault(webob.exc.HTTPException): """Currently just return the wrapped exception.""" serializer = wsgi.Serializer(self._serialization_metadata) content_type = request.best_match_content_type() - self.wrapped_exc.body = serializer.serialize(self.content, content_type) + content = serializer.serialize(self.content, content_type) + self.wrapped_exc.body = content return self.wrapped_exc -- cgit From f96dea3da633fc71f16de1bdb95e88249b316e29 Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 17 Mar 2011 13:11:40 -0400 Subject: Pep8 error, oddly specific to pep8 v0.5 < x > v0.6 --- nova/api/openstack/faults.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index 56f5b8e7e..ccccbd3d2 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -71,9 +71,9 @@ class OverLimitFault(webob.exc.HTTPException): _serialization_metadata = { "application/xml": { "attributes": { - "overLimitFault": "code" - } - } + "overLimitFault": "code", + }, + }, } def __init__(self, message, details, retry_time): -- cgit From ca267d0e52ed721f1236dc4b6030433fe92d0d51 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 17 Mar 2011 15:27:20 -0400 Subject: Move the check for None personalities into the create method. --- nova/api/openstack/servers.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 6dd66a9a5..c6422add4 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -172,8 +172,10 @@ class Controller(wsgi.Controller): for k, v in env['server']['metadata'].items(): metadata.append({'key': k, 'value': v}) - personality = env['server'].get('personality', []) - injected_files = self._get_injected_files(personality) + personality = env['server'].get('personality') + injected_files = [] + if personality: + injected_files = self._get_injected_files(personality) try: instances = self.compute_api.create( @@ -221,9 +223,6 @@ class Controller(wsgi.Controller): """ injected_files = [] - if not personality: - return injected_files - for item in personality: try: path = item['path'] -- cgit From 3628f50b4ecd2db0377fd9c158248d3b7e8e98ff Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Thu, 17 Mar 2011 16:26:52 -0400 Subject: Better comment for fault. Improved readability of two small sections. --- nova/api/openstack/faults.py | 5 ++++- nova/api/openstack/limits.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/faults.py b/nova/api/openstack/faults.py index ccccbd3d2..0e9c4b26f 100644 --- a/nova/api/openstack/faults.py +++ b/nova/api/openstack/faults.py @@ -91,7 +91,10 @@ class OverLimitFault(webob.exc.HTTPException): @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, request): - """Currently just return the wrapped exception.""" + """ + Return the wrapped exception with a serialized body conforming to our + error format. + """ serializer = wsgi.Serializer(self._serialization_metadata) content_type = request.best_match_content_type() content = serializer.serialize(self.content, content_type) diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py index 1fe519f8a..efc7d193d 100644 --- a/nova/api/openstack/limits.py +++ b/nova/api/openstack/limits.py @@ -151,7 +151,7 @@ class Limit(object): water = self.water_level val = self.value - self.remaining = math.floor((cap - water) / cap * val) + self.remaining = math.floor(((cap - water) / cap) * val) self.next_request = now def _get_time(self): -- cgit From a50deeb264ff721584d5b0a6ace749d8e2c44842 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 18 Mar 2011 10:10:46 -0400 Subject: Change cloud.id_to_ec2_id to ec2utils.id_to_ec2_id. Fixes EC2 API error handling when invalid instances and volume names are specified. --- nova/api/ec2/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index fccebca5d..20701cfa8 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -31,7 +31,7 @@ from nova import log as logging from nova import utils from nova import wsgi from nova.api.ec2 import apirequest -from nova.api.ec2 import cloud +from nova.api.ec2 import ec2utils from nova.auth import manager @@ -319,13 +319,13 @@ class Executor(wsgi.Application): except exception.InstanceNotFound as ex: LOG.info(_('InstanceNotFound raised: %s'), unicode(ex), context=context) - ec2_id = cloud.id_to_ec2_id(ex.instance_id) + ec2_id = ec2utils.id_to_ec2_id(ex.instance_id) message = _('Instance %s not found') % ec2_id return self._error(req, context, type(ex).__name__, message) except exception.VolumeNotFound as ex: LOG.info(_('VolumeNotFound raised: %s'), unicode(ex), context=context) - ec2_id = cloud.id_to_ec2_id(ex.volume_id, 'vol-%08x') + ec2_id = ec2utils.id_to_ec2_id(ex.volume_id, 'vol-%08x') message = _('Volume %s not found') % ec2_id return self._error(req, context, type(ex).__name__, message) except exception.NotFound as ex: -- cgit