Add '--fields' to show more columns for bay-list

This patch does:
  - move '_get_list_table_columns_and_formatters' function
    to cliutils.
  - add '--fields' to 'bay-list' command to show more
    specific fields.
  - add test case for bay-list with '--fields'.

Change-Id: I2fdd66f0c6648bb2c6ae65325cbba3656b9854f6
Closes-Bug: #1535687
This commit is contained in:
Anh Tran 2016-05-17 11:39:14 +07:00
parent f1d97b652a
commit d6a697cabb
4 changed files with 110 additions and 60 deletions

@ -24,6 +24,7 @@ import os
import sys
import textwrap
from magnumclient.common.apiclient import exceptions
from oslo_utils import encodeutils
from oslo_utils import strutils
import prettytable
@ -324,3 +325,60 @@ def make_field_formatter(attr, filters=None):
name = _format_field_name(attr)
formatter = get_field
return name, formatter
def _get_list_table_columns_and_formatters(fields, objs, exclude_fields=(),
filters=None):
"""Check and add fields to output columns.
If there is any value in fields that not an attribute of obj,
CommandError will be raised.
If fields has duplicate values (case sensitive), we will make them unique
and ignore duplicate ones.
:param fields: A list of string contains the fields to be printed.
:param objs: An list of object which will be used to check if field is
valid or not. Note, we don't check fields if obj is None or
empty.
:param exclude_fields: A tuple of string which contains the fields to be
excluded.
:param filters: A dictionary defines how to get value from fields, this
is useful when field's value is a complex object such as
dictionary.
:return: columns, formatters.
columns is a list of string which will be used as table header.
formatters is a dictionary specifies how to display the value
of the field.
They can be [], {}.
:raise: magnumclient.common.apiclient.exceptions.CommandError.
"""
if objs and isinstance(objs, list):
obj = objs[0]
else:
obj = None
fields = None
columns = []
formatters = {}
if fields:
non_existent_fields = []
exclude_fields = set(exclude_fields)
for field in fields.split(','):
if not hasattr(obj, field):
non_existent_fields.append(field)
continue
if field in exclude_fields:
continue
field_title, formatter = make_field_formatter(field, filters)
columns.append(field_title)
formatters[field_title] = formatter
exclude_fields.add(field)
if non_existent_fields:
raise exceptions.CommandError(
_("Non-existent fields are specified: %s") %
non_existent_fields
)
return columns, formatters

