diff options
| author | Naveed Massjouni <naveedm9@gmail.com> | 2011-03-17 20:56:45 +0000 |
|---|---|---|
| committer | Tarmac <> | 2011-03-17 20:56:45 +0000 |
| commit | 4b5a775ae4169faaf5716d362ead66aa443a2748 (patch) | |
| tree | 29801c0fc29cfeea41f702745581d25cd2ccc98e /nova/api | |
| parent | 88ae79505a84736ebdf57ba67c60ff16de5c9e87 (diff) | |
| parent | 866845870f71f08203d0b29e9f35ecd5eec44151 (diff) | |
- general approach for openstack api versioning
- openstack api version now preserved in request context
- added view builder classes to handle os api responses
- added imageRef and flavorRef to os api v1.1 servers
- modified addresses container structure in os api v1.1 servers
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/openstack/__init__.py | 7 | ||||
| -rw-r--r-- | nova/api/openstack/auth.py | 2 | ||||
| -rw-r--r-- | nova/api/openstack/common.py | 4 | ||||
| -rw-r--r-- | nova/api/openstack/servers.py | 88 | ||||
| -rw-r--r-- | nova/api/openstack/views/__init__.py | 0 | ||||
| -rw-r--r-- | nova/api/openstack/views/addresses.py | 54 | ||||
| -rw-r--r-- | nova/api/openstack/views/flavors.py | 51 | ||||
| -rw-r--r-- | nova/api/openstack/views/images.py | 51 | ||||
| -rw-r--r-- | nova/api/openstack/views/servers.py | 132 |
9 files changed, 324 insertions, 65 deletions
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"])}} diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index f3a9bdeca..5aa5e099b 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -69,6 +69,8 @@ class AuthMiddleware(wsgi.Middleware): return faults.Fault(webob.exc.HTTPUnauthorized()) req.environ['nova.context'] = context.RequestContext(user, account) + version = req.path.split('/')[1].replace('v', '') + req.environ['api.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..d6679de01 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -74,3 +74,7 @@ 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/servers.py b/nova/api/openstack/servers.py index 2f26fa873..2fbe02d9a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -29,6 +29,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 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 @@ -37,63 +39,9 @@ import nova.api.openstack LOG = logging.getLogger('server') - - FLAGS = flags.FLAGS -def _translate_detail_keys(inst): - """ Coerces into dictionary format, mapping everything to Rackspace-like - attributes for return""" - 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'] = 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 - - # 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 _translate_keys(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'])) - - class Controller(wsgi.Controller): """ The Server API controller for the OpenStack API """ @@ -101,36 +49,49 @@ 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() 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) + 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, 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(inst)['server'] for inst in limited_list] - return dict(servers=res) + builder = servers_views.get_view_builder(req) + servers = [builder.build(inst, is_detail)['server'] + for inst in limited_list] + 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 _translate_detail_keys(instance) + builder = servers_views.get_view_builder(req) + return builder.build(instance, is_detail=True) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -192,7 +153,8 @@ class Controller(wsgi.Controller): except QuotaError as error: self._handle_quota_errors(error) - server = _translate_keys(instances[0]) + 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)) server['server']['adminPass'] = password diff --git a/nova/api/openstack/views/__init__.py b/nova/api/openstack/views/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/nova/api/openstack/views/__init__.py diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py new file mode 100644 index 000000000..9d392aace --- /dev/null +++ b/nova/api/openstack/views/addresses.py @@ -0,0 +1,54 @@ +# 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): + ''' + A factory method that returns the correct builder based on the version of + the api requested. + ''' + version = common.get_api_version(req) + 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..dd2e75a7a --- /dev/null +++ b/nova/api/openstack/views/flavors.py @@ -0,0 +1,51 @@ +# 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 = common.get_api_version(req) + 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() + + +class ViewBuilder_1_1(ViewBuilder): + def __init__(self, base_url): + self.base_url = base_url + + def generate_href(self, flavor_id): + return "%s/flavors/%s" % (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..2369a8f9d --- /dev/null +++ b/nova/api/openstack/views/images.py @@ -0,0 +1,51 @@ +# 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 = common.get_api_version(req) + 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() + + +class ViewBuilder_1_1(ViewBuilder): + def __init__(self, base_url): + self.base_url = base_url + + def generate_href(self, image_id): + return "%s/images/%s" % (self.base_url, image_id) + + +class ViewBuilder_1_0(ViewBuilder): + pass diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py new file mode 100644 index 000000000..261acfed0 --- /dev/null +++ b/nova/api/openstack/views/servers.py @@ -0,0 +1,132 @@ +# 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 +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 = 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) + 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) + + +class ViewBuilder(object): + ''' + Models a server response as a python dictionary. + Abstract methods: _build_image, _build_flavor + ''' + + 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) + + 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', + 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') + + mapped_keys = dict(status='state', 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.addresses_builder.build(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() + + self._build_image(inst_dict, inst) + self._build_flavor(inst_dict, inst) + + return dict(server=inst_dict) + + def _build_image(self, response, inst): + raise NotImplementedError() + + def _build_flavor(self, response, inst): + raise NotImplementedError() + + +class ViewBuilder_1_0(ViewBuilder): + def _build_image(self, response, inst): + response["imageId"] = inst["image_id"] + + def _build_flavor(self, response, inst): + response["flavorId"] = inst["instance_type"] + + +class ViewBuilder_1_1(ViewBuilder): + 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 _build_image(self, response, inst): + image_id = inst["image_id"] + response["imageRef"] = self.image_builder.generate_href(image_id) + + def _build_flavor(self, response, inst): + flavor_id = inst["instance_type"] + response["flavorRef"] = self.flavor_builder.generate_href(flavor_id) |
