diff options
Diffstat (limited to 'openstackclient')
| -rw-r--r-- | openstackclient/network/v2/floating_ip.py | 109 | ||||
| -rw-r--r-- | openstackclient/network/v2/network_rbac.py | 75 | ||||
| -rw-r--r-- | openstackclient/tests/compute/v2/fakes.py | 8 | ||||
| -rw-r--r-- | openstackclient/tests/compute/v2/test_host.py | 104 | ||||
| -rw-r--r-- | openstackclient/tests/network/v2/fakes.py | 51 | ||||
| -rw-r--r-- | openstackclient/tests/network/v2/test_network_rbac.py | 122 | ||||
| -rw-r--r-- | openstackclient/tests/volume/v2/fakes.py | 33 | ||||
| -rw-r--r-- | openstackclient/tests/volume/v2/test_type.py | 72 | ||||
| -rw-r--r-- | openstackclient/volume/v2/volume_type.py | 18 |
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))) |
