From 4cd614305fbe8b90d45ad6803bfa8b7978f19318 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Mon, 15 May 2017 10:19:07 +0000 Subject: [PATCH] Use cliff formattable columns in volume v2 commands Partial-Bug: #1687955 Partially implement blueprint osc-formattable-columns Change-Id: I761ccac126208927594ad0d98a3cf5ad8b44bd48 --- .../tests/functional/volume/v2/test_qos.py | 6 +- .../tests/functional/volume/v2/test_volume.py | 6 +- .../volume/v2/test_volume_snapshot.py | 8 +- .../functional/volume/v2/test_volume_type.py | 74 +++++----- openstackclient/tests/unit/volume/v2/fakes.py | 5 +- .../unit/volume/v2/test_consistency_group.py | 17 +-- .../tests/unit/volume/v2/test_qos_specs.py | 28 ++-- .../tests/unit/volume/v2/test_type.py | 75 ++++++++--- .../tests/unit/volume/v2/test_volume.py | 126 +++++++++--------- .../unit/volume/v2/test_volume_backup.py | 6 +- .../volume/v2/consistency_group.py | 3 +- openstackclient/volume/v2/qos_specs.py | 13 +- openstackclient/volume/v2/volume.py | 62 ++++++--- openstackclient/volume/v2/volume_backup.py | 45 +++++-- openstackclient/volume/v2/volume_snapshot.py | 54 +++++--- openstackclient/volume/v2/volume_type.py | 61 +++++++-- 16 files changed, 360 insertions(+), 229 deletions(-) diff --git a/openstackclient/tests/functional/volume/v2/test_qos.py b/openstackclient/tests/functional/volume/v2/test_qos.py index 646becc1a7..f9f6e099fa 100644 --- a/openstackclient/tests/functional/volume/v2/test_qos.py +++ b/openstackclient/tests/functional/volume/v2/test_qos.py @@ -74,7 +74,7 @@ class QosTests(common.BaseVolumeTests): cmd_output['consumer'] ) self.assertEqual( - "Alpha='a'", + {'Alpha': 'a'}, cmd_output['properties'] ) @@ -97,7 +97,7 @@ class QosTests(common.BaseVolumeTests): cmd_output['name'] ) self.assertEqual( - "Alpha='c', Beta='b'", + {'Alpha': 'c', 'Beta': 'b'}, cmd_output['properties'] ) @@ -118,7 +118,7 @@ class QosTests(common.BaseVolumeTests): cmd_output['name'] ) self.assertEqual( - "Beta='b'", + {'Beta': 'b'}, cmd_output['properties'] ) diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py index 2930d48370..19fd58950e 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -128,7 +128,7 @@ class VolumeTests(common.BaseVolumeTests): cmd_output["description"], ) self.assertEqual( - "Alpha='a'", + {'Alpha': 'a'}, cmd_output["properties"], ) self.assertEqual( @@ -170,7 +170,7 @@ class VolumeTests(common.BaseVolumeTests): cmd_output["description"], ) self.assertEqual( - "Beta='b', Gamma='c'", + {'Beta': 'b', 'Gamma': 'c'}, cmd_output["properties"], ) self.assertEqual( @@ -196,7 +196,7 @@ class VolumeTests(common.BaseVolumeTests): new_name )) self.assertEqual( - "Gamma='c'", + {'Gamma': 'c'}, cmd_output["properties"], ) self.assertEqual( diff --git a/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py b/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py index 264f4adb6b..8d32d99731 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py @@ -182,7 +182,7 @@ class VolumeSnapshotTests(common.BaseVolumeTests): cmd_output["description"], ) self.assertEqual( - "Alpha='a'", + {'Alpha': 'a'}, cmd_output["properties"], ) self.wait_for_status('volume snapshot', name, 'available') @@ -216,7 +216,7 @@ class VolumeSnapshotTests(common.BaseVolumeTests): cmd_output["description"], ) self.assertEqual( - "Alpha='c', Beta='b'", + {'Alpha': 'c', 'Beta': 'b'}, cmd_output["properties"], ) @@ -233,7 +233,7 @@ class VolumeSnapshotTests(common.BaseVolumeTests): new_name )) self.assertEqual( - "Beta='b'", + {'Beta': 'b'}, cmd_output["properties"], ) @@ -249,6 +249,6 @@ class VolumeSnapshotTests(common.BaseVolumeTests): new_name )) self.assertNotIn( - "Beta='b'", + {'Beta': 'b'}, cmd_output["properties"], ) diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index d8dd5bd626..3f1a6ea8a5 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -65,8 +65,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - # TODO(amotoki): properties output should be machine-readable - self.assertEqual("a='b', c='d'", cmd_output['properties']) + self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) raw_output = self.openstack( 'volume type unset --property a %s' % name @@ -75,7 +74,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("c='d'", cmd_output['properties']) + self.assertEqual({'c': 'd'}, cmd_output['properties']) def test_volume_type_set_unset_multiple_properties(self): name = uuid.uuid4().hex @@ -96,7 +95,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("a='b', c='d'", cmd_output['properties']) + self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) raw_output = self.openstack( 'volume type unset --property a --property c %s' % name @@ -105,7 +104,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json %s' % name )) - self.assertEqual("", cmd_output['properties']) + self.assertEqual({}, cmd_output['properties']) def test_volume_type_set_unset_project(self): name = uuid.uuid4().hex @@ -155,35 +154,32 @@ class VolumeTypeTests(common.BaseVolumeTests): '--encryption-key-size 128 ' '--encryption-control-location front-end ' + encryption_type)) - # TODO(amotoki): encryption output should be machine-readable - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test show encryption type cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + encryption_type)) - # TODO(amotoki): encryption output should be machine-readable - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test list encryption type cmd_output = json.loads(self.openstack( 'volume type list -f json --encryption-type')) encryption_output = [t['Encryption'] for t in cmd_output if t['Name'] == encryption_type][0] - # TODO(amotoki): encryption output should be machine-readable - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, encryption_output) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, encryption_output[attr]) # test set existing encryption type raw_output = self.openstack( 'volume type set ' @@ -193,12 +189,12 @@ class VolumeTypeTests(common.BaseVolumeTests): self.assertEqual('', raw_output) cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + encryption_type)) - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='256'", - "control_location='back-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 256, + 'control_location': 'back-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test set new encryption type cmd_output = json.loads(self.openstack( 'volume type create -f json --private ' + @@ -222,12 +218,12 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + name )) - expected = ["provider='LuksEncryptor'", - "cipher='aes-xts-plain64'", - "key_size='128'", - "control_location='front-end'"] - for attr in expected: - self.assertIn(attr, cmd_output['encryption']) + expected = {'provider': 'LuksEncryptor', + 'cipher': 'aes-xts-plain64', + 'key_size': 128, + 'control_location': 'front-end'} + for attr, value in expected.items(): + self.assertEqual(value, cmd_output['encryption'][attr]) # test unset encryption type raw_output = self.openstack( 'volume type unset --encryption-type ' + name @@ -236,7 +232,7 @@ class VolumeTypeTests(common.BaseVolumeTests): cmd_output = json.loads(self.openstack( 'volume type show -f json --encryption-type ' + name )) - self.assertEqual('', cmd_output['encryption']) + self.assertEqual({}, cmd_output['encryption']) # test delete encryption type raw_output = self.openstack('volume type delete ' + encryption_type) self.assertEqual('', raw_output) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index c245cbf66b..5f976b0625 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -17,7 +17,8 @@ import random import uuid import mock -from osc_lib import utils as common_utils + +from osc_lib.cli import format_columns from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -468,7 +469,7 @@ class FakeVolume(object): if x == 'tags': # The 'tags' should be format_list data_list.append( - common_utils.format_list(volume.info.get(x))) + format_columns.ListColumn(volume.info.get(x))) else: data_list.append(volume.info.get(x)) return tuple(data_list) diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index 6eeeae393e..d238818245 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -15,6 +15,7 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -250,7 +251,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_consistency_group_create_from_source(self): arglist = [ @@ -278,7 +279,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_consistency_group_create_from_snapshot(self): arglist = [ @@ -306,7 +307,7 @@ class TestConsistencyGroupCreate(TestConsistencyGroup): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestConsistencyGroupDelete(TestConsistencyGroup): @@ -439,7 +440,7 @@ class TestConsistencyGroupList(TestConsistencyGroup): c.availability_zone, c.name, c.description, - utils.format_list(c.volume_types) + format_columns.ListColumn(c.volume_types) )) def setUp(self): @@ -462,7 +463,7 @@ class TestConsistencyGroupList(TestConsistencyGroup): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_consistency_group_list_with_all_project(self): arglist = [ @@ -479,7 +480,7 @@ class TestConsistencyGroupList(TestConsistencyGroup): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': True}) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_consistency_group_list_with_long(self): arglist = [ @@ -496,7 +497,7 @@ class TestConsistencyGroupList(TestConsistencyGroup): self.consistencygroups_mock.list.assert_called_once_with( detailed=True, search_opts={'all_tenants': False}) self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) class TestConsistencyGroupRemoveVolume(TestConsistencyGroup): @@ -704,4 +705,4 @@ class TestConsistencyGroupShow(TestConsistencyGroup): self.consistencygroups_mock.get.assert_called_once_with( self.consistency_group.id) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) diff --git a/openstackclient/tests/unit/volume/v2/test_qos_specs.py b/openstackclient/tests/unit/volume/v2/test_qos_specs.py index 2b935e205f..454747f5aa 100644 --- a/openstackclient/tests/unit/volume/v2/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v2/test_qos_specs.py @@ -17,6 +17,8 @@ import copy import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -88,7 +90,7 @@ class TestQosCreate(TestQos): self.new_qos_spec.consumer, self.new_qos_spec.id, self.new_qos_spec.name, - utils.format_dict(self.new_qos_spec.specs) + format_columns.DictColumn(self.new_qos_spec.specs) ) # Get the command object to test @@ -111,7 +113,7 @@ class TestQosCreate(TestQos): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_qos_create_with_consumer(self): arglist = [ @@ -132,7 +134,7 @@ class TestQosCreate(TestQos): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_qos_create_with_properties(self): arglist = [ @@ -158,7 +160,7 @@ class TestQosCreate(TestQos): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) class TestQosDelete(TestQos): @@ -318,8 +320,8 @@ class TestQosList(TestQos): q.id, q.name, q.consumer, - qos_association.name, - utils.format_dict(q.specs), + format_columns.ListColumn([qos_association.name]), + format_columns.DictColumn(q.specs), )) def setUp(self): @@ -341,7 +343,7 @@ class TestQosList(TestQos): self.qos_mock.list.assert_called_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_qos_list_no_association(self): self.qos_mock.reset_mock() @@ -365,10 +367,10 @@ class TestQosList(TestQos): self.qos_specs[1].id, self.qos_specs[1].name, self.qos_specs[1].consumer, - None, - utils.format_dict(self.qos_specs[1].specs), + format_columns.ListColumn(None), + format_columns.DictColumn(self.qos_specs[1].specs), ) - self.assertEqual(ex_data, list(data)) + self.assertListItemEqual(ex_data, list(data)) class TestQosSet(TestQos): @@ -416,11 +418,11 @@ class TestQosShow(TestQos): 'properties' ) data = ( - qos_association.name, + format_columns.ListColumn([qos_association.name]), qos_spec.consumer, qos_spec.id, qos_spec.name, - utils.format_dict(qos_spec.specs), + format_columns.DictColumn(qos_spec.specs), ) def setUp(self): @@ -448,7 +450,7 @@ class TestQosShow(TestQos): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, tuple(data)) + self.assertItemEqual(self.data, tuple(data)) class TestQosUnset(TestQos): diff --git a/openstackclient/tests/unit/volume/v2/test_type.py b/openstackclient/tests/unit/volume/v2/test_type.py index 4023d55b3d..17915928dc 100644 --- a/openstackclient/tests/unit/volume/v2/test_type.py +++ b/openstackclient/tests/unit/volume/v2/test_type.py @@ -15,6 +15,7 @@ import mock from mock import call +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -92,7 +93,7 @@ class TestTypeCreate(TestType): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_type_create_private(self): arglist = [ @@ -118,7 +119,7 @@ class TestTypeCreate(TestType): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_public_type_create_with_project(self): arglist = [ @@ -158,7 +159,7 @@ class TestTypeCreate(TestType): ) encryption_data = ( self.new_volume_type.description, - utils.format_dict(encryption_info), + format_columns.DictColumn(encryption_info), self.new_volume_type.id, True, self.new_volume_type.name, @@ -195,7 +196,7 @@ class TestTypeCreate(TestType): body, ) self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, data) + self.assertItemEqual(encryption_data, data) class TestTypeDelete(TestType): @@ -305,7 +306,7 @@ class TestTypeList(TestType): t.name, t.is_public, t.description, - utils.format_dict(t.extra_specs), + format_columns.DictColumn(t.extra_specs), )) def setUp(self): @@ -329,7 +330,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=None) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_type_list_with_options(self): arglist = [ @@ -347,7 +348,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=True) self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) def test_type_list_with_private_option(self): arglist = [ @@ -364,7 +365,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.list.assert_called_once_with(is_public=False) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_type_list_with_default_option(self): arglist = [ @@ -382,7 +383,7 @@ class TestTypeList(TestType): columns, data = self.cmd.take_action(parsed_args) self.types_mock.default.assert_called_once_with() self.assertEqual(self.columns, columns) - self.assertEqual(self.data_with_default_type, list(data)) + self.assertListItemEqual(self.data_with_default_type, list(data)) def test_type_list_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type( @@ -401,13 +402,16 @@ class TestTypeList(TestType): self.volume_types[0].id, self.volume_types[0].name, self.volume_types[0].is_public, - utils.format_dict(encryption_info), + volume_type.EncryptionInfoColumn( + self.volume_types[0].id, + {self.volume_types[0].id: encryption_info}), )) encryption_data.append(( self.volume_types[1].id, self.volume_types[1].name, self.volume_types[1].is_public, - '-', + volume_type.EncryptionInfoColumn( + self.volume_types[1].id, {}), )) self.encryption_types_mock.list.return_value = [encryption_type] @@ -423,7 +427,7 @@ class TestTypeList(TestType): self.encryption_types_mock.list.assert_called_once_with() self.types_mock.list.assert_called_once_with(is_public=None) self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, list(data)) + self.assertListItemEqual(encryption_data, list(data)) class TestTypeSet(TestType): @@ -687,7 +691,7 @@ class TestTypeShow(TestType): self.volume_type.id, True, self.volume_type.name, - utils.format_dict(self.volume_type.extra_specs) + format_columns.DictColumn(self.volume_type.extra_specs) ) self.types_mock.get.return_value = self.volume_type @@ -709,7 +713,7 @@ class TestTypeShow(TestType): self.types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, data) + self.assertItemEqual(self.data, data) def test_type_show_with_access(self): arglist = [ @@ -735,14 +739,14 @@ class TestTypeShow(TestType): self.assertEqual(self.columns, columns) private_type_data = ( - utils.format_list([type_access_list.project_id]), + format_columns.ListColumn([type_access_list.project_id]), private_type.description, private_type.id, private_type.is_public, private_type.name, - utils.format_dict(private_type.extra_specs) + format_columns.DictColumn(private_type.extra_specs) ) - self.assertEqual(private_type_data, data) + self.assertItemEqual(private_type_data, data) def test_type_show_with_list_access_exec(self): arglist = [ @@ -772,9 +776,9 @@ class TestTypeShow(TestType): private_type.id, private_type.is_public, private_type.name, - utils.format_dict(private_type.extra_specs) + format_columns.DictColumn(private_type.extra_specs) ) - self.assertEqual(private_type_data, data) + self.assertItemEqual(private_type_data, data) def test_type_show_with_encryption(self): encryption_type = volume_fakes.FakeType.create_one_encryption_type() @@ -800,11 +804,11 @@ class TestTypeShow(TestType): encryption_data = ( None, self.volume_type.description, - utils.format_dict(encryption_info), + format_columns.DictColumn(encryption_info), self.volume_type.id, True, self.volume_type.name, - utils.format_dict(self.volume_type.extra_specs) + format_columns.DictColumn(self.volume_type.extra_specs) ) arglist = [ '--encryption-type', @@ -820,7 +824,7 @@ class TestTypeShow(TestType): self.types_mock.get.assert_called_with(self.volume_type.id) self.encryption_types_mock.get.assert_called_with(self.volume_type.id) self.assertEqual(encryption_columns, columns) - self.assertEqual(encryption_data, data) + self.assertItemEqual(encryption_data, data) class TestTypeUnset(TestType): @@ -923,3 +927,30 @@ class TestTypeUnset(TestType): result = self.cmd.take_action(parsed_args) self.encryption_types_mock.delete.assert_called_with(self.volume_type) self.assertIsNone(result) + + +class TestColumns(TestType): + + def test_encryption_info_column_with_info(self): + fake_volume_type = volume_fakes.FakeType.create_one_type() + type_id = fake_volume_type.id + + encryption_info = { + 'provider': 'LuksEncryptor', + 'cipher': None, + 'key_size': None, + 'control_location': 'front-end', + } + col = volume_type.EncryptionInfoColumn(type_id, + {type_id: encryption_info}) + self.assertEqual(utils.format_dict(encryption_info), + col.human_readable()) + self.assertEqual(encryption_info, col.machine_readable()) + + def test_encryption_info_column_without_info(self): + fake_volume_type = volume_fakes.FakeType.create_one_type() + type_id = fake_volume_type.id + + col = volume_type.EncryptionInfoColumn(type_id, {}) + self.assertEqual('-', col.human_readable()) + self.assertIsNone(col.machine_readable()) diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 97da160148..332b50a78f 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -16,6 +16,8 @@ import argparse import mock from mock import call + +from osc_lib.cli import format_columns from osc_lib import exceptions from osc_lib import utils @@ -94,7 +96,7 @@ class TestVolumeCreate(TestVolume): self.new_volume.description, self.new_volume.id, self.new_volume.name, - utils.format_dict(self.new_volume.metadata), + format_columns.DictColumn(self.new_volume.metadata), self.new_volume.size, self.new_volume.snapshot_id, self.new_volume.status, @@ -135,7 +137,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_options(self): consistency_group = ( @@ -181,7 +183,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_properties(self): arglist = [ @@ -217,7 +219,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_image_id(self): image = image_fakes.FakeImage.create_one_image() @@ -255,7 +257,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_image_name(self): image = image_fakes.FakeImage.create_one_image() @@ -293,7 +295,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_with_snapshot(self): snapshot = volume_fakes.FakeSnapshot.create_one_snapshot() @@ -330,7 +332,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) def test_volume_create_with_bootable_and_readonly(self): arglist = [ @@ -368,7 +370,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -410,7 +412,7 @@ class TestVolumeCreate(TestVolume): ) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, False) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -462,7 +464,7 @@ class TestVolumeCreate(TestVolume): self.assertEqual(2, mock_error.call_count) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertItemEqual(self.datalist, data) self.volumes_mock.set_bootable.assert_called_with( self.new_volume.id, True) self.volumes_mock.update_readonly_flag.assert_called_with( @@ -672,17 +674,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_project(self): arglist = [ @@ -715,17 +714,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_project_domain(self): arglist = [ @@ -760,17 +756,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_user(self): arglist = [ @@ -800,19 +793,16 @@ class TestVolumeList(TestVolume): marker=None, limit=None, ) - self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) + datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_user_domain(self): arglist = [ @@ -847,17 +837,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_name(self): arglist = [ @@ -890,17 +877,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_status(self): arglist = [ @@ -933,17 +917,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_all_projects(self): arglist = [ @@ -976,17 +957,14 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_long(self): arglist = [ @@ -1030,9 +1008,6 @@ class TestVolumeList(TestVolume): ] self.assertEqual(collist, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, @@ -1040,10 +1015,10 @@ class TestVolumeList(TestVolume): self.mock_volume.size, self.mock_volume.volume_type, self.mock_volume.bootable, - msg, - utils.format_dict(self.mock_volume.metadata), + volume.AttachmentsColumn(self.mock_volume.attachments), + format_columns.DictColumn(self.mock_volume.metadata), ), ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_with_marker_and_limit(self): arglist = [ @@ -1064,15 +1039,12 @@ class TestVolumeList(TestVolume): self.assertEqual(self.columns, columns) - server = self.mock_volume.attachments[0]['server_id'] - device = self.mock_volume.attachments[0]['device'] - msg = 'Attached to %s on %s ' % (server, device) datalist = (( self.mock_volume.id, self.mock_volume.name, self.mock_volume.status, self.mock_volume.size, - msg, + volume.AttachmentsColumn(self.mock_volume.attachments), ), ) self.volumes_mock.list.assert_called_once_with( @@ -1085,7 +1057,7 @@ class TestVolumeList(TestVolume): 'name': None, 'all_tenants': False, } ) - self.assertEqual(datalist, tuple(data)) + self.assertListItemEqual(datalist, tuple(data)) def test_volume_list_negative_limit(self): arglist = [ @@ -1479,7 +1451,7 @@ class TestVolumeShow(TestVolume): volume_fakes.FakeVolume.get_volume_columns(self._volume), columns) - self.assertEqual( + self.assertItemEqual( volume_fakes.FakeVolume.get_volume_data(self._volume), data) @@ -1562,3 +1534,31 @@ class TestVolumeUnset(TestVolume): self.new_volume.id, parsed_args.image_property) self.volumes_mock.delete_metadata.assert_called_with( self.new_volume.id, parsed_args.property) + + +class TestColumns(TestVolume): + + def test_attachments_column_without_server_cache(self): + _volume = volume_fakes.FakeVolume.create_one_volume() + server_id = _volume.attachments[0]['server_id'] + device = _volume.attachments[0]['device'] + + col = volume.AttachmentsColumn(_volume.attachments, {}) + self.assertEqual('Attached to %s on %s ' % (server_id, device), + col.human_readable()) + self.assertEqual(_volume.attachments, col.machine_readable()) + + def test_attachments_column_with_server_cache(self): + _volume = volume_fakes.FakeVolume.create_one_volume() + + server_id = _volume.attachments[0]['server_id'] + device = _volume.attachments[0]['device'] + fake_server = mock.Mock() + fake_server.name = 'fake-server-name' + server_cache = {server_id: fake_server} + + col = volume.AttachmentsColumn(_volume.attachments, server_cache) + self.assertEqual( + 'Attached to %s on %s ' % ('fake-server-name', device), + col.human_readable()) + self.assertEqual(_volume.attachments, col.machine_readable()) diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py index d0d1da3b29..30c7915dd9 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -271,7 +271,7 @@ class TestBackupList(TestBackup): b.status, b.size, b.availability_zone, - b.volume_id, + volume_backup.VolumeIdColumn(b.volume_id), b.container, )) @@ -314,7 +314,7 @@ class TestBackupList(TestBackup): limit=None, ) self.assertEqual(self.columns, columns) - self.assertEqual(self.data, list(data)) + self.assertListItemEqual(self.data, list(data)) def test_backup_list_with_options(self): arglist = [ @@ -353,7 +353,7 @@ class TestBackupList(TestBackup): limit=3, ) self.assertEqual(self.columns_long, columns) - self.assertEqual(self.data_long, list(data)) + self.assertListItemEqual(self.data_long, list(data)) class TestBackupRestore(TestBackup): diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 0a932f8454..26dd8ffca1 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -16,6 +16,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 @@ -238,7 +239,7 @@ class ListConsistencyGroup(command.Lister): return (columns, ( utils.get_item_properties( s, columns, - formatters={'Volume Types': utils.format_list}) + formatters={'Volume Types': format_columns.ListColumn}) for s in consistency_groups)) diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index c71605818a..3037d34ae8 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -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 @@ -96,7 +97,8 @@ class CreateQos(command.ShowOne): qos_spec = volume_client.qos_specs.create(parsed_args.name, specs) qos_spec._info.update( - {'properties': utils.format_dict(qos_spec._info.pop('specs'))} + {'properties': + format_columns.DictColumn(qos_spec._info.pop('specs'))} ) return zip(*sorted(six.iteritems(qos_spec._info))) @@ -210,8 +212,8 @@ class ListQos(command.Lister): (utils.get_dict_properties( s._info, columns, formatters={ - 'Specs': utils.format_dict, - 'Associations': utils.format_list + 'Specs': format_columns.DictColumn, + 'Associations': format_columns.ListColumn }, ) for s in qos_specs_list)) @@ -267,10 +269,11 @@ class ShowQos(command.ShowOne): associations = [association.name for association in qos_associations] qos_spec._info.update({ - 'associations': utils.format_list(associations) + 'associations': format_columns.ListColumn(associations) }) qos_spec._info.update( - {'properties': utils.format_dict(qos_spec._info.pop('specs'))}) + {'properties': + format_columns.DictColumn(qos_spec._info.pop('specs'))}) return zip(*sorted(six.iteritems(qos_spec._info))) diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index ef65d0970a..17ccd3d31e 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -16,8 +16,11 @@ import argparse import copy +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -31,6 +34,37 @@ from openstackclient.identity import common as identity_common LOG = logging.getLogger(__name__) +class AttachmentsColumn(cliff_columns.FormattableColumn): + """Formattable column for attachments column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes server_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(AttachmentsColumn, server_cache)``. + """ + + def __init__(self, value, server_cache=None): + super(AttachmentsColumn, self).__init__(value) + self._server_cache = server_cache or {} + + def human_readable(self): + """Return a formatted string of a volume's attached instances + + :rtype: a string of formatted instances + """ + + msg = '' + for attachment in self._value: + server = attachment['server_id'] + if server in self._server_cache.keys(): + server = self._server_cache[server].name + device = attachment['device'] + msg += 'Attached to %s on %s ' % (server, device) + return msg + + def _check_size_arg(args): """Check whether --size option is required or not. @@ -212,7 +246,8 @@ class CreateVolume(command.ShowOne): # Remove key links from being displayed volume._info.update( { - 'properties': utils.format_dict(volume._info.pop('metadata')), + 'properties': + format_columns.DictColumn(volume._info.pop('metadata')), 'type': volume._info.pop('volume_type') } ) @@ -331,22 +366,6 @@ class ListVolume(command.Lister): compute_client = self.app.client_manager.compute identity_client = self.app.client_manager.identity - def _format_attach(attachments): - """Return a formatted string of a volume's attached instances - - :param attachments: a volume.attachments field - :rtype: a string of formatted instances - """ - - msg = '' - for attachment in attachments: - server = attachment['server_id'] - if server in server_cache: - server = server_cache[server].name - device = attachment['device'] - msg += 'Attached to %s on %s ' % (server, device) - return msg - if parsed_args.long: columns = [ 'ID', @@ -381,6 +400,8 @@ class ListVolume(command.Lister): except Exception: # Just forget it if there's any trouble pass + AttachmentsColumnWithCache = functools.partial( + AttachmentsColumn, server_cache=server_cache) project_id = None if parsed_args.project: @@ -417,8 +438,8 @@ class ListVolume(command.Lister): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Metadata': utils.format_dict, - 'Attachments': _format_attach}, + formatters={'Metadata': format_columns.DictColumn, + 'Attachments': AttachmentsColumnWithCache}, ) for s in data)) @@ -722,7 +743,8 @@ class ShowVolume(command.ShowOne): # 'volume_type' --> 'type' volume._info.update( { - 'properties': utils.format_dict(volume._info.pop('metadata')), + 'properties': + format_columns.DictColumn(volume._info.pop('metadata')), 'type': volume._info.pop('volume_type'), }, ) diff --git a/openstackclient/volume/v2/volume_backup.py b/openstackclient/volume/v2/volume_backup.py index 1d2b0cde04..4d0d54c1b5 100644 --- a/openstackclient/volume/v2/volume_backup.py +++ b/openstackclient/volume/v2/volume_backup.py @@ -15,8 +15,10 @@ """Volume v2 Backup action implementations""" import copy +import functools import logging +from cliff import columns as cliff_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -29,6 +31,33 @@ from openstackclient.i18n import _ LOG = logging.getLogger(__name__) +class VolumeIdColumn(cliff_columns.FormattableColumn): + """Formattable column for volume ID column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes volume_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(VolumeIdColumn, volume_cache)``. + """ + + def __init__(self, value, volume_cache=None): + super(VolumeIdColumn, self).__init__(value) + self._volume_cache = volume_cache or {} + + def human_readable(self): + """Return a volume name if available + + :rtype: either the volume ID or name + """ + volume_id = self._value + volume = volume_id + if volume_id in self._volume_cache.keys(): + volume = self._volume_cache[volume_id].name + return volume + + class CreateVolumeBackup(command.ShowOne): _description = _("Create new volume backup") @@ -189,18 +218,6 @@ class ListVolumeBackup(command.Lister): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].name - return volume - if parsed_args.long: columns = ['ID', 'Name', 'Description', 'Status', 'Size', 'Availability Zone', 'Volume ID', 'Container'] @@ -218,6 +235,8 @@ class ListVolumeBackup(command.Lister): except Exception: # Just forget it if there's any trouble pass + _VolumeIdColumn = functools.partial(VolumeIdColumn, + volume_cache=volume_cache) filter_volume_id = None if parsed_args.volume: @@ -242,7 +261,7 @@ class ListVolumeBackup(command.Lister): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Volume ID': _format_volume_id}, + formatters={'Volume ID': _VolumeIdColumn}, ) for s in data)) diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index fe9694104a..2b26ae323b 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -15,8 +15,11 @@ """Volume v2 snapshot action implementations""" import copy +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -30,6 +33,33 @@ from openstackclient.identity import common as identity_common LOG = logging.getLogger(__name__) +class VolumeIdColumn(cliff_columns.FormattableColumn): + """Formattable column for volume ID column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes volume_cache as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(VolumeIdColumn, volume_cache)``. + """ + + def __init__(self, value, volume_cache=None): + super(VolumeIdColumn, self).__init__(value) + self._volume_cache = volume_cache or {} + + def human_readable(self): + """Return a volume name if available + + :rtype: either the volume ID or name + """ + volume_id = self._value + volume = volume_id + if volume_id in self._volume_cache.keys(): + volume = self._volume_cache[volume_id].name + return volume + + class CreateVolumeSnapshot(command.ShowOne): _description = _("Create new volume snapshot") @@ -107,7 +137,8 @@ class CreateVolumeSnapshot(command.ShowOne): metadata=parsed_args.property, ) snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + {'properties': + format_columns.DictColumn(snapshot._info.pop('metadata'))} ) return zip(*sorted(six.iteritems(snapshot._info))) @@ -216,18 +247,6 @@ class ListVolumeSnapshot(command.Lister): volume_client = self.app.client_manager.volume identity_client = self.app.client_manager.identity - def _format_volume_id(volume_id): - """Return a volume name if available - - :param volume_id: a volume ID - :rtype: either the volume ID or name - """ - - volume = volume_id - if volume_id in volume_cache.keys(): - volume = volume_cache[volume_id].name - return volume - if parsed_args.long: columns = ['ID', 'Name', 'Description', 'Status', 'Size', 'Created At', 'Volume ID', 'Metadata'] @@ -246,6 +265,8 @@ class ListVolumeSnapshot(command.Lister): except Exception: # Just forget it if there's any trouble pass + _VolumeIdColumn = functools.partial(VolumeIdColumn, + volume_cache=volume_cache) volume_id = None if parsed_args.volume: @@ -279,8 +300,8 @@ class ListVolumeSnapshot(command.Lister): return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Metadata': utils.format_dict, - 'Volume ID': _format_volume_id}, + formatters={'Metadata': format_columns.DictColumn, + 'Volume ID': _VolumeIdColumn}, ) for s in data)) @@ -402,7 +423,8 @@ class ShowVolumeSnapshot(command.ShowOne): snapshot = utils.find_resource( volume_client.volume_snapshots, parsed_args.snapshot) snapshot._info.update( - {'properties': utils.format_dict(snapshot._info.pop('metadata'))} + {'properties': + format_columns.DictColumn(snapshot._info.pop('metadata'))} ) return zip(*sorted(six.iteritems(snapshot._info))) diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 749d1dd6fd..54b1f49719 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -14,8 +14,11 @@ """Volume v2 Type action implementations""" +import functools import logging +from cliff import columns as cliff_columns +from osc_lib.cli import format_columns from osc_lib.cli import parseractions from osc_lib.command import command from osc_lib import exceptions @@ -29,6 +32,36 @@ from openstackclient.identity import common as identity_common LOG = logging.getLogger(__name__) +class EncryptionInfoColumn(cliff_columns.FormattableColumn): + """Formattable column for encryption info column. + + Unlike the parent FormattableColumn class, the initializer of the + class takes encryption_data as the second argument. + osc_lib.utils.get_item_properties instantiate cliff FormattableColumn + object with a single parameter "column value", so you need to pass + a partially initialized class like + ``functools.partial(EncryptionInfoColumn encryption_data)``. + """ + + def __init__(self, value, encryption_data=None): + super(EncryptionInfoColumn, self).__init__(value) + self._encryption_data = encryption_data or {} + + def _get_encryption_info(self): + type_id = self._value + return self._encryption_data.get(type_id) + + def human_readable(self): + encryption_info = self._get_encryption_info() + if encryption_info: + return utils.format_dict(encryption_info) + else: + return '-' + + def machine_readable(self): + return self._get_encryption_info() + + def _create_encryption_type(volume_client, volume_type, parsed_args): if not parsed_args.encryption_provider: msg = _("'--encryption-provider' should be specified while " @@ -183,7 +216,8 @@ class CreateVolumeType(command.ShowOne): LOG.error(msg % {'project': parsed_args.project, 'e': e}) if parsed_args.property: result = volume_type.set_keys(parsed_args.property) - volume_type._info.update({'properties': utils.format_dict(result)}) + volume_type._info.update( + {'properties': format_columns.DictColumn(result)}) if (parsed_args.encryption_provider or parsed_args.encryption_cipher or parsed_args.encryption_key_size or @@ -198,7 +232,7 @@ class CreateVolumeType(command.ShowOne): # add encryption info in result encryption._info.pop("volume_type_id", None) volume_type._info.update( - {'encryption': utils.format_dict(encryption._info)}) + {'encryption': format_columns.DictColumn(encryption._info)}) volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -296,12 +330,7 @@ class ListVolumeType(command.Lister): data = volume_client.volume_types.list( is_public=is_public) - def _format_encryption_info(type_id, encryption_data=None): - encryption_data = encryption - encryption_info = '-' - if type_id in encryption_data.keys(): - encryption_info = encryption_data[type_id] - return encryption_info + formatters = {'Extra Specs': format_columns.DictColumn} if parsed_args.encryption_type: encryption = {} @@ -318,18 +347,21 @@ class ListVolumeType(command.Lister): for key in del_key: d._info.pop(key, None) # save the encryption information with their volume type ID - encryption[volume_type_id] = utils.format_dict(d._info) + encryption[volume_type_id] = d._info # We need to get volume type ID, then show encryption # information according to the ID, so use "id" to keep # difference to the real "ID" column. columns += ['id'] column_headers += ['Encryption'] + _EncryptionInfoColumn = functools.partial( + EncryptionInfoColumn, encryption_data=encryption) + formatters['id'] = _EncryptionInfoColumn + return (column_headers, (utils.get_item_properties( s, columns, - formatters={'Extra Specs': utils.format_dict, - 'id': _format_encryption_info}, + formatters=formatters, ) for s in data)) @@ -490,7 +522,7 @@ class ShowVolumeType(command.ShowOne): volume_client = self.app.client_manager.volume volume_type = utils.find_resource( volume_client.volume_types, parsed_args.volume_type) - properties = utils.format_dict( + properties = format_columns.DictColumn( volume_type._info.pop('extra_specs', {})) volume_type._info.update({'properties': properties}) access_project_ids = None @@ -502,7 +534,7 @@ class ShowVolumeType(command.ShowOne): for item in volume_type_access] # TODO(Rui Chen): This format list case can be removed after # patch https://review.opendev.org/#/c/330223/ merged. - access_project_ids = utils.format_list(project_ids) + access_project_ids = format_columns.ListColumn(project_ids) except Exception as e: msg = _('Failed to get access project list for volume type ' '%(type)s: %(e)s') @@ -515,7 +547,8 @@ class ShowVolumeType(command.ShowOne): volume_type.id) encryption._info.pop("volume_type_id", None) volume_type._info.update( - {'encryption': utils.format_dict(encryption._info)}) + {'encryption': + format_columns.DictColumn(encryption._info)}) except Exception as e: LOG.error(_("Failed to display the encryption information " "of this volume type: %s"), e)