@ -16,6 +16,21 @@ import mock
from magnumclient import exceptions
from magnumclient.tests.v1 import shell_test_base
from magnumclient.v1.bays import Bay
class FakeBay(Bay):
def __init__(self, manager=None, info={}, **kwargs):
Bay.__init__(self, manager=manager, info=info)
self.uuid = kwargs.get('uuid', 'x')
self.name = kwargs.get('name', 'x')
self.baymodel_id = kwargs.get('baymodel_id', 'x')
self.stack_id = kwargs.get('stack_id', 'x')
self.status = kwargs.get('status', 'x')
self.master_count = kwargs.get('master_count', 1)
self.node_count = kwargs.get('node_count', 1)
self.links = kwargs.get('links', [])
self.bay_create_timeout = kwargs.get('bay_create_timeout', 0)
class ShellTest(shell_test_base.TestCommandLineArgument):
@ -34,6 +49,29 @@ class ShellTest(shell_test_base.TestCommandLineArgument):
'--sort-key uuid')
self.assertTrue(mock_list.called)
@mock.patch('magnumclient.v1.bays.BayManager.list')
def test_bay_list_ignored_duplicated_field(self, mock_list):
mock_list.return_value = [FakeBay()]
self._test_arg_success('bay-list --fields status,status,status,name',
keyword='\n| uuid | name | Status |\n')
# Output should be
# +------+------+--------+
# | uuid | name | Status |
# +------+------+--------+
# | x | x | x |
# +------+------+--------+
self.assertTrue(mock_list.called)
@mock.patch('magnumclient.v1.bays.BayManager.list')
def test_bay_list_failure_with_invalid_field(self, mock_list):
mock_list.return_value = [FakeBay()]
_error_msg = [".*?^Non-existent fields are specified: ['xxx','zzz']"]
self.assertRaises(exceptions.CommandError,
self._test_arg_failure,
'bay-list --fields xxx,stack_id,zzz,status',
_error_msg)
self.assertTrue(mock_list.called)
@mock.patch('magnumclient.v1.bays.BayManager.list')
def test_bay_list_failure_invalid_arg(self, mock_list):
_error_msg = [

@ -14,7 +14,6 @@
import os.path
from magnumclient.common.apiclient import exceptions
from magnumclient.common import cliutils as utils
from magnumclient.common import utils as magnum_utils
from magnumclient.i18n import _
@ -175,7 +174,7 @@ def do_baymodel_list(cs, args):
sort_key=args.sort_key,
sort_dir=args.sort_dir)
columns = ['uuid', 'name']
columns += _get_list_table_columns_and_formatters(
columns += utils._get_list_table_columns_and_formatters(
args.fields, nodes,
exclude_fields=(c.lower() for c in columns))[0]
utils.print_list(nodes, columns,
@ -207,60 +206,3 @@ def do_baymodel_update(cs, args):
baymodel = cs.baymodels.update(args.baymodel, patch)
_show_baymodel(baymodel)
def _get_list_table_columns_and_formatters(fields, objs, exclude_fields=(),
filters=None):
"""Check and add fields to output columns.
If there is any value in fields that not an attribute of obj,
CommandError will be raised.
If fields has duplicate values (case sensitive), we will make them unique
and ignore duplicate ones.
:param fields: A list of string contains the fields to be printed.
:param objs: An list of object which will be used to check if field is
valid or not. Note, we don't check fields if obj is None or
empty.
:param exclude_fields: A tuple of string which contains the fields to be
excluded.
:param filters: A dictionary defines how to get value from fields, this
is useful when field's value is a complex object such as
dictionary.
:return: columns, formatters.
columns is a list of string which will be used as table header.
formatters is a dictionary specifies how to display the value
of the field.
They can be [], {}.
:raise: magnumclient.common.apiclient.exceptions.CommandError.
"""
if objs and isinstance(objs, list):
obj = objs[0]
else:
obj = None
fields = None
columns = []
formatters = {}
if fields:
non_existent_fields = []
exclude_fields = set(exclude_fields)
for field in fields.split(','):
if not hasattr(obj, field):
non_existent_fields.append(field)
continue
if field in exclude_fields:
continue
field_title, formatter = utils.make_field_formatter(field, filters)
columns.append(field_title)
formatters[field_title] = formatter
exclude_fields.add(field)
if non_existent_fields:
raise exceptions.CommandError(
_("Non-existent fields are specified: %s") %
non_existent_fields
)
return columns, formatters

@ -14,6 +14,7 @@
from magnumclient.common import cliutils as utils
from magnumclient.common import utils as magnum_utils
from magnumclient.i18n import _
def _show_bay(bay):
@ -37,12 +38,23 @@ def _show_bay(bay):
metavar='<sort-dir>',
choices=['desc', 'asc'],
help='Direction to sort. "asc" or "desc".')
@utils.arg('--fields',
default=None,
metavar='<fields>',
help=_('Comma-separated list of fields to display. '
'Available fields: uuid, name, baymodel_id, stack_id, '
'status, master_count, node_count, links, bay_create_timeout'
)
)
def do_bay_list(cs, args):
"""Print a list of available bays."""
bays = cs.bays.list(marker=args.marker, limit=args.limit,
sort_key=args.sort_key,
sort_dir=args.sort_dir)
columns = ('uuid', 'name', 'node_count', 'master_count', 'status')
columns = ['uuid', 'name']
columns += utils._get_list_table_columns_and_formatters(
args.fields, bays,
exclude_fields=(c.lower() for c in columns))[0]
utils.print_list(bays, columns,
{'versions': magnum_utils.print_list_field('versions')},
sortby_index=None)