diff options
| author | Jenkins <jenkins@review.openstack.org> | 2011-10-17 22:17:19 +0000 |
|---|---|---|
| committer | Gerrit Code Review <review@openstack.org> | 2011-10-17 22:17:19 +0000 |
| commit | 821fae95d6aa86ffd14a4e48254da8ee7392c042 (patch) | |
| tree | 35e173df84e1b0fa296c997ca4401ad03fb84cbc /nova/api | |
| parent | ed0c5731b70771e08e1ae75db0a0a0cf6e72c9e9 (diff) | |
| parent | 9a15c0d070db086111cbe5eff4f19dcb419b32bc (diff) | |
| download | nova-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.py | 25 | ||||
| -rw-r--r-- | nova/api/openstack/common.py | 91 | ||||
| -rw-r--r-- | nova/api/openstack/consoles.py | 49 | ||||
| -rw-r--r-- | nova/api/openstack/extensions.py | 14 | ||||
| -rw-r--r-- | nova/api/openstack/flavors.py | 92 | ||||
| -rw-r--r-- | nova/api/openstack/images.py | 133 | ||||
| -rw-r--r-- | nova/api/openstack/ips.py | 65 | ||||
| -rw-r--r-- | nova/api/openstack/limits.py | 78 | ||||
| -rw-r--r-- | nova/api/openstack/servers.py | 220 | ||||
| -rw-r--r-- | nova/api/openstack/users.py | 40 | ||||
| -rw-r--r-- | nova/api/openstack/wsgi.py | 49 | ||||
| -rw-r--r-- | nova/api/openstack/xmlutil.py | 855 | ||||
| -rw-r--r-- | nova/api/openstack/zones.py | 71 |
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) |
