diff --git a/doc/source/user/proxies/compute.rst b/doc/source/user/proxies/compute.rst index 65d7c4916..a4b771551 100644 --- a/doc/source/user/proxies/compute.rst +++ b/doc/source/user/proxies/compute.rst @@ -140,7 +140,8 @@ Hypervisor Operations .. autoclass:: openstack.compute.v2._proxy.Proxy :noindex: - :members: get_hypervisor, find_hypervisor, hypervisors + :members: get_hypervisor, find_hypervisor, hypervisors, + get_hypervisor_uptime Extension Operations ^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/source/user/resources/compute/index.rst b/doc/source/user/resources/compute/index.rst index f597c7d86..e56d2a058 100644 --- a/doc/source/user/resources/compute/index.rst +++ b/doc/source/user/resources/compute/index.rst @@ -12,3 +12,4 @@ Compute Resources v2/server v2/server_interface v2/server_ip + v2/hypervisor diff --git a/doc/source/user/resources/compute/v2/hypervisor.rst b/doc/source/user/resources/compute/v2/hypervisor.rst new file mode 100644 index 000000000..6959db4ac --- /dev/null +++ b/doc/source/user/resources/compute/v2/hypervisor.rst @@ -0,0 +1,12 @@ +openstack.compute.v2.hypervisor +=============================== + +.. automodule:: openstack.compute.v2.hypervisor + +The Hypervisor Class +-------------------- + +The ``Hypervisor`` class inherits from :class:`~openstack.resource.Resource`. + +.. autoclass:: openstack.compute.v2.hypervisor.Hypervisor + :members: diff --git a/openstack/compute/v2/_proxy.py b/openstack/compute/v2/_proxy.py index bf7295df8..5cc6fc187 100644 --- a/openstack/compute/v2/_proxy.py +++ b/openstack/compute/v2/_proxy.py @@ -1362,6 +1362,8 @@ class Proxy(proxy.Proxy): """ return self._list(_server_group.ServerGroup, **query) + # ========== Hypervisors ========== + def hypervisors(self, details=False, **query): """Return a generator of hypervisor @@ -1374,9 +1376,16 @@ class Proxy(proxy.Proxy): :rtype: class: `~openstack.compute.v2.hypervisor.Hypervisor` """ base_path = '/os-hypervisors/detail' if details else None + if ( + 'hypervisor_hostname_pattern' in query + and not utils.supports_microversion(self, '2.53') + ): + # Until 2.53 we need to use other API + base_path = '/os-hypervisors/{pattern}/search'.format( + pattern=query.pop('hypervisor_hostname_pattern')) return self._list(_hypervisor.Hypervisor, base_path=base_path, **query) - def find_hypervisor(self, name_or_id, ignore_missing=True): + def find_hypervisor(self, name_or_id, ignore_missing=True, details=True): """Find a hypervisor from name or id to get the corresponding info :param name_or_id: The name or id of a hypervisor @@ -1386,23 +1395,40 @@ class Proxy(proxy.Proxy): or None """ + list_base_path = '/os-hypervisors/detail' if details else None return self._find(_hypervisor.Hypervisor, name_or_id, + list_base_path=list_base_path, ignore_missing=ignore_missing) def get_hypervisor(self, hypervisor): """Get a single hypervisor :param hypervisor: The value can be the ID of a hypervisor or a - :class:`~openstack.compute.v2.hypervisor.Hypervisor` - instance. + :class:`~openstack.compute.v2.hypervisor.Hypervisor` + instance. :returns: A :class:`~openstack.compute.v2.hypervisor.Hypervisor` object. :raises: :class:`~openstack.exceptions.ResourceNotFound` - when no resource can be found. + when no resource can be found. """ return self._get(_hypervisor.Hypervisor, hypervisor) + def get_hypervisor_uptime(self, hypervisor): + """Get uptime information for hypervisor + + :param hypervisor: The value can be the ID of a hypervisor or a + :class:`~openstack.compute.v2.hypervisor.Hypervisor` + instance. + + :returns: + A :class:`~openstack.compute.v2.hypervisor.Hypervisor` object. + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + """ + hypervisor = self._get_resource(_hypervisor.Hypervisor, hypervisor) + return hypervisor.get_uptime(self) + # ========== Services ========== def update_service_forced_down( diff --git a/openstack/compute/v2/hypervisor.py b/openstack/compute/v2/hypervisor.py index 9c18e7502..ae56abc97 100644 --- a/openstack/compute/v2/hypervisor.py +++ b/openstack/compute/v2/hypervisor.py @@ -10,8 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +import warnings +from openstack import exceptions from openstack import resource +from openstack import utils class Hypervisor(resource.Resource): @@ -27,48 +30,72 @@ class Hypervisor(resource.Resource): 'hypervisor_hostname_pattern', 'with_servers' ) - # Hypervisor id is a UUID starting with 2.53 - _max_microversion = '2.53' + # Lot of attributes are dropped in 2.88 + _max_microversion = '2.88' # Properties - #: Status of hypervisor - status = resource.Body('status') - #: State of hypervisor - state = resource.Body('state') - #: Name of hypervisor - name = resource.Body('hypervisor_hostname') - #: Service details - service_details = resource.Body('service') - #: Count of the VCPUs in use - vcpus_used = resource.Body('vcpus_used') - #: Count of all VCPUs - vcpus = resource.Body('vcpus') - #: Count of the running virtual machines - running_vms = resource.Body('running_vms') + #: Information about the hypervisor's CPU. Up to 2.28 it was string. + cpu_info = resource.Body('cpu_info') + #: IP address of the host + host_ip = resource.Body('host_ip') #: The type of hypervisor hypervisor_type = resource.Body('hypervisor_type') #: Version of the hypervisor hypervisor_version = resource.Body('hypervisor_version') - #: The amount, in gigabytes, of local storage used - local_disk_used = resource.Body('local_gb_used') - #: The amount, in gigabytes, of the local storage device - local_disk_size = resource.Body('local_gb') - #: The amount, in gigabytes, of free space on the local storage device - local_disk_free = resource.Body('free_disk_gb') - #: The amount, in megabytes, of memory - memory_used = resource.Body('memory_mb_used') - #: The amount, in megabytes, of total memory - memory_size = resource.Body('memory_mb') - #: The amount, in megabytes, of available memory - memory_free = resource.Body('free_ram_mb') + #: Name of hypervisor + name = resource.Body('hypervisor_hostname') + #: Service details + service_details = resource.Body('service', type=dict) + #: List of Servers + servers = resource.Body('servers', type=list, list_type=dict) + #: State of hypervisor + state = resource.Body('state') + #: Status of hypervisor + status = resource.Body('status') + #: The total uptime of the hypervisor and information about average load. + #: This attribute is set only when querying uptime explicitly. + uptime = resource.Body('uptime') + + # Attributes deprecated with 2.88 #: Measurement of the hypervisor's current workload - current_workload = resource.Body('current_workload') - #: Information about the hypervisor's CPU - cpu_info = resource.Body('cpu_info') - #: IP address of the host - host_ip = resource.Body('host_ip') + current_workload = resource.Body('current_workload', deprecated=True) #: Disk space available to the scheduler - disk_available = resource.Body("disk_available_least") + disk_available = resource.Body("disk_available_least", deprecated=True) + #: The amount, in gigabytes, of local storage used + local_disk_used = resource.Body('local_gb_used', deprecated=True) + #: The amount, in gigabytes, of the local storage device + local_disk_size = resource.Body('local_gb', deprecated=True) + #: The amount, in gigabytes, of free space on the local storage device + local_disk_free = resource.Body('free_disk_gb', deprecated=True) + #: The amount, in megabytes, of memory + memory_used = resource.Body('memory_mb_used', deprecated=True) + #: The amount, in megabytes, of total memory + memory_size = resource.Body('memory_mb', deprecated=True) + #: The amount, in megabytes, of available memory + memory_free = resource.Body('free_ram_mb', deprecated=True) + #: Count of the running virtual machines + running_vms = resource.Body('running_vms', deprecated=True) + #: Count of the VCPUs in use + vcpus_used = resource.Body('vcpus_used', deprecated=True) + #: Count of all VCPUs + vcpus = resource.Body('vcpus', deprecated=True) + + def get_uptime(self, session): + """Get uptime information for the hypervisor + + Updates uptime attribute of the hypervisor object + """ + warnings.warn( + "This call is deprecated and is only available until Nova 2.88") + if utils.supports_microversion(session, '2.88'): + raise exceptions.SDKException( + 'Hypervisor.get_uptime is not supported anymore') + url = utils.urljoin(self.base_path, self.id, 'uptime') + microversion = self._get_microversion_for(session, 'fetch') + response = session.get( + url, microversion=microversion) + self._translate_response(response) + return self HypervisorDetail = Hypervisor diff --git a/openstack/tests/functional/compute/v2/test_hypervisor.py b/openstack/tests/functional/compute/v2/test_hypervisor.py new file mode 100644 index 000000000..383a51dc8 --- /dev/null +++ b/openstack/tests/functional/compute/v2/test_hypervisor.py @@ -0,0 +1,31 @@ +# 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 openstack.tests.functional import base + + +class TestHypervisor(base.BaseFunctionalTest): + + def setUp(self): + super(TestHypervisor, self).setUp() + + def test_list_hypervisors(self): + rslt = list(self.conn.compute.hypervisors()) + self.assertIsNotNone(rslt) + + rslt = list(self.conn.compute.hypervisors(details=True)) + self.assertIsNotNone(rslt) + + def test_get_find_hypervisors(self): + for hypervisor in self.conn.compute.hypervisors(): + self.conn.compute.get_hypervisor(hypervisor.id) + self.conn.compute.find_hypervisor(hypervisor.id) diff --git a/openstack/tests/unit/compute/v2/test_hypervisor.py b/openstack/tests/unit/compute/v2/test_hypervisor.py index 2a27c9464..b79b6bd80 100644 --- a/openstack/tests/unit/compute/v2/test_hypervisor.py +++ b/openstack/tests/unit/compute/v2/test_hypervisor.py @@ -9,41 +9,79 @@ # 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 copy +from unittest import mock +from keystoneauth1 import adapter + +from openstack import exceptions from openstack.tests.unit import base from openstack.compute.v2 import hypervisor EXAMPLE = { - "status": "enabled", - "service": { - "host": "fake-mini", - "disabled_reason": None, - "id": 6 + "cpu_info": { + "arch": "x86_64", + "model": "Nehalem", + "vendor": "Intel", + "features": [ + "pge", + "clflush" + ], + "topology": { + "cores": 1, + "threads": 1, + "sockets": 4 + } }, + "state": "up", + "status": "enabled", + "servers": [ + { + "name": "test_server1", + "uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" + }, + { + "name": "test_server2", + "uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb" + } + ], + "host_ip": "1.1.1.1", + "hypervisor_hostname": "fake-mini", + "hypervisor_type": "fake", + "hypervisor_version": 1000, + "id": "b1e43b5f-eec1-44e0-9f10-7b4945c0226d", + "uptime": ( + " 08:32:11 up 93 days, 18:25, 12 users, " + "load average: 0.20, 0.12, 0.14"), + "service": { + "host": "043b3cacf6f34c90a7245151fc8ebcda", + "id": "5d343e1d-938e-4284-b98b-6a2b5406ba76", + "disabled_reason": None + }, + # deprecated attributes "vcpus_used": 0, - "hypervisor_type": "QEMU", "local_gb_used": 0, "vcpus": 8, - "hypervisor_hostname": "fake-mini", "memory_mb_used": 512, "memory_mb": 7980, "current_workload": 0, - "state": "up", - "host_ip": "23.253.248.171", - "cpu_info": "some cpu info", "running_vms": 0, "free_disk_gb": 157, - "hypervisor_version": 2000000, "disk_available_least": 140, "local_gb": 157, "free_ram_mb": 7468, - "id": 1 } class TestHypervisor(base.TestCase): + def setUp(self): + super(TestHypervisor, self).setUp() + self.sess = mock.Mock(spec=adapter.Adapter) + self.sess.default_microversion = 1 + self.sess._get_connection = mock.Mock(return_value=self.cloud) + def test_basic(self): sot = hypervisor.Hypervisor() self.assertEqual('hypervisor', sot.resource_key) @@ -62,10 +100,17 @@ class TestHypervisor(base.TestCase): def test_make_it(self): sot = hypervisor.Hypervisor(**EXAMPLE) self.assertEqual(EXAMPLE['id'], sot.id) + self.assertEqual(EXAMPLE['cpu_info'], sot.cpu_info) + self.assertEqual(EXAMPLE['host_ip'], sot.host_ip) + self.assertEqual(EXAMPLE['hypervisor_type'], sot.hypervisor_type) + self.assertEqual(EXAMPLE['hypervisor_version'], sot.hypervisor_version) self.assertEqual(EXAMPLE['hypervisor_hostname'], sot.name) + self.assertEqual(EXAMPLE['service'], sot.service_details) + self.assertEqual(EXAMPLE['servers'], sot.servers) self.assertEqual(EXAMPLE['state'], sot.state) self.assertEqual(EXAMPLE['status'], sot.status) - self.assertEqual(EXAMPLE['service'], sot.service_details) + self.assertEqual(EXAMPLE['uptime'], sot.uptime) + # Verify deprecated attributes self.assertEqual(EXAMPLE['vcpus_used'], sot.vcpus_used) self.assertEqual(EXAMPLE['hypervisor_type'], sot.hypervisor_type) self.assertEqual(EXAMPLE['local_gb_used'], sot.local_disk_used) @@ -74,11 +119,46 @@ class TestHypervisor(base.TestCase): self.assertEqual(EXAMPLE['memory_mb_used'], sot.memory_used) self.assertEqual(EXAMPLE['memory_mb'], sot.memory_size) self.assertEqual(EXAMPLE['current_workload'], sot.current_workload) - self.assertEqual(EXAMPLE['host_ip'], sot.host_ip) - self.assertEqual(EXAMPLE['cpu_info'], sot.cpu_info) self.assertEqual(EXAMPLE['running_vms'], sot.running_vms) self.assertEqual(EXAMPLE['free_disk_gb'], sot.local_disk_free) - self.assertEqual(EXAMPLE['hypervisor_version'], sot.hypervisor_version) self.assertEqual(EXAMPLE['disk_available_least'], sot.disk_available) self.assertEqual(EXAMPLE['local_gb'], sot.local_disk_size) self.assertEqual(EXAMPLE['free_ram_mb'], sot.memory_free) + + @mock.patch('openstack.utils.supports_microversion', autospec=True, + return_value=False) + def test_get_uptime(self, mv_mock): + sot = hypervisor.Hypervisor(**copy.deepcopy(EXAMPLE)) + rsp = { + "hypervisor": { + "hypervisor_hostname": "fake-mini", + "id": sot.id, + "state": "up", + "status": "enabled", + "uptime": "08:32:11 up 93 days, 18:25, 12 users" + } + } + resp = mock.Mock() + resp.body = copy.deepcopy(rsp) + resp.json = mock.Mock(return_value=resp.body) + resp.headers = {} + resp.status_code = 200 + self.sess.get = mock.Mock(return_value=resp) + + hyp = sot.get_uptime(self.sess) + self.sess.get.assert_called_with( + 'os-hypervisors/{id}/uptime'.format(id=sot.id), + microversion=self.sess.default_microversion + ) + self.assertEqual(rsp['hypervisor']['uptime'], hyp.uptime) + self.assertEqual(rsp['hypervisor']['status'], sot.status) + + @mock.patch('openstack.utils.supports_microversion', autospec=True, + return_value=True) + def test_get_uptime_after_2_88(self, mv_mock): + sot = hypervisor.Hypervisor(**copy.deepcopy(EXAMPLE)) + self.assertRaises( + exceptions.SDKException, + sot.get_uptime, + self.sess + ) diff --git a/openstack/tests/unit/compute/v2/test_proxy.py b/openstack/tests/unit/compute/v2/test_proxy.py index 1fc21da07..fb6e05410 100644 --- a/openstack/tests/unit/compute/v2/test_proxy.py +++ b/openstack/tests/unit/compute/v2/test_proxy.py @@ -445,6 +445,74 @@ class TestService(TestComputeProxy): ) +class TestHypervisor(TestComputeProxy): + + def test_hypervisors_not_detailed(self): + self.verify_list(self.proxy.hypervisors, hypervisor.Hypervisor, + method_kwargs={"details": False}) + + def test_hypervisors_detailed(self): + self.verify_list(self.proxy.hypervisors, hypervisor.HypervisorDetail, + method_kwargs={"details": True}) + + @mock.patch('openstack.utils.supports_microversion', autospec=True, + return_value=False) + def test_hypervisors_search_before_253_no_qp(self, sm): + self.verify_list( + self.proxy.hypervisors, + hypervisor.Hypervisor, + method_kwargs={'details': True}, + base_path='/os-hypervisors/detail' + ) + + @mock.patch('openstack.utils.supports_microversion', autospec=True, + return_value=False) + def test_hypervisors_search_before_253(self, sm): + self.verify_list( + self.proxy.hypervisors, + hypervisor.Hypervisor, + method_kwargs={'hypervisor_hostname_pattern': 'substring'}, + base_path='/os-hypervisors/substring/search' + ) + + @mock.patch('openstack.utils.supports_microversion', autospec=True, + return_value=True) + def test_hypervisors_search_after_253(self, sm): + self.verify_list( + self.proxy.hypervisors, + hypervisor.Hypervisor, + method_kwargs={'hypervisor_hostname_pattern': 'substring'}, + base_path=None, + expected_kwargs={'hypervisor_hostname_pattern': 'substring'} + ) + + def test_find_hypervisor_detail(self): + self.verify_find(self.proxy.find_hypervisor, + hypervisor.Hypervisor, + expected_kwargs={ + 'list_base_path': '/os-hypervisors/detail', + 'ignore_missing': False}) + + def test_find_hypervisor_no_detail(self): + self.verify_find(self.proxy.find_hypervisor, + hypervisor.Hypervisor, + method_kwargs={'details': False}, + expected_kwargs={ + 'list_base_path': None, + 'ignore_missing': False}) + + def test_get_hypervisor(self): + self.verify_get(self.proxy.get_hypervisor, + hypervisor.Hypervisor) + + def test_get_hypervisor_uptime(self): + self._verify( + "openstack.compute.v2.hypervisor.Hypervisor.get_uptime", + self.proxy.get_hypervisor_uptime, + method_args=["value"], + expected_args=[]) + + class TestCompute(TestComputeProxy): def test_extension_find(self): self.verify_find(self.proxy.find_extension, extension.Extension) @@ -870,22 +938,6 @@ class TestCompute(TestComputeProxy): def test_server_groups(self): self.verify_list(self.proxy.server_groups, server_group.ServerGroup) - def test_hypervisors_not_detailed(self): - self.verify_list(self.proxy.hypervisors, hypervisor.Hypervisor, - method_kwargs={"details": False}) - - def test_hypervisors_detailed(self): - self.verify_list(self.proxy.hypervisors, hypervisor.HypervisorDetail, - method_kwargs={"details": True}) - - def test_find_hypervisor(self): - self.verify_find(self.proxy.find_hypervisor, - hypervisor.Hypervisor) - - def test_get_hypervisor(self): - self.verify_get(self.proxy.get_hypervisor, - hypervisor.Hypervisor) - def test_live_migrate_server(self): self._verify('openstack.compute.v2.server.Server.live_migrate', self.proxy.live_migrate_server, diff --git a/releasenotes/notes/rework-compute-hypervisor-a62f275a0fd1f074.yaml b/releasenotes/notes/rework-compute-hypervisor-a62f275a0fd1f074.yaml new file mode 100644 index 000000000..c82bf6284 --- /dev/null +++ b/releasenotes/notes/rework-compute-hypervisor-a62f275a0fd1f074.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Compute Hypervisor resource and functions are reworked to comply 2.88 + microversion with deprecating misleading attributes.