summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/openstack/common.py54
-rw-r--r--nova/api/openstack/contrib/createserverext.py36
-rw-r--r--nova/api/openstack/flavors.py49
-rw-r--r--nova/api/openstack/images.py21
-rw-r--r--nova/api/openstack/ips.py27
-rw-r--r--nova/api/openstack/servers.py60
-rw-r--r--nova/api/openstack/views/addresses.py58
-rw-r--r--nova/api/openstack/views/flavors.py93
-rw-r--r--nova/api/openstack/views/images.py271
-rw-r--r--nova/api/openstack/views/servers.py296
-rw-r--r--nova/api/openstack/wsgi.py10
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()