Fix requesting specific fields from ironic

Currently we try sending a Python dict in the query, which obviously
does not work. Convert fields to a comma-separated list first.

For chassis specify that the required API version 1.8 is supported.

Change-Id: Ie1c3230f4fd14a59237a55bab2e91f04d50529a7
This commit is contained in:
Dmitry Tantsur 2018-12-05 20:49:00 +01:00
parent 660d193623
commit 79dff76bef
12 changed files with 110 additions and 16 deletions

View File

@ -84,3 +84,10 @@ class ListMixin(object):
base_path += '/detail'
return super(ListMixin, cls).list(session, paginated=True,
base_path=base_path, **params)
def comma_separated_list(value):
if value is None:
return None
else:
return ','.join(value)

View File

@ -19,6 +19,9 @@ class Chassis(_common.ListMixin, resource.Resource):
resources_key = 'chassis'
base_path = '/chassis'
# Specifying fields became possible in 1.8.
_max_microversion = '1.8'
# capabilities
allow_create = True
allow_fetch = True
@ -29,7 +32,7 @@ class Chassis(_common.ListMixin, resource.Resource):
commit_jsonpatch = True
_query_mapping = resource.QueryParameters(
'fields'
fields={'name': 'fields', 'type': _common.comma_separated_list},
)
#: Timestamp at which the chassis was created.

View File

@ -49,8 +49,9 @@ class Node(_common.ListMixin, resource.Resource):
commit_jsonpatch = True
_query_mapping = resource.QueryParameters(
'associated', 'conductor_group', 'driver', 'fault', 'fields',
'associated', 'conductor_group', 'driver', 'fault',
'provision_state', 'resource_class',
fields={'name': 'fields', 'type': _common.comma_separated_list},
instance_id='instance_uuid',
is_maintenance='maintenance',
)

View File

@ -29,7 +29,8 @@ class Port(_common.ListMixin, resource.Resource):
commit_jsonpatch = True
_query_mapping = resource.QueryParameters(
'address', 'fields', 'node', 'portgroup',
'address', 'node', 'portgroup',
fields={'name': 'fields', 'type': _common.comma_separated_list},
node_id='node_uuid',
)

View File

@ -29,7 +29,8 @@ class PortGroup(_common.ListMixin, resource.Resource):
commit_jsonpatch = True
_query_mapping = resource.QueryParameters(
'node', 'address', 'fields',
'node', 'address',
fields={'name': 'fields', 'type': _common.comma_separated_list},
)
# The mode and properties field introduced in 1.26.

View File

@ -273,13 +273,18 @@ class QueryParameters(object):
:param mappings: Key-value pairs where the key is the client-side
name we'll accept here and the value is the name
the server expects, e.g, changes_since=changes-since
the server expects, e.g, changes_since=changes-since.
Additionally, a value can be a dict with optional keys
name - server-side name,
type - callable to convert from client to server
representation.
By default, both limit and marker are included in the initial mapping
as they're the most common query parameters used for listing resources.
"""
self._mapping = {"limit": "limit", "marker": "marker"}
self._mapping.update(dict({name: name for name in names}, **mappings))
self._mapping.update({name: name for name in names})
self._mapping.update(mappings)
def _validate(self, query, base_path=None):
"""Check that supplied query keys match known query mappings
@ -290,7 +295,9 @@ class QueryParameters(object):
the resource.
"""
expected_params = list(self._mapping.keys())
expected_params += self._mapping.values()
expected_params.extend(
value['name'] if isinstance(value, dict) else value
for value in self._mapping.values())
if base_path:
expected_params += utils.get_string_format_keys(base_path)
@ -312,11 +319,25 @@ class QueryParameters(object):
server side name.
"""
result = {}
for key, value in self._mapping.items():
if key in query:
result[value] = query[key]
elif value in query:
result[value] = query[value]
for client_side, server_side in self._mapping.items():
if isinstance(server_side, dict):
name = server_side['name']
type_ = server_side.get('type')
else:
name = server_side
type_ = None
if client_side in query:
value = query[client_side]
elif name in query:
value = query[name]
else:
continue
if type_ is not None:
result[name] = type_(value)
else:
result[name] = value
return result

