Refactor resource_fields.py

Refactor the resource_fields.py file to remove some (but not all) of the
duplication that is occurring.

  * Created a class Resource to contain the fields and label values
  * Added test cases for the new Resource class
  * Use the new Resource class to hold data for Chassis, Node, and
    Ports.
  * Have logic to support a 'sort_fields' and 'sort_labels' property
    which removes fields/labels which can not be used for sorting.

Change-Id: I846517a3714b1a86ef7ed7f52e911376e1915bb5
This commit is contained in:
John L. Villalovos
2015-05-11 10:00:20 -07:00
parent 2ce7fd2e58
commit 30b01689f4
6 changed files with 264 additions and 130 deletions

View File

@@ -21,6 +21,18 @@ import ironicclient.v1.chassis_shell as c_shell
class ChassisShellTest(utils.BaseTestCase):
def _get_client_mock_args(self, chassis=None, marker=None, limit=None,
sort_dir=None, sort_key=None, detail=False):
args = mock.MagicMock()
args.chassis = chassis
args.marker = marker
args.limit = limit
args.sort_dir = sort_dir
args.sort_key = sort_key
args.detail = detail
return args
def test_chassis_show(self):
actual = {}
fake_print_dict = lambda data, *args, **kwargs: actual.update(data)
@@ -47,17 +59,6 @@ class ChassisShellTest(utils.BaseTestCase):
c_shell.do_chassis_show,
client_mock, args)
def _get_client_mock_args(self, marker=None, limit=None, sort_dir=None,
sort_key=None, detail=False):
args = mock.MagicMock()
args.marker = marker
args.limit = limit
args.sort_dir = sort_dir
args.sort_key = sort_key
args.detail = detail
return args
def test_do_chassis_list(self):
client_mock = mock.MagicMock()
args = self._get_client_mock_args()
@@ -109,3 +110,21 @@ class ChassisShellTest(utils.BaseTestCase):
c_shell.do_chassis_list,
client_mock, args)
self.assertFalse(client_mock.chassis.list.called)
def test_do_chassis_node_list(self):
client_mock = mock.MagicMock()
chassis_mock = mock.MagicMock(spec_set=[])
args = self._get_client_mock_args(chassis=chassis_mock)
c_shell.do_chassis_node_list(client_mock, args)
client_mock.chassis.list_nodes.assert_called_once_with(
chassis_mock, detail=False)
def test_do_chassis_node_list_details(self):
client_mock = mock.MagicMock()
chassis_mock = mock.MagicMock(spec_set=[])
args = self._get_client_mock_args(chassis=chassis_mock, detail=True)
c_shell.do_chassis_node_list(client_mock, args)
client_mock.chassis.list_nodes.assert_called_once_with(
chassis_mock, detail=True)

View File

@@ -17,34 +17,53 @@ import testtools
from ironicclient.v1 import resource_fields
class ResourceFieldsTest(testtools.TestCase):
class ResourceTest(testtools.TestCase):
def setUp(self):
super(ResourceTest, self).setUp()
self._saved_ids = resource_fields.Resource.FIELDS
resource_fields.Resource.FIELDS = {
'item1': 'ITEM1',
'2nd_item': 'A second item',
'item_3': 'Third item',
}
def test_chassis_fields(self):
self.assertEqual(
len(resource_fields.CHASSIS_FIELDS),
len(resource_fields.CHASSIS_FIELD_LABELS))
def tearDown(self):
super(ResourceTest, self).tearDown()
resource_fields.Resource.FIELDS = self._saved_ids
def test_chassis_list_fields(self):
self.assertEqual(
len(resource_fields.CHASSIS_LIST_FIELDS),
len(resource_fields.CHASSIS_LIST_FIELD_LABELS))
def test_fields_single_value(self):
# Make sure single value is what we expect
foo = resource_fields.Resource(['item1'])
self.assertEqual(('item1',), foo.fields)
self.assertEqual(('ITEM1',), foo.labels)
self.assertEqual(('item1',), foo.sort_fields)
self.assertEqual(('ITEM1',), foo.sort_labels)
def test_node_fields(self):
self.assertEqual(
len(resource_fields.NODE_FIELDS),
len(resource_fields.NODE_FIELD_LABELS))
def test_fields_multiple_value_order(self):
# Make sure order is maintained
foo = resource_fields.Resource(['2nd_item', 'item1'])
self.assertEqual(('2nd_item', 'item1'), foo.fields)
self.assertEqual(('A second item', 'ITEM1'), foo.labels)
self.assertEqual(('2nd_item', 'item1'), foo.sort_fields)
self.assertEqual(('A second item', 'ITEM1'), foo.sort_labels)
def test_node_list_fields(self):
self.assertEqual(
len(resource_fields.NODE_LIST_FIELDS),
len(resource_fields.NODE_LIST_FIELD_LABELS))
def test_sort_excluded(self):
# Test excluding of fields for sort purposes
foo = resource_fields.Resource(['item_3', 'item1', '2nd_item'],
sort_excluded=['item1'])
self.assertEqual(('item_3', '2nd_item'), foo.sort_fields)
self.assertEqual(('Third item', 'A second item'), foo.sort_labels)
def test_port_fields(self):
self.assertEqual(
len(resource_fields.PORT_FIELDS),
len(resource_fields.PORT_FIELD_LABELS))
def test_sort_excluded_unknown(self):
# Test sort_excluded value not in the field_ids
self.assertRaises(
ValueError,
resource_fields.Resource,
['item_3', 'item1', '2nd_item'],
sort_excluded=['item1', 'foo'])
def test_port_list_fields(self):
self.assertEqual(
len(resource_fields.PORT_LIST_FIELDS),
len(resource_fields.PORT_LIST_FIELD_LABELS))
def test_unknown_field_id(self):
self.assertRaises(
KeyError,
resource_fields.Resource,
['item1', 'unknown_id'])

