From 5745beae5c456aafef82ab7af94b452875409760 Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Wed, 19 Jul 2017 23:54:12 -0400 Subject: [PATCH] Microversion 2.53 - services and hypervisors using UUIDs Adds support for the 2.53 microversion which changes the os-services and os-hypervisors APIs to use a UUID for the ID value on the resource. Also, the PUT and GET API methods have changed a bit for both resources in this microversion, so the pythong API bindings and command lines have been adjusted accordingly. Part of blueprint service-hyper-uuid-in-api Change-Id: Ic721143cc154d91e74a8a9dd2c1e991045c94305 --- novaclient/__init__.py | 2 +- .../functional/v2/legacy/test_hypervisors.py | 16 ++- .../tests/functional/v2/test_hypervisors.py | 7 ++ .../tests/functional/v2/test_os_services.py | 99 +++++++++++++++++++ .../tests/unit/fixture_data/hypervisors.py | 64 ++++++++---- novaclient/tests/unit/v2/fakes.py | 28 +++++- novaclient/tests/unit/v2/test_hypervisors.py | 64 ++++++++---- novaclient/tests/unit/v2/test_services.py | 69 ++++++++++++- novaclient/tests/unit/v2/test_shell.py | 43 ++++++++ novaclient/v2/hypervisors.py | 35 +++++-- novaclient/v2/services.py | 64 +++++++++++- novaclient/v2/shell.py | 75 +++++++++++++- .../microversion-v2_53-3463b546a38c5f84.yaml | 35 +++++++ 13 files changed, 544 insertions(+), 57 deletions(-) create mode 100644 releasenotes/notes/microversion-v2_53-3463b546a38c5f84.yaml diff --git a/novaclient/__init__.py b/novaclient/__init__.py index 926bfff6c..3fe7488d0 100644 --- a/novaclient/__init__.py +++ b/novaclient/__init__.py @@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1") # when client supported the max version, and bumped sequentially, otherwise # the client may break due to server side new version may include some # backward incompatible change. -API_MAX_VERSION = api_versions.APIVersion("2.52") +API_MAX_VERSION = api_versions.APIVersion("2.53") diff --git a/novaclient/tests/functional/v2/legacy/test_hypervisors.py b/novaclient/tests/functional/v2/legacy/test_hypervisors.py index 36edd60e1..ecc102dc0 100644 --- a/novaclient/tests/functional/v2/legacy/test_hypervisors.py +++ b/novaclient/tests/functional/v2/legacy/test_hypervisors.py @@ -13,18 +13,32 @@ import six from novaclient.tests.functional import base +from novaclient import utils class TestHypervisors(base.ClientTestBase): COMPUTE_API_VERSION = "2.1" - def _test_list(self, cpu_info_type): + def _test_list(self, cpu_info_type, uuid_as_id=False): hypervisors = self.client.hypervisors.list() if not len(hypervisors): self.fail("No hypervisors detected.") for hypervisor in hypervisors: self.assertIsInstance(hypervisor.cpu_info, cpu_info_type) + if uuid_as_id: + # microversion >= 2.53 returns a uuid for the id + self.assertFalse(utils.is_integer_like(hypervisor.id), + 'Expected hypervisor.id to be a UUID.') + self.assertFalse( + utils.is_integer_like(hypervisor.service['id']), + 'Expected hypervisor.service.id to be a UUID.') + else: + self.assertTrue(utils.is_integer_like(hypervisor.id), + 'Expected hypervisor.id to be an integer.') + self.assertTrue( + utils.is_integer_like(hypervisor.service['id']), + 'Expected hypervisor.service.id to be an integer.') def test_list(self): self._test_list(six.text_type) diff --git a/novaclient/tests/functional/v2/test_hypervisors.py b/novaclient/tests/functional/v2/test_hypervisors.py index 51327a6d9..ca66a44a2 100644 --- a/novaclient/tests/functional/v2/test_hypervisors.py +++ b/novaclient/tests/functional/v2/test_hypervisors.py @@ -19,3 +19,10 @@ class TestHypervisorsV28(test_hypervisors.TestHypervisors): def test_list(self): self._test_list(dict) + + +class TestHypervisorsV2_53(TestHypervisorsV28): + COMPUTE_API_VERSION = "2.53" + + def test_list(self): + self._test_list(cpu_info_type=dict, uuid_as_id=True) diff --git a/novaclient/tests/functional/v2/test_os_services.py b/novaclient/tests/functional/v2/test_os_services.py index 3da8449c0..719ad7330 100644 --- a/novaclient/tests/functional/v2/test_os_services.py +++ b/novaclient/tests/functional/v2/test_os_services.py @@ -10,7 +10,9 @@ # License for the specific language governing permissions and limitations # under the License. +from novaclient.tests.functional import base from novaclient.tests.functional.v2.legacy import test_os_services +from novaclient import utils class TestOsServicesNovaClientV211(test_os_services.TestOsServicesNovaClient): @@ -42,3 +44,100 @@ class TestOsServicesNovaClientV211(test_os_services.TestOsServicesNovaClient): status = self._get_column_value_from_single_row_table( service, 'Forced down') self.assertEqual('False', status) + + +class TestOsServicesNovaClientV2_53(base.ClientTestBase): + """Tests the nova service-* commands using the 2.53 microversion. + + The main difference with the 2.53 microversion in these commands is + the host/binary combination is replaced with the service.id as the + unique identifier for a service. + """ + COMPUTE_API_VERSION = "2.53" + + def test_os_services_list(self): + table = self.nova('service-list') + for serv in self.client.services.list(): + self.assertIn(serv.binary, table) + # the id should not be an integer and should be in the table + self.assertFalse(utils.is_integer_like(serv.id)) + self.assertIn(serv.id, table) + + def test_os_service_disable_enable(self): + # Disable and enable Nova services in accordance with list of nova + # services returned by client + # NOTE(sdague): service disable has the chance in racing + # with other tests. Now functional tests for novaclient are launched + # in serial way (https://review.openstack.org/#/c/217768/), but + # it's a potential issue for making these tests parallel in the future + for serv in self.client.services.list(): + # In Pike the os-services API was made multi-cell aware and it + # looks up services by host, which uses the host mapping record + # in the API DB which is only populated for nova-compute services, + # effectively making it impossible to perform actions like enable + # or disable non-nova-compute services since the API won't be able + # to find them. So filter out anything that's not nova-compute. + if serv.binary != 'nova-compute': + continue + service = self.nova('service-disable %s' % serv.id) + self.addCleanup(self.nova, 'service-enable', params="%s" % serv.id) + service_id = self._get_column_value_from_single_row_table( + service, 'ID') + self.assertEqual(serv.id, service_id) + status = self._get_column_value_from_single_row_table( + service, 'Status') + self.assertEqual('disabled', status) + service = self.nova('service-enable %s' % serv.id) + service_id = self._get_column_value_from_single_row_table( + service, 'ID') + self.assertEqual(serv.id, service_id) + status = self._get_column_value_from_single_row_table( + service, 'Status') + self.assertEqual('enabled', status) + + def test_os_service_disable_log_reason(self): + for serv in self.client.services.list(): + # In Pike the os-services API was made multi-cell aware and it + # looks up services by host, which uses the host mapping record + # in the API DB which is only populated for nova-compute services, + # effectively making it impossible to perform actions like enable + # or disable non-nova-compute services since the API won't be able + # to find them. So filter out anything that's not nova-compute. + if serv.binary != 'nova-compute': + continue + service = self.nova('service-disable --reason test_disable %s' + % serv.id) + self.addCleanup(self.nova, 'service-enable', params="%s" % serv.id) + service_id = self._get_column_value_from_single_row_table( + service, 'ID') + self.assertEqual(serv.id, service_id) + status = self._get_column_value_from_single_row_table( + service, 'Status') + log_reason = self._get_column_value_from_single_row_table( + service, 'Disabled Reason') + self.assertEqual('disabled', status) + self.assertEqual('test_disable', log_reason) + + def test_os_services_force_down_force_up(self): + for serv in self.client.services.list(): + # In Pike the os-services API was made multi-cell aware and it + # looks up services by host, which uses the host mapping record + # in the API DB which is only populated for nova-compute services, + # effectively making it impossible to perform actions like enable + # or disable non-nova-compute services since the API won't be able + # to find them. So filter out anything that's not nova-compute. + if serv.binary != 'nova-compute': + continue + service = self.nova('service-force-down %s' % serv.id) + self.addCleanup(self.nova, 'service-force-down --unset', + params="%s" % serv.id) + service_id = self._get_column_value_from_single_row_table( + service, 'ID') + self.assertEqual(serv.id, service_id) + forced_down = self._get_column_value_from_single_row_table( + service, 'Forced down') + self.assertEqual('True', forced_down) + service = self.nova('service-force-down --unset %s' % serv.id) + forced_down = self._get_column_value_from_single_row_table( + service, 'Forced down') + self.assertEqual('False', forced_down) diff --git a/novaclient/tests/unit/fixture_data/hypervisors.py b/novaclient/tests/unit/fixture_data/hypervisors.py index 43b3438b6..1e706786b 100644 --- a/novaclient/tests/unit/fixture_data/hypervisors.py +++ b/novaclient/tests/unit/fixture_data/hypervisors.py @@ -10,20 +10,28 @@ # License for the specific language governing permissions and limitations # under the License. +from novaclient import api_versions from novaclient.tests.unit.fixture_data import base class V1(base.Fixture): base_url = 'os-hypervisors' + api_version = '2.1' + hyper_id_1 = 1234 + hyper_id_2 = 5678 + service_id_1 = 1 + service_id_2 = 2 def setUp(self): super(V1, self).setUp() + uuid_as_id = (api_versions.APIVersion(self.api_version) >= + api_versions.APIVersion('2.53')) get_os_hypervisors = { 'hypervisors': [ - {'id': 1234, 'hypervisor_hostname': 'hyper1'}, - {'id': 5678, 'hypervisor_hostname': 'hyper2'}, + {'id': self.hyper_id_1, 'hypervisor_hostname': 'hyper1'}, + {'id': self.hyper_id_2, 'hypervisor_hostname': 'hyper2'}, ] } @@ -36,9 +44,9 @@ class V1(base.Fixture): get_os_hypervisors_detail = { 'hypervisors': [ { - 'id': 1234, + 'id': self.hyper_id_1, 'service': { - 'id': 1, + 'id': self.service_id_1, 'host': 'compute1', }, 'vcpus': 4, @@ -58,9 +66,9 @@ class V1(base.Fixture): 'disk_available_least': 100 }, { - 'id': 2, + 'id': self.hyper_id_2, 'service': { - 'id': 2, + 'id': self.service_id_2, 'host': 'compute2', }, 'vcpus': 4, @@ -109,19 +117,23 @@ class V1(base.Fixture): get_os_hypervisors_search = { 'hypervisors': [ - {'id': 1234, 'hypervisor_hostname': 'hyper1'}, - {'id': 5678, 'hypervisor_hostname': 'hyper2'} + {'id': self.hyper_id_1, 'hypervisor_hostname': 'hyper1'}, + {'id': self.hyper_id_2, 'hypervisor_hostname': 'hyper2'} ] } - self.requests_mock.get(self.url('hyper', 'search'), + if uuid_as_id: + url = self.url(hypervisor_hostname_pattern='hyper') + else: + url = self.url('hyper', 'search') + self.requests_mock.get(url, json=get_os_hypervisors_search, headers=self.headers) get_hyper_server = { 'hypervisors': [ { - 'id': 1234, + 'id': self.hyper_id_1, 'hypervisor_hostname': 'hyper1', 'servers': [ {'name': 'inst1', 'uuid': 'uuid1'}, @@ -129,7 +141,7 @@ class V1(base.Fixture): ] }, { - 'id': 5678, + 'id': self.hyper_id_2, 'hypervisor_hostname': 'hyper2', 'servers': [ {'name': 'inst3', 'uuid': 'uuid3'}, @@ -139,14 +151,19 @@ class V1(base.Fixture): ] } - self.requests_mock.get(self.url('hyper', 'servers'), + if uuid_as_id: + url = self.url(hypervisor_hostname_pattern='hyper', + with_servers=True) + else: + url = self.url('hyper', 'servers') + self.requests_mock.get(url, json=get_hyper_server, headers=self.headers) - get_os_hypervisors_1234 = { + get_os_hypervisors_hyper1 = { 'hypervisor': { - 'id': 1234, - 'service': {'id': 1, 'host': 'compute1'}, + 'id': self.hyper_id_1, + 'service': {'id': self.service_id_1, 'host': 'compute1'}, 'vcpus': 4, 'memory_mb': 10 * 1024, 'local_gb': 250, @@ -165,18 +182,27 @@ class V1(base.Fixture): } } - self.requests_mock.get(self.url(1234), - json=get_os_hypervisors_1234, + self.requests_mock.get(self.url(self.hyper_id_1), + json=get_os_hypervisors_hyper1, headers=self.headers) get_os_hypervisors_uptime = { 'hypervisor': { - 'id': 1234, + 'id': self.hyper_id_1, 'hypervisor_hostname': 'hyper1', 'uptime': 'fake uptime' } } - self.requests_mock.get(self.url(1234, 'uptime'), + self.requests_mock.get(self.url(self.hyper_id_1, 'uptime'), json=get_os_hypervisors_uptime, headers=self.headers) + + +class V2_53(V1): + """Fixture data for the os-hypervisors 2.53 API.""" + api_version = '2.53' + hyper_id_1 = 'd480b1b6-2255-43c2-b2c2-d60d42c2c074' + hyper_id_2 = '43a8214d-f36a-4fc0-a25c-3cf35c17522d' + service_id_1 = 'a87743ff-9c29-42ff-805d-2444659b5fc0' + service_id_2 = '0486ab8b-1cfc-4ccb-9d94-9f22ec8bbd6b' diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py index 1ebf1e3ea..4138e8a98 100644 --- a/novaclient/tests/unit/v2/fakes.py +++ b/novaclient/tests/unit/v2/fakes.py @@ -54,6 +54,9 @@ FAKE_REQUEST_ID = fakes.FAKE_REQUEST_ID FAKE_REQUEST_ID_LIST = fakes.FAKE_REQUEST_ID_LIST FAKE_RESPONSE_HEADERS = {'x-openstack-request-id': FAKE_REQUEST_ID} +FAKE_SERVICE_UUID_1 = '75e9eabc-ed3b-4f11-8bba-add1e7e7e2de' +FAKE_SERVICE_UUID_2 = '1f140183-c914-4ddf-8757-6df73028aa86' + class FakeClient(fakes.FakeClient, client.Client): @@ -1582,6 +1585,12 @@ class FakeSessionClient(base_client.SessionClient): def get_os_services(self, **kw): host = kw.get('host', 'host1') binary = kw.get('binary', 'nova-compute') + if self.api_version >= api_versions.APIVersion('2.53'): + service_id_1 = FAKE_SERVICE_UUID_1 + service_id_2 = FAKE_SERVICE_UUID_2 + else: + service_id_1 = 1 + service_id_2 = 2 return (200, FAKE_RESPONSE_HEADERS, {'services': [{'binary': binary, 'host': host, @@ -1589,14 +1598,16 @@ class FakeSessionClient(base_client.SessionClient): 'status': 'enabled', 'state': 'up', 'updated_at': datetime.datetime( - 2012, 10, 29, 13, 42, 2)}, + 2012, 10, 29, 13, 42, 2), + 'id': service_id_1}, {'binary': binary, 'host': host, 'zone': 'nova', 'status': 'disabled', 'state': 'down', 'updated_at': datetime.datetime( - 2012, 9, 18, 8, 3, 38)}, + 2012, 9, 18, 8, 3, 38), + 'id': service_id_2}, ]}) def put_os_services_enable(self, body, **kw): @@ -1618,9 +1629,22 @@ class FakeSessionClient(base_client.SessionClient): 'status': 'disabled', 'disabled_reason': body['disabled_reason']}}) + def put_os_services_75e9eabc_ed3b_4f11_8bba_add1e7e7e2de( + self, body, **kw): + """This should only be called with microversion >= 2.53.""" + return (200, FAKE_RESPONSE_HEADERS, {'service': { + 'host': 'host1', + 'binary': 'nova-compute', + 'status': body.get('status', 'enabled'), + 'disabled_reason': body.get('disabled_reason'), + 'forced_down': body.get('forced_down', False)}}) + def delete_os_services_1(self, **kw): return (204, FAKE_RESPONSE_HEADERS, None) + def delete_os_services_75e9eabc_ed3b_4f11_8bba_add1e7e7e2de(self, **kwarg): + return (204, FAKE_RESPONSE_HEADERS, None) + def put_os_services_force_down(self, body, **kw): return (200, FAKE_RESPONSE_HEADERS, {'service': { 'host': body['host'], diff --git a/novaclient/tests/unit/v2/test_hypervisors.py b/novaclient/tests/unit/v2/test_hypervisors.py index d3a578e69..0a3a2514f 100644 --- a/novaclient/tests/unit/v2/test_hypervisors.py +++ b/novaclient/tests/unit/v2/test_hypervisors.py @@ -31,8 +31,10 @@ class HypervisorsTest(utils.FixturedTestCase): def test_hypervisor_index(self): expected = [ - dict(id=1234, hypervisor_hostname='hyper1'), - dict(id=5678, hypervisor_hostname='hyper2')] + dict(id=self.data_fixture.hyper_id_1, + hypervisor_hostname='hyper1'), + dict(id=self.data_fixture.hyper_id_2, + hypervisor_hostname='hyper2')] result = self.cs.hypervisors.list(False) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) @@ -43,8 +45,9 @@ class HypervisorsTest(utils.FixturedTestCase): def test_hypervisor_detail(self): expected = [ - dict(id=1234, - service=dict(id=1, host='compute1'), + dict(id=self.data_fixture.hyper_id_1, + service=dict(id=self.data_fixture.service_id_1, + host='compute1'), vcpus=4, memory_mb=10 * 1024, local_gb=250, @@ -60,8 +63,9 @@ class HypervisorsTest(utils.FixturedTestCase): running_vms=2, cpu_info='cpu_info', disk_available_least=100), - dict(id=2, - service=dict(id=2, host="compute2"), + dict(id=self.data_fixture.hyper_id_2, + service=dict(id=self.data_fixture.service_id_2, + host="compute2"), vcpus=4, memory_mb=10 * 1024, local_gb=250, @@ -87,24 +91,30 @@ class HypervisorsTest(utils.FixturedTestCase): def test_hypervisor_search(self): expected = [ - dict(id=1234, hypervisor_hostname='hyper1'), - dict(id=5678, hypervisor_hostname='hyper2')] + dict(id=self.data_fixture.hyper_id_1, + hypervisor_hostname='hyper1'), + dict(id=self.data_fixture.hyper_id_2, + hypervisor_hostname='hyper2')] result = self.cs.hypervisors.search('hyper') self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) - self.assert_called('GET', '/os-hypervisors/hyper/search') + if self.cs.api_version >= api_versions.APIVersion('2.53'): + self.assert_called( + 'GET', '/os-hypervisors?hypervisor_hostname_pattern=hyper') + else: + self.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, + dict(id=self.data_fixture.hyper_id_1, hypervisor_hostname='hyper1', servers=[ dict(name='inst1', uuid='uuid1'), dict(name='inst2', uuid='uuid2')]), - dict(id=5678, + dict(id=self.data_fixture.hyper_id_2, hypervisor_hostname='hyper2', servers=[ dict(name='inst3', uuid='uuid3'), @@ -113,15 +123,20 @@ class HypervisorsTest(utils.FixturedTestCase): result = self.cs.hypervisors.search('hyper', True) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) - self.assert_called('GET', '/os-hypervisors/hyper/servers') + if self.cs.api_version >= api_versions.APIVersion('2.53'): + self.assert_called( + 'GET', '/os-hypervisors?hypervisor_hostname_pattern=hyper&' + 'with_servers=True') + else: + self.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'), + id=self.data_fixture.hyper_id_1, + service=dict(id=self.data_fixture.service_id_1, host='compute1'), vcpus=4, memory_mb=10 * 1024, local_gb=250, @@ -138,21 +153,23 @@ class HypervisorsTest(utils.FixturedTestCase): cpu_info='cpu_info', disk_available_least=100) - result = self.cs.hypervisors.get(1234) + result = self.cs.hypervisors.get(self.data_fixture.hyper_id_1) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) - self.assert_called('GET', '/os-hypervisors/1234') + self.assert_called( + 'GET', '/os-hypervisors/%s' % self.data_fixture.hyper_id_1) self.compare_to_expected(expected, result) def test_hypervisor_uptime(self): expected = dict( - id=1234, + id=self.data_fixture.hyper_id_1, hypervisor_hostname="hyper1", uptime="fake uptime") - result = self.cs.hypervisors.uptime(1234) + result = self.cs.hypervisors.uptime(self.data_fixture.hyper_id_1) self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST) - self.assert_called('GET', '/os-hypervisors/1234/uptime') + self.assert_called( + 'GET', '/os-hypervisors/%s/uptime' % self.data_fixture.hyper_id_1) self.compare_to_expected(expected, result) @@ -198,3 +215,12 @@ class HypervisorsV233Test(HypervisorsTest): self.cs.hypervisors.list(**params) for k, v in params.items(): self.assertEqual([v], self.requests_mock.last_request.qs[k]) + + +class HypervisorsV2_53Test(HypervisorsV233Test): + """Tests the os-hypervisors 2.53 API bindings.""" + data_fixture_class = data.V2_53 + + def setUp(self): + super(HypervisorsV2_53Test, self).setUp() + self.cs.api_version = api_versions.APIVersion("2.53") diff --git a/novaclient/tests/unit/v2/test_services.py b/novaclient/tests/unit/v2/test_services.py index 320f839ac..0c0434afd 100644 --- a/novaclient/tests/unit/v2/test_services.py +++ b/novaclient/tests/unit/v2/test_services.py @@ -34,11 +34,18 @@ class ServicesTest(utils.TestCase): svs = self.cs.services.list() self.assert_request_id(svs, fakes.FAKE_REQUEST_ID_LIST) self.cs.assert_called('GET', '/os-services') + expect_uuid_id = ( + api_versions.APIVersion(self.api_version) >= + api_versions.APIVersion('2.53')) for s in svs: self.assertIsInstance(s, self._get_service_type()) self.assertEqual('nova-compute', s.binary) self.assertEqual('host1', s.host) - self.assertTrue(str(s).startswith('= api_versions.APIVersion('2.53'): + url = ('/os-hypervisors?hypervisor_hostname_pattern=%s' % + parse.quote(hypervisor_match, safe='')) + if servers: + url += '&with_servers=True' + else: + target = 'servers' if servers else 'search' + url = ('/os-hypervisors/%s/%s' % + (parse.quote(hypervisor_match, safe=''), target)) return self._list(url, 'hypervisors') def get(self, hypervisor): """ Get a specific hypervisor. + + :param hypervisor: Either a Hypervisor object or an ID. Starting with + microversion 2.53 the ID must be a UUID value. """ return self._get("/os-hypervisors/%s" % base.getid(hypervisor), "hypervisor") @@ -87,6 +107,9 @@ class HypervisorManager(base.ManagerWithFind): def uptime(self, hypervisor): """ Get the uptime for a specific hypervisor. + + :param hypervisor: Either a Hypervisor object or an ID. Starting with + microversion 2.53 the ID must be a UUID value. """ return self._get("/os-hypervisors/%s/uptime" % base.getid(hypervisor), "hypervisor") diff --git a/novaclient/v2/services.py b/novaclient/v2/services.py index 46563e92e..3fe877d32 100644 --- a/novaclient/v2/services.py +++ b/novaclient/v2/services.py @@ -20,11 +20,16 @@ from six.moves import urllib from novaclient import api_versions from novaclient import base +from novaclient import utils class Service(base.Resource): def __repr__(self): - return "" % self.binary + # If the id is int-like, then represent the service using it's binary + # name, otherwise use the UUID ID. + if utils.is_integer_like(self.id): + return "" % self.binary + return "" % self.id def _add_details(self, info): dico = 'resource' in info and info['resource'] or info @@ -70,27 +75,80 @@ class ServiceManager(base.ManagerWithFind): body["forced_down"] = force_down return body + @api_versions.wraps('2.0', '2.52') def enable(self, host, binary): """Enable the service specified by hostname and binary.""" body = self._update_body(host, binary) return self._update("/os-services/enable", body, "service") + @api_versions.wraps('2.53') + def enable(self, service_uuid): + """Enable the service specified by the service UUID ID. + + :param service_uuid: The UUID ID of the service to enable. + """ + return self._update( + "/os-services/%s" % service_uuid, {'status': 'enabled'}, "service") + + @api_versions.wraps('2.0', '2.52') def disable(self, host, binary): """Disable the service specified by hostname and binary.""" body = self._update_body(host, binary) return self._update("/os-services/disable", body, "service") + @api_versions.wraps('2.53') + def disable(self, service_uuid): + """Disable the service specified by the service UUID ID. + + :param service_uuid: The UUID ID of the service to disable. + """ + return self._update("/os-services/%s" % service_uuid, + {'status': 'disabled'}, "service") + + @api_versions.wraps('2.0', '2.52') def disable_log_reason(self, host, binary, reason): """Disable the service with reason.""" body = self._update_body(host, binary, reason) return self._update("/os-services/disable-log-reason", body, "service") + @api_versions.wraps('2.53') + def disable_log_reason(self, service_uuid, reason): + """Disable the service with a reason. + + :param service_uuid: The UUID ID of the service to disable. + :param reason: The reason for disabling a service. The minimum length + is 1 and the maximum length is 255. + """ + body = { + 'status': 'disabled', + 'disabled_reason': reason + } + return self._update("/os-services/%s" % service_uuid, body, "service") + def delete(self, service_id): - """Delete a service.""" + """Delete a service. + + :param service_id: Before microversion 2.53, this must be an integer id + and may not uniquely the service in a multi-cell deployment. + Starting with microversion 2.53 this must be a UUID. + """ return self._delete("/os-services/%s" % service_id) - @api_versions.wraps("2.11") + @api_versions.wraps("2.11", "2.52") def force_down(self, host, binary, force_down=None): """Force service state to down specified by hostname and binary.""" body = self._update_body(host, binary, force_down=force_down) return self._update("/os-services/force-down", body, "service") + + @api_versions.wraps("2.53") + def force_down(self, service_uuid, force_down): + """Update the service's ``forced_down`` field specified by the + service UUID ID. + + :param service_uuid: The UUID ID of the service. + :param force_down: Whether or not this service was forced down manually + by an administrator. This value is useful to know that some 3rd + party has verified the service should be marked down. + """ + return self._update("/os-services/%s" % service_uuid, + {'forced_down': force_down}, "service") diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 947fc31a3..14c0c4027 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -3474,6 +3474,9 @@ def do_service_list(cs, args): utils.print_list(result, columns) +# Before microversion 2.53, the service was identified using it's host/binary +# values. +@api_versions.wraps('2.0', '2.52') @utils.arg('host', metavar='', help=_('Name of host.')) # TODO(mriedem): Eventually just hard-code the binary to "nova-compute". @utils.arg('binary', metavar='', help=_('Service binary. The only ' @@ -3485,6 +3488,18 @@ def do_service_enable(cs, args): utils.print_list([result], ['Host', 'Binary', 'Status']) +# Starting in microversion 2.53, the service is identified by UUID ID. +@api_versions.wraps('2.53') +@utils.arg('id', metavar='', help=_('ID of the service as a UUID.')) +def do_service_enable(cs, args): + """Enable the service.""" + result = cs.services.enable(args.id) + utils.print_list([result], ['ID', 'Host', 'Binary', 'Status']) + + +# Before microversion 2.53, the service was identified using it's host/binary +# values. +@api_versions.wraps('2.0', '2.52') @utils.arg('host', metavar='', help=_('Name of host.')) # TODO(mriedem): Eventually just hard-code the binary to "nova-compute". @utils.arg('binary', metavar='', help=_('Service binary. The only ' @@ -3506,7 +3521,27 @@ def do_service_disable(cs, args): utils.print_list([result], ['Host', 'Binary', 'Status']) -@api_versions.wraps("2.11") +# Starting in microversion 2.53, the service is identified by UUID ID. +@api_versions.wraps('2.53') +@utils.arg('id', metavar='', help=_('ID of the service as a UUID.')) +@utils.arg( + '--reason', + metavar='', + help=_('Reason for disabling the service.')) +def do_service_disable(cs, args): + """Disable the service.""" + if args.reason: + result = cs.services.disable_log_reason(args.id, args.reason) + utils.print_list( + [result], ['ID', 'Host', 'Binary', 'Status', 'Disabled Reason']) + else: + result = cs.services.disable(args.id) + utils.print_list([result], ['ID', 'Host', 'Binary', 'Status']) + + +# Before microversion 2.53, the service was identified using it's host/binary +# values. +@api_versions.wraps("2.11", "2.52") @utils.arg('host', metavar='', help=_('Name of host.')) # TODO(mriedem): Eventually just hard-code the binary to "nova-compute". @utils.arg('binary', metavar='', help=_('Service binary. The only ' @@ -3524,9 +3559,37 @@ def do_service_force_down(cs, args): utils.print_list([result], ['Host', 'Binary', 'Forced down']) -@utils.arg('id', metavar='', help=_('ID of service.')) +# Starting in microversion 2.53, the service is identified by UUID ID. +@api_versions.wraps('2.53') +@utils.arg('id', metavar='', help=_('ID of the service as a UUID.')) +@utils.arg( + '--unset', + dest='force_down', + help=_("Unset the forced_down state of the service."), + action='store_false', + default=True) +def do_service_force_down(cs, args): + """Force service to down.""" + result = cs.services.force_down(args.id, args.force_down) + utils.print_list([result], ['ID', 'Host', 'Binary', 'Forced down']) + + +# Before microversion 2.53, the service was identified using it's host/binary +# values. +@api_versions.wraps('2.0', '2.52') +@utils.arg('id', metavar='', + help=_('ID of service as an integer. Note that this may not ' + 'uniquely identify a service in a multi-cell deployment.')) def do_service_delete(cs, args): - """Delete the service.""" + """Delete the service by integer ID.""" + cs.services.delete(args.id) + + +# Starting in microversion 2.53, the service is identified by UUID ID. +@api_versions.wraps('2.53') +@utils.arg('id', metavar='', help=_('ID of service as a UUID.')) +def do_service_delete(cs, args): + """Delete the service by UUID ID.""" cs.services.delete(args.id) @@ -3691,7 +3754,8 @@ def do_hypervisor_servers(cs, args): @utils.arg( 'hypervisor', metavar='', - help=_('Name or ID of the hypervisor to show the details of.')) + help=_('Name or ID of the hypervisor. Starting with microversion 2.53 ' + 'the ID must be a UUID.')) @utils.arg( '--wrap', dest='wrap', metavar='', default=40, help=_('Wrap the output to a specified length. ' @@ -3705,7 +3769,8 @@ def do_hypervisor_show(cs, args): @utils.arg( 'hypervisor', metavar='', - help=_('Name or ID of the hypervisor to show the uptime of.')) + help=_('Name or ID of the hypervisor. Starting with microversion 2.53 ' + 'the ID must be a UUID.')) def do_hypervisor_uptime(cs, args): """Display the uptime of the specified hypervisor.""" hyper = _find_hypervisor(cs, args.hypervisor) diff --git a/releasenotes/notes/microversion-v2_53-3463b546a38c5f84.yaml b/releasenotes/notes/microversion-v2_53-3463b546a38c5f84.yaml new file mode 100644 index 000000000..0ee4cfcb9 --- /dev/null +++ b/releasenotes/notes/microversion-v2_53-3463b546a38c5f84.yaml @@ -0,0 +1,35 @@ +--- +features: + - | + Added support for `microversion 2.53`_. The following changes were made + for the ``services`` commands and python API bindings: + + - The ``nova service-list`` command and API will have a UUID value for the + ``id`` field in the output and response, respectively. + - The ``nova service-enable`` command and API will require a UUID service + id value to uniquely identify the service rather than a ``host`` and + ``binary`` value. The UUID ``id`` field will also be in the command + output. + - The ``nova service-disable`` command and API will require a UUID service + id value to uniquely identify the service rather than a ``host`` and + ``binary`` value. The UUID ``id`` field will also be in the command + output. + - The ``nova service-force-down`` command and API will require a UUID + service id value to uniquely identify the service rather than a ``host`` + and ``binary`` value. The UUID ``id`` field will also be in the command + output. + - The ``nova service-delete`` command and API will require a UUID + service id value to uniquely identify the service rather than an integer + service id value. + + The following changes were made for the ``hypervisors`` commands and python + API bindings: + + - The ID field in the various ``nova hypervisor-*`` commands and + ``Hypervisor.id`` attribute in the API binding will now be a UUID value. + - If paging over hypervisors using ``nova hypervisor-list``, the + ``--marker`` must be a UUID value. + - The ``nova hypervisor-show`` and ``nova hypervisor-uptime`` commands and + APIs now take a UUID value for the hypervisor ID. + + .. _microversion 2.53: https://docs.openstack.org/nova/latest/api_microversion_history.html#id48