summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--etc/nova/policy.json1
-rw-r--r--nova/api/openstack/compute/contrib/hypervisors.py192
-rw-r--r--nova/db/api.py10
-rw-r--r--nova/db/sqlalchemy/api.py13
-rw-r--r--nova/tests/api/openstack/compute/contrib/test_hypervisors.py400
-rw-r--r--nova/tests/policy.json1
6 files changed, 615 insertions, 2 deletions
diff --git a/etc/nova/policy.json b/etc/nova/policy.json
index f61c4dcac..7d86a9d3e 100644
--- a/etc/nova/policy.json
+++ b/etc/nova/policy.json
@@ -41,6 +41,7 @@
"compute_extension:floating_ip_pools": [],
"compute_extension:floating_ips": [],
"compute_extension:hosts": [["rule:admin_api"]],
+ "compute_extension:hypervisors": [["rule:admin_api"]],
"compute_extension:keypairs": [],
"compute_extension:multinic": [],
"compute_extension:networks": [["rule:admin_api"]],
diff --git a/nova/api/openstack/compute/contrib/hypervisors.py b/nova/api/openstack/compute/contrib/hypervisors.py
new file mode 100644
index 000000000..6686de5a9
--- /dev/null
+++ b/nova/api/openstack/compute/contrib/hypervisors.py
@@ -0,0 +1,192 @@
+# Copyright (c) 2012 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.
+
+"""The hypervisors admin extension."""
+
+import webob.exc
+
+from nova.api.openstack import extensions
+from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
+from nova import db
+from nova import exception
+from nova import log as logging
+
+
+LOG = logging.getLogger(__name__)
+authorize = extensions.extension_authorizer('compute', 'hypervisors')
+
+
+def make_hypervisor(elem, detail):
+ elem.set('hypervisor_hostname')
+ elem.set('id')
+ if detail:
+ elem.set('vcpus')
+ elem.set('memory_mb')
+ elem.set('local_gb')
+ elem.set('vcpus_used')
+ elem.set('memory_mb_used')
+ elem.set('local_gb_used')
+ elem.set('hypervisor_type')
+ elem.set('hypervisor_version')
+ elem.set('free_ram_mb')
+ elem.set('free_disk_gb')
+ elem.set('current_workload')
+ elem.set('running_vms')
+ elem.set('cpu_info')
+ elem.set('disk_available_least')
+
+ service = xmlutil.SubTemplateElement(elem, 'service',
+ selector='service')
+ service.set('id')
+ service.set('host')
+
+
+class HypervisorIndexTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('hypervisors')
+ elem = xmlutil.SubTemplateElement(root, 'hypervisor',
+ selector='hypervisors')
+ make_hypervisor(elem, False)
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class HypervisorDetailTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('hypervisors')
+ elem = xmlutil.SubTemplateElement(root, 'hypervisor',
+ selector='hypervisors')
+ make_hypervisor(elem, True)
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class HypervisorTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('hypervisor', selector='hypervisor')
+ make_hypervisor(root, True)
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class HypervisorServersTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('hypervisors')
+ elem = xmlutil.SubTemplateElement(root, 'hypervisor',
+ selector='hypervisors')
+ make_hypervisor(elem, False)
+
+ servers = xmlutil.SubTemplateElement(elem, 'servers')
+ server = xmlutil.SubTemplateElement(servers, 'server',
+ selector='servers')
+ server.set('name')
+ server.set('uuid')
+
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class HypervisorsController(object):
+ """The Hypervisors API controller for the OpenStack API."""
+
+ def _view_hypervisor(self, hypervisor, detail, servers=None):
+ hyp_dict = {
+ 'id': hypervisor['id'],
+ 'hypervisor_hostname': hypervisor['hypervisor_hostname'],
+ }
+
+ if detail and not servers:
+ for field in ('vcpus', 'memory_mb', 'local_gb', 'vcpus_used',
+ 'memory_mb_used', 'local_gb_used',
+ 'hypervisor_type', 'hypervisor_version',
+ 'free_ram_mb', 'free_disk_gb', 'current_workload',
+ 'running_vms', 'cpu_info', 'disk_available_least'):
+ hyp_dict[field] = hypervisor[field]
+
+ hyp_dict['service'] = {
+ 'id': hypervisor['service_id'],
+ 'host': hypervisor['service']['host'],
+ }
+
+ if servers:
+ hyp_dict['servers'] = [dict(name=serv['name'], uuid=serv['uuid'])
+ for serv in servers]
+
+ return hyp_dict
+
+ @wsgi.serializers(xml=HypervisorIndexTemplate)
+ def index(self, req):
+ context = req.environ['nova.context']
+ authorize(context)
+ return dict(hypervisors=[self._view_hypervisor(hyp, False)
+ for hyp in db.compute_node_get_all(context)])
+
+ @wsgi.serializers(xml=HypervisorDetailTemplate)
+ def detail(self, req):
+ context = req.environ['nova.context']
+ authorize(context)
+ return dict(hypervisors=[self._view_hypervisor(hyp, True)
+ for hyp in db.compute_node_get_all(context)])
+
+ @wsgi.serializers(xml=HypervisorTemplate)
+ def show(self, req, id):
+ context = req.environ['nova.context']
+ authorize(context)
+ try:
+ hyp = db.compute_node_get(context, int(id))
+ except (ValueError, exception.ComputeHostNotFound):
+ msg = _("Hypervisor with ID '%s' could not be found.") % id
+ raise webob.exc.HTTPNotFound(explanation=msg)
+ return dict(hypervisor=self._view_hypervisor(hyp, True))
+
+ @wsgi.serializers(xml=HypervisorIndexTemplate)
+ def search(self, req, id):
+ context = req.environ['nova.context']
+ authorize(context)
+ hypervisors = db.compute_node_search_by_hypervisor(context, id)
+ if hypervisors:
+ return dict(hypervisors=[self._view_hypervisor(hyp, False)
+ for hyp in hypervisors])
+ else:
+ msg = _("No hypervisor matching '%s' could be found.") % id
+ raise webob.exc.HTTPNotFound(explanation=msg)
+
+ @wsgi.serializers(xml=HypervisorServersTemplate)
+ def servers(self, req, id):
+ context = req.environ['nova.context']
+ authorize(context)
+ hypervisors = db.compute_node_search_by_hypervisor(context, id)
+ if hypervisors:
+ return dict(hypervisors=[self._view_hypervisor(hyp, False,
+ db.instance_get_all_by_host(context,
+ hyp['service']['host']))
+ for hyp in hypervisors])
+ else:
+ msg = _("No hypervisor matching '%s' could be found.") % id
+ raise webob.exc.HTTPNotFound(explanation=msg)
+
+
+class Hypervisors(extensions.ExtensionDescriptor):
+ """Admin-only hypervisor administration"""
+
+ name = "Hypervisors"
+ alias = "os-hypervisors"
+ namespace = "http://docs.openstack.org/compute/ext/hypervisors/api/v1.1"
+ updated = "2012-06-21T00:00:00+00:00"
+
+ def get_resources(self):
+ resources = [extensions.ResourceExtension('os-hypervisors',
+ HypervisorsController(),
+ collection_actions={'detail': 'GET'},
+ member_actions={'search': 'GET', 'servers': 'GET'})]
+
+ return resources
diff --git a/nova/db/api.py b/nova/db/api.py
index dc859f748..989864a2b 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -188,11 +188,21 @@ def service_update(context, service_id, values):
###################
+def compute_node_get(context, compute_id):
+ """Get a computeNode."""
+ return IMPL.compute_node_get(context, compute_id)
+
+
def compute_node_get_all(context):
"""Get all computeNodes."""
return IMPL.compute_node_get_all(context)
+def compute_node_search_by_hypervisor(context, hypervisor_match):
+ """Get computeNodes given a hypervisor hostname match string."""
+ return IMPL.compute_node_search_by_hypervisor(context, hypervisor_match)
+
+
def compute_node_create(context, values):
"""Create a computeNode from the values dictionary."""
return IMPL.compute_node_create(context, values)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 55effd645..d573690cc 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -473,7 +473,7 @@ def service_update(context, service_id, values):
###################
-def _compute_node_get(context, compute_id, session=None):
+def compute_node_get(context, compute_id, session=None):
result = model_query(context, models.ComputeNode, session=session).\
filter_by(id=compute_id).\
first()
@@ -491,6 +491,15 @@ def compute_node_get_all(context, session=None):
all()
+@require_admin_context
+def compute_node_search_by_hypervisor(context, hypervisor_match):
+ field = models.ComputeNode.hypervisor_hostname
+ return model_query(context, models.ComputeNode).\
+ options(joinedload('service')).\
+ filter(field.like('%%%s%%' % hypervisor_match)).\
+ all()
+
+
def _get_host_utilization(context, host, ram_mb, disk_gb):
"""Compute the current utilization of a given host."""
instances = instance_get_all_by_host(context, host)
@@ -542,7 +551,7 @@ def compute_node_update(context, compute_id, values, auto_adjust):
if auto_adjust:
_adjust_compute_node_values_for_utilization(context, values, session)
with session.begin(subtransactions=True):
- compute_ref = _compute_node_get(context, compute_id, session=session)
+ compute_ref = compute_node_get(context, compute_id, session=session)
compute_ref.update(values)
compute_ref.save(session=session)
diff --git a/nova/tests/api/openstack/compute/contrib/test_hypervisors.py b/nova/tests/api/openstack/compute/contrib/test_hypervisors.py
new file mode 100644
index 000000000..b00497ecc
--- /dev/null
+++ b/nova/tests/api/openstack/compute/contrib/test_hypervisors.py
@@ -0,0 +1,400 @@
+# Copyright (c) 2012 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.
+
+from lxml import etree
+from webob import exc
+
+from nova.api.openstack.compute.contrib import hypervisors
+from nova import context
+from nova import db
+from nova import exception
+from nova import test
+from nova.tests.api.openstack import fakes
+
+
+TEST_HYPERS = [
+ dict(id=1,
+ service_id=1,
+ service=dict(id=1,
+ host="compute1",
+ binary="nova-compute",
+ topic="compute_topic",
+ report_count=5,
+ disabled=False,
+ availability_zone="nova"),
+ vcpus=4,
+ memory_mb=10 * 1024,
+ local_gb=250,
+ vcpus_used=2,
+ memory_mb_used=5 * 1024,
+ local_gb_used=125,
+ hypervisor_type="xen",
+ hypervisor_version=3,
+ hypervisor_hostname="hyper1",
+ free_ram_mb=5 * 1024,
+ free_disk_gb=125,
+ current_workload=2,
+ running_vms=2,
+ cpu_info='cpu_info',
+ disk_available_least=100),
+ dict(id=2,
+ service_id=2,
+ service=dict(id=2,
+ host="compute2",
+ binary="nova-compute",
+ topic="compute_topic",
+ report_count=5,
+ disabled=False,
+ availability_zone="nova"),
+ vcpus=4,
+ memory_mb=10 * 1024,
+ local_gb=250,
+ vcpus_used=2,
+ memory_mb_used=5 * 1024,
+ local_gb_used=125,
+ hypervisor_type="xen",
+ hypervisor_version=3,
+ hypervisor_hostname="hyper2",
+ free_ram_mb=5 * 1024,
+ free_disk_gb=125,
+ current_workload=2,
+ running_vms=2,
+ cpu_info='cpu_info',
+ disk_available_least=100)]
+TEST_SERVERS = [dict(name="inst1", uuid="uuid1", host="compute1"),
+ dict(name="inst2", uuid="uuid2", host="compute2"),
+ dict(name="inst3", uuid="uuid3", host="compute1"),
+ dict(name="inst4", uuid="uuid4", host="compute2")]
+
+
+def fake_compute_node_get_all(context):
+ return TEST_HYPERS
+
+
+def fake_compute_node_search_by_hypervisor(context, hypervisor_re):
+ return TEST_HYPERS
+
+
+def fake_compute_node_get(context, compute_id):
+ for hyper in TEST_HYPERS:
+ if hyper['id'] == compute_id:
+ return hyper
+ raise exception.ComputeHostNotFound
+
+
+def fake_instance_get_all_by_host(context, host):
+ results = []
+ for inst in TEST_SERVERS:
+ if inst['host'] == host:
+ results.append(inst)
+ return results
+
+
+class HypervisorsTest(test.TestCase):
+ def setUp(self):
+ super(HypervisorsTest, self).setUp()
+ self.context = context.get_admin_context()
+ self.controller = hypervisors.HypervisorsController()
+
+ self.stubs.Set(db, 'compute_node_get_all', fake_compute_node_get_all)
+ self.stubs.Set(db, 'compute_node_search_by_hypervisor',
+ fake_compute_node_search_by_hypervisor)
+ self.stubs.Set(db, 'compute_node_get',
+ fake_compute_node_get)
+ self.stubs.Set(db, 'instance_get_all_by_host',
+ fake_instance_get_all_by_host)
+
+ def test_view_hypervisor_nodetail_noservers(self):
+ result = self.controller._view_hypervisor(TEST_HYPERS[0], False)
+
+ self.assertEqual(result, dict(id=1, hypervisor_hostname="hyper1"))
+
+ def test_view_hypervisor_detail_noservers(self):
+ result = self.controller._view_hypervisor(TEST_HYPERS[0], True)
+
+ self.assertEqual(result, dict(
+ id=1,
+ hypervisor_hostname="hyper1",
+ vcpus=4,
+ memory_mb=10 * 1024,
+ local_gb=250,
+ vcpus_used=2,
+ memory_mb_used=5 * 1024,
+ local_gb_used=125,
+ hypervisor_type="xen",
+ hypervisor_version=3,
+ free_ram_mb=5 * 1024,
+ free_disk_gb=125,
+ current_workload=2,
+ running_vms=2,
+ cpu_info='cpu_info',
+ disk_available_least=100,
+ service=dict(id=1, host='compute1')))
+
+ def test_view_hypervisor_servers(self):
+ result = self.controller._view_hypervisor(TEST_HYPERS[0], False,
+ TEST_SERVERS)
+
+ self.assertEqual(result, dict(
+ id=1,
+ hypervisor_hostname="hyper1",
+ servers=[
+ dict(name="inst1", uuid="uuid1"),
+ dict(name="inst2", uuid="uuid2"),
+ dict(name="inst3", uuid="uuid3"),
+ dict(name="inst4", uuid="uuid4")]))
+
+ def test_index(self):
+ req = fakes.HTTPRequest.blank('/v2/fake/os-hypervisors')
+ result = self.controller.index(req)
+
+ self.assertEqual(result, dict(hypervisors=[
+ dict(id=1, hypervisor_hostname="hyper1"),
+ dict(id=2, hypervisor_hostname="hyper2")]))
+
+ def test_detail(self):
+ req = fakes.HTTPRequest.blank('/v2/fake/os-hypervisors/detail')
+ result = self.controller.detail(req)
+
+ self.assertEqual(result, dict(hypervisors=[
+ dict(id=1,
+ service=dict(id=1, host="compute1"),
+ vcpus=4,
+ memory_mb=10 * 1024,
+ local_gb=250,
+ vcpus_used=2,
+ memory_mb_used=5 * 1024,
+ local_gb_used=125,
+ hypervisor_type="xen",
+ hypervisor_version=3,
+ hypervisor_hostname="hyper1",
+ free_ram_mb=5 * 1024,
+ free_disk_gb=125,
+ current_workload=2,
+ running_vms=2,
+ cpu_info='cpu_info',
+ disk_available_least=100),
+ dict(id=2,
+ service=dict(id=2, host="compute2"),
+ vcpus=4,
+ memory_mb=10 * 1024,
+ local_gb=250,
+ vcpus_used=2,
+ memory_mb_used=5 * 1024,
+ local_gb_used=125,
+ hypervisor_type="xen",
+ hypervisor_version=3,
+ hypervisor_hostname="hyper2",
+ free_ram_mb=5 * 1024,
+ free_disk_gb=125,
+ current_workload=2,
+ running_vms=2,
+ cpu_info='cpu_info',
+ disk_available_least=100)]))
+
+ def test_show_noid(self):
+ req = fakes.HTTPRequest.blank('/v2/fake/os-hypervisors/3')
+ self.assertRaises(exc.HTTPNotFound, self.controller.show, req, '3')
+
+ def test_show_withid(self):
+ req = fakes.HTTPRequest.blank('/v2/fake/os-hypervisors/1')
+ result = self.controller.show(req, '1')
+
+ self.assertEqual(result, dict(hypervisor=dict(
+ id=1,
+ service=dict(id=1, host="compute1"),
+ vcpus=4,
+ memory_mb=10 * 1024,
+ local_gb=250,
+ vcpus_used=2,
+ memory_mb_used=5 * 1024,
+ local_gb_used=125,
+ hypervisor_type="xen",
+ hypervisor_version=3,
+ hypervisor_hostname="hyper1",
+ free_ram_mb=5 * 1024,
+ free_disk_gb=125,
+ current_workload=2,
+ running_vms=2,
+ cpu_info='cpu_info',
+ disk_available_least=100)))
+
+ def test_search(self):
+ req = fakes.HTTPRequest.blank('/v2/fake/os-hypervisors/hyper/search')
+ result = self.controller.search(req, 'hyper')
+
+ self.assertEqual(result, dict(hypervisors=[
+ dict(id=1, hypervisor_hostname="hyper1"),
+ dict(id=2, hypervisor_hostname="hyper2")]))
+
+ def test_servers(self):
+ req = fakes.HTTPRequest.blank('/v2/fake/os-hypervisors/hyper/servers')
+ result = self.controller.servers(req, 'hyper')
+
+ self.assertEqual(result, dict(hypervisors=[
+ dict(id=1,
+ hypervisor_hostname="hyper1",
+ servers=[
+ dict(name="inst1", uuid="uuid1"),
+ dict(name="inst3", uuid="uuid3")]),
+ dict(id=2,
+ hypervisor_hostname="hyper2",
+ servers=[
+ dict(name="inst2", uuid="uuid2"),
+ dict(name="inst4", uuid="uuid4")])]))
+
+
+class HypervisorsSerializersTest(test.TestCase):
+ def compare_to_exemplar(self, exemplar, hyper):
+ self.assertEqual('hypervisor', hyper.tag)
+
+ # Check attributes
+ for key, value in exemplar.items():
+ if key in ('service', 'servers'):
+ # These turn into child elements and get tested
+ # separately below...
+ continue
+
+ self.assertEqual(str(value), hyper.get(key))
+
+ # Check child elements
+ required_children = set([child for child in ('service', 'servers')
+ if child in exemplar])
+ for child in hyper:
+ self.assertTrue(child.tag in required_children)
+ required_children.remove(child.tag)
+
+ # Check the node...
+ if child.tag == 'service':
+ for key, value in exemplar['service'].items():
+ self.assertEqual(str(value), child.get(key))
+ elif child.tag == 'servers':
+ for idx, grandchild in enumerate(child):
+ self.assertEqual('server', grandchild.tag)
+ for key, value in exemplar['servers'][idx].items():
+ self.assertEqual(str(value), grandchild.get(key))
+
+ # Are they all accounted for?
+ self.assertEqual(len(required_children), 0)
+
+ def test_index_serializer(self):
+ serializer = hypervisors.HypervisorIndexTemplate()
+ exemplar = dict(hypervisors=[
+ dict(hypervisor_hostname="hyper1",
+ id=1),
+ dict(hypervisor_hostname="hyper2",
+ id=2)])
+ text = serializer.serialize(exemplar)
+ tree = etree.fromstring(text)
+
+ self.assertEqual('hypervisors', tree.tag)
+ self.assertEqual(len(exemplar['hypervisors']), len(tree))
+ for idx, hyper in enumerate(tree):
+ self.compare_to_exemplar(exemplar['hypervisors'][idx], hyper)
+
+ def test_detail_serializer(self):
+ serializer = hypervisors.HypervisorDetailTemplate()
+ exemplar = dict(hypervisors=[
+ dict(hypervisor_hostname="hyper1",
+ id=1,
+ vcpus=4,
+ memory_mb=10 * 1024,
+ local_gb=500,
+ vcpus_used=2,
+ memory_mb_used=5 * 1024,
+ local_gb_used=250,
+ hypervisor_type='xen',
+ hypervisor_version=3,
+ free_ram_mb=5 * 1024,
+ free_disk_gb=250,
+ current_workload=2,
+ running_vms=2,
+ cpu_info="json data",
+ disk_available_least=100,
+ service=dict(id=1, host="compute1")),
+ dict(hypervisor_hostname="hyper2",
+ id=2,
+ vcpus=4,
+ memory_mb=10 * 1024,
+ local_gb=500,
+ vcpus_used=2,
+ memory_mb_used=5 * 1024,
+ local_gb_used=250,
+ hypervisor_type='xen',
+ hypervisor_version=3,
+ free_ram_mb=5 * 1024,
+ free_disk_gb=250,
+ current_workload=2,
+ running_vms=2,
+ cpu_info="json data",
+ disk_available_least=100,
+ service=dict(id=2, host="compute2"))])
+ text = serializer.serialize(exemplar)
+ tree = etree.fromstring(text)
+
+ self.assertEqual('hypervisors', tree.tag)
+ self.assertEqual(len(exemplar['hypervisors']), len(tree))
+ for idx, hyper in enumerate(tree):
+ self.compare_to_exemplar(exemplar['hypervisors'][idx], hyper)
+
+ def test_show_serializer(self):
+ serializer = hypervisors.HypervisorTemplate()
+ exemplar = dict(hypervisor=dict(
+ hypervisor_hostname="hyper1",
+ id=1,
+ vcpus=4,
+ memory_mb=10 * 1024,
+ local_gb=500,
+ vcpus_used=2,
+ memory_mb_used=5 * 1024,
+ local_gb_used=250,
+ hypervisor_type='xen',
+ hypervisor_version=3,
+ free_ram_mb=5 * 1024,
+ free_disk_gb=250,
+ current_workload=2,
+ running_vms=2,
+ cpu_info="json data",
+ disk_available_least=100,
+ service=dict(id=1, host="compute1")))
+ text = serializer.serialize(exemplar)
+ tree = etree.fromstring(text)
+
+ self.compare_to_exemplar(exemplar['hypervisor'], tree)
+
+ def test_servers_serializer(self):
+ serializer = hypervisors.HypervisorServersTemplate()
+ exemplar = dict(hypervisors=[
+ dict(hypervisor_hostname="hyper1",
+ id=1,
+ servers=[
+ dict(name="inst1",
+ uuid="uuid1"),
+ dict(name="inst2",
+ uuid="uuid2")]),
+ dict(hypervisor_hostname="hyper2",
+ id=2,
+ servers=[
+ dict(name="inst3",
+ uuid="uuid3"),
+ dict(name="inst4",
+ uuid="uuid4")])])
+ text = serializer.serialize(exemplar)
+ tree = etree.fromstring(text)
+
+ self.assertEqual('hypervisors', tree.tag)
+ self.assertEqual(len(exemplar['hypervisors']), len(tree))
+ for idx, hyper in enumerate(tree):
+ self.compare_to_exemplar(exemplar['hypervisors'][idx], hyper)
diff --git a/nova/tests/policy.json b/nova/tests/policy.json
index e6b534f5a..206cb574a 100644
--- a/nova/tests/policy.json
+++ b/nova/tests/policy.json
@@ -97,6 +97,7 @@
"compute_extension:floating_ip_pools": [],
"compute_extension:floating_ips": [],
"compute_extension:hosts": [],
+ "compute_extension:hypervisors": [],
"compute_extension:keypairs": [],
"compute_extension:multinic": [],
"compute_extension:networks": [],