summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xbin/nova-manage20
-rw-r--r--nova/api/ec2/apirequest.py8
-rw-r--r--nova/api/openstack/common.py15
-rw-r--r--nova/api/openstack/v2/__init__.py4
-rw-r--r--nova/api/openstack/v2/consoles.py84
-rw-r--r--nova/api/openstack/v2/contrib/accounts.py37
-rw-r--r--nova/api/openstack/v2/contrib/cloudpipe.py45
-rw-r--r--nova/api/openstack/v2/contrib/createserverext.py17
-rw-r--r--nova/api/openstack/v2/contrib/extended_status.py13
-rw-r--r--nova/api/openstack/v2/contrib/flavorextraspecs.py26
-rw-r--r--nova/api/openstack/v2/contrib/floating_ip_dns.py99
-rw-r--r--nova/api/openstack/v2/contrib/floating_ip_pools.py104
-rw-r--r--nova/api/openstack/v2/contrib/floating_ips.py77
-rw-r--r--nova/api/openstack/v2/contrib/hosts.py122
-rw-r--r--nova/api/openstack/v2/contrib/keypairs.py40
-rw-r--r--nova/api/openstack/v2/contrib/quotas.py38
-rw-r--r--nova/api/openstack/v2/contrib/security_groups.py317
-rw-r--r--nova/api/openstack/v2/contrib/server_action_list.py17
-rw-r--r--nova/api/openstack/v2/contrib/server_diagnostics.py15
-rw-r--r--nova/api/openstack/v2/contrib/simple_tenant_usage.py84
-rw-r--r--nova/api/openstack/v2/contrib/users.py65
-rw-r--r--nova/api/openstack/v2/contrib/virtual_interfaces.py39
-rw-r--r--nova/api/openstack/v2/contrib/virtual_storage_arrays.py256
-rw-r--r--nova/api/openstack/v2/contrib/volumes.py245
-rw-r--r--nova/api/openstack/v2/contrib/volumetypes.py111
-rw-r--r--nova/api/openstack/v2/contrib/zones.py113
-rw-r--r--nova/api/openstack/v2/extensions.py80
-rw-r--r--nova/api/openstack/v2/flavors.py94
-rw-r--r--nova/api/openstack/v2/image_metadata.py23
-rw-r--r--nova/api/openstack/v2/images.py114
-rw-r--r--nova/api/openstack/v2/ips.py70
-rw-r--r--nova/api/openstack/v2/limits.py46
-rw-r--r--nova/api/openstack/v2/server_metadata.py23
-rw-r--r--nova/api/openstack/v2/servers.py640
-rw-r--r--nova/api/openstack/v2/versions.py169
-rw-r--r--nova/api/openstack/wsgi.py340
-rw-r--r--nova/api/openstack/xmlutil.py4
-rw-r--r--nova/common/policy.py202
-rw-r--r--nova/db/api.py18
-rw-r--r--nova/db/sqlalchemy/api.py34
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/066_preload_instance_info_cache_table.py239
-rw-r--r--nova/db/sqlalchemy/migrate_repo/versions/067_add_pool_and_interface_to_floating_ip.py46
-rw-r--r--nova/db/sqlalchemy/models.py5
-rw-r--r--nova/exception.py4
-rw-r--r--nova/network/api.py15
-rwxr-xr-xnova/network/linux_net.py11
-rw-r--r--nova/network/manager.py198
-rw-r--r--nova/policy.py78
-rw-r--r--nova/tests/api/ec2/test_cloud.py12
-rw-r--r--nova/tests/api/openstack/test_wsgi.py248
-rw-r--r--nova/tests/api/openstack/test_xmlutil.py3
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_cloudpipe.py20
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_flavors_extra_specs.py2
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_floating_ip_dns.py10
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_floating_ip_pools.py73
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_floating_ips.py23
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_hosts.py12
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_keypairs.py7
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_quotas.py2
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_security_groups.py20
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_server_action_list.py42
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_server_diagnostics.py31
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_simple_tenant_usage.py10
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_snapshots.py8
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_users.py8
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_virtual_interfaces.py2
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_volume_types.py12
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_volume_types_extra_specs.py13
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_volumes.py17
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_vsa.py32
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_zones.py14
-rw-r--r--nova/tests/api/openstack/v2/test_consoles.py7
-rw-r--r--nova/tests/api/openstack/v2/test_extensions.py2
-rw-r--r--nova/tests/api/openstack/v2/test_flavors.py24
-rw-r--r--nova/tests/api/openstack/v2/test_images.py44
-rw-r--r--nova/tests/api/openstack/v2/test_limits.py12
-rw-r--r--nova/tests/api/openstack/v2/test_server_actions.py13
-rw-r--r--nova/tests/api/openstack/v2/test_servers.py174
-rw-r--r--nova/tests/api/openstack/v2/test_versions.py14
-rw-r--r--nova/tests/db/fakes.py7
-rw-r--r--nova/tests/fake_flags.py2
-rw-r--r--nova/tests/fake_network.py53
-rw-r--r--nova/tests/policy.json11
-rw-r--r--nova/tests/test_compute.py2
-rw-r--r--nova/tests/test_misc.py5
-rw-r--r--nova/tests/test_network.py19
-rw-r--r--nova/tests/test_policy.py139
-rw-r--r--nova/tests/test_utils.py20
-rw-r--r--nova/tests/test_xenapi.py11
-rw-r--r--nova/utils.py36
-rw-r--r--smoketests/test_netadmin.py26
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()