diff options
-rw-r--r-- | etc/nova/policy.json | 1 | ||||
-rw-r--r-- | nova/api/openstack/compute/contrib/hypervisors.py | 192 | ||||
-rw-r--r-- | nova/db/api.py | 10 | ||||
-rw-r--r-- | nova/db/sqlalchemy/api.py | 13 | ||||
-rw-r--r-- | nova/tests/api/openstack/compute/contrib/test_hypervisors.py | 400 | ||||
-rw-r--r-- | nova/tests/policy.json | 1 |
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": [], |