diff options
| author | Brian Lamar <brian.lamar@rackspace.com> | 2011-10-26 13:57:35 -0500 |
|---|---|---|
| committer | Brian Lamar <brian.lamar@rackspace.com> | 2011-11-16 17:33:40 -0500 |
| commit | e7aa4022374fb35d2131a7c633212c5d6302db3d (patch) | |
| tree | e23723c410275c12f74215e9dbf3f4a5fa69d4d6 | |
| parent | 217af7df5980ee7a258d2e8b24aea4444c083201 (diff) | |
Refactoring/cleanup of some view builders.
I have re-worked the "addresses", "flavors", "images", and "servers" view
builders to share logic which was previously duplicated across many ViewBuilder
classes. Also, I have re-worked the ViewBuilder class in general to persist
longer.
Overall we had good test coverage so not many tests had to be updated during
the refactor.
(Patch Set 2) Updated _set_request and _get_request to be a request property
and a set_request method which can be overridden.
(Patch Set 3) Fixed tests that I broke with the last update.
(Patch Set 4) Changed "id in network.keys()" to "id in network"
(Patch Set 5) Feedback changes from bcwaldon
(Patch Set 6) Updated based on feedback from Waldon.
(Patch Set 7) Feedback from s1rp (Rick Harris)
Change-Id: I034404892018e99987f80789d7f7e406ff31658c
| -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 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_images.py | 7 | ||||
| -rw-r--r-- | nova/tests/api/openstack/test_servers.py | 140 |
13 files changed, 516 insertions, 606 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() diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index d42c66940..eb200046f 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -974,9 +974,10 @@ class ImagesControllerTest(test.TestCase): self.mox.VerifyAll() def test_generate_alternate_link(self): - view = images_view.ViewBuilder(1) - generated_url = view.generate_alternate(1) - actual_url = "%s//images/1" % utils.generate_glance_url() + view = images_view.ViewBuilder() + request = fakes.HTTPRequest.blank('/v1.1/fake/images/1') + generated_url = view._get_alternate_link(request, 1) + actual_url = "%s/fake/images/1" % utils.generate_glance_url() self.assertEqual(generated_url, actual_url) def test_delete_image(self): diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 4588ca859..7eaaaa489 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -536,6 +536,30 @@ class ServersControllerTest(test.TestCase): } self.assertDictMatch(res_dict, expected) + def test_get_server_addresses_with_floating(self): + privates = ['192.168.0.3', '192.168.0.4'] + publics = ['172.19.0.1', '1.2.3.4', '172.19.0.2'] + new_return_server = return_server_with_attributes_by_uuid( + public_ips=publics, private_ips=privates, + public_ips_are_floating=True) + self.stubs.Set(nova.db, 'instance_get_by_uuid', new_return_server) + + req = fakes.HTTPRequest.blank('/v1.1/fake/servers/%s/ips' % FAKE_UUID) + res_dict = self.ips_controller.index(req, FAKE_UUID) + + expected = { + 'addresses': { + 'private': [ + {'version': 4, 'addr': '192.168.0.3'}, + {'version': 4, 'addr': '192.168.0.4'}, + {'version': 4, 'addr': '172.19.0.1'}, + {'version': 4, 'addr': '1.2.3.4'}, + {'version': 4, 'addr': '172.19.0.2'}, + ], + }, + } + self.assertDictMatch(res_dict, expected) + def test_get_server_addresses_single_network(self): self.flags(use_ipv6=True) privates = ['192.168.0.3', '192.168.0.4'] @@ -589,6 +613,7 @@ class ServersControllerTest(test.TestCase): if '_is_precooked' in s: self.assertEqual(s.get('reservation_id'), 'child') else: + print s self.assertEqual(s.get('name'), 'server%d' % i) i += 1 @@ -1369,7 +1394,8 @@ class ServersControllerCreateTest(test.TestCase): req.headers["content-type"] = "application/json" res = self.controller.create(req, body) - self.assertEqual(['server'], res.keys()) + self.assertEqual(FAKE_UUID, res["server"]["id"]) + self.assertEqual(12, len(res["server"]["adminPass"])) def test_create_multiple_instances_resv_id_return(self): """Test creating multiple instances with asking for @@ -1518,24 +1544,6 @@ class ServersControllerCreateTest(test.TestCase): image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6' image_href = 'http://localhost/v1.1/images/%s' % image_uuid flavor_ref = 'http://localhost/123/flavors/3' - expected_flavor = { - "id": "3", - "links": [ - { - "rel": "bookmark", - "href": 'http://localhost/fake/flavors/3', - }, - ], - } - expected_image = { - "id": image_uuid, - "links": [ - { - "rel": "bookmark", - "href": 'http://localhost/fake/images/%s' % image_uuid, - }, - ], - } body = { 'server': { 'name': 'server_test', @@ -1590,7 +1598,8 @@ class ServersControllerCreateTest(test.TestCase): req.headers["content-type"] = "application/json" res = self.controller.create(req, body) - self.assertEqual(res.keys(), ['server']) + self.assertEqual(FAKE_UUID, res["server"]["id"]) + self.assertEqual(12, len(res["server"]["adminPass"])) def test_create_instance_invalid_flavor_href(self): image_href = 'http://localhost/v1.1/images/2' @@ -2295,26 +2304,12 @@ class ServersViewBuilderTest(test.TestCase): include_fake_metadata=False) self.uuid = self.instance['uuid'] - self.view_builder = self._get_view_builder() - - def _get_view_builder(self, project_id=""): - base_url = "http://localhost/v1.1" - views = nova.api.openstack.views - address_builder = views.addresses.ViewBuilder() - flavor_builder = views.flavors.ViewBuilder(base_url, project_id) - image_builder = views.images.ViewBuilder(base_url, project_id) - - ctxt = context.RequestContext('fake_user', project_id) - view_builder = nova.api.openstack.views.servers.ViewBuilder( - ctxt, - address_builder, - flavor_builder, - image_builder, - base_url, - project_id) - return view_builder + self.view_builder = nova.api.openstack.views.servers.ViewBuilder() + self.request = fakes.HTTPRequest.blank("/v1.1") def test_build_server(self): + self_link = "http://localhost/v1.1/fake/servers/%s" % self.uuid + bookmark_link = "http://localhost/fake/servers/%s" % self.uuid expected_server = { "server": { "id": self.uuid, @@ -2322,17 +2317,17 @@ class ServersViewBuilderTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/servers/%s" % self.uuid, + "href": self_link, }, { "rel": "bookmark", - "href": "http://localhost/servers/%s" % self.uuid, + "href": bookmark_link, }, ], } } - output = self.view_builder.build(self.instance, False) + output = self.view_builder.basic(self.request, self.instance) self.assertDictMatch(output, expected_server) def test_build_server_with_project_id(self): @@ -2354,13 +2349,14 @@ class ServersViewBuilderTest(test.TestCase): } } - view_builder = self._get_view_builder(project_id='fake') - output = view_builder.build(self.instance, False) + output = self.view_builder.basic(self.request, self.instance) self.assertDictMatch(output, expected_server) def test_build_server_detail(self): - image_bookmark = "http://localhost/images/5" - flavor_bookmark = "http://localhost/flavors/1" + image_bookmark = "http://localhost/fake/images/5" + flavor_bookmark = "http://localhost/fake/flavors/1" + self_link = "http://localhost/v1.1/fake/servers/%s" % self.uuid + bookmark_link = "http://localhost/fake/servers/%s" % self.uuid expected_server = { "server": { "id": self.uuid, @@ -2407,25 +2403,27 @@ class ServersViewBuilderTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/servers/%s" % self.uuid, + "href": self_link, }, { "rel": "bookmark", - "href": "http://localhost/servers/%s" % self.uuid, + "href": bookmark_link, }, ], } } - output = self.view_builder.build(self.instance, True) + output = self.view_builder.show(self.request, self.instance) self.assertDictMatch(output, expected_server) def test_build_server_detail_active_status(self): #set the power state of the instance to running self.instance['vm_state'] = vm_states.ACTIVE self.instance['progress'] = 100 - image_bookmark = "http://localhost/images/5" - flavor_bookmark = "http://localhost/flavors/1" + image_bookmark = "http://localhost/fake/images/5" + flavor_bookmark = "http://localhost/fake/flavors/1" + self_link = "http://localhost/v1.1/fake/servers/%s" % self.uuid + bookmark_link = "http://localhost/fake/servers/%s" % self.uuid expected_server = { "server": { "id": self.uuid, @@ -2472,25 +2470,27 @@ class ServersViewBuilderTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/servers/%s" % self.uuid, + "href": self_link, }, { "rel": "bookmark", - "href": "http://localhost/servers/%s" % self.uuid, + "href": bookmark_link, }, ], } } - output = self.view_builder.build(self.instance, True) + output = self.view_builder.show(self.request, self.instance) self.assertDictMatch(output, expected_server) def test_build_server_detail_with_accessipv4(self): self.instance['access_ip_v4'] = '1.2.3.4' - image_bookmark = "http://localhost/images/5" - flavor_bookmark = "http://localhost/flavors/1" + image_bookmark = "http://localhost/fake/images/5" + flavor_bookmark = "http://localhost/fake/flavors/1" + self_link = "http://localhost/v1.1/fake/servers/%s" % self.uuid + bookmark_link = "http://localhost/fake/servers/%s" % self.uuid expected_server = { "server": { "id": self.uuid, @@ -2537,25 +2537,27 @@ class ServersViewBuilderTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/servers/%s" % self.uuid, + "href": self_link, }, { "rel": "bookmark", - "href": "http://localhost/servers/%s" % self.uuid, + "href": bookmark_link, }, ], } } - output = self.view_builder.build(self.instance, True) + output = self.view_builder.show(self.request, self.instance) self.assertDictMatch(output, expected_server) def test_build_server_detail_with_accessipv6(self): self.instance['access_ip_v6'] = 'fead::1234' - image_bookmark = "http://localhost/images/5" - flavor_bookmark = "http://localhost/flavors/1" + image_bookmark = "http://localhost/fake/images/5" + flavor_bookmark = "http://localhost/fake/flavors/1" + self_link = "http://localhost/v1.1/fake/servers/%s" % self.uuid + bookmark_link = "http://localhost/fake/servers/%s" % self.uuid expected_server = { "server": { "id": self.uuid, @@ -2602,17 +2604,17 @@ class ServersViewBuilderTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/servers/%s" % self.uuid, + "href": self_link, }, { "rel": "bookmark", - "href": "http://localhost/servers/%s" % self.uuid, + "href": bookmark_link, }, ], } } - output = self.view_builder.build(self.instance, True) + output = self.view_builder.show(self.request, self.instance) self.assertDictMatch(output, expected_server) def test_build_server_detail_with_metadata(self): @@ -2622,8 +2624,10 @@ class ServersViewBuilderTest(test.TestCase): metadata.append(InstanceMetadata(key="Number", value=1)) self.instance['metadata'] = metadata - image_bookmark = "http://localhost/images/5" - flavor_bookmark = "http://localhost/flavors/1" + image_bookmark = "http://localhost/fake/images/5" + flavor_bookmark = "http://localhost/fake/flavors/1" + self_link = "http://localhost/v1.1/fake/servers/%s" % self.uuid + bookmark_link = "http://localhost/fake/servers/%s" % self.uuid expected_server = { "server": { "id": self.uuid, @@ -2673,17 +2677,17 @@ class ServersViewBuilderTest(test.TestCase): "links": [ { "rel": "self", - "href": "http://localhost/v1.1/servers/%s" % self.uuid, + "href": self_link, }, { "rel": "bookmark", - "href": "http://localhost/servers/%s" % self.uuid, + "href": bookmark_link, }, ], } } - output = self.view_builder.build(self.instance, True) + output = self.view_builder.show(self.request, self.instance) self.assertDictMatch(output, expected_server) |
