From 5b866f3ad18d497d39a35248c2b0fdb62fcfaa81 Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Mon, 9 Jan 2012 13:13:08 -0600 Subject: Serialization, deserialization, and response code decorators. Sets up decorators for linking serializers, deserializers, and response codes directly to implementation methods, rather than using parallel object methods. In addition, methods are now able to return an instance of a ResponseObject class, binding alternative serializers; this is particularly useful for actions, as it enables the action processing method to return different objects for different actual actions. Change-Id: I8815590f29a935216dc766ce720ded638ebca0d0 --- nova/api/openstack/common.py | 15 + nova/api/openstack/v2/__init__.py | 4 - nova/api/openstack/v2/consoles.py | 84 +- nova/api/openstack/v2/contrib/accounts.py | 37 +- nova/api/openstack/v2/contrib/cloudpipe.py | 45 +- nova/api/openstack/v2/contrib/createserverext.py | 17 +- nova/api/openstack/v2/contrib/flavorextraspecs.py | 26 +- nova/api/openstack/v2/contrib/floating_ip_dns.py | 99 +- nova/api/openstack/v2/contrib/floating_ips.py | 58 +- nova/api/openstack/v2/contrib/hosts.py | 122 +- nova/api/openstack/v2/contrib/keypairs.py | 40 +- nova/api/openstack/v2/contrib/quotas.py | 38 +- nova/api/openstack/v2/contrib/security_groups.py | 317 ++-- .../api/openstack/v2/contrib/server_action_list.py | 17 +- .../api/openstack/v2/contrib/server_diagnostics.py | 15 + .../openstack/v2/contrib/simple_tenant_usage.py | 84 +- nova/api/openstack/v2/contrib/users.py | 65 +- .../api/openstack/v2/contrib/virtual_interfaces.py | 39 +- .../openstack/v2/contrib/virtual_storage_arrays.py | 256 ++- nova/api/openstack/v2/contrib/volumes.py | 245 ++- nova/api/openstack/v2/contrib/volumetypes.py | 111 +- nova/api/openstack/v2/contrib/zones.py | 113 +- nova/api/openstack/v2/extensions.py | 80 +- nova/api/openstack/v2/flavors.py | 94 +- nova/api/openstack/v2/image_metadata.py | 23 +- nova/api/openstack/v2/images.py | 114 +- nova/api/openstack/v2/ips.py | 70 +- nova/api/openstack/v2/limits.py | 46 +- nova/api/openstack/v2/server_metadata.py | 23 +- nova/api/openstack/v2/servers.py | 1708 ++++++++++---------- nova/api/openstack/v2/versions.py | 169 +- nova/api/openstack/wsgi.py | 340 +++- nova/api/openstack/xmlutil.py | 4 + nova/tests/api/openstack/test_wsgi.py | 248 ++- nova/tests/api/openstack/test_xmlutil.py | 3 +- .../api/openstack/v2/contrib/test_cloudpipe.py | 20 +- .../v2/contrib/test_flavors_extra_specs.py | 2 +- .../openstack/v2/contrib/test_floating_ip_dns.py | 10 +- .../api/openstack/v2/contrib/test_floating_ips.py | 6 +- nova/tests/api/openstack/v2/contrib/test_hosts.py | 12 +- .../api/openstack/v2/contrib/test_keypairs.py | 7 +- nova/tests/api/openstack/v2/contrib/test_quotas.py | 2 +- .../openstack/v2/contrib/test_security_groups.py | 20 +- .../v2/contrib/test_server_action_list.py | 42 +- .../v2/contrib/test_server_diagnostics.py | 31 + .../v2/contrib/test_simple_tenant_usage.py | 10 +- .../api/openstack/v2/contrib/test_snapshots.py | 8 +- nova/tests/api/openstack/v2/contrib/test_users.py | 8 +- .../v2/contrib/test_virtual_interfaces.py | 2 +- .../api/openstack/v2/contrib/test_volume_types.py | 12 +- .../v2/contrib/test_volume_types_extra_specs.py | 13 +- .../tests/api/openstack/v2/contrib/test_volumes.py | 17 +- nova/tests/api/openstack/v2/contrib/test_vsa.py | 32 +- nova/tests/api/openstack/v2/contrib/test_zones.py | 14 +- nova/tests/api/openstack/v2/test_consoles.py | 7 +- nova/tests/api/openstack/v2/test_flavors.py | 24 +- nova/tests/api/openstack/v2/test_images.py | 44 +- nova/tests/api/openstack/v2/test_limits.py | 12 +- nova/tests/api/openstack/v2/test_server_actions.py | 13 +- nova/tests/api/openstack/v2/test_servers.py | 174 +- nova/tests/api/openstack/v2/test_versions.py | 14 +- 61 files changed, 2811 insertions(+), 2514 deletions(-) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 5def03f78..e96c42ac9 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -334,6 +334,21 @@ def get_networks_for_instance(context, instance): return networks +class MetadataDeserializer(wsgi.MetadataXMLDeserializer): + def deserialize(self, text): + dom = minidom.parseString(text) + metadata_node = self.find_first_child_named(dom, "metadata") + metadata = self.extract_metadata(metadata_node) + return {'body': {'metadata': metadata}} + + +class MetaItemDeserializer(wsgi.MetadataXMLDeserializer): + def deserialize(self, text): + dom = minidom.parseString(text) + metadata_item = self.extract_metadata(dom) + return {'body': {'meta': metadata_item}} + + class MetadataXMLDeserializer(wsgi.XMLDeserializer): def extract_metadata(self, metadata_node): diff --git a/nova/api/openstack/v2/__init__.py b/nova/api/openstack/v2/__init__.py index 9504e4f7e..c211cd2f9 100644 --- a/nova/api/openstack/v2/__init__.py +++ b/nova/api/openstack/v2/__init__.py @@ -108,13 +108,9 @@ class APIRouter(base_wsgi.Router): super(APIRouter, self).__init__(mapper) def _setup_ext_routes(self, mapper, ext_mgr): - serializer = wsgi.ResponseSerializer( - {'application/xml': wsgi.XMLDictSerializer()}) for resource in ext_mgr.get_resources(): LOG.debug(_('Extended resource: %s'), resource.collection) - if resource.serializer is None: - resource.serializer = serializer kargs = dict( controller=wsgi.Resource( diff --git a/nova/api/openstack/v2/consoles.py b/nova/api/openstack/v2/consoles.py index 1c832f316..e9eee4c75 100644 --- a/nova/api/openstack/v2/consoles.py +++ b/nova/api/openstack/v2/consoles.py @@ -45,12 +45,47 @@ def _translate_detail_keys(cons): return dict(console=info) +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 Controller(object): """The Consoles controller for the Openstack API""" def __init__(self): self.console_api = console.API() + @wsgi.serializers(xml=ConsolesTemplate) def index(self, req, server_id): """Returns a list of consoles for this instance""" consoles = self.console_api.get_consoles( @@ -65,6 +100,7 @@ class Controller(object): req.environ['nova.context'], server_id) + @wsgi.serializers(xml=ConsoleTemplate) def show(self, req, server_id, id): """Shows in-depth information on a specific console""" try: @@ -91,51 +127,5 @@ 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(): - body_serializers = { - 'application/xml': ConsoleXMLSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - return wsgi.Resource(Controller(), serializer=serializer) + return wsgi.Resource(Controller()) diff --git a/nova/api/openstack/v2/contrib/accounts.py b/nova/api/openstack/v2/contrib/accounts.py index 263eda640..9bfff31d7 100644 --- a/nova/api/openstack/v2/contrib/accounts.py +++ b/nova/api/openstack/v2/contrib/accounts.py @@ -28,6 +28,17 @@ FLAGS = flags.FLAGS LOG = logging.getLogger('nova.api.openstack.v2.contrib.accounts') +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) + + def _translate_keys(account): return dict(id=account.id, name=account.name, @@ -49,6 +60,7 @@ class Controller(object): def index(self, req): raise webob.exc.HTTPNotImplemented() + @wsgi.serializers(xml=AccountTemplate) def show(self, req, id): """Return data about the given account id""" account = self.manager.get_project(id) @@ -64,6 +76,7 @@ class Controller(object): because the id comes from an external source""" raise webob.exc.HTTPNotImplemented() + @wsgi.serializers(xml=AccountTemplate) def update(self, req, id, body): """This is really create or update.""" self._check_admin(req.environ['nova.context']) @@ -77,22 +90,6 @@ class Controller(object): return dict(account=_translate_keys(account)) -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() - - class Accounts(extensions.ExtensionDescriptor): """Admin-only access to accounts""" @@ -103,14 +100,8 @@ class Accounts(extensions.ExtensionDescriptor): admin_only = True def get_resources(self): - body_serializers = { - 'application/xml': AccountXMLSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - #TODO(bcwaldon): This should be prefixed with 'os-' res = extensions.ResourceExtension('accounts', - Controller(), - serializer=serializer) + Controller()) return [res] diff --git a/nova/api/openstack/v2/contrib/cloudpipe.py b/nova/api/openstack/v2/contrib/cloudpipe.py index 33c23c1d0..9327e3987 100644 --- a/nova/api/openstack/v2/contrib/cloudpipe.py +++ b/nova/api/openstack/v2/contrib/cloudpipe.py @@ -34,6 +34,20 @@ FLAGS = flags.FLAGS LOG = logging.getLogger("nova.api.openstack.v2.contrib.cloudpipe") +class CloudpipeTemplate(xmlutil.TemplateBuilder): + def construct(self): + return xmlutil.MasterTemplate(xmlutil.make_flat_dict('cloudpipe'), 1) + + +class CloudpipesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('cloudpipes') + elem = xmlutil.make_flat_dict('cloudpipe', selector='cloudpipes', + subselector='cloudpipe') + root.append(elem) + return xmlutil.MasterTemplate(root, 1) + + class CloudpipeController(object): """Handle creating and listing cloudpipe instances.""" @@ -98,6 +112,7 @@ class CloudpipeController(object): rv['state'] = 'pending' return rv + @wsgi.serializers(xml=CloudpipeTemplate) def create(self, req, body): """Create a new cloudpipe instance, if none exists. @@ -120,6 +135,7 @@ class CloudpipeController(object): instance = self._get_cloudpipe_for_project(ctxt, proj) return {'instance_id': instance['uuid']} + @wsgi.serializers(xml=CloudpipesTemplate) def index(self, req): """Show admins the list of running cloudpipe instances.""" context = req.environ['nova.context'] @@ -150,34 +166,7 @@ class Cloudpipe(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - body_serializers = { - 'application/xml': CloudpipeSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) res = extensions.ResourceExtension('os-cloudpipe', - CloudpipeController(), - serializer=serializer) + CloudpipeController()) resources.append(res) return resources - - -class CloudpipeSerializer(xmlutil.XMLTemplateSerializer): - def index(self): - return CloudpipesTemplate() - - def default(self): - return CloudpipeTemplate() - - -class CloudpipeTemplate(xmlutil.TemplateBuilder): - def construct(self): - return xmlutil.MasterTemplate(xmlutil.make_flat_dict('cloudpipe'), 1) - - -class CloudpipesTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('cloudpipes') - elem = xmlutil.make_flat_dict('cloudpipe', selector='cloudpipes', - subselector='cloudpipe') - root.append(elem) - return xmlutil.MasterTemplate(root, 1) diff --git a/nova/api/openstack/v2/contrib/createserverext.py b/nova/api/openstack/v2/contrib/createserverext.py index 89307e711..70fe2796f 100644 --- a/nova/api/openstack/v2/contrib/createserverext.py +++ b/nova/api/openstack/v2/contrib/createserverext.py @@ -51,25 +51,10 @@ class Createserverext(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - - headers_serializer = servers.HeadersSerializer() - body_serializers = { - 'application/xml': servers.ServerXMLSerializer(), - } - - body_deserializers = { - 'application/xml': servers.ServerXMLDeserializer(), - } - - serializer = wsgi.ResponseSerializer(body_serializers, - headers_serializer) - deserializer = wsgi.RequestDeserializer(body_deserializers) controller = Controller() res = extensions.ResourceExtension('os-create-server-ext', - controller=controller, - deserializer=deserializer, - serializer=serializer) + controller=controller) resources.append(res) return resources diff --git a/nova/api/openstack/v2/contrib/flavorextraspecs.py b/nova/api/openstack/v2/contrib/flavorextraspecs.py index b7013a32d..cb5b572fa 100644 --- a/nova/api/openstack/v2/contrib/flavorextraspecs.py +++ b/nova/api/openstack/v2/contrib/flavorextraspecs.py @@ -26,6 +26,11 @@ from nova import db from nova import exception +class ExtraSpecsTemplate(xmlutil.TemplateBuilder): + def construct(self): + return xmlutil.MasterTemplate(xmlutil.make_flat_dict('extra_specs'), 1) + + class FlavorExtraSpecsController(object): """ The flavor extra specs API controller for the Openstack API """ @@ -41,11 +46,13 @@ class FlavorExtraSpecsController(object): expl = _('No Request Body') raise exc.HTTPBadRequest(explanation=expl) + @wsgi.serializers(xml=ExtraSpecsTemplate) def index(self, req, flavor_id): """ Returns the list of extra specs for a givenflavor """ context = req.environ['nova.context'] return self._get_extra_specs(context, flavor_id) + @wsgi.serializers(xml=ExtraSpecsTemplate) def create(self, req, flavor_id, body): self._check_body(body) context = req.environ['nova.context'] @@ -58,6 +65,7 @@ class FlavorExtraSpecsController(object): self._handle_quota_error(error) return body + @wsgi.serializers(xml=ExtraSpecsTemplate) def update(self, req, flavor_id, id, body): self._check_body(body) context = req.environ['nova.context'] @@ -76,6 +84,7 @@ class FlavorExtraSpecsController(object): return body + @wsgi.serializers(xml=ExtraSpecsTemplate) def show(self, req, flavor_id, id): """ Return a single extra spec item """ context = req.environ['nova.context'] @@ -97,16 +106,6 @@ class FlavorExtraSpecsController(object): raise error -class ExtraSpecsTemplate(xmlutil.TemplateBuilder): - def construct(self): - return xmlutil.MasterTemplate(xmlutil.make_flat_dict('extra_specs'), 1) - - -class ExtraSpecsSerializer(xmlutil.XMLTemplateSerializer): - def default(self): - return ExtraSpecsTemplate() - - class Flavorextraspecs(extensions.ExtensionDescriptor): """Instance type (flavor) extra specs""" @@ -119,16 +118,9 @@ class Flavorextraspecs(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - body_serializers = { - 'application/xml': ExtraSpecsSerializer(), - } - - serializer = wsgi.ResponseSerializer(body_serializers) - res = extensions.ResourceExtension( 'os-extra_specs', FlavorExtraSpecsController(), - serializer=serializer, parent=dict(member_name='flavor', collection_name='flavors')) resources.append(res) diff --git a/nova/api/openstack/v2/contrib/floating_ip_dns.py b/nova/api/openstack/v2/contrib/floating_ip_dns.py index cc33241b3..17efd6d84 100644 --- a/nova/api/openstack/v2/contrib/floating_ip_dns.py +++ b/nova/api/openstack/v2/contrib/floating_ip_dns.py @@ -29,6 +29,44 @@ from nova import network LOG = logging.getLogger('nova.api.openstack.v2.contrib.floating_ip_dns') +def make_dns_entry(elem): + elem.set('id') + elem.set('ip') + elem.set('type') + elem.set('zone') + elem.set('name') + + +def make_zone_entry(elem): + elem.set('zone') + + +class FloatingIPDNSTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('dns_entry', + selector='dns_entry') + make_dns_entry(root) + return xmlutil.MasterTemplate(root, 1) + + +class FloatingIPDNSsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('dns_entries') + elem = xmlutil.SubTemplateElement(root, 'dns_entry', + selector='dns_entries') + make_dns_entry(elem) + return xmlutil.MasterTemplate(root, 1) + + +class ZonesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('zones') + elem = xmlutil.SubTemplateElement(root, 'zone', + selector='zones') + make_zone_entry(elem) + return xmlutil.MasterTemplate(root, 1) + + def _translate_dns_entry_view(dns_entry): result = {} result['ip'] = dns_entry.get('ip') @@ -69,6 +107,7 @@ class FloatingIPDNSController(object): self.network_api = network.API() super(FloatingIPDNSController, self).__init__() + @wsgi.serializers(xml=FloatingIPDNSsTemplate) def show(self, req, id): """Return a list of dns entries. If ip is specified, query for names. if name is specified, query for ips. @@ -95,6 +134,7 @@ class FloatingIPDNSController(object): return _translate_dns_entries_view(entrylist) + @wsgi.serializers(xml=ZonesTemplate) def index(self, req): """Return a list of available DNS zones.""" @@ -103,6 +143,7 @@ class FloatingIPDNSController(object): return _translate_zone_entries_view(zones) + @wsgi.serializers(xml=FloatingIPDNSTemplate) def create(self, req, body): """Add dns entry for name and address""" context = req.environ['nova.context'] @@ -142,55 +183,6 @@ class FloatingIPDNSController(object): return webob.Response(status_int=200) -def make_dns_entry(elem): - elem.set('id') - elem.set('ip') - elem.set('type') - elem.set('zone') - elem.set('name') - - -def make_zone_entry(elem): - elem.set('zone') - - -class FloatingIPDNSTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('dns_entry', - selector='dns_entry') - make_dns_entry(root) - return xmlutil.MasterTemplate(root, 1) - - -class FloatingIPDNSsTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('dns_entries') - elem = xmlutil.SubTemplateElement(root, 'dns_entry', - selector='dns_entries') - make_dns_entry(elem) - return xmlutil.MasterTemplate(root, 1) - - -class ZonesTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('zones') - elem = xmlutil.SubTemplateElement(root, 'zone', - selector='zones') - make_zone_entry(elem) - return xmlutil.MasterTemplate(root, 1) - - -class FloatingIPDNSSerializer(xmlutil.XMLTemplateSerializer): - def index(self): - return ZonesTemplate() - - def show(self): - return FloatingIPDNSsTemplate() - - def default(self): - return FloatingIPDNSTemplate() - - class Floating_ip_dns(extensions.ExtensionDescriptor): """Floating IP DNS support""" @@ -206,15 +198,8 @@ class Floating_ip_dns(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - body_serializers = { - 'application/xml': FloatingIPDNSSerializer(), - } - - serializer = wsgi.ResponseSerializer(body_serializers) - res = extensions.ResourceExtension('os-floating-ip-dns', - FloatingIPDNSController(), - serializer=serializer) + FloatingIPDNSController()) resources.append(res) return resources diff --git a/nova/api/openstack/v2/contrib/floating_ips.py b/nova/api/openstack/v2/contrib/floating_ips.py index 4c7688693..39700d382 100644 --- a/nova/api/openstack/v2/contrib/floating_ips.py +++ b/nova/api/openstack/v2/contrib/floating_ips.py @@ -31,6 +31,30 @@ from nova import rpc LOG = logging.getLogger('nova.api.openstack.v2.contrib.floating_ips') +def make_float_ip(elem): + elem.set('id') + elem.set('ip') + elem.set('fixed_ip') + elem.set('instance_id') + + +class FloatingIPTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('floating_ip', + selector='floating_ip') + make_float_ip(root) + return xmlutil.MasterTemplate(root, 1) + + +class FloatingIPsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('floating_ips') + elem = xmlutil.SubTemplateElement(root, 'floating_ip', + selector='floating_ips') + make_float_ip(elem) + return xmlutil.MasterTemplate(root, 1) + + def _translate_floating_ip_view(floating_ip): result = {'id': floating_ip['id'], 'ip': floating_ip['address']} @@ -57,6 +81,7 @@ class FloatingIPController(object): self.network_api = network.API() super(FloatingIPController, self).__init__() + @wsgi.serializers(xml=FloatingIPTemplate) def show(self, req, id): """Return data about the given floating ip.""" context = req.environ['nova.context'] @@ -68,6 +93,7 @@ class FloatingIPController(object): return _translate_floating_ip_view(floating_ip) + @wsgi.serializers(xml=FloatingIPsTemplate) def index(self, req): """Return a list of floating ips allocated to a project.""" context = req.environ['nova.context'] @@ -76,6 +102,7 @@ class FloatingIPController(object): return _translate_floating_ips_view(floating_ips) + @wsgi.serializers(xml=FloatingIPTemplate) def create(self, req, body=None): context = req.environ['nova.context'] @@ -109,30 +136,6 @@ class FloatingIPController(object): return self.network_api.get_floating_ip(context, value)['address'] -def make_float_ip(elem): - elem.set('id') - elem.set('ip') - elem.set('fixed_ip') - elem.set('instance_id') - - -class FloatingIPTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('floating_ip', - selector='floating_ip') - make_float_ip(root) - return xmlutil.MasterTemplate(root, 1) - - -class FloatingIPsTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('floating_ips') - elem = xmlutil.SubTemplateElement(root, 'floating_ip', - selector='floating_ips') - make_float_ip(elem) - return xmlutil.MasterTemplate(root, 1) - - class FloatingIPSerializer(xmlutil.XMLTemplateSerializer): def index(self): return FloatingIPsTemplate() @@ -204,15 +207,8 @@ class Floating_ips(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - body_serializers = { - 'application/xml': FloatingIPSerializer(), - } - - serializer = wsgi.ResponseSerializer(body_serializers) - res = extensions.ResourceExtension('os-floating-ips', FloatingIPController(), - serializer=serializer, member_actions={}) resources.append(res) diff --git a/nova/api/openstack/v2/contrib/hosts.py b/nova/api/openstack/v2/contrib/hosts.py index b1f222027..2bb61696e 100644 --- a/nova/api/openstack/v2/contrib/hosts.py +++ b/nova/api/openstack/v2/contrib/hosts.py @@ -33,6 +33,53 @@ LOG = logging.getLogger("nova.api.openstack.v2.contrib.hosts") FLAGS = flags.FLAGS +class HostIndexTemplate(xmlutil.TemplateBuilder): + def construct(self): + def shimmer(obj, do_raise=False): + # A bare list is passed in; we need to wrap it in a dict + return dict(hosts=obj) + + root = xmlutil.TemplateElement('hosts', selector=shimmer) + elem = xmlutil.SubTemplateElement(root, 'host', selector='hosts') + elem.set('host_name') + elem.set('service') + + return xmlutil.MasterTemplate(root, 1) + + +class HostUpdateTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('host') + root.set('host') + root.set('status') + + return xmlutil.MasterTemplate(root, 1) + + +class HostActionTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('host') + root.set('host') + root.set('power_action') + + return xmlutil.MasterTemplate(root, 1) + + +class HostDeserializer(wsgi.XMLDeserializer): + def default(self, string): + try: + node = minidom.parseString(string) + except expat.ExpatError: + msg = _("cannot understand XML") + raise exception.MalformedRequestBody(reason=msg) + + updates = {} + for child in node.childNodes[0].childNodes: + updates[child.tagName] = self.extract_text(child) + + return dict(body=updates) + + def _list_hosts(req, service=None): """Returns a summary list of hosts, optionally filtering by service type. @@ -63,9 +110,12 @@ class HostController(object): self.compute_api = compute.API() super(HostController, self).__init__() + @wsgi.serializers(xml=HostIndexTemplate) def index(self, req): return {'hosts': _list_hosts(req)} + @wsgi.serializers(xml=HostUpdateTemplate) + @wsgi.deserializers(xml=HostDeserializer) @check_host def update(self, req, id, body): for raw_key, raw_val in body.iteritems(): @@ -106,74 +156,19 @@ class HostController(object): raise webob.exc.HTTPBadRequest(explanation=e.msg) return {"host": host, "power_action": result} + @wsgi.serializers(xml=HostActionTemplate) def startup(self, req, id): return self._host_power_action(req, host=id, action="startup") + @wsgi.serializers(xml=HostActionTemplate) def shutdown(self, req, id): return self._host_power_action(req, host=id, action="shutdown") + @wsgi.serializers(xml=HostActionTemplate) def reboot(self, req, id): return self._host_power_action(req, host=id, action="reboot") -class HostIndexTemplate(xmlutil.TemplateBuilder): - def construct(self): - def shimmer(obj, do_raise=False): - # A bare list is passed in; we need to wrap it in a dict - return dict(hosts=obj) - - root = xmlutil.TemplateElement('hosts', selector=shimmer) - elem = xmlutil.SubTemplateElement(root, 'host', selector='hosts') - elem.set('host_name') - elem.set('service') - - return xmlutil.MasterTemplate(root, 1) - - -class HostUpdateTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('host') - root.set('host') - root.set('status') - - return xmlutil.MasterTemplate(root, 1) - - -class HostActionTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('host') - root.set('host') - root.set('power_action') - - return xmlutil.MasterTemplate(root, 1) - - -class HostSerializer(xmlutil.XMLTemplateSerializer): - def index(self): - return HostIndexTemplate() - - def update(self): - return HostUpdateTemplate() - - def default(self): - return HostActionTemplate() - - -class HostDeserializer(wsgi.XMLDeserializer): - def update(self, string): - try: - node = minidom.parseString(string) - except expat.ExpatError: - msg = _("cannot understand XML") - raise exception.MalformedRequestBody(reason=msg) - - updates = {} - for child in node.childNodes[0].childNodes: - updates[child.tagName] = self.extract_text(child) - - return dict(body=updates) - - class Hosts(extensions.ExtensionDescriptor): """Admin-only host administration""" @@ -184,19 +179,8 @@ class Hosts(extensions.ExtensionDescriptor): admin_only = True def get_resources(self): - body_serializers = { - 'application/xml': HostSerializer(), - } - body_deserializers = { - 'application/xml': HostDeserializer(), - } - - serializer = wsgi.ResponseSerializer(body_serializers) - deserializer = wsgi.RequestDeserializer(body_deserializers) - resources = [extensions.ResourceExtension('os-hosts', HostController(), - serializer=serializer, deserializer=deserializer, collection_actions={'update': 'PUT'}, member_actions={"startup": "GET", "shutdown": "GET", "reboot": "GET"})] diff --git a/nova/api/openstack/v2/contrib/keypairs.py b/nova/api/openstack/v2/contrib/keypairs.py index 46754a891..2dc9b063d 100644 --- a/nova/api/openstack/v2/contrib/keypairs.py +++ b/nova/api/openstack/v2/contrib/keypairs.py @@ -32,6 +32,21 @@ from nova import db from nova import exception +class KeypairTemplate(xmlutil.TemplateBuilder): + def construct(self): + return xmlutil.MasterTemplate(xmlutil.make_flat_dict('keypair'), 1) + + +class KeypairsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('keypairs') + elem = xmlutil.make_flat_dict('keypair', selector='keypairs', + subselector='keypair') + root.append(elem) + + return xmlutil.MasterTemplate(root, 1) + + class KeypairController(object): """ Keypair API controller for the Openstack API """ @@ -47,6 +62,7 @@ class KeypairController(object): 'public_key': public_key, 'fingerprint': fingerprint} + @wsgi.serializers(xml=KeypairTemplate) def create(self, req, body): """ Create or import keypair. @@ -102,6 +118,7 @@ class KeypairController(object): db.key_pair_destroy(context, context.user_id, id) return webob.Response(status_int=202) + @wsgi.serializers(xml=KeypairsTemplate) def index(self, req): """ List of keypairs for a user @@ -119,21 +136,6 @@ class KeypairController(object): return {'keypairs': rval} -class KeypairTemplate(xmlutil.TemplateBuilder): - def construct(self): - return xmlutil.MasterTemplate(xmlutil.make_flat_dict('keypair'), 1) - - -class KeypairsTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('keypairs') - elem = xmlutil.make_flat_dict('keypair', selector='keypairs', - subselector='keypair') - root.append(elem) - - return xmlutil.MasterTemplate(root, 1) - - class KeypairsSerializer(xmlutil.XMLTemplateSerializer): def index(self): return KeypairsTemplate() @@ -153,15 +155,9 @@ class Keypairs(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - body_serializers = { - 'application/xml': KeypairsSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - res = extensions.ResourceExtension( 'os-keypairs', - KeypairController(), - serializer=serializer) + KeypairController()) resources.append(res) return resources diff --git a/nova/api/openstack/v2/contrib/quotas.py b/nova/api/openstack/v2/contrib/quotas.py index 7dfbc750a..191553a02 100644 --- a/nova/api/openstack/v2/contrib/quotas.py +++ b/nova/api/openstack/v2/contrib/quotas.py @@ -30,6 +30,18 @@ quota_resources = ['metadata_items', 'injected_file_content_bytes', 'injected_files', 'cores'] +class QuotaTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('quota_set', selector='quota_set') + root.set('id') + + for resource in quota_resources: + elem = xmlutil.SubTemplateElement(root, resource) + elem.text = resource + + return xmlutil.MasterTemplate(root, 1) + + class QuotaSetsController(object): def _format_quota_set(self, project_id, quota_set): @@ -42,6 +54,7 @@ class QuotaSetsController(object): return dict(quota_set=result) + @wsgi.serializers(xml=QuotaTemplate) def show(self, req, id): context = req.environ['nova.context'] try: @@ -51,6 +64,7 @@ class QuotaSetsController(object): except exception.NotAuthorized: raise webob.exc.HTTPForbidden() + @wsgi.serializers(xml=QuotaTemplate) def update(self, req, id, body): context = req.environ['nova.context'] project_id = id @@ -69,23 +83,6 @@ class QuotaSetsController(object): return self._format_quota_set(id, quota._get_default_quotas()) -class QuotaTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('quota_set', selector='quota_set') - root.set('id') - - for resource in quota_resources: - elem = xmlutil.SubTemplateElement(root, resource) - elem.text = resource - - return xmlutil.MasterTemplate(root, 1) - - -class QuotaSerializer(xmlutil.XMLTemplateSerializer): - def default(self): - return QuotaTemplate() - - class Quotas(extensions.ExtensionDescriptor): """Quotas management support""" @@ -97,15 +94,8 @@ class Quotas(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - body_serializers = { - 'application/xml': QuotaSerializer(), - } - - serializer = wsgi.ResponseSerializer(body_serializers) - res = extensions.ResourceExtension('os-quota-sets', QuotaSetsController(), - serializer=serializer, member_actions={'defaults': 'GET'}) resources.append(res) diff --git a/nova/api/openstack/v2/contrib/security_groups.py b/nova/api/openstack/v2/contrib/security_groups.py index 13d8b44e8..f4abbcd51 100644 --- a/nova/api/openstack/v2/contrib/security_groups.py +++ b/nova/api/openstack/v2/contrib/security_groups.py @@ -37,6 +37,141 @@ LOG = logging.getLogger("nova.api.openstack.v2.contrib.security_groups") FLAGS = flags.FLAGS +def make_rule(elem): + elem.set('id') + elem.set('parent_group_id') + + proto = xmlutil.SubTemplateElement(elem, 'ip_protocol') + proto.text = 'ip_protocol' + + from_port = xmlutil.SubTemplateElement(elem, 'from_port') + from_port.text = 'from_port' + + to_port = xmlutil.SubTemplateElement(elem, 'to_port') + to_port.text = 'to_port' + + group = xmlutil.SubTemplateElement(elem, 'group', selector='group') + name = xmlutil.SubTemplateElement(group, 'name') + name.text = 'name' + tenant_id = xmlutil.SubTemplateElement(group, 'tenant_id') + tenant_id.text = 'tenant_id' + + ip_range = xmlutil.SubTemplateElement(elem, 'ip_range', + selector='ip_range') + cidr = xmlutil.SubTemplateElement(ip_range, 'cidr') + cidr.text = 'cidr' + + +def make_sg(elem): + elem.set('id') + elem.set('tenant_id') + elem.set('name') + + desc = xmlutil.SubTemplateElement(elem, 'description') + desc.text = 'description' + + rules = xmlutil.SubTemplateElement(elem, 'rules') + rule = xmlutil.SubTemplateElement(rules, 'rule', selector='rules') + make_rule(rule) + + +sg_nsmap = {None: wsgi.XMLNS_V11} + + +class SecurityGroupRuleTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('security_group_rule', + selector='security_group_rule') + make_rule(root) + return xmlutil.MasterTemplate(root, 1, nsmap=sg_nsmap) + + +class SecurityGroupTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('security_group', + selector='security_group') + make_sg(root) + return xmlutil.MasterTemplate(root, 1, nsmap=sg_nsmap) + + +class SecurityGroupsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('security_groups') + elem = xmlutil.SubTemplateElement(root, 'security_group', + selector='security_groups') + make_sg(elem) + return xmlutil.MasterTemplate(root, 1, nsmap=sg_nsmap) + + +class SecurityGroupXMLDeserializer(wsgi.MetadataXMLDeserializer): + """ + Deserializer to handle xml-formatted security group requests. + """ + def default(self, string): + """Deserialize an xml-formatted security group create request""" + dom = minidom.parseString(string) + security_group = {} + sg_node = self.find_first_child_named(dom, + 'security_group') + if sg_node is not None: + if sg_node.hasAttribute('name'): + security_group['name'] = sg_node.getAttribute('name') + desc_node = self.find_first_child_named(sg_node, + "description") + if desc_node: + security_group['description'] = self.extract_text(desc_node) + return {'body': {'security_group': security_group}} + + +class SecurityGroupRulesXMLDeserializer(wsgi.MetadataXMLDeserializer): + """ + Deserializer to handle xml-formatted security group requests. + """ + + def default(self, string): + """Deserialize an xml-formatted security group create request""" + dom = minidom.parseString(string) + security_group_rule = self._extract_security_group_rule(dom) + return {'body': {'security_group_rule': security_group_rule}} + + def _extract_security_group_rule(self, node): + """Marshal the security group rule attribute of a parsed request""" + sg_rule = {} + sg_rule_node = self.find_first_child_named(node, + 'security_group_rule') + if sg_rule_node is not None: + ip_protocol_node = self.find_first_child_named(sg_rule_node, + "ip_protocol") + if ip_protocol_node is not None: + sg_rule['ip_protocol'] = self.extract_text(ip_protocol_node) + + from_port_node = self.find_first_child_named(sg_rule_node, + "from_port") + if from_port_node is not None: + sg_rule['from_port'] = self.extract_text(from_port_node) + + to_port_node = self.find_first_child_named(sg_rule_node, "to_port") + if to_port_node is not None: + sg_rule['to_port'] = self.extract_text(to_port_node) + + parent_group_id_node = self.find_first_child_named(sg_rule_node, + "parent_group_id") + if parent_group_id_node is not None: + sg_rule['parent_group_id'] = self.extract_text( + parent_group_id_node) + + group_id_node = self.find_first_child_named(sg_rule_node, + "group_id") + if group_id_node is not None: + sg_rule['group_id'] = self.extract_text(group_id_node) + + cidr_node = self.find_first_child_named(sg_rule_node, "cidr") + if cidr_node is not None: + sg_rule['cidr'] = self.extract_text(cidr_node) + + return sg_rule + + class SecurityGroupController(object): """The Security group API controller for the OpenStack API.""" @@ -84,6 +219,7 @@ class SecurityGroupController(object): raise exc.HTTPNotFound(explanation=unicode(exp)) return security_group + @wsgi.serializers(xml=SecurityGroupTemplate) def show(self, req, id): """Return data about the given security group.""" context = req.environ['nova.context'] @@ -100,6 +236,7 @@ class SecurityGroupController(object): return webob.Response(status_int=202) + @wsgi.serializers(xml=SecurityGroupsTemplate) def index(self, req): """Returns a list of security groups""" context = req.environ['nova.context'] @@ -115,6 +252,8 @@ class SecurityGroupController(object): list(sorted(result, key=lambda k: (k['tenant_id'], k['name'])))} + @wsgi.serializers(xml=SecurityGroupTemplate) + @wsgi.deserializers(xml=SecurityGroupXMLDeserializer) def create(self, req, body): """Creates a new security group.""" context = req.environ['nova.context'] @@ -170,6 +309,8 @@ class SecurityGroupController(object): class SecurityGroupRulesController(SecurityGroupController): + @wsgi.serializers(xml=SecurityGroupRuleTemplate) + @wsgi.deserializers(xml=SecurityGroupRulesXMLDeserializer) def create(self, req, body): context = req.environ['nova.context'] @@ -356,85 +497,6 @@ class SecurityGroupRulesController(SecurityGroupController): return webob.Response(status_int=202) -def make_rule(elem): - elem.set('id') - elem.set('parent_group_id') - - proto = xmlutil.SubTemplateElement(elem, 'ip_protocol') - proto.text = 'ip_protocol' - - from_port = xmlutil.SubTemplateElement(elem, 'from_port') - from_port.text = 'from_port' - - to_port = xmlutil.SubTemplateElement(elem, 'to_port') - to_port.text = 'to_port' - - group = xmlutil.SubTemplateElement(elem, 'group', selector='group') - name = xmlutil.SubTemplateElement(group, 'name') - name.text = 'name' - tenant_id = xmlutil.SubTemplateElement(group, 'tenant_id') - tenant_id.text = 'tenant_id' - - ip_range = xmlutil.SubTemplateElement(elem, 'ip_range', - selector='ip_range') - cidr = xmlutil.SubTemplateElement(ip_range, 'cidr') - cidr.text = 'cidr' - - -def make_sg(elem): - elem.set('id') - elem.set('tenant_id') - elem.set('name') - - desc = xmlutil.SubTemplateElement(elem, 'description') - desc.text = 'description' - - rules = xmlutil.SubTemplateElement(elem, 'rules') - rule = xmlutil.SubTemplateElement(rules, 'rule', selector='rules') - make_rule(rule) - - -sg_nsmap = {None: wsgi.XMLNS_V11} - - -class SecurityGroupRuleTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('security_group_rule', - selector='security_group_rule') - make_rule(root) - return xmlutil.MasterTemplate(root, 1, nsmap=sg_nsmap) - - -class SecurityGroupTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('security_group', - selector='security_group') - make_sg(root) - return xmlutil.MasterTemplate(root, 1, nsmap=sg_nsmap) - - -class SecurityGroupsTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('security_groups') - elem = xmlutil.SubTemplateElement(root, 'security_group', - selector='security_groups') - make_sg(elem) - return xmlutil.MasterTemplate(root, 1, nsmap=sg_nsmap) - - -class SecurityGroupXMLSerializer(xmlutil.XMLTemplateSerializer): - def index(self): - return SecurityGroupsTemplate() - - def default(self): - return SecurityGroupTemplate() - - -class SecurityGroupRulesXMLSerializer(xmlutil.XMLTemplateSerializer): - def default(self): - return SecurityGroupRuleTemplate() - - class Security_groups(extensions.ExtensionDescriptor): """Security group support""" @@ -519,105 +581,12 @@ class Security_groups(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - body_serializers = { - 'application/xml': SecurityGroupXMLSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers, None) - - body_deserializers = { - 'application/xml': SecurityGroupXMLDeserializer(), - } - deserializer = wsgi.RequestDeserializer(body_deserializers) - res = extensions.ResourceExtension('os-security-groups', - controller=SecurityGroupController(), - deserializer=deserializer, - serializer=serializer) + controller=SecurityGroupController()) resources.append(res) - body_serializers = { - 'application/xml': SecurityGroupRulesXMLSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers, None) - - body_deserializers = { - 'application/xml': SecurityGroupRulesXMLDeserializer(), - } - deserializer = wsgi.RequestDeserializer(body_deserializers) - res = extensions.ResourceExtension('os-security-group-rules', - controller=SecurityGroupRulesController(), - deserializer=deserializer, - serializer=serializer) + controller=SecurityGroupRulesController()) resources.append(res) return resources - - -class SecurityGroupXMLDeserializer(wsgi.MetadataXMLDeserializer): - """ - Deserializer to handle xml-formatted security group requests. - """ - def create(self, string): - """Deserialize an xml-formatted security group create request""" - dom = minidom.parseString(string) - security_group = {} - sg_node = self.find_first_child_named(dom, - 'security_group') - if sg_node is not None: - if sg_node.hasAttribute('name'): - security_group['name'] = sg_node.getAttribute('name') - desc_node = self.find_first_child_named(sg_node, - "description") - if desc_node: - security_group['description'] = self.extract_text(desc_node) - return {'body': {'security_group': security_group}} - - -class SecurityGroupRulesXMLDeserializer(wsgi.MetadataXMLDeserializer): - """ - Deserializer to handle xml-formatted security group requests. - """ - - def create(self, string): - """Deserialize an xml-formatted security group create request""" - dom = minidom.parseString(string) - security_group_rule = self._extract_security_group_rule(dom) - return {'body': {'security_group_rule': security_group_rule}} - - def _extract_security_group_rule(self, node): - """Marshal the security group rule attribute of a parsed request""" - sg_rule = {} - sg_rule_node = self.find_first_child_named(node, - 'security_group_rule') - if sg_rule_node is not None: - ip_protocol_node = self.find_first_child_named(sg_rule_node, - "ip_protocol") - if ip_protocol_node is not None: - sg_rule['ip_protocol'] = self.extract_text(ip_protocol_node) - - from_port_node = self.find_first_child_named(sg_rule_node, - "from_port") - if from_port_node is not None: - sg_rule['from_port'] = self.extract_text(from_port_node) - - to_port_node = self.find_first_child_named(sg_rule_node, "to_port") - if to_port_node is not None: - sg_rule['to_port'] = self.extract_text(to_port_node) - - parent_group_id_node = self.find_first_child_named(sg_rule_node, - "parent_group_id") - if parent_group_id_node is not None: - sg_rule['parent_group_id'] = self.extract_text( - parent_group_id_node) - - group_id_node = self.find_first_child_named(sg_rule_node, - "group_id") - if group_id_node is not None: - sg_rule['group_id'] = self.extract_text(group_id_node) - - cidr_node = self.find_first_child_named(sg_rule_node, "cidr") - if cidr_node is not None: - sg_rule['cidr'] = self.extract_text(cidr_node) - - return sg_rule diff --git a/nova/api/openstack/v2/contrib/server_action_list.py b/nova/api/openstack/v2/contrib/server_action_list.py index 6e2995e2b..90573043e 100644 --- a/nova/api/openstack/v2/contrib/server_action_list.py +++ b/nova/api/openstack/v2/contrib/server_action_list.py @@ -16,12 +16,27 @@ import webob.exc from nova.api.openstack.v2 import extensions +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil from nova import compute from nova import exception -class ServerActionListController(object): +sa_nsmap = {None: wsgi.XMLNS_V11} + + +class ServerActionsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('actions') + elem = xmlutil.SubTemplateElement(root, 'action', selector='actions') + elem.set('created_at') + elem.set('action') + elem.set('error') + return xmlutil.MasterTemplate(root, 1, nsmap=sa_nsmap) + +class ServerActionListController(object): + @wsgi.serializers(xml=ServerActionsTemplate) def index(self, req, server_id): context = req.environ["nova.context"] compute_api = compute.API() diff --git a/nova/api/openstack/v2/contrib/server_diagnostics.py b/nova/api/openstack/v2/contrib/server_diagnostics.py index 5f9c16fd4..daeda5081 100644 --- a/nova/api/openstack/v2/contrib/server_diagnostics.py +++ b/nova/api/openstack/v2/contrib/server_diagnostics.py @@ -16,12 +16,27 @@ import webob.exc from nova.api.openstack.v2 import extensions +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil from nova import compute from nova import exception from nova.scheduler import api as scheduler_api +sd_nsmap = {None: wsgi.XMLNS_V11} + + +class ServerDiagnosticsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('diagnostics') + elem = xmlutil.SubTemplateElement(root, xmlutil.Selector(0), + selector=xmlutil.get_items) + elem.text = 1 + return xmlutil.MasterTemplate(root, 1, nsmap=sd_nsmap) + + class ServerDiagnosticsController(object): + @wsgi.serializers(xml=ServerDiagnosticsTemplate) @exception.novaclient_converter @scheduler_api.redirect_handler def index(self, req, server_id): diff --git a/nova/api/openstack/v2/contrib/simple_tenant_usage.py b/nova/api/openstack/v2/contrib/simple_tenant_usage.py index c93fb5550..7b07dfae1 100644 --- a/nova/api/openstack/v2/contrib/simple_tenant_usage.py +++ b/nova/api/openstack/v2/contrib/simple_tenant_usage.py @@ -31,6 +31,39 @@ from nova import flags FLAGS = flags.FLAGS +def make_usage(elem): + for subelem_tag in ('tenant_id', 'total_local_gb_usage', + 'total_vcpus_usage', 'total_memory_mb_usage', + 'total_hours', 'start', 'stop'): + subelem = xmlutil.SubTemplateElement(elem, subelem_tag) + subelem.text = subelem_tag + + server_usages = xmlutil.SubTemplateElement(elem, 'server_usages') + server_usage = xmlutil.SubTemplateElement(server_usages, 'server_usage', + selector='server_usages') + for subelem_tag in ('name', 'hours', 'memory_mb', 'local_gb', 'vcpus', + 'tenant_id', 'flavor', 'started_at', 'ended_at', + 'state', 'uptime'): + subelem = xmlutil.SubTemplateElement(server_usage, subelem_tag) + subelem.text = subelem_tag + + +class SimpleTenantUsageTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('tenant_usage', selector='tenant_usage') + make_usage(root) + return xmlutil.MasterTemplate(root, 1) + + +class SimpleTenantUsagesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('tenant_usages') + elem = xmlutil.SubTemplateElement(root, 'tenant_usage', + selector='tenant_usages') + make_usage(elem) + return xmlutil.MasterTemplate(root, 1) + + class SimpleTenantUsageController(object): def _hours_for(self, instance, period_start, period_stop): launched_at = instance['launched_at'] @@ -174,6 +207,7 @@ class SimpleTenantUsageController(object): detailed = bool(env.get('detailed', False)) return (period_start, period_stop, detailed) + @wsgi.serializers(xml=SimpleTenantUsagesTemplate) def index(self, req): """Retrive tenant_usage for all tenants""" context = req.environ['nova.context'] @@ -188,6 +222,7 @@ class SimpleTenantUsageController(object): detailed=detailed) return {'tenant_usages': usages} + @wsgi.serializers(xml=SimpleTenantUsageTemplate) def show(self, req, id): """Retrive tenant_usage for a specified tenant""" tenant_id = id @@ -210,47 +245,6 @@ class SimpleTenantUsageController(object): return {'tenant_usage': usage} -def make_usage(elem): - for subelem_tag in ('tenant_id', 'total_local_gb_usage', - 'total_vcpus_usage', 'total_memory_mb_usage', - 'total_hours', 'start', 'stop'): - subelem = xmlutil.SubTemplateElement(elem, subelem_tag) - subelem.text = subelem_tag - - server_usages = xmlutil.SubTemplateElement(elem, 'server_usages') - server_usage = xmlutil.SubTemplateElement(server_usages, 'server_usage', - selector='server_usages') - for subelem_tag in ('name', 'hours', 'memory_mb', 'local_gb', 'vcpus', - 'tenant_id', 'flavor', 'started_at', 'ended_at', - 'state', 'uptime'): - subelem = xmlutil.SubTemplateElement(server_usage, subelem_tag) - subelem.text = subelem_tag - - -class SimpleTenantUsageTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('tenant_usage', selector='tenant_usage') - make_usage(root) - return xmlutil.MasterTemplate(root, 1) - - -class SimpleTenantUsagesTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('tenant_usages') - elem = xmlutil.SubTemplateElement(root, 'tenant_usage', - selector='tenant_usages') - make_usage(elem) - return xmlutil.MasterTemplate(root, 1) - - -class SimpleTenantUsageSerializer(xmlutil.XMLTemplateSerializer): - def index(self): - return SimpleTenantUsagesTemplate() - - def show(self): - return SimpleTenantUsageTemplate() - - class Simple_tenant_usage(extensions.ExtensionDescriptor): """Simple tenant usage extension""" @@ -264,14 +258,8 @@ class Simple_tenant_usage(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - body_serializers = { - 'application/xml': SimpleTenantUsageSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - res = extensions.ResourceExtension('os-simple-tenant-usage', - SimpleTenantUsageController(), - serializer=serializer) + SimpleTenantUsageController()) resources.append(res) return resources diff --git a/nova/api/openstack/v2/contrib/users.py b/nova/api/openstack/v2/contrib/users.py index b9bdd75e4..e24c7c068 100644 --- a/nova/api/openstack/v2/contrib/users.py +++ b/nova/api/openstack/v2/contrib/users.py @@ -29,6 +29,29 @@ FLAGS = flags.FLAGS LOG = logging.getLogger('nova.api.openstack.users') +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) + + def _translate_keys(user): return dict(id=user.id, name=user.name, @@ -48,6 +71,7 @@ class Controller(object): if not context.is_admin: raise exception.AdminRequired() + @wsgi.serializers(xml=UsersTemplate) def index(self, req): """Return all users in brief""" users = self.manager.get_users() @@ -55,10 +79,12 @@ class Controller(object): users = [_translate_keys(user) for user in users] return dict(users=users) + @wsgi.serializers(xml=UsersTemplate) def detail(self, req): """Return all users in detail""" return self.index(req) + @wsgi.serializers(xml=UserTemplate) def show(self, req, id): """Return data about the given user id""" @@ -79,6 +105,7 @@ class Controller(object): self.manager.delete_user(id) return {} + @wsgi.serializers(xml=UserTemplate) def create(self, req, body): self._check_admin(req.environ['nova.context']) is_admin = body['user'].get('admin') in ('T', 'True', True) @@ -88,6 +115,7 @@ class Controller(object): user = self.manager.create_user(name, access, secret, is_admin) return dict(user=_translate_keys(user)) + @wsgi.serializers(xml=UserTemplate) def update(self, req, id, body): self._check_admin(req.environ['nova.context']) is_admin = body['user'].get('admin') @@ -99,37 +127,6 @@ class Controller(object): return dict(user=_translate_keys(self.manager.get_user(id))) -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() - - class Users(extensions.ExtensionDescriptor): """Allow admins to acces user information""" @@ -140,15 +137,9 @@ class Users(extensions.ExtensionDescriptor): admin_only = True def get_resources(self): - body_serializers = { - 'application/xml': UserXMLSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - coll_actions = {'detail': 'GET'} res = extensions.ResourceExtension('users', Controller(), - serializer=serializer, collection_actions=coll_actions) return [res] diff --git a/nova/api/openstack/v2/contrib/virtual_interfaces.py b/nova/api/openstack/v2/contrib/virtual_interfaces.py index 34acc242a..401c7133e 100644 --- a/nova/api/openstack/v2/contrib/virtual_interfaces.py +++ b/nova/api/openstack/v2/contrib/virtual_interfaces.py @@ -26,6 +26,19 @@ from nova import network LOG = logging.getLogger("nova.api.openstack.v2.contrib.virtual_interfaces") +vif_nsmap = {None: wsgi.XMLNS_V11} + + +class VirtualInterfaceTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('virtual_interfaces') + elem = xmlutil.SubTemplateElement(root, 'virtual_interface', + selector='virtual_interfaces') + elem.set('id') + elem.set('mac_address') + return xmlutil.MasterTemplate(root, 1, nsmap=vif_nsmap) + + def _translate_vif_summary_view(_context, vif): """Maps keys for VIF summary view.""" d = {} @@ -51,30 +64,13 @@ class ServerVirtualInterfaceController(object): res = [entity_maker(context, vif) for vif in limited_list] return {'virtual_interfaces': res} + @wsgi.serializers(xml=VirtualInterfaceTemplate) def index(self, req, server_id): """Returns the list of VIFs for a given instance.""" return self._items(req, server_id, entity_maker=_translate_vif_summary_view) -vif_nsmap = {None: wsgi.XMLNS_V11} - - -class VirtualInterfaceTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('virtual_interfaces') - elem = xmlutil.SubTemplateElement(root, 'virtual_interface', - selector='virtual_interfaces') - elem.set('id') - elem.set('mac_address') - return xmlutil.MasterTemplate(root, 1, nsmap=vif_nsmap) - - -class VirtualInterfaceSerializer(xmlutil.XMLTemplateSerializer): - def default(self): - return VirtualInterfaceTemplate() - - class Virtual_interfaces(extensions.ExtensionDescriptor): """Virtual interface support""" @@ -87,15 +83,10 @@ class Virtual_interfaces(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - body_serializers = { - 'application/xml': VirtualInterfaceSerializer() - } - serializer = wsgi.ResponseSerializer(body_serializers, None) res = extensions.ResourceExtension( 'os-virtual-interfaces', controller=ServerVirtualInterfaceController(), - parent=dict(member_name='server', collection_name='servers'), - serializer=serializer) + parent=dict(member_name='server', collection_name='servers')) resources.append(res) return resources diff --git a/nova/api/openstack/v2/contrib/virtual_storage_arrays.py b/nova/api/openstack/v2/contrib/virtual_storage_arrays.py index 7812cdcaf..0dee3f1b4 100644 --- a/nova/api/openstack/v2/contrib/virtual_storage_arrays.py +++ b/nova/api/openstack/v2/contrib/virtual_storage_arrays.py @@ -82,6 +82,34 @@ def _vsa_view(context, vsa, details=False, instances=None): return d +def make_vsa(elem): + elem.set('id') + elem.set('name') + elem.set('displayName') + elem.set('displayDescription') + elem.set('createTime') + elem.set('status') + elem.set('vcType') + elem.set('vcCount') + elem.set('driveCount') + elem.set('ipAddress') + + +class VsaTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('vsa', selector='vsa') + make_vsa(root) + return xmlutil.MasterTemplate(root, 1) + + +class VsaSetTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('vsaSet') + elem = xmlutil.SubTemplateElement(root, 'vsa', selector='vsaSet') + make_vsa(elem) + return xmlutil.MasterTemplate(root, 1) + + class VsaController(object): """The Virtual Storage Array API controller for the OpenStack API.""" @@ -107,14 +135,17 @@ class VsaController(object): vsa_list.append(_vsa_view(context, vsa, details, instances)) return {'vsaSet': vsa_list} + @wsgi.serializers(xml=VsaSetTemplate) def index(self, req): """Return a short list of VSAs.""" return self._items(req, details=False) + @wsgi.serializers(xml=VsaSetTemplate) def detail(self, req): """Return a detailed list of VSAs.""" return self._items(req, details=True) + @wsgi.serializers(xml=VsaTemplate) def show(self, req, id): """Return data about the given VSA.""" context = req.environ['nova.context'] @@ -127,6 +158,7 @@ class VsaController(object): instances = self._get_instances_by_vsa_id(context, vsa.get('id')) return {'vsa': _vsa_view(context, vsa, True, instances)} + @wsgi.serializers(xml=VsaTemplate) def create(self, req, body): """Create a new VSA.""" context = req.environ['nova.context'] @@ -215,43 +247,10 @@ class VsaController(object): # Placeholder -def make_vsa(elem): - elem.set('id') +def make_volume(elem): + volumes.make_volume(elem) elem.set('name') - elem.set('displayName') - elem.set('displayDescription') - elem.set('createTime') - elem.set('status') - elem.set('vcType') - elem.set('vcCount') - elem.set('driveCount') - elem.set('ipAddress') - - -class VsaTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('vsa', selector='vsa') - make_vsa(root) - return xmlutil.MasterTemplate(root, 1) - - -class VsaSetTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('vsaSet') - elem = xmlutil.SubTemplateElement(root, 'vsa', selector='vsaSet') - make_vsa(elem) - return xmlutil.MasterTemplate(root, 1) - - -class VsaSerializer(xmlutil.XMLTemplateSerializer): - def default(self): - return VsaTemplate() - - def index(self): - return VsaSetTemplate() - - def detail(self): - return VsaSetTemplate() + elem.set('vsaId') class VsaVolumeDriveController(volumes.VolumeController): @@ -410,27 +409,6 @@ class VsaVolumeDriveController(volumes.VolumeController): return super(VsaVolumeDriveController, self).show(req, id) -def make_volume(elem): - volumes.make_volume(elem) - elem.set('name') - elem.set('vsaId') - - -class VsaVolumeController(VsaVolumeDriveController): - """The VSA volume API controller for the Openstack API. - - A child resource of the VSA object. Allows operations with volumes created - by particular VSA - - """ - - def __init__(self): - self.direction = 'from_vsa_id' - self.objects = 'volumes' - self.object = 'volume' - super(VsaVolumeController, self).__init__() - - class VsaVolumeTemplate(xmlutil.TemplateBuilder): def construct(self): root = xmlutil.TemplateElement('volume', selector='volume') @@ -446,42 +424,39 @@ class VsaVolumesTemplate(xmlutil.TemplateBuilder): return xmlutil.MasterTemplate(root, 1) -class VsaVolumeSerializer(xmlutil.XMLTemplateSerializer): - def default(self): - return VsaVolumeTemplate() - - def index(self): - return VsaVolumesTemplate() - - def detail(self): - return VsaVolumesTemplate() - - -class VsaDriveController(VsaVolumeDriveController): - """The VSA Drive API controller for the Openstack API. +class VsaVolumeController(VsaVolumeDriveController): + """The VSA volume API controller for the Openstack API. - A child resource of the VSA object. Allows operations with drives created - for particular VSA + A child resource of the VSA object. Allows operations with volumes created + by particular VSA """ def __init__(self): - self.direction = 'to_vsa_id' - self.objects = 'drives' - self.object = 'drive' - super(VsaDriveController, self).__init__() + self.direction = 'from_vsa_id' + self.objects = 'volumes' + self.object = 'volume' + super(VsaVolumeController, self).__init__() + + @wsgi.serializers(xml=VsaVolumesTemplate) + def index(self, req, vsa_id): + return super(VsaVolumeController, self).index(req, vsa_id) + + @wsgi.serializers(xml=VsaVolumesTemplate) + def detail(self, req, vsa_id): + return super(VsaVolumeController, self).detail(req, vsa_id) + @wsgi.serializers(xml=VsaVolumeTemplate) def create(self, req, vsa_id, body): - """Create a new drive for VSA. Should be done through VSA APIs""" - raise exc.HTTPBadRequest() + return super(VsaVolumeController, self).create(req, vsa_id, body) + @wsgi.serializers(xml=VsaVolumeTemplate) def update(self, req, vsa_id, id, body): - """Update a drive. Should be done through VSA APIs""" - raise exc.HTTPBadRequest() + return super(VsaVolumeController, self).update(req, vsa_id, id, body) - def delete(self, req, vsa_id, id): - """Delete a volume. Should be done through VSA APIs""" - raise exc.HTTPBadRequest() + @wsgi.serializers(xml=VsaVolumeTemplate) + def show(self, req, vsa_id, id): + return super(VsaVolumeController, self).show(req, vsa_id, id) class VsaDriveTemplate(xmlutil.TemplateBuilder): @@ -499,43 +474,43 @@ class VsaDrivesTemplate(xmlutil.TemplateBuilder): return xmlutil.MasterTemplate(root, 1) -class VsaDriveSerializer(xmlutil.XMLTemplateSerializer): - def default(self): - return VsaDriveTemplate() - - def index(self): - return VsaDrivesTemplate() - - def detail(self): - return VsaDrivesTemplate() +class VsaDriveController(VsaVolumeDriveController): + """The VSA Drive API controller for the Openstack API. + A child resource of the VSA object. Allows operations with drives created + for particular VSA -class VsaVPoolController(object): - """The vPool VSA API controller for the OpenStack API.""" + """ def __init__(self): - self.vsa_api = vsa.API() - super(VsaVPoolController, self).__init__() - - def index(self, req, vsa_id): - """Return a short list of vpools created from particular VSA.""" - return {'vpools': []} + self.direction = 'to_vsa_id' + self.objects = 'drives' + self.object = 'drive' + super(VsaDriveController, self).__init__() def create(self, req, vsa_id, body): - """Create a new vPool for VSA.""" + """Create a new drive for VSA. Should be done through VSA APIs""" raise exc.HTTPBadRequest() def update(self, req, vsa_id, id, body): - """Update vPool parameters.""" + """Update a drive. Should be done through VSA APIs""" raise exc.HTTPBadRequest() def delete(self, req, vsa_id, id): - """Delete a vPool.""" + """Delete a volume. Should be done through VSA APIs""" raise exc.HTTPBadRequest() + @wsgi.serializers(xml=VsaDrivesTemplate) + def index(self, req, vsa_id): + return super(VsaDriveController, self).index(req, vsa_id) + + @wsgi.serializers(xml=VsaDrivesTemplate) + def detail(self, req, vsa_id): + return super(VsaDriveController, self).detail(req, vsa_id) + + @wsgi.serializers(xml=VsaDriveTemplate) def show(self, req, vsa_id, id): - """Return data about the given vPool.""" - raise exc.HTTPBadRequest() + return super(VsaDriveController, self).show(req, vsa_id, id) def make_vpool(elem): @@ -572,12 +547,33 @@ class VsaVPoolsTemplate(xmlutil.TemplateBuilder): return xmlutil.MasterTemplate(root, 1) -class VsaVPoolSerializer(xmlutil.XMLTemplateSerializer): - def default(self): - return VsaVPoolTemplate() +class VsaVPoolController(object): + """The vPool VSA API controller for the OpenStack API.""" - def index(self): - return VsaVPoolsTemplate() + def __init__(self): + self.vsa_api = vsa.API() + super(VsaVPoolController, self).__init__() + + @wsgi.serializers(xml=VsaVPoolsTemplate) + def index(self, req, vsa_id): + """Return a short list of vpools created from particular VSA.""" + return {'vpools': []} + + def create(self, req, vsa_id, body): + """Create a new vPool for VSA.""" + raise exc.HTTPBadRequest() + + def update(self, req, vsa_id, id, body): + """Update vPool parameters.""" + raise exc.HTTPBadRequest() + + def delete(self, req, vsa_id, id): + """Delete a vPool.""" + raise exc.HTTPBadRequest() + + def show(self, req, vsa_id, id): + """Return data about the given vPool.""" + raise exc.HTTPBadRequest() class VsaVCController(servers.Controller): @@ -608,6 +604,7 @@ class VsaVCController(servers.Controller): for inst in limited_list] return dict(servers=servers) + @wsgi.serializers(xml=servers.MinimalServersTemplate) def index(self, req, vsa_id): """Return list of instances for particular VSA.""" @@ -630,6 +627,7 @@ class VsaVCController(servers.Controller): """Delete VSA instance.""" raise exc.HTTPBadRequest() + @wsgi.serializers(xml=servers.ServerTemplate) def show(self, req, vsa_id, id): """Return data about the given instance.""" return super(VsaVCController, self).show(req, id) @@ -646,15 +644,9 @@ class Virtual_storage_arrays(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - body_serializers = { - 'application/xml': VsaSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - res = extensions.ResourceExtension( 'zadr-vsa', VsaController(), - serializer=serializer, collection_actions={'detail': 'GET'}, member_actions={'add_capacity': 'POST', 'remove_capacity': 'POST', @@ -662,63 +654,31 @@ class Virtual_storage_arrays(extensions.ExtensionDescriptor): 'disassociate_address': 'POST'}) resources.append(res) - body_serializers = { - 'application/xml': VsaVolumeSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - res = extensions.ResourceExtension('volumes', VsaVolumeController(), - serializer=serializer, collection_actions={'detail': 'GET'}, parent=dict( member_name='vsa', collection_name='zadr-vsa')) resources.append(res) - body_serializers = { - 'application/xml': VsaDriveSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - res = extensions.ResourceExtension('drives', VsaDriveController(), - serializer=serializer, collection_actions={'detail': 'GET'}, parent=dict( member_name='vsa', collection_name='zadr-vsa')) resources.append(res) - body_serializers = { - 'application/xml': VsaVPoolSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - res = extensions.ResourceExtension('vpools', VsaVPoolController(), - serializer=serializer, parent=dict( member_name='vsa', collection_name='zadr-vsa')) resources.append(res) - headers_serializer = servers.HeadersSerializer() - body_serializers = { - 'application/xml': servers.ServerXMLSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers, - headers_serializer) - - body_deserializers = { - 'application/xml': servers.ServerXMLDeserializer(), - } - deserializer = wsgi.RequestDeserializer(body_deserializers) - res = extensions.ResourceExtension('instances', VsaVCController(), - serializer=serializer, - deserializer=deserializer, parent=dict( member_name='vsa', collection_name='zadr-vsa')) diff --git a/nova/api/openstack/v2/contrib/volumes.py b/nova/api/openstack/v2/contrib/volumes.py index 2e09f3d56..0ca50b288 100644 --- a/nova/api/openstack/v2/contrib/volumes.py +++ b/nova/api/openstack/v2/contrib/volumes.py @@ -84,6 +84,41 @@ def _translate_volume_summary_view(context, vol): return d +def make_volume(elem): + elem.set('id') + elem.set('status') + elem.set('size') + elem.set('availabilityZone') + elem.set('createdAt') + elem.set('displayName') + elem.set('displayDescription') + elem.set('volumeType') + elem.set('snapshotId') + + attachments = xmlutil.SubTemplateElement(elem, 'attachments') + attachment = xmlutil.SubTemplateElement(attachments, 'attachment', + selector='attachments') + make_attachment(attachment) + + metadata = xmlutil.make_flat_dict('metadata') + elem.append(metadata) + + +class VolumeTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('volume', selector='volume') + make_volume(root) + return xmlutil.MasterTemplate(root, 1) + + +class VolumesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('volumes') + elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes') + make_volume(elem) + return xmlutil.MasterTemplate(root, 1) + + class VolumeController(object): """The Volumes API controller for the OpenStack API.""" @@ -91,6 +126,7 @@ class VolumeController(object): self.volume_api = volume.API() super(VolumeController, self).__init__() + @wsgi.serializers(xml=VolumeTemplate) def show(self, req, id): """Return data about the given volume.""" context = req.environ['nova.context'] @@ -114,10 +150,12 @@ class VolumeController(object): raise exc.HTTPNotFound() return webob.Response(status_int=202) + @wsgi.serializers(xml=VolumesTemplate) def index(self, req): """Returns a summary list of volumes.""" return self._items(req, entity_maker=_translate_volume_summary_view) + @wsgi.serializers(xml=VolumesTemplate) def detail(self, req): """Returns a detailed list of volumes.""" return self._items(req, entity_maker=_translate_volume_detail_view) @@ -131,6 +169,7 @@ class VolumeController(object): res = [entity_maker(context, vol) for vol in limited_list] return {'volumes': res} + @wsgi.serializers(xml=VolumeTemplate) def create(self, req, body): """Creates a new volume.""" context = req.environ['nova.context'] @@ -167,52 +206,6 @@ class VolumeController(object): return {'volume': retval} -def make_volume(elem): - elem.set('id') - elem.set('status') - elem.set('size') - elem.set('availabilityZone') - elem.set('createdAt') - elem.set('displayName') - elem.set('displayDescription') - elem.set('volumeType') - elem.set('snapshotId') - - attachments = xmlutil.SubTemplateElement(elem, 'attachments') - attachment = xmlutil.SubTemplateElement(attachments, 'attachment', - selector='attachments') - make_attachment(attachment) - - metadata = xmlutil.make_flat_dict('metadata') - elem.append(metadata) - - -class VolumeTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('volume', selector='volume') - make_volume(root) - return xmlutil.MasterTemplate(root, 1) - - -class VolumesTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('volumes') - elem = xmlutil.SubTemplateElement(root, 'volume', selector='volumes') - make_volume(elem) - return xmlutil.MasterTemplate(root, 1) - - -class VolumeSerializer(xmlutil.XMLTemplateSerializer): - def default(self): - return VolumeTemplate() - - def index(self): - return VolumesTemplate() - - def detail(self): - return VolumesTemplate() - - def _translate_attachment_detail_view(_context, vol): """Maps keys for attachment details view.""" @@ -241,6 +234,30 @@ def _translate_attachment_summary_view(_context, vol): return d +def make_attachment(elem): + elem.set('id') + elem.set('serverId') + elem.set('volumeId') + elem.set('device') + + +class VolumeAttachmentTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('volumeAttachment', + selector='volumeAttachment') + make_attachment(root) + return xmlutil.MasterTemplate(root, 1) + + +class VolumeAttachmentsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('volumeAttachments') + elem = xmlutil.SubTemplateElement(root, 'volumeAttachment', + selector='volumeAttachments') + make_attachment(elem) + return xmlutil.MasterTemplate(root, 1) + + class VolumeAttachmentController(object): """The volume attachment API controller for the Openstack API. @@ -254,11 +271,13 @@ class VolumeAttachmentController(object): self.volume_api = volume.API() super(VolumeAttachmentController, self).__init__() + @wsgi.serializers(xml=VolumeAttachmentsTemplate) def index(self, req, server_id): """Returns the list of volume attachments for a given instance.""" return self._items(req, server_id, entity_maker=_translate_attachment_summary_view) + @wsgi.serializers(xml=VolumeAttachmentTemplate) def show(self, req, server_id, id): """Return data about the given volume attachment.""" context = req.environ['nova.context'] @@ -278,6 +297,7 @@ class VolumeAttachmentController(object): return {'volumeAttachment': _translate_attachment_detail_view(context, vol)} + @wsgi.serializers(xml=VolumeAttachmentTemplate) def create(self, req, server_id, body): """Attach a volume to an instance.""" context = req.environ['nova.context'] @@ -356,38 +376,6 @@ class VolumeAttachmentController(object): return {'volumeAttachments': res} -def make_attachment(elem): - elem.set('id') - elem.set('serverId') - elem.set('volumeId') - elem.set('device') - - -class VolumeAttachmentTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('volumeAttachment', - selector='volumeAttachment') - make_attachment(root) - return xmlutil.MasterTemplate(root, 1) - - -class VolumeAttachmentsTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('volumeAttachments') - elem = xmlutil.SubTemplateElement(root, 'volumeAttachment', - selector='volumeAttachments') - make_attachment(elem) - return xmlutil.MasterTemplate(root, 1) - - -class VolumeAttachmentSerializer(xmlutil.XMLTemplateSerializer): - def default(self): - return VolumeAttachmentTemplate() - - def index(self): - return VolumeAttachmentsTemplate() - - class BootFromVolumeController(servers.Controller): """The boot from volume API controller for the Openstack API.""" @@ -419,6 +407,32 @@ def _translate_snapshot_summary_view(context, vol): return d +def make_snapshot(elem): + elem.set('id') + elem.set('status') + elem.set('size') + elem.set('createdAt') + elem.set('displayName') + elem.set('displayDescription') + elem.set('volumeId') + + +class SnapshotTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('snapshot', selector='snapshot') + make_snapshot(root) + return xmlutil.MasterTemplate(root, 1) + + +class SnapshotsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('snapshots') + elem = xmlutil.SubTemplateElement(root, 'snapshot', + selector='snapshots') + make_snapshot(elem) + return xmlutil.MasterTemplate(root, 1) + + class SnapshotController(object): """The Volumes API controller for the OpenStack API.""" @@ -426,6 +440,7 @@ class SnapshotController(object): self.volume_api = volume.API() super(SnapshotController, self).__init__() + @wsgi.serializers(xml=SnapshotTemplate) def show(self, req, id): """Return data about the given snapshot.""" context = req.environ['nova.context'] @@ -449,10 +464,12 @@ class SnapshotController(object): return exc.HTTPNotFound() return webob.Response(status_int=202) + @wsgi.serializers(xml=SnapshotsTemplate) def index(self, req): """Returns a summary list of snapshots.""" return self._items(req, entity_maker=_translate_snapshot_summary_view) + @wsgi.serializers(xml=SnapshotsTemplate) def detail(self, req): """Returns a detailed list of snapshots.""" return self._items(req, entity_maker=_translate_snapshot_detail_view) @@ -466,6 +483,7 @@ class SnapshotController(object): res = [entity_maker(context, snapshot) for snapshot in limited_list] return {'snapshots': res} + @wsgi.serializers(xml=SnapshotTemplate) def create(self, req, body): """Creates a new snapshot.""" context = req.environ['nova.context'] @@ -495,43 +513,6 @@ class SnapshotController(object): return {'snapshot': retval} -def make_snapshot(elem): - elem.set('id') - elem.set('status') - elem.set('size') - elem.set('createdAt') - elem.set('displayName') - elem.set('displayDescription') - elem.set('volumeId') - - -class SnapshotTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('snapshot', selector='snapshot') - make_snapshot(root) - return xmlutil.MasterTemplate(root, 1) - - -class SnapshotsTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('snapshots') - elem = xmlutil.SubTemplateElement(root, 'snapshot', - selector='snapshots') - make_snapshot(elem) - return xmlutil.MasterTemplate(root, 1) - - -class SnapshotSerializer(xmlutil.XMLTemplateSerializer): - def default(self): - return SnapshotTemplate() - - def index(self): - return SnapshotsTemplate() - - def detail(self): - return SnapshotsTemplate() - - class Volumes(extensions.ExtensionDescriptor): """Volumes support""" @@ -543,58 +524,26 @@ class Volumes(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - body_serializers = { - 'application/xml': VolumeSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - # NOTE(justinsb): No way to provide singular name ('volume') # Does this matter? res = extensions.ResourceExtension('os-volumes', VolumeController(), - serializer=serializer, collection_actions={'detail': 'GET'}) resources.append(res) - body_serializers = { - 'application/xml': VolumeAttachmentSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - res = extensions.ResourceExtension('os-volume_attachments', VolumeAttachmentController(), - serializer=serializer, parent=dict( member_name='server', collection_name='servers')) resources.append(res) - headers_serializer = servers.HeadersSerializer() - body_serializers = { - 'application/xml': servers.ServerXMLSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers, - headers_serializer) - - body_deserializers = { - 'application/xml': servers.ServerXMLDeserializer(), - } - deserializer = wsgi.RequestDeserializer(body_deserializers) - res = extensions.ResourceExtension('os-volumes_boot', - BootFromVolumeController(), - serializer=serializer, - deserializer=deserializer) + BootFromVolumeController()) resources.append(res) - snapshot_serializers = { - 'application/xml': SnapshotSerializer(), - } - snap_serializer = wsgi.ResponseSerializer(snapshot_serializers) - res = extensions.ResourceExtension('os-snapshots', SnapshotController(), - serializer=snap_serializer, collection_actions={'detail': 'GET'}) resources.append(res) diff --git a/nova/api/openstack/v2/contrib/volumetypes.py b/nova/api/openstack/v2/contrib/volumetypes.py index 6906b522f..231c86b1b 100644 --- a/nova/api/openstack/v2/contrib/volumetypes.py +++ b/nova/api/openstack/v2/contrib/volumetypes.py @@ -27,14 +27,39 @@ from nova import exception from nova.volume import volume_types +def make_voltype(elem): + elem.set('id') + elem.set('name') + extra_specs = xmlutil.make_flat_dict('extra_specs', selector='extra_specs') + elem.append(extra_specs) + + +class VolumeTypeTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('volume_type', selector='volume_type') + make_voltype(root) + return xmlutil.MasterTemplate(root, 1) + + +class VolumeTypesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('volume_types') + sel = lambda obj, do_raise=False: obj.values() + elem = xmlutil.SubTemplateElement(root, 'volume_type', selector=sel) + make_voltype(elem) + return xmlutil.MasterTemplate(root, 1) + + class VolumeTypesController(object): """ The volume types API controller for the Openstack API """ + @wsgi.serializers(xml=VolumeTypesTemplate) def index(self, req): """ Returns the list of volume types """ context = req.environ['nova.context'] return volume_types.get_all_types(context) + @wsgi.serializers(xml=VolumeTypeTemplate) def create(self, req, body): """Creates a new volume type.""" context = req.environ['nova.context'] @@ -62,6 +87,7 @@ class VolumeTypesController(object): return {'volume_type': vol_type} + @wsgi.serializers(xml=VolumeTypeTemplate) def show(self, req, id): """ Return a single volume type item """ context = req.environ['nova.context'] @@ -90,35 +116,24 @@ class VolumeTypesController(object): raise error -def make_voltype(elem): - elem.set('id') - elem.set('name') - extra_specs = xmlutil.make_flat_dict('extra_specs', selector='extra_specs') - elem.append(extra_specs) - - -class VolumeTypeTemplate(xmlutil.TemplateBuilder): +class VolumeTypeExtraSpecsTemplate(xmlutil.TemplateBuilder): def construct(self): - root = xmlutil.TemplateElement('volume_type', selector='volume_type') - make_voltype(root) + root = xmlutil.make_flat_dict('extra_specs', selector='extra_specs') return xmlutil.MasterTemplate(root, 1) -class VolumeTypesTemplate(xmlutil.TemplateBuilder): +class VolumeTypeExtraSpecTemplate(xmlutil.TemplateBuilder): def construct(self): - root = xmlutil.TemplateElement('volume_types') - sel = lambda obj, do_raise=False: obj.values() - elem = xmlutil.SubTemplateElement(root, 'volume_type', selector=sel) - make_voltype(elem) - return xmlutil.MasterTemplate(root, 1) - + tagname = xmlutil.Selector('key') -class VolumeTypesSerializer(xmlutil.XMLTemplateSerializer): - def index(self): - return VolumeTypesTemplate() + def extraspec_sel(obj, do_raise=False): + # Have to extract the key and value for later use... + key, value = obj.items()[0] + return dict(key=key, value=value) - def default(self): - return VolumeTypeTemplate() + root = xmlutil.TemplateElement(tagname, selector=extraspec_sel) + root.text = 'value' + return xmlutil.MasterTemplate(root, 1) class VolumeTypeExtraSpecsController(object): @@ -136,11 +151,13 @@ class VolumeTypeExtraSpecsController(object): expl = _('No Request Body') raise exc.HTTPBadRequest(explanation=expl) + @wsgi.serializers(xml=VolumeTypeExtraSpecsTemplate) def index(self, req, vol_type_id): """ Returns the list of extra specs for a given volume type """ context = req.environ['nova.context'] return self._get_extra_specs(context, vol_type_id) + @wsgi.serializers(xml=VolumeTypeExtraSpecsTemplate) def create(self, req, vol_type_id, body): self._check_body(body) context = req.environ['nova.context'] @@ -153,6 +170,7 @@ class VolumeTypeExtraSpecsController(object): self._handle_quota_error(error) return body + @wsgi.serializers(xml=VolumeTypeExtraSpecTemplate) def update(self, req, vol_type_id, id, body): self._check_body(body) context = req.environ['nova.context'] @@ -171,6 +189,7 @@ class VolumeTypeExtraSpecsController(object): return body + @wsgi.serializers(xml=VolumeTypeExtraSpecTemplate) def show(self, req, vol_type_id, id): """ Return a single extra spec item """ context = req.environ['nova.context'] @@ -192,40 +211,6 @@ class VolumeTypeExtraSpecsController(object): raise error -class VolumeTypeExtraSpecsTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.make_flat_dict('extra_specs', selector='extra_specs') - return xmlutil.MasterTemplate(root, 1) - - -class VolumeTypeExtraSpecTemplate(xmlutil.TemplateBuilder): - def construct(self): - tagname = xmlutil.Selector('key') - - def extraspec_sel(obj, do_raise=False): - # Have to extract the key and value for later use... - key, value = obj.items()[0] - return dict(key=key, value=value) - - root = xmlutil.TemplateElement(tagname, selector=extraspec_sel) - root.text = 'value' - return xmlutil.MasterTemplate(root, 1) - - -class VolumeTypeExtraSpecsSerializer(xmlutil.XMLTemplateSerializer): - def index(self): - return VolumeTypeExtraSpecsTemplate() - - def create(self): - return VolumeTypeExtraSpecsTemplate() - - def update(self): - return VolumeTypeExtraSpecTemplate() - - def show(self): - return VolumeTypeExtraSpecTemplate() - - class Volumetypes(extensions.ExtensionDescriptor): """Volume types support""" @@ -237,25 +222,13 @@ class Volumetypes(extensions.ExtensionDescriptor): def get_resources(self): resources = [] - body_serializers = { - 'application/xml': VolumeTypesSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - res = extensions.ResourceExtension( 'os-volume-types', - VolumeTypesController(), - serializer=serializer) + VolumeTypesController()) resources.append(res) - body_serializers = { - 'application/xml': VolumeTypeExtraSpecsSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - res = extensions.ResourceExtension('extra_specs', VolumeTypeExtraSpecsController(), - serializer=serializer, parent=dict( member_name='vol_type', collection_name='os-volume-types')) diff --git a/nova/api/openstack/v2/contrib/zones.py b/nova/api/openstack/v2/contrib/zones.py index 62ce423a7..adbc6580f 100644 --- a/nova/api/openstack/v2/contrib/zones.py +++ b/nova/api/openstack/v2/contrib/zones.py @@ -36,6 +36,52 @@ LOG = logging.getLogger("nova.api.openstack.v2.contrib.zones") FLAGS = flags.FLAGS +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.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) + + def _filter_keys(item, keys): """ Filters all model attributes except for keys @@ -68,6 +114,7 @@ class Controller(object): def __init__(self): self.compute_api = compute.API() + @wsgi.serializers(xml=ZonesTemplate) def index(self, req): """Return all zones in brief""" # Ask the ZoneManager in the Scheduler for most recent data, @@ -77,10 +124,12 @@ class Controller(object): items = [_scrub_zone(item) for item in items] return dict(zones=items) + @wsgi.serializers(xml=ZonesTemplate) def detail(self, req): """Return all zones in detail""" return self.index(req) + @wsgi.serializers(xml=ZoneTemplate) def info(self, req): """Return name and capabilities for this zone.""" context = req.environ['nova.context'] @@ -95,6 +144,7 @@ class Controller(object): zone[item] = "%s,%s" % (min_value, max_value) return dict(zone=zone) + @wsgi.serializers(xml=ZoneTemplate) def show(self, req, id): """Return data about the given zone id""" zone_id = int(id) @@ -108,12 +158,15 @@ class Controller(object): nova.scheduler.api.zone_delete(req.environ['nova.context'], zone_id) return {} + @wsgi.serializers(xml=ZoneTemplate) + @wsgi.deserializers(xml=servers.CreateDeserializer) def create(self, req, body): """Create a child zone entry.""" context = req.environ['nova.context'] zone = nova.scheduler.api.zone_create(context, body["zone"]) return dict(zone=_scrub_zone(zone)) + @wsgi.serializers(xml=ZoneTemplate) def update(self, req, id, body): """Update a child zone entry.""" context = req.environ['nova.context'] @@ -121,6 +174,7 @@ class Controller(object): zone = nova.scheduler.api.zone_update(context, zone_id, body["zone"]) return dict(zone=_scrub_zone(zone)) + @wsgi.serializers(xml=WeightsTemplate) @check_encryption_key def select(self, req, body): """Returns a weighted list of costs to create instances @@ -145,53 +199,6 @@ class Controller(object): return cooked -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() @@ -219,16 +226,6 @@ class Zones(extensions.ExtensionDescriptor): admin_only = True def get_resources(self): - body_serializers = { - 'application/xml': ZonesXMLSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) - - body_deserializers = { - 'application/xml': servers.ServerXMLDeserializer(), - } - deserializer = wsgi.RequestDeserializer(body_deserializers) - #NOTE(bcwaldon): This resource should be prefixed with 'os-' coll_actions = { 'detail': 'GET', @@ -238,7 +235,5 @@ class Zones(extensions.ExtensionDescriptor): res = extensions.ResourceExtension('zones', Controller(), - deserializer=deserializer, - serializer=serializer, collection_actions=coll_actions) return [res] diff --git a/nova/api/openstack/v2/extensions.py b/nova/api/openstack/v2/extensions.py index b6ed897cb..8efd2a138 100644 --- a/nova/api/openstack/v2/extensions.py +++ b/nova/api/openstack/v2/extensions.py @@ -136,7 +136,9 @@ class ActionExtensionResource(wsgi.Resource): def __init__(self, application): controller = ActionExtensionController(application) - wsgi.Resource.__init__(self, controller) + wsgi.Resource.__init__(self, controller, + serializer=wsgi.ResponseSerializer(), + deserializer=wsgi.RequestDeserializer()) def add_action(self, action_name, handler): self.controller.add_action(action_name, handler) @@ -187,7 +189,9 @@ class RequestExtensionResource(wsgi.Resource): def __init__(self, application): controller = RequestExtensionController(application) - wsgi.Resource.__init__(self, controller) + wsgi.Resource.__init__(self, controller, + serializer=wsgi.ResponseSerializer(), + deserializer=wsgi.RequestDeserializer()) def add_handler(self, handler): self.controller.add_handler(handler) @@ -196,10 +200,42 @@ class RequestExtensionResource(wsgi.Resource): self.controller.add_pre_handler(pre_handler) +def make_ext(elem): + elem.set('name') + elem.set('namespace') + elem.set('alias') + elem.set('updated') + + desc = xmlutil.SubTemplateElement(elem, 'description') + desc.text = 'description' + + xmlutil.make_links(elem, 'links') + + +ext_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM} + + +class ExtensionTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('extension', selector='extension') + make_ext(root) + return xmlutil.MasterTemplate(root, 1, nsmap=ext_nsmap) + + +class ExtensionsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('extensions') + elem = xmlutil.SubTemplateElement(root, 'extension', + selector='extensions') + make_ext(elem) + return xmlutil.MasterTemplate(root, 1, nsmap=ext_nsmap) + + class ExtensionsResource(wsgi.Resource): def __init__(self, extension_manager): self.extension_manager = extension_manager + super(ExtensionsResource, self).__init__(None) def _translate(self, ext): ext_data = {} @@ -211,12 +247,14 @@ class ExtensionsResource(wsgi.Resource): ext_data['links'] = [] # TODO(dprince): implement extension links return ext_data + @wsgi.serializers(xml=ExtensionsTemplate) def index(self, req): extensions = [] for _alias, ext in self.extension_manager.extensions.iteritems(): extensions.append(self._translate(ext)) return dict(extensions=extensions) + @wsgi.serializers(xml=ExtensionTemplate) def show(self, req, id): try: # NOTE(dprince): the extensions alias is used as the 'id' for show @@ -374,12 +412,11 @@ class ExtensionManager(object): def get_resources(self): """Returns a list of ResourceExtension objects.""" + resources = [] - serializer = wsgi.ResponseSerializer( - {'application/xml': ExtensionsXMLSerializer()}) resources.append(ResourceExtension('extensions', - ExtensionsResource(self), - serializer=serializer)) + ExtensionsResource(self))) + for ext in self.extensions.values(): try: resources.extend(ext.get_resources()) @@ -508,37 +545,6 @@ class ResourceExtension(object): self.serializer = serializer -def make_ext(elem): - elem.set('name') - elem.set('namespace') - elem.set('alias') - elem.set('updated') - - desc = xmlutil.SubTemplateElement(elem, 'description') - desc.text = 'description' - - xmlutil.make_links(elem, 'links') - - -ext_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM} - - -class ExtensionTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('extension', selector='extension') - make_ext(root) - return xmlutil.MasterTemplate(root, 1, nsmap=ext_nsmap) - - -class ExtensionsTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('extensions') - elem = xmlutil.SubTemplateElement(root, 'extension', - selector='extensions') - make_ext(elem) - return xmlutil.MasterTemplate(root, 1, nsmap=ext_nsmap) - - class ExtensionsXMLSerializer(xmlutil.XMLTemplateSerializer): def index(self): return ExtensionsTemplate() diff --git a/nova/api/openstack/v2/flavors.py b/nova/api/openstack/v2/flavors.py index 0d1d4d996..21c2a8120 100644 --- a/nova/api/openstack/v2/flavors.py +++ b/nova/api/openstack/v2/flavors.py @@ -24,48 +24,6 @@ from nova.compute import instance_types from nova import exception -class Controller(wsgi.Controller): - """Flavor controller for the OpenStack API.""" - - _view_builder_class = flavors_view.ViewBuilder - - def index(self, req): - """Return all flavors in brief.""" - flavors = self._get_flavors(req) - return self._view_builder.index(req, flavors) - - def detail(self, req): - """Return all flavors in detail.""" - flavors = self._get_flavors(req) - return self._view_builder.detail(req, flavors) - - def show(self, req, id): - """Return data about the given flavor id.""" - try: - flavor = instance_types.get_instance_type_by_flavor_id(id) - except exception.NotFound: - raise webob.exc.HTTPNotFound() - - return self._view_builder.show(req, flavor) - - def _get_flavors(self, req): - """Helper function that returns a list of flavor dicts.""" - filters = {} - if 'minRam' in req.params: - try: - filters['min_memory_mb'] = int(req.params['minRam']) - except ValueError: - pass # ignore bogus values per spec - - if 'minDisk' in req.params: - try: - filters['min_local_gb'] = int(req.params['minDisk']) - except ValueError: - pass # ignore bogus values per spec - - return instance_types.get_all_types(filters=filters) - - def make_flavor(elem, detailed=False): elem.set('name') elem.set('id') @@ -105,18 +63,50 @@ class FlavorsTemplate(xmlutil.TemplateBuilder): return xmlutil.MasterTemplate(root, 1, nsmap=flavor_nsmap) -class FlavorXMLSerializer(xmlutil.XMLTemplateSerializer): - def show(self): - return FlavorTemplate() +class Controller(wsgi.Controller): + """Flavor controller for the OpenStack API.""" + + _view_builder_class = flavors_view.ViewBuilder + + @wsgi.serializers(xml=MinimalFlavorsTemplate) + def index(self, req): + """Return all flavors in brief.""" + flavors = self._get_flavors(req) + return self._view_builder.index(req, flavors) + + @wsgi.serializers(xml=FlavorsTemplate) + def detail(self, req): + """Return all flavors in detail.""" + flavors = self._get_flavors(req) + return self._view_builder.detail(req, flavors) + + @wsgi.serializers(xml=FlavorTemplate) + def show(self, req, id): + """Return data about the given flavor id.""" + try: + flavor = instance_types.get_instance_type_by_flavor_id(id) + except exception.NotFound: + raise webob.exc.HTTPNotFound() + + return self._view_builder.show(req, flavor) + + def _get_flavors(self, req): + """Helper function that returns a list of flavor dicts.""" + filters = {} + if 'minRam' in req.params: + try: + filters['min_memory_mb'] = int(req.params['minRam']) + except ValueError: + pass # ignore bogus values per spec - def detail(self): - return FlavorsTemplate() + if 'minDisk' in req.params: + try: + filters['min_local_gb'] = int(req.params['minDisk']) + except ValueError: + pass # ignore bogus values per spec - def index(self): - return MinimalFlavorsTemplate() + return instance_types.get_all_types(filters=filters) def create_resource(): - body_serializers = {'application/xml': FlavorXMLSerializer()} - serializer = wsgi.ResponseSerializer(body_serializers) - return wsgi.Resource(Controller(), serializer=serializer) + return wsgi.Resource(Controller()) diff --git a/nova/api/openstack/v2/image_metadata.py b/nova/api/openstack/v2/image_metadata.py index 1afbe01bb..1e29d23ce 100644 --- a/nova/api/openstack/v2/image_metadata.py +++ b/nova/api/openstack/v2/image_metadata.py @@ -40,12 +40,14 @@ class Controller(object): msg = _("Image not found.") raise exc.HTTPNotFound(explanation=msg) + @wsgi.serializers(xml=common.MetadataTemplate) def index(self, req, image_id): """Returns the list of metadata for a given instance""" context = req.environ['nova.context'] metadata = self._get_image(context, image_id)['properties'] return dict(metadata=metadata) + @wsgi.serializers(xml=common.MetaItemTemplate) def show(self, req, image_id, id): context = req.environ['nova.context'] metadata = self._get_image(context, image_id)['properties'] @@ -54,6 +56,8 @@ class Controller(object): else: raise exc.HTTPNotFound() + @wsgi.serializers(xml=common.MetadataTemplate) + @wsgi.deserializers(xml=common.MetadataDeserializer) def create(self, req, image_id, body): context = req.environ['nova.context'] image = self._get_image(context, image_id) @@ -64,6 +68,8 @@ class Controller(object): self.image_service.update(context, image_id, image, None) return dict(metadata=image['properties']) + @wsgi.serializers(xml=common.MetaItemTemplate) + @wsgi.deserializers(xml=common.MetaItemDeserializer) def update(self, req, image_id, id, body): context = req.environ['nova.context'] @@ -86,6 +92,8 @@ class Controller(object): self.image_service.update(context, image_id, image, None) return dict(meta=meta) + @wsgi.serializers(xml=common.MetadataTemplate) + @wsgi.deserializers(xml=common.MetadataDeserializer) def update_all(self, req, image_id, body): context = req.environ['nova.context'] image = self._get_image(context, image_id) @@ -95,6 +103,7 @@ class Controller(object): self.image_service.update(context, image_id, image, None) return dict(metadata=metadata) + @wsgi.response(204) def delete(self, req, image_id, id): context = req.environ['nova.context'] image = self._get_image(context, image_id) @@ -106,16 +115,4 @@ class Controller(object): def create_resource(): - headers_serializer = common.MetadataHeadersSerializer() - - body_deserializers = { - 'application/xml': common.MetadataXMLDeserializer(), - } - - body_serializers = { - 'application/xml': common.MetadataXMLSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) - deserializer = wsgi.RequestDeserializer(body_deserializers) - - return wsgi.Resource(Controller(), deserializer, serializer) + return wsgi.Resource(Controller()) diff --git a/nova/api/openstack/v2/images.py b/nova/api/openstack/v2/images.py index d30bf7385..a07753729 100644 --- a/nova/api/openstack/v2/images.py +++ b/nova/api/openstack/v2/images.py @@ -40,6 +40,54 @@ SUPPORTED_FILTERS = { } +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 Controller(wsgi.Controller): """Base controller for retrieving/displaying images.""" @@ -72,6 +120,7 @@ class Controller(wsgi.Controller): filters[filter_name] = req.str_params.get(param) return filters + @wsgi.serializers(xml=ImageTemplate) def show(self, req, id): """Return detailed information about a specific image. @@ -102,6 +151,7 @@ class Controller(wsgi.Controller): raise webob.exc.HTTPNotFound(explanation=explanation) return webob.exc.HTTPNoContent() + @wsgi.serializers(xml=MinimalImagesTemplate) def index(self, req): """Return an index listing of images available to the request. @@ -119,6 +169,7 @@ class Controller(wsgi.Controller): **page_params) return self._view_builder.index(req, images) + @wsgi.serializers(xml=ImagesTemplate) def detail(self, req): """Return a detailed index listing of images available to the request. @@ -140,66 +191,5 @@ class Controller(wsgi.Controller): raise webob.exc.HTTPMethodNotAllowed() -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(): - body_serializers = {'application/xml': ImageXMLSerializer()} - serializer = wsgi.ResponseSerializer(body_serializers) - return wsgi.Resource(Controller(), serializer=serializer) + return wsgi.Resource(Controller()) diff --git a/nova/api/openstack/v2/ips.py b/nova/api/openstack/v2/ips.py index f903cb8ba..3dc9cf928 100644 --- a/nova/api/openstack/v2/ips.py +++ b/nova/api/openstack/v2/ips.py @@ -30,6 +30,34 @@ LOG = logging.getLogger('nova.api.openstack.v2.ips') FLAGS = flags.FLAGS +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 Controller(wsgi.Controller): """The servers addresses API controller for the Openstack API.""" @@ -53,12 +81,14 @@ class Controller(wsgi.Controller): def delete(self, req, server_id, id): raise exc.HTTPNotImplemented() + @wsgi.serializers(xml=AddressesTemplate) def index(self, req, server_id): context = req.environ["nova.context"] instance = self._get_instance(context, server_id) networks = common.get_networks_for_instance(context, instance) return self._view_builder.index(networks) + @wsgi.serializers(xml=NetworkTemplate) def show(self, req, server_id, id): context = req.environ["nova.context"] instance = self._get_instance(context, server_id) @@ -71,43 +101,5 @@ class Controller(wsgi.Controller): return self._view_builder.show(networks[id], id) -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(): - body_serializers = {'application/xml': IPXMLSerializer()} - serializer = wsgi.ResponseSerializer(body_serializers) - return wsgi.Resource(Controller(), serializer=serializer) + return wsgi.Resource(Controller()) diff --git a/nova/api/openstack/v2/limits.py b/nova/api/openstack/v2/limits.py index 19c25b0b4..e1f5ff836 100644 --- a/nova/api/openstack/v2/limits.py +++ b/nova/api/openstack/v2/limits.py @@ -43,26 +43,6 @@ PER_HOUR = 60 * 60 PER_DAY = 60 * 60 * 24 -class LimitsController(object): - """ - Controller for accessing limits in the OpenStack API. - """ - - def index(self, req): - """ - Return all global and rate limit information. - """ - context = req.environ['nova.context'] - abs_limits = quota.get_project_quotas(context, context.project_id) - rate_limits = req.environ.get("nova.limits", []) - - builder = self._get_view_builder(req) - return builder.build(rate_limits, abs_limits) - - def _get_view_builder(self, req): - return limits_views.ViewBuilder() - - limits_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM} @@ -91,15 +71,29 @@ class LimitsTemplate(xmlutil.TemplateBuilder): return xmlutil.MasterTemplate(root, 1, nsmap=limits_nsmap) -class LimitsXMLSerializer(xmlutil.XMLTemplateSerializer): - def index(self): - return LimitsTemplate() +class LimitsController(object): + """ + Controller for accessing limits in the OpenStack API. + """ + + @wsgi.serializers(xml=LimitsTemplate) + def index(self, req): + """ + Return all global and rate limit information. + """ + context = req.environ['nova.context'] + abs_limits = quota.get_project_quotas(context, context.project_id) + rate_limits = req.environ.get("nova.limits", []) + + builder = self._get_view_builder(req) + return builder.build(rate_limits, abs_limits) + + def _get_view_builder(self, req): + return limits_views.ViewBuilder() def create_resource(): - body_serializers = {'application/xml': LimitsXMLSerializer()} - serializer = wsgi.ResponseSerializer(body_serializers) - return wsgi.Resource(LimitsController(), serializer=serializer) + return wsgi.Resource(LimitsController()) class Limit(object): diff --git a/nova/api/openstack/v2/server_metadata.py b/nova/api/openstack/v2/server_metadata.py index 47dbe2fbd..52a90f96e 100644 --- a/nova/api/openstack/v2/server_metadata.py +++ b/nova/api/openstack/v2/server_metadata.py @@ -43,11 +43,14 @@ class Controller(object): meta_dict[key] = value return meta_dict + @wsgi.serializers(xml=common.MetadataTemplate) def index(self, req, server_id): """ Returns the list of metadata for a given instance """ context = req.environ['nova.context'] return {'metadata': self._get_metadata(context, server_id)} + @wsgi.serializers(xml=common.MetadataTemplate) + @wsgi.deserializers(xml=common.MetadataDeserializer) def create(self, req, server_id, body): try: metadata = body['metadata'] @@ -64,6 +67,8 @@ class Controller(object): return {'metadata': new_metadata} + @wsgi.serializers(xml=common.MetaItemTemplate) + @wsgi.deserializers(xml=common.MetaItemDeserializer) def update(self, req, server_id, id, body): try: meta_item = body['meta'] @@ -89,6 +94,8 @@ class Controller(object): return {'meta': meta_item} + @wsgi.serializers(xml=common.MetadataTemplate) + @wsgi.deserializers(xml=common.MetadataDeserializer) def update_all(self, req, server_id, body): try: metadata = body['metadata'] @@ -124,6 +131,7 @@ class Controller(object): except exception.QuotaError as error: self._handle_quota_error(error) + @wsgi.serializers(xml=common.MetaItemTemplate) def show(self, req, server_id, id): """ Return a single metadata item """ context = req.environ['nova.context'] @@ -135,6 +143,7 @@ class Controller(object): msg = _("Metadata item was not found") raise exc.HTTPNotFound(explanation=msg) + @wsgi.response(204) def delete(self, req, server_id, id): """ Deletes an existing metadata """ context = req.environ['nova.context'] @@ -163,16 +172,4 @@ class Controller(object): def create_resource(): - headers_serializer = common.MetadataHeadersSerializer() - - body_deserializers = { - 'application/xml': common.MetadataXMLDeserializer(), - } - - body_serializers = { - 'application/xml': common.MetadataXMLSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) - deserializer = wsgi.RequestDeserializer(body_deserializers) - - return wsgi.Resource(Controller(), deserializer, serializer) + return wsgi.Resource(Controller()) diff --git a/nova/api/openstack/v2/servers.py b/nova/api/openstack/v2/servers.py index bab2976f9..ac8dd457a 100644 --- a/nova/api/openstack/v2/servers.py +++ b/nova/api/openstack/v2/servers.py @@ -41,1061 +41,1063 @@ LOG = logging.getLogger('nova.api.openstack.v2.servers') FLAGS = flags.FLAGS -class Controller(wsgi.Controller): - """ The Server API base controller class for the OpenStack API """ +class SecurityGroupsTemplateElement(xmlutil.TemplateElement): + def will_render(self, datum): + return 'security_groups' in datum - _view_builder_class = views_servers.ViewBuilder - def __init__(self, **kwargs): - super(Controller, self).__init__(**kwargs) - self.compute_api = compute.API() - self.network_api = network.API() +def make_fault(elem): + fault = xmlutil.SubTemplateElement(elem, 'fault', selector='fault') + fault.set('code') + fault.set('created') + msg = xmlutil.SubTemplateElement(fault, 'message') + msg.text = 'message' + det = xmlutil.SubTemplateElement(fault, 'details') + det.text = 'details' - def index(self, req): - """ Returns a list of server names and ids for a given user """ - try: - servers = self._get_servers(req, is_detail=False) - except exception.Invalid as err: - raise exc.HTTPBadRequest(explanation=str(err)) - except exception.NotFound: - raise exc.HTTPNotFound() - return servers - def detail(self, req): - """ Returns a list of server details for a given user """ - try: - servers = self._get_servers(req, is_detail=True) - except exception.Invalid as err: - raise exc.HTTPBadRequest(explanation=str(err)) - except exception.NotFound as err: - raise exc.HTTPNotFound() - return servers +def make_server(elem, detailed=False): + elem.set('name') + elem.set('id') - def _get_block_device_mapping(self, data): - """Get block_device_mapping from 'server' dictionary. - Overridden by volumes controller. - """ - return None + if detailed: + 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') - def _add_instance_faults(self, ctxt, instances): - faults = self.compute_api.get_instance_faults(ctxt, instances) - if faults is not None: - for instance in instances: - faults_list = faults.get(instance['uuid'], []) - try: - instance['fault'] = faults_list[0] - except IndexError: - pass + # Attach image node + image = xmlutil.SubTemplateElement(elem, 'image', selector='image') + image.set('id') + xmlutil.make_links(image, 'links') - return instances + # Attach flavor node + flavor = xmlutil.SubTemplateElement(elem, 'flavor', selector='flavor') + flavor.set('id') + xmlutil.make_links(flavor, 'links') - def _get_servers(self, req, is_detail): - """Returns a list of servers, taking into account any search - options specified. - """ + # Attach fault node + make_fault(elem) - search_opts = {} - search_opts.update(req.str_GET) + # Attach metadata node + elem.append(common.MetadataTemplate()) - context = req.environ['nova.context'] - remove_invalid_options(context, search_opts, - self._get_server_search_options()) + # Attach addresses node + elem.append(ips.AddressesTemplate()) - # Convert local_zone_only into a boolean - search_opts['local_zone_only'] = utils.bool_from_str( - search_opts.get('local_zone_only', False)) + # Attach security groups node + secgrps = SecurityGroupsTemplateElement('security_groups') + elem.append(secgrps) + secgrp = xmlutil.SubTemplateElement(secgrps, 'security_group', + selector='security_groups') + secgrp.set('name') - # If search by 'status', we need to convert it to 'vm_state' - # to pass on to child zones. - if 'status' in search_opts: - status = search_opts['status'] - state = common.vm_state_from_status(status) - if state is None: - reason = _('Invalid server status: %(status)s') % locals() - raise exception.InvalidInput(reason=reason) - search_opts['vm_state'] = state + xmlutil.make_links(elem, 'links') - if 'changes-since' in search_opts: - try: - parsed = utils.parse_isotime(search_opts['changes-since']) - except ValueError: - msg = _('Invalid changes-since value') - raise exc.HTTPBadRequest(explanation=msg) - search_opts['changes-since'] = parsed - # By default, compute's get_all() will return deleted instances. - # If an admin hasn't specified a 'deleted' search option, we need - # to filter out deleted instances by setting the filter ourselves. - # ... Unless 'changes-since' is specified, because 'changes-since' - # should return recently deleted images according to the API spec. +server_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM} - if 'deleted' not in search_opts: - if 'changes-since' not in search_opts: - # No 'changes-since', so we only want non-deleted servers - search_opts['deleted'] = False - instance_list = self.compute_api.get_all(context, - search_opts=search_opts) +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) - limited_list = self._limit_items(instance_list, req) - if is_detail: - self._add_instance_faults(context, limited_list) - return self._view_builder.detail(req, limited_list) - else: - return self._view_builder.index(req, limited_list) - def _get_server(self, context, instance_uuid): - """Utility function for looking up an instance by uuid""" - try: - return self.compute_api.routing_get(context, instance_uuid) - except exception.NotFound: - raise exc.HTTPNotFound() +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) - def _handle_quota_error(self, error): - """ - Reraise quota errors as api-specific http exceptions - """ - code_mappings = { - "OnsetFileLimitExceeded": - _("Personality file limit exceeded"), - "OnsetFilePathLimitExceeded": - _("Personality file path too long"), - "OnsetFileContentLimitExceeded": - _("Personality file content too long"), +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) - # NOTE(bcwaldon): expose the message generated below in order - # to better explain how the quota was exceeded - "InstanceLimitExceeded": error.message, - } - expl = code_mappings.get(error.code) - if expl: - raise exc.HTTPRequestEntityTooLarge(explanation=expl, - headers={'Retry-After': 0}) - # if the original error is okay, just reraise it - raise error +class ServerAdminPassTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('server') + root.set('adminPass') + return xmlutil.SlaveTemplate(root, 1, nsmap=server_nsmap) - def _validate_server_name(self, value): - if not isinstance(value, basestring): - msg = _("Server name is not a string or unicode") - raise exc.HTTPBadRequest(explanation=msg) - if value.strip() == '': - msg = _("Server name is an empty string") - raise exc.HTTPBadRequest(explanation=msg) +def FullServerTemplate(): + master = ServerTemplate() + master.attach(ServerAdminPassTemplate()) + return master - def _get_injected_files(self, personality): - """ - Create a list of injected files from the personality attribute - At this time, injected_files must be formatted as a list of - (file_path, file_content) pairs for compatibility with the - underlying compute service. - """ - injected_files = [] +class CommonDeserializer(wsgi.MetadataXMLDeserializer): + """ + Common deserializer to handle xml-formatted server create + requests. - for item in personality: - try: - path = item['path'] - contents = item['contents'] - except KeyError as key: - expl = _('Bad personality format: missing %s') % key - raise exc.HTTPBadRequest(explanation=expl) - except TypeError: - expl = _('Bad personality format') - raise exc.HTTPBadRequest(explanation=expl) - try: - contents = base64.b64decode(contents) - except TypeError: - expl = _('Personality content for %s cannot be decoded') % path - raise exc.HTTPBadRequest(explanation=expl) - injected_files.append((path, contents)) - return injected_files + Handles standard server attributes as well as optional metadata + and personality attributes + """ - def _get_requested_networks(self, requested_networks): - """ - Create a list of requested networks from the networks attribute - """ - networks = [] - for network in requested_networks: - try: - network_uuid = network['uuid'] + metadata_deserializer = common.MetadataXMLDeserializer() - if not utils.is_uuid_like(network_uuid): - msg = _("Bad networks format: network uuid is not in" - " proper format (%s)") % network_uuid - raise exc.HTTPBadRequest(explanation=msg) + def _extract_personality(self, server_node): + """Marshal the personality attribute of a parsed request""" + node = self.find_first_child_named(server_node, "personality") + if node is not None: + personality = [] + for file_node in self.find_children_named(node, "file"): + item = {} + if file_node.hasAttribute("path"): + item["path"] = file_node.getAttribute("path") + item["contents"] = self.extract_text(file_node) + personality.append(item) + return personality + else: + return None - #fixed IP address is optional - #if the fixed IP address is not provided then - #it will use one of the available IP address from the network - address = network.get('fixed_ip', None) - if address is not None and not utils.is_valid_ipv4(address): - msg = _("Invalid fixed IP address (%s)") % address - raise exc.HTTPBadRequest(explanation=msg) - # check if the network id is already present in the list, - # we don't want duplicate networks to be passed - # at the boot time - for id, ip in networks: - if id == network_uuid: - expl = _("Duplicate networks (%s) are not allowed")\ - % network_uuid - raise exc.HTTPBadRequest(explanation=expl) - - networks.append((network_uuid, address)) - except KeyError as key: - expl = _('Bad network format: missing %s') % key - raise exc.HTTPBadRequest(explanation=expl) - except TypeError: - expl = _('Bad networks format') - raise exc.HTTPBadRequest(explanation=expl) + def _extract_server(self, node): + """Marshal the server attribute of a parsed request""" + server = {} + server_node = self.find_first_child_named(node, 'server') - return networks + attributes = ["name", "imageRef", "flavorRef", "adminPass", + "accessIPv4", "accessIPv6"] + for attr in attributes: + if server_node.getAttribute(attr): + server[attr] = server_node.getAttribute(attr) - def _validate_user_data(self, user_data): - """Check if the user_data is encoded properly""" - if not user_data: - return - try: - user_data = base64.b64decode(user_data) - except TypeError: - expl = _('Userdata content cannot be decoded') - raise exc.HTTPBadRequest(explanation=expl) + metadata_node = self.find_first_child_named(server_node, "metadata") + if metadata_node is not None: + server["metadata"] = self.extract_metadata(metadata_node) - @exception.novaclient_converter - @scheduler_api.redirect_handler - def show(self, req, id): - """ Returns server details by server id """ - try: - context = req.environ['nova.context'] - instance = self.compute_api.routing_get(context, id) - self._add_instance_faults(context, [instance]) - return self._view_builder.show(req, instance) - except exception.NotFound: - raise exc.HTTPNotFound() + personality = self._extract_personality(server_node) + if personality is not None: + server["personality"] = personality - def create(self, req, body): - """ Creates a new server for a given user """ - if not body: - raise exc.HTTPUnprocessableEntity() + networks = self._extract_networks(server_node) + if networks is not None: + server["networks"] = networks - if not 'server' in body: - raise exc.HTTPUnprocessableEntity() + security_groups = self._extract_security_groups(server_node) + if security_groups is not None: + server["security_groups"] = security_groups - body['server']['key_name'] = self._get_key_name(req, body) + auto_disk_config = server_node.getAttribute('auto_disk_config') + if auto_disk_config: + server['auto_disk_config'] = utils.bool_from_str(auto_disk_config) - context = req.environ['nova.context'] - server_dict = body['server'] - password = self._get_server_admin_password(server_dict) + return server - if not 'name' in server_dict: - msg = _("Server name is not defined") - raise exc.HTTPBadRequest(explanation=msg) + def _extract_networks(self, server_node): + """Marshal the networks attribute of a parsed request""" + node = self.find_first_child_named(server_node, "networks") + if node is not None: + networks = [] + for network_node in self.find_children_named(node, + "network"): + item = {} + if network_node.hasAttribute("uuid"): + item["uuid"] = network_node.getAttribute("uuid") + if network_node.hasAttribute("fixed_ip"): + item["fixed_ip"] = network_node.getAttribute("fixed_ip") + networks.append(item) + return networks + else: + return None - name = server_dict['name'] - self._validate_server_name(name) - name = name.strip() + def _extract_security_groups(self, server_node): + """Marshal the security_groups attribute of a parsed request""" + node = self.find_first_child_named(server_node, "security_groups") + if node is not None: + security_groups = [] + for sg_node in self.find_children_named(node, "security_group"): + item = {} + name_node = self.find_first_child_named(sg_node, "name") + if name_node: + item["name"] = self.extract_text(name_node) + security_groups.append(item) + return security_groups + else: + return None - image_href = self._image_ref_from_req_data(body) - # If the image href was generated by nova api, strip image_href - # down to an id and use the default glance connection params - if str(image_href).startswith(req.application_url): - image_href = image_href.split('/').pop() +class ActionDeserializer(CommonDeserializer): + """ + Deserializer to handle xml-formatted server action requests. - personality = server_dict.get('personality') - config_drive = server_dict.get('config_drive') + Handles standard server attributes as well as optional metadata + and personality attributes + """ - injected_files = [] - if personality: - injected_files = self._get_injected_files(personality) + def default(self, string): + dom = minidom.parseString(string) + action_node = dom.childNodes[0] + action_name = action_node.tagName - sg_names = [] - security_groups = server_dict.get('security_groups') - if security_groups is not None: - sg_names = [sg['name'] for sg in security_groups if sg.get('name')] - if not sg_names: - sg_names.append('default') + action_deserializer = { + 'createImage': self._action_create_image, + 'changePassword': self._action_change_password, + 'reboot': self._action_reboot, + 'rebuild': self._action_rebuild, + 'resize': self._action_resize, + 'confirmResize': self._action_confirm_resize, + 'revertResize': self._action_revert_resize, + }.get(action_name, super(ActionDeserializer, self).default) - sg_names = list(set(sg_names)) + action_data = action_deserializer(action_node) - requested_networks = server_dict.get('networks') - if requested_networks is not None: - requested_networks = self._get_requested_networks( - requested_networks) + return {'body': {action_name: action_data}} - try: - flavor_id = self._flavor_id_from_req_data(body) - except ValueError as error: - msg = _("Invalid flavorRef provided.") - raise exc.HTTPBadRequest(explanation=msg) + def _action_create_image(self, node): + return self._deserialize_image_action(node, ('name',)) - zone_blob = server_dict.get('blob') + def _action_change_password(self, node): + if not node.hasAttribute("adminPass"): + raise AttributeError("No adminPass was specified in request") + return {"adminPass": node.getAttribute("adminPass")} - # optional openstack extensions: - key_name = server_dict.get('key_name') - user_data = server_dict.get('user_data') - self._validate_user_data(user_data) + def _action_reboot(self, node): + if not node.hasAttribute("type"): + raise AttributeError("No reboot type was specified in request") + return {"type": node.getAttribute("type")} - availability_zone = server_dict.get('availability_zone') - name = server_dict['name'] - self._validate_server_name(name) - name = name.strip() + def _action_rebuild(self, node): + rebuild = {} + if node.hasAttribute("name"): + rebuild['name'] = node.getAttribute("name") - block_device_mapping = self._get_block_device_mapping(server_dict) + metadata_node = self.find_first_child_named(node, "metadata") + if metadata_node is not None: + rebuild["metadata"] = self.extract_metadata(metadata_node) - # Only allow admins to specify their own reservation_ids - # This is really meant to allow zones to work. - reservation_id = server_dict.get('reservation_id') - if all([reservation_id is not None, - reservation_id != '', - not context.is_admin]): - reservation_id = None + personality = self._extract_personality(node) + if personality is not None: + rebuild["personality"] = personality - ret_resv_id = server_dict.get('return_reservation_id', False) + if not node.hasAttribute("imageRef"): + raise AttributeError("No imageRef was specified in request") + rebuild["imageRef"] = node.getAttribute("imageRef") - min_count = server_dict.get('min_count') - max_count = server_dict.get('max_count') - # min_count and max_count are optional. If they exist, they come - # in as strings. We want to default 'min_count' to 1, and default - # 'max_count' to be 'min_count'. - min_count = int(min_count) if min_count else 1 - max_count = int(max_count) if max_count else min_count - if min_count > max_count: - min_count = max_count + return rebuild - auto_disk_config = server_dict.get('auto_disk_config') + def _action_resize(self, node): + if not node.hasAttribute("flavorRef"): + raise AttributeError("No flavorRef was specified in request") + return {"flavorRef": node.getAttribute("flavorRef")} - try: - inst_type = \ - instance_types.get_instance_type_by_flavor_id(flavor_id) + def _action_confirm_resize(self, node): + return None - (instances, resv_id) = self.compute_api.create(context, - inst_type, - image_href, - display_name=name, - display_description=name, - key_name=key_name, - metadata=server_dict.get('metadata', {}), - access_ip_v4=server_dict.get('accessIPv4'), - access_ip_v6=server_dict.get('accessIPv6'), - injected_files=injected_files, - admin_password=password, - zone_blob=zone_blob, - reservation_id=reservation_id, - min_count=min_count, - max_count=max_count, - requested_networks=requested_networks, - security_group=sg_names, - user_data=user_data, - availability_zone=availability_zone, - config_drive=config_drive, - block_device_mapping=block_device_mapping, - auto_disk_config=auto_disk_config) - except exception.QuotaError as error: - self._handle_quota_error(error) - except exception.InstanceTypeMemoryTooSmall as error: - raise exc.HTTPBadRequest(explanation=unicode(error)) - except exception.InstanceTypeDiskTooSmall as error: - raise exc.HTTPBadRequest(explanation=unicode(error)) - except exception.ImageNotFound as error: - msg = _("Can not find requested image") - raise exc.HTTPBadRequest(explanation=msg) - except exception.FlavorNotFound as error: - msg = _("Invalid flavorRef provided.") - raise exc.HTTPBadRequest(explanation=msg) - except exception.KeypairNotFound as error: - msg = _("Invalid key_name provided.") - raise exc.HTTPBadRequest(explanation=msg) - except exception.SecurityGroupNotFound as error: - raise exc.HTTPBadRequest(explanation=unicode(error)) - except rpc_common.RemoteError as err: - msg = "%(err_type)s: %(err_msg)s" % \ - {'err_type': err.exc_type, 'err_msg': err.value} - raise exc.HTTPBadRequest(explanation=msg) - # Let the caller deal with unhandled exceptions. + def _action_revert_resize(self, node): + return None - # If the caller wanted a reservation_id, return it - if ret_resv_id: - return {'reservation_id': resv_id} - - server = self._view_builder.create(req, instances[0]) + def _deserialize_image_action(self, node, allowed_attributes): + data = {} + for attribute in allowed_attributes: + value = node.getAttribute(attribute) + if value: + data[attribute] = value + metadata_node = self.find_first_child_named(node, 'metadata') + if metadata_node is not None: + metadata = self.metadata_deserializer.extract_metadata( + metadata_node) + data['metadata'] = metadata + return data - if '_is_precooked' in server['server'].keys(): - del server['server']['_is_precooked'] - else: - server['server']['adminPass'] = password - return server +class CreateDeserializer(CommonDeserializer): + """ + Deserializer to handle xml-formatted server create requests. - def _delete(self, context, id): - instance = self._get_server(context, id) - if FLAGS.reclaim_instance_interval: - self.compute_api.soft_delete(context, instance) - else: - self.compute_api.delete(context, instance) + Handles standard server attributes as well as optional metadata + and personality attributes + """ - @scheduler_api.redirect_handler - def update(self, req, id, body): - """Update server then pass on to version-specific controller""" - if len(req.body) == 0: - raise exc.HTTPUnprocessableEntity() + def default(self, string): + """Deserialize an xml-formatted server create request""" + dom = minidom.parseString(string) + server = self._extract_server(dom) + return {'body': {'server': server}} - if not body: - raise exc.HTTPUnprocessableEntity() - ctxt = req.environ['nova.context'] - update_dict = {} +class Controller(wsgi.Controller): + """ The Server API base controller class for the OpenStack API """ - if 'name' in body['server']: - name = body['server']['name'] - self._validate_server_name(name) - update_dict['display_name'] = name.strip() + _view_builder_class = views_servers.ViewBuilder - if 'accessIPv4' in body['server']: - access_ipv4 = body['server']['accessIPv4'] - update_dict['access_ip_v4'] = access_ipv4.strip() + @staticmethod + def _add_location(robj): + # Just in case... + if 'server' not in robj.obj: + return robj - if 'accessIPv6' in body['server']: - access_ipv6 = body['server']['accessIPv6'] - update_dict['access_ip_v6'] = access_ipv6.strip() + link = filter(lambda l: l['rel'] == 'self', + robj.obj['server']['links']) + if link: + robj['Location'] = link[0]['href'] - if 'auto_disk_config' in body['server']: - auto_disk_config = utils.bool_from_str( - body['server']['auto_disk_config']) - update_dict['auto_disk_config'] = auto_disk_config + # Convenience return + return robj - instance = self.compute_api.routing_get(ctxt, id) + def __init__(self, **kwargs): + super(Controller, self).__init__(**kwargs) + self.compute_api = compute.API() + self.network_api = network.API() + @wsgi.serializers(xml=MinimalServersTemplate) + def index(self, req): + """ Returns a list of server names and ids for a given user """ try: - self.compute_api.update(ctxt, instance, **update_dict) + servers = self._get_servers(req, is_detail=False) + except exception.Invalid as err: + raise exc.HTTPBadRequest(explanation=str(err)) except exception.NotFound: raise exc.HTTPNotFound() + return servers - instance.update(update_dict) + @wsgi.serializers(xml=ServersTemplate) + def detail(self, req): + """ Returns a list of server details for a given user """ + try: + servers = self._get_servers(req, is_detail=True) + except exception.Invalid as err: + raise exc.HTTPBadRequest(explanation=str(err)) + except exception.NotFound as err: + raise exc.HTTPNotFound() + return servers - self._add_instance_faults(ctxt, [instance]) - return self._view_builder.show(req, instance) + def _get_block_device_mapping(self, data): + """Get block_device_mapping from 'server' dictionary. + Overridden by volumes controller. + """ + return None - @exception.novaclient_converter - @scheduler_api.redirect_handler - def action(self, req, id, body): - """Multi-purpose method used to take actions on a server""" - _actions = { - 'changePassword': self._action_change_password, - 'reboot': self._action_reboot, - 'resize': self._action_resize, - 'confirmResize': self._action_confirm_resize, - 'revertResize': self._action_revert_resize, - 'rebuild': self._action_rebuild, - 'createImage': self._action_create_image, - } + def _add_instance_faults(self, ctxt, instances): + faults = self.compute_api.get_instance_faults(ctxt, instances) + if faults is not None: + for instance in instances: + faults_list = faults.get(instance['uuid'], []) + try: + instance['fault'] = faults_list[0] + except IndexError: + pass - for key in body: - if key in _actions: - return _actions[key](body, req, id) - else: - msg = _("There is no such server action: %s") % (key,) - raise exc.HTTPBadRequest(explanation=msg) + return instances - msg = _("Invalid request body") - raise exc.HTTPBadRequest(explanation=msg) + def _get_servers(self, req, is_detail): + """Returns a list of servers, taking into account any search + options specified. + """ - def _action_confirm_resize(self, input_dict, req, id): - context = req.environ['nova.context'] - instance = self._get_server(context, id) - try: - self.compute_api.confirm_resize(context, instance) - except exception.MigrationNotFound: - msg = _("Instance has not been resized.") - raise exc.HTTPBadRequest(explanation=msg) - except Exception, e: - LOG.exception(_("Error in confirm-resize %s"), e) - raise exc.HTTPBadRequest() - return exc.HTTPNoContent() + search_opts = {} + search_opts.update(req.str_GET) - def _action_revert_resize(self, input_dict, req, id): context = req.environ['nova.context'] - instance = self._get_server(context, id) - try: - self.compute_api.revert_resize(context, instance) - except exception.MigrationNotFound: - msg = _("Instance has not been resized.") - raise exc.HTTPBadRequest(explanation=msg) - except Exception, e: - LOG.exception(_("Error in revert-resize %s"), e) - raise exc.HTTPBadRequest() - return webob.Response(status_int=202) + remove_invalid_options(context, search_opts, + self._get_server_search_options()) - def _action_reboot(self, input_dict, req, id): - if 'reboot' in input_dict and 'type' in input_dict['reboot']: - valid_reboot_types = ['HARD', 'SOFT'] - reboot_type = input_dict['reboot']['type'].upper() - if not valid_reboot_types.count(reboot_type): - msg = _("Argument 'type' for reboot is not HARD or SOFT") - LOG.exception(msg) - raise exc.HTTPBadRequest(explanation=msg) - else: - msg = _("Missing argument 'type' for reboot") - LOG.exception(msg) - raise exc.HTTPBadRequest(explanation=msg) + # Convert local_zone_only into a boolean + search_opts['local_zone_only'] = utils.bool_from_str( + search_opts.get('local_zone_only', False)) - context = req.environ['nova.context'] - instance = self._get_server(context, id) + # If search by 'status', we need to convert it to 'vm_state' + # to pass on to child zones. + if 'status' in search_opts: + status = search_opts['status'] + state = common.vm_state_from_status(status) + if state is None: + reason = _('Invalid server status: %(status)s') % locals() + raise exception.InvalidInput(reason=reason) + search_opts['vm_state'] = state - try: - self.compute_api.reboot(context, instance, reboot_type) - except Exception, e: - LOG.exception(_("Error in reboot %s"), e) - raise exc.HTTPUnprocessableEntity() - return webob.Response(status_int=202) + if 'changes-since' in search_opts: + try: + parsed = utils.parse_isotime(search_opts['changes-since']) + except ValueError: + msg = _('Invalid changes-since value') + raise exc.HTTPBadRequest(explanation=msg) + search_opts['changes-since'] = parsed - def _resize(self, req, instance_id, flavor_id): - """Begin the resize process with given instance/flavor.""" - context = req.environ["nova.context"] - instance = self._get_server(context, instance_id) + # By default, compute's get_all() will return deleted instances. + # If an admin hasn't specified a 'deleted' search option, we need + # to filter out deleted instances by setting the filter ourselves. + # ... Unless 'changes-since' is specified, because 'changes-since' + # should return recently deleted images according to the API spec. - try: - self.compute_api.resize(context, instance, flavor_id) - except exception.FlavorNotFound: - msg = _("Unable to locate requested flavor.") - raise exc.HTTPBadRequest(explanation=msg) - except exception.CannotResizeToSameSize: - msg = _("Resize requires a change in size.") - raise exc.HTTPBadRequest(explanation=msg) + if 'deleted' not in search_opts: + if 'changes-since' not in search_opts: + # No 'changes-since', so we only want non-deleted servers + search_opts['deleted'] = False - return webob.Response(status_int=202) + instance_list = self.compute_api.get_all(context, + search_opts=search_opts) - @exception.novaclient_converter - @scheduler_api.redirect_handler - def delete(self, req, id): - """ Destroys a server """ + limited_list = self._limit_items(instance_list, req) + if is_detail: + self._add_instance_faults(context, limited_list) + return self._view_builder.detail(req, limited_list) + else: + return self._view_builder.index(req, limited_list) + + def _get_server(self, context, instance_uuid): + """Utility function for looking up an instance by uuid""" try: - self._delete(req.environ['nova.context'], id) + return self.compute_api.routing_get(context, instance_uuid) except exception.NotFound: raise exc.HTTPNotFound() - except exception.InstanceInvalidState as state_error: - state = state_error.kwargs.get("state") - msg = _("Unable to delete instance when %s") % state - raise exc.HTTPConflict(explanation=msg) - def _get_key_name(self, req, body): - if 'server' in body: - try: - return body['server'].get('key_name') - except AttributeError: - msg = _("Malformed server entity") - raise exc.HTTPBadRequest(explanation=msg) + def _handle_quota_error(self, error): + """ + Reraise quota errors as api-specific http exceptions + """ - def _image_ref_from_req_data(self, data): - try: - return data['server']['imageRef'] - except (TypeError, KeyError): - msg = _("Missing imageRef attribute") - raise exc.HTTPBadRequest(explanation=msg) + code_mappings = { + "OnsetFileLimitExceeded": + _("Personality file limit exceeded"), + "OnsetFilePathLimitExceeded": + _("Personality file path too long"), + "OnsetFileContentLimitExceeded": + _("Personality file content too long"), - def _flavor_id_from_req_data(self, data): - try: - flavor_ref = data['server']['flavorRef'] - except (TypeError, KeyError): - msg = _("Missing flavorRef attribute") - raise exc.HTTPBadRequest(explanation=msg) + # NOTE(bcwaldon): expose the message generated below in order + # to better explain how the quota was exceeded + "InstanceLimitExceeded": error.message, + } - return common.get_id_from_href(flavor_ref) + expl = code_mappings.get(error.code) + if expl: + raise exc.HTTPRequestEntityTooLarge(explanation=expl, + headers={'Retry-After': 0}) + # if the original error is okay, just reraise it + raise error - def _action_change_password(self, input_dict, req, id): - context = req.environ['nova.context'] - if (not 'changePassword' in input_dict - or not 'adminPass' in input_dict['changePassword']): - msg = _("No adminPass was specified") + def _validate_server_name(self, value): + if not isinstance(value, basestring): + msg = _("Server name is not a string or unicode") raise exc.HTTPBadRequest(explanation=msg) - password = input_dict['changePassword']['adminPass'] - if not isinstance(password, basestring) or password == '': - msg = _("Invalid adminPass") + + if value.strip() == '': + msg = _("Server name is an empty string") raise exc.HTTPBadRequest(explanation=msg) - server = self._get_server(context, id) - self.compute_api.set_admin_password(context, server, password) - return webob.Response(status_int=202) - def _limit_items(self, items, req): - return common.limited_by_marker(items, req) + def _get_injected_files(self, personality): + """ + Create a list of injected files from the personality attribute - def _validate_metadata(self, metadata): - """Ensure that we can work with the metadata given.""" - try: - metadata.iteritems() - except AttributeError as ex: - msg = _("Unable to parse metadata key/value pairs.") - LOG.debug(msg) - raise exc.HTTPBadRequest(explanation=msg) + At this time, injected_files must be formatted as a list of + (file_path, file_content) pairs for compatibility with the + underlying compute service. + """ + injected_files = [] - def _action_resize(self, input_dict, req, id): - """ Resizes a given instance to the flavor size requested """ - try: - flavor_ref = input_dict["resize"]["flavorRef"] - if not flavor_ref: - msg = _("Resize request has invalid 'flavorRef' attribute.") - raise exc.HTTPBadRequest(explanation=msg) - except (KeyError, TypeError): - msg = _("Resize requests require 'flavorRef' attribute.") - raise exc.HTTPBadRequest(explanation=msg) + for item in personality: + try: + path = item['path'] + contents = item['contents'] + except KeyError as key: + expl = _('Bad personality format: missing %s') % key + raise exc.HTTPBadRequest(explanation=expl) + except TypeError: + expl = _('Bad personality format') + raise exc.HTTPBadRequest(explanation=expl) + try: + contents = base64.b64decode(contents) + except TypeError: + expl = _('Personality content for %s cannot be decoded') % path + raise exc.HTTPBadRequest(explanation=expl) + injected_files.append((path, contents)) + return injected_files + + def _get_requested_networks(self, requested_networks): + """ + Create a list of requested networks from the networks attribute + """ + networks = [] + for network in requested_networks: + try: + network_uuid = network['uuid'] - return self._resize(req, id, flavor_ref) + if not utils.is_uuid_like(network_uuid): + msg = _("Bad networks format: network uuid is not in" + " proper format (%s)") % network_uuid + raise exc.HTTPBadRequest(explanation=msg) - def _action_rebuild(self, info, request, instance_id): - """Rebuild an instance with the given attributes""" - try: - body = info['rebuild'] - except (KeyError, TypeError): - raise exc.HTTPBadRequest(_("Invalid request body")) + #fixed IP address is optional + #if the fixed IP address is not provided then + #it will use one of the available IP address from the network + address = network.get('fixed_ip', None) + if address is not None and not utils.is_valid_ipv4(address): + msg = _("Invalid fixed IP address (%s)") % address + raise exc.HTTPBadRequest(explanation=msg) + # check if the network id is already present in the list, + # we don't want duplicate networks to be passed + # at the boot time + for id, ip in networks: + if id == network_uuid: + expl = _("Duplicate networks (%s) are not allowed")\ + % network_uuid + raise exc.HTTPBadRequest(explanation=expl) + + networks.append((network_uuid, address)) + except KeyError as key: + expl = _('Bad network format: missing %s') % key + raise exc.HTTPBadRequest(explanation=expl) + except TypeError: + expl = _('Bad networks format') + raise exc.HTTPBadRequest(explanation=expl) + + return networks + def _validate_user_data(self, user_data): + """Check if the user_data is encoded properly""" + if not user_data: + return try: - image_href = body["imageRef"] - except (KeyError, TypeError): - msg = _("Could not parse imageRef from request.") - raise exc.HTTPBadRequest(explanation=msg) + user_data = base64.b64decode(user_data) + except TypeError: + expl = _('Userdata content cannot be decoded') + raise exc.HTTPBadRequest(explanation=expl) + @wsgi.serializers(xml=ServerTemplate) + @exception.novaclient_converter + @scheduler_api.redirect_handler + def show(self, req, id): + """ Returns server details by server id """ try: - password = body['adminPass'] - except (KeyError, TypeError): - password = utils.generate_password(FLAGS.password_length) + context = req.environ['nova.context'] + instance = self.compute_api.routing_get(context, id) + self._add_instance_faults(context, [instance]) + return self._view_builder.show(req, instance) + except exception.NotFound: + raise exc.HTTPNotFound() - context = request.environ['nova.context'] - instance = self._get_server(context, instance_id) + @wsgi.response(202) + @wsgi.serializers(xml=FullServerTemplate) + @wsgi.deserializers(xml=CreateDeserializer) + def create(self, req, body): + """ Creates a new server for a given user """ + if not body: + raise exc.HTTPUnprocessableEntity() - attr_map = { - 'personality': 'files_to_inject', - 'name': 'display_name', - 'accessIPv4': 'access_ip_v4', - 'accessIPv6': 'access_ip_v6', - 'metadata': 'metadata', - } + if not 'server' in body: + raise exc.HTTPUnprocessableEntity() - kwargs = {} + body['server']['key_name'] = self._get_key_name(req, body) - for request_attribute, instance_attribute in attr_map.items(): - try: - kwargs[instance_attribute] = body[request_attribute] - except (KeyError, TypeError): - pass + context = req.environ['nova.context'] + server_dict = body['server'] + password = self._get_server_admin_password(server_dict) - self._validate_metadata(kwargs.get('metadata', {})) + if not 'name' in server_dict: + msg = _("Server name is not defined") + raise exc.HTTPBadRequest(explanation=msg) - if 'files_to_inject' in kwargs: - personality = kwargs['files_to_inject'] - kwargs['files_to_inject'] = self._get_injected_files(personality) + name = server_dict['name'] + self._validate_server_name(name) + name = name.strip() - try: - self.compute_api.rebuild(context, - instance, - image_href, - password, - **kwargs) + image_href = self._image_ref_from_req_data(body) - except exception.RebuildRequiresActiveInstance: - msg = _("Instance must be active to rebuild.") - raise exc.HTTPConflict(explanation=msg) - except exception.InstanceNotFound: - msg = _("Instance could not be found") - raise exc.HTTPNotFound(explanation=msg) + # If the image href was generated by nova api, strip image_href + # down to an id and use the default glance connection params + if str(image_href).startswith(req.application_url): + image_href = image_href.split('/').pop() - instance = self._get_server(context, instance_id) + personality = server_dict.get('personality') + config_drive = server_dict.get('config_drive') - self._add_instance_faults(context, [instance]) - view = self._view_builder.show(request, instance) + injected_files = [] + if personality: + injected_files = self._get_injected_files(personality) - # Add on the adminPass attribute since the view doesn't do it - view['server']['adminPass'] = password + sg_names = [] + security_groups = server_dict.get('security_groups') + if security_groups is not None: + sg_names = [sg['name'] for sg in security_groups if sg.get('name')] + if not sg_names: + sg_names.append('default') - return view + sg_names = list(set(sg_names)) - @common.check_snapshots_enabled - def _action_create_image(self, input_dict, req, instance_id): - """Snapshot a server instance.""" - context = req.environ['nova.context'] - entity = input_dict.get("createImage", {}) + requested_networks = server_dict.get('networks') + if requested_networks is not None: + requested_networks = self._get_requested_networks( + requested_networks) try: - image_name = entity["name"] - - except KeyError: - msg = _("createImage entity requires name attribute") + flavor_id = self._flavor_id_from_req_data(body) + except ValueError as error: + msg = _("Invalid flavorRef provided.") raise exc.HTTPBadRequest(explanation=msg) - except TypeError: - msg = _("Malformed createImage entity") - raise exc.HTTPBadRequest(explanation=msg) + zone_blob = server_dict.get('blob') - # preserve link to server in image properties - server_ref = os.path.join(req.application_url, 'servers', instance_id) - props = {'instance_ref': server_ref} + # optional openstack extensions: + key_name = server_dict.get('key_name') + user_data = server_dict.get('user_data') + self._validate_user_data(user_data) - metadata = entity.get('metadata', {}) - common.check_img_metadata_quota_limit(context, metadata) - try: - props.update(metadata) - except ValueError: - msg = _("Invalid metadata") - raise exc.HTTPBadRequest(explanation=msg) + availability_zone = server_dict.get('availability_zone') + name = server_dict['name'] + self._validate_server_name(name) + name = name.strip() - instance = self._get_server(context, instance_id) + block_device_mapping = self._get_block_device_mapping(server_dict) - try: - image = self.compute_api.snapshot(context, - instance, - image_name, - extra_properties=props) - except exception.InstanceBusy: - msg = _("Server is currently creating an image. Please wait.") - raise webob.exc.HTTPConflict(explanation=msg) + # Only allow admins to specify their own reservation_ids + # This is really meant to allow zones to work. + reservation_id = server_dict.get('reservation_id') + if all([reservation_id is not None, + reservation_id != '', + not context.is_admin]): + reservation_id = None - # build location of newly-created image entity - image_id = str(image['id']) - image_ref = os.path.join(req.application_url, - context.project_id, - 'images', - image_id) + ret_resv_id = server_dict.get('return_reservation_id', False) - resp = webob.Response(status_int=202) - resp.headers['Location'] = image_ref - return resp + min_count = server_dict.get('min_count') + max_count = server_dict.get('max_count') + # min_count and max_count are optional. If they exist, they come + # in as strings. We want to default 'min_count' to 1, and default + # 'max_count' to be 'min_count'. + min_count = int(min_count) if min_count else 1 + max_count = int(max_count) if max_count else min_count + if min_count > max_count: + min_count = max_count - def _get_server_admin_password(self, server): - """ Determine the admin password for a server on creation """ - password = server.get('adminPass') + auto_disk_config = server_dict.get('auto_disk_config') - if password is None: - return utils.generate_password(FLAGS.password_length) - if not isinstance(password, basestring) or password == '': - msg = _("Invalid adminPass") + try: + inst_type = \ + instance_types.get_instance_type_by_flavor_id(flavor_id) + + (instances, resv_id) = self.compute_api.create(context, + inst_type, + image_href, + display_name=name, + display_description=name, + key_name=key_name, + metadata=server_dict.get('metadata', {}), + access_ip_v4=server_dict.get('accessIPv4'), + access_ip_v6=server_dict.get('accessIPv6'), + injected_files=injected_files, + admin_password=password, + zone_blob=zone_blob, + reservation_id=reservation_id, + min_count=min_count, + max_count=max_count, + requested_networks=requested_networks, + security_group=sg_names, + user_data=user_data, + availability_zone=availability_zone, + config_drive=config_drive, + block_device_mapping=block_device_mapping, + auto_disk_config=auto_disk_config) + except exception.QuotaError as error: + self._handle_quota_error(error) + except exception.InstanceTypeMemoryTooSmall as error: + raise exc.HTTPBadRequest(explanation=unicode(error)) + except exception.InstanceTypeDiskTooSmall as error: + raise exc.HTTPBadRequest(explanation=unicode(error)) + except exception.ImageNotFound as error: + msg = _("Can not find requested image") + raise exc.HTTPBadRequest(explanation=msg) + except exception.FlavorNotFound as error: + msg = _("Invalid flavorRef provided.") + raise exc.HTTPBadRequest(explanation=msg) + except exception.KeypairNotFound as error: + msg = _("Invalid key_name provided.") + raise exc.HTTPBadRequest(explanation=msg) + except exception.SecurityGroupNotFound as error: + raise exc.HTTPBadRequest(explanation=unicode(error)) + except rpc_common.RemoteError as err: + msg = "%(err_type)s: %(err_msg)s" % \ + {'err_type': err.exc_type, 'err_msg': err.value} raise exc.HTTPBadRequest(explanation=msg) - return password - - def _get_server_search_options(self): - """Return server search options allowed by non-admin""" - return ('reservation_id', 'name', 'local_zone_only', - 'status', 'image', 'flavor', 'changes-since') - - -class HeadersSerializer(wsgi.ResponseHeadersSerializer): - - def _add_server_location(self, response, data): - link = filter(lambda l: l['rel'] == 'self', data['server']['links']) - if link: - response.headers['Location'] = link[0]['href'] + # Let the caller deal with unhandled exceptions. - def create(self, response, data): - if 'server' in data: - self._add_server_location(response, data) - response.status_int = 202 + # If the caller wanted a reservation_id, return it + if ret_resv_id: + return {'reservation_id': resv_id} - def delete(self, response, data): - response.status_int = 204 + server = self._view_builder.create(req, instances[0]) - def action(self, response, data): - # FIXME(jerdfelt): This is kind of a hack. Unfortunately the original - # action requested isn't available to us, so we need to look at the - # response to see if it looks like a rebuild response. - if data.get('server', {}).get('status') == 'REBUILD': - self._add_server_location(response, data) - response.status_int = 202 + if '_is_precooked' in server['server'].keys(): + del server['server']['_is_precooked'] + else: + server['server']['adminPass'] = password + robj = wsgi.ResponseObject(server) -class SecurityGroupsTemplateElement(xmlutil.TemplateElement): - def will_render(self, datum): - return 'security_groups' in datum + return self._add_location(robj) + def _delete(self, context, id): + instance = self._get_server(context, id) + if FLAGS.reclaim_instance_interval: + self.compute_api.soft_delete(context, instance) + else: + self.compute_api.delete(context, instance) -def make_fault(elem): - fault = xmlutil.SubTemplateElement(elem, 'fault', selector='fault') - fault.set('code') - fault.set('created') - msg = xmlutil.SubTemplateElement(fault, 'message') - msg.text = 'message' - det = xmlutil.SubTemplateElement(fault, 'details') - det.text = 'details' + @wsgi.serializers(xml=ServerTemplate) + @scheduler_api.redirect_handler + def update(self, req, id, body): + """Update server then pass on to version-specific controller""" + if len(req.body) == 0: + raise exc.HTTPUnprocessableEntity() + if not body: + raise exc.HTTPUnprocessableEntity() -def make_server(elem, detailed=False): - elem.set('name') - elem.set('id') + ctxt = req.environ['nova.context'] + update_dict = {} - if detailed: - 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') + if 'name' in body['server']: + name = body['server']['name'] + self._validate_server_name(name) + update_dict['display_name'] = name.strip() - # Attach image node - image = xmlutil.SubTemplateElement(elem, 'image', selector='image') - image.set('id') - xmlutil.make_links(image, 'links') + if 'accessIPv4' in body['server']: + access_ipv4 = body['server']['accessIPv4'] + update_dict['access_ip_v4'] = access_ipv4.strip() - # Attach flavor node - flavor = xmlutil.SubTemplateElement(elem, 'flavor', selector='flavor') - flavor.set('id') - xmlutil.make_links(flavor, 'links') + if 'accessIPv6' in body['server']: + access_ipv6 = body['server']['accessIPv6'] + update_dict['access_ip_v6'] = access_ipv6.strip() - # Attach fault node - make_fault(elem) + if 'auto_disk_config' in body['server']: + auto_disk_config = utils.bool_from_str( + body['server']['auto_disk_config']) + update_dict['auto_disk_config'] = auto_disk_config - # Attach metadata node - elem.append(common.MetadataTemplate()) + instance = self.compute_api.routing_get(ctxt, id) - # Attach addresses node - elem.append(ips.AddressesTemplate()) + try: + self.compute_api.update(ctxt, instance, **update_dict) + except exception.NotFound: + raise exc.HTTPNotFound() - # Attach security groups node - secgrps = SecurityGroupsTemplateElement('security_groups') - elem.append(secgrps) - secgrp = xmlutil.SubTemplateElement(secgrps, 'security_group', - selector='security_groups') - secgrp.set('name') + instance.update(update_dict) - xmlutil.make_links(elem, 'links') + self._add_instance_faults(ctxt, [instance]) + return self._view_builder.show(req, instance) + @wsgi.response(202) + @wsgi.serializers(xml=FullServerTemplate) + @wsgi.deserializers(xml=ActionDeserializer) + @exception.novaclient_converter + @scheduler_api.redirect_handler + def action(self, req, id, body): + """Multi-purpose method used to take actions on a server""" + _actions = { + 'changePassword': self._action_change_password, + 'reboot': self._action_reboot, + 'resize': self._action_resize, + 'confirmResize': self._action_confirm_resize, + 'revertResize': self._action_revert_resize, + 'rebuild': self._action_rebuild, + 'createImage': self._action_create_image, + } -server_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM} + for key in body: + if key in _actions: + return _actions[key](body, req, id) + else: + msg = _("There is no such server action: %s") % (key,) + raise exc.HTTPBadRequest(explanation=msg) + msg = _("Invalid request body") + raise exc.HTTPBadRequest(explanation=msg) -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) + def _action_confirm_resize(self, input_dict, req, id): + context = req.environ['nova.context'] + instance = self._get_server(context, id) + try: + self.compute_api.confirm_resize(context, instance) + except exception.MigrationNotFound: + msg = _("Instance has not been resized.") + raise exc.HTTPBadRequest(explanation=msg) + except Exception, e: + LOG.exception(_("Error in confirm-resize %s"), e) + raise exc.HTTPBadRequest() + return exc.HTTPNoContent() + def _action_revert_resize(self, input_dict, req, id): + context = req.environ['nova.context'] + instance = self._get_server(context, id) + try: + self.compute_api.revert_resize(context, instance) + except exception.MigrationNotFound: + msg = _("Instance has not been resized.") + raise exc.HTTPBadRequest(explanation=msg) + except Exception, e: + LOG.exception(_("Error in revert-resize %s"), e) + raise exc.HTTPBadRequest() + return webob.Response(status_int=202) -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) + def _action_reboot(self, input_dict, req, id): + if 'reboot' in input_dict and 'type' in input_dict['reboot']: + valid_reboot_types = ['HARD', 'SOFT'] + reboot_type = input_dict['reboot']['type'].upper() + if not valid_reboot_types.count(reboot_type): + msg = _("Argument 'type' for reboot is not HARD or SOFT") + LOG.exception(msg) + raise exc.HTTPBadRequest(explanation=msg) + else: + msg = _("Missing argument 'type' for reboot") + LOG.exception(msg) + raise exc.HTTPBadRequest(explanation=msg) + context = req.environ['nova.context'] + instance = self._get_server(context, id) -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) + try: + self.compute_api.reboot(context, instance, reboot_type) + except Exception, e: + LOG.exception(_("Error in reboot %s"), e) + raise exc.HTTPUnprocessableEntity() + return webob.Response(status_int=202) + def _resize(self, req, instance_id, flavor_id): + """Begin the resize process with given instance/flavor.""" + context = req.environ["nova.context"] + instance = self._get_server(context, instance_id) -class ServerAdminPassTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('server') - root.set('adminPass') - return xmlutil.SlaveTemplate(root, 1, nsmap=server_nsmap) + try: + self.compute_api.resize(context, instance, flavor_id) + except exception.FlavorNotFound: + msg = _("Unable to locate requested flavor.") + raise exc.HTTPBadRequest(explanation=msg) + except exception.CannotResizeToSameSize: + msg = _("Resize requires a change in size.") + raise exc.HTTPBadRequest(explanation=msg) + return webob.Response(status_int=202) -class ServerXMLSerializer(xmlutil.XMLTemplateSerializer): - def index(self): - return MinimalServersTemplate() + @wsgi.response(204) + @exception.novaclient_converter + @scheduler_api.redirect_handler + def delete(self, req, id): + """ Destroys a server """ + try: + self._delete(req.environ['nova.context'], id) + except exception.NotFound: + raise exc.HTTPNotFound() + except exception.InstanceInvalidState as state_error: + state = state_error.kwargs.get("state") + msg = _("Unable to delete instance when %s") % state + raise exc.HTTPConflict(explanation=msg) - def detail(self): - return ServersTemplate() + def _get_key_name(self, req, body): + if 'server' in body: + try: + return body['server'].get('key_name') + except AttributeError: + msg = _("Malformed server entity") + raise exc.HTTPBadRequest(explanation=msg) - def show(self): - return ServerTemplate() + def _image_ref_from_req_data(self, data): + try: + return data['server']['imageRef'] + except (TypeError, KeyError): + msg = _("Missing imageRef attribute") + raise exc.HTTPBadRequest(explanation=msg) - def update(self): - return ServerTemplate() + def _flavor_id_from_req_data(self, data): + try: + flavor_ref = data['server']['flavorRef'] + except (TypeError, KeyError): + msg = _("Missing flavorRef attribute") + raise exc.HTTPBadRequest(explanation=msg) - def create(self): - master = ServerTemplate() - master.attach(ServerAdminPassTemplate()) - return master + return common.get_id_from_href(flavor_ref) - def action(self): - return self.create() + def _action_change_password(self, input_dict, req, id): + context = req.environ['nova.context'] + if (not 'changePassword' in input_dict + or not 'adminPass' in input_dict['changePassword']): + msg = _("No adminPass was specified") + raise exc.HTTPBadRequest(explanation=msg) + password = input_dict['changePassword']['adminPass'] + if not isinstance(password, basestring) or password == '': + msg = _("Invalid adminPass") + raise exc.HTTPBadRequest(explanation=msg) + server = self._get_server(context, id) + self.compute_api.set_admin_password(context, server, password) + return webob.Response(status_int=202) + def _limit_items(self, items, req): + return common.limited_by_marker(items, req) -class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): - """ - Deserializer to handle xml-formatted server create requests. + def _validate_metadata(self, metadata): + """Ensure that we can work with the metadata given.""" + try: + metadata.iteritems() + except AttributeError as ex: + msg = _("Unable to parse metadata key/value pairs.") + LOG.debug(msg) + raise exc.HTTPBadRequest(explanation=msg) - Handles standard server attributes as well as optional metadata - and personality attributes - """ + def _action_resize(self, input_dict, req, id): + """ Resizes a given instance to the flavor size requested """ + try: + flavor_ref = input_dict["resize"]["flavorRef"] + if not flavor_ref: + msg = _("Resize request has invalid 'flavorRef' attribute.") + raise exc.HTTPBadRequest(explanation=msg) + except (KeyError, TypeError): + msg = _("Resize requests require 'flavorRef' attribute.") + raise exc.HTTPBadRequest(explanation=msg) - metadata_deserializer = common.MetadataXMLDeserializer() + return self._resize(req, id, flavor_ref) - def action(self, string): - dom = minidom.parseString(string) - action_node = dom.childNodes[0] - action_name = action_node.tagName + def _action_rebuild(self, info, request, instance_id): + """Rebuild an instance with the given attributes""" + try: + body = info['rebuild'] + except (KeyError, TypeError): + raise exc.HTTPBadRequest(_("Invalid request body")) - action_deserializer = { - 'createImage': self._action_create_image, - 'changePassword': self._action_change_password, - 'reboot': self._action_reboot, - 'rebuild': self._action_rebuild, - 'resize': self._action_resize, - 'confirmResize': self._action_confirm_resize, - 'revertResize': self._action_revert_resize, - }.get(action_name, self.default) + try: + image_href = body["imageRef"] + except (KeyError, TypeError): + msg = _("Could not parse imageRef from request.") + raise exc.HTTPBadRequest(explanation=msg) - action_data = action_deserializer(action_node) + try: + password = body['adminPass'] + except (KeyError, TypeError): + password = utils.generate_password(FLAGS.password_length) - return {'body': {action_name: action_data}} + context = request.environ['nova.context'] + instance = self._get_server(context, instance_id) - def _action_create_image(self, node): - return self._deserialize_image_action(node, ('name',)) + attr_map = { + 'personality': 'files_to_inject', + 'name': 'display_name', + 'accessIPv4': 'access_ip_v4', + 'accessIPv6': 'access_ip_v6', + 'metadata': 'metadata', + } - def _action_change_password(self, node): - if not node.hasAttribute("adminPass"): - raise AttributeError("No adminPass was specified in request") - return {"adminPass": node.getAttribute("adminPass")} + kwargs = {} - def _action_reboot(self, node): - if not node.hasAttribute("type"): - raise AttributeError("No reboot type was specified in request") - return {"type": node.getAttribute("type")} + for request_attribute, instance_attribute in attr_map.items(): + try: + kwargs[instance_attribute] = body[request_attribute] + except (KeyError, TypeError): + pass - def _action_rebuild(self, node): - rebuild = {} - if node.hasAttribute("name"): - rebuild['name'] = node.getAttribute("name") + self._validate_metadata(kwargs.get('metadata', {})) - metadata_node = self.find_first_child_named(node, "metadata") - if metadata_node is not None: - rebuild["metadata"] = self.extract_metadata(metadata_node) + if 'files_to_inject' in kwargs: + personality = kwargs['files_to_inject'] + kwargs['files_to_inject'] = self._get_injected_files(personality) - personality = self._extract_personality(node) - if personality is not None: - rebuild["personality"] = personality + try: + self.compute_api.rebuild(context, + instance, + image_href, + password, + **kwargs) - if not node.hasAttribute("imageRef"): - raise AttributeError("No imageRef was specified in request") - rebuild["imageRef"] = node.getAttribute("imageRef") + except exception.RebuildRequiresActiveInstance: + msg = _("Instance must be active to rebuild.") + raise exc.HTTPConflict(explanation=msg) + except exception.InstanceNotFound: + msg = _("Instance could not be found") + raise exc.HTTPNotFound(explanation=msg) - return rebuild + instance = self._get_server(context, instance_id) - def _action_resize(self, node): - if not node.hasAttribute("flavorRef"): - raise AttributeError("No flavorRef was specified in request") - return {"flavorRef": node.getAttribute("flavorRef")} + self._add_instance_faults(context, [instance]) + view = self._view_builder.show(request, instance) - def _action_confirm_resize(self, node): - return None + # Add on the adminPass attribute since the view doesn't do it + view['server']['adminPass'] = password - def _action_revert_resize(self, node): - return None + robj = wsgi.ResponseObject(view) + return self._add_location(robj) - def _deserialize_image_action(self, node, allowed_attributes): - data = {} - for attribute in allowed_attributes: - value = node.getAttribute(attribute) - if value: - data[attribute] = value - metadata_node = self.find_first_child_named(node, 'metadata') - if metadata_node is not None: - metadata = self.metadata_deserializer.extract_metadata( - metadata_node) - data['metadata'] = metadata - return data + @common.check_snapshots_enabled + def _action_create_image(self, input_dict, req, instance_id): + """Snapshot a server instance.""" + context = req.environ['nova.context'] + entity = input_dict.get("createImage", {}) - def create(self, string): - """Deserialize an xml-formatted server create request""" - dom = minidom.parseString(string) - server = self._extract_server(dom) - return {'body': {'server': server}} + try: + image_name = entity["name"] - def _extract_server(self, node): - """Marshal the server attribute of a parsed request""" - server = {} - server_node = self.find_first_child_named(node, 'server') + except KeyError: + msg = _("createImage entity requires name attribute") + raise exc.HTTPBadRequest(explanation=msg) - attributes = ["name", "imageRef", "flavorRef", "adminPass", - "accessIPv4", "accessIPv6"] - for attr in attributes: - if server_node.getAttribute(attr): - server[attr] = server_node.getAttribute(attr) + except TypeError: + msg = _("Malformed createImage entity") + raise exc.HTTPBadRequest(explanation=msg) - metadata_node = self.find_first_child_named(server_node, "metadata") - if metadata_node is not None: - server["metadata"] = self.extract_metadata(metadata_node) + # preserve link to server in image properties + server_ref = os.path.join(req.application_url, 'servers', instance_id) + props = {'instance_ref': server_ref} - personality = self._extract_personality(server_node) - if personality is not None: - server["personality"] = personality + metadata = entity.get('metadata', {}) + common.check_img_metadata_quota_limit(context, metadata) + try: + props.update(metadata) + except ValueError: + msg = _("Invalid metadata") + raise exc.HTTPBadRequest(explanation=msg) - networks = self._extract_networks(server_node) - if networks is not None: - server["networks"] = networks + instance = self._get_server(context, instance_id) - security_groups = self._extract_security_groups(server_node) - if security_groups is not None: - server["security_groups"] = security_groups + try: + image = self.compute_api.snapshot(context, + instance, + image_name, + extra_properties=props) + except exception.InstanceBusy: + msg = _("Server is currently creating an image. Please wait.") + raise webob.exc.HTTPConflict(explanation=msg) - auto_disk_config = server_node.getAttribute('auto_disk_config') - if auto_disk_config: - server['auto_disk_config'] = utils.bool_from_str(auto_disk_config) + # build location of newly-created image entity + image_id = str(image['id']) + image_ref = os.path.join(req.application_url, + context.project_id, + 'images', + image_id) - return server + resp = webob.Response(status_int=202) + resp.headers['Location'] = image_ref + return resp - def _extract_personality(self, server_node): - """Marshal the personality attribute of a parsed request""" - node = self.find_first_child_named(server_node, "personality") - if node is not None: - personality = [] - for file_node in self.find_children_named(node, "file"): - item = {} - if file_node.hasAttribute("path"): - item["path"] = file_node.getAttribute("path") - item["contents"] = self.extract_text(file_node) - personality.append(item) - return personality - else: - return None + def _get_server_admin_password(self, server): + """ Determine the admin password for a server on creation """ + password = server.get('adminPass') - def _extract_networks(self, server_node): - """Marshal the networks attribute of a parsed request""" - node = self.find_first_child_named(server_node, "networks") - if node is not None: - networks = [] - for network_node in self.find_children_named(node, - "network"): - item = {} - if network_node.hasAttribute("uuid"): - item["uuid"] = network_node.getAttribute("uuid") - if network_node.hasAttribute("fixed_ip"): - item["fixed_ip"] = network_node.getAttribute("fixed_ip") - networks.append(item) - return networks - else: - return None + if password is None: + return utils.generate_password(FLAGS.password_length) + if not isinstance(password, basestring) or password == '': + msg = _("Invalid adminPass") + raise exc.HTTPBadRequest(explanation=msg) + return password - def _extract_security_groups(self, server_node): - """Marshal the security_groups attribute of a parsed request""" - node = self.find_first_child_named(server_node, "security_groups") - if node is not None: - security_groups = [] - for sg_node in self.find_children_named(node, "security_group"): - item = {} - name_node = self.find_first_child_named(sg_node, "name") - if name_node: - item["name"] = self.extract_text(name_node) - security_groups.append(item) - return security_groups - else: - return None + def _get_server_search_options(self): + """Return server search options allowed by non-admin""" + return ('reservation_id', 'name', 'local_zone_only', + 'status', 'image', 'flavor', 'changes-since') def create_resource(): - headers_serializer = HeadersSerializer() - body_serializers = {'application/xml': ServerXMLSerializer()} - serializer = wsgi.ResponseSerializer(body_serializers, headers_serializer) - body_deserializers = {'application/xml': ServerXMLDeserializer()} - deserializer = wsgi.RequestDeserializer(body_deserializers) - return wsgi.Resource(Controller(), deserializer, serializer) + return wsgi.Resource(Controller()) def remove_invalid_options(context, search_options, allowed_search_options): diff --git a/nova/api/openstack/v2/versions.py b/nova/api/openstack/v2/versions.py index 4f5dcc0b0..45b84f7c2 100644 --- a/nova/api/openstack/v2/versions.py +++ b/nova/api/openstack/v2/versions.py @@ -57,103 +57,53 @@ VERSIONS = { } -class Versions(wsgi.Resource): - def __init__(self): - metadata = { - "attributes": { - "version": ["status", "id"], - "link": ["rel", "href"], - } - } +class MediaTypesTemplateElement(xmlutil.TemplateElement): + def will_render(self, datum): + return 'media-types' in datum - headers_serializer = VersionsHeadersSerializer() - body_serializers = { - 'application/atom+xml': VersionsAtomSerializer(metadata=metadata), - 'application/xml': VersionsXMLSerializer(metadata=metadata), - } - serializer = wsgi.ResponseSerializer( - body_serializers=body_serializers, - headers_serializer=headers_serializer) +def make_version(elem): + elem.set('id') + elem.set('status') + elem.set('updated') - deserializer = VersionsRequestDeserializer() + mts = MediaTypesTemplateElement('media-types') + elem.append(mts) - wsgi.Resource.__init__(self, None, serializer=serializer, - deserializer=deserializer) + mt = xmlutil.SubTemplateElement(mts, 'media-type', selector='media-types') + mt.set('base') + mt.set('type') - def dispatch(self, request, *args): - """Respond to a request for all OpenStack API versions.""" - builder = views_versions.get_view_builder(request) - if request.path == '/': - # List Versions - return builder.build_versions(VERSIONS) - else: - # Versions Multiple Choice - return builder.build_choices(VERSIONS, request) + xmlutil.make_links(elem, 'links') -class VersionV2(object): - def show(self, req): - builder = views_versions.get_view_builder(req) - return builder.build_version(VERSIONS['v2.0']) +version_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM} -class VersionsRequestDeserializer(wsgi.RequestDeserializer): - def get_action_args(self, request_environment): - """Parse dictionary created by routes library.""" - args = {} - if request_environment['PATH_INFO'] == '/': - args['action'] = 'index' - else: - args['action'] = 'multi' - - return args +class VersionTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('version', selector='version') + make_version(root) + return xmlutil.MasterTemplate(root, 1, nsmap=version_nsmap) -class VersionsXMLSerializer(wsgi.XMLDictSerializer): - - def _populate_version(self, version_node, version): - version_node.set('id', version['id']) - version_node.set('status', version['status']) - if 'updated' in version: - version_node.set('updated', version['updated']) - if 'media-types' in version: - media_types = etree.SubElement(version_node, 'media-types') - for mtype in version['media-types']: - elem = etree.SubElement(media_types, 'media-type') - elem.set('base', mtype['base']) - elem.set('type', mtype['type']) - for link in version.get('links', []): - elem = etree.SubElement(version_node, - '{%s}link' % xmlutil.XMLNS_ATOM) - elem.set('rel', link['rel']) - elem.set('href', link['href']) - if 'type' in link: - elem.set('type', link['type']) +class VersionsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('versions') + elem = xmlutil.SubTemplateElement(root, 'version', selector='versions') + make_version(elem) + return xmlutil.MasterTemplate(root, 1, nsmap=version_nsmap) - NSMAP = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM} - def index(self, data): - root = etree.Element('versions', nsmap=self.NSMAP) - for version in data['versions']: - version_elem = etree.SubElement(root, 'version') - self._populate_version(version_elem, version) - return self._to_xml(root) +class ChoicesTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('choices') + elem = xmlutil.SubTemplateElement(root, 'version', selector='choices') + make_version(elem) + return xmlutil.MasterTemplate(root, 1, nsmap=version_nsmap) - def show(self, data): - root = etree.Element('version', nsmap=self.NSMAP) - self._populate_version(root, data['version']) - return self._to_xml(root) - def multi(self, data): - root = etree.Element('choices', nsmap=self.NSMAP) - for version in data['choices']: - version_elem = etree.SubElement(root, 'version') - self._populate_version(version_elem, version) - return self._to_xml(root) - - -class VersionsAtomSerializer(wsgi.XMLDictSerializer): +class AtomSerializer(wsgi.XMLDictSerializer): NSMAP = {None: xmlutil.XMLNS_ATOM} @@ -228,32 +178,59 @@ class VersionsAtomSerializer(wsgi.XMLDictSerializer): version['updated']) return entry - def index(self, data): + +class VersionsAtomSerializer(AtomSerializer): + def default(self, data): versions = data['versions'] feed_id = self._get_base_url(versions[0]['links'][0]['href']) feed = self._create_feed(versions, 'Available API Versions', feed_id) return self._to_xml(feed) - def show(self, data): + +class VersionAtomSerializer(AtomSerializer): + def default(self, data): version = data['version'] feed_id = version['links'][0]['href'] feed = self._create_feed([version], 'About This Version', feed_id) return self._to_xml(feed) -class VersionsHeadersSerializer(wsgi.ResponseHeadersSerializer): - def multi(self, response, data): - response.status_int = 300 +class Versions(wsgi.Resource): + def __init__(self): + super(Versions, self).__init__(None) + @wsgi.serializers(xml=VersionsTemplate, + atom=VersionsAtomSerializer) + def index(self, req): + """Return all versions.""" + builder = views_versions.get_view_builder(req) + return builder.build_versions(VERSIONS) -def create_resource(): - body_serializers = { - 'application/xml': VersionsXMLSerializer(), - 'application/atom+xml': VersionsAtomSerializer(), - } - serializer = wsgi.ResponseSerializer(body_serializers) + @wsgi.serializers(xml=ChoicesTemplate) + @wsgi.response(300) + def multi(self, req): + """Return multiple choices.""" + builder = views_versions.get_view_builder(req) + return builder.build_choices(VERSIONS, req) + + def get_action_args(self, request_environment): + """Parse dictionary created by routes library.""" + args = {} + if request_environment['PATH_INFO'] == '/': + args['action'] = 'index' + else: + args['action'] = 'multi' + + return args - deserializer = wsgi.RequestDeserializer() - return wsgi.Resource(VersionV2(), serializer=serializer, - deserializer=deserializer) +class VersionV2(object): + @wsgi.serializers(xml=VersionTemplate, + atom=VersionAtomSerializer) + def show(self, req): + builder = views_versions.get_view_builder(req) + return builder.build_version(VERSIONS['v2.0']) + + +def create_resource(): + return wsgi.Resource(VersionV2()) diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 59cc67bb7..bc19a0ae6 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -49,6 +49,14 @@ SUPPORTED_CONTENT_TYPES = ( 'application/vnd.openstack.compute+xml', ) +_MEDIA_TYPE_MAP = { + 'application/vnd.openstack.compute+json': 'json', + 'application/json': 'json', + 'application/vnd.openstack.compute+xml': 'xml', + 'application/xml': 'xml', + 'application/atom+xml': 'atom', +} + class Request(webob.Request): """Add some Openstack API-specific logic to the base webob.Request.""" @@ -499,6 +507,13 @@ class LazySerializationMiddleware(wsgi.Middleware): response = req.get_response(self.application) + # See if we're using the simple serialization driver + simple_serial = req.environ.get('nova.simple_serial') + if simple_serial is not None: + body_obj = utils.loads(response.body) + response.body = simple_serial.serialize(body_obj) + return response + # See if there's a serializer... serializer = req.environ.get('nova.serializer') if serializer is None: @@ -515,6 +530,173 @@ class LazySerializationMiddleware(wsgi.Middleware): return response +def serializers(**serializers): + """Attaches serializers to a method. + + This decorator associates a dictionary of serializers with a + method. Note that the function attributes are directly + manipulated; the method is not wrapped. + """ + + def decorator(func): + if not hasattr(func, 'wsgi_serializers'): + func.wsgi_serializers = {} + func.wsgi_serializers.update(serializers) + return func + return decorator + + +def deserializers(**deserializers): + """Attaches deserializers to a method. + + This decorator associates a dictionary of deserializers with a + method. Note that the function attributes are directly + manipulated; the method is not wrapped. + """ + + def decorator(func): + if not hasattr(func, 'wsgi_deserializers'): + func.wsgi_deserializers = {} + func.wsgi_deserializers.update(deserializers) + return func + return decorator + + +def response(code): + """Attaches response code to a method. + + This decorator associates a response code with a method. Note + that the function attributes are directly manipulated; the method + is not wrapped. + """ + + def decorator(func): + func.wsgi_code = code + return func + return decorator + + +class ResponseObject(object): + """Bundles a response object with appropriate serializers. + + Object that app methods may return in order to bind alternate + serializers with a response object to be serialized. Its use is + optional. + """ + + def __init__(self, obj, code=None, **serializers): + """Binds serializers with an object. + + Takes keyword arguments akin to the @serializer() decorator + for specifying serializers. Serializers specified will be + given preference over default serializers or method-specific + serializers on return. + """ + + self.obj = obj + self.serializers = serializers + self._default_code = 200 + self._code = code + self._headers = {} + + def __getitem__(self, key): + """Retrieves a header with the given name.""" + + return self._headers[key.lower()] + + def __setitem__(self, key, value): + """Sets a header with the given name to the given value.""" + + self._headers[key.lower()] = value + + def __delitem__(self, key): + """Deletes the header with the given name.""" + + del self._headers[key.lower()] + + def _bind_method_serializers(self, meth_serializers): + """Binds method serializers with the response object. + + Binds the method serializers with the response object. + Serializers specified to the constructor will take precedence + over serializers specified to this method. + + :param meth_serializers: A dictionary with keys mapping to + response types and values containing + serializer objects. + """ + + # We can't use update because that would be the wrong + # precedence + for mtype, serializer in meth_serializers.items(): + self.serializers.setdefault(mtype, serializer) + + def get_serializer(self, content_type, default_serializers=None): + """Returns the serializer for the wrapped object. + + Returns the serializer for the wrapped object subject to the + indicated content type. If no serializer matching the content + type is attached, an appropriate serializer drawn from the + default serializers will be used. If no appropriate + serializer is available, raises InvalidContentType. + """ + + default_serializers = default_serializers or {} + + try: + mtype = _MEDIA_TYPE_MAP.get(content_type, content_type) + if mtype in self.serializers: + return self.serializers[mtype] + else: + return default_serializers[mtype] + except (KeyError, TypeError): + raise exception.InvalidContentType(content_type=content_type) + + def serialize(self, request, content_type, default_serializers=None): + """Serializes the wrapped object. + + Utility method for serializing the wrapped object. Returns a + webob.Response object. + """ + + serializer = self.get_serializer(content_type, default_serializers)() + + response = webob.Response() + response.status_int = self.code + for hdr, value in self._headers.items(): + response.headers[hdr] = value + response.headers['Content-Type'] = content_type + if self.obj is not None: + # TODO(Vek): When lazy serialization is retired, so can + # this inner 'if'... + lazy_serialize = request.environ.get('nova.lazy_serialize', False) + if lazy_serialize: + response.body = utils.dumps(self.obj) + request.environ['nova.simple_serial'] = serializer + # NOTE(Vek): Temporary ugly hack to support xml + # templates in extensions, until we can + # fold extensions into Resource and do away + # with lazy serialization... + if _MEDIA_TYPE_MAP.get(content_type) == 'xml': + request.environ['nova.template'] = serializer + else: + response.body = serializer.serialize(self.obj) + + return response + + @property + def code(self): + """Retrieve the response status.""" + + return self._code or self._default_code + + @property + def headers(self): + """Retrieve the headers.""" + + return self._headers.copy() + + class Resource(wsgi.Application): """WSGI app that handles (de)serialization and controller dispatch. @@ -531,7 +713,8 @@ class Resource(wsgi.Application): """ - def __init__(self, controller, deserializer=None, serializer=None): + def __init__(self, controller, deserializer=None, serializer=None, + **deserializers): """ :param controller: object that implement methods created by routes lib :param deserializer: object that can serialize the output of a @@ -541,18 +724,100 @@ class Resource(wsgi.Application): """ self.controller = controller - self.deserializer = deserializer or RequestDeserializer() - self.serializer = serializer or ResponseSerializer() + self.deserializer = deserializer + self.serializer = serializer + + default_deserializers = dict(xml=XMLDeserializer, + json=JSONDeserializer) + default_deserializers.update(deserializers) + + self.default_deserializers = default_deserializers + self.default_serializers = dict(xml=XMLDictSerializer, + json=JSONDictSerializer) + + def get_action_args(self, request_environment): + """Parse dictionary created by routes library.""" + + # NOTE(Vek): Check for get_action_args() override in the + # controller + if hasattr(self.controller, 'get_action_args'): + return self.controller.get_action_args(request_environment) + + try: + args = request_environment['wsgiorg.routing_args'][1].copy() + except (KeyError, IndexError, AttributeError): + return {} + + try: + del args['controller'] + except KeyError: + pass + + try: + del args['format'] + except KeyError: + pass + + return args + + def get_body(self, request): + try: + content_type = request.get_content_type() + except exception.InvalidContentType: + LOG.debug(_("Unrecognized Content-Type provided in request")) + return None, '' + + if not content_type: + LOG.debug(_("No Content-Type provided in request")) + return None, '' + + if len(request.body) <= 0: + LOG.debug(_("Empty body provided in request")) + return None, '' + + return content_type, request.body + + def deserialize(self, meth, content_type, body): + meth_deserializers = getattr(meth, 'wsgi_deserializers', {}) + try: + mtype = _MEDIA_TYPE_MAP.get(content_type, content_type) + if mtype in meth_deserializers: + deserializer = meth_deserializers[mtype] + else: + deserializer = self.default_deserializers[mtype] + except (KeyError, TypeError): + raise exception.InvalidContentType(content_type=content_type) + + return deserializer().deserialize(body) @webob.dec.wsgify(RequestClass=Request) def __call__(self, request): """WSGI method that controls (de)serialization and method dispatch.""" LOG.info("%(method)s %(url)s" % {"method": request.method, - "url": request.url}) + "url": request.url}) + + # Identify the action, its arguments, and the requested + # content type + action_args = self.get_action_args(request.environ) + action = action_args.pop('action', None) + content_type, body = self.get_body(request) + accept = request.best_match_content_type() + # Get the implementing method try: - action, args, accept = self.deserializer.deserialize(request) + meth = self.get_method(request, action) + except (AttributeError, TypeError): + return Fault(webob.exc.HTTPNotFound()) + + # Now, deserialize the request body... + try: + if self.deserializer is not None: + contents = self.deserializer.deserialize_body(request, action) + elif content_type is not None: + contents = self.deserialize(meth, content_type, body) + else: + contents = {} except exception.InvalidContentType: msg = _("Unsupported Content-Type") return Fault(webob.exc.HTTPBadRequest(explanation=msg)) @@ -560,26 +825,51 @@ class Resource(wsgi.Application): msg = _("Malformed request body") return Fault(webob.exc.HTTPBadRequest(explanation=msg)) - project_id = args.pop("project_id", None) + # Update the action args + action_args.update(contents) + + project_id = action_args.pop("project_id", None) if 'nova.context' in request.environ and project_id: request.environ['nova.context'].project_id = project_id + response = None try: - action_result = self.dispatch(request, action, args) + action_result = self.dispatch(meth, request, action_args) + except TypeError as ex: + LOG.exception(ex) + response = Fault(webob.exc.HTTPBadRequest()) except Fault as ex: LOG.info(_("Fault thrown: %s"), unicode(ex)) - action_result = ex + response = ex except webob.exc.HTTPException as ex: LOG.info(_("HTTP exception thrown: %s"), unicode(ex)) - action_result = Fault(ex) + response = Fault(ex) + + if not response: + # No exceptions; convert action_result into a + # ResponseObject + resp_obj = None + if type(action_result) is dict or action_result is None: + resp_obj = ResponseObject(action_result) + elif isinstance(action_result, ResponseObject): + resp_obj = action_result + else: + response = action_result + + if resp_obj: + if self.serializer: + response = self.serializer.serialize(request, + resp_obj.obj, + accept, + action=action) + else: + serializers = getattr(meth, 'wsgi_serializers', {}) + resp_obj._bind_method_serializers(serializers) + if hasattr(meth, 'wsgi_code'): + resp_obj._default_code = meth.wsgi_code - if isinstance(action_result, dict) or action_result is None: - response = self.serializer.serialize(request, - action_result, - accept, - action=action) - else: - response = action_result + response = resp_obj.serialize(request, accept, + self.default_serializers) try: msg_dict = dict(url=request.url, status=response.status_int) @@ -592,15 +882,17 @@ class Resource(wsgi.Application): return response - def dispatch(self, request, action, action_args): - """Find action-spefic method on controller and call it.""" + def get_method(self, request, action): + """Look up the action-specific method.""" - controller_method = getattr(self.controller, action) - try: - return controller_method(req=request, **action_args) - except TypeError as exc: - LOG.exception(exc) - return Fault(webob.exc.HTTPBadRequest()) + if self.controller is None: + return getattr(self, action) + return getattr(self.controller, action) + + def dispatch(self, method, request, action_args): + """Dispatch a call to the action-specific method.""" + + return method(req=request, **action_args) class Controller(object): diff --git a/nova/api/openstack/xmlutil.py b/nova/api/openstack/xmlutil.py index cc689e2c8..90861ad4f 100644 --- a/nova/api/openstack/xmlutil.py +++ b/nova/api/openstack/xmlutil.py @@ -526,6 +526,7 @@ class Template(object): self.root = root.unwrap() if root is not None else None self.nsmap = nsmap or {} + self.serialize_options = dict(encoding='UTF-8', xml_declaration=True) def _serialize(self, parent, obj, siblings, nsmap=None): """Internal serialization. @@ -585,6 +586,9 @@ class Template(object): if elem is None: return '' + for k, v in self.serialize_options.items(): + kwargs.setdefault(k, v) + # Serialize it into XML return etree.tostring(elem, *args, **kwargs) diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index 5aea8275d..af9497a5a 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -404,16 +404,252 @@ class ResourceTest(test.TestCase): def index(self, req, pants=None): return pants - resource = wsgi.Resource(Controller()) - actual = resource.dispatch(None, 'index', {'pants': 'off'}) + controller = Controller() + resource = wsgi.Resource(controller) + method = resource.get_method(None, 'index') + actual = resource.dispatch(method, None, {'pants': 'off'}) expected = 'off' self.assertEqual(actual, expected) - def test_dispatch_unknown_controller_action(self): + def test_get_method_unknown_controller_action(self): class Controller(object): def index(self, req, pants=None): return pants - resource = wsgi.Resource(Controller()) - self.assertRaises(AttributeError, resource.dispatch, - None, 'create', {}) + controller = Controller() + resource = wsgi.Resource(controller) + self.assertRaises(AttributeError, resource.get_method, + None, 'create') + + def test_get_action_args(self): + class Controller(object): + def index(self, req, pants=None): + return pants + + controller = Controller() + resource = wsgi.Resource(controller) + + env = { + 'wsgiorg.routing_args': [None, { + 'controller': None, + 'format': None, + 'action': 'update', + 'id': 12, + }], + } + + expected = {'action': 'update', 'id': 12} + + self.assertEqual(resource.get_action_args(env), expected) + + def test_get_body_bad_content(self): + class Controller(object): + def index(self, req, pants=None): + return pants + + controller = Controller() + resource = wsgi.Resource(controller) + + request = wsgi.Request.blank('/', method='POST') + request.headers['Content-Type'] = 'application/none' + request.body = 'foo' + + content_type, body = resource.get_body(request) + self.assertEqual(content_type, None) + self.assertEqual(body, '') + + def test_get_body_no_content_type(self): + class Controller(object): + def index(self, req, pants=None): + return pants + + controller = Controller() + resource = wsgi.Resource(controller) + + request = wsgi.Request.blank('/', method='POST') + request.body = 'foo' + + content_type, body = resource.get_body(request) + self.assertEqual(content_type, None) + self.assertEqual(body, '') + + def test_get_body_no_content_body(self): + class Controller(object): + def index(self, req, pants=None): + return pants + + controller = Controller() + resource = wsgi.Resource(controller) + + request = wsgi.Request.blank('/', method='POST') + request.headers['Content-Type'] = 'application/json' + request.body = '' + + content_type, body = resource.get_body(request) + self.assertEqual(content_type, None) + self.assertEqual(body, '') + + def test_get_body(self): + class Controller(object): + def index(self, req, pants=None): + return pants + + controller = Controller() + resource = wsgi.Resource(controller) + + request = wsgi.Request.blank('/', method='POST') + request.headers['Content-Type'] = 'application/json' + request.body = 'foo' + + content_type, body = resource.get_body(request) + self.assertEqual(content_type, 'application/json') + self.assertEqual(body, 'foo') + + def test_deserialize_badtype(self): + class Controller(object): + def index(self, req, pants=None): + return pants + + controller = Controller() + resource = wsgi.Resource(controller) + self.assertRaises(exception.InvalidContentType, + resource.deserialize, + controller.index, 'application/none', 'foo') + + def test_deserialize_default(self): + class JSONDeserializer(object): + def deserialize(self, body): + return 'json' + + class XMLDeserializer(object): + def deserialize(self, body): + return 'xml' + + class Controller(object): + @wsgi.deserializers(xml=XMLDeserializer) + def index(self, req, pants=None): + return pants + + controller = Controller() + resource = wsgi.Resource(controller, json=JSONDeserializer) + + obj = resource.deserialize(controller.index, 'application/json', 'foo') + self.assertEqual(obj, 'json') + + def test_deserialize_decorator(self): + class JSONDeserializer(object): + def deserialize(self, body): + return 'json' + + class XMLDeserializer(object): + def deserialize(self, body): + return 'xml' + + class Controller(object): + @wsgi.deserializers(xml=XMLDeserializer) + def index(self, req, pants=None): + return pants + + controller = Controller() + resource = wsgi.Resource(controller, json=JSONDeserializer) + + obj = resource.deserialize(controller.index, 'application/xml', 'foo') + self.assertEqual(obj, 'xml') + + +class ResponseObjectTest(test.TestCase): + def test_default_code(self): + robj = wsgi.ResponseObject({}) + self.assertEqual(robj.code, 200) + + def test_modified_code(self): + robj = wsgi.ResponseObject({}) + robj._default_code = 202 + self.assertEqual(robj.code, 202) + + def test_override_default_code(self): + robj = wsgi.ResponseObject({}, code=404) + self.assertEqual(robj.code, 404) + + def test_override_modified_code(self): + robj = wsgi.ResponseObject({}, code=404) + robj._default_code = 202 + self.assertEqual(robj.code, 404) + + def test_set_header(self): + robj = wsgi.ResponseObject({}) + robj['Header'] = 'foo' + self.assertEqual(robj.headers, {'header': 'foo'}) + + def test_get_header(self): + robj = wsgi.ResponseObject({}) + robj['Header'] = 'foo' + self.assertEqual(robj['hEADER'], 'foo') + + def test_del_header(self): + robj = wsgi.ResponseObject({}) + robj['Header'] = 'foo' + del robj['hEADER'] + self.assertFalse('header' in robj.headers) + + def test_header_isolation(self): + robj = wsgi.ResponseObject({}) + robj['Header'] = 'foo' + hdrs = robj.headers + hdrs['hEADER'] = 'bar' + self.assertEqual(robj['hEADER'], 'foo') + + def test_default_serializers(self): + robj = wsgi.ResponseObject({}) + self.assertEqual(robj.serializers, {}) + + def test_bind_serializers(self): + robj = wsgi.ResponseObject({}, json='foo') + robj._bind_method_serializers(dict(xml='bar', json='baz')) + self.assertEqual(robj.serializers, dict(xml='bar', json='foo')) + + def test_get_serializer(self): + robj = wsgi.ResponseObject({}, json='json', xml='xml', atom='atom') + for content_type, mtype in wsgi._MEDIA_TYPE_MAP.items(): + serializer = robj.get_serializer(content_type) + self.assertEqual(serializer, mtype) + + def test_get_serializer_defaults(self): + robj = wsgi.ResponseObject({}) + default_serializers = dict(json='json', xml='xml', atom='atom') + for content_type, mtype in wsgi._MEDIA_TYPE_MAP.items(): + self.assertRaises(exception.InvalidContentType, + robj.get_serializer, content_type) + serializer = robj.get_serializer(content_type, + default_serializers) + self.assertEqual(serializer, mtype) + + def test_serialize(self): + class JSONSerializer(object): + def serialize(self, obj): + return 'json' + + class XMLSerializer(object): + def serialize(self, obj): + return 'xml' + + class AtomSerializer(object): + def serialize(self, obj): + return 'atom' + + robj = wsgi.ResponseObject({}, code=202, + json=JSONSerializer, + xml=XMLSerializer, + atom=AtomSerializer) + robj['X-header1'] = 'header1' + robj['X-header2'] = 'header2' + + for content_type, mtype in wsgi._MEDIA_TYPE_MAP.items(): + request = wsgi.Request.blank('/tests/123') + response = robj.serialize(request, content_type) + + self.assertEqual(response.headers['Content-Type'], content_type) + self.assertEqual(response.headers['X-header1'], 'header1') + self.assertEqual(response.headers['X-header2'], 'header2') + self.assertEqual(response.status_int, 202) + self.assertEqual(response.body, mtype) diff --git a/nova/tests/api/openstack/test_xmlutil.py b/nova/tests/api/openstack/test_xmlutil.py index cb2ba4e35..de15cfe1f 100644 --- a/nova/tests/api/openstack/test_xmlutil.py +++ b/nova/tests/api/openstack/test_xmlutil.py @@ -786,7 +786,8 @@ class XMLTemplateSerializerTest(test.TestCase): class MiscellaneousXMLUtilTests(test.TestCase): def test_make_flat_dict(self): - expected_xml = ('foobar') + expected_xml = ("\n" + 'foobar') root = xmlutil.make_flat_dict('wrapper') tmpl = xmlutil.MasterTemplate(root, 1) result = tmpl.serialize(dict(wrapper=dict(a='foo', b='bar'))) diff --git a/nova/tests/api/openstack/v2/contrib/test_cloudpipe.py b/nova/tests/api/openstack/v2/contrib/test_cloudpipe.py index 8dfac4765..736cfc8f6 100644 --- a/nova/tests/api/openstack/v2/contrib/test_cloudpipe.py +++ b/nova/tests/api/openstack/v2/contrib/test_cloudpipe.py @@ -190,14 +190,10 @@ class CloudpipeTest(test.TestCase): class CloudpipesXMLSerializerTest(test.TestCase): - def setUp(self): - super(CloudpipesXMLSerializerTest, self).setUp() - self.serializer = cloudpipe.CloudpipeSerializer() - self.deserializer = wsgi.XMLDeserializer() - def test_default_serializer(self): + serializer = cloudpipe.CloudpipeTemplate() exemplar = dict(cloudpipe=dict(instance_id='1234-1234-1234-1234')) - text = self.serializer.serialize(exemplar) + text = serializer.serialize(exemplar) tree = etree.fromstring(text) self.assertEqual('cloudpipe', tree.tag) for child in tree: @@ -205,6 +201,7 @@ class CloudpipesXMLSerializerTest(test.TestCase): self.assertEqual(child.text, exemplar['cloudpipe'][child.tag]) def test_index_serializer(self): + serializer = cloudpipe.CloudpipesTemplate() exemplar = dict(cloudpipes=[ dict(cloudpipe=dict( project_id='1234', @@ -218,20 +215,21 @@ class CloudpipesXMLSerializerTest(test.TestCase): public_ip='4.3.2.1', public_port='123', state='pending'))]) - text = self.serializer.serialize(exemplar, 'index') + text = serializer.serialize(exemplar) tree = etree.fromstring(text) self.assertEqual('cloudpipes', tree.tag) self.assertEqual(len(exemplar['cloudpipes']), len(tree)) - for idx, cloudpipe in enumerate(tree): - self.assertEqual('cloudpipe', cloudpipe.tag) + for idx, cl_pipe in enumerate(tree): + self.assertEqual('cloudpipe', cl_pipe.tag) kp_data = exemplar['cloudpipes'][idx]['cloudpipe'] - for child in cloudpipe: + for child in cl_pipe: self.assertTrue(child.tag in kp_data) self.assertEqual(child.text, kp_data[child.tag]) def test_deserializer(self): + deserializer = wsgi.XMLDeserializer() exemplar = dict(cloudpipe=dict(project_id='4321')) intext = ("\n" '4321') - result = self.deserializer.deserialize(intext)['body'] + result = deserializer.deserialize(intext)['body'] self.assertEqual(result, exemplar) diff --git a/nova/tests/api/openstack/v2/contrib/test_flavors_extra_specs.py b/nova/tests/api/openstack/v2/contrib/test_flavors_extra_specs.py index 645f6be58..8660e4fc0 100644 --- a/nova/tests/api/openstack/v2/contrib/test_flavors_extra_specs.py +++ b/nova/tests/api/openstack/v2/contrib/test_flavors_extra_specs.py @@ -168,7 +168,7 @@ class FlavorsExtraSpecsTest(test.TestCase): class FlavorsExtraSpecsXMLSerializerTest(test.TestCase): def test_serializer(self): - serializer = flavorextraspecs.ExtraSpecsSerializer() + serializer = flavorextraspecs.ExtraSpecsTemplate() expected = ("\n" 'value1') text = serializer.serialize(dict(extra_specs={"key1": "value1"})) diff --git a/nova/tests/api/openstack/v2/contrib/test_floating_ip_dns.py b/nova/tests/api/openstack/v2/contrib/test_floating_ip_dns.py index d658fa542..3c7af5afb 100644 --- a/nova/tests/api/openstack/v2/contrib/test_floating_ip_dns.py +++ b/nova/tests/api/openstack/v2/contrib/test_floating_ip_dns.py @@ -186,7 +186,7 @@ class FloatingIpDNSTest(test.TestCase): class FloatingIpDNSSerializerTest(test.TestCase): def test_default_serializer(self): - serializer = floating_ip_dns.FloatingIPDNSSerializer() + serializer = floating_ip_dns.FloatingIPDNSTemplate() text = serializer.serialize(dict( dns_entry=dict( ip=testaddress, @@ -202,11 +202,11 @@ class FloatingIpDNSSerializerTest(test.TestCase): self.assertEqual(name, tree.get('name')) def test_index_serializer(self): - serializer = floating_ip_dns.FloatingIPDNSSerializer() + serializer = floating_ip_dns.ZonesTemplate() text = serializer.serialize(dict( zones=[ dict(zone=zone), - dict(zone=zone2)]), 'index') + dict(zone=zone2)])) tree = etree.fromstring(text) self.assertEqual('zones', tree.tag) @@ -215,7 +215,7 @@ class FloatingIpDNSSerializerTest(test.TestCase): self.assertEqual(zone2, tree[1].get('zone')) def test_show_serializer(self): - serializer = floating_ip_dns.FloatingIPDNSSerializer() + serializer = floating_ip_dns.FloatingIPDNSsTemplate() text = serializer.serialize(dict( dns_entries=[ dict(ip=testaddress, @@ -225,7 +225,7 @@ class FloatingIpDNSSerializerTest(test.TestCase): dict(ip=testaddress2, type='C', zone=zone, - name=name2)]), 'show') + name=name2)])) tree = etree.fromstring(text) self.assertEqual('dns_entries', tree.tag) diff --git a/nova/tests/api/openstack/v2/contrib/test_floating_ips.py b/nova/tests/api/openstack/v2/contrib/test_floating_ips.py index 762cf4b78..d1a0d5795 100644 --- a/nova/tests/api/openstack/v2/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/v2/contrib/test_floating_ips.py @@ -273,7 +273,7 @@ class FloatingIpTest(test.TestCase): class FloatingIpSerializerTest(test.TestCase): def test_default_serializer(self): - serializer = floating_ips.FloatingIPSerializer() + serializer = floating_ips.FloatingIPTemplate() text = serializer.serialize(dict( floating_ip=dict( instance_id=1, @@ -290,7 +290,7 @@ class FloatingIpSerializerTest(test.TestCase): self.assertEqual('1', tree.get('id')) def test_index_serializer(self): - serializer = floating_ips.FloatingIPSerializer() + serializer = floating_ips.FloatingIPsTemplate() text = serializer.serialize(dict( floating_ips=[ dict(instance_id=1, @@ -300,7 +300,7 @@ class FloatingIpSerializerTest(test.TestCase): dict(instance_id=None, ip='10.10.10.11', fixed_ip=None, - id=2)]), 'index') + id=2)])) tree = etree.fromstring(text) diff --git a/nova/tests/api/openstack/v2/contrib/test_hosts.py b/nova/tests/api/openstack/v2/contrib/test_hosts.py index a537ff2f3..a954890ba 100644 --- a/nova/tests/api/openstack/v2/contrib/test_hosts.py +++ b/nova/tests/api/openstack/v2/contrib/test_hosts.py @@ -126,11 +126,11 @@ class HostTestCase(test.TestCase): class HostSerializerTest(test.TestCase): def setUp(self): super(HostSerializerTest, self).setUp() - self.serializer = os_hosts.HostSerializer() self.deserializer = os_hosts.HostDeserializer() def test_index_serializer(self): - text = self.serializer.serialize(HOST_LIST, 'index') + serializer = os_hosts.HostIndexTemplate() + text = serializer.serialize(HOST_LIST) tree = etree.fromstring(text) @@ -145,7 +145,8 @@ class HostSerializerTest(test.TestCase): def test_update_serializer(self): exemplar = dict(host='host_c1', status='enabled') - text = self.serializer.serialize(exemplar, 'update') + serializer = os_hosts.HostUpdateTemplate() + text = serializer.serialize(exemplar) tree = etree.fromstring(text) @@ -155,7 +156,8 @@ class HostSerializerTest(test.TestCase): def test_action_serializer(self): exemplar = dict(host='host_c1', power_action='reboot') - text = self.serializer.serialize(exemplar) + serializer = os_hosts.HostActionTemplate() + text = serializer.serialize(exemplar) tree = etree.fromstring(text) @@ -167,6 +169,6 @@ class HostSerializerTest(test.TestCase): exemplar = dict(status='enabled', foo='bar') intext = ("\n" 'enabledbar') - result = self.deserializer.deserialize(intext, action='update') + result = self.deserializer.deserialize(intext) self.assertEqual(dict(body=exemplar), result) diff --git a/nova/tests/api/openstack/v2/contrib/test_keypairs.py b/nova/tests/api/openstack/v2/contrib/test_keypairs.py index 3a27b1d0b..7056dc6c8 100644 --- a/nova/tests/api/openstack/v2/contrib/test_keypairs.py +++ b/nova/tests/api/openstack/v2/contrib/test_keypairs.py @@ -118,7 +118,6 @@ class KeypairsTest(test.TestCase): class KeypairsXMLSerializerTest(test.TestCase): def setUp(self): super(KeypairsXMLSerializerTest, self).setUp() - self.serializer = keypairs.KeypairsSerializer() self.deserializer = wsgi.XMLDeserializer() def test_default_serializer(self): @@ -128,7 +127,8 @@ class KeypairsXMLSerializerTest(test.TestCase): fingerprint='fake_fingerprint', user_id='fake_user_id', name='fake_key_name')) - text = self.serializer.serialize(exemplar) + serializer = keypairs.KeypairTemplate() + text = serializer.serialize(exemplar) print text tree = etree.fromstring(text) @@ -148,7 +148,8 @@ class KeypairsXMLSerializerTest(test.TestCase): name='key2_name', public_key='key2_key', fingerprint='key2_fingerprint'))]) - text = self.serializer.serialize(exemplar, 'index') + serializer = keypairs.KeypairsTemplate() + text = serializer.serialize(exemplar) print text tree = etree.fromstring(text) diff --git a/nova/tests/api/openstack/v2/contrib/test_quotas.py b/nova/tests/api/openstack/v2/contrib/test_quotas.py index 104f44a82..b67bba850 100644 --- a/nova/tests/api/openstack/v2/contrib/test_quotas.py +++ b/nova/tests/api/openstack/v2/contrib/test_quotas.py @@ -137,7 +137,7 @@ class QuotaSetsTest(test.TestCase): class QuotaXMLSerializerTest(test.TestCase): def setUp(self): super(QuotaXMLSerializerTest, self).setUp() - self.serializer = quotas.QuotaSerializer() + self.serializer = quotas.QuotaTemplate() self.deserializer = wsgi.XMLDeserializer() def test_serializer(self): diff --git a/nova/tests/api/openstack/v2/contrib/test_security_groups.py b/nova/tests/api/openstack/v2/contrib/test_security_groups.py index b4ef608d8..85f061fa1 100644 --- a/nova/tests/api/openstack/v2/contrib/test_security_groups.py +++ b/nova/tests/api/openstack/v2/contrib/test_security_groups.py @@ -769,7 +769,7 @@ class TestSecurityGroupRulesXMLDeserializer(unittest.TestCase): tcp 10.0.0.0/24 """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "security_group_rule": { "parent_group_id": "12", @@ -791,7 +791,7 @@ class TestSecurityGroupRulesXMLDeserializer(unittest.TestCase): 10.0.0.0/24 """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "security_group_rule": { "parent_group_id": "12", @@ -814,7 +814,7 @@ class TestSecurityGroupXMLDeserializer(unittest.TestCase): test """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "security_group": { "name": "test", @@ -827,7 +827,7 @@ class TestSecurityGroupXMLDeserializer(unittest.TestCase): serial_request = """ """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "security_group": { "name": "test", @@ -840,7 +840,7 @@ class TestSecurityGroupXMLDeserializer(unittest.TestCase): test """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "security_group": { "description": "test", @@ -852,9 +852,9 @@ class TestSecurityGroupXMLDeserializer(unittest.TestCase): class TestSecurityGroupXMLSerializer(unittest.TestCase): def setUp(self): self.namespace = wsgi.XMLNS_V11 - tmp = security_groups.SecurityGroupRulesXMLSerializer() - self.rule_serializer = tmp - self.group_serializer = security_groups.SecurityGroupXMLSerializer() + self.rule_serializer = security_groups.SecurityGroupRuleTemplate() + self.index_serializer = security_groups.SecurityGroupsTemplate() + self.default_serializer = security_groups.SecurityGroupTemplate() def _tag(self, elem): tagname = elem.tag @@ -949,7 +949,7 @@ class TestSecurityGroupXMLSerializer(unittest.TestCase): tenant_id='tenant', rules=rules) sg_group = dict(security_group=raw_group) - text = self.group_serializer.serialize(sg_group) + text = self.default_serializer.serialize(sg_group) print text tree = etree.fromstring(text) @@ -1002,7 +1002,7 @@ class TestSecurityGroupXMLSerializer(unittest.TestCase): tenant_id='tenant2', rules=rules[2:4])] sg_groups = dict(security_groups=groups) - text = self.group_serializer.serialize(sg_groups, 'index') + text = self.index_serializer.serialize(sg_groups) print text tree = etree.fromstring(text) diff --git a/nova/tests/api/openstack/v2/contrib/test_server_action_list.py b/nova/tests/api/openstack/v2/contrib/test_server_action_list.py index c6a185e56..d943376b3 100644 --- a/nova/tests/api/openstack/v2/contrib/test_server_action_list.py +++ b/nova/tests/api/openstack/v2/contrib/test_server_action_list.py @@ -15,8 +15,12 @@ import datetime import json +import unittest + +from lxml import etree from nova.api.openstack import v2 +from nova.api.openstack.v2.contrib import server_action_list from nova.api.openstack.v2 import extensions from nova.api.openstack import wsgi import nova.compute @@ -39,10 +43,10 @@ def fake_instance_get(self, _context, instance_uuid): return {'uuid': instance_uuid} -class ServerDiagnosticsTest(test.TestCase): +class ServerActionsTest(test.TestCase): def setUp(self): - super(ServerDiagnosticsTest, self).setUp() + super(ServerActionsTest, self).setUp() self.flags(allow_admin_api=True) self.flags(verbose=True) self.stubs.Set(nova.compute.API, 'get_actions', fake_get_actions) @@ -63,3 +67,37 @@ class ServerDiagnosticsTest(test.TestCase): {'action': 'reboot', 'error': 'Failed!', 'created_at': str(dt)}, ]} self.assertEqual(output, expected) + + +class TestServerActionsXMLSerializer(unittest.TestCase): + namespace = wsgi.XMLNS_V11 + + def _tag(self, elem): + tagname = elem.tag + self.assertEqual(tagname[0], '{') + tmp = tagname.partition('}') + namespace = tmp[0][1:] + self.assertEqual(namespace, self.namespace) + return tmp[2] + + def test_index_serializer(self): + serializer = server_action_list.ServerActionsTemplate() + exemplar = [dict( + created_at=datetime.datetime.now(), + action='foo', + error='quxx'), + dict( + created_at=datetime.datetime.now(), + action='bar', + error='xxuq')] + text = serializer.serialize(dict(actions=exemplar)) + + print text + tree = etree.fromstring(text) + + self.assertEqual('actions', self._tag(tree)) + self.assertEqual(len(tree), len(exemplar)) + for idx, child in enumerate(tree): + self.assertEqual('action', self._tag(child)) + for field in ('created_at', 'action', 'error'): + self.assertEqual(str(exemplar[idx][field]), child.get(field)) diff --git a/nova/tests/api/openstack/v2/contrib/test_server_diagnostics.py b/nova/tests/api/openstack/v2/contrib/test_server_diagnostics.py index f7d6932d0..2e2850f32 100644 --- a/nova/tests/api/openstack/v2/contrib/test_server_diagnostics.py +++ b/nova/tests/api/openstack/v2/contrib/test_server_diagnostics.py @@ -14,8 +14,12 @@ # under the License. import json +import unittest + +from lxml import etree from nova.api.openstack import v2 +from nova.api.openstack.v2.contrib import server_diagnostics from nova.api.openstack.v2 import extensions from nova.api.openstack import wsgi import nova.compute @@ -53,3 +57,30 @@ class ServerDiagnosticsTest(test.TestCase): res = req.get_response(self.app) output = json.loads(res.body) self.assertEqual(output, {'data': 'Some diagnostic info'}) + + +class TestServerDiagnosticsXMLSerializer(unittest.TestCase): + namespace = wsgi.XMLNS_V11 + + def _tag(self, elem): + tagname = elem.tag + self.assertEqual(tagname[0], '{') + tmp = tagname.partition('}') + namespace = tmp[0][1:] + self.assertEqual(namespace, self.namespace) + return tmp[2] + + def test_index_serializer(self): + serializer = server_diagnostics.ServerDiagnosticsTemplate() + exemplar = dict(diag1='foo', diag2='bar') + text = serializer.serialize(exemplar) + + print text + tree = etree.fromstring(text) + + self.assertEqual('diagnostics', self._tag(tree)) + self.assertEqual(len(tree), len(exemplar)) + for child in tree: + tag = self._tag(child) + self.assertTrue(tag in exemplar) + self.assertEqual(child.text, exemplar[tag]) diff --git a/nova/tests/api/openstack/v2/contrib/test_simple_tenant_usage.py b/nova/tests/api/openstack/v2/contrib/test_simple_tenant_usage.py index 5d36518a6..1a1fb0ca5 100644 --- a/nova/tests/api/openstack/v2/contrib/test_simple_tenant_usage.py +++ b/nova/tests/api/openstack/v2/contrib/test_simple_tenant_usage.py @@ -176,10 +176,6 @@ class SimpleTenantUsageTest(test.TestCase): class SimpleTenantUsageSerializerTest(test.TestCase): - def setUp(self): - super(SimpleTenantUsageSerializerTest, self).setUp() - self.serializer = simple_tenant_usage.SimpleTenantUsageSerializer() - def _verify_server_usage(self, raw_usage, tree): self.assertEqual('server_usage', tree.tag) @@ -212,6 +208,7 @@ class SimpleTenantUsageSerializerTest(test.TestCase): self.assertEqual(len(not_seen), 0) def test_serializer_show(self): + serializer = simple_tenant_usage.SimpleTenantUsageTemplate() today = datetime.datetime.now() yesterday = today - datetime.timedelta(days=1) raw_usage = dict( @@ -249,7 +246,7 @@ class SimpleTenantUsageSerializerTest(test.TestCase): ], ) tenant_usage = dict(tenant_usage=raw_usage) - text = self.serializer.serialize(tenant_usage, 'show') + text = serializer.serialize(tenant_usage) print text tree = etree.fromstring(text) @@ -257,6 +254,7 @@ class SimpleTenantUsageSerializerTest(test.TestCase): self._verify_tenant_usage(raw_usage, tree) def test_serializer_index(self): + serializer = simple_tenant_usage.SimpleTenantUsagesTemplate() today = datetime.datetime.now() yesterday = today - datetime.timedelta(days=1) raw_usages = [dict( @@ -329,7 +327,7 @@ class SimpleTenantUsageSerializerTest(test.TestCase): ), ] tenant_usages = dict(tenant_usages=raw_usages) - text = self.serializer.serialize(tenant_usages, 'index') + text = serializer.serialize(tenant_usages) print text tree = etree.fromstring(text) diff --git a/nova/tests/api/openstack/v2/contrib/test_snapshots.py b/nova/tests/api/openstack/v2/contrib/test_snapshots.py index a04c9b734..4db0c3948 100644 --- a/nova/tests/api/openstack/v2/contrib/test_snapshots.py +++ b/nova/tests/api/openstack/v2/contrib/test_snapshots.py @@ -251,7 +251,7 @@ class SnapshotSerializerTest(test.TestCase): self.assertEqual(str(snap[attr]), tree.get(attr)) def test_snapshot_show_create_serializer(self): - serializer = volumes.SnapshotSerializer() + serializer = volumes.SnapshotTemplate() raw_snapshot = dict( id='snap_id', status='snap_status', @@ -261,7 +261,7 @@ class SnapshotSerializerTest(test.TestCase): displayDescription='snap_desc', volumeId='vol_id', ) - text = serializer.serialize(dict(snapshot=raw_snapshot), 'show') + text = serializer.serialize(dict(snapshot=raw_snapshot)) print text tree = etree.fromstring(text) @@ -269,7 +269,7 @@ class SnapshotSerializerTest(test.TestCase): self._verify_snapshot(raw_snapshot, tree) def test_snapshot_index_detail_serializer(self): - serializer = volumes.SnapshotSerializer() + serializer = volumes.SnapshotsTemplate() raw_snapshots = [dict( id='snap1_id', status='snap1_status', @@ -288,7 +288,7 @@ class SnapshotSerializerTest(test.TestCase): displayDescription='snap2_desc', volumeId='vol2_id', )] - text = serializer.serialize(dict(snapshots=raw_snapshots), 'index') + text = serializer.serialize(dict(snapshots=raw_snapshots)) print text tree = etree.fromstring(text) diff --git a/nova/tests/api/openstack/v2/contrib/test_users.py b/nova/tests/api/openstack/v2/contrib/test_users.py index 24a14e250..ace243f58 100644 --- a/nova/tests/api/openstack/v2/contrib/test_users.py +++ b/nova/tests/api/openstack/v2/contrib/test_users.py @@ -116,9 +116,8 @@ class UsersTest(test.TestCase): class TestUsersXMLSerializer(test.TestCase): - serializer = users.UserXMLSerializer() - def test_index(self): + serializer = users.UsersTemplate() fixture = {'users': [{'id': 'id1', 'name': 'guy1', 'secret': 'secret1', @@ -128,7 +127,7 @@ class TestUsersXMLSerializer(test.TestCase): 'secret': 'secret2', 'admin': True}]} - output = self.serializer.serialize(fixture, 'index') + output = serializer.serialize(fixture) res_tree = etree.XML(output) self.assertEqual(res_tree.tag, 'users') @@ -139,12 +138,13 @@ class TestUsersXMLSerializer(test.TestCase): self.assertEqual(res_tree[1].get('id'), 'id2') def test_show(self): + serializer = users.UserTemplate() fixture = {'user': {'id': 'id2', 'name': 'guy2', 'secret': 'secret2', 'admin': True}} - output = self.serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) res_tree = etree.XML(output) self.assertEqual(res_tree.tag, 'user') diff --git a/nova/tests/api/openstack/v2/contrib/test_virtual_interfaces.py b/nova/tests/api/openstack/v2/contrib/test_virtual_interfaces.py index 47f085b00..ad3c53723 100644 --- a/nova/tests/api/openstack/v2/contrib/test_virtual_interfaces.py +++ b/nova/tests/api/openstack/v2/contrib/test_virtual_interfaces.py @@ -61,7 +61,7 @@ class ServerVirtualInterfaceSerializerTest(test.TestCase): def setUp(self): super(ServerVirtualInterfaceSerializerTest, self).setUp() self.namespace = wsgi.XMLNS_V11 - self.serializer = virtual_interfaces.VirtualInterfaceSerializer() + self.serializer = virtual_interfaces.VirtualInterfaceTemplate() def _tag(self, elem): tagname = elem.tag diff --git a/nova/tests/api/openstack/v2/contrib/test_volume_types.py b/nova/tests/api/openstack/v2/contrib/test_volume_types.py index 4343a6d04..7b2b9cad0 100644 --- a/nova/tests/api/openstack/v2/contrib/test_volume_types.py +++ b/nova/tests/api/openstack/v2/contrib/test_volume_types.py @@ -166,10 +166,6 @@ class VolumeTypesApiTest(test.TestCase): class VolumeTypesSerializerTest(test.TestCase): - def setUp(self): - super(VolumeTypesSerializerTest, self).setUp() - self.serializer = volumetypes.VolumeTypesSerializer() - def _verify_volume_type(self, vtype, tree): self.assertEqual('volume_type', tree.tag) self.assertEqual(vtype['name'], tree.get('name')) @@ -185,9 +181,11 @@ class VolumeTypesSerializerTest(test.TestCase): self.assertEqual(len(seen), 0) def test_index_serializer(self): + serializer = volumetypes.VolumeTypesTemplate() + # Just getting some input data vtypes = return_volume_types_get_all_types(None) - text = self.serializer.serialize(vtypes, 'index') + text = serializer.serialize(vtypes) print text tree = etree.fromstring(text) @@ -200,8 +198,10 @@ class VolumeTypesSerializerTest(test.TestCase): self._verify_volume_type(vtypes[name], child) def test_voltype_serializer(self): + serializer = volumetypes.VolumeTypeTemplate() + vtype = stub_volume_type(1) - text = self.serializer.serialize(dict(volume_type=vtype)) + text = serializer.serialize(dict(volume_type=vtype)) print text tree = etree.fromstring(text) diff --git a/nova/tests/api/openstack/v2/contrib/test_volume_types_extra_specs.py b/nova/tests/api/openstack/v2/contrib/test_volume_types_extra_specs.py index 41b50118f..e8f932345 100644 --- a/nova/tests/api/openstack/v2/contrib/test_volume_types_extra_specs.py +++ b/nova/tests/api/openstack/v2/contrib/test_volume_types_extra_specs.py @@ -165,15 +165,12 @@ class VolumeTypesExtraSpecsTest(test.TestCase): class VolumeTypeExtraSpecsSerializerTest(test.TestCase): - def setUp(self): - super(VolumeTypeExtraSpecsSerializerTest, self).setUp() - self.serializer = volumetypes.VolumeTypeExtraSpecsSerializer() - def test_index_create_serializer(self): + serializer = volumetypes.VolumeTypeExtraSpecsTemplate() + # Just getting some input data extra_specs = stub_volume_type_extra_specs() - text = self.serializer.serialize(dict(extra_specs=extra_specs), - 'index') + text = serializer.serialize(dict(extra_specs=extra_specs)) print text tree = etree.fromstring(text) @@ -188,8 +185,10 @@ class VolumeTypeExtraSpecsSerializerTest(test.TestCase): self.assertEqual(len(seen), 0) def test_update_show_serializer(self): + serializer = volumetypes.VolumeTypeExtraSpecTemplate() + exemplar = dict(key1='value1') - text = self.serializer.serialize(exemplar, 'update') + text = serializer.serialize(exemplar) print text tree = etree.fromstring(text) diff --git a/nova/tests/api/openstack/v2/contrib/test_volumes.py b/nova/tests/api/openstack/v2/contrib/test_volumes.py index 6dc4fb87c..a5585bd64 100644 --- a/nova/tests/api/openstack/v2/contrib/test_volumes.py +++ b/nova/tests/api/openstack/v2/contrib/test_volumes.py @@ -120,13 +120,13 @@ class VolumeSerializerTest(test.TestCase): self.assertEqual(0, len(not_seen)) def test_attach_show_create_serializer(self): - serializer = volumes.VolumeAttachmentSerializer() + serializer = volumes.VolumeAttachmentTemplate() raw_attach = dict( id='vol_id', volumeId='vol_id', serverId='instance_uuid', device='/foo') - text = serializer.serialize(dict(volumeAttachment=raw_attach), 'show') + text = serializer.serialize(dict(volumeAttachment=raw_attach)) print text tree = etree.fromstring(text) @@ -135,7 +135,7 @@ class VolumeSerializerTest(test.TestCase): self._verify_volume_attachment(raw_attach, tree) def test_attach_index_serializer(self): - serializer = volumes.VolumeAttachmentSerializer() + serializer = volumes.VolumeAttachmentsTemplate() raw_attaches = [dict( id='vol_id1', volumeId='vol_id1', @@ -146,8 +146,7 @@ class VolumeSerializerTest(test.TestCase): volumeId='vol_id2', serverId='instance2_uuid', device='/foo2')] - text = serializer.serialize(dict(volumeAttachments=raw_attaches), - 'index') + text = serializer.serialize(dict(volumeAttachments=raw_attaches)) print text tree = etree.fromstring(text) @@ -159,7 +158,7 @@ class VolumeSerializerTest(test.TestCase): self._verify_volume_attachment(raw_attaches[idx], child) def test_volume_show_create_serializer(self): - serializer = volumes.VolumeSerializer() + serializer = volumes.VolumeTemplate() raw_volume = dict( id='vol_id', status='vol_status', @@ -180,7 +179,7 @@ class VolumeSerializerTest(test.TestCase): baz='quux', ), ) - text = serializer.serialize(dict(volume=raw_volume), 'show') + text = serializer.serialize(dict(volume=raw_volume)) print text tree = etree.fromstring(text) @@ -188,7 +187,7 @@ class VolumeSerializerTest(test.TestCase): self._verify_volume(raw_volume, tree) def test_volume_index_detail_serializer(self): - serializer = volumes.VolumeSerializer() + serializer = volumes.VolumesTemplate() raw_volumes = [dict( id='vol1_id', status='vol1_status', @@ -229,7 +228,7 @@ class VolumeSerializerTest(test.TestCase): bar='vol2_bar', ), )] - text = serializer.serialize(dict(volumes=raw_volumes), 'index') + text = serializer.serialize(dict(volumes=raw_volumes)) print text tree = etree.fromstring(text) diff --git a/nova/tests/api/openstack/v2/contrib/test_vsa.py b/nova/tests/api/openstack/v2/contrib/test_vsa.py index d48803bef..e313298f5 100644 --- a/nova/tests/api/openstack/v2/contrib/test_vsa.py +++ b/nova/tests/api/openstack/v2/contrib/test_vsa.py @@ -453,19 +453,14 @@ class VSADriveApiTest(VSAVolumeApiTest): class SerializerTestCommon(test.TestCase): - def setUp(self): - super(SerializerTestCommon, self).setUp() - self.serializer = self.serializer_class() - def _verify_attrs(self, obj, tree, attrs): for attr in attrs: self.assertEqual(str(obj[attr]), tree.get(attr)) class VsaSerializerTest(SerializerTestCommon): - serializer_class = vsa_ext.VsaSerializer - def test_serialize_show_create(self): + serializer = vsa_ext.VsaTemplate() exemplar = dict( id='vsa_id', name='vsa_name', @@ -477,7 +472,7 @@ class VsaSerializerTest(SerializerTestCommon): vcCount=24, driveCount=48, ipAddress='10.11.12.13') - text = self.serializer.serialize(dict(vsa=exemplar), 'show') + text = serializer.serialize(dict(vsa=exemplar)) print text tree = etree.fromstring(text) @@ -486,6 +481,7 @@ class VsaSerializerTest(SerializerTestCommon): self._verify_attrs(exemplar, tree, exemplar.keys()) def test_serialize_index_detail(self): + serializer = vsa_ext.VsaSetTemplate() exemplar = [dict( id='vsa1_id', name='vsa1_name', @@ -508,7 +504,7 @@ class VsaSerializerTest(SerializerTestCommon): vcCount=42, driveCount=84, ipAddress='11.12.13.14')] - text = self.serializer.serialize(dict(vsaSet=exemplar), 'index') + text = serializer.serialize(dict(vsaSet=exemplar)) print text tree = etree.fromstring(text) @@ -521,7 +517,8 @@ class VsaSerializerTest(SerializerTestCommon): class VsaVolumeSerializerTest(SerializerTestCommon): - serializer_class = vsa_ext.VsaVolumeSerializer + show_serializer = vsa_ext.VsaVolumeTemplate + index_serializer = vsa_ext.VsaVolumesTemplate object = 'volume' objects = 'volumes' @@ -550,6 +547,7 @@ class VsaVolumeSerializerTest(SerializerTestCommon): self.assertEqual(0, len(not_seen)) def test_show_create_serializer(self): + serializer = self.show_serializer() raw_volume = dict( id='vol_id', status='vol_status', @@ -571,7 +569,7 @@ class VsaVolumeSerializerTest(SerializerTestCommon): vsaId='vol_vsa_id', name='vol_vsa_name', ) - text = self.serializer.serialize({self.object: raw_volume}, 'show') + text = serializer.serialize({self.object: raw_volume}) print text tree = etree.fromstring(text) @@ -579,6 +577,7 @@ class VsaVolumeSerializerTest(SerializerTestCommon): self._verify_voldrive(raw_volume, tree) def test_index_detail_serializer(self): + serializer = self.index_serializer() raw_volumes = [dict( id='vol1_id', status='vol1_status', @@ -621,7 +620,7 @@ class VsaVolumeSerializerTest(SerializerTestCommon): vsaId='vol2_vsa_id', name='vol2_vsa_name', )] - text = self.serializer.serialize({self.objects: raw_volumes}, 'index') + text = serializer.serialize({self.objects: raw_volumes}) print text tree = etree.fromstring(text) @@ -633,14 +632,13 @@ class VsaVolumeSerializerTest(SerializerTestCommon): class VsaDriveSerializerTest(VsaVolumeSerializerTest): - serializer_class = vsa_ext.VsaDriveSerializer + show_serializer = vsa_ext.VsaDriveTemplate + index_serializer = vsa_ext.VsaDrivesTemplate object = 'drive' objects = 'drives' class VsaVPoolSerializerTest(SerializerTestCommon): - serializer_class = vsa_ext.VsaVPoolSerializer - def _verify_vpool(self, vpool, tree): self._verify_attrs(vpool, tree, ('id', 'vsaId', 'name', 'displayName', 'displayDescription', 'driveCount', @@ -656,6 +654,7 @@ class VsaVPoolSerializerTest(SerializerTestCommon): self.assertEqual(str(vpool['driveIds'][idx]), gr_child.text) def test_vpool_create_show_serializer(self): + serializer = vsa_ext.VsaVPoolTemplate() exemplar = dict( id='vpool_id', vsaId='vpool_vsa_id', @@ -669,7 +668,7 @@ class VsaVPoolSerializerTest(SerializerTestCommon): stripeWidth=2048, createTime=datetime.datetime.now(), status='available') - text = self.serializer.serialize(dict(vpool=exemplar), 'show') + text = serializer.serialize(dict(vpool=exemplar)) print text tree = etree.fromstring(text) @@ -677,6 +676,7 @@ class VsaVPoolSerializerTest(SerializerTestCommon): self._verify_vpool(exemplar, tree) def test_vpool_index_serializer(self): + serializer = vsa_ext.VsaVPoolsTemplate() exemplar = [dict( id='vpool1_id', vsaId='vpool1_vsa_id', @@ -703,7 +703,7 @@ class VsaVPoolSerializerTest(SerializerTestCommon): stripeWidth=256, createTime=datetime.datetime.now(), status='available')] - text = self.serializer.serialize(dict(vpools=exemplar), 'index') + text = serializer.serialize(dict(vpools=exemplar)) print text tree = etree.fromstring(text) diff --git a/nova/tests/api/openstack/v2/contrib/test_zones.py b/nova/tests/api/openstack/v2/contrib/test_zones.py index 79c03443a..a44a7c82e 100644 --- a/nova/tests/api/openstack/v2/contrib/test_zones.py +++ b/nova/tests/api/openstack/v2/contrib/test_zones.py @@ -201,9 +201,9 @@ class ZonesTest(test.TestCase): class TestZonesXMLSerializer(test.TestCase): - serializer = zones.ZonesXMLSerializer() - def test_select(self): + serializer = zones.WeightsTemplate() + key = 'c286696d887c9aa0611bbb3e2025a45a' encrypt = crypto.encryptor(key) @@ -213,7 +213,7 @@ class TestZonesXMLSerializer(test.TestCase): fixture = {'weights': {'blob': encrypt(json.dumps(item)), 'weight': item['weight']}} - output = self.serializer.serialize(fixture, 'select') + output = serializer.serialize(fixture) res_tree = etree.XML(output) self.assertEqual(res_tree.tag, '{%s}weights' % xmlutil.XMLNS_V10) @@ -242,9 +242,11 @@ class TestZonesXMLSerializer(test.TestCase): self.assertTrue(weight) def test_index(self): + serializer = zones.ZonesTemplate() + fixture = {'zones': zone_get_all_scheduler()} - output = self.serializer.serialize(fixture, 'index') + output = serializer.serialize(fixture) res_tree = etree.XML(output) self.assertEqual(res_tree.tag, '{%s}zones' % xmlutil.XMLNS_V10) @@ -253,6 +255,8 @@ class TestZonesXMLSerializer(test.TestCase): self.assertEqual(res_tree[1].tag, '{%s}zone' % xmlutil.XMLNS_V10) def test_show(self): + serializer = zones.ZoneTemplate() + zone = {'id': 1, 'api_url': 'http://example.com', 'name': 'darksecret', @@ -260,7 +264,7 @@ class TestZonesXMLSerializer(test.TestCase): 'cap2': 'c;d'} fixture = {'zone': zone} - output = self.serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) print repr(output) res_tree = etree.XML(output) diff --git a/nova/tests/api/openstack/v2/test_consoles.py b/nova/tests/api/openstack/v2/test_consoles.py index 801dab767..5953737a8 100644 --- a/nova/tests/api/openstack/v2/test_consoles.py +++ b/nova/tests/api/openstack/v2/test_consoles.py @@ -255,9 +255,6 @@ class ConsolesControllerTest(test.TestCase): class TestConsolesXMLSerializer(test.TestCase): - - serializer = consoles.ConsoleXMLSerializer() - def test_show(self): fixture = {'console': {'id': 20, 'password': 'fake_password', @@ -265,7 +262,7 @@ class TestConsolesXMLSerializer(test.TestCase): 'host': 'fake_hostname', 'console_type': 'fake_type'}} - output = self.serializer.serialize(fixture, 'show') + output = consoles.ConsoleTemplate().serialize(fixture) res_tree = etree.XML(output) self.assertEqual(res_tree.tag, 'console') @@ -281,7 +278,7 @@ class TestConsolesXMLSerializer(test.TestCase): {'console': {'id': 11, 'console_type': 'fake_type2'}}]} - output = self.serializer.serialize(fixture, 'index') + output = consoles.ConsolesTemplate().serialize(fixture) res_tree = etree.XML(output) self.assertEqual(res_tree.tag, 'consoles') diff --git a/nova/tests/api/openstack/v2/test_flavors.py b/nova/tests/api/openstack/v2/test_flavors.py index 786dfc90b..d70d581df 100644 --- a/nova/tests/api/openstack/v2/test_flavors.py +++ b/nova/tests/api/openstack/v2/test_flavors.py @@ -400,7 +400,7 @@ class FlavorsTest(test.TestCase): class FlavorsXMLSerializationTest(test.TestCase): def test_xml_declaration(self): - serializer = flavors.FlavorXMLSerializer() + serializer = flavors.FlavorTemplate() fixture = { "flavor": { @@ -424,13 +424,13 @@ class FlavorsXMLSerializationTest(test.TestCase): }, } - output = serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) print output has_dec = output.startswith("") self.assertTrue(has_dec) def test_show(self): - serializer = flavors.FlavorXMLSerializer() + serializer = flavors.FlavorTemplate() fixture = { "flavor": { @@ -454,7 +454,7 @@ class FlavorsXMLSerializationTest(test.TestCase): }, } - output = serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) print output root = etree.XML(output) xmlutil.validate_schema(root, 'flavor') @@ -470,7 +470,7 @@ class FlavorsXMLSerializationTest(test.TestCase): self.assertEqual(link_nodes[i].get(key), value) def test_show_handles_integers(self): - serializer = flavors.FlavorXMLSerializer() + serializer = flavors.FlavorTemplate() fixture = { "flavor": { @@ -494,7 +494,7 @@ class FlavorsXMLSerializationTest(test.TestCase): }, } - output = serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) print output root = etree.XML(output) xmlutil.validate_schema(root, 'flavor') @@ -510,7 +510,7 @@ class FlavorsXMLSerializationTest(test.TestCase): self.assertEqual(link_nodes[i].get(key), value) def test_detail(self): - serializer = flavors.FlavorXMLSerializer() + serializer = flavors.FlavorsTemplate() fixture = { "flavors": [ @@ -555,7 +555,7 @@ class FlavorsXMLSerializationTest(test.TestCase): ], } - output = serializer.serialize(fixture, 'detail') + output = serializer.serialize(fixture) print output root = etree.XML(output) xmlutil.validate_schema(root, 'flavors') @@ -574,7 +574,7 @@ class FlavorsXMLSerializationTest(test.TestCase): self.assertEqual(link_nodes[i].get(key), value) def test_index(self): - serializer = flavors.FlavorXMLSerializer() + serializer = flavors.MinimalFlavorsTemplate() fixture = { "flavors": [ @@ -619,7 +619,7 @@ class FlavorsXMLSerializationTest(test.TestCase): ], } - output = serializer.serialize(fixture, 'index') + output = serializer.serialize(fixture) print output root = etree.XML(output) xmlutil.validate_schema(root, 'flavors_index') @@ -638,13 +638,13 @@ class FlavorsXMLSerializationTest(test.TestCase): self.assertEqual(link_nodes[i].get(key), value) def test_index_empty(self): - serializer = flavors.FlavorXMLSerializer() + serializer = flavors.MinimalFlavorsTemplate() fixture = { "flavors": [], } - output = serializer.serialize(fixture, 'index') + output = serializer.serialize(fixture) print output root = etree.XML(output) xmlutil.validate_schema(root, 'flavors_index') diff --git a/nova/tests/api/openstack/v2/test_images.py b/nova/tests/api/openstack/v2/test_images.py index 40bacd10c..70c6db679 100644 --- a/nova/tests/api/openstack/v2/test_images.py +++ b/nova/tests/api/openstack/v2/test_images.py @@ -1005,7 +1005,7 @@ class ImageXMLSerializationTest(test.TestCase): IMAGE_BOOKMARK = 'http://localhost/fake/images/%s' def test_xml_declaration(self): - serializer = images.ImageXMLSerializer() + serializer = images.ImageTemplate() fixture = { 'image': { @@ -1044,12 +1044,12 @@ class ImageXMLSerializationTest(test.TestCase): }, } - output = serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) has_dec = output.startswith("") self.assertTrue(has_dec) def test_show(self): - serializer = images.ImageXMLSerializer() + serializer = images.ImageTemplate() fixture = { 'image': { @@ -1090,7 +1090,7 @@ class ImageXMLSerializationTest(test.TestCase): }, } - output = serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) root = etree.XML(output) xmlutil.validate_schema(root, 'image') image_dict = fixture['image'] @@ -1121,7 +1121,7 @@ class ImageXMLSerializationTest(test.TestCase): self.assertEqual(link_nodes[i].get(key), value) def test_show_zero_metadata(self): - serializer = images.ImageXMLSerializer() + serializer = images.ImageTemplate() fixture = { 'image': { @@ -1157,7 +1157,7 @@ class ImageXMLSerializationTest(test.TestCase): }, } - output = serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) root = etree.XML(output) xmlutil.validate_schema(root, 'image') image_dict = fixture['image'] @@ -1183,7 +1183,7 @@ class ImageXMLSerializationTest(test.TestCase): self.assertEqual(link_nodes[i].get(key), value) def test_show_image_no_metadata_key(self): - serializer = images.ImageXMLSerializer() + serializer = images.ImageTemplate() fixture = { 'image': { @@ -1218,7 +1218,7 @@ class ImageXMLSerializationTest(test.TestCase): }, } - output = serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) root = etree.XML(output) xmlutil.validate_schema(root, 'image') image_dict = fixture['image'] @@ -1244,7 +1244,7 @@ class ImageXMLSerializationTest(test.TestCase): self.assertEqual(link_nodes[i].get(key), value) def test_show_no_server(self): - serializer = images.ImageXMLSerializer() + serializer = images.ImageTemplate() fixture = { 'image': { @@ -1269,7 +1269,7 @@ class ImageXMLSerializationTest(test.TestCase): }, } - output = serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) root = etree.XML(output) xmlutil.validate_schema(root, 'image') image_dict = fixture['image'] @@ -1295,7 +1295,7 @@ class ImageXMLSerializationTest(test.TestCase): self.assertEqual(server_root, None) def test_show_with_min_ram(self): - serializer = images.ImageXMLSerializer() + serializer = images.ImageTemplate() fixture = { 'image': { @@ -1335,7 +1335,7 @@ class ImageXMLSerializationTest(test.TestCase): }, } - output = serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) root = etree.XML(output) xmlutil.validate_schema(root, 'image') image_dict = fixture['image'] @@ -1367,7 +1367,7 @@ class ImageXMLSerializationTest(test.TestCase): self.assertEqual(link_nodes[i].get(key), value) def test_show_with_min_disk(self): - serializer = images.ImageXMLSerializer() + serializer = images.ImageTemplate() fixture = { 'image': { @@ -1407,7 +1407,7 @@ class ImageXMLSerializationTest(test.TestCase): }, } - output = serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) root = etree.XML(output) xmlutil.validate_schema(root, 'image') image_dict = fixture['image'] @@ -1439,7 +1439,7 @@ class ImageXMLSerializationTest(test.TestCase): self.assertEqual(link_nodes[i].get(key), value) def test_index(self): - serializer = images.ImageXMLSerializer() + serializer = images.MinimalImagesTemplate() fixture = { 'images': [ @@ -1474,7 +1474,7 @@ class ImageXMLSerializationTest(test.TestCase): ] } - output = serializer.serialize(fixture, 'index') + output = serializer.serialize(fixture) root = etree.XML(output) xmlutil.validate_schema(root, 'images_index') image_elems = root.findall('{0}image'.format(NS)) @@ -1492,7 +1492,7 @@ class ImageXMLSerializationTest(test.TestCase): self.assertEqual(link_nodes[i].get(key), value) def test_index_with_links(self): - serializer = images.ImageXMLSerializer() + serializer = images.MinimalImagesTemplate() fixture = { 'images': [ @@ -1533,7 +1533,7 @@ class ImageXMLSerializationTest(test.TestCase): ], } - output = serializer.serialize(fixture, 'index') + output = serializer.serialize(fixture) root = etree.XML(output) xmlutil.validate_schema(root, 'images_index') image_elems = root.findall('{0}image'.format(NS)) @@ -1557,20 +1557,20 @@ class ImageXMLSerializationTest(test.TestCase): self.assertEqual(images_links[i].get(key), value) def test_index_zero_images(self): - serializer = images.ImageXMLSerializer() + serializer = images.MinimalImagesTemplate() fixtures = { 'images': [], } - output = serializer.serialize(fixtures, 'index') + output = serializer.serialize(fixtures) root = etree.XML(output) xmlutil.validate_schema(root, 'images_index') image_elems = root.findall('{0}image'.format(NS)) self.assertEqual(len(image_elems), 0) def test_detail(self): - serializer = images.ImageXMLSerializer() + serializer = images.ImagesTemplate() fixture = { 'images': [ @@ -1628,7 +1628,7 @@ class ImageXMLSerializationTest(test.TestCase): ] } - output = serializer.serialize(fixture, 'detail') + output = serializer.serialize(fixture) root = etree.XML(output) xmlutil.validate_schema(root, 'images') image_elems = root.findall('{0}image'.format(NS)) diff --git a/nova/tests/api/openstack/v2/test_limits.py b/nova/tests/api/openstack/v2/test_limits.py index 8516c4945..1c299d751 100644 --- a/nova/tests/api/openstack/v2/test_limits.py +++ b/nova/tests/api/openstack/v2/test_limits.py @@ -853,19 +853,19 @@ class LimitsXMLSerializationTest(test.TestCase): pass def test_xml_declaration(self): - serializer = limits.LimitsXMLSerializer() + serializer = limits.LimitsTemplate() fixture = {"limits": { "rate": [], "absolute": {}}} - output = serializer.serialize(fixture, 'index') + output = serializer.serialize(fixture) print output has_dec = output.startswith("") self.assertTrue(has_dec) def test_index(self): - serializer = limits.LimitsXMLSerializer() + serializer = limits.LimitsTemplate() fixture = { "limits": { "rate": [{ @@ -890,7 +890,7 @@ class LimitsXMLSerializationTest(test.TestCase): "maxPersonality": 5, "maxPersonalitySize": 10240}}} - output = serializer.serialize(fixture, 'index') + output = serializer.serialize(fixture) print output root = etree.XML(output) xmlutil.validate_schema(root, 'limits') @@ -919,13 +919,13 @@ class LimitsXMLSerializationTest(test.TestCase): str(fixture['limits']['rate'][i]['limit'][j][key])) def test_index_no_limits(self): - serializer = limits.LimitsXMLSerializer() + serializer = limits.LimitsTemplate() fixture = {"limits": { "rate": [], "absolute": {}}} - output = serializer.serialize(fixture, 'index') + output = serializer.serialize(fixture) print output root = etree.XML(output) xmlutil.validate_schema(root, 'limits') diff --git a/nova/tests/api/openstack/v2/test_server_actions.py b/nova/tests/api/openstack/v2/test_server_actions.py index 8d9db71ef..781c5b4e8 100644 --- a/nova/tests/api/openstack/v2/test_server_actions.py +++ b/nova/tests/api/openstack/v2/test_server_actions.py @@ -257,6 +257,7 @@ class ServerActionsControllerTest(test.TestCase): def test_rebuild_accepted_minimum(self): new_return_server = return_server_with_attributes(image_ref='2') self.stubs.Set(nova.db, 'instance_get', new_return_server) + self_href = 'http://localhost/v2/fake/servers/%s' % FAKE_UUID body = { "rebuild": { @@ -265,11 +266,13 @@ class ServerActionsControllerTest(test.TestCase): } req = fakes.HTTPRequest.blank(self.url) - body = self.controller.action(req, FAKE_UUID, body) + robj = self.controller.action(req, FAKE_UUID, body) + body = robj.obj self.assertEqual(body['server']['image']['id'], '2') self.assertEqual(len(body['server']['adminPass']), FLAGS.password_length) + self.assertEqual(robj['location'], self_href) def test_rebuild_rejected_when_building(self): body = { @@ -301,7 +304,7 @@ class ServerActionsControllerTest(test.TestCase): } req = fakes.HTTPRequest.blank(self.url) - body = self.controller.action(req, FAKE_UUID, body) + body = self.controller.action(req, FAKE_UUID, body).obj self.assertEqual(body['server']['metadata'], metadata) @@ -355,7 +358,7 @@ class ServerActionsControllerTest(test.TestCase): } req = fakes.HTTPRequest.blank(self.url) - body = self.controller.action(req, FAKE_UUID, body) + body = self.controller.action(req, FAKE_UUID, body).obj self.assertTrue('personality' not in body['server']) @@ -371,7 +374,7 @@ class ServerActionsControllerTest(test.TestCase): } req = fakes.HTTPRequest.blank(self.url) - body = self.controller.action(req, FAKE_UUID, body) + body = self.controller.action(req, FAKE_UUID, body).obj self.assertEqual(body['server']['image']['id'], '2') self.assertEqual(body['server']['adminPass'], 'asdf') @@ -605,7 +608,7 @@ class ServerActionsControllerTest(test.TestCase): class TestServerActionXMLDeserializer(test.TestCase): def setUp(self): - self.deserializer = servers.ServerXMLDeserializer() + self.deserializer = servers.ActionDeserializer() def tearDown(self): pass diff --git a/nova/tests/api/openstack/v2/test_servers.py b/nova/tests/api/openstack/v2/test_servers.py index 67bd446f8..44c344029 100644 --- a/nova/tests/api/openstack/v2/test_servers.py +++ b/nova/tests/api/openstack/v2/test_servers.py @@ -1397,7 +1397,7 @@ class ServersControllerCreateTest(test.TestCase): req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" - server = self.controller.create(req, body)['server'] + server = self.controller.create(req, body).obj['server'] self.assertEqual(FLAGS.password_length, len(server['adminPass'])) self.assertEqual(FAKE_UUID, server['id']) @@ -1424,7 +1424,7 @@ class ServersControllerCreateTest(test.TestCase): req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" - res = self.controller.create(req, body) + res = self.controller.create(req, body).obj self.assertEqual(FAKE_UUID, res["server"]["id"]) self.assertEqual(12, len(res["server"]["adminPass"])) @@ -1565,7 +1565,7 @@ class ServersControllerCreateTest(test.TestCase): req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" - res = self.controller.create(req, body) + res = self.controller.create(req, body).obj server = res['server'] self.assertEqual(FLAGS.password_length, len(server['adminPass'])) @@ -1598,7 +1598,7 @@ class ServersControllerCreateTest(test.TestCase): req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" - res = self.controller.create(req, body) + res = self.controller.create(req, body).obj server = res['server'] self.assertEqual(FLAGS.password_length, len(server['adminPass'])) @@ -1628,7 +1628,7 @@ class ServersControllerCreateTest(test.TestCase): req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" - res = self.controller.create(req, body) + res = self.controller.create(req, body).obj self.assertEqual(FAKE_UUID, res["server"]["id"]) self.assertEqual(12, len(res["server"]["adminPass"])) @@ -1700,7 +1700,7 @@ class ServersControllerCreateTest(test.TestCase): req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" - res = self.controller.create(req, body) + res = self.controller.create(req, body).obj server = res['server'] self.assertEqual(FAKE_UUID, server['id']) @@ -1727,7 +1727,7 @@ class ServersControllerCreateTest(test.TestCase): req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" - res = self.controller.create(req, body) + res = self.controller.create(req, body).obj server = res['server'] self.assertEqual(FAKE_UUID, server['id']) @@ -1779,7 +1779,7 @@ class ServersControllerCreateTest(test.TestCase): req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" - res = self.controller.create(req, body) + res = self.controller.create(req, body).obj server = res['server'] self.assertEqual(FAKE_UUID, server['id']) @@ -1814,7 +1814,7 @@ class ServersControllerCreateTest(test.TestCase): req.method = 'POST' req.body = json.dumps(body) req.headers["content-type"] = "application/json" - res = self.controller.create(req, body) + res = self.controller.create(req, body).obj server = res['server'] self.assertEqual(FAKE_UUID, server['id']) @@ -1834,7 +1834,7 @@ class ServersControllerCreateTest(test.TestCase): req.method = 'POST' req.body = json.dumps(body) req.headers['content-type'] = "application/json" - res = self.controller.create(req, body) + res = self.controller.create(req, body).obj server = res['server'] self.assertEqual(server['adminPass'], body['server']['adminPass']) @@ -1867,12 +1867,44 @@ class ServersControllerCreateTest(test.TestCase): self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, req, body) + def test_create_location(self): + selfhref = 'http://localhost/v2/fake/servers/%s' % FAKE_UUID + bookhref = 'http://localhost/fake/servers/%s' % FAKE_UUID + image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6' + image_href = 'http://localhost/v2/images/%s' % image_uuid + flavor_ref = 'http://localhost/123/flavors/3' + body = { + 'server': { + 'name': 'server_test', + 'imageRef': image_href, + 'flavorRef': flavor_ref, + 'metadata': { + 'hello': 'world', + 'open': 'stack', + }, + 'personality': [ + { + "path": "/etc/banner.txt", + "contents": "MQ==", + }, + ], + }, + } + + req = fakes.HTTPRequest.blank('/v2/fake/servers') + req.method = 'POST' + req.body = json.dumps(body) + req.headers['content-type'] = 'application/json' + robj = self.controller.create(req, body) + + self.assertEqual(robj['Location'], selfhref) + class TestServerCreateRequestXMLDeserializer(test.TestCase): def setUp(self): super(TestServerCreateRequestXMLDeserializer, self).setUp() - self.deserializer = servers.ServerXMLDeserializer() + self.deserializer = servers.CreateDeserializer() def test_minimal_request(self): serial_request = """ @@ -1880,7 +1912,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): name="new-server-test" imageRef="1" flavorRef="2"/>""" - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "server": { "name": "new-server-test", @@ -1897,7 +1929,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): imageRef="1" flavorRef="2" accessIPv4="1.2.3.4"/>""" - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "server": { "name": "new-server-test", @@ -1915,7 +1947,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): imageRef="1" flavorRef="2" accessIPv6="fead::1234"/>""" - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "server": { "name": "new-server-test", @@ -1934,7 +1966,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): flavorRef="2" accessIPv4="1.2.3.4" accessIPv6="fead::1234"/>""" - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "server": { "name": "new-server-test", @@ -1953,7 +1985,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): imageRef="1" flavorRef="2" adminPass="1234"/>""" - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "server": { "name": "new-server-test", @@ -1970,7 +2002,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): name="new-server-test" imageRef="http://localhost:8774/v2/images/2" flavorRef="3"/>""" - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "server": { "name": "new-server-test", @@ -1986,7 +2018,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): name="new-server-test" imageRef="1" flavorRef="http://localhost:8774/v2/flavors/3"/>""" - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "server": { "name": "new-server-test", @@ -2005,7 +2037,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "server": { "name": "new-server-test", @@ -2028,7 +2060,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): snack """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "server": { "name": "new-server-test", @@ -2050,7 +2082,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): Mg== """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "server": { "name": "new-server-test", @@ -2079,7 +2111,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): Mg== """ % (image_bookmark_link) - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = { "server": { "name": "new-server-test", @@ -2103,7 +2135,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): name="new-server-test" imageRef="1" flavorRef="1"> """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = {"server": { "name": "new-server-test", "imageRef": "1", @@ -2120,7 +2152,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = {"server": { "name": "new-server-test", "imageRef": "1", @@ -2138,7 +2170,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = {"server": { "name": "new-server-test", "imageRef": "1", @@ -2159,7 +2191,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = {"server": { "name": "new-server-test", "imageRef": "1", @@ -2176,7 +2208,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = {"server": { "name": "new-server-test", "imageRef": "1", @@ -2193,7 +2225,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = {"server": { "name": "new-server-test", "imageRef": "1", @@ -2210,7 +2242,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = {"server": { "name": "new-server-test", "imageRef": "1", @@ -2227,7 +2259,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = {"server": { "name": "new-server-test", "imageRef": "1", @@ -2245,7 +2277,7 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): """ - request = self.deserializer.deserialize(serial_request, 'create') + request = self.deserializer.deserialize(serial_request) expected = {"server": { "name": "new-server-test", "imageRef": "1", @@ -2258,7 +2290,8 @@ class TestServerCreateRequestXMLDeserializer(test.TestCase): class TestAddressesXMLSerialization(test.TestCase): - serializer = nova.api.openstack.v2.ips.IPXMLSerializer() + index_serializer = nova.api.openstack.v2.ips.AddressesTemplate() + show_serializer = nova.api.openstack.v2.ips.NetworkTemplate() def test_xml_declaration(self): fixture = { @@ -2267,7 +2300,7 @@ class TestAddressesXMLSerialization(test.TestCase): {'addr': 'fe80::beef', 'version': 6}, ], } - output = self.serializer.serialize(fixture, 'show') + output = self.show_serializer.serialize(fixture) has_dec = output.startswith("") self.assertTrue(has_dec) @@ -2278,7 +2311,7 @@ class TestAddressesXMLSerialization(test.TestCase): {'addr': 'fe80::beef', 'version': 6}, ], } - output = self.serializer.serialize(fixture, 'show') + output = self.show_serializer.serialize(fixture) root = etree.XML(output) network = fixture['network_2'] self.assertEqual(str(root.get('id')), 'network_2') @@ -2303,7 +2336,7 @@ class TestAddressesXMLSerialization(test.TestCase): ], }, } - output = self.serializer.serialize(fixture, 'index') + output = self.index_serializer.serialize(fixture) root = etree.XML(output) xmlutil.validate_schema(root, 'addresses') addresses_dict = fixture['addresses'] @@ -2889,7 +2922,7 @@ class ServerXMLSerializationTest(test.TestCase): test.TestCase.setUp(self) def test_xml_declaration(self): - serializer = servers.ServerXMLSerializer() + serializer = servers.ServerTemplate() fixture = { "server": { @@ -2961,13 +2994,13 @@ class ServerXMLSerializationTest(test.TestCase): } } - output = serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) print output has_dec = output.startswith("") self.assertTrue(has_dec) def test_show(self): - serializer = servers.ServerXMLSerializer() + serializer = servers.ServerTemplate() fixture = { "server": { @@ -3040,7 +3073,7 @@ class ServerXMLSerializationTest(test.TestCase): } } - output = serializer.serialize(fixture, 'show') + output = serializer.serialize(fixture) print output root = etree.XML(output) xmlutil.validate_schema(root, 'server') @@ -3098,7 +3131,7 @@ class ServerXMLSerializationTest(test.TestCase): str(ip['addr'])) def test_create(self): - serializer = servers.ServerXMLSerializer() + serializer = servers.FullServerTemplate() fixture = { "server": { @@ -3171,7 +3204,7 @@ class ServerXMLSerializationTest(test.TestCase): } } - output = serializer.serialize(fixture, 'create') + output = serializer.serialize(fixture) print output root = etree.XML(output) xmlutil.validate_schema(root, 'server') @@ -3229,7 +3262,7 @@ class ServerXMLSerializationTest(test.TestCase): str(ip['addr'])) def test_index(self): - serializer = servers.ServerXMLSerializer() + serializer = servers.MinimalServersTemplate() uuid1 = get_fake_uuid(1) uuid2 = get_fake_uuid(2) @@ -3268,7 +3301,7 @@ class ServerXMLSerializationTest(test.TestCase): }, ]} - output = serializer.serialize(fixture, 'index') + output = serializer.serialize(fixture) print output root = etree.XML(output) xmlutil.validate_schema(root, 'servers_index') @@ -3286,7 +3319,7 @@ class ServerXMLSerializationTest(test.TestCase): self.assertEqual(link_nodes[i].get(key), value) def test_index_with_servers_links(self): - serializer = servers.ServerXMLSerializer() + serializer = servers.MinimalServersTemplate() uuid1 = get_fake_uuid(1) uuid2 = get_fake_uuid(2) @@ -3332,7 +3365,7 @@ class ServerXMLSerializationTest(test.TestCase): }, ]} - output = serializer.serialize(fixture, 'index') + output = serializer.serialize(fixture) print output root = etree.XML(output) xmlutil.validate_schema(root, 'servers_index') @@ -3356,7 +3389,7 @@ class ServerXMLSerializationTest(test.TestCase): self.assertEqual(servers_links[i].get(key), value) def test_detail(self): - serializer = servers.ServerXMLSerializer() + serializer = servers.ServersTemplate() uuid1 = get_fake_uuid(1) expected_server_href = 'http://localhost/v2/servers/%s' % uuid1 @@ -3482,7 +3515,7 @@ class ServerXMLSerializationTest(test.TestCase): }, ]} - output = serializer.serialize(fixture, 'detail') + output = serializer.serialize(fixture) root = etree.XML(output) xmlutil.validate_schema(root, 'servers') server_elems = root.findall('{0}server'.format(NS)) @@ -3541,7 +3574,7 @@ class ServerXMLSerializationTest(test.TestCase): str(ip['addr'])) def test_update(self): - serializer = servers.ServerXMLSerializer() + serializer = servers.ServerTemplate() fixture = { "server": { @@ -3619,7 +3652,7 @@ class ServerXMLSerializationTest(test.TestCase): } } - output = serializer.serialize(fixture, 'update') + output = serializer.serialize(fixture) print output root = etree.XML(output) xmlutil.validate_schema(root, 'server') @@ -3686,7 +3719,7 @@ class ServerXMLSerializationTest(test.TestCase): self.assertEqual(det_elem.text, fault_dict["details"]) def test_action(self): - serializer = servers.ServerXMLSerializer() + serializer = servers.FullServerTemplate() fixture = { "server": { @@ -3759,7 +3792,7 @@ class ServerXMLSerializationTest(test.TestCase): } } - output = serializer.serialize(fixture, 'action') + output = serializer.serialize(fixture) root = etree.XML(output) xmlutil.validate_schema(root, 'server') @@ -3814,42 +3847,3 @@ class ServerXMLSerializationTest(test.TestCase): str(ip['version'])) self.assertEqual(str(ip_elem.get('addr')), str(ip['addr'])) - - -class ServerHeadersSerializationTest(test.TestCase): - def test_create_location(self): - selfhref = 'http://localhost/v2/fake/servers/%s' % FAKE_UUID - bookhref = 'http://localhost/fake/servers/%s' % FAKE_UUID - - serializer = servers.HeadersSerializer() - response = webob.Response() - server = { - 'links': [{ - 'rel': 'self', - 'href': selfhref, - }, { - 'rel': 'bookmark', - 'href': bookhref, - }], - } - serializer.create(response, {'server': server}) - self.assertEqual(response.headers['Location'], selfhref) - - def test_rebuild_location(self): - selfhref = 'http://localhost/v2/fake/servers/%s' % FAKE_UUID - bookhref = 'http://localhost/fake/servers/%s' % FAKE_UUID - - serializer = servers.HeadersSerializer() - response = webob.Response() - server = { - 'status': 'REBUILD', - 'links': [{ - 'rel': 'self', - 'href': selfhref, - }, { - 'rel': 'bookmark', - 'href': bookhref, - }], - } - serializer.action(response, {'server': server}) - self.assertEqual(response.headers['Location'], selfhref) diff --git a/nova/tests/api/openstack/v2/test_versions.py b/nova/tests/api/openstack/v2/test_versions.py index 7b2599ee3..7e250471a 100644 --- a/nova/tests/api/openstack/v2/test_versions.py +++ b/nova/tests/api/openstack/v2/test_versions.py @@ -490,8 +490,8 @@ class VersionsSerializerTests(test.TestCase): ] } - serializer = versions.VersionsXMLSerializer() - response = serializer.index(versions_data) + serializer = versions.VersionsTemplate() + response = serializer.serialize(versions_data) root = etree.XML(response) xmlutil.validate_schema(root, 'versions') @@ -528,8 +528,8 @@ class VersionsSerializerTests(test.TestCase): ] } - serializer = versions.VersionsXMLSerializer() - response = serializer.multi(versions_data) + serializer = versions.ChoicesTemplate() + response = serializer.serialize(versions_data) root = etree.XML(response) self.assertTrue(root.xpath('/ns:choices', namespaces=NS)) @@ -568,7 +568,7 @@ class VersionsSerializerTests(test.TestCase): } serializer = versions.VersionsAtomSerializer() - response = serializer.index(versions_data) + response = serializer.serialize(versions_data) f = feedparser.parse(response) self.assertEqual(f.feed.title, 'Available API Versions') @@ -631,8 +631,8 @@ class VersionsSerializerTests(test.TestCase): }, } - serializer = versions.VersionsAtomSerializer() - response = serializer.show(versions_data) + serializer = versions.VersionAtomSerializer() + response = serializer.serialize(versions_data) f = feedparser.parse(response) self.assertEqual(f.feed.title, 'About This Version') -- cgit