diff options
91 files changed, 3607 insertions, 2099 deletions
diff --git a/bin/nova-manage b/bin/nova-manage index a61b5c1dd..79683fef7 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -1,6 +1,7 @@ #!/usr/bin/env python # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. @@ -103,6 +104,8 @@ flags.DECLARE('multi_host', 'nova.network.manager') flags.DECLARE('network_size', 'nova.network.manager') flags.DECLARE('vlan_start', 'nova.network.manager') flags.DECLARE('vpn_start', 'nova.network.manager') +flags.DECLARE('default_floating_pool', 'nova.network.manager') +flags.DECLARE('public_interface', 'nova.network.linux_net') flags.DECLARE('libvirt_type', 'nova.virt.libvirt.connection') flags.DEFINE_flag(flags.HelpFlag()) flags.DEFINE_flag(flags.HelpshortFlag()) @@ -684,14 +687,23 @@ class FixedIpCommands(object): class FloatingIpCommands(object): """Class for managing floating ip.""" - @args('--ip_range', dest="range", metavar='<range>', help='IP range') - def create(self, range): + @args('--ip_range', dest="ip_range", metavar='<range>', help='IP range') + @args('--pool', dest="pool", metavar='<pool>', help='Optional pool') + @args('--interface', dest="interface", metavar='<interface>', + help='Optional interface') + def create(self, ip_range, pool=None, interface=None): """Creates floating ips for zone by range""" - addresses = netaddr.IPNetwork(range) + addresses = netaddr.IPNetwork(ip_range) admin_context = context.get_admin_context() + if not pool: + pool = FLAGS.default_floating_pool + if not interface: + interface = FLAGS.public_interface for address in addresses.iter_hosts(): db.floating_ip_create(admin_context, - {'address': str(address)}) + {'address': str(address), + 'pool': pool, + 'interface': interface}) @args('--ip_range', dest="ip_range", metavar='<range>', help='IP range') def delete(self, ip_range): diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py index f40be6e2a..156037a87 100644 --- a/nova/api/ec2/apirequest.py +++ b/nova/api/ec2/apirequest.py @@ -108,7 +108,13 @@ class APIRequest(object): response = xml.toxml() xml.unlink() - LOG.debug(response) + + # Don't write private key to log + if self.action != "CreateKeyPair": + LOG.debug(response) + else: + LOG.debug("CreateKeyPair: Return Private Key") + return response def _render_dict(self, xml, el, data): 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/extended_status.py b/nova/api/openstack/v2/contrib/extended_status.py index 123c4f287..a3b0410f6 100644 --- a/nova/api/openstack/v2/contrib/extended_status.py +++ b/nova/api/openstack/v2/contrib/extended_status.py @@ -46,6 +46,7 @@ class Extended_status(extensions.ExtensionDescriptor): try: inst_ref = compute_api.routing_get(context, server_id) except exception.NotFound: + LOG.warn("Instance %s not found (one)" % server_id) explanation = _("Server not found.") raise exc.HTTPNotFound(explanation=explanation) @@ -61,12 +62,18 @@ class Extended_status(extensions.ExtensionDescriptor): # and whatever else elements and find each individual. compute_api = compute.API() - for server in body['servers']: + for server in list(body['servers']): try: inst_ref = compute_api.routing_get(context, server['id']) except exception.NotFound: - explanation = _("Server not found.") - raise exc.HTTPNotFound(explanation=explanation) + # NOTE(dtroyer): A NotFound exception at this point + # happens because a delete was in progress and the + # server that was present in the original call to + # compute.api.get_all() is no longer present. + # Delete it from the response and move on. + LOG.warn("Instance %s not found (all)" % server['id']) + body['servers'].remove(server) + continue #TODO(bcwaldon): these attributes should be prefixed with # something specific to this extension 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_ip_pools.py b/nova/api/openstack/v2/contrib/floating_ip_pools.py new file mode 100644 index 000000000..9d6386f25 --- /dev/null +++ b/nova/api/openstack/v2/contrib/floating_ip_pools.py @@ -0,0 +1,104 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License + +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil +from nova.api.openstack.v2 import extensions +from nova import log as logging +from nova import network + + +LOG = logging.getLogger('nova.api.openstack.v2.contrib.floating_ip_poolss') + + +def _translate_floating_ip_view(pool): + return { + 'name': pool['name'], + } + + +def _translate_floating_ip_pools_view(pools): + return { + 'floating_ip_pools': [_translate_floating_ip_view(pool) + for pool in pools] + } + + +class FloatingIPPoolsController(object): + """The Floating IP Pool API controller for the OpenStack API.""" + + def __init__(self): + self.network_api = network.API() + super(FloatingIPPoolsController, self).__init__() + + def index(self, req): + """Return a list of pools.""" + context = req.environ['nova.context'] + pools = self.network_api.get_floating_ip_pools(context) + return _translate_floating_ip_pools_view(pools) + + +def make_float_ip(elem): + elem.set('name') + + +class FloatingIPPoolTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('floating_ip_pool', + selector='floating_ip_pool') + make_float_ip(root) + return xmlutil.MasterTemplate(root, 1) + + +class FloatingIPPoolsTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('floating_ip_pools') + elem = xmlutil.SubTemplateElement(root, 'floating_ip_pool', + selector='floating_ip_pools') + make_float_ip(elem) + return xmlutil.MasterTemplate(root, 1) + + +class FloatingIPPoolsSerializer(xmlutil.XMLTemplateSerializer): + def index(self): + return FloatingIPPoolsTemplate() + + +class Floating_ip_pools(extensions.ExtensionDescriptor): + """Floating IPs support""" + + name = "Floating_ip_pools" + alias = "os-floating-ip-pools" + namespace = \ + "http://docs.openstack.org/compute/ext/floating_ip_pools/api/v1.1" + updated = "2012-01-04T00:00:00+00:00" + + def get_resources(self): + resources = [] + + body_serializers = { + 'application/xml': FloatingIPPoolsSerializer(), + } + + serializer = wsgi.ResponseSerializer(body_serializers) + + res = extensions.ResourceExtension('os-floating-ip-pools', + FloatingIPPoolsController(), + serializer=serializer, + member_actions={}) + 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..3a6f4ee34 100644 --- a/nova/api/openstack/v2/contrib/floating_ips.py +++ b/nova/api/openstack/v2/contrib/floating_ips.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 OpenStack LLC. +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2011 Grid Dynamics # Copyright 2011 Eldar Nugaev, Kirill Shileev, Ilya Alekseyev # @@ -31,9 +32,37 @@ 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('pool') + 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']} + result = { + 'id': floating_ip['id'], + 'ip': floating_ip['address'], + 'pool': floating_ip['pool'], + } try: result['fixed_ip'] = floating_ip['fixed_ip']['address'] except (TypeError, KeyError): @@ -57,6 +86,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 +98,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,16 +107,23 @@ 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'] + pool = None + if body and 'pool' in body: + pool = body['pool'] try: - address = self.network_api.allocate_floating_ip(context) + address = self.network_api.allocate_floating_ip(context, pool) ip = self.network_api.get_floating_ip_by_address(context, address) except rpc.RemoteError as ex: # NOTE(tr3buchet) - why does this block exist? if ex.exc_type == 'NoMoreFloatingIps': - msg = _("No more floating ips available.") + if pool: + msg = _("No more floating ips in pool %s.") % pool + else: + msg = _("No more floating ips available.") raise webob.exc.HTTPBadRequest(explanation=msg) else: raise @@ -109,30 +147,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 +218,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,16 +41,321 @@ LOG = logging.getLogger('nova.api.openstack.v2.servers') FLAGS = flags.FLAGS +class SecurityGroupsTemplateElement(xmlutil.TemplateElement): + def will_render(self, datum): + return 'security_groups' in datum + + +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 make_server(elem, detailed=False): + elem.set('name') + elem.set('id') + + 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') + + # Attach image node + image = xmlutil.SubTemplateElement(elem, 'image', selector='image') + image.set('id') + xmlutil.make_links(image, 'links') + + # Attach flavor node + flavor = xmlutil.SubTemplateElement(elem, 'flavor', selector='flavor') + flavor.set('id') + xmlutil.make_links(flavor, 'links') + + # Attach fault node + make_fault(elem) + + # Attach metadata node + elem.append(common.MetadataTemplate()) + + # Attach addresses node + elem.append(ips.AddressesTemplate()) + + # Attach security groups node + secgrps = SecurityGroupsTemplateElement('security_groups') + elem.append(secgrps) + secgrp = xmlutil.SubTemplateElement(secgrps, 'security_group', + selector='security_groups') + secgrp.set('name') + + xmlutil.make_links(elem, 'links') + + +server_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM} + + +class ServerTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('server', selector='server') + make_server(root, detailed=True) + return xmlutil.MasterTemplate(root, 1, nsmap=server_nsmap) + + +class MinimalServersTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('servers') + elem = xmlutil.SubTemplateElement(root, 'server', selector='servers') + make_server(elem) + xmlutil.make_links(root, 'servers_links') + return xmlutil.MasterTemplate(root, 1, nsmap=server_nsmap) + + +class ServersTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('servers') + elem = xmlutil.SubTemplateElement(root, 'server', selector='servers') + make_server(elem, detailed=True) + return xmlutil.MasterTemplate(root, 1, nsmap=server_nsmap) + + +class ServerAdminPassTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('server') + root.set('adminPass') + return xmlutil.SlaveTemplate(root, 1, nsmap=server_nsmap) + + +def FullServerTemplate(): + master = ServerTemplate() + master.attach(ServerAdminPassTemplate()) + return master + + +class CommonDeserializer(wsgi.MetadataXMLDeserializer): + """ + Common deserializer to handle xml-formatted server create + requests. + + Handles standard server attributes as well as optional metadata + and personality attributes + """ + + metadata_deserializer = common.MetadataXMLDeserializer() + + 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 _extract_server(self, node): + """Marshal the server attribute of a parsed request""" + server = {} + server_node = self.find_first_child_named(node, 'server') + + attributes = ["name", "imageRef", "flavorRef", "adminPass", + "accessIPv4", "accessIPv6"] + for attr in attributes: + if server_node.getAttribute(attr): + server[attr] = server_node.getAttribute(attr) + + metadata_node = self.find_first_child_named(server_node, "metadata") + if metadata_node is not None: + server["metadata"] = self.extract_metadata(metadata_node) + + personality = self._extract_personality(server_node) + if personality is not None: + server["personality"] = personality + + networks = self._extract_networks(server_node) + if networks is not None: + server["networks"] = networks + + security_groups = self._extract_security_groups(server_node) + if security_groups is not None: + server["security_groups"] = security_groups + + auto_disk_config = server_node.getAttribute('auto_disk_config') + if auto_disk_config: + server['auto_disk_config'] = utils.bool_from_str(auto_disk_config) + + return server + + 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 + + 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 + + +class ActionDeserializer(CommonDeserializer): + """ + Deserializer to handle xml-formatted server action requests. + + Handles standard server attributes as well as optional metadata + and personality attributes + """ + + def default(self, string): + dom = minidom.parseString(string) + action_node = dom.childNodes[0] + action_name = action_node.tagName + + 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) + + action_data = action_deserializer(action_node) + + return {'body': {action_name: action_data}} + + def _action_create_image(self, node): + return self._deserialize_image_action(node, ('name',)) + + def _action_change_password(self, node): + if not node.hasAttribute("adminPass"): + raise AttributeError("No adminPass was specified in request") + return {"adminPass": node.getAttribute("adminPass")} + + def _action_reboot(self, node): + if not node.hasAttribute("type"): + raise AttributeError("No reboot type was specified in request") + return {"type": node.getAttribute("type")} + + def _action_rebuild(self, node): + rebuild = {} + if node.hasAttribute("name"): + rebuild['name'] = node.getAttribute("name") + + metadata_node = self.find_first_child_named(node, "metadata") + if metadata_node is not None: + rebuild["metadata"] = self.extract_metadata(metadata_node) + + personality = self._extract_personality(node) + if personality is not None: + rebuild["personality"] = personality + + if not node.hasAttribute("imageRef"): + raise AttributeError("No imageRef was specified in request") + rebuild["imageRef"] = node.getAttribute("imageRef") + + return rebuild + + def _action_resize(self, node): + if not node.hasAttribute("flavorRef"): + raise AttributeError("No flavorRef was specified in request") + return {"flavorRef": node.getAttribute("flavorRef")} + + def _action_confirm_resize(self, node): + return None + + def _action_revert_resize(self, node): + return None + + 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 + + +class CreateDeserializer(CommonDeserializer): + """ + Deserializer to handle xml-formatted server create requests. + + Handles standard server attributes as well as optional metadata + and personality attributes + """ + + def default(self, string): + """Deserialize an xml-formatted server create request""" + dom = minidom.parseString(string) + server = self._extract_server(dom) + return {'body': {'server': server}} + + class Controller(wsgi.Controller): """ The Server API base controller class for the OpenStack API """ _view_builder_class = views_servers.ViewBuilder + @staticmethod + def _add_location(robj): + # Just in case... + if 'server' not in robj.obj: + return robj + + link = filter(lambda l: l['rel'] == 'self', + robj.obj['server']['links']) + if link: + robj['Location'] = link[0]['href'] + + # Convenience return + return robj + 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: @@ -61,6 +366,7 @@ class Controller(wsgi.Controller): raise exc.HTTPNotFound() return servers + @wsgi.serializers(xml=ServersTemplate) def detail(self, req): """ Returns a list of server details for a given user """ try: @@ -263,6 +569,7 @@ class Controller(wsgi.Controller): 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): @@ -275,6 +582,9 @@ class Controller(wsgi.Controller): except exception.NotFound: raise exc.HTTPNotFound() + @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: @@ -427,7 +737,9 @@ class Controller(wsgi.Controller): else: server['server']['adminPass'] = password - return server + robj = wsgi.ResponseObject(server) + + return self._add_location(robj) def _delete(self, context, id): instance = self._get_server(context, id) @@ -436,6 +748,7 @@ class Controller(wsgi.Controller): else: self.compute_api.delete(context, instance) + @wsgi.serializers(xml=ServerTemplate) @scheduler_api.redirect_handler def update(self, req, id, body): """Update server then pass on to version-specific controller""" @@ -478,6 +791,9 @@ class Controller(wsgi.Controller): 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): @@ -567,6 +883,7 @@ class Controller(wsgi.Controller): return webob.Response(status_int=202) + @wsgi.response(204) @exception.novaclient_converter @scheduler_api.redirect_handler def delete(self, req, id): @@ -708,7 +1025,8 @@ class Controller(wsgi.Controller): # Add on the adminPass attribute since the view doesn't do it view['server']['adminPass'] = password - return view + robj = wsgi.ResponseObject(view) + return self._add_location(robj) @common.check_snapshots_enabled def _action_create_image(self, input_dict, req, instance_id): @@ -778,324 +1096,8 @@ class Controller(wsgi.Controller): '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'] - - def create(self, response, data): - if 'server' in data: - self._add_server_location(response, data) - response.status_int = 202 - - def delete(self, response, data): - response.status_int = 204 - - 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 - - -class SecurityGroupsTemplateElement(xmlutil.TemplateElement): - def will_render(self, datum): - return 'security_groups' in datum - - -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 make_server(elem, detailed=False): - elem.set('name') - elem.set('id') - - 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') - - # Attach image node - image = xmlutil.SubTemplateElement(elem, 'image', selector='image') - image.set('id') - xmlutil.make_links(image, 'links') - - # Attach flavor node - flavor = xmlutil.SubTemplateElement(elem, 'flavor', selector='flavor') - flavor.set('id') - xmlutil.make_links(flavor, 'links') - - # Attach fault node - make_fault(elem) - - # Attach metadata node - elem.append(common.MetadataTemplate()) - - # Attach addresses node - elem.append(ips.AddressesTemplate()) - - # Attach security groups node - secgrps = SecurityGroupsTemplateElement('security_groups') - elem.append(secgrps) - secgrp = xmlutil.SubTemplateElement(secgrps, 'security_group', - selector='security_groups') - secgrp.set('name') - - xmlutil.make_links(elem, 'links') - - -server_nsmap = {None: xmlutil.XMLNS_V11, 'atom': xmlutil.XMLNS_ATOM} - - -class ServerTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('server', selector='server') - make_server(root, detailed=True) - return xmlutil.MasterTemplate(root, 1, nsmap=server_nsmap) - - -class MinimalServersTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('servers') - elem = xmlutil.SubTemplateElement(root, 'server', selector='servers') - make_server(elem) - xmlutil.make_links(root, 'servers_links') - return xmlutil.MasterTemplate(root, 1, nsmap=server_nsmap) - - -class ServersTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('servers') - elem = xmlutil.SubTemplateElement(root, 'server', selector='servers') - make_server(elem, detailed=True) - return xmlutil.MasterTemplate(root, 1, nsmap=server_nsmap) - - -class ServerAdminPassTemplate(xmlutil.TemplateBuilder): - def construct(self): - root = xmlutil.TemplateElement('server') - root.set('adminPass') - return xmlutil.SlaveTemplate(root, 1, nsmap=server_nsmap) - - -class ServerXMLSerializer(xmlutil.XMLTemplateSerializer): - def index(self): - return MinimalServersTemplate() - - def detail(self): - return ServersTemplate() - - def show(self): - return ServerTemplate() - - def update(self): - return ServerTemplate() - - def create(self): - master = ServerTemplate() - master.attach(ServerAdminPassTemplate()) - return master - - def action(self): - return self.create() - - -class ServerXMLDeserializer(wsgi.MetadataXMLDeserializer): - """ - Deserializer to handle xml-formatted server create requests. - - Handles standard server attributes as well as optional metadata - and personality attributes - """ - - metadata_deserializer = common.MetadataXMLDeserializer() - - def action(self, string): - dom = minidom.parseString(string) - action_node = dom.childNodes[0] - action_name = action_node.tagName - - 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) - - action_data = action_deserializer(action_node) - - return {'body': {action_name: action_data}} - - def _action_create_image(self, node): - return self._deserialize_image_action(node, ('name',)) - - def _action_change_password(self, node): - if not node.hasAttribute("adminPass"): - raise AttributeError("No adminPass was specified in request") - return {"adminPass": node.getAttribute("adminPass")} - - def _action_reboot(self, node): - if not node.hasAttribute("type"): - raise AttributeError("No reboot type was specified in request") - return {"type": node.getAttribute("type")} - - def _action_rebuild(self, node): - rebuild = {} - if node.hasAttribute("name"): - rebuild['name'] = node.getAttribute("name") - - metadata_node = self.find_first_child_named(node, "metadata") - if metadata_node is not None: - rebuild["metadata"] = self.extract_metadata(metadata_node) - - personality = self._extract_personality(node) - if personality is not None: - rebuild["personality"] = personality - - if not node.hasAttribute("imageRef"): - raise AttributeError("No imageRef was specified in request") - rebuild["imageRef"] = node.getAttribute("imageRef") - - return rebuild - - def _action_resize(self, node): - if not node.hasAttribute("flavorRef"): - raise AttributeError("No flavorRef was specified in request") - return {"flavorRef": node.getAttribute("flavorRef")} - - def _action_confirm_resize(self, node): - return None - - def _action_revert_resize(self, node): - return None - - 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 - - def create(self, string): - """Deserialize an xml-formatted server create request""" - dom = minidom.parseString(string) - server = self._extract_server(dom) - return {'body': {'server': server}} - - def _extract_server(self, node): - """Marshal the server attribute of a parsed request""" - server = {} - server_node = self.find_first_child_named(node, 'server') - - attributes = ["name", "imageRef", "flavorRef", "adminPass", - "accessIPv4", "accessIPv6"] - for attr in attributes: - if server_node.getAttribute(attr): - server[attr] = server_node.getAttribute(attr) - - metadata_node = self.find_first_child_named(server_node, "metadata") - if metadata_node is not None: - server["metadata"] = self.extract_metadata(metadata_node) - - personality = self._extract_personality(server_node) - if personality is not None: - server["personality"] = personality - - networks = self._extract_networks(server_node) - if networks is not None: - server["networks"] = networks - - security_groups = self._extract_security_groups(server_node) - if security_groups is not None: - server["security_groups"] = security_groups - - auto_disk_config = server_node.getAttribute('auto_disk_config') - if auto_disk_config: - server['auto_disk_config'] = utils.bool_from_str(auto_disk_config) - - return server - - 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 _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 - - 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 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/common/policy.py b/nova/common/policy.py new file mode 100644 index 000000000..9d811c784 --- /dev/null +++ b/nova/common/policy.py @@ -0,0 +1,202 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 OpenStack, LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Common Policy Engine Implementation""" + +import json +import urllib +import urllib2 + + +class NotAllowed(Exception): + pass + + +_BRAIN = None + + +def set_brain(brain): + """Set the brain used by enforce(). + + Defaults use Brain() if not set. + + """ + global _BRAIN + _BRAIN = brain + + +def reset(): + """Clear the brain used by enforce().""" + global _BRAIN + _BRAIN = None + + +def enforce(match_list, target_dict, credentials_dict): + """Enforces authorization of some rules against credentials. + + :param match_list: nested tuples of data to match against + The basic brain supports three types of match lists: + 1) rules + looks like: ('rule:compute:get_instance',) + Retrieves the named rule from the rules dict and recursively + checks against the contents of the rule. + 2) roles + looks like: ('role:compute:admin',) + Matches if the specified role is in credentials_dict['roles']. + 3) generic + ('tenant_id:%(tenant_id)s',) + Substitutes values from the target dict into the match using + the % operator and matches them against the creds dict. + + Combining rules: + The brain returns True if any of the outer tuple of rules match + and also True if all of the inner tuples match. You can use this to + perform simple boolean logic. For example, the following rule would + return True if the creds contain the role 'admin' OR the if the + tenant_id matches the target dict AND the the creds contains the + role 'compute_sysadmin': + + { + "rule:combined": ( + 'role:admin', + ('tenant_id:%(tenant_id)s', 'role:compute_sysadmin') + ) + } + + + Note that rule and role are reserved words in the credentials match, so + you can't match against properties with those names. Custom brains may + also add new reserved words. For example, the HttpBrain adds http as a + reserved word. + + :param target_dict: dict of object properties + Target dicts contain as much information as we can about the object being + operated on. + + :param credentials_dict: dict of actor properties + Credentials dicts contain as much information as we can about the user + performing the action. + + :raises NotAllowed if the check fails + + """ + global _BRAIN + if not _BRAIN: + _BRAIN = Brain() + if not _BRAIN.check(match_list, target_dict, credentials_dict): + raise NotAllowed() + + +class Brain(object): + """Implements policy checking.""" + @classmethod + def load_json(cls, data): + """Init a brain using json instead of a rules dictionary.""" + rules_dict = json.loads(data) + return cls(rules=rules_dict) + + def __init__(self, rules=None): + self.rules = rules or {} + + def add_rule(self, key, match): + self.rules[key] = match + + def _check(self, match, target_dict, cred_dict): + match_kind, match_value = match.split(':', 1) + try: + f = getattr(self, '_check_%s' % match_kind) + except AttributeError: + if not self._check_generic(match, target_dict, cred_dict): + return False + else: + if not f(match_value, target_dict, cred_dict): + return False + return True + + def check(self, match_list, target_dict, cred_dict): + """Checks authorization of some rules against credentials. + + Detailed description of the check with examples in policy.enforce(). + + :param match_list: nested tuples of data to match against + :param target_dict: dict of object properties + :param credentials_dict: dict of actor properties + + :returns: True if the check passes + + """ + if not match_list: + return True + for and_list in match_list: + if isinstance(and_list, basestring): + and_list = (and_list,) + if all([self._check(item, target_dict, cred_dict) + for item in and_list]): + return True + return False + + def _check_rule(self, match, target_dict, cred_dict): + """Recursively checks credentials based on the brains rules.""" + try: + new_match_list = self.rules[match] + except KeyError: + return False + return self.check(new_match_list, target_dict, cred_dict) + + def _check_role(self, match, target_dict, cred_dict): + """Check that there is a matching role in the cred dict.""" + return match in cred_dict['roles'] + + def _check_generic(self, match, target_dict, cred_dict): + """Check an individual match. + + Matches look like: + + tenant:%(tenant_id)s + role:compute:admin + + """ + + # TODO(termie): do dict inspection via dot syntax + match = match % target_dict + key, value = match.split(':', 1) + if key in cred_dict: + return value == cred_dict[key] + return False + + +class HttpBrain(Brain): + """A brain that can check external urls for policy. + + Posts json blobs for target and credentials. + + """ + + def _check_http(self, match, target_dict, cred_dict): + """Check http: rules by calling to a remote server. + + This example implementation simply verifies that the response is + exactly 'True'. A custom brain using response codes could easily + be implemented. + + """ + url = match % target_dict + data = {'target': json.dumps(target_dict), + 'credentials': json.dumps(cred_dict)} + post_data = urllib.urlencode(data) + f = urllib2.urlopen(url, post_data) + return f.read() == "True" diff --git a/nova/db/api.py b/nova/db/api.py index 533f34ee5..5f9f9f43e 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -1,5 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. @@ -237,13 +238,18 @@ def floating_ip_get(context, id): return IMPL.floating_ip_get(context, id) -def floating_ip_allocate_address(context, project_id): - """Allocate free floating ip and return the address. +def floating_ip_get_pools(context): + """Returns a list of floating ip pools""" + return IMPL.floating_ip_get_pools(context) + + +def floating_ip_allocate_address(context, project_id, pool): + """Allocate free floating ip from specified pool and return the address. Raises if one is not available. """ - return IMPL.floating_ip_allocate_address(context, project_id) + return IMPL.floating_ip_allocate_address(context, project_id, pool) def floating_ip_create(context, values): @@ -668,14 +674,12 @@ def instance_info_cache_update(context, instance_id, values, session) -def instance_info_cache_delete_by_instance_id(context, instance_id, - session=None): +def instance_info_cache_delete(context, instance_id, session=None): """Deletes an existing instance_info_cache record :param instance_id: = id of the instance tied to the cache record """ - return IMPL.instance_info_cache_delete_by_instance_id(context, instance_id, - session) + return IMPL.instance_info_cache_delete(context, instance_id, session) ################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index c835c59f0..83a022292 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1,5 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. @@ -489,7 +490,16 @@ def floating_ip_get(context, id): @require_context -def floating_ip_allocate_address(context, project_id): +def floating_ip_get_pools(context): + session = get_session() + pools = [] + for result in session.query(models.FloatingIp.pool).distinct(): + pools.append({'name': result[0]}) + return pools + + +@require_context +def floating_ip_allocate_address(context, project_id, pool): authorize_project_context(context, project_id) session = get_session() with session.begin(): @@ -497,6 +507,7 @@ def floating_ip_allocate_address(context, project_id): session=session, read_deleted="no").\ filter_by(fixed_ip_id=None).\ filter_by(project_id=None).\ + filter_by(pool=pool).\ with_lockmode('update').\ first() # NOTE(vish): if with_lockmode isn't supported, as in sqlite, @@ -1093,6 +1104,10 @@ def instance_create(context, values): session = get_session() with session.begin(): instance_ref.save(session=session) + + # and creat the info_cache table entry for instance + instance_info_cache_create(context, {'instance_id': instance_ref['uuid']}) + return instance_ref @@ -1133,8 +1148,7 @@ def instance_destroy(context, instance_id): update({'deleted': True, 'deleted_at': utils.utcnow(), 'updated_at': literal_column('updated_at')}) - instance_info_cache_delete_by_instance_id(context, instance_id, - session=session) + instance_info_cache_delete(context, instance_id, session=session) @require_context @@ -1189,6 +1203,7 @@ def _build_instance_get(context, session=None): options(joinedload_all('fixed_ips.network')).\ options(joinedload_all('fixed_ips.virtual_interface')).\ options(joinedload_all('security_groups.rules')).\ + options(joinedload('info_cache')).\ options(joinedload('volumes')).\ options(joinedload('metadata')).\ options(joinedload('instance_type')) @@ -1198,6 +1213,7 @@ def _build_instance_get(context, session=None): def instance_get_all(context): return model_query(context, models.Instance).\ options(joinedload_all('fixed_ips.floating_ips')).\ + options(joinedload('info_cache')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ @@ -1250,6 +1266,7 @@ def instance_get_all_by_filters(context, filters): options(joinedload_all('fixed_ips.floating_ips')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload_all('fixed_ips.virtual_interface')).\ + options(joinedload('info_cache')).\ options(joinedload('security_groups')).\ options(joinedload('metadata')).\ options(joinedload('instance_type')).\ @@ -1365,6 +1382,7 @@ def instance_get_active_by_window_joined(context, begin, end=None, def _instance_get_all_query(context, project_only=False): return model_query(context, models.Instance, project_only=project_only).\ options(joinedload_all('fixed_ips.floating_ips')).\ + options(joinedload('info_cache')).\ options(joinedload('security_groups')).\ options(joinedload_all('fixed_ips.network')).\ options(joinedload('metadata')).\ @@ -1563,7 +1581,6 @@ def instance_info_cache_create(context, values): :param values: = dict containing column values """ info_cache = models.InstanceInfoCache() - info_cache['id'] = str(utils.gen_uuid()) info_cache.update(values) session = get_session() @@ -1576,7 +1593,7 @@ def instance_info_cache_create(context, values): def instance_info_cache_get(context, instance_id, session=None): """Gets an instance info cache from the table. - :param instance_id: = id of the info cache's instance + :param instance_id: = uuid of the info cache's instance :param session: = optional session object """ session = session or get_session() @@ -1592,7 +1609,7 @@ def instance_info_cache_update(context, instance_id, values, session=None): """Update an instance info cache record in the table. - :param instance_id: = id of info cache's instance + :param instance_id: = uuid of info cache's instance :param values: = dict containing column values to update :param session: = optional session object """ @@ -1609,11 +1626,10 @@ def instance_info_cache_update(context, instance_id, values, @require_context -def instance_info_cache_delete_by_instance_id(context, instance_id, - session=None): +def instance_info_cache_delete(context, instance_id, session=None): """Deletes an existing instance_info_cache record - :param instance_id: = id of the instance tied to the cache record + :param instance_id: = uuid of the instance tied to the cache record :param session: = optional session object """ values = {'deleted': True, diff --git a/nova/db/sqlalchemy/migrate_repo/versions/066_preload_instance_info_cache_table.py b/nova/db/sqlalchemy/migrate_repo/versions/066_preload_instance_info_cache_table.py new file mode 100644 index 000000000..a92dd434b --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/066_preload_instance_info_cache_table.py @@ -0,0 +1,239 @@ +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import datetime +import json + +from sqlalchemy import * +from migrate import * + +from nova import ipv6 +from nova import log as logging +from nova import utils + +meta = MetaData() + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + # grab tables + instance_info_caches = Table('instance_info_caches', meta, autoload=True) + instances = Table('instances', meta, autoload=True) + vifs = Table('virtual_interfaces', meta, autoload=True) + networks = Table('networks', meta, autoload=True) + fixed_ips = Table('fixed_ips', meta, autoload=True) + floating_ips = Table('floating_ips', meta, autoload=True) + + # all of these functions return a python list of python dicts + # that have nothing to do with sqlalchemy objects whatsoever + # after returning + def get_instances(): + # want all instances whether there is network info or not + s = select([instances.c.id, instances.c.uuid]) + keys = ('id', 'uuid') + + return [dict(zip(keys, row)) for row in s.execute()] + + def get_vifs_by_instance_id(instance_id): + s = select([vifs.c.id, vifs.c.uuid, vifs.c.address, vifs.c.network_id], + vifs.c.instance_id == instance_id) + keys = ('id', 'uuid', 'address', 'network_id') + return [dict(zip(keys, row)) for row in s.execute()] + + def get_network_by_id(network_id): + s = select([networks.c.uuid, networks.c.label, + networks.c.project_id, + networks.c.dns1, networks.c.dns2, + networks.c.cidr, networks.c.cidr_v6, + networks.c.gateway, networks.c.gateway_v6, + networks.c.injected, networks.c.multi_host, + networks.c.bridge, networks.c.bridge_interface, + networks.c.vlan], + networks.c.id == network_id) + keys = ('uuid', 'label', 'project_id', 'dns1', 'dns2', + 'cidr', 'cidr_v6', 'gateway', 'gateway_v6', + 'injected', 'multi_host', 'bridge', 'bridge_interface', 'vlan') + return [dict(zip(keys, row)) for row in s.execute()] + + def get_fixed_ips_by_vif_id(vif_id): + s = select([fixed_ips.c.id, fixed_ips.c.address], + fixed_ips.c.virtual_interface_id == vif_id) + keys = ('id', 'address') + fixed_ip_list = [dict(zip(keys, row)) for row in s.execute()] + + # fixed ips have floating ips, so here they are + for fixed_ip in fixed_ip_list: + fixed_ip['version'] = 4 + fixed_ip['floating_ips'] =\ + get_floating_ips_by_fixed_ip_id(fixed_ip['id']) + fixed_ip['type'] = 'fixed' + del fixed_ip['id'] + + return fixed_ip_list + + def get_floating_ips_by_fixed_ip_id(fixed_ip_id): + s = select([floating_ips.c.address], + floating_ips.c.fixed_ip_id == fixed_ip_id) + keys = ('address') + floating_ip_list = [dict(zip(keys, row)) for row in s.execute()] + + for floating_ip in floating_ip_list: + floating_ip['version'] = 4 + floating_ip['type'] = 'floating' + + return floating_ip_list + + def _ip_dict_from_string(ip_string, type): + if ip_string: + ip = {'address': ip_string, + 'type': type} + if ':' in ip_string: + ip['version'] = 6 + else: + ip['version'] = 4 + + return ip + + def _get_fixed_ipv6_dict(cidr, mac, project_id): + ip_string = ipv6.to_global(cidr, mac, project_id) + return {'version': 6, + 'address': ip_string, + 'floating_ips': []} + + def _create_subnet(version, network, vif): + if version == 4: + cidr = network['cidr'] + gateway = network['gateway'] + ips = get_fixed_ips_by_vif_id(vif['id']) + else: + cidr = network['cidr_v6'] + gateway = network['gateway_v6'] + ips = [_get_fixed_ipv6_dict(network['cidr_v6'], + vif['address'], + network['project_id'])] + + # NOTE(tr3buchet) routes is left empty for now because there + # is no good way to generate them or determine which is default + subnet = {'version': version, + 'cidr': cidr, + 'dns': [], + 'gateway': _ip_dict_from_string(gateway, 'gateway'), + 'routes': [], + 'ips': ips} + + if network['dns1'] and network['dns1']['version'] == version: + subnet['dns'].append(network['dns1']) + if network['dns2'] and network['dns2']['version'] == version: + subnet['dns'].append(network['dns2']) + + return subnet + + # preload caches table + # list is made up of a row(instance_id, nw_info_json) for each instance + for instance in get_instances(): + logging.info("Updating %s" % (instance['uuid'])) + instance_id = instance['id'] + instance_uuid = instance['uuid'] + + # instances have vifs so aninstance nw_info is + # is a list of dicts, 1 dict for each vif + nw_info = get_vifs_by_instance_id(instance_id) + logging.info("VIFs for Instance %s: \n %s" % \ + (instance['uuid'], nw_info)) + for vif in nw_info: + network = get_network_by_id(vif['network_id'])[0] + logging.info("Network for Instance %s: \n %s" % \ + (instance['uuid'], network)) + + # vifs have a network which has subnets, so create the subnets + # subnets contain all of the ip information + network['subnets'] = [] + + network['dns1'] = _ip_dict_from_string(network['dns1'], 'dns') + network['dns2'] = _ip_dict_from_string(network['dns2'], 'dns') + + # nova networks can only have 2 subnets + if network['cidr']: + network['subnets'].append(_create_subnet(4, network, vif)) + if network['cidr_v6']: + network['subnets'].append(_create_subnet(6, network, vif)) + + # put network together to fit model + network['id'] = network.pop('uuid') + network['meta'] = {} + + # NOTE(tr3buchet) this isn't absolutely necessary as hydration + # would still work with these as keys, but cache generated by + # the model would show these keys as a part of meta. i went + # ahead and set it up the same way just so it looks the same + if network['project_id']: + network['meta']['project_id'] = network['project_id'] + del network['project_id'] + if network['injected']: + network['meta']['injected'] = network['injected'] + del network['injected'] + if network['multi_host']: + network['meta']['multi_host'] = network['multi_host'] + del network['multi_host'] + if network['bridge_interface']: + network['meta']['bridge_interface'] = \ + network['bridge_interface'] + del network['bridge_interface'] + if network['vlan']: + network['meta']['vlan'] = network['vlan'] + del network['vlan'] + + # ip information now lives in the subnet, pull them out of network + del network['dns1'] + del network['dns2'] + del network['cidr'] + del network['cidr_v6'] + del network['gateway'] + del network['gateway_v6'] + + # don't need meta if it's empty + if not network['meta']: + del network['meta'] + + # put vif together to fit model + del vif['network_id'] + vif['id'] = vif.pop('uuid') + vif['network'] = network + # vif['meta'] could also be set to contain rxtx data here + # but it isn't exposed in the api and is still being rewritten + + logging.info("VIF network for instance %s: \n %s" % \ + (instance['uuid'], vif['network'])) + + # jsonify nw_info + row = {'created_at': utils.utcnow(), + 'updated_at': utils.utcnow(), + 'instance_id': instance_uuid, + 'network_info': json.dumps(nw_info)} + + # write write row to table + insert = instance_info_caches.insert().values(**row) + migrate_engine.execute(insert) + + +def downgrade(migrate_engine): + # facepalm + meta.bind = migrate_engine + instance_info_caches = Table('instance_info_caches', meta, autoload=True) + + # there is really no way to know what data was added by the migration and + # what was added afterward. Of note is the fact that before this migration + # the cache table was empty; therefore, delete everything. Also, aliens. + instance_info_caches.delete() diff --git a/nova/db/sqlalchemy/migrate_repo/versions/067_add_pool_and_interface_to_floating_ip.py b/nova/db/sqlalchemy/migrate_repo/versions/067_add_pool_and_interface_to_floating_ip.py new file mode 100644 index 000000000..205d7e034 --- /dev/null +++ b/nova/db/sqlalchemy/migrate_repo/versions/067_add_pool_and_interface_to_floating_ip.py @@ -0,0 +1,46 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License.from sqlalchemy import * + +from sqlalchemy import Column, MetaData, Table, String + +from nova import flags + + +flags.DECLARE('default_floating_pool', 'nova.network.manager') +flags.DECLARE('public_interface', 'nova.network.linux_net') +FLAGS = flags.FLAGS + +meta = MetaData() + +pool_column = Column('pool', String(255)) +interface_column = Column('interface', String(255)) + + +def upgrade(migrate_engine): + meta.bind = migrate_engine + table = Table('floating_ips', meta, autoload=True) + table.create_column(pool_column) + table.create_column(interface_column) + table.update().values(pool=FLAGS.default_floating_pool, + interface=FLAGS.public_interface).execute() + + +def downgrade(migrate_engine): + meta.bind = migrate_engine + table = Table('floating_ips', meta, autoload=True) + table.c.pool.drop() + table.c.interface.drop() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 8a026b37f..fad41334e 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -1,5 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright 2011 Piston Cloud Computing, Inc. @@ -262,7 +263,7 @@ class InstanceInfoCache(BASE, NovaBase): Represents a cache of information about an instance """ __tablename__ = 'instance_info_caches' - id = Column(String(36), primary_key=True) + id = Column(Integer, primary_key=True, autoincrement=True) # text column used for storing a json object of network data for api network_info = Column(Text) @@ -711,6 +712,8 @@ class FloatingIp(BASE, NovaBase): project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) auto_assigned = Column(Boolean, default=False, nullable=False) + pool = Column(String(255)) + interface = Column(String(255)) class AuthToken(BASE, NovaBase): diff --git a/nova/exception.py b/nova/exception.py index f2df97964..8ec942a76 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -207,6 +207,10 @@ class AdminRequired(NotAuthorized): message = _("User does not have admin privileges") +class PolicyNotAllowed(NotAuthorized): + message = _("Policy Doesn't allow %(action)s to be performed.") + + class InstanceBusy(NovaException): message = _("Instance %(instance_id)s is busy. (%(task_state)s)") diff --git a/nova/network/api.py b/nova/network/api.py index e0a5afdd9..82b3739a3 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -1,5 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. @@ -39,6 +40,11 @@ class API(base.Base): {'method': 'get_floating_ip', 'args': {'id': id}}) + def get_floating_ip_pools(self, context): + return rpc.call(context, + FLAGS.network_topic, + {'method': 'get_floating_pools'}) + def get_floating_ip_by_address(self, context, address): return rpc.call(context, FLAGS.network_topic, @@ -62,8 +68,8 @@ class API(base.Base): {'method': 'get_vifs_by_instance', 'args': {'instance_id': instance_id}}) - def allocate_floating_ip(self, context): - """Adds a floating ip to a project. (allocates)""" + def allocate_floating_ip(self, context, pool=None): + """Adds a floating ip to a project from a pool. (allocates)""" # NOTE(vish): We don't know which network host should get the ip # when we allocate, so just send it to any one. This # will probably need to move into a network supervisor @@ -71,7 +77,8 @@ class API(base.Base): return rpc.call(context, FLAGS.network_topic, {'method': 'allocate_floating_ip', - 'args': {'project_id': context.project_id}}) + 'args': {'project_id': context.project_id, + 'pool': pool}}) def release_floating_ip(self, context, address, affect_auto_assigned=False): @@ -110,6 +117,7 @@ class API(base.Base): """ args = kwargs args['instance_id'] = instance['id'] + args['instance_uuid'] = instance['uuid'] args['project_id'] = instance['project_id'] args['host'] = instance['host'] args['instance_type_id'] = instance['instance_type_id'] @@ -153,6 +161,7 @@ class API(base.Base): def get_instance_nw_info(self, context, instance): """Returns all network info related to an instance.""" args = {'instance_id': instance['id'], + 'instance_uuid': instance['uuid'], 'instance_type_id': instance['instance_type_id'], 'host': instance['host']} try: diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index 3784a1117..5b0c80eec 100755 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -1,5 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. @@ -432,21 +433,21 @@ def init_host(ip_range=None): iptables_manager.apply() -def bind_floating_ip(floating_ip, check_exit_code=True): +def bind_floating_ip(floating_ip, device, check_exit_code=True): """Bind ip to public interface.""" _execute('ip', 'addr', 'add', str(floating_ip) + '/32', - 'dev', FLAGS.public_interface, + 'dev', device, run_as_root=True, check_exit_code=check_exit_code) if FLAGS.send_arp_for_ha: _execute('arping', '-U', floating_ip, - '-A', '-I', FLAGS.public_interface, + '-A', '-I', device, '-c', 1, run_as_root=True, check_exit_code=False) -def unbind_floating_ip(floating_ip): +def unbind_floating_ip(floating_ip, device): """Unbind a public ip from public interface.""" _execute('ip', 'addr', 'del', str(floating_ip) + '/32', - 'dev', FLAGS.public_interface, run_as_root=True) + 'dev', device, run_as_root=True) def ensure_metadata_ip(): diff --git a/nova/network/manager.py b/nova/network/manager.py index ea65dc5a4..eff312f5b 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -1,5 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. @@ -63,6 +64,7 @@ from nova import ipv6 from nova import log as logging from nova import manager from nova.network import api as network_api +from nova.network import model as network_model from nova import quota from nova import utils from nova import rpc @@ -93,6 +95,8 @@ flags.DEFINE_integer('network_size', 256, 'Number of addresses in each private subnet') flags.DEFINE_string('floating_range', '4.4.4.0/24', 'Floating IP address block') +flags.DEFINE_string('default_floating_pool', 'nova', + 'Default pool for floating ips') flags.DEFINE_string('fixed_range', '10.0.0.0/8', 'Fixed IP address block') flags.DEFINE_string('fixed_range_v6', 'fd00::/48', 'Fixed IPv6 address block') flags.DEFINE_string('gateway', None, 'Default IPv4 gateway') @@ -200,7 +204,9 @@ class FloatingIP(object): fixed_address = floating_ip['fixed_ip']['address'] # NOTE(vish): The False here is because we ignore the case # that the ip is already bound. - self.driver.bind_floating_ip(floating_ip['address'], False) + self.driver.bind_floating_ip(floating_ip['address'], + floating_ip['interface'], + False) self.driver.ensure_floating_forward(floating_ip['address'], fixed_address) @@ -285,8 +291,8 @@ class FloatingIP(object): 'project': context.project_id}) raise exception.NotAuthorized() - def allocate_floating_ip(self, context, project_id): - """Gets an floating ip from the pool.""" + def allocate_floating_ip(self, context, project_id, pool=None): + """Gets a floating ip from the pool.""" # NOTE(tr3buchet): all network hosts in zone now use the same pool LOG.debug("QUOTA: %s" % quota.allowed_floating_ips(context, 1)) if quota.allowed_floating_ips(context, 1) < 1: @@ -295,9 +301,10 @@ class FloatingIP(object): context.project_id) raise exception.QuotaError(_('Address quota exceeded. You cannot ' 'allocate any more addresses')) - # TODO(vish): add floating ips through manage command + pool = pool or FLAGS.default_floating_pool return self.db.floating_ip_allocate_address(context, - project_id) + project_id, + pool) def deallocate_floating_ip(self, context, address, affect_auto_assigned=False): @@ -336,7 +343,6 @@ class FloatingIP(object): # make sure floating ip isn't already associated if floating_ip['fixed_ip_id']: - floating_address = floating_ip['address'] raise exception.FloatingIpAssociated(address=floating_address) fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_address) @@ -347,20 +353,22 @@ class FloatingIP(object): host = instance['host'] else: host = fixed_ip['network']['host'] - LOG.info("%s", self.host) + interface = floating_ip['interface'] if host == self.host: # i'm the correct host self._associate_floating_ip(context, floating_address, - fixed_address) + fixed_address, interface) else: # send to correct host rpc.cast(context, self.db.queue_get_for(context, FLAGS.network_topic, host), {'method': '_associate_floating_ip', - 'args': {'floating_address': floating_ip['address'], - 'fixed_address': fixed_ip['address']}}) + 'args': {'floating_address': floating_address, + 'fixed_address': fixed_address, + 'interface': interface}}) - def _associate_floating_ip(self, context, floating_address, fixed_address): + def _associate_floating_ip(self, context, floating_address, fixed_address, + interface): """Performs db and driver calls to associate floating ip & fixed ip""" # associate floating ip self.db.floating_ip_fixed_ip_associate(context, @@ -368,7 +376,7 @@ class FloatingIP(object): fixed_address, self.host) # gogo driver time - self.driver.bind_floating_ip(floating_address) + self.driver.bind_floating_ip(floating_address, interface) self.driver.ensure_floating_forward(floating_address, fixed_address) def disassociate_floating_ip(self, context, address, @@ -400,29 +408,36 @@ class FloatingIP(object): host = instance['host'] else: host = fixed_ip['network']['host'] + interface = floating_ip['interface'] if host == self.host: # i'm the correct host - self._disassociate_floating_ip(context, address) + self._disassociate_floating_ip(context, address, interface) else: # send to correct host rpc.cast(context, self.db.queue_get_for(context, FLAGS.network_topic, host), {'method': '_disassociate_floating_ip', - 'args': {'address': address}}) + 'args': {'address': address, + 'interface': interface}}) - def _disassociate_floating_ip(self, context, address): + def _disassociate_floating_ip(self, context, address, interface): """Performs db and driver calls to disassociate floating ip""" # disassociate floating ip fixed_address = self.db.floating_ip_disassociate(context, address) # go go driver time - self.driver.unbind_floating_ip(address) + self.driver.unbind_floating_ip(address, interface) self.driver.remove_floating_forward(address, fixed_address) def get_floating_ip(self, context, id): """Returns a floating IP as a dict""" return dict(self.db.floating_ip_get(context, id).iteritems()) + def get_floating_pools(self, context): + """Returns list of floating pools""" + pools = self.db.floating_ip_get_pools(context) + return [dict(pool.iteritems()) for pool in pools] + def get_floating_ip_by_address(self, context, address): """Returns a floating IP as a dict""" return dict(self.db.floating_ip_get_by_address(context, @@ -624,8 +639,7 @@ class NetworkManager(manager.SchedulerDependentManager): # a non-vlan instance should connect to if requested_networks is not None and len(requested_networks) != 0: network_uuids = [uuid for (uuid, fixed_ip) in requested_networks] - networks = self.db.network_get_all_by_uuids(context, - network_uuids) + networks = self.db.network_get_all_by_uuids(context, network_uuids) else: try: networks = self.db.network_get_all(context) @@ -641,6 +655,7 @@ class NetworkManager(manager.SchedulerDependentManager): rpc.called by network_api """ instance_id = kwargs.pop('instance_id') + instance_uuid = kwargs.pop('instance_uuid') host = kwargs.pop('host') project_id = kwargs.pop('project_id') type_id = kwargs.pop('instance_type_id') @@ -656,7 +671,8 @@ class NetworkManager(manager.SchedulerDependentManager): self._allocate_fixed_ips(admin_context, instance_id, host, networks, vpn=vpn, requested_networks=requested_networks) - return self.get_instance_nw_info(context, instance_id, type_id, host) + return self.get_instance_nw_info(context, instance_id, instance_uuid, + type_id, host) def deallocate_for_instance(self, context, **kwargs): """Handles deallocating various network resources for an instance. @@ -679,7 +695,7 @@ class NetworkManager(manager.SchedulerDependentManager): # deallocate vifs (mac addresses) self.db.virtual_interface_delete_by_instance(context, instance_id) - def get_instance_nw_info(self, context, instance_id, + def get_instance_nw_info(self, context, instance_id, instance_uuid, instance_type_id, host): """Creates network info list for instance. @@ -773,8 +789,150 @@ class NetworkManager(manager.SchedulerDependentManager): info['dns'].append(network['dns2']) network_info.append((network_dict, info)) + + # update instance network cache and return network_info + nw_info = self.build_network_info_model(context, vifs, fixed_ips, + instance_type) + self.db.instance_info_cache_update(context, instance_uuid, + {'network_info': nw_info.as_cache()}) + + # TODO(tr3buchet): return model return network_info + def build_network_info_model(self, context, vifs, fixed_ips, + instance_type): + """Returns a NetworkInfo object containing all network information + for an instance""" + nw_info = network_model.NetworkInfo() + for vif in vifs: + network = self.db.network_get(context, vif['network_id']) + subnets = self._get_subnets_from_network(network) + + # if rxtx_cap data are not set everywhere, set to none + try: + rxtx_cap = network['rxtx_base'] * instance_type['rxtx_factor'] + except (TypeError, KeyError): + rxtx_cap = None + + # determine which of the instance's fixed IPs are on this network + network_IPs = [fixed_ip['address'] for fixed_ip in fixed_ips if + fixed_ip['network_id'] == network['id']] + + # create model FixedIPs from these fixed_ips + network_IPs = [network_model.FixedIP(address=ip_address) + for ip_address in network_IPs] + + # get floating_ips for each fixed_ip + # add them to the fixed ip + for fixed_ip in network_IPs: + fipgbfa = self.db.floating_ip_get_by_fixed_address + floating_ips = fipgbfa(context, fixed_ip['address']) + floating_ips = [network_model.IP(address=ip['address'], + type='floating') + for ip in floating_ips] + for ip in floating_ips: + fixed_ip.add_floating_ip(ip) + + # at this point nova networks can only have 2 subnets, + # one for v4 and one for v6, all ips will belong to the v4 subnet + # and the v6 subnet contains a single calculated v6 address + for subnet in subnets: + if subnet['version'] == 4: + # since subnet currently has no IPs, easily add them all + subnet['ips'] = network_IPs + else: + v6_addr = ipv6.to_global(subnet['cidr'], vif['address'], + context.project_id) + subnet.add_ip(network_model.FixedIP(address=v6_addr)) + + # convert network into a Network model object + network = network_model.Network(**self._get_network_dict(network)) + + # since network currently has no subnets, easily add them all + network['subnets'] = subnets + + # create the vif model and add to network_info + vif_dict = {'id': vif['uuid'], + 'address': vif['address'], + 'network': network} + if rxtx_cap: + vif_dict['rxtx_cap'] = rxtx_cap + + vif = network_model.VIF(**vif_dict) + nw_info.append(vif) + + return nw_info + + def _get_network_dict(self, network): + """Returns the dict representing necessary fields from network""" + network_dict = {'id': network['uuid'], + 'bridge': network['bridge'], + 'label': network['label']} + + if network['injected']: + network_dict['injected'] = network['injected'] + if network['vlan']: + network_dict['vlan'] = network['vlan'] + if network['bridge_interface']: + network_dict['bridge_interface'] = network['bridge_interface'] + if network['multi_host']: + network_dict['multi_host'] = network['multi_host'] + + return network_dict + + def _get_subnets_from_network(self, network): + """Returns the 1 or 2 possible subnets for a nova network""" + subnets = [] + + # get dns information from network + dns = [] + if network['dns1']: + dns.append(network_model.IP(address=network['dns1'], type='dns')) + if network['dns2']: + dns.append(network_model.IP(address=network['dns2'], type='dns')) + + # if network contains v4 subnet + if network['cidr']: + subnet = network_model.Subnet(cidr=network['cidr'], + gateway=network_model.IP( + address=network['gateway'], + type='gateway')) + # if either dns address is v4, add it to subnet + for ip in dns: + if ip['version'] == 4: + subnet.add_dns(ip) + + # TODO(tr3buchet): add routes to subnet once it makes sense + # create default route from gateway + #route = network_model.Route(cidr=network['cidr'], + # gateway=network['gateway']) + #subnet.add_route(route) + + # store subnet for return + subnets.append(subnet) + + # if network contains a v6 subnet + if network['cidr_v6']: + subnet = network_model.Subnet(cidr=network['cidr_v6'], + gateway=network_model.IP( + address=network['gateway_v6'], + type='gateway')) + # if either dns address is v6, add it to subnet + for entry in dns: + if entry['version'] == 6: + subnet.add_dns(entry) + + # TODO(tr3buchet): add routes to subnet once it makes sense + # create default route from gateway + #route = network_model.Route(cidr=network['cidr_v6'], + # gateway=network['gateway_v6']) + #subnet.add_route(route) + + # store subnet for return + subnets.append(subnet) + + return subnets + def _allocate_mac_addresses(self, context, instance_id, networks): """Generates mac addresses and creates vif rows in db for them.""" for network in networks: diff --git a/nova/policy.py b/nova/policy.py new file mode 100644 index 000000000..2ebe6a69c --- /dev/null +++ b/nova/policy.py @@ -0,0 +1,78 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 OpenStack, LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Policy Engine For Nova""" + +from nova import exception +from nova import flags +from nova import utils +from nova.common import policy + +FLAGS = flags.FLAGS +flags.DEFINE_string('policy_file', 'policy.json', + _('JSON file representing policy')) + +_POLICY_PATH = None +_POLICY_CACHE = {} + + +def reset(): + global _POLICY_PATH + global _POLICY_CACHE + _POLICY_PATH = None + _POLICY_CACHE = {} + policy.reset() + + +def init(): + global _POLICY_PATH + global _POLICY_CACHE + if not _POLICY_PATH: + _POLICY_PATH = utils.find_config(FLAGS.policy_file) + data = utils.read_cached_file(_POLICY_PATH, _POLICY_CACHE, + reload_func=_set_brain) + + +def _set_brain(data): + policy.set_brain(policy.HttpBrain.load_json(data)) + + +def enforce(context, action, target): + """Verifies that the action is valid on the target in this context. + + :param context: nova context + :param action: string representing the action to be checked + this should be colon separated for clarity. + i.e. compute:create_instance + compute:attach_volume + volume:attach_volume + + :param object: dictionary representing the object of the action + for object creation this should be a dictionary representing the + location of the object e.g. {'project_id': context.project_id} + + :raises: `nova.exception.PolicyNotAllowed` if verification fails. + + """ + init() + match_list = ('rule:%s' % action,) + target_dict = target + credentials_dict = context.to_dict() + try: + policy.enforce(match_list, target_dict, credentials_dict) + except policy.NotAllowed: + raise exception.PolicyNotAllowed(action=action) diff --git a/nova/tests/api/ec2/test_cloud.py b/nova/tests/api/ec2/test_cloud.py index aa07aae1e..6015069bc 100644 --- a/nova/tests/api/ec2/test_cloud.py +++ b/nova/tests/api/ec2/test_cloud.py @@ -1,5 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. @@ -153,7 +154,7 @@ class CloudTestCase(test.TestCase): address = "10.10.10.10" db.floating_ip_create(self.context, {'address': address, - 'host': self.network.host}) + 'pool': 'nova'}) self.cloud.allocate_address(self.context) self.cloud.describe_addresses(self.context) self.cloud.release_address(self.context, @@ -165,7 +166,7 @@ class CloudTestCase(test.TestCase): allocate = self.cloud.allocate_address db.floating_ip_create(self.context, {'address': address, - 'host': self.network.host}) + 'pool': 'nova'}) self.assertEqual(allocate(self.context)['publicIp'], address) db.floating_ip_destroy(self.context, address) self.assertRaises(exception.NoMoreFloatingIps, @@ -177,7 +178,7 @@ class CloudTestCase(test.TestCase): allocate = self.cloud.allocate_address db.floating_ip_create(self.context, {'address': address, - 'host': self.network.host, + 'pool': 'nova', 'project_id': self.project_id}) result = self.cloud.release_address(self.context, address) self.assertEqual(result['releaseResponse'], ['Address released.']) @@ -185,7 +186,9 @@ class CloudTestCase(test.TestCase): def test_associate_disassociate_address(self): """Verifies associate runs cleanly without raising an exception""" address = "10.10.10.10" - db.floating_ip_create(self.context, {'address': address}) + db.floating_ip_create(self.context, + {'address': address, + 'pool': 'nova'}) self.cloud.allocate_address(self.context) # TODO(jkoelker) Probably need to query for instance_type_id and # make sure we get a valid one @@ -199,6 +202,7 @@ class CloudTestCase(test.TestCase): type_id = inst['instance_type_id'] ips = self.network.allocate_for_instance(self.context, instance_id=inst['id'], + instance_uuid='', host=inst['host'], vpn=None, instance_type_id=type_id, 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 = ('<wrapper><a>foo</a><b>bar</b></wrapper>') + expected_xml = ("<?xml version='1.0' encoding='UTF-8'?>\n" + '<wrapper><a>foo</a><b>bar</b></wrapper>') 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 = ("<?xml version='1.0' encoding='UTF-8'?>\n" '<cloudpipe><project_id>4321</project_id></cloudpipe>') - 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 = ("<?xml version='1.0' encoding='UTF-8'?>\n" '<extra_specs><key1>value1</key1></extra_specs>') 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_ip_pools.py b/nova/tests/api/openstack/v2/contrib/test_floating_ip_pools.py new file mode 100644 index 000000000..d061f9af3 --- /dev/null +++ b/nova/tests/api/openstack/v2/contrib/test_floating_ip_pools.py @@ -0,0 +1,73 @@ +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from lxml import etree + +from nova.api.openstack.v2.contrib import floating_ip_pools +from nova import context +from nova import network +from nova import test +from nova.tests.api.openstack import fakes + + +def fake_get_floating_ip_pools(self, context): + return [{'name': 'nova'}, + {'name': 'other'}] + + +class FloatingIpPoolTest(test.TestCase): + def setUp(self): + super(FloatingIpPoolTest, self).setUp() + self.stubs.Set(network.api.API, "get_floating_ip_pools", + fake_get_floating_ip_pools) + + self.context = context.RequestContext('fake', 'fake') + self.controller = floating_ip_pools.FloatingIPPoolsController() + + def test_translate_floating_ip_pools_view(self): + pools = fake_get_floating_ip_pools(None, self.context) + view = floating_ip_pools._translate_floating_ip_pools_view(pools) + self.assertTrue('floating_ip_pools' in view) + self.assertEqual(view['floating_ip_pools'][0]['name'], + pools[0]['name']) + self.assertEqual(view['floating_ip_pools'][1]['name'], + pools[1]['name']) + + def test_floating_ips_pools_list(self): + req = fakes.HTTPRequest.blank('/v2/fake/os-floating-ip-pools') + res_dict = self.controller.index(req) + + pools = fake_get_floating_ip_pools(None, self.context) + response = {'floating_ip_pools': pools} + self.assertEqual(res_dict, response) + + +class FloatingIpPoolSerializerTest(test.TestCase): + def test_index_serializer(self): + serializer = floating_ip_pools.FloatingIPPoolsSerializer() + text = serializer.serialize(dict( + floating_ip_pools=[ + dict(name='nova'), + dict(name='other') + ]), 'index') + + tree = etree.fromstring(text) + + self.assertEqual('floating_ip_pools', tree.tag) + self.assertEqual(2, len(tree)) + self.assertEqual('floating_ip_pool', tree[0].tag) + self.assertEqual('floating_ip_pool', tree[1].tag) + self.assertEqual('nova', tree[0].get('name')) + self.assertEqual('other', tree[1].get('name')) 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..ffdace53c 100644 --- a/nova/tests/api/openstack/v2/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/v2/contrib/test_floating_ips.py @@ -1,3 +1,4 @@ +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2011 Eldar Nugaev # All Rights Reserved. # @@ -30,11 +31,13 @@ FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' def network_api_get_floating_ip(self, context, id): return {'id': 1, 'address': '10.10.10.10', + 'pool': 'nova', 'fixed_ip': None} def network_api_get_floating_ip_by_address(self, context, address): return {'id': 1, 'address': '10.10.10.10', + 'pool': 'nova', 'fixed_ip': {'address': '10.0.0.1', 'instance': {'uuid': FAKE_UUID}}} @@ -42,9 +45,11 @@ def network_api_get_floating_ip_by_address(self, context, address): def network_api_get_floating_ips_by_project(self, context): return [{'id': 1, 'address': '10.10.10.10', + 'pool': 'nova', 'fixed_ip': {'address': '10.0.0.1', 'instance': {'uuid': FAKE_UUID}}}, {'id': 2, + 'pool': 'nova', 'interface': 'eth0', 'address': '10.10.10.11'}] @@ -107,6 +112,7 @@ class FloatingIpTest(test.TestCase): host = "fake_host" return db.floating_ip_create(self.context, {'address': self.floating_ip, + 'pool': 'nova', 'host': host}) def _delete_floating_ip(self): @@ -151,7 +157,8 @@ class FloatingIpTest(test.TestCase): self.assertEqual(view['floating_ip']['instance_id'], None) def test_translate_floating_ip_view_dict(self): - floating_ip = {'id': 0, 'address': '10.0.0.10', 'fixed_ip': None} + floating_ip = {'id': 0, 'address': '10.0.0.10', 'pool': 'nova', + 'fixed_ip': None} view = floating_ips._translate_floating_ip_view(floating_ip) self.assertTrue('floating_ip' in view) @@ -161,10 +168,12 @@ class FloatingIpTest(test.TestCase): response = {'floating_ips': [{'instance_id': FAKE_UUID, 'ip': '10.10.10.10', + 'pool': 'nova', 'fixed_ip': '10.0.0.1', 'id': 1}, {'instance_id': None, 'ip': '10.10.10.11', + 'pool': 'nova', 'fixed_ip': None, 'id': 2}]} self.assertEqual(res_dict, response) @@ -180,6 +189,7 @@ class FloatingIpTest(test.TestCase): def test_show_associated_floating_ip(self): def get_floating_ip(self, context, id): return {'id': 1, 'address': '10.10.10.10', + 'pool': 'nova', 'fixed_ip': {'address': '10.0.0.1', 'instance': {'uuid': FAKE_UUID}}} self.stubs.Set(network.api.API, "get_floating_ip", get_floating_ip) @@ -207,7 +217,7 @@ class FloatingIpTest(test.TestCase): pass def fake2(*args, **kwargs): - return {'id': 1, 'address': '10.10.10.10'} + return {'id': 1, 'address': '10.10.10.10', 'pool': 'nova'} self.stubs.Set(network.api.API, "allocate_floating_ip", fake1) @@ -223,7 +233,8 @@ class FloatingIpTest(test.TestCase): "id": 1, "instance_id": None, "ip": "10.10.10.10", - "fixed_ip": None} + "fixed_ip": None, + "pool": 'nova'} self.assertEqual(ip, expected) def test_floating_ip_release(self): @@ -273,7 +284,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 +301,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 +311,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 = ("<?xml version='1.0' encoding='UTF-8'?>\n" '<updates><status>enabled</status><foo>bar</foo></updates>') - 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): <ip_protocol>tcp</ip_protocol> <cidr>10.0.0.0/24</cidr> </security_group_rule>""" - 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): <group_id></group_id> <cidr>10.0.0.0/24</cidr> </security_group_rule>""" - 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): <security_group name="test"> <description>test</description> </security_group>""" - 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 = """ <security_group name="test"> </security_group>""" - 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): <security_group> <description>test</description> </security_group>""" - 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_extensions.py b/nova/tests/api/openstack/v2/test_extensions.py index 51af89da3..70a936727 100644 --- a/nova/tests/api/openstack/v2/test_extensions.py +++ b/nova/tests/api/openstack/v2/test_extensions.py @@ -1,5 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2011 OpenStack LLC. # All Rights Reserved. # @@ -110,6 +111,7 @@ class ExtensionControllerTest(ExtensionTestCase): "FlavorExtraData", "Floating_ips", "Floating_ip_dns", + "Floating_ip_pools", "Fox In Socks", "Hosts", "Keypairs", 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("<?xml version='1.0' encoding='UTF-8'?>") 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("<?xml version='1.0' encoding='UTF-8'?>") 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("<?xml version='1.0' encoding='UTF-8'?>") 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): <metadata/> <personality/> </server>""" - 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): <meta key="open">snack</meta> </metadata> </server>""" - 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): <file path="/etc/hosts">Mg==</file> </personality> </server>""" - 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): <file path="/etc/banner.txt">Mg==</file> </personality> </server>""" % (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"> <networks/> </server>""" - 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): <network uuid="1" fixed_ip="10.0.1.12"/> </networks> </server>""" - 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): <network uuid="2" fixed_ip="10.0.2.12"/> </networks> </server>""" - 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): <network uuid="2" fixed_ip="10.0.2.12"/> </networks> </server>""" - 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): <network fixed_ip="10.0.1.12"/> </networks> </server>""" - 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): <network uuid="1"/> </networks> </server>""" - 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): <network uuid="" fixed_ip="10.0.1.12"/> </networks> </server>""" - 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): <network uuid="1" fixed_ip=""/> </networks> </server>""" - 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): <network uuid="1" fixed_ip="10.0.2.12"/> </networks> </server>""" - 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("<?xml version='1.0' encoding='UTF-8'?>") 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("<?xml version='1.0' encoding='UTF-8'?>") 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') diff --git a/nova/tests/db/fakes.py b/nova/tests/db/fakes.py index a28d6ded0..ef3162eb4 100644 --- a/nova/tests/db/fakes.py +++ b/nova/tests/db/fakes.py @@ -1,5 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 OpenStack, LLC # All Rights Reserved. # @@ -88,6 +89,7 @@ def stub_out_db_network_api(stubs): 'fixed_ip_id': None, 'fixed_ip': None, 'project_id': None, + 'pool': 'nova', 'auto_assigned': False} virtual_interface_fields = {'id': 0, @@ -101,9 +103,10 @@ def stub_out_db_network_api(stubs): virtual_interfacees = [virtual_interface_fields] networks = [network_fields] - def fake_floating_ip_allocate_address(context, project_id): + def fake_floating_ip_allocate_address(context, project_id, pool): ips = filter(lambda i: i['fixed_ip_id'] is None \ - and i['project_id'] is None, + and i['project_id'] is None \ + and i['pool'] == pool, floating_ips) if not ips: raise exception.NoMoreFloatingIps() diff --git a/nova/tests/fake_flags.py b/nova/tests/fake_flags.py index fc7bb059a..b95aa77d0 100644 --- a/nova/tests/fake_flags.py +++ b/nova/tests/fake_flags.py @@ -41,3 +41,5 @@ FLAGS['sqlite_db'].SetDefault("tests.sqlite") FLAGS['use_ipv6'].SetDefault(True) FLAGS['flat_network_bridge'].SetDefault('br100') FLAGS['sqlite_synchronous'].SetDefault(False) +flags.DECLARE('policy_file', 'nova.policy') +FLAGS['policy_file'].SetDefault('nova/tests/policy.json') diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py index e1af85239..03c12ed37 100644 --- a/nova/tests/fake_network.py +++ b/nova/tests/fake_network.py @@ -143,25 +143,26 @@ def fake_network(network_id, ipv6=None): if ipv6 is None: ipv6 = FLAGS.use_ipv6 fake_network = {'id': network_id, - 'label': 'test%d' % network_id, - 'injected': False, - 'multi_host': False, - 'cidr': '192.168.%d.0/24' % network_id, - 'cidr_v6': None, - 'netmask': '255.255.255.0', - 'netmask_v6': None, - 'bridge': 'fake_br%d' % network_id, - 'bridge_interface': 'fake_eth%d' % network_id, - 'gateway': '192.168.%d.1' % network_id, - 'gateway_v6': None, - 'broadcast': '192.168.%d.255' % network_id, - 'dns1': '192.168.%d.3' % network_id, - 'dns2': '192.168.%d.4' % network_id, - 'vlan': None, - 'host': None, - 'project_id': 'fake_project', - 'vpn_public_address': '192.168.%d.2' % network_id, - 'rxtx_base': '%d' % network_id * 10} + 'uuid': '00000000-0000-0000-0000-00000000000000%02d' % network_id, + 'label': 'test%d' % network_id, + 'injected': False, + 'multi_host': False, + 'cidr': '192.168.%d.0/24' % network_id, + 'cidr_v6': None, + 'netmask': '255.255.255.0', + 'netmask_v6': None, + 'bridge': 'fake_br%d' % network_id, + 'bridge_interface': 'fake_eth%d' % network_id, + 'gateway': '192.168.%d.1' % network_id, + 'gateway_v6': None, + 'broadcast': '192.168.%d.255' % network_id, + 'dns1': '192.168.%d.3' % network_id, + 'dns2': '192.168.%d.4' % network_id, + 'vlan': None, + 'host': None, + 'project_id': 'fake_project', + 'vpn_public_address': '192.168.%d.2' % network_id, + 'rxtx_base': '%d' % network_id * 10} if ipv6: fake_network['cidr_v6'] = '2001:db8:0:%x::/64' % network_id fake_network['gateway_v6'] = '2001:db8:0:%x::1' % network_id @@ -248,6 +249,9 @@ def fake_get_instance_nw_info(stubs, num_networks=1, ips_per_vif=2, return [next_fixed_ip(i, floating_ips_per_fixed_ip) for i in xrange(num_networks) for j in xrange(ips_per_vif)] + def floating_ips_fake(*args, **kwargs): + return [] + def virtual_interfaces_fake(*args, **kwargs): return [vif for vif in vifs(num_networks)] @@ -260,9 +264,18 @@ def fake_get_instance_nw_info(stubs, num_networks=1, ips_per_vif=2, raise exception.NetworkNotFound(network_id=network_id) return nets[0] + def update_cache_fake(*args, **kwargs): + pass + stubs.Set(db, 'fixed_ip_get_by_instance', fixed_ips_fake) + stubs.Set(db, 'floating_ip_get_by_fixed_address', floating_ips_fake) stubs.Set(db, 'virtual_interface_get_by_instance', virtual_interfaces_fake) stubs.Set(db, 'instance_type_get', instance_type_fake) stubs.Set(db, 'network_get', network_get_fake) + stubs.Set(db, 'instance_info_cache_update', update_cache_fake) + + class FakeContext(object): + def __init__(self): + self.project_id = 1 - return network.get_instance_nw_info(None, 0, 0, None) + return network.get_instance_nw_info(FakeContext(), 0, 0, 0, None) diff --git a/nova/tests/policy.json b/nova/tests/policy.json new file mode 100644 index 000000000..47c3d870e --- /dev/null +++ b/nova/tests/policy.json @@ -0,0 +1,11 @@ +{ + "true" : [], + "compute:create_instance" : [], + "compute:attach_network" : [], + "compute:attach_volume" : [], + "compute:list_instances": [], + "compute:get_instance": [], + "network:attach_network" : [], + "volume:create_volume": [], + "volume:attach_volume": [] +} diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index fb38e7b98..f2098de56 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -817,6 +817,7 @@ class ComputeTestCase(BaseTestCase): instance['uuid']) self.compute.terminate_instance(self.context, instance['uuid']) + @test.skip_test('test fails: lp892005') def test_instance_set_to_error_on_uncaught_exception(self): """Test that instance is set to error state when exception is raised""" instance = self._create_fake_instance() @@ -845,6 +846,7 @@ class ComputeTestCase(BaseTestCase): self.compute.terminate_instance(self.context, instance['uuid']) + @test.skip_test('test fails: lp892005') def test_network_is_deallocated_on_spawn_failure(self): """When a spawn fails the network must be deallocated""" instance = self._create_fake_instance() diff --git a/nova/tests/test_misc.py b/nova/tests/test_misc.py index 6bee4022f..0a673e68e 100644 --- a/nova/tests/test_misc.py +++ b/nova/tests/test_misc.py @@ -47,7 +47,8 @@ class ProjectTestCase(test.TestCase): missing = set() contributors = set() mailmap = parse_mailmap(os.path.join(topdir, '.mailmap')) - authors_file = open(os.path.join(topdir, 'Authors'), 'r').read() + authors_file = open(os.path.join(topdir, + 'Authors'), 'r').read().lower() if os.path.exists(os.path.join(topdir, '.git')): for email in commands.getoutput('git log --format=%ae').split(): @@ -55,7 +56,7 @@ class ProjectTestCase(test.TestCase): continue if "jenkins" in email and "openstack.org" in email: continue - email = '<' + email + '>' + email = '<' + email.lower() + '>' contributors.add(str_dict_replace(email, mailmap)) else: return diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 4eee3c8d4..274d5dd86 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright 2011 Rackspace +# Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -108,6 +109,8 @@ flavor = {'id': 0, floating_ip_fields = {'id': 0, 'address': '192.168.10.100', + 'pool': 'nova', + 'interface': 'eth0', 'fixed_ip_id': 0, 'project_id': None, 'auto_assigned': False} @@ -580,21 +583,29 @@ class VlanNetworkTestCase(test.TestCase): # floating ip that's already associated def fake2(*args, **kwargs): return {'address': '10.0.0.1', + 'pool': 'nova', + 'interface': 'eth0', 'fixed_ip_id': 1} # floating ip that isn't associated def fake3(*args, **kwargs): return {'address': '10.0.0.1', + 'pool': 'nova', + 'interface': 'eth0', 'fixed_ip_id': None} # fixed ip with remote host def fake4(*args, **kwargs): return {'address': '10.0.0.1', + 'pool': 'nova', + 'interface': 'eth0', 'network': {'multi_host': False, 'host': 'jibberjabber'}} # fixed ip with local host def fake5(*args, **kwargs): return {'address': '10.0.0.1', + 'pool': 'nova', + 'interface': 'eth0', 'network': {'multi_host': False, 'host': 'testhost'}} def fake6(*args, **kwargs): @@ -641,21 +652,29 @@ class VlanNetworkTestCase(test.TestCase): # floating ip that isn't associated def fake2(*args, **kwargs): return {'address': '10.0.0.1', + 'pool': 'nova', + 'interface': 'eth0', 'fixed_ip_id': None} # floating ip that is associated def fake3(*args, **kwargs): return {'address': '10.0.0.1', + 'pool': 'nova', + 'interface': 'eth0', 'fixed_ip_id': 1} # fixed ip with remote host def fake4(*args, **kwargs): return {'address': '10.0.0.1', + 'pool': 'nova', + 'interface': 'eth0', 'network': {'multi_host': False, 'host': 'jibberjabber'}} # fixed ip with local host def fake5(*args, **kwargs): return {'address': '10.0.0.1', + 'pool': 'nova', + 'interface': 'eth0', 'network': {'multi_host': False, 'host': 'testhost'}} def fake6(*args, **kwargs): diff --git a/nova/tests/test_policy.py b/nova/tests/test_policy.py new file mode 100644 index 000000000..39dca2ae7 --- /dev/null +++ b/nova/tests/test_policy.py @@ -0,0 +1,139 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Piston Cloud Computing, Inc. +# All Rights Reserved. + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +"""Test of Policy Engine For Nova""" + +import StringIO +import tempfile +import urllib2 + +from nova.common import policy as common_policy +from nova import context +from nova import exception +from nova import flags +from nova import policy +from nova import test + +FLAGS = flags.FLAGS + + +class PolicyFileTestCase(test.TestCase): + def setUp(self): + super(PolicyFileTestCase, self).setUp() + policy.reset() + _, self.tmpfilename = tempfile.mkstemp() + self.flags(policy_file=self.tmpfilename) + self.context = context.RequestContext('fake', 'fake') + self.target = {} + + def tearDown(self): + super(PolicyFileTestCase, self).tearDown() + policy.reset() + + def test_modified_policy_reloads(self): + action = "example:test" + with open(self.tmpfilename, "w") as policyfile: + policyfile.write("""{"example:test": []}""") + policy.enforce(self.context, action, self.target) + with open(self.tmpfilename, "w") as policyfile: + policyfile.write("""{"example:test": ["false:false"]}""") + # NOTE(vish): reset stored policy cache so we don't have to sleep(1) + policy._POLICY_CACHE = {} + self.assertRaises(exception.PolicyNotAllowed, policy.enforce, + self.context, action, self.target) + + +class PolicyTestCase(test.TestCase): + def setUp(self): + super(PolicyTestCase, self).setUp() + policy.reset() + # NOTE(vish): preload rules to circumvent reloading from file + policy.init() + rules = { + "true": [], + "example:allowed": [], + "example:denied": [["false:false"]], + "example:get_http": [["http:http://www.example.com"]], + "example:my_file": [["role:compute_admin"], + ["project_id:%(project_id)s"]], + "example:early_and_fail": [["false:false", "rule:true"]], + "example:early_or_success": [["rule:true"], ["false:false"]], + "example:sysadmin_allowed": [["role:admin"], ["role:sysadmin"]], + } + # NOTE(vish): then overload underlying brain + common_policy.set_brain(common_policy.HttpBrain(rules)) + self.context = context.RequestContext('fake', 'fake', roles=['member']) + self.admin_context = context.RequestContext('admin', + 'fake', + roles=['admin'], + is_admin=True) + self.target = {} + + def tearDown(self): + policy.reset() + super(PolicyTestCase, self).tearDown() + + def test_enforce_nonexistent_action_throws(self): + action = "example:noexist" + self.assertRaises(exception.PolicyNotAllowed, policy.enforce, + self.context, action, self.target) + + def test_enforce_bad_action_throws(self): + action = "example:denied" + self.assertRaises(exception.PolicyNotAllowed, policy.enforce, + self.context, action, self.target) + + def test_enforce_good_action(self): + action = "example:allowed" + policy.enforce(self.context, action, self.target) + + def test_enforce_http_true(self): + + def fakeurlopen(url, post_data): + return StringIO.StringIO("True") + self.stubs.Set(urllib2, 'urlopen', fakeurlopen) + action = "example:get_http" + target = {} + result = policy.enforce(self.context, action, target) + self.assertEqual(result, None) + + def test_enforce_http_false(self): + + def fakeurlopen(url, post_data): + return StringIO.StringIO("False") + self.stubs.Set(urllib2, 'urlopen', fakeurlopen) + action = "example:get_http" + target = {} + self.assertRaises(exception.PolicyNotAllowed, policy.enforce, + self.context, action, target) + + def test_templatized_enforcement(self): + target_mine = {'project_id': 'fake'} + target_not_mine = {'project_id': 'another'} + action = "example:my_file" + policy.enforce(self.context, action, target_mine) + self.assertRaises(exception.PolicyNotAllowed, policy.enforce, + self.context, action, target_not_mine) + + def test_early_AND_enforcement(self): + action = "example:early_and_fail" + self.assertRaises(exception.PolicyNotAllowed, policy.enforce, + self.context, action, self.target) + + def test_early_OR_enforcement(self): + action = "example:early_or_success" + policy.enforce(self.context, action, self.target) diff --git a/nova/tests/test_utils.py b/nova/tests/test_utils.py index 92376f1f2..d221212df 100644 --- a/nova/tests/test_utils.py +++ b/nova/tests/test_utils.py @@ -345,20 +345,32 @@ class GenericUtilsTestCase(test.TestCase): def test_read_modified_cached_file(self): self.mox.StubOutWithMock(os.path, "getmtime") self.mox.StubOutWithMock(__builtin__, 'open') - os.path.getmtime(mox.IgnoreArg()).AndReturn(2) fake_contents = "lorem ipsum" fake_file = self.mox.CreateMockAnything() fake_file.read().AndReturn(fake_contents) - __builtin__.open(mox.IgnoreArg()).AndReturn(fake_file) + fake_context_manager = self.mox.CreateMockAnything() + fake_context_manager.__enter__().AndReturn(fake_file) + fake_context_manager.__exit__(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()) + + __builtin__.open(mox.IgnoreArg()).AndReturn(fake_context_manager) self.mox.ReplayAll() cache_data = {"data": 1123, "mtime": 1} - data = utils.read_cached_file("/this/is/a/fake", cache_data) - self.mox.VerifyAll() + self.reload_called = False + + def test_reload(reloaded_data): + self.assertEqual(reloaded_data, fake_contents) + self.reload_called = True + + data = utils.read_cached_file("/this/is/a/fake", cache_data, + reload_func=test_reload) self.mox.UnsetStubs() self.assertEqual(data, fake_contents) + self.assertTrue(self.reload_called) def test_generate_password(self): password = utils.generate_password() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 90a4dcba6..2b9f977cc 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -619,11 +619,12 @@ class XenAPIVMTestCase(test.TestCase): self.network.set_network_host(ctxt, network) self.network.allocate_for_instance(ctxt, - instance_id=2, - host=FLAGS.host, - vpn=None, - instance_type_id=1, - project_id=self.project_id) + instance_id=2, + instance_uuid="00000000-0000-0000-0000-000000000000", + host=FLAGS.host, + vpn=None, + instance_type_id=1, + project_id=self.project_id) self._test_spawn(glance_stubs.FakeGlance.IMAGE_MACHINE, glance_stubs.FakeGlance.IMAGE_KERNEL, glance_stubs.FakeGlance.IMAGE_RAMDISK, diff --git a/nova/utils.py b/nova/utils.py index efefc5fd1..f34db4aba 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -543,7 +543,7 @@ def parse_mailmap(mailmap='.mailmap'): l = l.strip() if not l.startswith('#') and ' ' in l: canonical_email, alias = l.split(' ') - mapping[alias] = canonical_email + mapping[alias.lower()] = canonical_email.lower() return mapping @@ -689,6 +689,14 @@ def to_primitive(value, convert_instances=False, level=0): if test(value): return unicode(value) + # FIXME(vish): Workaround for LP bug 852095. Without this workaround, + # tests that raise an exception in a mocked method that + # has a @wrap_exception with a notifier will fail. If + # we up the dependency to 0.5.4 (when it is released) we + # can remove this workaround. + if getattr(value, '__module__', None) == 'mox': + return 'mock' + if level > 3: return '?' @@ -1168,18 +1176,24 @@ def sanitize_hostname(hostname): return hostname -def read_cached_file(filename, cache_info): - """Return the contents of a file. If the file hasn't changed since the - last invocation, a cached version will be returned. +def read_cached_file(filename, cache_info, reload_func=None): + """Read from a file if it has been modified. + + :param cache_info: dictionary to hold opaque cache. + :param reload_func: optional function to be called with data when + file is reloaded due to a modification. + + :returns: data from file + """ mtime = os.path.getmtime(filename) - if cache_info and mtime == cache_info.get('mtime', None): - return cache_info['data'] - - data = open(filename).read() - cache_info['data'] = data - cache_info['mtime'] = mtime - return data + if not cache_info or mtime != cache_info.get('mtime'): + with open(filename) as fap: + cache_info['data'] = fap.read() + cache_info['mtime'] = mtime + if reload_func: + reload_func(cache_info['data']) + return cache_info['data'] @contextlib.contextmanager diff --git a/smoketests/test_netadmin.py b/smoketests/test_netadmin.py index c7db64f42..d097d232a 100644 --- a/smoketests/test_netadmin.py +++ b/smoketests/test_netadmin.py @@ -161,20 +161,23 @@ class SecurityGroupTests(base.UserSmokeTestCase): result = self.conn.associate_address(self.data['instance'].id, self.data['public_ip']) start_time = time.time() - try: - while not self.__public_instance_is_accessible(): - # 1 minute to launch - if time.time() - start_time > 60: - raise Exception("Timeout") - time.sleep(1) - finally: - result = self.conn.disassociate_address(self.data['public_ip']) + while not self.__public_instance_is_accessible(): + # 1 minute to launch + if time.time() - start_time > 60: + raise Exception("Timeout") + time.sleep(1) def test_005_validate_metadata(self): - instance = self.data['instance'] - self.assertTrue(instance.instance_type, - self.__get_metadata_item("instance-type")) + start_time = time.time() + instance_type = False + while not instance_type: + instance_type = self.__get_metadata_item('instance-type') + # 10 seconds to restart proxy + if time.time() - start_time > 10: + raise Exception("Timeout") + time.sleep(1) + self.assertEqual(instance.instance_type, instance_type) #FIXME(dprince): validate more metadata here def test_006_can_revoke_security_group_ingress(self): @@ -190,6 +193,7 @@ class SecurityGroupTests(base.UserSmokeTestCase): time.sleep(1) def test_999_tearDown(self): + self.conn.disassociate_address(self.data['public_ip']) self.conn.delete_key_pair(TEST_KEY) self.conn.delete_security_group(TEST_GROUP) groups = self.conn.get_all_security_groups() |