View File

@ -49,3 +49,15 @@ class TestBareMetalChassis(base.BaseBaremetalTest):
ignore_missing=False)
self.assertIsNone(self.conn.baremetal.find_chassis(uuid))
self.assertIsNone(self.conn.baremetal.delete_chassis(uuid))
class TestBareMetalChassisFields(base.BaseBaremetalTest):
min_microversion = '1.8'
def test_chassis_fields(self):
self.create_chassis(description='something')
result = self.conn.baremetal.chassis(fields=['uuid', 'extra'])
for ch in result:
self.assertIsNotNone(ch.id)
self.assertIsNone(ch.description)

View File

@ -142,6 +142,18 @@ class TestBareMetalNode(base.BaseBaremetalTest):
self.assertIsNone(self.conn.baremetal.delete_node(uuid))
class TestBareMetalNodeFields(base.BaseBaremetalTest):
min_microversion = '1.8'
def test_node_fields(self):
self.create_node()
result = self.conn.baremetal.nodes(fields=['uuid', 'name'])
for item in result:
self.assertIsNotNone(item.id)
self.assertIsNone(item.driver)
class TestBareMetalVif(base.BaseBaremetalTest):
min_microversion = '1.28'

View File

@ -92,3 +92,16 @@ class TestBareMetalPort(base.BaseBaremetalTest):
pxe_enabled=True)
self.assertIsNone(self.conn.baremetal.find_port(uuid))
self.assertIsNone(self.conn.baremetal.delete_port(uuid))
class TestBareMetalPortFields(base.BaseBaremetalTest):
min_microversion = '1.8'
def test_port_fields(self):
self.create_node()
self.create_port(address='11:22:33:44:55:66')
result = self.conn.baremetal.ports(fields=['uuid'])
for item in result:
self.assertIsNotNone(item.id)
self.assertIsNone(item.address)

View File

@ -84,3 +84,11 @@ class TestBareMetalPortGroup(base.BaseBaremetalTest):
ignore_missing=False)
self.assertIsNone(self.conn.baremetal.find_port_group(uuid))
self.assertIsNone(self.conn.baremetal.delete_port_group(uuid))
def test_port_group_fields(self):
self.create_node()
self.create_port_group(address='11:22:33:44:55:66')
result = self.conn.baremetal.port_groups(fields=['uuid', 'name'])
for item in result:
self.assertIsNotNone(item.id)
self.assertIsNone(item.address)

View File

@ -360,32 +360,43 @@ class TestQueryParameters(base.TestCase):
def test_create(self):
location = "location"
mapping = {"first_name": "first-name"}
mapping = {"first_name": "first-name",
"second_name": {"name": "second-name"},
"third_name": {"name": "third", "type": int}}
sot = resource.QueryParameters(location, **mapping)
self.assertEqual({"location": "location",
"first_name": "first-name",
"second_name": {"name": "second-name"},
"third_name": {"name": "third", "type": int},
"limit": "limit",
"marker": "marker"},
sot._mapping)
def test_transpose_unmapped(self):
location = "location"
mapping = {"first_name": "first-name"}
mapping = {"first_name": "first-name",
"pet_name": {"name": "pet"},
"answer": {"name": "answer", "type": int}}
sot = resource.QueryParameters(location, **mapping)
result = sot._transpose({"location": "Brooklyn",
"first_name": "Brian",
"pet_name": "Meow",
"answer": "42",
"last_name": "Curtin"})
# last_name isn't mapped and shouldn't be included
self.assertEqual({"location": "Brooklyn", "first-name": "Brian"},
self.assertEqual({"location": "Brooklyn", "first-name": "Brian",
"pet": "Meow", "answer": 42},
result)
def test_transpose_not_in_query(self):
location = "location"
mapping = {"first_name": "first-name"}
mapping = {"first_name": "first-name",
"pet_name": {"name": "pet"},
"answer": {"name": "answer", "type": int}}
sot = resource.QueryParameters(location, **mapping)
result = sot._transpose({"location": "Brooklyn"})

View File

@ -0,0 +1,4 @@
---
fixes:
- |
Fixes specifying fields when listing bare metal resources.