summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/network/v2/floating_ip.py109
-rw-r--r--openstackclient/network/v2/network_rbac.py75
-rw-r--r--openstackclient/tests/compute/v2/fakes.py8
-rw-r--r--openstackclient/tests/compute/v2/test_host.py104
-rw-r--r--openstackclient/tests/network/v2/fakes.py51
-rw-r--r--openstackclient/tests/network/v2/test_network_rbac.py122
-rw-r--r--openstackclient/tests/volume/v2/fakes.py33
-rw-r--r--openstackclient/tests/volume/v2/test_type.py72
-rw-r--r--openstackclient/volume/v2/volume_type.py18
9 files changed, 585 insertions, 7 deletions
diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py
index 8fbf049e..454335f1 100644
--- a/openstackclient/network/v2/floating_ip.py
+++ b/openstackclient/network/v2/floating_ip.py
@@ -13,6 +13,8 @@
"""IP Floating action implementations"""
+import logging
+
from osc_lib import utils
from openstackclient.i18n import _
@@ -31,25 +33,26 @@ def _get_attrs(client_manager, parsed_args):
attrs = {}
network_client = client_manager.network
+ # Name of a network could be empty string.
if parsed_args.network is not None:
network = network_client.find_network(parsed_args.network,
ignore_missing=False)
attrs['floating_network_id'] = network.id
- if parsed_args.subnet is not None:
+ if parsed_args.subnet:
subnet = network_client.find_subnet(parsed_args.subnet,
ignore_missing=False)
attrs['subnet_id'] = subnet.id
- if parsed_args.port is not None:
+ if parsed_args.port:
port = network_client.find_port(parsed_args.port,
ignore_missing=False)
attrs['port_id'] = port.id
- if parsed_args.floating_ip_address is not None:
+ if parsed_args.floating_ip_address:
attrs['floating_ip_address'] = parsed_args.floating_ip_address
- if parsed_args.fixed_ip_address is not None:
+ if parsed_args.fixed_ip_address:
attrs['fixed_ip_address'] = parsed_args.fixed_ip_address
return attrs
@@ -110,6 +113,30 @@ class CreateFloatingIP(common.NetworkAndComputeShowOne):
return (columns, data)
+class CreateIPFloating(CreateFloatingIP):
+ """Create floating IP"""
+
+ # TODO(tangchen): Remove this class and ``ip floating create`` command
+ # two cycles after Mitaka.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action_network(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip create" instead.'))
+ return super(CreateIPFloating, self).take_action_network(
+ client, parsed_args)
+
+ def take_action_compute(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip create" instead.'))
+ return super(CreateIPFloating, self).take_action_compute(
+ client, parsed_args)
+
+
class DeleteFloatingIP(common.NetworkAndComputeDelete):
"""Delete floating IP(s)"""
@@ -135,6 +162,30 @@ class DeleteFloatingIP(common.NetworkAndComputeDelete):
client.floating_ips.delete(obj.id)
+class DeleteIPFloating(DeleteFloatingIP):
+ """Delete floating IP(s)"""
+
+ # TODO(tangchen): Remove this class and ``ip floating delete`` command
+ # two cycles after Mitaka.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action_network(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip delete" instead.'))
+ return super(DeleteIPFloating, self).take_action_network(
+ client, parsed_args)
+
+ def take_action_compute(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip delete" instead.'))
+ return super(DeleteIPFloating, self).take_action_compute(
+ client, parsed_args)
+
+
class ListFloatingIP(common.NetworkAndComputeLister):
"""List floating IP(s)"""
@@ -186,8 +237,32 @@ class ListFloatingIP(common.NetworkAndComputeLister):
) for s in data))
+class ListIPFloating(ListFloatingIP):
+ """List floating IP(s)"""
+
+ # TODO(tangchen): Remove this class and ``ip floating list`` command
+ # two cycles after Mitaka.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action_network(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip list" instead.'))
+ return super(ListIPFloating, self).take_action_network(
+ client, parsed_args)
+
+ def take_action_compute(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip list" instead.'))
+ return super(ListIPFloating, self).take_action_compute(
+ client, parsed_args)
+
+
class ShowFloatingIP(common.NetworkAndComputeShowOne):
- """Show floating IP details"""
+ """Display floating IP details"""
def update_parser_common(self, parser):
parser.add_argument(
@@ -211,3 +286,27 @@ class ShowFloatingIP(common.NetworkAndComputeShowOne):
columns = _get_columns(obj._info)
data = utils.get_dict_properties(obj._info, columns)
return (columns, data)
+
+
+class ShowIPFloating(ShowFloatingIP):
+ """Display floating IP details"""
+
+ # TODO(tangchen): Remove this class and ``ip floating show`` command
+ # two cycles after Mitaka.
+
+ # This notifies cliff to not display the help for this command
+ deprecated = True
+
+ log = logging.getLogger('deprecated')
+
+ def take_action_network(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip show" instead.'))
+ return super(ShowIPFloating, self).take_action_network(
+ client, parsed_args)
+
+ def take_action_compute(self, client, parsed_args):
+ self.log.warning(_('This command has been deprecated. '
+ 'Please use "floating ip show" instead.'))
+ return super(ShowIPFloating, self).take_action_compute(
+ client, parsed_args)
diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py
new file mode 100644
index 00000000..7a759449
--- /dev/null
+++ b/openstackclient/network/v2/network_rbac.py
@@ -0,0 +1,75 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""RBAC action implementations"""
+
+from osc_lib.command import command
+from osc_lib import utils
+
+from openstackclient.i18n import _
+
+
+def _get_columns(item):
+ columns = list(item.keys())
+ if 'tenant_id' in columns:
+ columns.remove('tenant_id')
+ columns.append('project_id')
+ if 'target_tenant' in columns:
+ columns.remove('target_tenant')
+ columns.append('target_project')
+ return tuple(sorted(columns))
+
+
+class ListNetworkRBAC(command.Lister):
+ """List network RBAC policies"""
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+
+ columns = (
+ 'id',
+ 'object_type',
+ 'object_id',
+ )
+ column_headers = (
+ 'ID',
+ 'Object Type',
+ 'Object ID',
+ )
+
+ data = client.rbac_policies()
+ return (column_headers,
+ (utils.get_item_properties(
+ s, columns,
+ ) for s in data))
+
+
+class ShowNetworkRBAC(command.ShowOne):
+ """Display network RBAC policy details"""
+
+ def get_parser(self, prog_name):
+ parser = super(ShowNetworkRBAC, self).get_parser(prog_name)
+ parser.add_argument(
+ 'rbac_policy',
+ metavar="<rbac-policy>",
+ help=_("RBAC policy (ID only)")
+ )
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.network
+ obj = client.find_rbac_policy(parsed_args.rbac_policy,
+ ignore_missing=False)
+ columns = _get_columns(obj)
+ data = utils.get_item_properties(obj, columns)
+ return columns, data
diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py
index 76402476..b47b00b2 100644
--- a/openstackclient/tests/compute/v2/fakes.py
+++ b/openstackclient/tests/compute/v2/fakes.py
@@ -1166,7 +1166,13 @@ class FakeHost(object):
"stats": "",
"numa_topology": "",
"ram_allocation_ratio": 1.0,
- "cpu_allocation_ratio": 1.0
+ "cpu_allocation_ratio": 1.0,
+ "zone": 'zone-' + uuid.uuid4().hex,
+ "host_name": 'name-' + uuid.uuid4().hex,
+ "service": 'service-' + uuid.uuid4().hex,
+ "cpu": 4,
+ "disk_gb": 100,
+ 'project': 'project-' + uuid.uuid4().hex,
}
host_info.update(attrs)
host = fakes.FakeResource(
diff --git a/openstackclient/tests/compute/v2/test_host.py b/openstackclient/tests/compute/v2/test_host.py
index 63ae1f6d..9ebed68d 100644
--- a/openstackclient/tests/compute/v2/test_host.py
+++ b/openstackclient/tests/compute/v2/test_host.py
@@ -15,6 +15,7 @@
from openstackclient.compute.v2 import host
from openstackclient.tests.compute.v2 import fakes as compute_fakes
+from openstackclient.tests import utils as tests_utils
class TestHost(compute_fakes.TestComputev2):
@@ -27,6 +28,58 @@ class TestHost(compute_fakes.TestComputev2):
self.host_mock.reset_mock()
+class TestHostList(TestHost):
+
+ host = compute_fakes.FakeHost.create_one_host()
+
+ columns = (
+ 'Host Name',
+ 'Service',
+ 'Zone',
+ )
+
+ data = [(
+ host.host_name,
+ host.service,
+ host.zone,
+ )]
+
+ def setUp(self):
+ super(TestHostList, self).setUp()
+
+ self.host_mock.list_all.return_value = [self.host]
+
+ self.cmd = host.ListHost(self.app, None)
+
+ def test_host_list_no_option(self):
+ arglist = []
+ verifylist = []
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.host_mock.list_all.assert_called_with(None)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+ def test_host_list_with_option(self):
+ arglist = [
+ '--zone', self.host.zone,
+ ]
+ verifylist = [
+ ('zone', self.host.zone),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.host_mock.list_all.assert_called_with(self.host.zone)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
class TestHostSet(TestHost):
def setUp(self):
@@ -73,3 +126,54 @@ class TestHostSet(TestHost):
body = {'status': 'enable', 'maintenance_mode': 'disable'}
self.host_mock.update.assert_called_with(self.host.host, body)
+
+
+class TestHostShow(TestHost):
+
+ host = compute_fakes.FakeHost.create_one_host()
+
+ columns = (
+ 'Host',
+ 'Project',
+ 'CPU',
+ 'Memory MB',
+ 'Disk GB',
+ )
+ data = [(
+ host.host,
+ host.project,
+ host.cpu,
+ host.memory_mb,
+ host.disk_gb,
+ )]
+
+ def setUp(self):
+ super(TestHostShow, self).setUp()
+
+ self.host_mock.get.return_value = [self.host]
+
+ self.cmd = host.ShowHost(self.app, None)
+
+ def test_host_show_no_option(self):
+ arglist = []
+ verifylist = []
+
+ # Missing required args should bail here
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_host_show_with_option(self):
+ arglist = [
+ self.host.host_name,
+ ]
+ verifylist = [
+ ('host', self.host.host_name),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.host_mock.get.assert_called_with(self.host.host_name)
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py
index 1bac9036..05752094 100644
--- a/openstackclient/tests/network/v2/fakes.py
+++ b/openstackclient/tests/network/v2/fakes.py
@@ -491,6 +491,57 @@ class FakePort(object):
return mock.MagicMock(side_effect=ports)
+class FakeNetworkRBAC(object):
+ """Fake one or more network rbac policies."""
+
+ @staticmethod
+ def create_one_network_rbac(attrs=None):
+ """Create a fake network rbac
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with id, action, target_tenant,
+ tenant_id, type
+ """
+ attrs = attrs or {}
+
+ # Set default attributes
+ rbac_attrs = {
+ 'id': 'rbac-id-' + uuid.uuid4().hex,
+ 'object_type': 'network',
+ 'object_id': 'object-id-' + uuid.uuid4().hex,
+ 'action': 'access_as_shared',
+ 'target_tenant': 'target-tenant-' + uuid.uuid4().hex,
+ 'tenant_id': 'tenant-id-' + uuid.uuid4().hex,
+ }
+ rbac_attrs.update(attrs)
+ rbac = fakes.FakeResource(info=copy.deepcopy(rbac_attrs),
+ loaded=True)
+ # Set attributes with special mapping in OpenStack SDK.
+ rbac.project_id = rbac_attrs['tenant_id']
+ rbac.target_project = rbac_attrs['target_tenant']
+ return rbac
+
+ @staticmethod
+ def create_network_rbacs(attrs=None, count=2):
+ """Create multiple fake network rbac policies.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :param int count:
+ The number of rbac policies to fake
+ :return:
+ A list of FakeResource objects faking the rbac policies
+ """
+ rbac_policies = []
+ for i in range(0, count):
+ rbac_policies.append(FakeNetworkRBAC.
+ create_one_network_rbac(attrs))
+
+ return rbac_policies
+
+
class FakeRouter(object):
"""Fake one or more routers."""
diff --git a/openstackclient/tests/network/v2/test_network_rbac.py b/openstackclient/tests/network/v2/test_network_rbac.py
new file mode 100644
index 00000000..057e0adc
--- /dev/null
+++ b/openstackclient/tests/network/v2/test_network_rbac.py
@@ -0,0 +1,122 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import mock
+
+from openstackclient.network.v2 import network_rbac
+from openstackclient.tests.network.v2 import fakes as network_fakes
+from openstackclient.tests import utils as tests_utils
+
+
+class TestNetworkRBAC(network_fakes.TestNetworkV2):
+
+ def setUp(self):
+ super(TestNetworkRBAC, self).setUp()
+
+ # Get a shortcut to the network client
+ self.network = self.app.client_manager.network
+
+
+class TestListNetworkRABC(TestNetworkRBAC):
+
+ # The network rbac policies going to be listed up.
+ rbac_policies = network_fakes.FakeNetworkRBAC.create_network_rbacs(count=3)
+
+ columns = (
+ 'ID',
+ 'Object Type',
+ 'Object ID',
+ )
+
+ data = []
+ for r in rbac_policies:
+ data.append((
+ r.id,
+ r.object_type,
+ r.object_id,
+ ))
+
+ def setUp(self):
+ super(TestListNetworkRABC, self).setUp()
+
+ # Get the command object to test
+ self.cmd = network_rbac.ListNetworkRBAC(self.app, self.namespace)
+
+ self.network.rbac_policies = mock.Mock(return_value=self.rbac_policies)
+
+ def test_network_rbac_list(self):
+ arglist = []
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.rbac_policies.assert_called_with()
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
+
+
+class TestShowNetworkRBAC(TestNetworkRBAC):
+
+ rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac()
+
+ columns = (
+ 'action',
+ 'id',
+ 'object_id',
+ 'object_type',
+ 'project_id',
+ 'target_project',
+ )
+
+ data = [
+ rbac_policy.action,
+ rbac_policy.id,
+ rbac_policy.object_id,
+ rbac_policy.object_type,
+ rbac_policy.tenant_id,
+ rbac_policy.target_tenant,
+ ]
+
+ def setUp(self):
+ super(TestShowNetworkRBAC, self).setUp()
+
+ # Get the command object to test
+ self.cmd = network_rbac.ShowNetworkRBAC(self.app, self.namespace)
+
+ self.network.find_rbac_policy = mock.Mock(
+ return_value=self.rbac_policy)
+
+ def test_show_no_options(self):
+ arglist = []
+ verifylist = []
+
+ self.assertRaises(tests_utils.ParserException, self.check_parser,
+ self.cmd, arglist, verifylist)
+
+ def test_network_rbac_show_all_options(self):
+ arglist = [
+ self.rbac_policy.object_id,
+ ]
+ verifylist = []
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ # DisplayCommandBase.take_action() returns two tuples
+ columns, data = self.cmd.take_action(parsed_args)
+
+ self.network.find_rbac_policy.assert_called_with(
+ self.rbac_policy.object_id, ignore_missing=False
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, list(data))
diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py
index 74e30a41..6809bebd 100644
--- a/openstackclient/tests/volume/v2/fakes.py
+++ b/openstackclient/tests/volume/v2/fakes.py
@@ -76,6 +76,38 @@ class FakeTransfer(object):
return transfer
+class FakeTypeAccess(object):
+ """Fake one or more volume type access."""
+
+ @staticmethod
+ def create_one_type_access(attrs=None):
+ """Create a fake volume type access for project.
+
+ :param Dictionary attrs:
+ A dictionary with all attributes
+ :return:
+ A FakeResource object, with Volume_type_ID and Project_ID.
+ """
+ if attrs is None:
+ attrs = {}
+
+ # Set default attributes.
+ type_access_attrs = {
+ 'volume_type_id': 'volume-type-id-' + uuid.uuid4().hex,
+ 'project_id': 'project-id-' + uuid.uuid4().hex,
+ }
+
+ # Overwrite default attributes.
+ type_access_attrs.update(attrs)
+
+ type_access = fakes.FakeResource(
+ None,
+ type_access_attrs,
+ loaded=True)
+
+ return type_access
+
+
class FakeServiceClient(object):
def __init__(self, **kwargs):
@@ -666,6 +698,7 @@ class FakeType(object):
"name": 'type-name-' + uuid.uuid4().hex,
"description": 'type-description-' + uuid.uuid4().hex,
"extra_specs": {"foo": "bar"},
+ "is_public": True,
}
# Overwrite default attributes.
diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py
index a7db2e49..e148bba4 100644
--- a/openstackclient/tests/volume/v2/test_type.py
+++ b/openstackclient/tests/volume/v2/test_type.py
@@ -13,6 +13,7 @@
#
import copy
+import mock
from osc_lib import exceptions
from osc_lib import utils
@@ -46,6 +47,7 @@ class TestTypeCreate(TestType):
columns = (
'description',
'id',
+ 'is_public',
'name',
)
@@ -56,6 +58,7 @@ class TestTypeCreate(TestType):
self.data = (
self.new_volume_type.description,
self.new_volume_type.id,
+ True,
self.new_volume_type.name,
)
@@ -357,8 +360,10 @@ class TestTypeSet(TestType):
class TestTypeShow(TestType):
columns = (
+ 'access_project_ids',
'description',
'id',
+ 'is_public',
'name',
'properties',
)
@@ -368,8 +373,10 @@ class TestTypeShow(TestType):
self.volume_type = volume_fakes.FakeType.create_one_type()
self.data = (
+ None,
self.volume_type.description,
self.volume_type.id,
+ True,
self.volume_type.name,
utils.format_dict(self.volume_type.extra_specs)
)
@@ -394,6 +401,71 @@ class TestTypeShow(TestType):
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
+ def test_type_show_with_access(self):
+ arglist = [
+ self.volume_type.id
+ ]
+ verifylist = [
+ ("volume_type", self.volume_type.id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ private_type = volume_fakes.FakeType.create_one_type(
+ attrs={'is_public': False})
+ type_access_list = volume_fakes.FakeTypeAccess.create_one_type_access()
+ with mock.patch.object(self.types_mock, 'get',
+ return_value=private_type):
+ with mock.patch.object(self.types_access_mock, 'list',
+ return_value=[type_access_list]):
+ columns, data = self.cmd.take_action(parsed_args)
+ self.types_mock.get.assert_called_once_with(
+ self.volume_type.id)
+ self.types_access_mock.list.assert_called_once_with(
+ private_type.id)
+
+ self.assertEqual(self.columns, columns)
+ private_type_data = (
+ utils.format_list([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)
+ )
+ self.assertEqual(private_type_data, data)
+
+ def test_type_show_with_list_access_exec(self):
+ arglist = [
+ self.volume_type.id
+ ]
+ verifylist = [
+ ("volume_type", self.volume_type.id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ private_type = volume_fakes.FakeType.create_one_type(
+ attrs={'is_public': False})
+ with mock.patch.object(self.types_mock, 'get',
+ return_value=private_type):
+ with mock.patch.object(self.types_access_mock, 'list',
+ side_effect=Exception()):
+ columns, data = self.cmd.take_action(parsed_args)
+ self.types_mock.get.assert_called_once_with(
+ self.volume_type.id)
+ self.types_access_mock.list.assert_called_once_with(
+ private_type.id)
+
+ self.assertEqual(self.columns, columns)
+ private_type_data = (
+ None,
+ private_type.description,
+ private_type.id,
+ private_type.is_public,
+ private_type.name,
+ utils.format_dict(private_type.extra_specs)
+ )
+ self.assertEqual(private_type_data, data)
+
class TestTypeUnset(TestType):
diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py
index ac11785c..62d619d0 100644
--- a/openstackclient/volume/v2/volume_type.py
+++ b/openstackclient/volume/v2/volume_type.py
@@ -282,8 +282,24 @@ 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(volume_type._info.pop('extra_specs'))
+ properties = utils.format_dict(
+ volume_type._info.pop('extra_specs', {}))
volume_type._info.update({'properties': properties})
+ access_project_ids = None
+ if not volume_type.is_public:
+ try:
+ volume_type_access = volume_client.volume_type_access.list(
+ volume_type.id)
+ project_ids = [utils.get_field(item, 'project_id')
+ for item in volume_type_access]
+ # TODO(Rui Chen): This format list case can be removed after
+ # patch https://review.openstack.org/#/c/330223/ merged.
+ access_project_ids = utils.format_list(project_ids)
+ except Exception as e:
+ msg = _('Failed to get access project list for volume type '
+ '%(type)s: %(e)s')
+ LOG.error(msg % {'type': volume_type.id, 'e': e})
+ volume_type._info.update({'access_project_ids': access_project_ids})
return zip(*sorted(six.iteritems(volume_type._info)))