Add microversion override for get and list

A continuation of previous patches adding support for node
get and list calls to be able to overriden with an
os_ironic_api_version keyword argument.

Also adds a release note covering the prior patches in this
series.

Change-Id: I870540a23555e6ae37659452f727872d9d7882a3
Related-Bug: #1739440
Story: #2001870
Task: #14325
This commit is contained in:
Julia Kreger 2018-05-02 13:49:35 -07:00
parent 5448011fdf
commit 144ce25e42
5 changed files with 128 additions and 27 deletions

View File

@ -67,11 +67,13 @@ class Manager(object):
"""
def _get(self, resource_id, fields=None):
def _get(self, resource_id, fields=None, os_ironic_api_version=None):
"""Retrieve a resource.
:param resource_id: Identifier of the resource.
:param fields: List of specific fields to be returned.
:param os_ironic_api_version: String version (e.g. "1.35") to use for
the request. If not specified, the client's default is used.
:raises exc.ValidationError: For invalid resource_id arg value.
"""
@ -85,19 +87,25 @@ class Manager(object):
resource_id += ','.join(fields)
try:
return self._list(self._path(resource_id))[0]
return self._list(
self._path(resource_id),
os_ironic_api_version=os_ironic_api_version)[0]
except IndexError:
return None
def _get_as_dict(self, resource_id, fields=None):
def _get_as_dict(self, resource_id, fields=None,
os_ironic_api_version=None):
"""Retrieve a resource as a dictionary
:param resource_id: Identifier of the resource.
:param fields: List of specific fields to be returned.
:param os_ironic_api_version: String version (e.g. "1.35") to use for
the request. If not specified, the client's default is used.
:returns: a dictionary representing the resource; may be empty
"""
resource = self._get(resource_id, fields=fields)
resource = self._get(resource_id, fields=fields,
os_ironic_api_version=os_ironic_api_version)
if resource:
return resource.to_dict()
else:
@ -118,7 +126,7 @@ class Manager(object):
return data
def _list_pagination(self, url, response_key=None, obj_class=None,
limit=None):
limit=None, os_ironic_api_version=None):
"""Retrieve a list of items.
The Ironic API is configured to return a maximum number of
@ -134,19 +142,24 @@ class Manager(object):
:param obj_class: class for constructing the returned objects.
:param limit: maximum number of items to return. If None returns
everything.
:param os_ironic_api_version: String version (e.g. "1.35") to use for
the request. If not specified, the client's default is used.
"""
if obj_class is None:
obj_class = self.resource_class
if limit is not None:
limit = int(limit)
kwargs = {}
if os_ironic_api_version is not None:
kwargs['headers'] = {'X-OpenStack-Ironic-API-Version':
os_ironic_api_version}
object_list = []
object_count = 0
limit_reached = False
while url:
resp, body = self.api.json_request('GET', url)
resp, body = self.api.json_request('GET', url, **kwargs)
data = self._format_body_data(body, response_key)
for obj in data:
object_list.append(obj_class(self, obj, loaded=True))
@ -170,16 +183,26 @@ class Manager(object):
return object_list
def __list(self, url, response_key=None, body=None):
resp, body = self.api.json_request('GET', url)
def __list(self, url, response_key=None, body=None,
os_ironic_api_version=None):
kwargs = {}
if os_ironic_api_version is not None:
kwargs['headers'] = {'X-OpenStack-Ironic-API-Version':
os_ironic_api_version}
resp, body = self.api.json_request('GET', url, **kwargs)
data = self._format_body_data(body, response_key)
return data
def _list(self, url, response_key=None, obj_class=None, body=None):
def _list(self, url, response_key=None, obj_class=None, body=None,
os_ironic_api_version=None):
if obj_class is None:
obj_class = self.resource_class
data = self.__list(url, response_key=response_key, body=body)
data = self.__list(url, response_key=response_key, body=body,
os_ironic_api_version=os_ironic_api_version)
return [obj_class(self, res, loaded=True) for res in data if res]
def _list_primitives(self, url, response_key=None):

View File

@ -87,16 +87,16 @@ class TestableManager(base.CreateManager):
return ('/v1/testableresources/%s' % id if id
else '/v1/testableresources')
def get(self, testable_resource_id, fields=None):
def get(self, testable_resource_id, fields=None, **kwargs):
return self._get(resource_id=testable_resource_id,
fields=fields)
fields=fields, **kwargs)
def delete(self, testable_resource_id):
return self._delete(resource_id=testable_resource_id)
def delete(self, testable_resource_id, **kwargs):
return self._delete(resource_id=testable_resource_id, **kwargs)
def update(self, testable_resource_id, patch):
def update(self, testable_resource_id, patch, **kwargs):
return self._update(resource_id=testable_resource_id,
patch=patch)
patch=patch, **kwargs)
class ManagerTestCase(testtools.TestCase):
@ -120,12 +120,13 @@ class ManagerTestCase(testtools.TestCase):
self.manager.create,
**INVALID_ATTRIBUTE_TESTABLE_RESOURCE)
def test__get(self):
def test__get_microversion_override(self):
resource_id = TESTABLE_RESOURCE['uuid']
resource = self.manager._get(resource_id)
resource = self.manager._get(resource_id,
os_ironic_api_version='1.22')
expect = [
('GET', '/v1/testableresources/%s' % resource_id,
{}, None),
{'X-OpenStack-Ironic-API-Version': '1.22'}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(resource_id, resource.uuid)
@ -147,12 +148,24 @@ class ManagerTestCase(testtools.TestCase):
self.assertEqual(expect, self.api.calls)
self.assertEqual(TESTABLE_RESOURCE, resource)
def test__get_as_dict_microversion_override(self):
resource_id = TESTABLE_RESOURCE['uuid']
resource = self.manager._get_as_dict(resource_id,
os_ironic_api_version='1.21')
expect = [
('GET', '/v1/testableresources/%s' % resource_id,
{'X-OpenStack-Ironic-API-Version': '1.21'}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(TESTABLE_RESOURCE, resource)
@mock.patch.object(base.Manager, '_get', autospec=True)
def test__get_as_dict_empty(self, mock_get):
mock_get.return_value = None
resource_id = TESTABLE_RESOURCE['uuid']
resource = self.manager._get_as_dict(resource_id)
mock_get.assert_called_once_with(mock.ANY, resource_id, fields=None)
mock_get.assert_called_once_with(mock.ANY, resource_id, fields=None,
os_ironic_api_version=None)
self.assertEqual({}, resource)
def test_get(self):
@ -165,6 +178,17 @@ class ManagerTestCase(testtools.TestCase):
self.assertEqual(TESTABLE_RESOURCE['uuid'], resource.uuid)
self.assertEqual(TESTABLE_RESOURCE['attribute1'], resource.attribute1)
def test_get_microversion_override(self):
resource = self.manager.get(TESTABLE_RESOURCE['uuid'],
os_ironic_api_version='1.10')
expect = [
('GET', '/v1/testableresources/%s' % TESTABLE_RESOURCE['uuid'],
{'X-OpenStack-Ironic-API-Version': '1.10'}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(TESTABLE_RESOURCE['uuid'], resource.uuid)
self.assertEqual(TESTABLE_RESOURCE['attribute1'], resource.attribute1)
def test_update(self):
patch = {'op': 'replace',
'value': NEW_ATTRIBUTE_VALUE,
@ -180,6 +204,21 @@ class ManagerTestCase(testtools.TestCase):
self.assertEqual(expect, self.api.calls)
self.assertEqual(NEW_ATTRIBUTE_VALUE, resource.attribute1)
def test_update_microversion_override(self):
patch = {'op': 'replace',
'value': NEW_ATTRIBUTE_VALUE,
'path': '/attribute1'}
resource = self.manager.update(
testable_resource_id=TESTABLE_RESOURCE['uuid'],
patch=patch, os_ironic_api_version='1.9'
)
expect = [
('PATCH', '/v1/testableresources/%s' % TESTABLE_RESOURCE['uuid'],
{'X-OpenStack-Ironic-API-Version': '1.9'}, patch),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(NEW_ATTRIBUTE_VALUE, resource.attribute1)
def test_delete(self):
resource = self.manager.delete(
testable_resource_id=TESTABLE_RESOURCE['uuid']

View File

@ -817,6 +817,16 @@ class NodeManagerTest(testtools.TestCase):
self.assertEqual(2, len(nodes))
self.assertEqual(nodes[0].extra, {})
def test_node_list_detail_microversion_override(self):
nodes = self.mgr.list(detail=True, os_ironic_api_version='1.30')
expect = [
('GET', '/v1/nodes/detail',
{'X-OpenStack-Ironic-API-Version': '1.30'}, None),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(2, len(nodes))
self.assertEqual(nodes[0].extra, {})
def test_node_list_fields(self):
nodes = self.mgr.list(fields=['uuid', 'extra'])
expect = [
@ -898,6 +908,19 @@ class NodeManagerTest(testtools.TestCase):
self.assertEqual(expect, self.api.calls)
self.assertEqual(NEW_DRIVER, node.driver)
def test_update_microversion_override(self):
patch = {'op': 'replace',
'value': NEW_DRIVER,
'path': '/driver'}
node = self.mgr.update(node_id=NODE1['uuid'], patch=patch,
os_ironic_api_version='1.24')
expect = [
('PATCH', '/v1/nodes/%s' % NODE1['uuid'],
{'X-OpenStack-Ironic-API-Version': '1.24'}, patch),
]
self.assertEqual(expect, self.api.calls)
self.assertEqual(NEW_DRIVER, node.driver)
def test_node_port_list_with_uuid(self):
ports = self.mgr.list_ports(NODE1['uuid'])
expect = [

View File

@ -58,7 +58,7 @@ class NodeManager(base.CreateManager):
def list(self, associated=None, maintenance=None, marker=None, limit=None,
detail=False, sort_key=None, sort_dir=None, fields=None,
provision_state=None, driver=None, resource_class=None,
chassis=None, fault=None):
chassis=None, fault=None, os_ironic_api_version=None):
"""Retrieve a list of nodes.
:param associated: Optional. Either a Boolean or a string
@ -107,6 +107,8 @@ class NodeManager(base.CreateManager):
:param fault: Optional. String value to get only nodes with
specified fault.
:param os_ironic_api_version: String version (e.g. "1.35") to use for
the request. If not specified, the client's default is used.
:returns: A list of nodes.
@ -142,10 +144,12 @@ class NodeManager(base.CreateManager):
path += '?' + '&'.join(filters)
if limit is None:
return self._list(self._path(path), "nodes")
return self._list(self._path(path), "nodes",
os_ironic_api_version=os_ironic_api_version)
else:
return self._list_pagination(self._path(path), "nodes",
limit=limit)
return self._list_pagination(
self._path(path), "nodes", limit=limit,
os_ironic_api_version=os_ironic_api_version)
def list_ports(self, node_id, marker=None, limit=None, sort_key=None,
sort_dir=None, detail=False, fields=None):
@ -314,8 +318,9 @@ class NodeManager(base.CreateManager):
self._path(path), response_key="targets", limit=limit,
obj_class=volume_target.VolumeTarget)
def get(self, node_id, fields=None):
return self._get(resource_id=node_id, fields=fields)
def get(self, node_id, fields=None, os_ironic_api_version=None):
return self._get(resource_id=node_id, fields=fields,
os_ironic_api_version=os_ironic_api_version)
def get_by_instance_uuid(self, instance_uuid, fields=None):
path = '?instance_uuid=%s' % instance_uuid

View File

@ -0,0 +1,11 @@
---
features:
- |
Adds support for ``NodeManager.set_provision_state``,
``NodeManager.update``, ``NodeManager.get``, and ``NodeManager.list``
to accept an ``os_ironic_api_version`` keyword argument to override
the API version for that specific call to the REST API.
When overridden, the API version is not preserved, and if an unsupported
version is requested from the remote API, an ``UnsupportedVersion``
exception is raised.