summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorBrian Waldon <brian.waldon@rackspace.com>2011-07-15 14:46:47 -0400
committerBrian Waldon <brian.waldon@rackspace.com>2011-07-15 14:46:47 -0400
commit1dcc37a2c129a225f83c5cc391412d5ac08b869a (patch)
treebbaa201cccc1b4a57368c399c1f5c2a7a30038fa /nova/api
parent486afc9b9e38a68c18b80daab4f23c5b936ee185 (diff)
parent3a11738f517999ed1fd3a2c0a7ca452c7191b50f (diff)
merging trunk
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/openstack/__init__.py12
-rw-r--r--nova/api/openstack/common.py26
-rw-r--r--nova/api/openstack/images.py111
-rw-r--r--nova/api/openstack/ips.py79
-rw-r--r--nova/api/openstack/limits.py108
-rw-r--r--nova/api/openstack/servers.py26
-rw-r--r--nova/api/openstack/views/addresses.py39
-rw-r--r--nova/api/openstack/views/images.py28
-rw-r--r--nova/api/openstack/views/servers.py13
-rw-r--r--nova/api/openstack/wsgi.py23
10 files changed, 364 insertions, 101 deletions
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index f24017df0..e87d7c754 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -125,6 +125,10 @@ class APIRouter(base_wsgi.Router):
collection={'detail': 'GET'},
member=self.server_members)
+ mapper.resource("ip", "ips", controller=ips.create_resource(version),
+ parent_resource=dict(member_name='server',
+ collection_name='servers'))
+
mapper.resource("image", "images",
controller=images.create_resource(version),
collection={'detail': 'GET'})
@@ -144,9 +148,6 @@ class APIRouterV10(APIRouter):
def _setup_routes(self, mapper):
super(APIRouterV10, self)._setup_routes(mapper, '1.0')
- mapper.resource("image", "images",
- controller=images.create_resource('1.0'),
- collection={'detail': 'GET'})
mapper.resource("shared_ip_group", "shared_ip_groups",
collection={'detail': 'GET'},
@@ -157,11 +158,6 @@ class APIRouterV10(APIRouter):
parent_resource=dict(member_name='server',
collection_name='servers'))
- mapper.resource("ip", "ips", controller=ips.create_resource(),
- collection=dict(public='GET', private='GET'),
- parent_resource=dict(member_name='server',
- collection_name='servers'))
-
class APIRouterV11(APIRouter):
"""Define routes specific to OpenStack API V1.1."""
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index 9aa384f33..8e12ce0c0 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -133,14 +133,32 @@ def get_id_from_href(href):
return int(urlparse(href).path.split('/')[-1])
except:
LOG.debug(_("Error extracting id from href: %s") % href)
- raise webob.exc.HTTPBadRequest(_('could not parse id from href'))
+ raise ValueError(_('could not parse id from href'))
-def remove_version_from_href(base_url):
- """Removes the api version from the href.
+def remove_version_from_href(href):
+ """Removes the first api version from the href.
Given: 'http://www.nova.com/v1.1/123'
Returns: 'http://www.nova.com/123'
+ Given: 'http://www.nova.com/v1.1'
+ Returns: 'http://www.nova.com'
+
"""
- return base_url.rsplit('/', 1).pop(0)
+ try:
+ #removes the first instance that matches /v#.#/
+ new_href = re.sub(r'[/][v][0-9]+\.[0-9]+[/]', '/', href, count=1)
+
+ #if no version was found, try finding /v#.# at the end of the string
+ if new_href == href:
+ new_href = re.sub(r'[/][v][0-9]+\.[0-9]+$', '', href, count=1)
+ except:
+ LOG.debug(_("Error removing version from href: %s") % href)
+ msg = _('could not parse version from href')
+ raise ValueError(msg)
+
+ if new_href == href:
+ msg = _('href does not contain version')
+ raise ValueError(msg)
+ return new_href
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index 8ff92b8fe..d0317583e 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -27,6 +27,7 @@ from nova import log
from nova.api.openstack import common
from nova.api.openstack import faults
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
@@ -274,59 +275,99 @@ class ControllerV11(Controller):
class ImageXMLSerializer(wsgi.XMLDictSerializer):
- metadata = {
- "attributes": {
- "image": ["id", "name", "updated", "created", "status",
- "serverId", "progress", "serverRef"],
- "link": ["rel", "type", "href"],
- },
- }
-
xmlns = wsgi.XMLNS_V11
def __init__(self):
self.metadata_serializer = image_metadata.ImageMetadataXMLSerializer()
def _image_to_xml(self, xml_doc, image):
- try:
- metadata = image.pop('metadata').items()
- except Exception:
- LOG.debug(_("Image object missing metadata attribute"))
- metadata = {}
-
- node = self._to_xml_node(xml_doc, self.metadata, 'image', image)
- metadata_node = self.metadata_serializer.meta_list_to_xml(xml_doc,
- metadata)
- node.appendChild(metadata_node)
- return node
-
- def _image_list_to_xml(self, xml_doc, images):
+ image_node = xml_doc.createElement('image')
+ image_node.setAttribute('id', str(image['id']))
+ image_node.setAttribute('name', image['name'])
+ link_nodes = self._create_link_nodes(xml_doc,
+ image['links'])
+ for link_node in link_nodes:
+ image_node.appendChild(link_node)
+ return image_node
+
+ def _image_to_xml_detailed(self, xml_doc, image):
+ image_node = xml_doc.createElement('image')
+ self._add_image_attributes(image_node, image)
+
+ if 'server' in image:
+ server_node = self._create_server_node(xml_doc, image['server'])
+ image_node.appendChild(server_node)
+
+ metadata = image.get('metadata', {}).items()
+ if len(metadata) > 0:
+ metadata_node = self._create_metadata_node(xml_doc, metadata)
+ image_node.appendChild(metadata_node)
+
+ link_nodes = self._create_link_nodes(xml_doc,
+ image['links'])
+ for link_node in link_nodes:
+ image_node.appendChild(link_node)
+
+ return image_node
+
+ def _add_image_attributes(self, node, image):
+ node.setAttribute('id', str(image['id']))
+ node.setAttribute('name', image['name'])
+ node.setAttribute('created', image['created'])
+ node.setAttribute('updated', image['updated'])
+ node.setAttribute('status', image['status'])
+ if 'progress' in image:
+ node.setAttribute('progress', str(image['progress']))
+
+ def _create_metadata_node(self, xml_doc, metadata):
+ return self.metadata_serializer.meta_list_to_xml(xml_doc, metadata)
+
+ def _create_server_node(self, xml_doc, server):
+ server_node = xml_doc.createElement('server')
+ server_node.setAttribute('id', str(server['id']))
+ link_nodes = self._create_link_nodes(xml_doc,
+ server['links'])
+ for link_node in link_nodes:
+ server_node.appendChild(link_node)
+ return server_node
+
+ def _image_list_to_xml(self, xml_doc, images, detailed):
container_node = xml_doc.createElement('images')
+ if detailed:
+ image_to_xml = self._image_to_xml_detailed
+ else:
+ image_to_xml = self._image_to_xml
+
for image in images:
- item_node = self._image_to_xml(xml_doc, image)
+ item_node = image_to_xml(xml_doc, image)
container_node.appendChild(item_node)
return container_node
- def _image_to_xml_string(self, image):
- xml_doc = minidom.Document()
- item_node = self._image_to_xml(xml_doc, image)
- self._add_xmlns(item_node)
- return item_node.toprettyxml(indent=' ')
-
- def _image_list_to_xml_string(self, images):
+ def index(self, images_dict):
xml_doc = minidom.Document()
- container_node = self._image_list_to_xml(xml_doc, images)
- self._add_xmlns(container_node)
- return container_node.toprettyxml(indent=' ')
+ node = self._image_list_to_xml(xml_doc,
+ images_dict['images'],
+ detailed=False)
+ return self.to_xml_string(node, True)
def detail(self, images_dict):
- return self._image_list_to_xml_string(images_dict['images'])
+ xml_doc = minidom.Document()
+ node = self._image_list_to_xml(xml_doc,
+ images_dict['images'],
+ detailed=True)
+ return self.to_xml_string(node, True)
def show(self, image_dict):
- return self._image_to_xml_string(image_dict['image'])
+ xml_doc = minidom.Document()
+ node = self._image_to_xml_detailed(xml_doc,
+ image_dict['image'])
+ return self.to_xml_string(node, True)
def create(self, image_dict):
- return self._image_to_xml_string(image_dict['image'])
+ xml_doc = minidom.Document()
+ node = self._image_to_xml_detailed(xml_doc,
+ image_dict['image'])
+ return self.to_xml_string(node, True)
def create_resource(version='1.0'):
diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py
index 23e5432d6..1ebfdb831 100644
--- a/nova/api/openstack/ips.py
+++ b/nova/api/openstack/ips.py
@@ -23,6 +23,7 @@ import nova
from nova.api.openstack import faults
import nova.api.openstack.views.addresses
from nova.api.openstack import wsgi
+from nova import db
class Controller(object):
@@ -30,7 +31,6 @@ class Controller(object):
def __init__(self):
self.compute_api = nova.compute.API()
- self.builder = nova.api.openstack.views.addresses.ViewBuilderV10()
def _get_instance(self, req, server_id):
try:
@@ -40,29 +40,78 @@ class Controller(object):
return faults.Fault(exc.HTTPNotFound())
return instance
+ def create(self, req, server_id, body):
+ return faults.Fault(exc.HTTPNotImplemented())
+
+ def delete(self, req, server_id, id):
+ return faults.Fault(exc.HTTPNotImplemented())
+
+
+class ControllerV10(Controller):
+
def index(self, req, server_id):
instance = self._get_instance(req, server_id)
- return {'addresses': self.builder.build(instance)}
+ builder = nova.api.openstack.views.addresses.ViewBuilderV10()
+ return {'addresses': builder.build(instance)}
- def public(self, req, server_id):
+ def show(self, req, server_id, id):
instance = self._get_instance(req, server_id)
- return {'public': self.builder.build_public_parts(instance)}
+ builder = self._get_view_builder(req)
+ if id == 'private':
+ view = builder.build_private_parts(instance)
+ elif id == 'public':
+ view = builder.build_public_parts(instance)
+ else:
+ msg = _("Only private and public networks available")
+ return faults.Fault(exc.HTTPNotFound(explanation=msg))
- def private(self, req, server_id):
- instance = self._get_instance(req, server_id)
- return {'private': self.builder.build_private_parts(instance)}
+ return {id: view}
+
+ def _get_view_builder(self, req):
+ return nova.api.openstack.views.addresses.ViewBuilderV10()
+
+
+class ControllerV11(Controller):
+
+ def index(self, req, server_id):
+ context = req.environ['nova.context']
+ interfaces = self._get_virtual_interfaces(context, server_id)
+ networks = self._get_view_builder(req).build(interfaces)
+ return {'addresses': networks}
def show(self, req, server_id, id):
- return faults.Fault(exc.HTTPNotImplemented())
+ context = req.environ['nova.context']
+ interfaces = self._get_virtual_interfaces(context, server_id)
+ network = self._get_view_builder(req).build_network(interfaces, id)
- def create(self, req, server_id, body):
- return faults.Fault(exc.HTTPNotImplemented())
+ if network is None:
+ msg = _("Instance is not a member of specified network")
+ return faults.Fault(exc.HTTPNotFound(explanation=msg))
- def delete(self, req, server_id, id):
- return faults.Fault(exc.HTTPNotImplemented())
+ return network
+
+ def _get_virtual_interfaces(self, context, server_id):
+ try:
+ return db.api.virtual_interface_get_by_instance(context, server_id)
+ except nova.exception.InstanceNotFound:
+ msg = _("Instance does not exist")
+ raise exc.HTTPNotFound(explanation=msg)
+
+ def _get_view_builder(self, req):
+ return nova.api.openstack.views.addresses.ViewBuilderV11()
+
+
+def create_resource(version):
+ controller = {
+ '1.0': ControllerV10,
+ '1.1': ControllerV11,
+ }[version]()
+ xmlns = {
+ '1.0': wsgi.XMLNS_V10,
+ '1.1': wsgi.XMLNS_V11,
+ }[version]
-def create_resource():
metadata = {
'list_collections': {
'public': {'item_name': 'ip', 'item_key': 'addr'},
@@ -72,8 +121,8 @@ def create_resource():
body_serializers = {
'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
- xmlns=wsgi.XMLNS_V10),
+ xmlns=xmlns),
}
serializer = wsgi.ResponseSerializer(body_serializers)
- return wsgi.Resource(Controller(), serializer=serializer)
+ return wsgi.Resource(controller, serializer=serializer)
diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py
index d08287f6b..bc76547d8 100644
--- a/nova/api/openstack/limits.py
+++ b/nova/api/openstack/limits.py
@@ -31,8 +31,8 @@ from collections import defaultdict
from webob.dec import wsgify
from nova import quota
+from nova import utils
from nova import wsgi as base_wsgi
-from nova import wsgi
from nova.api.openstack import common
from nova.api.openstack import faults
from nova.api.openstack.views import limits as limits_views
@@ -119,6 +119,8 @@ class Limit(object):
60 * 60 * 24: "DAY",
}
+ UNIT_MAP = dict([(v, k) for k, v in UNITS.items()])
+
def __init__(self, verb, uri, regex, value, unit):
"""
Initialize a new `Limit`.
@@ -224,16 +226,30 @@ class RateLimitingMiddleware(base_wsgi.Middleware):
is stored in memory for this implementation.
"""
- def __init__(self, application, limits=None):
+ def __init__(self, application, limits=None, limiter=None, **kwargs):
"""
Initialize new `RateLimitingMiddleware`, which wraps the given WSGI
application and sets up the given limits.
@param application: WSGI application to wrap
- @param limits: List of dictionaries describing limits
+ @param limits: String describing limits
+ @param limiter: String identifying class for representing limits
+
+ Other parameters are passed to the constructor for the limiter.
"""
base_wsgi.Middleware.__init__(self, application)
- self._limiter = Limiter(limits or DEFAULT_LIMITS)
+
+ # Select the limiter class
+ if limiter is None:
+ limiter = Limiter
+ else:
+ limiter = utils.import_class(limiter)
+
+ # Parse the limits, if any are provided
+ if limits is not None:
+ limits = limiter.parse_limits(limits)
+
+ self._limiter = limiter(limits or DEFAULT_LIMITS, **kwargs)
@wsgify(RequestClass=wsgi.Request)
def __call__(self, req):
@@ -271,7 +287,7 @@ class Limiter(object):
Rate-limit checking class which handles limits in memory.
"""
- def __init__(self, limits):
+ def __init__(self, limits, **kwargs):
"""
Initialize the new `Limiter`.
@@ -280,6 +296,12 @@ class Limiter(object):
self.limits = copy.deepcopy(limits)
self.levels = defaultdict(lambda: copy.deepcopy(limits))
+ # Pick up any per-user limit information
+ for key, value in kwargs.items():
+ if key.startswith('user:'):
+ username = key[5:]
+ self.levels[username] = self.parse_limits(value)
+
def get_limits(self, username=None):
"""
Return the limits for a given user.
@@ -305,6 +327,66 @@ class Limiter(object):
return None, None
+ # Note: This method gets called before the class is instantiated,
+ # so this must be either a static method or a class method. It is
+ # used to develop a list of limits to feed to the constructor. We
+ # put this in the class so that subclasses can override the
+ # default limit parsing.
+ @staticmethod
+ def parse_limits(limits):
+ """
+ Convert a string into a list of Limit instances. This
+ implementation expects a semicolon-separated sequence of
+ parenthesized groups, where each group contains a
+ comma-separated sequence consisting of HTTP method,
+ user-readable URI, a URI reg-exp, an integer number of
+ requests which can be made, and a unit of measure. Valid
+ values for the latter are "SECOND", "MINUTE", "HOUR", and
+ "DAY".
+
+ @return: List of Limit instances.
+ """
+
+ # Handle empty limit strings
+ limits = limits.strip()
+ if not limits:
+ return []
+
+ # Split up the limits by semicolon
+ result = []
+ for group in limits.split(';'):
+ group = group.strip()
+ if group[:1] != '(' or group[-1:] != ')':
+ raise ValueError("Limit rules must be surrounded by "
+ "parentheses")
+ group = group[1:-1]
+
+ # Extract the Limit arguments
+ args = [a.strip() for a in group.split(',')]
+ if len(args) != 5:
+ raise ValueError("Limit rules must contain the following "
+ "arguments: verb, uri, regex, value, unit")
+
+ # Pull out the arguments
+ verb, uri, regex, value, unit = args
+
+ # Upper-case the verb
+ verb = verb.upper()
+
+ # Convert value--raises ValueError if it's not integer
+ value = int(value)
+
+ # Convert unit
+ unit = unit.upper()
+ if unit not in Limit.UNIT_MAP:
+ raise ValueError("Invalid units specified")
+ unit = Limit.UNIT_MAP[unit]
+
+ # Build a limit
+ result.append(Limit(verb, uri, regex, value, unit))
+
+ return result
+
class WsgiLimiter(object):
"""
@@ -388,3 +470,19 @@ class WsgiLimiterProxy(object):
return None, None
return resp.getheader("X-Wait-Seconds"), resp.read() or None
+
+ # Note: This method gets called before the class is instantiated,
+ # so this must be either a static method or a class method. It is
+ # used to develop a list of limits to feed to the constructor.
+ # This implementation returns an empty list, since all limit
+ # decisions are made by a remote server.
+ @staticmethod
+ def parse_limits(limits):
+ """
+ Ignore a limits string--simply doesn't apply for the limit
+ proxy.
+
+ @return: Empty list.
+ """
+
+ return []
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 50d1442d4..ded1f0610 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -19,6 +19,7 @@ import traceback
from webob import exc
from nova import compute
+from nova import db
from nova import exception
from nova import flags
from nova import log as logging
@@ -62,7 +63,7 @@ class Controller(object):
return exc.HTTPBadRequest(explanation=str(err))
return servers
- def _get_view_builder(self, req):
+ def _build_view(self, req, instance, is_detail=False):
raise NotImplementedError()
def _limit_items(self, items, req):
@@ -88,8 +89,7 @@ class Controller(object):
fixed_ip=fixed_ip,
recurse_zones=recurse_zones)
limited_list = self._limit_items(instance_list, req)
- builder = self._get_view_builder(req)
- servers = [builder.build(inst, is_detail)['server']
+ servers = [self._build_view(req, inst, is_detail)['server']
for inst in limited_list]
return dict(servers=servers)
@@ -99,8 +99,7 @@ class Controller(object):
try:
instance = self.compute_api.routing_get(
req.environ['nova.context'], id)
- builder = self._get_view_builder(req)
- return builder.build(instance, is_detail=True)
+ return self._build_view(req, instance, is_detail=True)
except exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
@@ -121,8 +120,7 @@ class Controller(object):
for key in ['instance_type', 'image_ref']:
inst[key] = extra_values[key]
- builder = self._get_view_builder(req)
- server = builder.build(inst, is_detail=True)
+ server = self._build_view(req, inst, is_detail=True)
server['server']['adminPass'] = extra_values['password']
return server
@@ -426,10 +424,10 @@ class ControllerV10(Controller):
def _flavor_id_from_req_data(self, data):
return data['server']['flavorId']
- def _get_view_builder(self, req):
- addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV10()
- return nova.api.openstack.views.servers.ViewBuilderV10(
- addresses_builder)
+ def _build_view(self, req, instance, is_detail=False):
+ addresses = nova.api.openstack.views.addresses.ViewBuilderV10()
+ builder = nova.api.openstack.views.servers.ViewBuilderV10(addresses)
+ return builder.build(instance, is_detail=is_detail)
def _limit_items(self, items, req):
return common.limited(items, req)
@@ -550,16 +548,18 @@ class ControllerV11(Controller):
else:
return common.get_id_from_href(flavor_ref)
- def _get_view_builder(self, req):
+ def _build_view(self, req, instance, is_detail=False):
base_url = req.application_url
flavor_builder = nova.api.openstack.views.flavors.ViewBuilderV11(
base_url)
image_builder = nova.api.openstack.views.images.ViewBuilderV11(
base_url)
addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV11()
- return nova.api.openstack.views.servers.ViewBuilderV11(
+ builder = nova.api.openstack.views.servers.ViewBuilderV11(
addresses_builder, flavor_builder, image_builder, base_url)
+ return builder.build(instance, is_detail=is_detail)
+
def _action_change_password(self, input_dict, req, id):
context = req.environ['nova.context']
if (not 'changePassword' in input_dict
diff --git a/nova/api/openstack/views/addresses.py b/nova/api/openstack/views/addresses.py
index b59eb4751..a242efa45 100644
--- a/nova/api/openstack/views/addresses.py
+++ b/nova/api/openstack/views/addresses.py
@@ -20,13 +20,14 @@ from nova.api.openstack import common
class ViewBuilder(object):
- ''' Models a server addresses response as a python dictionary.'''
+ """Models a server addresses response as a python dictionary."""
def build(self, inst):
raise NotImplementedError()
class ViewBuilderV10(ViewBuilder):
+
def build(self, inst):
private_ips = self.build_private_parts(inst)
public_ips = self.build_public_parts(inst)
@@ -40,11 +41,31 @@ class ViewBuilderV10(ViewBuilder):
class ViewBuilderV11(ViewBuilder):
- def build(self, inst):
- # TODO(tr3buchet) - this shouldn't be hard coded to 4...
- private_ips = utils.get_from_path(inst, 'fixed_ips/address')
- private_ips = [dict(version=4, addr=a) for a in private_ips]
- public_ips = utils.get_from_path(inst,
- 'fixed_ips/floating_ips/address')
- public_ips = [dict(version=4, addr=a) for a in public_ips]
- return dict(public=public_ips, private=private_ips)
+
+ def build(self, interfaces):
+ networks = {}
+ for interface in interfaces:
+ network_label = interface['network']['label']
+
+ if network_label not in networks:
+ networks[network_label] = []
+
+ networks[network_label].extend(self._extract_ipv4(interface))
+
+ return networks
+
+ def build_network(self, interfaces, network_label):
+ for interface in interfaces:
+ if interface['network']['label'] == network_label:
+ ips = self._extract_ipv4(interface)
+ return {network_label: list(ips)}
+ return None
+
+ def _extract_ipv4(self, interface):
+ for fixed_ip in interface['fixed_ips']:
+ yield self._build_ip_entity(fixed_ip['address'], 4)
+ for floating_ip in fixed_ip.get('floating_ips', []):
+ yield self._build_ip_entity(floating_ip['address'], 4)
+
+ def _build_ip_entity(self, address, version):
+ return {'addr': address, 'version': version}
diff --git a/nova/api/openstack/views/images.py b/nova/api/openstack/views/images.py
index 005341c62..5c0510377 100644
--- a/nova/api/openstack/views/images.py
+++ b/nova/api/openstack/views/images.py
@@ -98,7 +98,20 @@ class ViewBuilderV11(ViewBuilder):
def _build_server(self, image, image_obj):
try:
- image['serverRef'] = image_obj['properties']['instance_ref']
+ 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
@@ -108,18 +121,17 @@ class ViewBuilderV11(ViewBuilder):
href = self.generate_href(image_obj["id"])
bookmark = self.generate_bookmark(image_obj["id"])
- if detail:
- image["metadata"] = image_obj.get("properties", {})
-
image["links"] = [{
"rel": "self",
"href": href,
- },
- {
- "rel": "bookmark",
- "href": bookmark,
}]
+ if detail:
+ image["metadata"] = image_obj.get("properties", {})
+ image["links"].append({"rel": "bookmark",
+ "href": bookmark,
+ })
+
return image
def generate_bookmark(self, image_id):
diff --git a/nova/api/openstack/views/servers.py b/nova/api/openstack/views/servers.py
index 67fb6a84e..ab7e8da61 100644
--- a/nova/api/openstack/views/servers.py
+++ b/nova/api/openstack/views/servers.py
@@ -77,7 +77,6 @@ class ViewBuilder(object):
inst_dict = {
'id': inst['id'],
'name': inst['display_name'],
- 'addresses': self.addresses_builder.build(inst),
'status': power_mapping[inst.get('state')]}
ctxt = nova.context.get_admin_context()
@@ -98,10 +97,15 @@ class ViewBuilder(object):
self._build_image(inst_dict, inst)
self._build_flavor(inst_dict, inst)
+ self._build_addresses(inst_dict, inst)
inst_dict['uuid'] = inst['uuid']
return dict(server=inst_dict)
+ def _build_addresses(self, response, inst):
+ """Return the addresses sub-resource of a server."""
+ raise NotImplementedError()
+
def _build_image(self, response, inst):
"""Return the image sub-resource of a server."""
raise NotImplementedError()
@@ -128,6 +132,9 @@ class ViewBuilderV10(ViewBuilder):
if 'instance_type' in dict(inst):
response['flavorId'] = inst['instance_type']['flavorid']
+ def _build_addresses(self, response, inst):
+ response['addresses'] = self.addresses_builder.build(inst)
+
class ViewBuilderV11(ViewBuilder):
"""Model an Openstack API V1.0 server response."""
@@ -151,6 +158,10 @@ class ViewBuilderV11(ViewBuilder):
flavor_ref = self.flavor_builder.generate_href(flavor_id)
response["flavorRef"] = flavor_ref
+ def _build_addresses(self, response, inst):
+ interfaces = inst.get('virtual_interfaces', [])
+ response['addresses'] = self.addresses_builder.build(interfaces)
+
def _build_extra(self, response, inst):
self._build_links(response, inst)
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index 0ece2cff0..f9aa18ff1 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -291,13 +291,21 @@ class XMLDictSerializer(DictSerializer):
doc = minidom.Document()
node = self._to_xml_node(doc, self.metadata, root_key, data[root_key])
- self._add_xmlns(node)
+ return self.to_xml_string(node)
- return node.toprettyxml(indent=' ', encoding='utf-8')
+ def to_xml_string(self, node, has_atom=False):
+ self._add_xmlns(node, has_atom)
+ return node.toprettyxml(indent=' ', encoding='UTF-8')
- def _add_xmlns(self, node):
+ #NOTE (ameade): the has_atom should be removed after all of the
+ # xml serializers and view builders have been updated to the current
+ # spec that required all responses include the xmlns:atom, the has_atom
+ # flag is to prevent current tests from breaking
+ def _add_xmlns(self, node, has_atom=False):
if self.xmlns is not None:
node.setAttribute('xmlns', self.xmlns)
+ if has_atom:
+ node.setAttribute('xmlns:atom', "http://www.w3.org/2005/Atom")
def _to_xml_node(self, doc, metadata, nodename, data):
"""Recursive method to convert data members to XML nodes."""
@@ -353,6 +361,15 @@ class XMLDictSerializer(DictSerializer):
result.appendChild(node)
return result
+ def _create_link_nodes(self, xml_doc, links):
+ link_nodes = []
+ for link in links:
+ link_node = xml_doc.createElement('atom:link')
+ link_node.setAttribute('rel', link['rel'])
+ link_node.setAttribute('href', link['href'])
+ link_nodes.append(link_node)
+ return link_nodes
+
class ResponseHeadersSerializer(ActionDispatcher):
"""Default response headers serialization"""