diff options
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/openstack/common.py | 54 | ||||
| -rw-r--r-- | nova/api/openstack/contrib/createserverext.py | 36 | ||||
| -rw-r--r-- | nova/api/openstack/flavors.py | 49 | ||||
| -rw-r--r-- | nova/api/openstack/images.py | 21 | ||||
| -rw-r--r-- | nova/api/openstack/ips.py | 27 | ||||
| -rw-r--r-- | nova/api/openstack/servers.py | 60 | ||||
| -rw-r--r-- | nova/api/openstack/views/addresses.py | 58 | ||||
| -rw-r--r-- | nova/api/openstack/views/flavors.py | 93 | ||||
| -rw-r--r-- | nova/api/openstack/views/images.py | 271 | ||||
| -rw-r--r-- | nova/api/openstack/views/servers.py | 296 | ||||
| -rw-r--r-- | nova/api/openstack/wsgi.py | 10 |
11 files changed, 440 insertions, 535 deletions
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 3b82d358f..1a6800ac0 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -16,6 +16,7 @@ # under the License. import functools +import os import re import urlparse @@ -428,3 +429,56 @@ def check_snapshots_enabled(f): raise webob.exc.HTTPBadRequest(explanation=msg) return f(*args, **kwargs) return inner + + +class ViewBuilder(object): + """Model API responses as dictionaries.""" + + _collection_name = None + + def _get_links(self, request, identifier): + return [{ + "rel": "self", + "href": self._get_href_link(request, identifier), + }, + { + "rel": "bookmark", + "href": self._get_bookmark_link(request, identifier), + }] + + def _get_next_link(self, request, identifier): + """Return href string with proper limit and marker params.""" + params = request.params.copy() + params["marker"] = identifier + url = os.path.join(request.application_url, + request.environ["nova.context"].project_id, + self._collection_name) + return "%s?%s" % (url, dict_to_query_str(params)) + + def _get_href_link(self, request, identifier): + """Return an href string pointing to this object.""" + return os.path.join(request.application_url, + request.environ["nova.context"].project_id, + self._collection_name, + str(identifier)) + + def _get_bookmark_link(self, request, identifier): + """Create a URL that refers to a specific resource.""" + base_url = remove_version_from_href(request.application_url) + return os.path.join(base_url, + request.environ["nova.context"].project_id, + self._collection_name, + str(identifier)) + + def _get_collection_links(self, request, items): + """Retrieve 'next' link, if applicable.""" + links = [] + limit = int(request.params.get("limit", 0)) + if limit and limit == len(items): + last_item = items[-1] + last_item_id = last_item.get("uuid", last_item["id"]) + links.append({ + "rel": "next", + "href": self._get_next_link(request, last_item_id), + }) + return links diff --git a/nova/api/openstack/contrib/createserverext.py b/nova/api/openstack/contrib/createserverext.py index ab5037304..4e6556f5d 100644 --- a/nova/api/openstack/contrib/createserverext.py +++ b/nova/api/openstack/contrib/createserverext.py @@ -14,31 +14,30 @@ # License for the specific language governing permissions and limitations # under the License -from nova import utils from nova.api.openstack import extensions from nova.api.openstack import servers +from nova.api.openstack import views from nova.api.openstack import wsgi -class CreateServerController(servers.Controller): - def _build_view(self, req, instance, is_detail=False, is_create=False): - server = super(CreateServerController, self).\ - _build_view(req, - instance, - is_detail=is_detail, - is_create=is_create) - if is_detail: - self._build_security_groups(server['server'], instance) +class ViewBuilder(views.servers.ViewBuilder): + """Adds security group output when viewing server details.""" + + def show(self, request, instance): + """Detailed view of a single instance.""" + server = super(ViewBuilder, self).show(request, instance) + server["server"]["security_groups"] = self._get_groups(instance) return server - def _build_security_groups(self, response, inst): - sg_names = [] - sec_groups = inst.get('security_groups') - if sec_groups: - sg_names = [sec_group['name'] for sec_group in sec_groups] + def _get_groups(self, instance): + """Get a list of security groups for this instance.""" + groups = instance.get('security_groups') + if groups is not None: + return [{"name": group["name"]} for group in groups] + - response['security_groups'] = utils.convert_to_list_dict(sg_names, - 'name') +class Controller(servers.Controller): + _view_builder_class = ViewBuilder class Createserverext(extensions.ExtensionDescriptor): @@ -64,9 +63,10 @@ class Createserverext(extensions.ExtensionDescriptor): serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) deserializer = wsgi.RequestDeserializer(body_deserializers) + controller = Controller() res = extensions.ResourceExtension('os-create-server-ext', - controller=CreateServerController(), + controller=controller, deserializer=deserializer, serializer=serializer) resources.append(res) diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py index 7280ecbce..7680ef1ca 100644 --- a/nova/api/openstack/flavors.py +++ b/nova/api/openstack/flavors.py @@ -18,7 +18,7 @@ import webob from lxml import etree -from nova.api.openstack import views +from nova.api.openstack.views import flavors as flavors_view from nova.api.openstack import wsgi from nova.api.openstack import xmlutil from nova.compute import instance_types @@ -26,20 +26,31 @@ from nova import db from nova import exception -class Controller(object): +class Controller(wsgi.Controller): """Flavor controller for the OpenStack API.""" + _view_builder_class = flavors_view.ViewBuilder + def index(self, req): """Return all flavors in brief.""" - items = self._get_flavors(req, is_detail=False) - return dict(flavors=items) + flavors = self._get_flavors(req) + return self._view_builder.index(req, flavors) def detail(self, req): """Return all flavors in detail.""" - items = self._get_flavors(req, is_detail=True) - return dict(flavors=items) + flavors = self._get_flavors(req) + return self._view_builder.detail(req, flavors) + + def show(self, req, id): + """Return data about the given flavor id.""" + try: + flavor = instance_types.get_instance_type_by_flavor_id(id) + except exception.NotFound: + raise webob.exc.HTTPNotFound() + + return self._view_builder.show(req, flavor) - def _get_flavors(self, req, is_detail=True): + def _get_flavors(self, req): """Helper function that returns a list of flavor dicts.""" filters = {} if 'minRam' in req.params: @@ -54,29 +65,7 @@ class Controller(object): except ValueError: pass # ignore bogus values per spec - ctxt = req.environ['nova.context'] - inst_types = instance_types.get_all_types(filters=filters) - builder = self._get_view_builder(req) - items = [builder.build(inst_type, is_detail=is_detail) - for inst_type in inst_types.values()] - return items - - def show(self, req, id): - """Return data about the given flavor id.""" - try: - ctxt = req.environ['nova.context'] - flavor = instance_types.get_instance_type_by_flavor_id(id) - except exception.NotFound: - raise webob.exc.HTTPNotFound() - - builder = self._get_view_builder(req) - values = builder.build(flavor, is_detail=True) - return dict(flavor=values) - - def _get_view_builder(self, req): - base_url = req.application_url - project_id = getattr(req.environ['nova.context'], 'project_id', '') - return views.flavors.ViewBuilder(base_url, project_id) + return instance_types.get_all_types(filters=filters) def make_flavor(elem, detailed=False): diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index b86b48ff5..f3ef63b49 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -26,9 +26,9 @@ from nova import log from nova.api.openstack import common from nova.api.openstack import image_metadata from nova.api.openstack import servers -from nova.api.openstack.views import images as images_view from nova.api.openstack import wsgi from nova.api.openstack import xmlutil +from nova.api.openstack.views import images as views_images LOG = log.getLogger('nova.api.openstack.images') @@ -45,16 +45,19 @@ SUPPORTED_FILTERS = { } -class Controller(object): +class Controller(wsgi.Controller): """Base controller for retrieving/displaying images.""" - def __init__(self, image_service=None, compute_service=None): + _view_builder_class = views_images.ViewBuilder + + def __init__(self, image_service=None, compute_service=None, **kwargs): """Initialize new `ImageController`. :param compute_service: `nova.compute.api:API` :param image_service: `nova.image.glance:GlancemageService` """ + super(Controller, self).__init__(**kwargs) self._compute_service = compute_service or compute.API() self._image_service = image_service or \ nova.image.get_default_image_service() @@ -88,7 +91,7 @@ class Controller(object): explanation = _("Image not found.") raise webob.exc.HTTPNotFound(explanation=explanation) - return dict(image=self.get_builder(req).build(image, detail=True)) + return self._view_builder.show(req, image) def delete(self, req, id): """Delete an image, if allowed. @@ -104,12 +107,6 @@ class Controller(object): raise webob.exc.HTTPNotFound(explanation=explanation) return webob.exc.HTTPNoContent() - def get_builder(self, req): - """Property to get the ViewBuilder class we need to use.""" - base_url = req.application_url - project_id = getattr(req.environ['nova.context'], 'project_id', '') - return images_view.ViewBuilder(base_url, project_id) - def index(self, req): """Return an index listing of images available to the request. @@ -125,7 +122,7 @@ class Controller(object): images = self._image_service.index(context, filters=filters, **page_params) - return self.get_builder(req).build_list(images, **params) + return self._view_builder.index(req, images) def detail(self, req): """Return a detailed index listing of images available to the request. @@ -142,7 +139,7 @@ class Controller(object): images = self._image_service.detail(context, filters=filters, **page_params) - return self.get_builder(req).build_list(images, detail=True, **params) + return self._view_builder.detail(req, images) def create(self, *args, **kwargs): raise webob.exc.HTTPMethodNotAllowed() diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py index 1ab9a4fdc..2bef453a2 100644 --- a/nova/api/openstack/ips.py +++ b/nova/api/openstack/ips.py @@ -21,9 +21,9 @@ from webob import exc import nova from nova.api.openstack import common -from nova.api.openstack.views import addresses as views_addresses from nova.api.openstack import wsgi from nova.api.openstack import xmlutil +from nova.api.openstack.views import addresses as view_addresses from nova import log as logging from nova import flags @@ -32,15 +32,18 @@ LOG = logging.getLogger('nova.api.openstack.ips') FLAGS = flags.FLAGS -class Controller(object): +class Controller(wsgi.Controller): """The servers addresses API controller for the Openstack API.""" - def __init__(self): - self.compute_api = nova.compute.API() + _view_builder_class = view_addresses.ViewBuilder + + def __init__(self, **kwargs): + super(Controller, self).__init__(**kwargs) + self._compute_api = nova.compute.API() def _get_instance(self, context, server_id): try: - instance = self.compute_api.get(context, server_id) + instance = self._compute_api.get(context, server_id) except nova.exception.NotFound: msg = _("Instance does not exist") raise exc.HTTPNotFound(explanation=msg) @@ -53,25 +56,21 @@ class Controller(object): raise exc.HTTPNotImplemented() def index(self, req, server_id): - context = req.environ['nova.context'] + context = req.environ["nova.context"] instance = self._get_instance(context, server_id) networks = common.get_networks_for_instance(context, instance) - return {'addresses': self._get_view_builder(req).build(networks)} + return self._view_builder.index(networks) def show(self, req, server_id, id): - context = req.environ['nova.context'] + context = req.environ["nova.context"] instance = self._get_instance(context, server_id) networks = common.get_networks_for_instance(context, instance) - network = self._get_view_builder(req).build_network(networks, id) - if network is None: + if id not in networks: msg = _("Instance is not a member of specified network") raise exc.HTTPNotFound(explanation=msg) - return network - - def _get_view_builder(self, req): - return views_addresses.ViewBuilder() + return self._view_builder.show(networks[id], id) def make_network(elem): diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 677635354..8b11b30f6 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -57,10 +57,13 @@ class ConvertedException(exc.WSGIHTTPException): super(ConvertedException, self).__init__() -class Controller(object): +class Controller(wsgi.Controller): """ The Server API base controller class for the OpenStack API """ - def __init__(self): + _view_builder_class = views_servers.ViewBuilder + + def __init__(self, **kwargs): + super(Controller, self).__init__(**kwargs) self.compute_api = compute.API() self.network_api = network.API() @@ -139,7 +142,10 @@ class Controller(object): search_opts=search_opts) limited_list = self._limit_items(instance_list, req) - return self._build_list(req, limited_list, is_detail=is_detail) + if is_detail: + return self._view_builder.detail(req, limited_list) + else: + return self._view_builder.index(req, limited_list) def _get_server(self, context, instance_uuid): """Utility function for looking up an instance by uuid""" @@ -291,13 +297,12 @@ class Controller(object): try: instance = self.compute_api.routing_get( req.environ['nova.context'], id) - return self._build_view(req, instance, is_detail=True) + return self._view_builder.show(req, instance) except exception.NotFound: raise exc.HTTPNotFound() def create(self, req, body): """ Creates a new server for a given user """ - if not body: raise exc.HTTPUnprocessableEntity() @@ -441,17 +446,13 @@ class Controller(object): if ret_resv_id: return {'reservation_id': resv_id} - # Instances is a list - instance = instances[0] - if not instance.get('_is_precooked', False): - instance['instance_type'] = inst_type - instance['image_ref'] = image_href + server = self._view_builder.create(req, instances[0]) - server = self._build_view(req, instance, is_create=True) - if '_is_precooked' in server['server']: + if '_is_precooked' in server['server'].keys(): del server['server']['_is_precooked'] else: server['server']['adminPass'] = password + return server def _delete(self, context, id): @@ -497,13 +498,12 @@ class Controller(object): raise exc.HTTPNotFound() instance = self.compute_api.routing_get(ctxt, id) - return self._build_view(req, instance, is_detail=True) + return self._view_builder.show(req, instance) @exception.novaclient_converter @scheduler_api.redirect_handler def action(self, req, id, body): """Multi-purpose method used to take actions on a server""" - self.actions = { 'changePassword': self._action_change_password, 'reboot': self._action_reboot, @@ -716,36 +716,6 @@ class Controller(object): return common.get_id_from_href(flavor_ref) - def _build_view(self, req, instance, is_detail=False, is_create=False): - context = req.environ['nova.context'] - project_id = getattr(context, 'project_id', '') - base_url = req.application_url - flavor_builder = views_flavors.ViewBuilder(base_url, project_id) - image_builder = views_images.ViewBuilder(base_url, project_id) - addresses_builder = views_addresses.ViewBuilder() - builder = views_servers.ViewBuilder(context, addresses_builder, - flavor_builder, image_builder, base_url, project_id) - return builder.build(instance, - is_detail=is_detail, - is_create=is_create) - - def _build_list(self, req, instances, is_detail=False): - params = req.GET.copy() - pagination_params = common.get_pagination_params(req) - # Update params with int() values from pagination params - for key, val in pagination_params.iteritems(): - params[key] = val - - context = req.environ['nova.context'] - project_id = getattr(context, 'project_id', '') - base_url = req.application_url - flavor_builder = views_flavors.ViewBuilder(base_url, project_id) - image_builder = views_images.ViewBuilder(base_url, project_id) - addresses_builder = views_addresses.ViewBuilder() - builder = views_servers.ViewBuilder(context, addresses_builder, - flavor_builder, image_builder, base_url, project_id) - return builder.build_list(instances, is_detail=is_detail, **params) - def _action_change_password(self, input_dict, req, id): context = req.environ['nova.context'] if (not 'changePassword' in input_dict @@ -824,7 +794,7 @@ class Controller(object): raise exc.HTTPNotFound(explanation=msg) instance = self._get_server(context, instance_id) - view = self._build_view(request, instance, is_detail=True) + view = self._view_builder.show(request, instance) view['server']['adminPass'] = password return view diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py index 2a5286646..1dbcaffe0 100644 --- a/nova/api/openstack/views/addresses.py +++ b/nova/api/openstack/views/addresses.py @@ -17,6 +17,7 @@ import itertools +from nova.api.openstack import common from nova import flags from nova import log as logging @@ -25,36 +26,27 @@ FLAGS = flags.FLAGS LOG = logging.getLogger('nova.api.openstack.views.addresses') -class ViewBuilder(object): - """Models a server addresses response as a python dictionary.""" - - def _extract_ips(self, network, key=None): - if key: - chain = network[key] - else: - chain = itertools.chain(network['ips'], network['floating_ips']) - for ip in chain: - if not FLAGS.use_ipv6 and ip['version'] == 6: - continue - yield ip - - def build(self, networks): - result = {} - for network in networks: - if network not in result: - result[network] = [] - - result[network].extend(self._extract_ips(networks[network])) - return result - - def build_network(self, networks, requested_network): - for network in networks: - if network == requested_network: - return {network: list(self._extract_ips(networks[network]))} - return None - - def _extract_network_label(self, network): - try: - return network['label'] - except (TypeError, KeyError) as exc: - raise TypeError +class ViewBuilder(common.ViewBuilder): + """Models server addresses as a dictionary.""" + + _collection_name = "addresses" + + def basic(self, ip): + """Return a dictionary describing an IP address.""" + return { + "version": ip["version"], + "addr": ip["addr"], + } + + def show(self, network, label): + """Returns a dictionary describing a network.""" + all_ips = itertools.chain(network["ips"], network["floating_ips"]) + return {label: [self.basic(ip) for ip in all_ips]} + + def index(self, networks): + """Return a dictionary describing a list of networks.""" + addresses = {} + for label, network in networks.items(): + network_dict = self.show(network, label) + addresses[label] = network_dict[label] + return dict(addresses=addresses) diff --git a/nova/api/openstack/views/flavors.py b/nova/api/openstack/views/flavors.py index 9e180640b..f84ae5987 100644 --- a/nova/api/openstack/views/flavors.py +++ b/nova/api/openstack/views/flavors.py @@ -21,73 +21,46 @@ import os.path from nova.api.openstack import common -class ViewBuilder(object): +class ViewBuilder(common.ViewBuilder): - def __init__(self, base_url, project_id=""): - """ - :param base_url: url of the root wsgi application - """ - self.base_url = base_url - self.project_id = project_id + _collection_name = "flavors" - def build(self, flavor_obj, is_detail=False): - """Generic method used to generate a flavor entity.""" - if is_detail: - flavor = self._build_detail(flavor_obj) - else: - flavor = self._build_simple(flavor_obj) - - flavor["links"] = self._build_links(flavor) - - return flavor - - def _build_simple(self, flavor_obj): - """Build a minimal representation of a flavor.""" + def basic(self, request, flavor): return { - "id": flavor_obj["flavorid"], - "name": flavor_obj["name"], + "flavor": { + "id": flavor["flavorid"], + "name": flavor["name"], + "links": self._get_links(request, flavor["flavorid"]), + }, } - def _build_detail(self, flavor_obj): - """Build a more complete representation of a flavor.""" - simple = self._build_simple(flavor_obj) - - detail = { - "ram": flavor_obj["memory_mb"], - "disk": flavor_obj["local_gb"], + def show(self, request, flavor): + return { + "flavor": { + "id": flavor["flavorid"], + "name": flavor["name"], + "ram": flavor["memory_mb"], + "disk": flavor["local_gb"], + "vcpus": flavor.get("vcpus") or "", + "swap": flavor.get("swap") or "", + "rxtx_quota": flavor.get("rxtx_quota") or "", + "rxtx_cap": flavor.get("rxtx_cap") or "", + "links": self._get_links(request, flavor["flavorid"]), + }, } - for key in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"): - detail[key] = flavor_obj.get(key, "") - - detail.update(simple) - - return detail - - def _build_links(self, flavor_obj): - """Generate a container of links that refer to the provided flavor.""" - href = self.generate_href(flavor_obj["id"]) - bookmark = self.generate_bookmark(flavor_obj["id"]) - - links = [ - { - "rel": "self", - "href": href, - }, - { - "rel": "bookmark", - "href": bookmark, - }, - ] + def index(self, request, flavors): + """Return the 'index' view of flavors.""" + def _get_flavors(request, flavors): + for _, flavor in flavors.iteritems(): + yield self.basic(request, flavor)["flavor"] - return links + return dict(flavors=list(_get_flavors(request, flavors))) - def generate_href(self, flavor_id): - """Create an url that refers to a specific flavor id.""" - return os.path.join(self.base_url, self.project_id, - "flavors", str(flavor_id)) + def detail(self, request, flavors): + """Return the 'detail' view of flavors.""" + def _get_flavors(request, flavors): + for _, flavor in flavors.iteritems(): + yield self.show(request, flavor)["flavor"] - def generate_bookmark(self, flavor_id): - """Create an url that refers to a specific flavor id.""" - return os.path.join(common.remove_version_from_href(self.base_url), - self.project_id, "flavors", str(flavor_id)) + return dict(flavors=list(_get_flavors(request, flavors))) diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py index 9ef4c3ea0..c4cfe8031 100644 --- a/nova/api/openstack/views/images.py +++ b/nova/api/openstack/views/images.py @@ -21,180 +21,119 @@ from nova.api.openstack import common from nova import utils -class ViewBuilder(object): - """Base class for generating responses to OpenStack API image requests.""" +class ViewBuilder(common.ViewBuilder): - def __init__(self, base_url, project_id=""): - """Initialize new `ViewBuilder`.""" - self.base_url = base_url - self.project_id = project_id + _collection_name = "images" - def _format_dates(self, image): - """Update all date fields to ensure standardized formatting.""" - for attr in ['created_at', 'updated_at', 'deleted_at']: - if image.get(attr) is not None: - image[attr] = image[attr].strftime('%Y-%m-%dT%H:%M:%SZ') + def basic(self, request, image): + """Return a dictionary with basic image attributes.""" + return { + "image": { + "id": image.get("id"), + "name": image.get("name"), + "links": self._get_links(request, image["id"]), + }, + } - def _format_status(self, image): - """Update the status field to standardize format.""" + def show(self, request, image): + """Return a dictionary with image details.""" + image_dict = { + "id": image.get("id"), + "name": image.get("name"), + "minRam": int(image.get("min_ram") or 0), + "minDisk": int(image.get("min_disk") or 0), + "metadata": image.get("properties", {}), + "created": self._format_date(image.get("created_at")), + "updated": self._format_date(image.get("updated_at")), + "status": self._get_status(image), + "progress": self._get_progress(image), + "links": self._get_links(request, image["id"]), + } - if 'status' not in image: - return + server_ref = image.get("properties", {}).get("instance_ref") + + if server_ref is not None: + image_dict["server"] = { + "id": common.get_id_from_href(server_ref), + "links": [{ + "rel": "self", + "href": server_ref, + }, + { + "rel": "bookmark", + "href": common.remove_version_from_href(server_ref), + }], + } - status_mapping = { + return dict(image=image_dict) + + def detail(self, request, images): + """Show a list of images with details.""" + list_func = self.show + return self._list_view(list_func, request, images) + + def index(self, request, images): + """Show a list of images with basic attributes.""" + list_func = self.basic + return self._list_view(list_func, request, images) + + def _list_view(self, list_func, request, images): + """Provide a view for a list of images.""" + image_list = [list_func(request, image)["image"] for image in images] + images_links = self._get_collection_links(request, images) + images_dict = dict(images=image_list) + + if images_links: + images_dict["images_links"] = images_links + + return images_dict + + def _get_links(self, request, identifier): + """Return a list of links for this image.""" + return [{ + "rel": "self", + "href": self._get_href_link(request, identifier), + }, + { + "rel": "bookmark", + "href": self._get_bookmark_link(request, identifier), + }, + { + "rel": "alternate", + "type": "application/vnd.openstack.image", + "href": self._get_alternate_link(request, identifier), + }] + + def _get_alternate_link(self, request, identifier): + """Create an alternate link for a specific flavor id.""" + glance_url = utils.generate_glance_url() + return os.path.join(glance_url, + request.environ["nova.context"].project_id, + self._collection_name, + str(identifier)) + + @staticmethod + def _format_date(date_string): + """Return standard format for given date.""" + if date_string is not None: + return date_string.strftime('%Y-%m-%dT%H:%M:%SZ') + + @staticmethod + def _get_status(image): + """Update the status field to standardize format.""" + return { 'active': 'ACTIVE', 'queued': 'SAVING', 'saving': 'SAVING', 'deleted': 'DELETED', 'pending_delete': 'DELETED', 'killed': 'ERROR', - } - - try: - image['status'] = status_mapping[image['status']] - except KeyError: - image['status'] = 'UNKNOWN' - - def _get_progress_for_status(self, status): - progress_map = { - 'queued': 25, - 'saving': 50, - 'active': 100, - } - return progress_map.get(status, 0) - - def _build_server(self, image, image_obj): - """Indicates that you must use a ViewBuilder subclass.""" - raise NotImplementedError() - - def generate_href(self, image_id): - """Return an href string pointing to this object.""" - return os.path.join(self.base_url, "images", str(image_id)) - - def build(self, image_obj, detail=False): - """Return a standardized image structure for display by the API.""" - self._format_dates(image_obj) - - orig_status = image_obj.get('status', '').lower() - self._format_status(image_obj) - - image = { - "id": image_obj.get("id"), - "name": image_obj.get("name"), - } - - self._build_server(image, image_obj) - self._build_image_id(image, image_obj) - - href = self.generate_href(image_obj["id"]) - bookmark = self.generate_bookmark(image_obj["id"]) - alternate = self.generate_alternate(image_obj["id"]) - - image["links"] = [ - { - "rel": "self", - "href": href, - }, - { - "rel": "bookmark", - "href": bookmark, - }, - { - "rel": "alternate", - "type": "application/vnd.openstack.image", - "href": alternate, - }, - - ] - - if detail: - image.update({ - "created": image_obj.get("created_at"), - "updated": image_obj.get("updated_at"), - "status": image_obj.get("status"), - }) - image["progress"] = self._get_progress_for_status(orig_status) - - image["metadata"] = image_obj.get("properties", {}) - - min_ram = image_obj.get('min_ram') or 0 - try: - min_ram = int(min_ram) - except ValueError: - min_ram = 0 - image['minRam'] = min_ram - - min_disk = image_obj.get('min_disk') or 0 - try: - min_disk = int(min_disk) - except ValueError: - min_disk = 0 - image['minDisk'] = min_disk - - return image - - def _build_server(self, image, image_obj): - try: - serverRef = image_obj['properties']['instance_ref'] - image['server'] = { - "id": common.get_id_from_href(serverRef), - "links": [ - { - "rel": "self", - "href": serverRef, - }, - { - "rel": "bookmark", - "href": common.remove_version_from_href(serverRef), - }, - ] - } - except KeyError: - return - - def _build_image_id(self, image, image_obj): - image['id'] = "%s" % image_obj['id'] - - def generate_href(self, image_id): - """Return an href string pointing to this object.""" - return os.path.join(self.base_url, self.project_id, - "images", str(image_id)) - - def generate_next_link(self, image_id, params): - """ Return an href string with proper limit and marker params""" - params['marker'] = image_id - return "%s?%s" % ( - os.path.join(self.base_url, self.project_id, "images"), - common.dict_to_query_str(params)) - - def build_list(self, image_objs, detail=False, **kwargs): - """Return a standardized image list structure for display.""" - limit = kwargs.get('limit', None) - images = [] - images_links = [] - - for image_obj in image_objs: - image = self.build(image_obj, detail=detail) - images.append(image) - - if (len(images) and limit) and (limit == len(images)): - next_link = self.generate_next_link(images[-1]["id"], kwargs) - images_links = [dict(rel="next", href=next_link)] - - reval = dict(images=images) - if len(images_links) > 0: - reval['images_links'] = images_links - - return reval - - def generate_bookmark(self, image_id): - """Create a URL that refers to a specific flavor id.""" - return os.path.join(common.remove_version_from_href(self.base_url), - self.project_id, "images", str(image_id)) - - def generate_alternate(self, image_id): - """Create an alternate link for a specific flavor id.""" - glance_url = utils.generate_glance_url() - - return "%s/%s/images/%s" % (glance_url, self.project_id, - str(image_id)) + }.get(image.get("status"), 'UNKNOWN') + + @staticmethod + def _get_progress(image): + return { + "queued": 25, + "saving": 50, + "active": 100, + }.get(image.get("status"), 0) diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py index 4a0be46c1..e6f4bc2c4 100644 --- a/nova/api/openstack/views/servers.py +++ b/nova/api/openstack/views/servers.py @@ -20,6 +20,9 @@ import hashlib import os from nova.api.openstack import common +from nova.api.openstack.views import addresses as views_addresses +from nova.api.openstack.views import flavors as views_flavors +from nova.api.openstack.views import images as views_images from nova.compute import vm_states from nova import exception from nova import log as logging @@ -29,168 +32,147 @@ from nova import utils LOG = logging.getLogger('nova.api.openstack.views.servers') -class ViewBuilder(object): - """Model a server response as a python dictionary.""" - - def __init__(self, context, addresses_builder, flavor_builder, - image_builder, base_url, project_id=""): - self.context = context - self.addresses_builder = addresses_builder - self.flavor_builder = flavor_builder - self.image_builder = image_builder - self.base_url = base_url - self.project_id = project_id - - def build(self, inst, is_detail=False, is_create=False): - """Return a dict that represenst a server.""" - if inst.get('_is_precooked', False): - server = dict(server=inst) - else: - if is_detail: - server = self._build_detail(inst) - elif is_create: - server = self._build_create(inst) - else: - server = self._build_simple(inst) +class ViewBuilder(common.ViewBuilder): + """Model a server API response as a python dictionary.""" - self._build_links(server['server'], inst) + _collection_name = "servers" - return server + _progress_statuses = ( + "ACTIVE", + "BUILD", + "REBUILD", + "RESIZE", + "VERIFY_RESIZE", + ) - def _build_simple(self, inst): - """Return a simple model of a server.""" - return dict(server=dict(id=inst['uuid'], name=inst['display_name'])) + def __init__(self): + """Initialize view builder.""" + super(ViewBuilder, self).__init__() + self._address_builder = views_addresses.ViewBuilder() + self._flavor_builder = views_flavors.ViewBuilder() + self._image_builder = views_images.ViewBuilder() + + def _skip_precooked(func): + def wrapped(self, request, instance): + if instance.get("_is_precooked"): + return dict(server=instance) + else: + return func(self, request, instance) + return wrapped + + def create(self, request, instance): + """View that should be returned when an instance is created.""" + return { + "server": { + "id": instance["uuid"], + "links": self._get_links(request, instance["uuid"]), + }, + } + + @_skip_precooked + def basic(self, request, instance): + """Generic, non-detailed view of an instance.""" + return { + "server": { + "id": instance["uuid"], + "name": instance["display_name"], + "links": self._get_links(request, instance["uuid"]), + }, + } + + @_skip_precooked + def show(self, request, instance): + """Detailed view of a single instance.""" + server = { + "server": { + "id": instance["uuid"], + "name": instance["display_name"], + "status": self._get_vm_state(instance), + "tenant_id": instance.get("project_id") or "", + "user_id": instance.get("user_id") or "", + "metadata": self._get_metadata(instance), + "hostId": self._get_host_id(instance) or "", + "image": self._get_image(request, instance), + "flavor": self._get_flavor(request, instance), + "created": utils.isotime(instance["created_at"]), + "updated": utils.isotime(instance["updated_at"]), + "addresses": self._get_addresses(request, instance), + "accessIPv4": instance.get("access_ip_v4") or "", + "accessIPv6": instance.get("access_ip_v6") or "", + "key_name": instance.get("key_name") or "", + "config_drive": instance.get("config_drive"), + "links": self._get_links(request, instance["uuid"]), + }, + } + + if server["server"]["status"] in self._progress_statuses: + server["server"]["progress"] = instance.get("progress", 0) - def _build_create(self, inst): - """Return data that should be returned from a server create.""" - server = dict(server=dict(id=inst['uuid'])) - self._build_links(server['server'], inst) return server - def _build_detail(self, inst): - """Returns a detailed model of a server.""" - vm_state = inst.get('vm_state', vm_states.BUILDING) - task_state = inst.get('task_state') - - inst_dict = { - 'id': inst['uuid'], - 'name': inst['display_name'], - 'user_id': inst.get('user_id', ''), - 'tenant_id': inst.get('project_id', ''), - 'status': common.status_from_state(vm_state, task_state)} - - # Return the metadata as a dictionary - metadata = {} - for item in inst.get('metadata', []): - metadata[item['key']] = str(item['value']) - inst_dict['metadata'] = metadata - - inst_dict['hostId'] = '' - if inst.get('host'): - inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest() - - self._build_image(inst_dict, inst) - self._build_flavor(inst_dict, inst) - networks = common.get_networks_for_instance(self.context, inst) - self._build_addresses(inst_dict, networks) - - inst_dict['created'] = utils.isotime(inst['created_at']) - inst_dict['updated'] = utils.isotime(inst['updated_at']) - - status = inst_dict.get('status') - if status in ('ACTIVE', 'BUILD', 'REBUILD', 'RESIZE', - 'VERIFY_RESIZE'): - inst_dict['progress'] = inst['progress'] or 0 - - inst_dict['accessIPv4'] = inst.get('access_ip_v4') or "" - inst_dict['accessIPv6'] = inst.get('access_ip_v6') or "" - inst_dict['key_name'] = inst.get('key_name', '') - inst_dict['config_drive'] = inst.get('config_drive') - - return dict(server=inst_dict) - - def _build_addresses(self, response, networks): - """Return the addresses sub-resource of a server.""" - response['addresses'] = self.addresses_builder.build(networks) - - def _build_image(self, response, inst): - if inst.get("image_ref", None): - image_href = inst['image_ref'] - image_id = str(common.get_id_from_href(image_href)) - _bookmark = self.image_builder.generate_bookmark(image_id) - response['image'] = { - "id": image_id, - "links": [ - { - "rel": "bookmark", - "href": _bookmark, - }, - ] - } - - def _build_flavor(self, response, inst): - if inst.get("instance_type", None): - flavor_id = inst["instance_type"]['flavorid'] - flavor_ref = self.flavor_builder.generate_href(flavor_id) - flavor_bookmark = self.flavor_builder.generate_bookmark(flavor_id) - response["flavor"] = { - "id": str(common.get_id_from_href(flavor_ref)), - "links": [ - { - "rel": "bookmark", - "href": flavor_bookmark, - }, - ] - } - - def _build_links(self, response, inst): - href = self.generate_href(inst["uuid"]) - bookmark = self.generate_bookmark(inst["uuid"]) - - links = [ - { - "rel": "self", - "href": href, - }, - { + def index(self, request, instances): + """Show a list of servers without many details.""" + list_func = self.basic + return self._list_view(list_func, request, instances) + + def detail(self, request, instances): + """Detailed view of a list of instance.""" + list_func = self.show + return self._list_view(list_func, request, instances) + + def _list_view(self, func, request, servers): + """Provide a view for a list of servers.""" + server_list = [func(request, server)["server"] for server in servers] + servers_links = self._get_collection_links(request, servers) + servers_dict = dict(servers=server_list) + + if servers_links: + servers_dict["servers_links"] = servers_links + + return servers_dict + + @staticmethod + def _get_metadata(instance): + metadata = instance.get("metadata", []) + return dict((item['key'], str(item['value'])) for item in metadata) + + @staticmethod + def _get_vm_state(instance): + return common.status_from_state(instance.get("vm_state"), + instance.get("task_state")) + + @staticmethod + def _get_host_id(instance): + host = instance.get("host") + if host: + return hashlib.sha224(host).hexdigest() # pylint: disable=E1101 + + def _get_addresses(self, request, instance): + context = request.environ["nova.context"] + networks = common.get_networks_for_instance(context, instance) + return self._address_builder.index(networks)["addresses"] + + def _get_image(self, request, instance): + image_ref = instance["image_ref"] + image_id = str(common.get_id_from_href(image_ref)) + bookmark = self._image_builder._get_bookmark_link(request, image_id) + return { + "id": image_id, + "links": [{ "rel": "bookmark", "href": bookmark, - }, - ] - - response["links"] = links - - def build_list(self, server_objs, is_detail=False, **kwargs): - limit = kwargs.get('limit', None) - servers = [] - servers_links = [] - - for server_obj in server_objs: - servers.append(self.build(server_obj, is_detail)['server']) - - if (len(servers) and limit) and (limit == len(servers)): - next_link = self.generate_next_link(servers[-1]['id'], - kwargs, is_detail) - servers_links = [dict(rel='next', href=next_link)] - - reval = dict(servers=servers) - if len(servers_links) > 0: - reval['servers_links'] = servers_links - return reval - - def generate_next_link(self, server_id, params, is_detail=False): - """ Return an href string with proper limit and marker params""" - params['marker'] = server_id - return "%s?%s" % ( - os.path.join(self.base_url, self.project_id, "servers"), - common.dict_to_query_str(params)) - - def generate_href(self, server_id): - """Create an url that refers to a specific server id.""" - return os.path.join(self.base_url, self.project_id, - "servers", str(server_id)) - - def generate_bookmark(self, server_id): - """Create an url that refers to a specific flavor id.""" - return os.path.join(common.remove_version_from_href(self.base_url), - self.project_id, "servers", str(server_id)) + }], + } + + def _get_flavor(self, request, instance): + flavor_id = instance["instance_type"]["flavorid"] + flavor_ref = self._flavor_builder._get_href_link(request, flavor_id) + flavor_bookmark = self._flavor_builder._get_bookmark_link(request, + flavor_id) + return { + "id": str(common.get_id_from_href(flavor_ref)), + "links": [{ + "rel": "bookmark", + "href": flavor_bookmark, + }], + } diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 1d1337626..8c6b30ca9 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -602,3 +602,13 @@ class Resource(wsgi.Application): except TypeError as exc: LOG.exception(exc) return faults.Fault(webob.exc.HTTPBadRequest()) + + +class Controller(object): + """Default controller.""" + + _view_builder_class = None + + def __init__(self, view_builder=None): + """Initialize controller with a view builder instance.""" + self._view_builder = view_builder or self._view_builder_class() |
