[Compute]Make column content readable for both human and machine

Currently, we use utils.format_dict(), utils.format_list(),
utils.format_list_of_dicts to make column value can be easy to read by
human, but osc support to format the CLI output into several format,
like: json, shell, csv, yaml, most of these should be understand by
program and code, so keeping the column content as the original value
make sense, like {u'name': u'RuiChen'} than name='RuiChen'

The patch include all compute commands.

Change-Id: I313a52f94895625e6045df870320840fee157759
Implements: blueprint osc-formattable-columns
Partial-Bug: #1538015
Partial-Bug: #1538006
This commit is contained in:
Rui Chen 2017-03-10 17:32:44 +08:00
parent 5309bf5f87
commit ff9bd34b3c
15 changed files with 335 additions and 175 deletions

@ -18,6 +18,7 @@
import logging
from osc_lib.cli import format_columns
from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
@ -324,7 +325,9 @@ class ShowAggregate(command.ShowOne):
# 'metadata' --> 'properties'
data._info.update(
{
'properties': utils.format_dict(data._info.pop('metadata')),
'properties': format_columns.DictColumn(
data._info.pop('metadata')
),
},
)

@ -17,6 +17,7 @@
import logging
from osc_lib.cli import format_columns
from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
@ -180,7 +181,14 @@ class CreateFlavor(command.ShowOne):
flavor_info = flavor._info.copy()
flavor_info.pop("links")
flavor_info['properties'] = utils.format_dict(flavor.get_keys())
flavor_info['properties'] = format_columns.DictColumn(
# NOTE(RuiChen): novaclient flavor.get_keys() return a mixin class
# DictWithMeta, that can't be represented properly
# in yaml format(-f yaml), wrapping it in base type
# dict is a workaround, please do not remove the
# conversion.
dict(flavor.get_keys())
)
return zip(*sorted(six.iteritems(flavor_info)))
@ -291,13 +299,19 @@ class ListFlavor(command.Lister):
"Properties",
)
for f in data:
f.properties = f.get_keys()
# NOTE(RuiChen): novaclient flavor.get_keys() return a mixin
# class DictWithMeta, that can't be represented
# properly in yaml format(-f yaml), wrapping it
# in base type dict is a workaround, please do
# not remove the conversion.
f.properties = dict(f.get_keys())
column_headers = columns
return (column_headers,
(utils.get_item_properties(
s, columns, formatters={'Properties': utils.format_dict},
s, columns,
formatters={'Properties': format_columns.DictColumn},
) for s in data))
@ -405,9 +419,7 @@ class ShowFlavor(command.ShowOne):
flavor=resource_flavor.id)
projects = [utils.get_field(access, 'tenant_id')
for access in flavor_access]
# TODO(Huanxuan Ao): This format case can be removed after
# patch https://review.openstack.org/#/c/330223/ merged.
access_projects = utils.format_list(projects)
access_projects = format_columns.ListColumn(projects)
except Exception as e:
msg = _("Failed to get access projects list "
"for flavor '%(flavor)s': %(e)s")
@ -419,7 +431,14 @@ class ShowFlavor(command.ShowOne):
})
flavor.pop("links", None)
flavor['properties'] = utils.format_dict(resource_flavor.get_keys())
flavor['properties'] = format_columns.DictColumn(
# NOTE(RuiChen): novaclient flavor.get_keys() return a mixin class
# DictWithMeta, that can't be represented properly
# in yaml format(-f yaml), wrapping it in base type
# dict is a workaround, please do not remove the
# conversion.
dict(resource_flavor.get_keys())
)
return zip(*sorted(six.iteritems(flavor)))

