Added optional arguments to host-list command and unit tests.

Implemented handling of argument values as well as defined
acceptable field values for hosts.

Change-Id: I2fc2267d402a87e09c059dfdc8b10d86e8eeb69c
This commit is contained in:
Chris Spencer
2016-07-28 12:33:55 -07:00
parent d4f0c1ccb8
commit 052caea612
4 changed files with 188 additions and 2 deletions

View File

@@ -75,6 +75,12 @@ class HTTPError(ClientException):
self.status_code = code self.status_code = code
class CommandError(ClientException):
"""Client command was invalid or failed."""
message = "The command used was invalid or caused an error."""
class ConnectionFailed(HTTPError): class ConnectionFailed(HTTPError):
"""Connecting to the server failed.""" """Connecting to the server failed."""

View File

@@ -13,11 +13,73 @@
# under the License. # under the License.
"""Hosts resource and resource shell wrapper.""" """Hosts resource and resource shell wrapper."""
from cratonclient.common import cliutils from cratonclient.common import cliutils
from cratonclient import exceptions as exc
from cratonclient.v1.hosts import HOST_FIELDS as h_fields
@cliutils.arg('-c', '--cell',
metavar='<cell>',
type=int,
help='Integer ID of the cell that contains '
'the desired list of hosts.')
@cliutils.arg('--detail',
action='store_true',
default=False,
help='Show detailed information about the hosts.')
@cliutils.arg('--limit',
metavar='<limit>',
type=int,
help='Maximum number of hosts to return.')
@cliutils.arg('--sort-key',
metavar='<field>',
help='Host field that will be used for sorting.')
@cliutils.arg('--sort-dir',
metavar='<direction>',
default='asc',
help='Sort direction: "asc" (default) or "desc".')
@cliutils.arg('--fields',
nargs='+',
metavar='<fields>',
default=[],
help='Comma-separated list of fields to display. '
'Only these fields will be fetched from the server. '
'Can not be used when "--detail" is specified')
def do_host_list(cc, args): def do_host_list(cc, args):
"""Print list of hosts which are registered with the Craton service.""" """Print list of hosts which are registered with the Craton service."""
params = {} params = {}
columns = ['id', 'name'] default_fields = ['id', 'name', 'type', 'active', 'cell_id']
if args.cell is not None:
params['cell'] = args.cell
if args.limit is not None:
if args.limit < 0:
raise exc.CommandError('Invalid limit specified. Expected '
'non-negative limit, got {0}'
.format(args.limit))
params['limit'] = args.limit
if args.detail:
fields = h_fields
params['detail'] = args.detail
elif args.fields:
fields = {x: h_fields[x] for x in args.fields}
else:
fields = {x: h_fields[x] for x in default_fields}
if args.sort_key is not None:
fields_map = dict(zip(fields.keys(), fields.keys()))
# TODO(cmspence): Do we want to allow sorting by field heading value?
try:
sort_key = fields_map[args.sort_key]
except KeyError:
raise exc.CommandError(
'{0} is an invalid key for sorting, valid values for '
'--sort-key are: {1}'.format(args.sort_key, h_fields.keys())
)
params['sort_key'] = sort_key
if args.sort_dir is not None:
if args.sort_dir not in ('asc', 'desc'):
raise exc.CommandError('Invalid sort direction specified. The '
'expected valid values for --sort-dir '
'are: "asc", "desc".')
params['sort_dir'] = args.sort_dir
hosts = cc.hosts.list(args.craton_project_id, **params) hosts = cc.hosts.list(args.craton_project_id, **params)
cliutils.print_list(hosts, columns) cliutils.print_list(hosts, list(fields))

View File

