summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2011-10-17 22:17:19 +0000
committerGerrit Code Review <review@openstack.org>2011-10-17 22:17:19 +0000
commit821fae95d6aa86ffd14a4e48254da8ee7392c042 (patch)
tree35e173df84e1b0fa296c997ca4401ad03fb84cbc /nova/api
parented0c5731b70771e08e1ae75db0a0a0cf6e72c9e9 (diff)
parent9a15c0d070db086111cbe5eff4f19dcb419b32bc (diff)
downloadnova-821fae95d6aa86ffd14a4e48254da8ee7392c042.tar.gz
nova-821fae95d6aa86ffd14a4e48254da8ee7392c042.tar.xz
nova-821fae95d6aa86ffd14a4e48254da8ee7392c042.zip
Merge "Add XML templates."
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/openstack/accounts.py25
-rw-r--r--nova/api/openstack/common.py91
-rw-r--r--nova/api/openstack/consoles.py49
-rw-r--r--nova/api/openstack/extensions.py14
-rw-r--r--nova/api/openstack/flavors.py92
-rw-r--r--nova/api/openstack/images.py133
-rw-r--r--nova/api/openstack/ips.py65
-rw-r--r--nova/api/openstack/limits.py78
-rw-r--r--nova/api/openstack/servers.py220
-rw-r--r--nova/api/openstack/users.py40
-rw-r--r--nova/api/openstack/wsgi.py49
-rw-r--r--nova/api/openstack/xmlutil.py855
-rw-r--r--nova/api/openstack/zones.py71
13 files changed, 1388 insertions, 394 deletions
diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py
index a13a758ab..3a19d5c89 100644
--- a/nova/api/openstack/accounts.py
+++ b/nova/api/openstack/accounts.py
@@ -22,6 +22,7 @@ from nova import log as logging
from nova.auth import manager
from nova.api.openstack import faults
from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
FLAGS = flags.FLAGS
@@ -80,15 +81,25 @@ class Controller(object):
return dict(account=_translate_keys(account))
-def create_resource():
- metadata = {
- "attributes": {
- "account": ["id", "name", "description", "manager"],
- },
- }
+class AccountTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('account', selector='account')
+ root.set('id', 'id')
+ root.set('name', 'name')
+ root.set('description', 'description')
+ root.set('manager', 'manager')
+
+ return xmlutil.MasterTemplate(root, 1)
+
+class AccountXMLSerializer(xmlutil.XMLTemplateSerializer):
+ def default(self):
+ return AccountTemplate()
+
+
+def create_resource():
body_serializers = {
- 'application/xml': wsgi.XMLDictSerializer(metadata=metadata),
+ 'application/xml': AccountXMLSerializer(),
}
serializer = wsgi.ResponseSerializer(body_serializers)
return wsgi.Resource(Controller(), serializer=serializer)
diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py
index 30ca43414..4a26ee42f 100644
--- a/nova/api/openstack/common.py
+++ b/nova/api/openstack/common.py
@@ -356,52 +356,51 @@ class MetadataHeadersSerializer(wsgi.ResponseHeadersSerializer):
response.status_int = 204
-class MetadataXMLSerializer(wsgi.XMLDictSerializer):
-
- NSMAP = {None: xmlutil.XMLNS_V11}
-
- def __init__(self, xmlns=wsgi.XMLNS_V11):
- super(MetadataXMLSerializer, self).__init__(xmlns=xmlns)
-
- def populate_metadata(self, metadata_elem, meta_dict):
- for (key, value) in meta_dict.items():
- elem = etree.SubElement(metadata_elem, 'meta')
- elem.set('key', str(key))
- elem.text = value
-
- def _populate_meta_item(self, meta_elem, meta_item_dict):
- """Populate a meta xml element from a dict."""
- (key, value) = meta_item_dict.items()[0]
- meta_elem.set('key', str(key))
- meta_elem.text = value
-
- def index(self, metadata_dict):
- metadata = etree.Element('metadata', nsmap=self.NSMAP)
- self.populate_metadata(metadata, metadata_dict.get('metadata', {}))
- return self._to_xml(metadata)
-
- def create(self, metadata_dict):
- metadata = etree.Element('metadata', nsmap=self.NSMAP)
- self.populate_metadata(metadata, metadata_dict.get('metadata', {}))
- return self._to_xml(metadata)
-
- def update_all(self, metadata_dict):
- metadata = etree.Element('metadata', nsmap=self.NSMAP)
- self.populate_metadata(metadata, metadata_dict.get('metadata', {}))
- return self._to_xml(metadata)
-
- def show(self, meta_item_dict):
- meta = etree.Element('meta', nsmap=self.NSMAP)
- self._populate_meta_item(meta, meta_item_dict['meta'])
- return self._to_xml(meta)
-
- def update(self, meta_item_dict):
- meta = etree.Element('meta', nsmap=self.NSMAP)
- self._populate_meta_item(meta, meta_item_dict['meta'])
- return self._to_xml(meta)
-
- def default(self, *args, **kwargs):
- return ''
+metadata_nsmap = {None: xmlutil.XMLNS_V11}
+
+
+class MetaItemTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ sel = xmlutil.Selector('meta', xmlutil.get_items, 0)
+ root = xmlutil.TemplateElement('meta', selector=sel)
+ root.set('key', 0)
+ root.text = 1
+ return xmlutil.MasterTemplate(root, 1, nsmap=metadata_nsmap)
+
+
+class MetadataTemplateElement(xmlutil.TemplateElement):
+ def will_render(self, datum):
+ return True
+
+
+class MetadataTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = MetadataTemplateElement('metadata', selector='metadata')
+ elem = xmlutil.SubTemplateElement(root, 'meta',
+ selector=xmlutil.get_items)
+ elem.set('key', 0)
+ elem.text = 1
+ return xmlutil.MasterTemplate(root, 1, nsmap=metadata_nsmap)
+
+
+class MetadataXMLSerializer(xmlutil.XMLTemplateSerializer):
+ def index(self):
+ return MetadataTemplate()
+
+ def create(self):
+ return MetadataTemplate()
+
+ def update_all(self):
+ return MetadataTemplate()
+
+ def show(self):
+ return MetaItemTemplate()
+
+ def update(self):
+ return MetaItemTemplate()
+
+ def default(self):
+ return xmlutil.MasterTemplate(None, 1)
def check_snapshots_enabled(f):
diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py
index 8f6dbaadf..d42d34069 100644
--- a/nova/api/openstack/consoles.py
+++ b/nova/api/openstack/consoles.py
@@ -21,6 +21,7 @@ import webob
from nova import console
from nova import exception
from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
def _translate_keys(cons):
@@ -89,5 +90,51 @@ class Controller(object):
return webob.Response(status_int=202)
+class ConsoleTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('console', selector='console')
+
+ id_elem = xmlutil.SubTemplateElement(root, 'id', selector='id')
+ id_elem.text = xmlutil.Selector()
+
+ port_elem = xmlutil.SubTemplateElement(root, 'port', selector='port')
+ port_elem.text = xmlutil.Selector()
+
+ host_elem = xmlutil.SubTemplateElement(root, 'host', selector='host')
+ host_elem.text = xmlutil.Selector()
+
+ passwd_elem = xmlutil.SubTemplateElement(root, 'password',
+ selector='password')
+ passwd_elem.text = xmlutil.Selector()
+
+ constype_elem = xmlutil.SubTemplateElement(root, 'console_type',
+ selector='console_type')
+ constype_elem.text = xmlutil.Selector()
+
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class ConsolesTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('consoles')
+ console = xmlutil.SubTemplateElement(root, 'console',
+ selector='consoles')
+ console.append(ConsoleTemplate())
+
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class ConsoleXMLSerializer(xmlutil.XMLTemplateSerializer):
+ def index(self):
+ return ConsolesTemplate()
+
+ def show(self):
+ return ConsoleTemplate()
+
+
def create_resource():
- return wsgi.Resource(Controller())
+ body_serializers = {
+ 'application/xml': ConsoleXMLSerializer(),
+ }
+ serializer = wsgi.ResponseSerializer(body_serializers)
+ return wsgi.Resource(Controller(), serializer=serializer)
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py
index 43bc06014..6eb085d09 100644
--- a/nova/api/openstack/extensions.py
+++ b/nova/api/openstack/extensions.py
@@ -30,6 +30,7 @@ import webob.exc
from nova import exception
from nova import flags
from nova import log as logging
+from nova import utils
from nova import wsgi as base_wsgi
import nova.api.openstack
from nova.api.openstack import common
@@ -159,9 +160,20 @@ class RequestExtensionController(object):
def process(self, req, *args, **kwargs):
res = req.get_response(self.application)
+
+ # Deserialize the response body, if any
+ body = None
+ if res.body:
+ body = utils.loads(res.body)
+
# currently request handlers are un-ordered
for handler in self.handlers:
- res = handler(req, res)
+ res = handler(req, res, body)
+
+ # Reserialize the response body
+ if body is not None:
+ res.body = utils.dumps(body)
+
return res
diff --git a/nova/api/openstack/flavors.py b/nova/api/openstack/flavors.py
index cd6562e4f..0727ee258 100644
--- a/nova/api/openstack/flavors.py
+++ b/nova/api/openstack/flavors.py
@@ -81,50 +81,54 @@ class Controller(object):
return views.flavors.ViewBuilder(base_url, project_id)
-class FlavorXMLSerializer(wsgi.XMLDictSerializer):
-
- NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
-
- def __init__(self):
- super(FlavorXMLSerializer, self).__init__(xmlns=wsgi.XMLNS_V11)
-
- def _populate_flavor(self, flavor_elem, flavor_dict, detailed=False):
- """Populate a flavor xml element from a dict."""
-
- flavor_elem.set('name', flavor_dict['name'])
- flavor_elem.set('id', str(flavor_dict['id']))
- if detailed:
- flavor_elem.set('ram', str(flavor_dict['ram']))
- flavor_elem.set('disk', str(flavor_dict['disk']))
-
- for attr in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"):
- flavor_elem.set(attr, str(flavor_dict.get(attr, "")))
-
- for link in flavor_dict.get('links', []):
- elem = etree.SubElement(flavor_elem,
- '{%s}link' % xmlutil.XMLNS_ATOM)
- elem.set('rel', link['rel'])
- elem.set('href', link['href'])
- return flavor_elem
-
- def show(self, flavor_container):
- flavor = etree.Element('flavor', nsmap=self.NSMAP)
- self._populate_flavor(flavor, flavor_container['flavor'], True)
- return self._to_xml(flavor)
-
- def detail(self, flavors_dict):
- flavors = etree.Element('flavors', nsmap=self.NSMAP)
- for flavor_dict in flavors_dict['flavors']:
- flavor = etree.SubElement(flavors, 'flavor')
- self._populate_flavor(flavor, flavor_dict, True)
- return self._to_xml(flavors)
-
- def index(self, flavors_dict):
- flavors = etree.Element('flavors', nsmap=self.NSMAP)
- for flavor_dict in flavors_dict['flavors']:
- flavor = etree.SubElement(flavors, 'flavor')
- self._populate_flavor(flavor, flavor_dict, False)
- return self._to_xml(flavors)
+def make_flavor(elem, detailed=False):
+ elem.set('name')
+ elem.set('id')
+ if detailed:
+ elem.set('ram')
+ elem.set('disk')
+
+ for attr in ("vcpus", "swap", "rxtx_quota", "rxtx_cap"):
+ elem.set(attr, xmlutil.EmptyStringSelector(attr))
+
+ xmlutil.make_links(elem, 'links')
+
+
+flavor_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
+
+
+class FlavorTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('flavor', selector='flavor')
+ make_flavor(root, detailed=True)
+ return xmlutil.MasterTemplate(root, 1, nsmap=flavor_nsmap)
+
+
+class MinimalFlavorsTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('flavors')
+ elem = xmlutil.SubTemplateElement(root, 'flavor', selector='flavors')
+ make_flavor(elem)
+ return xmlutil.MasterTemplate(root, 1, nsmap=flavor_nsmap)
+
+
+class FlavorsTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('flavors')
+ elem = xmlutil.SubTemplateElement(root, 'flavor', selector='flavors')
+ make_flavor(elem, detailed=True)
+ return xmlutil.MasterTemplate(root, 1, nsmap=flavor_nsmap)
+
+
+class FlavorXMLSerializer(xmlutil.XMLTemplateSerializer):
+ def show(self):
+ return FlavorTemplate()
+
+ def detail(self):
+ return FlavorsTemplate()
+
+ def index(self):
+ return MinimalFlavorsTemplate()
def create_resource():
diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py
index 11b847266..b86b48ff5 100644
--- a/nova/api/openstack/images.py
+++ b/nova/api/openstack/images.py
@@ -148,82 +148,63 @@ class Controller(object):
raise webob.exc.HTTPMethodNotAllowed()
-class ImageXMLSerializer(wsgi.XMLDictSerializer):
-
- NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
-
- def __init__(self):
- self.metadata_serializer = common.MetadataXMLSerializer()
-
- def _create_metadata_node(self, metadata_dict):
- metadata_elem = etree.Element('metadata', nsmap=self.NSMAP)
- self.metadata_serializer.populate_metadata(metadata_elem,
- metadata_dict)
- return metadata_elem
-
- def _create_server_node(self, server_dict):
- server_elem = etree.Element('server', nsmap=self.NSMAP)
- server_elem.set('id', str(server_dict['id']))
- for link in server_dict.get('links', []):
- elem = etree.SubElement(server_elem,
- '{%s}link' % xmlutil.XMLNS_ATOM)
- elem.set('rel', link['rel'])
- elem.set('href', link['href'])
- return server_elem
-
- def _populate_image(self, image_elem, image_dict, detailed=False):
- """Populate an image xml element from a dict."""
-
- image_elem.set('name', image_dict['name'])
- image_elem.set('id', str(image_dict['id']))
- if detailed:
- image_elem.set('updated', str(image_dict['updated']))
- image_elem.set('created', str(image_dict['created']))
- image_elem.set('status', str(image_dict['status']))
- if 'progress' in image_dict:
- image_elem.set('progress', str(image_dict['progress']))
- if 'minRam' in image_dict:
- image_elem.set('minRam', str(image_dict['minRam']))
- if 'minDisk' in image_dict:
- image_elem.set('minDisk', str(image_dict['minDisk']))
- if 'server' in image_dict:
- server_elem = self._create_server_node(image_dict['server'])
- image_elem.append(server_elem)
-
- meta_elem = self._create_metadata_node(
- image_dict.get('metadata', {}))
- image_elem.append(meta_elem)
-
- self._populate_links(image_elem, image_dict.get('links', []))
-
- def _populate_links(self, parent, links):
- for link in links:
- elem = etree.SubElement(parent, '{%s}link' % xmlutil.XMLNS_ATOM)
- elem.set('rel', link['rel'])
- if 'type' in link:
- elem.set('type', link['type'])
- elem.set('href', link['href'])
-
- def index(self, images_dict):
- images = etree.Element('images', nsmap=self.NSMAP)
- for image_dict in images_dict['images']:
- image = etree.SubElement(images, 'image')
- self._populate_image(image, image_dict, False)
-
- self._populate_links(images, images_dict.get('images_links', []))
- return self._to_xml(images)
-
- def detail(self, images_dict):
- images = etree.Element('images', nsmap=self.NSMAP)
- for image_dict in images_dict['images']:
- image = etree.SubElement(images, 'image')
- self._populate_image(image, image_dict, True)
- return self._to_xml(images)
-
- def show(self, image_dict):
- image = etree.Element('image', nsmap=self.NSMAP)
- self._populate_image(image, image_dict['image'], True)
- return self._to_xml(image)
+def make_image(elem, detailed=False):
+ elem.set('name')
+ elem.set('id')
+
+ if detailed:
+ elem.set('updated')
+ elem.set('created')
+ elem.set('status')
+ elem.set('progress')
+ elem.set('minRam')
+ elem.set('minDisk')
+
+ server = xmlutil.SubTemplateElement(elem, 'server', selector='server')
+ server.set('id')
+ xmlutil.make_links(server, 'links')
+
+ elem.append(common.MetadataTemplate())
+
+ xmlutil.make_links(elem, 'links')
+
+
+image_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
+
+
+class ImageTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('image', selector='image')
+ make_image(root, detailed=True)
+ return xmlutil.MasterTemplate(root, 1, nsmap=image_nsmap)
+
+
+class MinimalImagesTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('images')
+ elem = xmlutil.SubTemplateElement(root, 'image', selector='images')
+ make_image(elem)
+ xmlutil.make_links(root, 'images_links')
+ return xmlutil.MasterTemplate(root, 1, nsmap=image_nsmap)
+
+
+class ImagesTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('images')
+ elem = xmlutil.SubTemplateElement(root, 'image', selector='images')
+ make_image(elem, detailed=True)
+ return xmlutil.MasterTemplate(root, 1, nsmap=image_nsmap)
+
+
+class ImageXMLSerializer(xmlutil.XMLTemplateSerializer):
+ def index(self):
+ return MinimalImagesTemplate()
+
+ def detail(self):
+ return ImagesTemplate()
+
+ def show(self):
+ return ImageTemplate()
def create_resource():
diff --git a/nova/api/openstack/ips.py b/nova/api/openstack/ips.py
index 48fd4eeb7..1ab9a4fdc 100644
--- a/nova/api/openstack/ips.py
+++ b/nova/api/openstack/ips.py
@@ -74,37 +74,40 @@ class Controller(object):
return views_addresses.ViewBuilder()
-class IPXMLSerializer(wsgi.XMLDictSerializer):
-
- NSMAP = {None: xmlutil.XMLNS_V11}
-
- def __init__(self, xmlns=wsgi.XMLNS_V11):
- super(IPXMLSerializer, self).__init__(xmlns=xmlns)
-
- def populate_addresses_node(self, addresses_elem, addresses_dict):
- for (network_id, ip_dicts) in addresses_dict.items():
- network_elem = self._create_network_node(network_id, ip_dicts)
- addresses_elem.append(network_elem)
-
- def _create_network_node(self, network_id, ip_dicts):
- network_elem = etree.Element('network', nsmap=self.NSMAP)
- network_elem.set('id', str(network_id))
- for ip_dict in ip_dicts:
- ip_elem = etree.SubElement(network_elem, 'ip')
- ip_elem.set('version', str(ip_dict['version']))
- ip_elem.set('addr', ip_dict['addr'])
- return network_elem
-
- def show(self, network_dict):
- (network_id, ip_dicts) = network_dict.items()[0]
- network = self._create_network_node(network_id, ip_dicts)
- return self._to_xml(network)
-
- def index(self, addresses_dict):
- addresses = etree.Element('addresses', nsmap=self.NSMAP)
- self.populate_addresses_node(addresses,
- addresses_dict.get('addresses', {}))
- return self._to_xml(addresses)
+def make_network(elem):
+ elem.set('id', 0)
+
+ ip = xmlutil.SubTemplateElement(elem, 'ip', selector=1)
+ ip.set('version')
+ ip.set('addr')
+
+
+network_nsmap = {None: xmlutil.XMLNS_V11}
+
+
+class NetworkTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ sel = xmlutil.Selector(xmlutil.get_items, 0)
+ root = xmlutil.TemplateElement('network', selector=sel)
+ make_network(root)
+ return xmlutil.MasterTemplate(root, 1, nsmap=network_nsmap)
+
+
+class AddressesTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('addresses', selector='addresses')
+ elem = xmlutil.SubTemplateElement(root, 'network',
+ selector=xmlutil.get_items)
+ make_network(elem)
+ return xmlutil.MasterTemplate(root, 1, nsmap=network_nsmap)
+
+
+class IPXMLSerializer(xmlutil.XMLTemplateSerializer):
+ def show(self):
+ return NetworkTemplate()
+
+ def index(self):
+ return AddressesTemplate()
def create_resource():
diff --git a/nova/api/openstack/limits.py b/nova/api/openstack/limits.py
index 564f62b12..0b549de2e 100644
--- a/nova/api/openstack/limits.py
+++ b/nova/api/openstack/limits.py
@@ -68,53 +68,37 @@ class LimitsController(object):
return limits_views.ViewBuilder()
-class LimitsXMLSerializer(wsgi.XMLDictSerializer):
-
- xmlns = wsgi.XMLNS_V11
-
- NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
-
- def __init__(self):
- pass
-
- def _create_rates_node(self, rates):
- rates_elem = etree.Element('rates', nsmap=self.NSMAP)
- for rate in rates:
- rate_node = etree.SubElement(rates_elem, 'rate')
- rate_node.set('uri', rate['uri'])
- rate_node.set('regex', rate['regex'])
- for limit in rate['limit']:
- limit_elem = etree.SubElement(rate_node, 'limit')
- limit_elem.set('value', str(limit['value']))
- limit_elem.set('verb', str(limit['verb']))
- limit_elem.set('remaining', str(limit['remaining']))
- limit_elem.set('unit', str(limit['unit']))
- limit_elem.set('next-available', str(limit['next-available']))
- return rates_elem
-
- def _create_absolute_node(self, absolute_dict):
- absolute_elem = etree.Element('absolute', nsmap=self.NSMAP)
- for key, value in absolute_dict.items():
- limit_elem = etree.SubElement(absolute_elem, 'limit')
- limit_elem.set('name', str(key))
- limit_elem.set('value', str(value))
- return absolute_elem
-
- def _populate_limits(self, limits_elem, limits_dict):
- """Populate a limits xml element from a dict."""
-
- rates_elem = self._create_rates_node(
- limits_dict.get('rate', []))
- limits_elem.append(rates_elem)
-
- absolutes_elem = self._create_absolute_node(
- limits_dict.get('absolute', {}))
- limits_elem.append(absolutes_elem)
-
- def index(self, limits_dict):
- limits = etree.Element('limits', nsmap=self.NSMAP)
- self._populate_limits(limits, limits_dict['limits'])
- return self._to_xml(limits)
+limits_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
+
+
+class LimitsTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('limits', selector='limits')
+
+ rates = xmlutil.SubTemplateElement(root, 'rates')
+ rate = xmlutil.SubTemplateElement(rates, 'rate', selector='rate')
+ rate.set('uri', 'uri')
+ rate.set('regex', 'regex')
+ limit = xmlutil.SubTemplateElement(rate, 'limit', selector='limit')
+ limit.set('value', 'value')
+ limit.set('verb', 'verb')
+ limit.set('remaining', 'remaining')
+ limit.set('unit', 'unit')
+ limit.set('next-available', 'next-available')
+
+ absolute = xmlutil.SubTemplateElement(root, 'absolute',
+ selector='absolute')
+ limit = xmlutil.SubTemplateElement(absolute, 'limit',
+ selector=xmlutil.get_items)
+ limit.set('name', 0)
+ limit.set('value', 1)
+
+ return xmlutil.MasterTemplate(root, 1, nsmap=limits_nsmap)
+
+
+class LimitsXMLSerializer(xmlutil.XMLTemplateSerializer):
+ def index(self):
+ return LimitsTemplate()
def create_resource():
diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py
index 4d55f40f4..83231b119 100644
--- a/nova/api/openstack/servers.py
+++ b/nova/api/openstack/servers.py
@@ -988,129 +988,107 @@ class HeadersSerializer(wsgi.ResponseHeadersSerializer):
response.status_int = 202
-class ServerXMLSerializer(wsgi.XMLDictSerializer):
+class SecurityGroupsTemplateElement(xmlutil.TemplateElement):
+ def will_render(self, datum):
+ return 'security_groups' in datum
- NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
- def __init__(self):
- self.metadata_serializer = common.MetadataXMLSerializer()
- self.addresses_serializer = ips.IPXMLSerializer()
-
- def _create_metadata_node(self, metadata_dict):
- metadata_elem = etree.Element('metadata', nsmap=self.NSMAP)
- self.metadata_serializer.populate_metadata(metadata_elem,
- metadata_dict)
- return metadata_elem
-
- def _create_image_node(self, image_dict):
- image_elem = etree.Element('image', nsmap=self.NSMAP)
- image_elem.set('id', str(image_dict['id']))
- for link in image_dict.get('links', []):
- elem = etree.SubElement(image_elem,
- '{%s}link' % xmlutil.XMLNS_ATOM)
- elem.set('rel', link['rel'])
- elem.set('href', link['href'])
- return image_elem
-
- def _create_flavor_node(self, flavor_dict):
- flavor_elem = etree.Element('flavor', nsmap=self.NSMAP)
- flavor_elem.set('id', str(flavor_dict['id']))
- for link in flavor_dict.get('links', []):
- elem = etree.SubElement(flavor_elem,
- '{%s}link' % xmlutil.XMLNS_ATOM)
- elem.set('rel', link['rel'])
- elem.set('href', link['href'])
- return flavor_elem
-
- def _create_addresses_node(self, addresses_dict):
- addresses_elem = etree.Element('addresses', nsmap=self.NSMAP)
- self.addresses_serializer.populate_addresses_node(addresses_elem,
- addresses_dict)
- return addresses_elem
-
- def _populate_server(self, server_elem, server_dict, detailed=False):
- """Populate a server xml element from a dict."""
-
- server_elem.set('name', server_dict['name'])
- server_elem.set('id', str(server_dict['id']))
- if detailed:
- server_elem.set('uuid', str(server_dict['uuid']))
- server_elem.set('userId', str(server_dict['user_id']))
- server_elem.set('tenantId', str(server_dict['tenant_id']))
- server_elem.set('updated', str(server_dict['updated']))
- server_elem.set('created', str(server_dict['created']))
- server_elem.set('hostId', str(server_dict['hostId']))
- server_elem.set('accessIPv4', str(server_dict['accessIPv4']))
- server_elem.set('accessIPv6', str(server_dict['accessIPv6']))
- server_elem.set('status', str(server_dict['status']))
- if 'progress' in server_dict:
- server_elem.set('progress', str(server_dict['progress']))
- image_elem = self._create_image_node(server_dict['image'])
- server_elem.append(image_elem)
-
- flavor_elem = self._create_flavor_node(server_dict['flavor'])
- server_elem.append(flavor_elem)
-
- meta_elem = self._create_metadata_node(
- server_dict.get('metadata', {}))
- server_elem.append(meta_elem)
-
- addresses_elem = self._create_addresses_node(
- server_dict.get('addresses', {}))
- server_elem.append(addresses_elem)
- groups = server_dict.get('security_groups')
- if groups:
- groups_elem = etree.SubElement(server_elem, 'security_groups')
- for group in groups:
- group_elem = etree.SubElement(groups_elem,
- 'security_group')
- group_elem.set('name', group['name'])
-
- self._populate_links(server_elem, server_dict.get('links', []))
-
- def _populate_links(self, parent, links):
- for link in links:
- elem = etree.SubElement(parent,
- '{%s}link' % xmlutil.XMLNS_ATOM)
- elem.set('rel', link['rel'])
- elem.set('href', link['href'])
-
- def index(self, servers_dict):
- servers = etree.Element('servers', nsmap=self.NSMAP)
- for server_dict in servers_dict['servers']:
- server = etree.SubElement(servers, 'server')
- self._populate_server(server, server_dict, False)
-
- self._populate_links(servers, servers_dict.get('servers_links', []))
- return self._to_xml(servers)
-
- def detail(self, servers_dict):
- servers = etree.Element('servers', nsmap=self.NSMAP)
- for server_dict in servers_dict['servers']:
- server = etree.SubElement(servers, 'server')
- self._populate_server(server, server_dict, True)
- return self._to_xml(servers)
-
- def show(self, server_dict):
- server = etree.Element('server', nsmap=self.NSMAP)
- self._populate_server(server, server_dict['server'], True)
- return self._to_xml(server)
-
- def create(self, server_dict):
- server = etree.Element('server', nsmap=self.NSMAP)
- self._populate_server(server, server_dict['server'], True)
- server.set('adminPass', server_dict['server']['adminPass'])
- return self._to_xml(server)
-
- def action(self, server_dict):
- #NOTE(bcwaldon): We need a way to serialize actions individually. This
- # assumes all actions return a server entity
- return self.create(server_dict)
-
- def update(self, server_dict):
- server = etree.Element('server', nsmap=self.NSMAP)
- self._populate_server(server, server_dict['server'], True)
- return self._to_xml(server)
+def make_server(elem, detailed=False):
+ elem.set('name')
+ elem.set('id')
+
+ if detailed:
+ elem.set('uuid')
+ elem.set('userId', 'user_id')
+ elem.set('tenantId', 'tenant_id')
+ elem.set('updated')
+ elem.set('created')
+ elem.set('hostId')
+ elem.set('accessIPv4')
+ elem.set('accessIPv6')
+ elem.set('status')
+ elem.set('progress')
+
+ # Attach image node
+ image = xmlutil.SubTemplateElement(elem, 'image', selector='image')
+ image.set('id')
+ xmlutil.make_links(image, 'links')
+
+ # Attach flavor node
+ flavor = xmlutil.SubTemplateElement(elem, 'flavor', selector='flavor')
+ flavor.set('id')
+ xmlutil.make_links(flavor, 'links')
+
+ # Attach metadata node
+ elem.append(common.MetadataTemplate())
+
+ # Attach addresses node
+ elem.append(ips.AddressesTemplate())
+
+ # Attach security groups node
+ secgrps = SecurityGroupsTemplateElement('security_groups')
+ elem.append(secgrps)
+ secgrp = xmlutil.SubTemplateElement(secgrps, 'security_group',
+ selector='security_groups')
+ secgrp.set('name')
+
+ xmlutil.make_links(elem, 'links')
+
+
+server_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM}
+
+
+class ServerTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('server', selector='server')
+ make_server(root, detailed=True)
+ return xmlutil.MasterTemplate(root, 1, nsmap=server_nsmap)
+
+
+class MinimalServersTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('servers')
+ elem = xmlutil.SubTemplateElement(root, 'server', selector='servers')
+ make_server(elem)
+ xmlutil.make_links(root, 'servers_links')
+ return xmlutil.MasterTemplate(root, 1, nsmap=server_nsmap)
+
+
+class ServersTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('servers')
+ elem = xmlutil.SubTemplateElement(root, 'server', selector='servers')
+ make_server(elem, detailed=True)
+ return xmlutil.MasterTemplate(root, 1, nsmap=server_nsmap)
+
+
+class ServerAdminPassTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('server')
+ root.set('adminPass')
+ return xmlutil.SlaveTemplate(root, 1, nsmap=server_nsmap)
+
+
+class ServerXMLSerializer(xmlutil.XMLTemplateSerializer):
+ def index(self):
+ return MinimalServersTemplate()
+
+ def detail(self):
+ return ServersTemplate()
+
+ def show(self):
+ return ServerTemplate()
+
+ def update(self):
+ return ServerTemplate()
+
+ def create(self):
+ master = ServerTemplate()
+ master.attach(ServerAdminPassTemplate())
+ return master
+
+ def action(self):
+ return self.create()
class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer):
diff --git a/nova/api/openstack/users.py b/nova/api/openstack/users.py
index 8dd72d559..9fac45763 100644
--- a/nova/api/openstack/users.py
+++ b/nova/api/openstack/users.py
@@ -20,6 +20,7 @@ from nova import flags
from nova import log as logging
from nova.api.openstack import common
from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
from nova.auth import manager
@@ -97,15 +98,40 @@ class Controller(object):
return dict(user=_translate_keys(self.manager.get_user(id)))
-def create_resource():
- metadata = {
- "attributes": {
- "user": ["id", "name", "access", "secret", "admin"],
- },
- }
+def make_user(elem):
+ elem.set('id')
+ elem.set('name')
+ elem.set('access')
+ elem.set('secret')
+ elem.set('admin')
+
+
+class UserTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('user', selector='user')
+ make_user(root)
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class UsersTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('users')
+ elem = xmlutil.SubTemplateElement(root, 'user', selector='users')
+ make_user(elem)
+ return xmlutil.MasterTemplate(root, 1)
+
+class UserXMLSerializer(xmlutil.XMLTemplateSerializer):
+ def index(self):
+ return UsersTemplate()
+
+ def default(self):
+ return UserTemplate()
+
+
+def create_resource():
body_serializers = {
- 'application/xml': wsgi.XMLDictSerializer(metadata=metadata),
+ 'application/xml': UserXMLSerializer(),
}
serializer = wsgi.ResponseSerializer(body_serializers)
diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py
index aa420953b..a3ca5cd5b 100644
--- a/nova/api/openstack/wsgi.py
+++ b/nova/api/openstack/wsgi.py
@@ -449,7 +449,8 @@ class ResponseSerializer(object):
self.headers_serializer = headers_serializer or \
ResponseHeadersSerializer()
- def serialize(self, response_data, content_type, action='default'):
+ def serialize(self, request, response_data, content_type,
+ action='default'):
"""Serialize a dict into a string and wrap in a wsgi.Request object.
:param response_data: dict produced by the Controller
@@ -458,17 +459,28 @@ class ResponseSerializer(object):
"""
response = webob.Response()
self.serialize_headers(response, response_data, action)
- self.serialize_body(response, response_data, content_type, action)
+ self.serialize_body(request, response, response_data, content_type,
+ action)
return response
def serialize_headers(self, response, data, action):
self.headers_serializer.serialize(response, data, action)
- def serialize_body(self, response, data, content_type, action):
+ def serialize_body(self, request, response, data, content_type, action):
response.headers['Content-Type'] = content_type
if data is not None:
serializer = self.get_body_serializer(content_type)
- response.body = serializer.serialize(data, action)
+ lazy_serialize = request.environ.get('nova.lazy_serialize', False)
+ if lazy_serialize:
+ response.body = utils.dumps(data)
+ request.environ['nova.serializer'] = serializer
+ request.environ['nova.action'] = action
+ if (hasattr(serializer, 'get_template') and
+ 'nova.template' not in request.environ):
+ template = serializer.get_template(action)
+ request.environ['nova.template'] = template
+ else:
+ response.body = serializer.serialize(data, action)
def get_body_serializer(self, content_type):
try:
@@ -478,6 +490,32 @@ class ResponseSerializer(object):
raise exception.InvalidContentType(content_type=content_type)
+class LazySerializationMiddleware(wsgi.Middleware):
+ """Lazy serialization middleware."""
+ @webob.dec.wsgify(RequestClass=Request)
+ def __call__(self, req):
+ # Request lazy serialization
+ req.environ['nova.lazy_serialize'] = True
+
+ response = req.get_response(self.application)
+
+ # See if there's a serializer...
+ serializer = req.environ.get('nova.serializer')
+ if serializer is None:
+ return response
+
+ # OK, build up the arguments for the serialize() method
+ kwargs = dict(action=req.environ['nova.action'])
+ if 'nova.template' in req.environ:
+ kwargs['template'] = req.environ['nova.template']
+
+ # Re-serialize the body
+ response.body = serializer.serialize(utils.loads(response.body),
+ **kwargs)
+
+ return response
+
+
class Resource(wsgi.Application):
"""WSGI app that handles (de)serialization and controller dispatch.
@@ -531,7 +569,8 @@ class Resource(wsgi.Application):
action_result = faults.Fault(ex)
if type(action_result) is dict or action_result is None:
- response = self.serializer.serialize(action_result,
+ response = self.serializer.serialize(request,
+ action_result,
accept,
action=action)
else:
diff --git a/nova/api/openstack/xmlutil.py b/nova/api/openstack/xmlutil.py
index d5eb88a57..5779849b3 100644
--- a/nova/api/openstack/xmlutil.py
+++ b/nova/api/openstack/xmlutil.py
@@ -20,6 +20,7 @@ import os.path
from lxml import etree
from nova import utils
+from nova.api.openstack import wsgi
XMLNS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0'
@@ -38,3 +39,857 @@ def validate_schema(xml, schema_name):
schema_doc = etree.parse(schema_path)
relaxng = etree.RelaxNG(schema_doc)
relaxng.assertValid(xml)
+
+
+class Selector(object):
+ """Selects datum to operate on from an object."""
+
+ def __init__(self, *chain):
+ """Initialize the selector.
+
+ Each argument is a subsequent index into the object.
+ """
+
+ self.chain = chain
+
+ def __repr__(self):
+ """Return a representation of the selector."""
+
+ return "Selector" + repr(self.chain)
+
+ def __call__(self, obj, do_raise=False):
+ """Select a datum to operate on.
+
+ Selects the relevant datum within the object.
+
+ :param obj: The object from which to select the object.
+ :param do_raise: If False (the default), return None if the
+ indexed datum does not exist. Otherwise,
+ raise a KeyError.
+ """
+
+ # Walk the selector list
+ for elem in self.chain:
+ # If it's callable, call it
+ if callable(elem):
+ obj = elem(obj)
+ else:
+ # Use indexing
+ try:
+ obj = obj[elem]
+ except (KeyError, IndexError):
+ # No sense going any further
+ if do_raise:
+ # Convert to a KeyError, for consistency
+ raise KeyError(elem)
+ return None
+
+ # Return the finally-selected object
+ return obj
+
+
+def get_items(obj):
+ """Get items in obj."""
+
+ return list(obj.items())
+
+
+class EmptyStringSelector(Selector):
+ """Returns the empty string if Selector would return None."""
+ def __call__(self, obj, do_raise=False):
+ """Returns empty string if the selected value does not exist."""
+
+ try:
+ return super(EmptyStringSelector, self).__call__(obj, True)
+ except KeyError:
+ return ""
+
+
+class ConstantSelector(object):
+ """Returns a constant."""
+
+ def __init__(self, value):
+ """Initialize the selector.
+
+ :param value: The value to return.
+ """
+
+ self.value = value
+
+ def __repr__(self):
+ """Return a representation of the selector."""
+
+ return repr(self.value)
+
+ def __call__(self, _obj, _do_raise=False):
+ """Select a datum to operate on.
+
+ Returns a constant value. Compatible with
+ Selector.__call__().
+ """
+
+ return self.value
+
+
+class TemplateElement(object):
+ """Represent an element in the template."""
+
+ def __init__(self, tag, attrib=None, selector=None, **extra):
+ """Initialize an element.
+
+ Initializes an element in the template. Keyword arguments
+ specify attributes to be set on the element; values must be
+ callables. See TemplateElement.set() for more information.
+
+ :param tag: The name of the tag to create.
+ :param attrib: An optional dictionary of element attributes.
+ :param selector: An optional callable taking an object and
+ optional boolean do_raise indicator and
+ returning the object bound to the element.
+ """
+
+ # Convert selector into a Selector
+ if selector is None:
+ selector = Selector()
+ elif not callable(selector):
+ selector = Selector(selector)
+
+ self.tag = tag
+ self.selector = selector
+ self.attrib = {}
+ self._text = None
+ self._children = []
+ self._childmap = {}
+
+ # Run the incoming attributes through set() so that they
+ # become selectorized
+ if not attrib:
+ attrib = {}
+ attrib.update(extra)
+ for k, v in attrib.items():
+ self.set(k, v)
+
+ def __repr__(self):
+ """Return a representation of the template element."""
+
+ return ('<%s.%s %r at %#x>' %
+ (self.__class__.__module__, self.__class__.__name__,
+ self.tag, id(self)))
+
+ def __len__(self):
+ """Return the number of child elements."""
+
+ return len(self._children)
+
+ def __contains__(self, key):
+ """Determine whether a child node named by key exists."""
+
+ return key in self._childmap
+
+ def __getitem__(self, idx):
+ """Retrieve a child node by index or name."""
+
+ if isinstance(idx, basestring):
+ # Allow access by node name
+ return self._childmap[idx]
+ else:
+ return self._children[idx]
+
+ def append(self, elem):
+ """Append a child to the element."""
+
+ # Unwrap templates...
+ elem = elem.unwrap()
+
+ # Avoid duplications
+ if elem.tag in self._childmap:
+ raise KeyError(elem.tag)
+
+ self._children.append(elem)
+ self._childmap[elem.tag] = elem
+
+ def extend(self, elems):
+ """Append children to the element."""
+
+ # Pre-evaluate the elements
+ elemmap = {}
+ elemlist = []
+ for elem in elems:
+ # Unwrap templates...
+ elem = elem.unwrap()
+
+ # Avoid duplications
+ if elem.tag in self._childmap or elem.tag in elemmap:
+ raise KeyError(elem.tag)
+
+ elemmap[elem.tag] = elem
+ elemlist.append(elem)
+
+ # Update the children
+ self._children.extend(elemlist)
+ self._childmap.update(elemmap)
+
+ def insert(self, idx, elem):
+ """Insert a child element at the given index."""
+
+ # Unwrap templates...
+ elem = elem.unwrap()
+
+ # Avoid duplications
+ if elem.tag in self._childmap:
+ raise KeyError(elem.tag)
+
+ self._children.insert(idx, elem)
+ self._childmap[elem.tag] = elem
+
+ def remove(self, elem):
+ """Remove a child element."""
+
+ # Unwrap templates...
+ elem = elem.unwrap()
+
+ # Check if element exists
+ if elem.tag not in self._childmap or self._childmap[elem.tag] != elem:
+ raise ValueError(_('element is not a child'))
+
+ self._children.remove(elem)
+ del self._childmap[elem.tag]
+
+ def get(self, key):
+ """Get an attribute.
+
+ Returns a callable which performs datum selection.
+
+ :param key: The name of the attribute to get.
+ """
+
+ return self.attrib[key]
+
+ def set(self, key, value=None):
+ """Set an attribute.
+
+ :param key: The name of the attribute to set.
+
+ :param value: A callable taking an object and optional boolean
+ do_raise indicator and returning the datum bound
+ to the attribute. If None, a Selector() will be
+ constructed from the key. If a string, a
+ Selector() will be constructed from the string.
+ """
+
+ # Convert value to a selector
+ if value is None:
+ value = Selector(key)
+ elif not callable(value):
+ value = Selector(value)
+
+ self.attrib[key] = value
+
+ def keys(self):
+ """Return the attribute names."""
+
+ return self.attrib.keys()
+
+ def items(self):
+ """Return the attribute names and values."""
+
+ return self.attrib.items()
+
+ def unwrap(self):
+ """Unwraps a template to return a template element."""
+
+ # We are a template element
+ return self
+
+ def wrap(self):
+ """Wraps a template element to return a template."""
+
+ # Wrap in a basic Template
+ return Template(self)
+
+ def apply(self, elem, obj):
+ """Apply text and attributes to an etree.Element.
+
+ Applies the text and attribute instructions in the template
+ element to an etree.Element instance.
+
+ :param elem: An etree.Element instance.
+ :param obj: The base object associated with this template
+ element.
+ """
+
+ # Start with the text...
+ if self.text is not None:
+ elem.text = unicode(self.text(obj))
+
+ # Now set up all the attributes...
+ for key, value in self.attrib.items():
+ try:
+ elem.set(key, unicode(value(obj, True)))
+ except KeyError:
+ # Attribute has no value, so don't include it
+ pass
+
+ def _render(self, parent, datum, patches, nsmap):
+ """Internal rendering.
+
+ Renders the template node into an etree.Element object.
+ Returns the etree.Element object.
+
+ :param parent: The parent etree.Element instance.
+ :param datum: The datum associated with this template element.
+ :param patches: A list of other template elements that must
+ also be applied.
+ :param nsmap: An optional namespace dictionary to be
+ associated with the etree.Element instance.
+ """
+
+ # Allocate a node
+ if callable(self.tag):
+ tagname = self.tag(datum)
+ else:
+ tagname = self.tag
+ elem = etree.Element(tagname, nsmap=nsmap)
+
+ # If we have a parent, append the node to the parent
+ if parent is not None:
+ parent.append(elem)
+
+ # If the datum is None, do nothing else
+ if datum is None:
+ return elem
+
+ # Apply this template element to the element
+ self.apply(elem, datum)
+
+ # Additionally, apply the patches
+ for patch in patches:
+ patch.apply(elem, datum)
+
+ # We have fully rendered the element; return it
+ return elem
+
+ def render(self, parent, obj, patches=[], nsmap=None):
+ """Render an object.
+
+ Renders an object against this template node. Returns a list
+ of two-item tuples, where the first item is an etree.Element
+ instance and the second item is the datum associated with that
+ instance.
+
+ :param parent: The parent for the etree.Element instances.
+ :param obj: The object to render this template element
+ against.
+ :param patches: A list of other template elements to apply
+ when rendering this template element.
+ :param nsmap: An optional namespace dictionary to attach to
+ the etree.Element instances.
+ """
+
+ # First, get the datum we're rendering
+ data = None if obj is None else self.selector(obj)
+
+ # Check if we should render at all
+ if not self.will_render(data):
+ return []
+ elif data is None:
+ return [(self._render(parent, None, patches, nsmap), None)]
+
+ # Make the data into a list if it isn't already
+ if not isinstance(data, list):
+ data = [data]
+ elif parent is None:
+ raise ValueError(_('root element selecting a list'))
+
+ # Render all the elements
+ elems = []
+ for datum in data:
+ elems.append((self._render(parent, datum, patches, nsmap), datum))
+
+ # Return all the elements rendered, as well as the
+ # corresponding datum for the next step down the tree
+ return elems
+
+ def will_render(self, datum):
+ """Hook method.
+
+ An overridable hook method to determine whether this template
+ element will be rendered at all. By default, returns False
+ (inhibiting rendering) if the datum is None.
+
+ :param datum: The datum associated with this template element.
+ """
+
+ # Don't render if datum is None
+ return datum is not None
+
+ def _text_get(self):
+ """Template element text.
+
+ Either None or a callable taking an object and optional
+ boolean do_raise indicator and returning the datum bound to
+ the text of the template element.
+ """
+
+ return self._text
+
+ def _text_set(self, value):
+ # Convert value to a selector
+ if value is not None and not callable(value):
+ value = Selector(value)
+
+ self._text = value
+
+ def _text_del(self):
+ self._text = None
+
+ text = property(_text_get, _text_set, _text_del)
+
+ def tree(self):
+ """Return string representation of the template tree.
+
+ Returns a representation of the template rooted at this
+ element as a string, suitable for inclusion in debug logs.
+ """
+
+ # Build the inner contents of the tag...
+ contents = [self.tag, '!selector=%r' % self.selector]
+
+ # Add the text...
+ if self.text is not None:
+ contents.append('!text=%r' % self.text)
+
+ # Add all the other attributes
+ for key, value in self.attrib.items():
+ contents.append('%s=%r' % (key, value))
+
+ # If there are no children, return it as a closed tag
+ if len(self) == 0:
+ return '<%s/>' % ' '.join(contents)
+
+ # OK, recurse to our children
+ children = [c.tree() for c in self]
+
+ # Return the result
+ return ('<%s>%s</%s>' %
+ (' '.join(contents), ''.join(children), self.tag))
+
+
+def SubTemplateElement(parent, tag, attrib=None, selector=None, **extra):
+ """Create a template element as a child of another.
+
+ Corresponds to the etree.SubElement interface. Parameters are as
+ for TemplateElement, with the addition of the parent.
+ """
+
+ # Convert attributes
+ attrib = attrib or {}
+ attrib.update(extra)
+
+ # Get a TemplateElement
+ elem = TemplateElement(tag, attrib=attrib, selector=selector)
+
+ # Append the parent safely
+ if parent is not None:
+ parent.append(elem)
+
+ return elem
+
+
+class Template(object):
+ """Represent a template."""
+
+ def __init__(self, root, nsmap=None):
+ """Initialize a template.
+
+ :param root: The root element of the template.
+ :param nsmap: An optional namespace dictionary to be
+ associated with the root element of the
+ template.
+ """
+
+ self.root = root.unwrap() if root is not None else None
+ self.nsmap = nsmap or {}
+
+ def _serialize(self, parent, obj, siblings, nsmap=None):
+ """Internal serialization.
+
+ Recursive routine to build a tree of etree.Element instances
+ from an object based on the template. Returns the first
+ etree.Element instance rendered, or None.
+
+ :param parent: The parent etree.Element instance. Can be
+ None.
+ :param obj: The object to render.
+ :param siblings: The TemplateElement instances against which
+ to render the object.
+ :param nsmap: An optional namespace dictionary to be
+ associated with the etree.Element instance
+ rendered.
+ """
+
+ # First step, render the element
+ elems = siblings[0].render(parent, obj, siblings[1:], nsmap)
+
+ # Now, recurse to all child elements
+ seen = set()
+ for idx, sibling in enumerate(siblings):
+ for child in sibling:
+ # Have we handled this child already?
+ if child.tag in seen:
+ continue
+ seen.add(child.tag)
+
+ # Determine the child's siblings
+ nieces = [child]
+ for sib in siblings[idx + 1:]:
+ if child.tag in sib:
+ nieces.append(sib[child.tag])
+
+ # Now we recurse for every data element
+ for elem, datum in elems:
+ self._serialize(elem, datum, nieces)
+
+ # Return the first element; at the top level, this will be the
+ # root element
+ if elems:
+ return elems[0][0]
+
+ def serialize(self, obj, *args, **kwargs):
+ """Serialize an object.
+
+ Serializes an object against the template. Returns a string
+ with the serialized XML. Positional and keyword arguments are
+ passed to etree.tostring().
+
+ :param obj: The object to serialize.
+ """
+
+ elem = self.make_tree(obj)
+ if elem is None:
+ return ''
+
+ # Serialize it into XML
+ return etree.tostring(elem, *args, **kwargs)
+
+ def make_tree(self, obj):
+ """Create a tree.
+
+ Serializes an object against the template. Returns an Element
+ node with appropriate children.
+
+ :param obj: The object to serialize.
+ """
+
+ # If the template is empty, return the empty string
+ if self.root is None:
+ return None
+
+ # Get the siblings and nsmap of the root element
+ siblings = self._siblings()
+ nsmap = self._nsmap()
+
+ # Form the element tree
+ return self._serialize(None, obj, siblings, nsmap)
+
+ def _siblings(self):
+ """Hook method for computing root siblings.
+
+ An overridable hook method to return the siblings of the root
+ element. By default, this is the root element itself.
+ """
+
+ return [self.root]
+
+ def _nsmap(self):
+ """Hook method for computing the namespace dictionary.
+
+ An overridable hook method to return the namespace dictionary.
+ """
+
+ return self.nsmap.copy()
+
+ def unwrap(self):
+ """Unwraps a template to return a template element."""
+
+ # Return the root element
+ return self.root
+
+ def wrap(self):
+ """Wraps a template element to return a template."""
+
+ # We are a template
+ return self
+
+ def apply(self, master):
+ """Hook method for determining slave applicability.
+
+ An overridable hook method used to determine if this template
+ is applicable as a slave to a given master template.
+
+ :param master: The master template to test.
+ """
+
+ return True
+
+ def tree(self):
+ """Return string representation of the template tree.
+
+ Returns a representation of the template as a string, suitable
+ for inclusion in debug logs.
+ """
+
+ return "%r: %s" % (self, self.root.tree())
+
+
+class MasterTemplate(Template):
+ """Represent a master template.
+
+ Master templates are versioned derivatives of templates that
+ additionally allow slave templates to be attached. Slave
+ templates allow modification of the serialized result without
+ directly changing the master.
+ """
+
+ def __init__(self, root, version, nsmap=None):
+ """Initialize a master template.
+
+ :param root: The root element of the template.
+ :param version: The version number of the template.
+ :param nsmap: An optional namespace dictionary to be
+ associated with the root element of the
+ template.
+ """
+
+ super(MasterTemplate, self).__init__(root, nsmap)
+ self.version = version
+ self.slaves = []
+
+ def __repr__(self):
+ """Return string representation of the template."""
+
+ return ("<%s.%s object version %s at %#x>" %
+ (self.__class__.__module__, self.__class__.__name__,
+ self.version, id(self)))
+
+ def _siblings(self):
+ """Hook method for computing root siblings.
+
+ An overridable hook method to return the siblings of the root
+ element. This is the root element plus the root elements of
+ all the slave templates.
+ """
+
+ return [self.root] + [slave.root for slave in self.slaves]
+
+ def _nsmap(self):
+ """Hook method for computing the namespace dictionary.
+
+ An overridable hook method to return the namespace dictionary.
+ The namespace dictionary is computed by taking the master
+ template's namespace dictionary and updating it from all the
+ slave templates.
+ """
+
+ nsmap = self.nsmap.copy()
+ for slave in self.slaves:
+ nsmap.update(slave._nsmap())
+ return nsmap
+
+ def attach(self, *slaves):
+ """Attach one or more slave templates.
+
+ Attaches one or more slave templates to the master template.
+ Slave templates must have a root element with the same tag as
+ the master template. The slave template's apply() method will
+ be called to determine if the slave should be applied to this
+ master; if it returns False, that slave will be skipped.
+ (This allows filtering of slaves based on the version of the
+ master template.)
+ """
+
+ slave_list = []
+ for slave in slaves:
+ slave = slave.wrap()
+
+ # Make sure we have a tree match
+ if slave.root.tag != self.root.tag:
+ slavetag = slave.root.tag
+ mastertag = self.root.tag
+ msg = _("Template tree mismatch; adding slave %(slavetag)s "
+ "to master %(mastertag)s") % locals()
+ raise ValueError(msg)
+
+ # Make sure slave applies to this template
+ if not slave.apply(self):
+ continue
+
+ slave_list.append(slave)
+
+ # Add the slaves
+ self.slaves.extend(slave_list)
+
+ def copy(self):
+ """Return a copy of this master template."""
+
+ # Return a copy of the MasterTemplate
+ tmp = self.__class__(self.root, self.version, self.nsmap)
+ tmp.slaves = self.slaves[:]
+ return tmp
+
+
+class SlaveTemplate(Template):
+ """Represent a slave template.
+
+ Slave templates are versioned derivatives of templates. Each
+ slave has a minimum version and optional maximum version of the
+ master template to which they can be attached.
+ """
+
+ def __init__(self, root, min_vers, max_vers=None, nsmap=None):
+ """Initialize a slave template.
+
+ :param root: The root element of the template.
+ :param min_vers: The minimum permissible version of the master
+ template for this slave template to apply.
+ :param max_vers: An optional upper bound for the master
+ template version.
+ :param nsmap: An optional namespace dictionary to be
+ associated with the root element of the
+ template.
+ """
+
+ super(SlaveTemplate, self).__init__(root, nsmap)
+ self.min_vers = min_vers
+ self.max_vers = max_vers
+
+ def __repr__(self):
+ """Return string representation of the template."""
+
+ return ("<%s.%s object versions %s-%s at %#x>" %
+ (self.__class__.__module__, self.__class__.__name__,
+ self.min_vers, self.max_vers, id(self)))
+
+ def apply(self, master):
+ """Hook method for determining slave applicability.
+
+ An overridable hook method used to determine if this template
+ is applicable as a slave to a given master template. This
+ version requires the master template to have a version number
+ between min_vers and max_vers.
+
+ :param master: The master template to test.
+ """
+
+ # Does the master meet our minimum version requirement?
+ if master.version < self.min_vers:
+ return False
+
+ # How about our maximum version requirement?
+ if self.max_vers is not None and master.version > self.max_vers:
+ return False
+
+ return True
+
+
+class TemplateBuilder(object):
+ """Template builder.
+
+ This class exists to allow templates to be lazily built without
+ having to build them each time they are needed. It must be
+ subclassed, and the subclass must implement the construct()
+ method, which must return a Template (or subclass) instance. The
+ constructor will always return the template returned by
+ construct(), or, if it has a copy() method, a copy of that
+ template.
+ """
+
+ _tmpl = None
+
+ def __new__(cls, copy=True):
+ """Construct and return a template.
+
+ :param copy: If True (the default), a copy of the template
+ will be constructed and returned, if possible.
+ """
+
+ # Do we need to construct the template?
+ if cls._tmpl is None:
+ tmp = super(TemplateBuilder, cls).__new__(cls)
+
+ # Construct the template
+ cls._tmpl = tmp.construct()
+
+ # If the template has a copy attribute, return the result of
+ # calling it
+ if copy and hasattr(cls._tmpl, 'copy'):
+ return cls._tmpl.copy()
+
+ # Return the template
+ return cls._tmpl
+
+ def construct(self):
+ """Construct a template.
+
+ Called to construct a template instance, which it must return.
+ Only called once.
+ """
+
+ raise NotImplementedError(_("subclasses must implement construct()!"))
+
+
+class XMLTemplateSerializer(wsgi.ActionDispatcher):
+ """Template-based XML serializer.
+
+ Data serializer that uses templates to perform its serialization.
+ """
+
+ def get_template(self, action='default'):
+ """Retrieve the template to use for serialization."""
+
+ return self.dispatch(action=action)
+
+ def serialize(self, data, action='default', template=None):
+ """Serialize data.
+
+ :param data: The data to serialize.
+ :param action: The action, for identifying the template to
+ use. If no template is provided,
+ get_template() will be called with this action
+ to retrieve the template.
+ :param template: The template to use in serialization.
+ """
+
+ # No template provided, look one up
+ if template is None:
+ template = self.get_template(action)
+
+ # Still couldn't find a template; try the base
+ # XMLDictSerializer
+ if template is None:
+ serial = wsgi.XMLDictSerializer()
+ return serial.serialize(data, action=action)
+
+ # Serialize the template
+ return template.serialize(data, encoding='UTF-8',
+ xml_declaration=True)
+
+ def default(self):
+ """Retrieve the default template to use."""
+
+ return None
+
+
+def make_links(parent, selector=None):
+ """
+ Attach an Atom <links> element to the parent.
+ """
+
+ elem = SubTemplateElement(parent, '{%s}link' % XMLNS_ATOM,
+ selector=selector)
+ elem.set('rel')
+ elem.set('type')
+ elem.set('href')
+
+ # Just for completeness...
+ return elem
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index be1644df8..2a95cd0a4 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -27,6 +27,7 @@ from nova.scheduler import api
from nova.api.openstack import common
from nova.api.openstack import servers
+from nova.api.openstack import xmlutil
from nova.api.openstack import wsgi
@@ -143,16 +144,70 @@ class Controller(object):
return cooked
-def create_resource():
- metadata = {
- "attributes": {
- "zone": ["id", "api_url", "name", "capabilities"],
- },
- }
+class CapabilitySelector(object):
+ def __call__(self, obj, do_raise=False):
+ return [(k, v) for k, v in obj.items()
+ if k not in ('id', 'api_url', 'name', 'capabilities')]
+
+
+def make_zone(elem):
+ #elem = xmlutil.SubTemplateElement(parent, 'zone', selector=selector)
+ elem.set('id')
+ elem.set('api_url')
+ elem.set('name')
+ elem.set('capabilities')
+
+ cap = xmlutil.SubTemplateElement(elem, xmlutil.Selector(0),
+ selector=CapabilitySelector())
+ cap.text = 1
+
+
+zone_nsmap = {None: wsgi.XMLNS_V10}
+
+
+class ZoneTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('zone', selector='zone')
+ make_zone(root)
+ return xmlutil.MasterTemplate(root, 1, nsmap=zone_nsmap)
+
+class ZonesTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('zones')
+ elem = xmlutil.SubTemplateElement(root, 'zone', selector='zones')
+ make_zone(elem)
+ return xmlutil.MasterTemplate(root, 1, nsmap=zone_nsmap)
+
+
+class WeightsTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('weights')
+ weight = xmlutil.SubTemplateElement(root, 'weight', selector='weights')
+ blob = xmlutil.SubTemplateElement(weight, 'blob')
+ blob.text = 'blob'
+ inner_weight = xmlutil.SubTemplateElement(weight, 'weight')
+ inner_weight.text = 'weight'
+ return xmlutil.MasterTemplate(root, 1, nsmap=zone_nsmap)
+
+
+class ZonesXMLSerializer(xmlutil.XMLTemplateSerializer):
+ def index(self):
+ return ZonesTemplate()
+
+ def detail(self):
+ return ZonesTemplate()
+
+ def select(self):
+ return WeightsTemplate()
+
+ def default(self):
+ return ZoneTemplate()
+
+
+def create_resource():
body_serializers = {
- 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11,
- metadata=metadata),
+ 'application/xml': ZonesXMLSerializer(),
}
serializer = wsgi.ResponseSerializer(body_serializers)