View File

@@ -63,13 +63,13 @@ def do_chassis_show(cc, args):
def do_chassis_list(cc, args):
"""List the chassis."""
if args.detail:
fields = res_fields.CHASSIS_FIELDS
field_labels = res_fields.CHASSIS_FIELD_LABELS
sort_fields = res_fields.CHASSIS_SORT_FIELDS
sort_field_labels = res_fields.CHASSIS_SORT_FIELD_LABELS
fields = res_fields.CHASSIS_DETAILED_RESOURCE.fields
field_labels = res_fields.CHASSIS_DETAILED_RESOURCE.labels
sort_fields = res_fields.CHASSIS_DETAILED_RESOURCE.sort_fields
sort_field_labels = res_fields.CHASSIS_DETAILED_RESOURCE.sort_labels
else:
fields = res_fields.CHASSIS_LIST_FIELDS
field_labels = res_fields.CHASSIS_LIST_FIELD_LABELS
fields = res_fields.CHASSIS_RESOURCE.fields
field_labels = res_fields.CHASSIS_RESOURCE.labels
sort_fields = fields
sort_field_labels = field_labels
@@ -169,11 +169,11 @@ def do_chassis_update(cc, args):
def do_chassis_node_list(cc, args):
"""List the nodes contained in a chassis."""
if args.detail:
fields = res_fields.NODE_FIELDS
field_labels = res_fields.NODE_FIELD_LABELS
fields = res_fields.NODE_DETAILED_RESOURCE.fields
field_labels = res_fields.NODE_DETAILED_RESOURCE.labels
else:
fields = res_fields.NODE_LIST_FIELDS
field_labels = res_fields.NODE_LIST_FIELD_LABELS
fields = res_fields.NODE_RESOURCE.fields
field_labels = res_fields.NODE_RESOURCE.labels
params = utils.common_params_for_list(args, fields, field_labels)

View File

@@ -25,7 +25,9 @@ from ironicclient.v1 import resource_fields as res_fields
def _print_node_show(node):
data = dict([(f, getattr(node, f, '')) for f in res_fields.NODE_FIELDS])
data = dict(
[(f, getattr(node, f, ''))
for f in res_fields.NODE_DETAILED_RESOURCE.fields])
cliutils.print_dict(data, wrap=72)
@@ -97,13 +99,13 @@ def do_node_list(cc, args):
params['detail'] = args.detail
if args.detail:
fields = res_fields.NODE_FIELDS
field_labels = res_fields.NODE_FIELD_LABELS
sort_fields = res_fields.NODE_SORT_FIELDS
sort_field_labels = res_fields.NODE_SORT_FIELD_LABELS
fields = res_fields.NODE_DETAILED_RESOURCE.fields
field_labels = res_fields.NODE_DETAILED_RESOURCE.labels
sort_fields = res_fields.NODE_DETAILED_RESOURCE.sort_fields
sort_field_labels = res_fields.NODE_DETAILED_RESOURCE.sort_labels
else:
fields = res_fields.NODE_LIST_FIELDS
field_labels = res_fields.NODE_LIST_FIELD_LABELS
fields = res_fields.NODE_RESOURCE.fields
field_labels = res_fields.NODE_RESOURCE.labels
sort_fields = fields
sort_field_labels = field_labels
@@ -277,11 +279,11 @@ def do_node_vendor_passthru(cc, args):
def do_node_port_list(cc, args):
"""List the ports associated with a node."""
if args.detail:
fields = res_fields.PORT_FIELDS
field_labels = res_fields.PORT_FIELD_LABELS
fields = res_fields.PORTS_DETAILED_RESOURCE.fields
field_labels = res_fields.PORTS_DETAILED_RESOURCE.labels
else:
fields = res_fields.PORT_LIST_FIELDS
field_labels = res_fields.PORT_LIST_FIELD_LABELS
fields = res_fields.PORT_RESOURCE.fields
field_labels = res_fields.PORT_RESOURCE.labels
params = utils.common_params_for_list(args, fields, field_labels)

