diff --git a/manila/cmd/manage.py b/manila/cmd/manage.py index 59bcb3aa33..6d0aafebcc 100644 --- a/manila/cmd/manage.py +++ b/manila/cmd/manage.py @@ -55,9 +55,11 @@ import os import sys +import yaml from oslo_config import cfg from oslo_log import log +from oslo_serialization import jsonutils from manila.common import config # Need to register global_opts # noqa from manila import context @@ -69,6 +71,7 @@ from manila import version CONF = cfg.CONF +ALLOWED_OUTPUT_FORMATS = ['table', 'json', 'yaml'] HOST_UPDATE_HELP_MSG = ("A fully qualified host string is of the format " "'HostA@BackendB#PoolC'. Provide only the host name " "(ex: 'HostA') to update the hostname part of " @@ -78,6 +81,8 @@ HOST_UPDATE_HELP_MSG = ("A fully qualified host string is of the format " HOST_UPDATE_CURRENT_HOST_HELP = ("Current share host name. %s" % HOST_UPDATE_HELP_MSG) HOST_UPDATE_NEW_HOST_HELP = "New share host name. %s" % HOST_UPDATE_HELP_MSG +LIST_OUTPUT_FORMAT_HELP = ("Format to be used to print the output (table, " + "json, yaml). Defaults to 'table'") SHARE_SERVERS_UPDATE_HELP = ("List of share servers to be updated, separated " "by commas.") SHARE_SERVERS_UPDATE_CAPABILITIES_HELP = ( @@ -93,6 +98,37 @@ def args(*args, **kwargs): return _decorator +class ListCommand(object): + + def list_json(self, resource_name, resource_list): + resource_list = {resource_name: resource_list} + object_list = jsonutils.dumps(resource_list, indent=4) + print(object_list) + + def list_yaml(self, resource_name, resource_list): + resource_list = {resource_name: resource_list} + data_yaml = yaml.dump(resource_list) + print(data_yaml) + + def list_table(self, resource_name, resource_list): + print_format = "{0:<16} {1:<36} {2:<16} {3:<10} {4:<5} {5:<10}" + print(print_format.format( + *[k.capitalize().replace( + '_', ' ') for k in resource_list[0].keys()])) + for resource in resource_list: + # Print is not transforming into a string, so let's ensure it + # happens + resource['updated_at'] = str(resource['updated_at']) + print(print_format.format(*resource.values())) + + def _check_format_output(self, format_output): + if format_output not in ALLOWED_OUTPUT_FORMATS: + print('Invalid output format specified. Defaulting to table.') + return 'table' + else: + return format_output + + class ShellCommands(object): def bpython(self): """Runs a bpython shell. @@ -319,35 +355,35 @@ class GetLogCommands(object): print("No manila entries in syslog!") -class ServiceCommands(object): +class ServiceCommands(ListCommand): """Methods for managing services.""" - def list(self): + + @args('--format_output', required=False, default='table', + help=LIST_OUTPUT_FORMAT_HELP) + def list(self, format_output): """Show a list of all manila services.""" ctxt = context.get_admin_context() services = db.service_get_all(ctxt) - print_format = "%-16s %-36s %-16s %-10s %-5s %-10s" - print(print_format % ( - _('Binary'), - _('Host'), - _('Zone'), - _('Status'), - _('State'), - _('Updated At')) - ) - for svc in services: - alive = utils.service_is_up(svc) - art = ":-)" if alive else "XXX" + format_output = self._check_format_output(format_output) + + services_list = [] + for service in services: + alive = utils.service_is_up(service) + state = ":-)" if alive else "XXX" status = 'enabled' - if svc['disabled']: + if service['disabled']: status = 'disabled' - print(print_format % ( - svc['binary'], - svc['host'].partition('.')[0], - svc['availability_zone']['name'], - status, - art, - svc['updated_at'], - )) + services_list.append({ + 'binary': service['binary'], + 'host': service['host'].partition('.')[0], + 'zone': service['availability_zone']['name'], + 'status': status, + 'state': state, + 'updated_at': str(service['updated_at']), + }) + + method_list_name = f'list_{format_output}' + getattr(self, method_list_name)('services', services_list) def cleanup(self): """Remove manila services reporting as 'down'.""" diff --git a/manila/tests/cmd/test_manage.py b/manila/tests/cmd/test_manage.py index b784e975cc..cfee058986 100644 --- a/manila/tests/cmd/test_manage.py +++ b/manila/tests/cmd/test_manage.py @@ -18,9 +18,11 @@ import io import readline import sys from unittest import mock +import yaml import ddt from oslo_config import cfg +from oslo_serialization import jsonutils from manila.cmd import manage as manila_manage from manila import context @@ -48,6 +50,7 @@ class ManilaCmdManageTestCase(test.TestCase): self.service_cmds = manila_manage.ServiceCommands() self.share_cmds = manila_manage.ShareCommands() self.server_cmds = manila_manage.ShareServerCommands() + self.list_commands = manila_manage.ListCommand() @mock.patch.object(manila_manage.ShellCommands, 'run', mock.Mock()) def test_shell_commands_bpython(self): @@ -291,7 +294,7 @@ class ManilaCmdManageTestCase(test.TestCase): 'Zone', 'Status', 'State', - 'Updated At') + 'Updated at') service_format = format % (service['binary'], service['host'].partition('.')[0], service['availability_zone']['name'], @@ -299,12 +302,47 @@ class ManilaCmdManageTestCase(test.TestCase): ':-)', service['updated_at']) expected_out = print_format + '\n' + service_format + '\n' - self.service_cmds.list() + self.service_cmds.list(format_output='table') self.assertEqual(expected_out, fake_out.getvalue()) get_admin_context.assert_called_with() service_get_all.assert_called_with(ctxt) service_is_up.assert_called_with(service) + @ddt.data('json', 'yaml') + def test_service_commands_list_format(self, format_output): + ctxt = context.RequestContext('fake-user', 'fake-project') + format_method_name = f'list_{format_output}' + mock_list_method = self.mock_object( + self.service_cmds, format_method_name) + get_admin_context = self.mock_object(context, 'get_admin_context') + service_get_all = self.mock_object(db, 'service_get_all') + service_is_up = self.mock_object(utils, 'service_is_up') + get_admin_context.return_value = ctxt + service = {'binary': 'manila-binary', + 'host': 'fake-host.fake-domain', + 'availability_zone': {'name': 'fake-zone'}, + 'updated_at': '2014-06-30 11:22:33', + 'disabled': False} + services = [service] + service_get_all.return_value = services + service_is_up.return_value = True + + with mock.patch('sys.stdout', new=io.StringIO()): + self.service_cmds.list(format_output=format_output) + get_admin_context.assert_called_with() + service_get_all.assert_called_with(ctxt) + service_is_up.assert_called_with(service) + service_format = { + 'binary': service['binary'], + 'host': service['host'].partition('.')[0], + 'zone': service['availability_zone']['name'], + 'status': 'enabled', + 'state': ':-)', + 'updated_at': service['updated_at'], + } + mock_list_method.assert_called_once_with( + 'services', [service_format]) + @ddt.data(True, False) def test_service_commands_cleanup(self, service_is_up): ctxt = context.RequestContext('fake-user', 'fake-project') @@ -508,3 +546,47 @@ class ManilaCmdManageTestCase(test.TestCase): True) self.assertEqual(1, exit.code) + + @mock.patch('builtins.print') + def test_list_commands_json(self, mock_print): + resource_name = 'service' + service_format = [{ + 'binary': 'manila-binary', + 'host': 'fake-host', + 'availability_zone': 'fakeaz', + 'status': 'enabled', + 'state': ':-)', + 'updated_at': '13 04:57:49 PM -03 2023' + }] + + mock_json_dumps = self.mock_object( + jsonutils, 'dumps', mock.Mock(return_value=service_format[0])) + services = {resource_name: service_format} + + self.list_commands.list_json('service', service_format) + + mock_json_dumps.assert_called_once_with( + services, indent=4) + mock_print.assert_called_once_with(service_format[0]) + + @mock.patch('builtins.print') + def test_list_commands_yaml(self, mock_print): + resource_name = 'service' + service_format = [{ + 'binary': 'manila-binary', + 'host': 'fake-host', + 'availability_zone': 'fakeaz', + 'status': 'enabled', + 'state': ':-)', + 'updated_at': '13 04:57:49 PM -03 2023' + }] + + mock_yaml_dump = self.mock_object( + yaml, 'dump', mock.Mock(return_value=service_format[0])) + services = {resource_name: service_format} + + self.list_commands.list_yaml('service', service_format) + + mock_yaml_dump.assert_called_once_with( + services) + mock_print.assert_called_once_with(service_format[0]) diff --git a/releasenotes/notes/add-format-output-to-manila-manage-c0bbccb16369e5d3.yaml b/releasenotes/notes/add-format-output-to-manila-manage-c0bbccb16369e5d3.yaml new file mode 100644 index 0000000000..c2ced67979 --- /dev/null +++ b/releasenotes/notes/add-format-output-to-manila-manage-c0bbccb16369e5d3.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The possibility to specify the desired output format while issuing the + `manila-manage service list` command was added. It is now possible to + display the output also in yaml and json formats.