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
This commit is contained in:
Matt Riedemann 2017-07-19 23:54:12 -04:00
parent ed058c46a1
commit 5745beae5c
13 changed files with 544 additions and 57 deletions

View File

@ -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")

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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'

View File

@ -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'],

View File

@ -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")

View File

@ -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('<Service: '))
if expect_uuid_id:
stringified = '<Service: %s>' % s.id
else:
stringified = '<Service: %s>' % s.binary
self.assertEqual(stringified, str(s))
def test_list_services_with_hostname(self):
svs = self.cs.services.list(host='host2')
@ -129,3 +136,63 @@ class ServicesV211TestCase(ServicesTest):
self.cs.assert_called('PUT', '/os-services/force-down', values)
self.assertIsInstance(service, self._get_service_type())
self.assertFalse(service.forced_down)
class ServicesV2_53TestCase(ServicesV211TestCase):
api_version = "2.53"
def _update_body(self, status=None, disabled_reason=None, force_down=None):
body = {}
if status is not None:
body['status'] = status
if disabled_reason is not None:
body['disabled_reason'] = disabled_reason
if force_down is not None:
body['forced_down'] = force_down
return body
def test_services_enable(self):
service = self.cs.services.enable(fakes.FAKE_SERVICE_UUID_1)
self.assert_request_id(service, fakes.FAKE_REQUEST_ID_LIST)
values = self._update_body(status='enabled')
self.cs.assert_called(
'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, values)
self.assertIsInstance(service, self._get_service_type())
self.assertEqual('enabled', service.status)
def test_services_delete(self):
ret = self.cs.services.delete(fakes.FAKE_SERVICE_UUID_1)
self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST)
self.cs.assert_called('DELETE',
'/os-services/%s' % fakes.FAKE_SERVICE_UUID_1)
def test_services_disable(self):
service = self.cs.services.disable(fakes.FAKE_SERVICE_UUID_1)
self.assert_request_id(service, fakes.FAKE_REQUEST_ID_LIST)
values = self._update_body(status='disabled')
self.cs.assert_called(
'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, values)
self.assertIsInstance(service, self._get_service_type())
self.assertEqual('disabled', service.status)
def test_services_disable_log_reason(self):
service = self.cs.services.disable_log_reason(
fakes.FAKE_SERVICE_UUID_1, 'disable bad host')
self.assert_request_id(service, fakes.FAKE_REQUEST_ID_LIST)
values = self._update_body(status='disabled',
disabled_reason='disable bad host')
self.cs.assert_called(
'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, values)
self.assertIsInstance(service, self._get_service_type())
self.assertEqual('disabled', service.status)
self.assertEqual('disable bad host', service.disabled_reason)
def test_services_force_down(self):
service = self.cs.services.force_down(
fakes.FAKE_SERVICE_UUID_1, False)
self.assert_request_id(service, fakes.FAKE_REQUEST_ID_LIST)
values = self._update_body(force_down=False)
self.cs.assert_called(
'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, values)
self.assertIsInstance(service, self._get_service_type())
self.assertFalse(service.forced_down)

View File

@ -2223,6 +2223,11 @@ class ShellTest(utils.TestCase):
self.run_command('service-list')
self.assert_called('GET', '/os-services')
def test_services_list_v2_53(self):
"""Tests nova service-list at the 2.53 microversion."""
self.run_command('service-list', api_version='2.53')
self.assert_called('GET', '/os-services')
def test_services_list_with_host(self):
self.run_command('service-list --host host1')
self.assert_called('GET', '/os-services?host=host1')
@ -2240,6 +2245,14 @@ class ShellTest(utils.TestCase):
body = {'host': 'host1', 'binary': 'nova-cert'}
self.assert_called('PUT', '/os-services/enable', body)
def test_services_enable_v2_53(self):
"""Tests nova service-enable at the 2.53 microversion."""
self.run_command('service-enable %s' % fakes.FAKE_SERVICE_UUID_1,
api_version='2.53')
body = {'status': 'enabled'}
self.assert_called(
'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, body)
def test_services_enable_default_binary(self):
"""Tests that the default binary is nova-compute if not specified."""
self.run_command('service-enable host1')
@ -2251,6 +2264,14 @@ class ShellTest(utils.TestCase):
body = {'host': 'host1', 'binary': 'nova-cert'}
self.assert_called('PUT', '/os-services/disable', body)
def test_services_disable_v2_53(self):
"""Tests nova service-disable at the 2.53 microversion."""
self.run_command('service-disable %s' % fakes.FAKE_SERVICE_UUID_1,
api_version='2.53')
body = {'status': 'disabled'}
self.assert_called(
'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, body)
def test_services_disable_default_binary(self):
"""Tests that the default binary is nova-compute if not specified."""
self.run_command('service-disable host1')
@ -2263,10 +2284,32 @@ class ShellTest(utils.TestCase):
'disabled_reason': 'no_reason'}
self.assert_called('PUT', '/os-services/disable-log-reason', body)
def test_services_disable_with_reason_v2_53(self):
"""Tests nova service-disable --reason at microversion 2.53."""
self.run_command('service-disable %s --reason no_reason' %
fakes.FAKE_SERVICE_UUID_1, api_version='2.53')
body = {'status': 'disabled', 'disabled_reason': 'no_reason'}
self.assert_called(
'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, body)
def test_service_force_down_v2_53(self):
"""Tests nova service-force-down at the 2.53 microversion."""
self.run_command('service-force-down %s' %
fakes.FAKE_SERVICE_UUID_1, api_version='2.53')
body = {'forced_down': True}
self.assert_called(
'PUT', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1, body)
def test_services_delete(self):
self.run_command('service-delete 1')
self.assert_called('DELETE', '/os-services/1')
def test_services_delete_v2_53(self):
"""Tests nova service-delete at the 2.53 microversion."""
self.run_command('service-delete %s' % fakes.FAKE_SERVICE_UUID_1)
self.assert_called(
'DELETE', '/os-services/%s' % fakes.FAKE_SERVICE_UUID_1)
def test_host_list(self):
_, err = self.run_command('host-list')
# make sure we said it's deprecated

View File

@ -51,6 +51,8 @@ class HypervisorManager(base.ManagerWithFind):
def list(self, detailed=True):
"""
Get a list of hypervisors.
:param detailed: Include a detailed response.
"""
return self._list_base(detailed=detailed)
@ -59,10 +61,13 @@ class HypervisorManager(base.ManagerWithFind):
"""
Get a list of hypervisors.
:param marker: Begin returning hypervisor that appear later in the
keypair list than that represented by this keypair name
:param detailed: Include a detailed response.
:param marker: Begin returning hypervisors that appear later in the
hypervisors list than that represented by this
hypervisor ID. Starting with microversion 2.53 the
marker must be a UUID hypervisor ID.
(optional).
:param limit: maximum number of keypairs to return (optional).
:param limit: maximum number of hypervisors to return (optional).
"""
return self._list_base(detailed=detailed, marker=marker, limit=limit)
@ -70,16 +75,31 @@ class HypervisorManager(base.ManagerWithFind):
"""
Get a list of matching hypervisors.
:param hypervisor_match: The hypervisor host name or a portion of it.
The hypervisor hosts are selected with the host name matching
this pattern.
:param servers: If True, server information is also retrieved.
"""
target = 'servers' if servers else 'search'
url = ('/os-hypervisors/%s/%s' %
(parse.quote(hypervisor_match, safe=''), target))
# Starting with microversion 2.53, the /servers and /search routes are
# deprecated and we get the same results using GET /os-hypervisors
# using query parameters for the hostname pattern and servers.
if self.api_version >= 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")

View File

@ -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 "<Service: %s>" % 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 "<Service: %s>" % self.binary
return "<Service: %s>" % 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")

View File

@ -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='<hostname>', help=_('Name of host.'))
# TODO(mriedem): Eventually just hard-code the binary to "nova-compute".
@utils.arg('binary', metavar='<binary>', 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='<id>', 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='<hostname>', help=_('Name of host.'))
# TODO(mriedem): Eventually just hard-code the binary to "nova-compute".
@utils.arg('binary', metavar='<binary>', 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='<id>', help=_('ID of the service as a UUID.'))
@utils.arg(
'--reason',
metavar='<reason>',
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='<hostname>', help=_('Name of host.'))
# TODO(mriedem): Eventually just hard-code the binary to "nova-compute".
@utils.arg('binary', metavar='<binary>', 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='<id>', 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='<id>', 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='<id>',
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='<id>', 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='<hypervisor>',
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='<integer>', default=40,
help=_('Wrap the output to a specified length. '
@ -3705,7 +3769,8 @@ def do_hypervisor_show(cs, args):
@utils.arg(
'hypervisor',
metavar='<hypervisor>',
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)

View File

@ -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