View File

@@ -84,13 +84,13 @@ def do_port_list(cc, args):
params['address'] = args.address
if args.detail:
fields = res_fields.PORT_FIELDS
field_labels = res_fields.PORT_FIELD_LABELS
sort_fields = res_fields.PORT_SORT_FIELDS
sort_field_labels = res_fields.PORT_SORT_FIELD_LABELS
fields = res_fields.PORT_DETAILED_RESOURCE.fields
field_labels = res_fields.PORT_DETAILED_RESOURCE.labels
sort_fields = res_fields.PORT_DETAILED_RESOURCE.sort_fields
sort_field_labels = res_fields.PORT_DETAILED_RESOURCE.sort_labels
else:
fields = res_fields.PORT_LIST_FIELDS
field_labels = res_fields.PORT_LIST_FIELD_LABELS
fields = res_fields.PORT_RESOURCE.fields
field_labels = res_fields.PORT_RESOURCE.labels
sort_fields = fields
sort_field_labels = field_labels

View File

@@ -13,77 +13,171 @@
# License for the specific language governing permissions and limitations
# under the License.
from ironicclient.common.i18n import _
class Resource(object):
"""Resource class
This class is used to manage the various fields that a resource (e.g.
Chassis, Node, Port) contains. An individual field consists of a
'field_id' (key) and a 'label' (value). The caller only provides the
'field_ids' when instantiating the object.
Ordering of the 'field_ids' will be preserved as specified by the caller.
It also provides the ability to exclude some of these fields when they are
being used for sorting.
"""
FIELDS = {
'address': 'Address',
'chassis_uuid': 'Chassis UUID',
'console_enabled': 'Console Enabled',
'created_at': 'Created At',
'description': 'Description',
'driver': 'Driver',
'driver_info': 'Driver Info',
'driver_internal_info': 'Driver Internal Info',
'extra': 'Extra',
'inspection_finished_at': 'Inspection Finished At',
'inspection_started_at': 'Inspection Started At',
'instance_info': 'Instance Info',
'instance_uuid': 'Instance UUID',
'last_error': 'Last Error',
'maintenance': 'Maintenance',
'maintenance_reason': 'Maintenance Reason',
'name': 'Name',
'node_uuid': 'Node UUID',
'power_state': 'Power State',
'properties': 'Properties',
'provision_state': 'Provisioning State',
'reservation': 'Reservation',
'target_power_state': 'Target Power State',
'target_provision_state': 'Target Provision State',
'updated_at': 'Updated At',
'uuid': 'UUID',
}
def __init__(self, field_ids, sort_excluded=None):
"""Create a Resource object
:param field_ids: A list of strings that the Resource object will
contain. Each string must match an existing key in
FIELDS.
:param sort_excluded: Optional. A list of strings that will not be used
for sorting. Must be a subset of 'field_ids'.
:raises: ValueError if sort_excluded contains value not in field_ids
"""
self._fields = tuple(field_ids)
self._labels = tuple([self.FIELDS[x] for x in field_ids])
if sort_excluded is None:
sort_excluded = []
not_existing = set(sort_excluded) - set(field_ids)
if not_existing:
raise ValueError(
_("sort_excluded specified with value not contained in "
"field_ids. Unknown value(s): %s") % ','.join(not_existing))
self._sort_fields = tuple(
[x for x in field_ids if x not in sort_excluded])
self._sort_labels = tuple([self.FIELDS[x] for x in self._sort_fields])
@property
def fields(self):
return self._fields
@property
def labels(self):
return self._labels
@property
def sort_fields(self):
return self._sort_fields
@property
def sort_labels(self):
return self._sort_labels
# Chassis
CHASSIS_FIELDS = ['uuid', 'description', 'created_at', 'updated_at', 'extra']
CHASSIS_FIELD_LABELS = ['UUID', 'Description', 'Created At', 'Updated At',
'Extra']
CHASSIS_SORT_FIELDS = ['uuid', 'description', 'created_at', 'updated_at']
CHASSIS_SORT_FIELD_LABELS = ['UUID', 'Description', 'Created At', 'Updated At']
CHASSIS_LIST_FIELDS = ['uuid', 'description']
CHASSIS_LIST_FIELD_LABELS = ['UUID', 'Description']
CHASSIS_DETAILED_RESOURCE = Resource(
['uuid',
'description',
'created_at',
'updated_at',
'extra',
],
sort_excluded=['extra'])
CHASSIS_RESOURCE = Resource(
['uuid',
'description',
])
# Nodes
NODE_FIELDS = ['chassis_uuid', 'created_at', 'console_enabled', 'driver',
'driver_info', 'driver_internal_info', 'extra',
'instance_info', 'instance_uuid', 'last_error',
'maintenance', 'maintenance_reason', 'power_state',
'properties', 'provision_state', 'reservation',
'target_power_state', 'target_provision_state',
'updated_at', 'inspection_finished_at',
'inspection_started_at', 'uuid', 'name']
NODE_FIELD_LABELS = ['Chassis UUID', 'Created At', 'Console Enabled', 'Driver',
'Driver Info', 'Driver Internal Info', 'Extra',
'Instance Info', 'Instance UUID', 'Last Error',
'Maintenance', 'Maintenance Reason', 'Power State',
'Properties', 'Provision State', 'Reservation',
'Target Power State', 'Target Provision State',
'Updated At', 'Inspection Finished At',
'Inspection Started At', 'UUID', 'Name']
# The server cannot sort on "chassis_uuid" because it isn't a
# column in the "nodes" database table. "chassis_id" is stored,
# but it is internal to ironic. See bug #1443003 for more details.
NODE_SORT_FIELDS = [x for x in NODE_FIELDS if x not in [
'chassis_uuid', 'driver_info', 'driver_internal_info',
'extra', 'instance_info', 'properties']]
NODE_SORT_FIELD_LABELS = [x for x in NODE_FIELD_LABELS if x not in [
'Chassis UUID', 'Driver Info',
'Driver Internal Info', 'EXTRA',
'Instance Info', 'Properties']]
NODE_LIST_FIELDS = ['uuid', 'name', 'instance_uuid', 'power_state',
'provision_state', 'maintenance']
NODE_LIST_FIELD_LABELS = ['UUID', 'Name', 'Instance UUID', 'Power State',
'Provisioning State', 'Maintenance']
NODE_DETAILED_RESOURCE = Resource(
['chassis_uuid',
'created_at',
'console_enabled',
'driver',
'driver_info',
'driver_internal_info',
'extra',
'instance_info',
'instance_uuid',
'last_error',
'maintenance',
'maintenance_reason',
'power_state',
'properties',
'provision_state',
'reservation',
'target_power_state',
'target_provision_state',
'updated_at',
'inspection_finished_at',
'inspection_started_at',
'uuid',
'name',
],
sort_excluded=[
# The server cannot sort on "chassis_uuid" because it isn't a column in
# the "nodes" database table. "chassis_id" is stored, but it is
# internal to ironic. See bug #1443003 for more details.
'chassis_uuid',
'driver_info',
'driver_internal_info',
'extra',
'instance_info',
'properties',
])
NODE_RESOURCE = Resource(
['uuid',
'name',
'instance_uuid',
'power_state',
'provision_state',
'maintenance',
])
# Ports
PORT_FIELDS = ['uuid', 'address', 'created_at', 'extra', 'node_uuid',
'updated_at']
PORT_FIELD_LABELS = ['UUID', 'Address', 'Created At', 'Extra', 'Node UUID',
'Updated At']
# The server cannot sort on "node_uuid" because it isn't a
# column in the "ports" database table. "node_id" is stored,
# but it is internal to ironic. See bug #1443003 for more details.
PORT_SORT_FIELDS = ['uuid', 'address', 'created_at', 'updated_at']
PORT_SORT_FIELD_LABELS = ['UUID', 'Address', 'Created At', 'Updated At']
PORT_LIST_FIELDS = ['uuid', 'address']
PORT_LIST_FIELD_LABELS = ['UUID', 'Address']
PORT_DETAILED_RESOURCE = Resource(
['uuid',
'address',
'created_at',
'extra',
'node_uuid',
'updated_at',
],
sort_excluded=[
'extra',
# The server cannot sort on "node_uuid" because it isn't a column in
# the "ports" database table. "node_id" is stored, but it is internal
# to ironic. See bug #1443003 for more details.
'node_uuid',
])
PORT_RESOURCE = Resource(
['uuid',
'address',
])