@@ -14,6 +14,7 @@
import mock import mock
from cratonclient import exceptions as exc
from cratonclient.tests import base from cratonclient.tests import base
@@ -25,3 +26,105 @@ class TestHostsShell(base.ShellTestCase):
"""Verify that no arguments prints out all project hosts.""" """Verify that no arguments prints out all project hosts."""
self.shell('host-list') self.shell('host-list')
self.assertTrue(mock_list.called) self.assertTrue(mock_list.called)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_parse_param_success(self, mock_list):
"""Verify that success of parsing a subcommand argument."""
self.shell('host-list --limit 0')
self.assertTrue(mock_list.called)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_limit_0_succcess(self, mock_list):
"""Verify that --limit 0 prints out all project hosts."""
self.shell('host-list --limit 0')
mock_list.assert_called_once_with(mock.ANY, limit=0)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_limit_positive_num_success(self, mock_list):
"""Verify --limit X, where X is a positive integer, succeeds.
The command will print out X number of project hosts.
"""
self.shell('host-list --limit 1')
mock_list.assert_called_once_with(mock.ANY, limit=1)
def test_host_list_limit_negative_num_failure(self):
"""Verify --limit X, where X is a negative integer, fails.
The command will cause a Command Error message response.
"""
self.assertRaises(exc.CommandError,
self.shell,
'host-list --limit -1')
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_cell_success(self, mock_list):
"""Verify --cell arguments successfully pass cell to Client."""
for cell_arg in ['-c', '--cell']:
self.shell('host-list {0} 1'.format(cell_arg))
mock_list.assert_called_once_with(mock.ANY, cell=1)
mock_list.reset_mock()
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_detail_success(self, mock_list):
"""Verify --detail argument successfully pass detail to Client."""
self.shell('host-list --detail')
mock_list.assert_called_once_with(mock.ANY, detail=True)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
@mock.patch('cratonclient.common.cliutils.print_list')
def test_host_list_fields_success(self, mock_printlist, mock_list):
"""Verify --fields argument successfully passed to Client."""
self.shell('host-list --fields id name')
mock_list.assert_called_once_with(mock.ANY)
mock_printlist.assert_called_once_with(mock.ANY,
list({'id': 'ID',
'name': 'Name'}))
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_detail_and_fields_specified(self, mock_list):
"""Verify --fields ignored when --detail argument passed in."""
self.shell('host-list --fields id name --detail')
mock_list.assert_called_once_with(mock.ANY, detail=True)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_sort_key_field_key_success(self, mock_list):
"""Verify --sort-key arguments successfully passed to Client."""
self.shell('host-list --sort-key cell_id')
mock_list.assert_called_once_with(mock.ANY,
sort_key='cell_id',
sort_dir='asc')
def test_host_list_sort_key_invalid(self):
"""Verify --sort-key with invalid args, fails with Command Error."""
self.assertRaises(exc.CommandError,
self.shell,
'host-list --sort-key invalid')
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_sort_dir_not_passed_without_sort_key(self, mock_list):
"""Verify --sort-dir arg ignored without --sort-key."""
self.shell('host-list --sort-dir desc')
mock_list.assert_called_once_with(mock.ANY)
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_sort_dir_asc_success(self, mock_list):
"""Verify --sort-dir asc successfully passed to Client."""
self.shell('host-list --sort-key name --sort-dir asc')
mock_list.assert_called_once_with(mock.ANY,
sort_key='name',
sort_dir='asc')
@mock.patch('cratonclient.v1.hosts.HostManager.list')
def test_host_list_sort_dir_desc_success(self, mock_list):
"""Verify --sort-dir desc successfully passed to Client."""
self.shell('host-list --sort-key name --sort-dir desc')
mock_list.assert_called_once_with(mock.ANY,
sort_key='name',
sort_dir='desc')
def test_host_list_sort_dir_invalid_value(self):
"""Verify --sort-dir with invalid args, fails with Command Error."""
self.assertRaises(exc.CommandError,
self.shell,
'host-list --sort-key name --sort-dir invalid')

View File

@@ -32,3 +32,18 @@ class HostManager(crud.CRUDClient):
"""Retrieve the hosts in a specific region.""" """Retrieve the hosts in a specific region."""
kwargs['project'] = str(project_id) kwargs['project'] = str(project_id)
super(HostManager, self).list(**kwargs) super(HostManager, self).list(**kwargs)
HOST_FIELDS = {
'id': 'ID',
'name': 'Name',
'type': 'Type',
'project_id': 'Project ID',
'region_id': 'Region ID',
'cell_id': 'Cell ID',
'ip_address': 'IP Address',
'active': 'Active',
'note': 'Note',
'access_secret_id': "Access Secret ID",
'created_at': 'Created At',
'update_at': 'Updated At'
}