summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2017-07-26 17:33:03 +0000
committerGerrit Code Review <review@openstack.org>2017-07-26 17:33:03 +0000
commit2e307bb3e8c92c68e4de7829df60055e9b573c40 (patch)
tree15a44e0b00ce6b340d8323732cf092ee4e4c07a2
parent3e0ac73abc4a7da0e8f40f77c6dc84614e548ddb (diff)
parent5745beae5c456aafef82ab7af94b452875409760 (diff)
downloadpython-novaclient-2e307bb3e8c92c68e4de7829df60055e9b573c40.tar.gz
Merge "Microversion 2.53 - services and hypervisors using UUIDs"
-rw-r--r--novaclient/__init__.py2
-rw-r--r--novaclient/tests/functional/v2/legacy/test_hypervisors.py16
-rw-r--r--novaclient/tests/functional/v2/test_hypervisors.py7
-rw-r--r--novaclient/tests/functional/v2/test_os_services.py99
-rw-r--r--novaclient/tests/unit/fixture_data/hypervisors.py64
-rw-r--r--novaclient/tests/unit/v2/fakes.py28
-rw-r--r--novaclient/tests/unit/v2/test_hypervisors.py64
-rw-r--r--novaclient/tests/unit/v2/test_services.py69
-rw-r--r--novaclient/tests/unit/v2/test_shell.py43
-rw-r--r--novaclient/v2/hypervisors.py35
-rw-r--r--novaclient/v2/services.py64
-rw-r--r--novaclient/v2/shell.py75
-rw-r--r--releasenotes/notes/microversion-v2_53-3463b546a38c5f84.yaml35
13 files changed, 544 insertions, 57 deletions
diff --git a/novaclient/__init__.py b/novaclient/__init__.py
index 926bfff6..3fe7488d 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 36edd60e..ecc102dc 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 51327a6d..ca66a44a 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 3da8449c..719ad733 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 43b3438b..1e706786 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 1ebf1e3e..4138e8a9 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 d3a578e6..0a3a2514 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 320f839a..0c0434af 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('<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)
diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py
index 66c462d9..2a151455 100644
--- a/novaclient/tests/unit/v2/test_shell.py
+++ b/novaclient/tests/unit/v2/test_shell.py
@@ -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
diff --git a/novaclient/v2/hypervisors.py b/novaclient/v2/hypervisors.py
index 761bf1c9..e43ef7a1 100644
--- a/novaclient/v2/hypervisors.py
+++ b/novaclient/v2/hypervisors.py
@@ -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")
diff --git a/novaclient/v2/services.py b/novaclient/v2/services.py
index 46563e92..3fe877d3 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 "<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")
diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py
index 947fc31a..14c0c402 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='<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 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."""
+ """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)
diff --git a/releasenotes/notes/microversion-v2_53-3463b546a38c5f84.yaml b/releasenotes/notes/microversion-v2_53-3463b546a38c5f84.yaml
new file mode 100644
index 00000000..0ee4cfcb
--- /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