From 4cd614305fbe8b90d45ad6803bfa8b7978f19318 Mon Sep 17 00:00:00 2001
From: Akihiro Motoki <amotoki@gmail.com>
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)