From a11788515e800a95d5b83448c2a9403eed509bdf Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Wed, 27 Jun 2012 18:45:28 -0500 Subject: [PATCH] Add hypervisor information extension. Adds support for a new nova extension for getting information about hypervisors (as opposed to compute hosts), including a list of hypervisors matching a regular expression (database regular expression, i.e., %'s) and a list of hypervisors with the list of instances living on those hypervisors. Change-Id: I7353991ffbf484da175a0912ee46e80f623e230f --- novaclient/v1_1/client.py | 2 + novaclient/v1_1/hypervisors.py | 58 ++++++++++++++ novaclient/v1_1/shell.py | 54 +++++++++++++ tests/v1_1/fakes.py | 86 ++++++++++++++++++++ tests/v1_1/test_hypervisors.py | 138 +++++++++++++++++++++++++++++++++ tests/v1_1/test_shell.py | 16 ++++ 6 files changed, 354 insertions(+) create mode 100644 novaclient/v1_1/hypervisors.py create mode 100644 tests/v1_1/test_hypervisors.py diff --git a/novaclient/v1_1/client.py b/novaclient/v1_1/client.py index 153640896..ba3bf7ae7 100644 --- a/novaclient/v1_1/client.py +++ b/novaclient/v1_1/client.py @@ -7,6 +7,7 @@ from novaclient.v1_1 import floating_ip_dns from novaclient.v1_1 import floating_ips from novaclient.v1_1 import floating_ip_pools from novaclient.v1_1 import hosts +from novaclient.v1_1 import hypervisors from novaclient.v1_1 import images from novaclient.v1_1 import keypairs from novaclient.v1_1 import limits @@ -76,6 +77,7 @@ class Client(object): virtual_interfaces.VirtualInterfaceManager(self) self.aggregates = aggregates.AggregateManager(self) self.hosts = hosts.HostManager(self) + self.hypervisors = hypervisors.HypervisorManager(self) # Add in any extensions... if extensions: diff --git a/novaclient/v1_1/hypervisors.py b/novaclient/v1_1/hypervisors.py new file mode 100644 index 000000000..9f56ae05f --- /dev/null +++ b/novaclient/v1_1/hypervisors.py @@ -0,0 +1,58 @@ +# Copyright 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. + +""" +Hypervisors interface (1.1 extension). +""" + +import urllib + +from novaclient import base + + +class Hypervisor(base.Resource): + def __repr__(self): + return "" % self.id + + +class HypervisorManager(base.Manager): + resource_class = Hypervisor + + def list(self, detailed=True): + """ + Get a list of hypervisors. + """ + detail = "" + if detailed: + detail = "/detail" + return self._list('/os-hypervisors%s' % detail, 'hypervisors') + + def search(self, hypervisor_match, servers=False): + """ + Get a list of matching hypervisors. + + :param servers: If True, server information is also retrieved. + """ + target = 'servers' if servers else 'search' + url = ('/os-hypervisors/%s/%s' % + (urllib.quote(hypervisor_match, safe=''), target)) + return self._list(url, 'hypervisors') + + def get(self, hypervisor): + """ + Get a specific hypervisor. + """ + return self._get("/os-hypervisors/%s" % base.getid(hypervisor), + "hypervisor") diff --git a/novaclient/v1_1/shell.py b/novaclient/v1_1/shell.py index 8d92e249c..faf9d655f 100644 --- a/novaclient/v1_1/shell.py +++ b/novaclient/v1_1/shell.py @@ -1649,6 +1649,60 @@ def do_host_action(cs, args): utils.print_list([result], ['HOST', 'power_action']) +@utils.arg('--matching', metavar='', default=None, + help='List hypervisors matching the given .') +def do_hypervisor_list(cs, args): + """List hypervisors.""" + columns = ['ID', 'Hypervisor hostname'] + if args.matching: + utils.print_list(cs.hypervisors.search(args.matching), columns) + else: + # Since we're not outputting detail data, choose + # detailed=False for server-side efficiency + utils.print_list(cs.hypervisors.list(False), columns) + + +@utils.arg('hostname', metavar='', + help='The hypervisor hostname (or pattern) to search for.') +def do_hypervisor_servers(cs, args): + """List instances belonging to specific hypervisors.""" + hypers = cs.hypervisors.search(args.hostname, servers=True) + + class InstanceOnHyper(object): + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + # Massage the result into a list to be displayed + instances = [] + for hyper in hypers: + hyper_host = hyper.hypervisor_hostname + hyper_id = hyper.id + instances.extend([InstanceOnHyper(id=serv['uuid'], + name=serv['name'], + hypervisor_hostname=hyper_host, + hypervisor_id=hyper_id) + for serv in hyper.servers]) + + # Output the data + utils.print_list(instances, ['ID', 'Name', 'Hypervisor ID', + 'Hypervisor Hostname']) + + +@utils.arg('hypervisor_id', metavar='', + help='The ID of the hypervisor to show the details of.') +def do_hypervisor_show(cs, args): + """Display the details of the specified hypervisor.""" + hyper = utils.find_resource(cs.hypervisors, args.hypervisor_id) + + # Build up the dict + info = hyper._info.copy() + info['service_id'] = info['service']['id'] + info['service_host'] = info['service']['host'] + del info['service'] + + utils.print_dict(info) + + def do_endpoints(cs, _args): """Discover endpoints that get returned from the authenticate services""" catalog = cs.client.service_catalog.catalog diff --git a/tests/v1_1/fakes.py b/tests/v1_1/fakes.py index f2112f23d..4a7c3aa1e 100644 --- a/tests/v1_1/fakes.py +++ b/tests/v1_1/fakes.py @@ -822,3 +822,89 @@ class FakeHTTPClient(base_client.HTTPClient): result = {'host': 'dummy'} result.update(body) return (200, result) + + def get_os_hypervisors(self, **kw): + return (200, {"hypervisors": [ + {'id': 1234, 'hypervisor_hostname': 'hyper1'}, + {'id': 5678, 'hypervisor_hostname': 'hyper2'}, + ]}) + + def get_os_hypervisors_detail(self, **kw): + return (200, {"hypervisors": [ + {'id': 1234, + 'service': {'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}, + {'id': 2, + 'service': {'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 get_os_hypervisors_hyper_search(self, **kw): + return (200, {'hypervisors': [ + {'id': 1234, 'hypervisor_hostname': 'hyper1'}, + {'id': 5678, 'hypervisor_hostname': 'hyper2'} + ]}) + + def get_os_hypervisors_hyper_servers(self, **kw): + return (200, {'hypervisors': [ + {'id': 1234, + 'hypervisor_hostname': 'hyper1', + 'servers': [ + {'name': 'inst1', 'uuid': 'uuid1'}, + {'name': 'inst2', 'uuid': 'uuid2'} + ]}, + {'id': 5678, + 'hypervisor_hostname': 'hyper2', + 'servers': [ + {'name': 'inst3', 'uuid': 'uuid3'}, + {'name': 'inst4', 'uuid': 'uuid4'} + ]} + ]}) + + def get_os_hypervisors_1234(self, **kw): + return (200, {'hypervisor': + {'id': 1234, + 'service': {'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}}) diff --git a/tests/v1_1/test_hypervisors.py b/tests/v1_1/test_hypervisors.py new file mode 100644 index 000000000..2febadaef --- /dev/null +++ b/tests/v1_1/test_hypervisors.py @@ -0,0 +1,138 @@ +# Copyright 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 tests import utils +from tests.v1_1 import fakes + + +cs = fakes.FakeClient() + + +class HypervisorsTest(utils.TestCase): + def compare_to_expected(self, expected, hyper): + for key, value in expected.items(): + self.assertEqual(getattr(hyper, key), value) + + def test_hypervisor_index(self): + expected = [ + dict(id=1234, hypervisor_hostname='hyper1'), + dict(id=5678, hypervisor_hostname='hyper2'), + ] + + result = cs.hypervisors.list(False) + cs.assert_called('GET', '/os-hypervisors') + + for idx, hyper in enumerate(result): + self.compare_to_expected(expected[idx], hyper) + + def test_hypervisor_detail(self): + expected = [ + dict(id=1234, + 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)] + + result = cs.hypervisors.list() + cs.assert_called('GET', '/os-hypervisors/detail') + + for idx, hyper in enumerate(result): + self.compare_to_expected(expected[idx], hyper) + + def test_hypervisor_search(self): + expected = [ + dict(id=1234, hypervisor_hostname='hyper1'), + dict(id=5678, hypervisor_hostname='hyper2'), + ] + + result = cs.hypervisors.search('hyper') + cs.assert_called('GET', '/os-hypervisors/hyper/search') + + for idx, hyper in enumerate(result): + self.compare_to_expected(expected[idx], hyper) + + def test_hypervisor_servers(self): + expected = [ + dict(id=1234, + hypervisor_hostname='hyper1', + servers=[ + dict(name='inst1', uuid='uuid1'), + dict(name='inst2', uuid='uuid2')]), + dict(id=5678, + hypervisor_hostname='hyper2', + servers=[ + dict(name='inst3', uuid='uuid3'), + dict(name='inst4', uuid='uuid4')]), + ] + + result = cs.hypervisors.search('hyper', True) + cs.assert_called('GET', '/os-hypervisors/hyper/servers') + + for idx, hyper in enumerate(result): + self.compare_to_expected(expected[idx], hyper) + + def test_hypervisor_get(self): + expected = dict( + id=1234, + 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) + + result = cs.hypervisors.get(1234) + cs.assert_called('GET', '/os-hypervisors/1234') + + self.compare_to_expected(expected, result) diff --git a/tests/v1_1/test_shell.py b/tests/v1_1/test_shell.py index e790cc9e5..b31070fb7 100644 --- a/tests/v1_1/test_shell.py +++ b/tests/v1_1/test_shell.py @@ -476,6 +476,22 @@ class ShellTest(utils.TestCase): self.run_command('host-action sample-host --action reboot') self.assert_called('GET', '/os-hosts/sample-host/reboot') + def test_hypervisor_list(self): + self.run_command('hypervisor-list') + self.assert_called('GET', '/os-hypervisors') + + def test_hypervisor_list_matching(self): + self.run_command('hypervisor-list --matching hyper') + self.assert_called('GET', '/os-hypervisors/hyper/search') + + def test_hypervisor_servers(self): + self.run_command('hypervisor-servers hyper') + self.assert_called('GET', '/os-hypervisors/hyper/servers') + + def test_hypervisor_show(self): + self.run_command('hypervisor-show 1234') + self.assert_called('GET', '/os-hypervisors/1234') + def test_quota_show(self): self.run_command('quota-show test') self.assert_called('GET', '/os-quota-sets/test')