@ -23,6 +23,7 @@ import os
import sys
from novaclient.v2 import servers
from osc_lib.cli import format_columns
from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
@ -37,22 +38,6 @@ from openstackclient.identity import common as identity_common
LOG = logging.getLogger(__name__)
def _format_servers_list_networks(networks):
"""Return a formatted string of a server's networks
:param networks: a Server.networks field
:rtype: a string of formatted network addresses
"""
output = []
for (network, addresses) in networks.items():
if not addresses:
continue
addresses_csv = ', '.join(addresses)
group = "%s=%s" % (network, addresses_csv)
output.append(group)
return '; '.join(output)
def _format_servers_list_power_state(state):
"""Return a formatted string of a server's power state
@ -154,24 +139,26 @@ def _prep_server_detail(compute_client, image_client, server):
if 'os-extended-volumes:volumes_attached' in info:
info.update(
{
'volumes_attached': utils.format_list_of_dicts(
info.pop('os-extended-volumes:volumes_attached'))
'volumes_attached': format_columns.ListDictColumn(
info.pop('os-extended-volumes:volumes_attached')
)
}
)
if 'security_groups' in info:
info.update(
{
'security_groups': utils.format_list_of_dicts(
info.pop('security_groups'))
'security_groups': format_columns.ListDictColumn(
info.pop('security_groups')
)
}
)
# NOTE(dtroyer): novaclient splits these into separate entries...
# Format addresses in a useful way
info['addresses'] = _format_servers_list_networks(server.networks)
info['addresses'] = format_columns.DictListColumn(server.networks)
# Map 'metadata' field to 'properties'
info.update(
{'properties': utils.format_dict(info.pop('metadata'))}
{'properties': format_columns.DictColumn(info.pop('metadata'))}
)
# Migrate tenant_id to project_id naming
@ -1161,8 +1148,8 @@ class ListServer(command.Lister):
formatters={
'OS-EXT-STS:power_state':
_format_servers_list_power_state,
'Networks': _format_servers_list_networks,
'Metadata': utils.format_dict,
'Networks': format_columns.DictListColumn,
'Metadata': format_columns.DictColumn,
},
) for s in data))
return table

@ -17,6 +17,7 @@
import sys
from osc_lib.cli import format_columns
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@ -122,7 +123,9 @@ class CreateServerBackup(command.ShowOne):
if self.app.client_manager._api_version['image'] == '1':
info = {}
info.update(image._info)
info['properties'] = utils.format_dict(info.get('properties', {}))
info['properties'] = format_columns.DictColumn(
info.get('properties', {})
)
else:
# Get the right image module to format the output
image_module = importutils.import_module(

@ -17,6 +17,7 @@
import logging
from osc_lib.cli import format_columns
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@ -28,8 +29,8 @@ LOG = logging.getLogger(__name__)
_formatters = {
'policies': utils.format_list,
'members': utils.format_list,
'policies': format_columns.ListColumn,
'members': format_columns.ListColumn,
}
@ -155,8 +156,8 @@ class ListServerGroup(command.Lister):
(utils.get_item_properties(
s, columns,
formatters={
'Policies': utils.format_list,
'Members': utils.format_list,
'Policies': format_columns.ListColumn,
'Members': format_columns.ListColumn,
}
) for s in data))

@ -18,6 +18,7 @@
import logging
import sys
from osc_lib.cli import format_columns
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
@ -101,7 +102,9 @@ class CreateServerImage(command.ShowOne):
if self.app.client_manager._api_version['image'] == '1':
info = {}
info.update(image._info)
info['properties'] = utils.format_dict(info.get('properties', {}))
info['properties'] = format_columns.DictColumn(
info.get('properties', {})
)
else:
# Get the right image module to format the output
image_module = importutils.import_module(

@ -136,14 +136,7 @@ class AggregateTests(base.TestCase):
'internal',
cmd_output['availability_zone']
)
self.assertIn(
"c='d'",
cmd_output['properties']
)
self.assertNotIn(
"a='b'",
cmd_output['properties']
)
self.assertEqual({'c': 'd'}, cmd_output['properties'])
# Test unset
raw_output = self.openstack(
@ -157,10 +150,7 @@ class AggregateTests(base.TestCase):
'aggregate show -f json ' +
name2
))
self.assertNotIn(
"c='d'",
cmd_output['properties']
)
self.assertEqual({}, cmd_output['properties'])
def test_aggregate_add_and_remove_host(self):
"""Test aggregate add and remove host"""

@ -91,7 +91,7 @@ class FlavorTests(base.TestCase):
"--ram 123 " +
"--private " +
"--property a=b2 " +
"--property b=d2 " +
"--property c=d2 " +
name2
))
self.addCleanup(self.openstack, "flavor delete " + name2)
@ -116,10 +116,7 @@ class FlavorTests(base.TestCase):
False,
cmd_output["os-flavor-access:is_public"],
)
self.assertEqual(
"a='b2', b='d2'",
cmd_output["properties"],
)
self.assertEqual({'a': 'b2', 'c': 'd2'}, cmd_output["properties"])
# Test list
cmd_output = json.loads(self.openstack(
@ -135,11 +132,11 @@ class FlavorTests(base.TestCase):
"--long"
))
col_name = [x["Name"] for x in cmd_output]
col_properties = [x['Properties'] for x in cmd_output]
self.assertIn(name1, col_name)
self.assertIn("a='b', c='d'", col_properties)
self.assertNotIn(name2, col_name)
self.assertNotIn("b2', b='d2'", col_properties)
props = [x['Properties'] for x in cmd_output]
self.assertIn({'a': 'b', 'c': 'd'}, props)
# Test list --public
cmd_output = json.loads(self.openstack(
@ -203,10 +200,8 @@ class FlavorTests(base.TestCase):
False,
cmd_output["os-flavor-access:is_public"],
)
self.assertEqual(
"a='first', b='second'",
cmd_output["properties"],
)
self.assertEqual({'a': 'first', 'b': 'second'},
cmd_output['properties'])
raw_output = self.openstack(
"flavor set " +
@ -224,10 +219,8 @@ class FlavorTests(base.TestCase):
"qaz",
cmd_output["id"],
)
self.assertEqual(
"a='third and 10', b='second', g='fourth'",
cmd_output['properties'],
)
self.assertEqual({'a': 'third and 10', 'b': 'second', 'g': 'fourth'},
cmd_output['properties'])
raw_output = self.openstack(
"flavor unset " +
@ -240,7 +233,5 @@ class FlavorTests(base.TestCase):
"flavor show -f json " +
name1
))
self.assertEqual(
"a='third and 10', g='fourth'",
cmd_output["properties"],
)
self.assertEqual({'a': 'third and 10', 'g': 'fourth'},
cmd_output['properties'])

@ -107,25 +107,19 @@ class ServerTests(common.ComputeTestCase):
'server show -f json ' +
name
))
# Really, shouldn't this be a list?
self.assertEqual(
"a='b', c='d'",
cmd_output['properties'],
)
self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties'])
raw_output = self.openstack(
'server unset ' +
'--property a ' +
name
)
self.assertOutput('', raw_output)
cmd_output = json.loads(self.openstack(
'server show -f json ' +
name
))
self.assertEqual(
"c='d'",
cmd_output['properties'],
)
self.assertEqual({'c': 'd'}, cmd_output['properties'])
# Test set --name
new_name = uuid.uuid4().hex
@ -248,10 +242,8 @@ class ServerTests(common.ComputeTestCase):
'server show -f json ' +
name
))
self.assertIn(
floating_ip,
cmd_output['addresses'],
)
self.assertIsInstance(cmd_output['addresses'], dict)
self.assertIn(floating_ip, cmd_output['addresses']['private'])
# detach ip
raw_output = self.openstack(
@ -265,10 +257,8 @@ class ServerTests(common.ComputeTestCase):
'server show -f json ' +
name
))
self.assertNotIn(
floating_ip,
cmd_output['addresses'],
)
self.assertIsInstance(cmd_output['addresses'], dict)
self.assertNotIn(floating_ip, cmd_output['addresses']['private'])
def test_server_reboot(self):
"""Test server reboot"""
@ -456,8 +446,10 @@ class ServerTests(common.ComputeTestCase):
server_name
))
volumes_attached = cmd_output['volumes_attached']
self.assertTrue(volumes_attached.startswith('id='))
attached_volume_id = volumes_attached.replace('id=', '')
self.assertIsInstance(volumes_attached, list)
self.assertEqual(1, len(volumes_attached))
self.assertIn('id', volumes_attached[0])
attached_volume_id = volumes_attached[0]['id']
# check the volume that attached on server
cmd_output = json.loads(self.openstack(
@ -514,7 +506,7 @@ class ServerTests(common.ComputeTestCase):
'server show -f json ' + server_name
))
self.assertIsNotNone(server['addresses'])
self.assertEqual('', server['addresses'])
self.assertEqual({}, server['addresses'])
def test_server_create_with_security_group(self):
"""Test server create with security group ID and name"""
@ -553,14 +545,27 @@ class ServerTests(common.ComputeTestCase):
self.assertIsNotNone(server['id'])
self.assertEqual(server_name, server['name'])
self.assertIn(str(security_group1['id']), server['security_groups'])
self.assertIn(str(security_group2['id']), server['security_groups'])
self.assertIsInstance(server['security_groups'], list)
self.assertEqual(2, len(server['security_groups']))
# NOTE(RuiChen): Nova return security group id in response of server
# create API, but return security group name in server
# show API for the same server, so we assert id and name
# for different create and show commands in the
# following code.
sg_ids = [each_sg['name'] for each_sg in server['security_groups']]
# Security group id is integer in nova-network, convert to string
self.assertIn(str(security_group1['id']), sg_ids)
self.assertIn(str(security_group2['id']), sg_ids)
self.wait_for_status(server_name, 'ACTIVE')
server = json.loads(self.openstack(
'server show -f json ' + server_name
))
self.assertIn(sg_name1, server['security_groups'])
self.assertIn(sg_name2, server['security_groups'])
self.assertIsInstance(server['security_groups'], list)
self.assertEqual(2, len(server['security_groups']))
sg_names = [each_sg['name'] for each_sg in server['security_groups']]
self.assertIn(sg_name1, sg_names)
self.assertIn(sg_name2, sg_names)
def test_server_create_with_empty_network_option_latest(self):
"""Test server create with empty network option in nova 2.latest."""

@ -32,10 +32,7 @@ class ServerGroupTests(base.TestCase):
name1,
cmd_output['name']
)
self.assertEqual(
'affinity',
cmd_output['policies']
)
self.assertEqual(['affinity'], cmd_output['policies'])
cmd_output = json.loads(self.openstack(
'server group create -f json ' +
@ -46,10 +43,7 @@ class ServerGroupTests(base.TestCase):
name2,
cmd_output['name']
)
self.assertEqual(
'anti-affinity',
cmd_output['policies']
)
self.assertEqual(['anti-affinity'], cmd_output['policies'])
del_output = self.openstack(
'server group delete ' + name1 + ' ' + name2)
@ -73,10 +67,7 @@ class ServerGroupTests(base.TestCase):
name1,
cmd_output['name']
)
self.assertEqual(
'affinity',
cmd_output['policies']
)
self.assertEqual(['affinity'], cmd_output['policies'])
cmd_output = json.loads(self.openstack(
'server group create -f json ' +
@ -90,10 +81,7 @@ class ServerGroupTests(base.TestCase):
name2,
cmd_output['name']
)
self.assertEqual(
'anti-affinity',
cmd_output['policies']
)
self.assertEqual(['anti-affinity'], cmd_output['policies'])
# test server group list
cmd_output = json.loads(self.openstack(
@ -102,5 +90,5 @@ class ServerGroupTests(base.TestCase):
self.assertIn(name1, names)
self.assertIn(name2, names)
policies = [x["Policies"] for x in cmd_output]
self.assertIn('affinity', policies)
self.assertIn('anti-affinity', policies)
self.assertIn(['affinity'], policies)
self.assertIn(['anti-affinity'], policies)

@ -16,6 +16,7 @@
import mock
from mock import call
from osc_lib.cli import format_columns
from osc_lib import exceptions
from osc_lib import utils
@ -443,7 +444,7 @@ class TestAggregateShow(TestAggregate):
TestAggregate.fake_ag.hosts,
TestAggregate.fake_ag.id,
TestAggregate.fake_ag.name,
utils.format_dict(
format_columns.DictColumn(
{key: value
for key, value in TestAggregate.fake_ag.metadata.items()
if key != 'availability_zone'}),
@ -467,7 +468,7 @@ class TestAggregateShow(TestAggregate):
self.aggregate_mock.get.assert_called_once_with(parsed_args.aggregate)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
self.assertItemEqual(self.data, data)
class TestAggregateUnset(TestAggregate):

@ -16,8 +16,8 @@
import mock
from mock import call
from osc_lib.cli import format_columns
from osc_lib import exceptions
from osc_lib import utils
from openstackclient.compute.v2 import flavor
from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes
@ -67,7 +67,7 @@ class TestFlavorCreate(TestFlavor):
flavor.id,
flavor.name,
flavor.is_public,
utils.format_dict(flavor.properties),
format_columns.DictColumn(flavor.properties),
flavor.ram,
flavor.rxtx_factor,
flavor.swap,
@ -107,7 +107,7 @@ class TestFlavorCreate(TestFlavor):
self.flavors_mock.create.assert_called_once_with(*default_args)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
self.assertItemEqual(self.data, data)
def test_flavor_create_all_options(self):
@ -154,7 +154,7 @@ class TestFlavorCreate(TestFlavor):
self.flavor.get_keys.assert_called_once_with()
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
self.assertItemEqual(self.data, data)
def test_flavor_create_other_options(self):
@ -208,7 +208,7 @@ class TestFlavorCreate(TestFlavor):
{'key1': 'value1', 'key2': 'value2'})
self.flavor.get_keys.assert_called_with()
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
self.assertItemEqual(self.data, data)
def test_public_flavor_create_with_project(self):
arglist = [
@ -338,7 +338,7 @@ class TestFlavorList(TestFlavor):
data_long = (data[0] + (
flavors[0].swap,
flavors[0].rxtx_factor,
u'property=\'value\''
format_columns.DictColumn(flavors[0].properties),
), )
def setUp(self):
@ -376,7 +376,7 @@ class TestFlavorList(TestFlavor):
)
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
self.assertListItemEqual(self.data, list(data))
def test_flavor_list_all_flavors(self):
arglist = [
@ -405,7 +405,7 @@ class TestFlavorList(TestFlavor):
)
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
self.assertListItemEqual(self.data, list(data))
def test_flavor_list_private_flavors(self):
arglist = [
@ -434,7 +434,7 @@ class TestFlavorList(TestFlavor):
)
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
self.assertListItemEqual(self.data, list(data))
def test_flavor_list_public_flavors(self):
arglist = [
@ -463,7 +463,7 @@ class TestFlavorList(TestFlavor):
)
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
self.assertListItemEqual(self.data, list(data))
def test_flavor_list_long(self):
arglist = [
@ -492,7 +492,7 @@ class TestFlavorList(TestFlavor):
)
self.assertEqual(self.columns_long, columns)
self.assertEqual(tuple(self.data_long), tuple(data))
self.assertListItemEqual(self.data_long, list(data))
class TestFlavorSet(TestFlavor):
@ -652,7 +652,7 @@ class TestFlavorShow(TestFlavor):
flavor.id,
flavor.name,
flavor.is_public,
utils.format_dict(flavor.get_keys()),
format_columns.DictColumn(flavor.get_keys()),
flavor.ram,
flavor.rxtx_factor,
flavor.swap,
@ -689,7 +689,7 @@ class TestFlavorShow(TestFlavor):
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
self.assertItemEqual(self.data, data)
def test_private_flavor_show(self):
private_flavor = compute_fakes.FakeFlavor.create_one_flavor(
@ -709,12 +709,12 @@ class TestFlavorShow(TestFlavor):
data_with_project = (
private_flavor.disabled,
private_flavor.ephemeral,
self.flavor_access.tenant_id,
format_columns.ListColumn([self.flavor_access.tenant_id]),
private_flavor.disk,
private_flavor.id,
private_flavor.name,
private_flavor.is_public,
utils.format_dict(private_flavor.get_keys()),
format_columns.DictColumn(private_flavor.get_keys()),
private_flavor.ram,
private_flavor.rxtx_factor,
private_flavor.swap,
@ -728,7 +728,7 @@ class TestFlavorShow(TestFlavor):
self.flavor_access_mock.list.assert_called_with(
flavor=private_flavor.id)
self.assertEqual(self.columns, columns)
self.assertEqual(data_with_project, data)
self.assertItemEqual(data_with_project, data)
class TestFlavorUnset(TestFlavor):

@ -18,6 +18,7 @@ import getpass
import mock
from mock import call
from osc_lib.cli import format_columns
from osc_lib import exceptions
from osc_lib import utils as common_utils
from oslo_utils import timeutils
@ -298,14 +299,15 @@ class TestServerCreate(TestServer):
def datalist(self):
datalist = (
server._format_servers_list_power_state(
getattr(self.new_server, 'OS-EXT-STS:power_state')),
'',
getattr(self.new_server, 'OS-EXT-STS:power_state')
),
format_columns.DictListColumn(self.new_server.networks),
self.flavor.name + ' (' + self.new_server.flavor.get('id') + ')',
self.new_server.id,
self.image.name + ' (' + self.new_server.image.get('id') + ')',
self.new_server.name,
self.new_server.networks,
'',
format_columns.DictColumn(''),
)
return datalist
@ -313,7 +315,10 @@ class TestServerCreate(TestServer):
super(TestServerCreate, self).setUp()
attrs = {
'networks': {},
'networks': {
'private': ['fdb4:4f0f:960b:0:f816:3eff:fed9:af5e',
'10.0.0.8']
},
}
self.new_server = compute_fakes.FakeServer.create_one_server(
attrs=attrs)
@ -394,7 +399,7 @@ class TestServerCreate(TestServer):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
self.assertFalse(self.images_mock.called)
self.assertFalse(self.flavors_mock.called)
@ -459,7 +464,7 @@ class TestServerCreate(TestServer):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
def test_server_create_with_not_exist_security_group(self):
arglist = [
@ -545,7 +550,7 @@ class TestServerCreate(TestServer):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
def test_server_create_with_network(self):
arglist = [
@ -650,7 +655,7 @@ class TestServerCreate(TestServer):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
def test_server_create_with_auto_network(self):
arglist = [
@ -695,7 +700,7 @@ class TestServerCreate(TestServer):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
def test_server_create_with_none_network(self):
arglist = [
@ -740,7 +745,7 @@ class TestServerCreate(TestServer):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
def test_server_create_with_conflict_network_options(self):
arglist = [
@ -903,7 +908,7 @@ class TestServerCreate(TestServer):
**kwargs
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
@mock.patch.object(common_utils, 'wait_for_status', return_value=False)
def test_server_create_with_wait_fails(self, mock_wait_for_status):
@ -1010,7 +1015,7 @@ class TestServerCreate(TestServer):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
def test_server_create_with_block_device_mapping(self):
arglist = [
@ -1062,7 +1067,7 @@ class TestServerCreate(TestServer):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
def test_server_create_with_block_device_mapping_min_input(self):
arglist = [
@ -1113,7 +1118,7 @@ class TestServerCreate(TestServer):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
def test_server_create_with_block_device_mapping_default_input(self):
arglist = [
@ -1164,7 +1169,7 @@ class TestServerCreate(TestServer):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
def test_server_create_with_block_device_mapping_full_input(self):
arglist = [
@ -1219,7 +1224,7 @@ class TestServerCreate(TestServer):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
def test_server_create_with_block_device_mapping_snapshot(self):
arglist = [
@ -1274,7 +1279,7 @@ class TestServerCreate(TestServer):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
def test_server_create_with_block_device_mapping_multiple(self):
arglist = [
@ -1337,7 +1342,7 @@ class TestServerCreate(TestServer):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.datalist(), data)
self.assertItemEqual(self.datalist(), data)
def test_server_create_with_block_device_mapping_invalid_format(self):
# 1. block device mapping don't contain equal sign "="
@ -1597,7 +1602,7 @@ class TestServerList(TestServer):
s.id,
s.name,
s.status,
server._format_servers_list_networks(s.networks),
format_columns.DictListColumn(s.networks),
self.image.name,
self.flavor.name,
))
@ -1609,20 +1614,20 @@ class TestServerList(TestServer):
server._format_servers_list_power_state(
getattr(s, 'OS-EXT-STS:power_state')
),
server._format_servers_list_networks(s.networks),
format_columns.DictListColumn(s.networks),
self.image.name,
s.image['id'],
self.flavor.name,
s.flavor['id'],
getattr(s, 'OS-EXT-AZ:availability_zone'),
getattr(s, 'OS-EXT-SRV-ATTR:host'),
s.Metadata,
format_columns.DictColumn(s.Metadata),
))
self.data_no_name_lookup.append((
s.id,
s.name,
s.status,
server._format_servers_list_networks(s.networks),
format_columns.DictListColumn(s.networks),
s.image['id'],
s.flavor['id']
))
@ -1640,7 +1645,7 @@ class TestServerList(TestServer):
self.servers_mock.list.assert_called_with(**self.kwargs)
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
self.assertListItemEqual(self.data, list(data))
def test_server_list_long_option(self):
arglist = [
@ -1656,7 +1661,7 @@ class TestServerList(TestServer):
self.servers_mock.list.assert_called_with(**self.kwargs)
self.assertEqual(self.columns_long, columns)
self.assertEqual(tuple(self.data_long), tuple(data))
self.assertListItemEqual(self.data_long, list(data))
def test_server_list_no_name_lookup_option(self):
arglist = [
@ -1672,7 +1677,7 @@ class TestServerList(TestServer):
self.servers_mock.list.assert_called_with(**self.kwargs)
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data_no_name_lookup), tuple(data))
self.assertListItemEqual(self.data_no_name_lookup, list(data))
def test_server_list_n_option(self):
arglist = [
@ -1688,7 +1693,7 @@ class TestServerList(TestServer):
self.servers_mock.list.assert_called_with(**self.kwargs)
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data_no_name_lookup), tuple(data))
self.assertListItemEqual(self.data_no_name_lookup, list(data))
def test_server_list_with_image(self):
@ -1708,7 +1713,7 @@ class TestServerList(TestServer):
self.servers_mock.list.assert_called_with(**self.kwargs)
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
self.assertListItemEqual(self.data, list(data))
def test_server_list_with_flavor(self):
@ -1728,7 +1733,7 @@ class TestServerList(TestServer):
self.servers_mock.list.assert_called_with(**self.kwargs)
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
self.assertListItemEqual(self.data, list(data))
def test_server_list_with_changes_since(self):
@ -1749,7 +1754,7 @@ class TestServerList(TestServer):
self.servers_mock.list.assert_called_with(**self.kwargs)
self.assertEqual(self.columns, columns)
self.assertEqual(tuple(self.data), tuple(data))
self.assertListItemEqual(self.data, list(data))
@mock.patch.object(timeutils, 'parse_isotime', side_effect=ValueError)
def test_server_list_with_invalid_changes_since(self, mock_parse_isotime):
@ -2700,14 +2705,16 @@ class TestServerShow(TestServer):
self.data = (
'Running',
'public=10.20.30.40, 2001:db8::f',
format_columns.DictListColumn(
{'public': ['10.20.30.40', '2001:db8::f']}
),
self.flavor.name + " (" + self.flavor.id + ")",
self.server.id,
self.image.name + " (" + self.image.id + ")",
self.server.name,
{'public': ['10.20.30.40', '2001:db8::f']},
'tenant-id-xxx',
'',
format_columns.DictColumn(''),
)
def test_show_no_options(self):
@ -2730,7 +2737,7 @@ class TestServerShow(TestServer):
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
self.assertItemEqual(self.data, data)
def test_show_diagnostics(self):
arglist = [
@ -2989,13 +2996,13 @@ class TestServerGeneral(TestServer):
# Prepare expected data.
# Since networks is a dict, whose items are in random order, there
# could be two results after formatted.
data_1 = (u'private=2001:db8::f, 10.20.30.40; '
data_1 = (u'private=10.20.30.40, 2001:db8::f; '
u'public=10.20.30.40, 2001:db8::f')
data_2 = (u'public=10.20.30.40, 2001:db8::f; '
u'private=2001:db8::f, 10.20.30.40')
u'private=10.20.30.40, 2001:db8::f')
# Call _format_servers_list_networks().
networks_format = server._format_servers_list_networks(networks)
format_col = format_columns.DictListColumn(networks)
networks_format = format_col.human_readable()
msg = ('Network string is not formatted correctly.\n'
'reference = %s or %s\n'
@ -3044,5 +3051,17 @@ class TestServerGeneral(TestServer):
# 'networks' is used to create _server. Remove it.
server_detail.pop('networks')
# Special handle for 'properties', it's DictColumn type
prop = server_detail.pop('properties')
expected_prop = info.pop('properties')
self.assertIsInstance(prop, format_columns.DictColumn)
self.assertEqual(expected_prop, prop.human_readable())
# Special handle for 'addresses', it's DictListColumn type
prop = server_detail.pop('addresses')
expected_prop = info.pop('addresses')
self.assertIsInstance(prop, format_columns.DictListColumn)
self.assertEqual(expected_prop, prop.human_readable())
# Check the results.
self.assertEqual(info, server_detail)

@ -15,6 +15,7 @@
import mock
from osc_lib.cli import format_columns
from osc_lib import exceptions
from osc_lib import utils
@ -38,9 +39,9 @@ class TestServerGroup(compute_fakes.TestComputev2):
data = (
fake_server_group.id,
utils.format_list(fake_server_group.members),
format_columns.ListColumn(fake_server_group.members),
fake_server_group.name,
utils.format_list(fake_server_group.policies),
format_columns.ListColumn(fake_server_group.policies),
fake_server_group.project_id,
fake_server_group.user_id,
)
@ -78,7 +79,7 @@ class TestServerGroupCreate(TestServerGroup):
)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
self.assertItemEqual(self.data, data)
class TestServerGroupDelete(TestServerGroup):
@ -182,14 +183,14 @@ class TestServerGroupList(TestServerGroup):
list_data = ((
TestServerGroup.fake_server_group.id,
TestServerGroup.fake_server_group.name,
utils.format_list(TestServerGroup.fake_server_group.policies),
format_columns.ListColumn(TestServerGroup.fake_server_group.policies),
),)
list_data_long = ((
TestServerGroup.fake_server_group.id,
TestServerGroup.fake_server_group.name,
utils.format_list(TestServerGroup.fake_server_group.policies),
utils.format_list(TestServerGroup.fake_server_group.members),
format_columns.ListColumn(TestServerGroup.fake_server_group.policies),
format_columns.ListColumn(TestServerGroup.fake_server_group.members),
TestServerGroup.fake_server_group.project_id,
TestServerGroup.fake_server_group.user_id,
),)
@ -211,7 +212,7 @@ class TestServerGroupList(TestServerGroup):
self.server_groups_mock.list.assert_called_once_with(False)
self.assertEqual(self.list_columns, columns)
self.assertEqual(self.list_data, tuple(data))
self.assertListItemEqual(self.list_data, list(data))
def test_server_group_list_with_all_projects_and_long(self):
arglist = [
@ -227,7 +228,7 @@ class TestServerGroupList(TestServerGroup):
self.server_groups_mock.list.assert_called_once_with(True)
self.assertEqual(self.list_columns_long, columns)
self.assertEqual(self.list_data_long, tuple(data))
self.assertListItemEqual(self.list_data_long, list(data))
class TestServerGroupShow(TestServerGroup):
@ -250,4 +251,4 @@ class TestServerGroupShow(TestServerGroup):
columns, data = self.cmd.take_action(parsed_args)
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
self.assertItemEqual(self.data, data)

@ -0,0 +1,149 @@
---
features:
- |
Make console output machine readable. Openstackclient support to format the
console output into several formats, like: json, shell, csv, yaml, table.
Most of these should be understood by program and code, so we should keep
column content as actually complex data structures rather than string, if
users specify the output format as machine readable type, for example:
``json``, ``yaml``, ``cvs`` and so on, that make sense, like:
{'key': 'value'} vs "key='value'"
[Blueprint :oscbp:`osc-formattable-columns`]
The following output format are changed when machine readable format is
specified:
- Compute v2:
- ``properties`` in **aggregate show**
- ``properties`` in **flavor create**
- ``Properties`` in **flavor list**
- ``properties``, ``access_project_ids`` in **flavor show**
- ``volumes_attached``, ``security_groups``, ``addresses``,
``properties`` in **server create**
- ``volumes_attached``, ``security_groups``, ``addresses``,
``properties`` in **server rebuild**
- ``volumes_attached``, ``security_groups``, ``addresses``,
``properties`` in **server show**
- ``Networks``, ``Metadata`` in **server list**
- ``properties`` in **server backup create**
- ``policies``, ``members`` in **server group create**
- ``policies``, ``members`` in **server group show**
- ``Policies``, ``Members`` in **server group list**
- ``properties`` in **server image create**
- Object store v1:
- ``properties`` in **account show**
- ``properties`` in **container show**
- ``properties`` in **object show**
- Volume v1:
- ``Volume ID`` in **volume backup list**
- ``properties`` in **volume qos create**
- ``Specs``, ``Associations`` in **volume qos list**
- ``associations``, ``properties`` in **volume qos show**
- ``properties`` in **snapshot create**
- ``Metadata``, ``Volume ID`` in **snapshot list**
- ``properties`` in **snapshot show**
- ``properties`` in **volume create**
- ``Metadata``, ``Attachments`` in **volume list**
- ``properties`` in **volume show**
- ``properties`` in **volume snapshot create**
- ``Metadata``, ``Volume ID`` in **volume snapshot list**
- ``properties`` in **volume snapshot show**
- ``properties``, ``encryption`` in **volume type create**
- ``Extra Specs``, ``id`` in **volume type list**
- ``properties``, ``encryption`` in **volume type show**
- Volume v2:
- ``Volume ID`` in **volume backup list**
- ``Volume Types`` in **consistency group list**
- ``properties`` in **volume qos create**
- ``Specs``, ``Associations`` in **volume qos list**
- ``associations``, ``properties`` in **volume qos show**
- ``properties`` in **snapshot create**
- ``Metadata``, ``Volume ID`` in **snapshot list**
- ``properties`` in **snapshot show**
- ``properties`` in **volume create**
- ``Metadata``, ``Attachments`` in **volume list**
- ``properties`` in **volume show**
- ``properties`` in **volume snapshot create**
- ``Metadata``, ``Volume ID`` in **volume snapshot list**
- ``properties`` in **volume snapshot show**
- ``properties``, ``encryption`` in **volume type create**
- ``Extra Specs``, ``id`` in **volume type list**
- ``properties``, ``access_project_ids``, ``encryption``
in **volume type show**
- Identity v2.0:
- ``Endpoints`` in **catalog list**
- ``endpoints`` in **catalog show**
- ``properties`` in **project show**
- ``tenantId`` in **user list**
- Identity v3:
- ``Endpoints`` in **catalog list**
- ``endpoints`` in **catalog show**
- ``remote_ids`` in **identity provider create**
- ``remote_ids`` in **identity provider show**
- Image v1:
- ``properties`` in **image create**
- ``Visibility``, ``Properties`` in **image list**
- ``properties`` in **image show**
- Image v2:
- ``tags``, ``properties`` in **image create**
- ``Tags`` in **image list**
- ``tags``, ``properties`` in **image show**
- Network v2:
- ``subnet_ip_availability`` in **ip availability show**
- ``subnets``, ``subnet_ids``, ``admin_state_up``,
``is_admin_state_up``, ``router:external``, ``is_router_external``,
``availability_zones``, ``availability_zone_hints``, ``tags`` in
**network create**
- ``Subnets``, ``State``, ``Router Type``, ``Availability Zones``,
``Tags`` in **network list**
- ``subnets``, ``subnet_ids``, ``admin_state_up``,
``is_admin_state_up``, ``router:external``, ``is_router_external``,
``availability_zones``, ``availability_zone_hints``, ``tags`` in
**network show**
- ``Alive``, ``State`` in **network agent list**
- ``alive``, ``admin_state_up``, ``configurations`` in
**network agent show**
- ``admin_state_up``, ``allowed_address_pairs``, ``binding_profile``,
``binding_vif_details``, ``dns_assignment``, ``extra_dhcp_opts``,
``fixed_ips``, ``security_group_ids``, ``tags`` in **port create**
- ``Fixed IP Addresses``, ``Status``, ``Security Groups``, ``Tags``
in **port list**
- ``admin_state_up``, ``allowed_address_pairs``, ``binding_profile``,
``binding_vif_details``, ``dns_assignment``, ``extra_dhcp_opts``,
``fixed_ips``, ``security_group_ids``, ``tags`` in **port show**
- ``admin_state_up``, ``external_gateway_info``,
``availability_zones``, ``availability_zone_hints``, ``routes``,
``tags`` in **router create**
- ``State``, ``Routes``, ``External gateway info``, ``Tags``,
``Availability zones`` in **router list**
- ``admin_state_up``, ``external_gateway_info``,
``availability_zones``, ``availability_zone_hints``, ``routes``,
``tags`` in **router show**
- ``security_group_rules`` in **security group create**
- ``security_group_rules`` in **security group show**
- ``allocation_pools``, ``dns_nameservers``, ``host_routes``, ``tags``,
``service_types`` in **subnet create**
- ``Name Servers``, ``Allocation Pools``, ``Host Routes``, ``Tags``,
``Service Types`` in **subnet list**
- ``allocation_pools``, ``dns_nameservers``, ``host_routes``, ``tags``,
``service_types`` in **subnet show**
- ``prefixes``, ``tags`` in **subnet pool create**
- ``Prefixes``, ``Tags`` in **subnet pool list**
- ``prefixes``, ``tags`` in **subnet pool show**