diff --git a/neutronclient/client.py b/neutronclient/client.py index 10a189ba0..c96381a89 100644 --- a/neutronclient/client.py +++ b/neutronclient/client.py @@ -276,6 +276,9 @@ class HTTPClient(object): 'auth_user_id': self.auth_user_id, 'endpoint_url': self.endpoint_url} + def get_auth_ref(self): + return getattr(self, 'auth_ref', None) + class SessionClient(adapter.Adapter): @@ -346,6 +349,9 @@ class SessionClient(adapter.Adapter): return auth_info + def get_auth_ref(self): + return self.session.auth.get_auth_ref(self.session) + # FIXME(bklei): Should refactor this to use kwargs and only # explicitly list arguments that are not None. diff --git a/neutronclient/neutron/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py index 8ed8df83f..4d365819c 100644 --- a/neutronclient/neutron/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -715,7 +715,8 @@ class ListCommand(NeutronCommand, lister.Lister): # if no -c(s) by user and list_columns, we use columns in # both list_columns and returned resource. # Also Keep their order the same as in list_columns - _columns = [x for x in self.list_columns if x in _columns] + _columns = self._setup_columns_with_tenant_id(self.list_columns, + _columns) if parsed_args.formatter == 'table': formatters = self._formatters @@ -730,6 +731,32 @@ class ListCommand(NeutronCommand, lister.Lister): s, _columns, formatters=formatters, ) for s in info), ) + def _setup_columns_with_tenant_id(self, display_columns, avail_columns): + _columns = [x for x in display_columns if x in avail_columns] + if 'tenant_id' in display_columns: + return _columns + if 'tenant_id' not in avail_columns: + return _columns + if not self.is_admin_role(): + return _columns + try: + pos_id = _columns.index('id') + except ValueError: + pos_id = 0 + try: + pos_name = _columns.index('name') + except ValueError: + pos_name = 0 + _columns.insert(max(pos_id, pos_name) + 1, 'tenant_id') + return _columns + + def is_admin_role(self): + client = self.get_client() + auth_ref = client.httpclient.get_auth_ref() + if not auth_ref: + return False + return 'admin' in auth_ref.role_names + def take_action(self, parsed_args): self.set_extra_attrs(parsed_args) data = self.retrieve_list(parsed_args) diff --git a/neutronclient/tests/functional/base.py b/neutronclient/tests/functional/base.py index 16bbe2b07..b0c672f89 100644 --- a/neutronclient/tests/functional/base.py +++ b/neutronclient/tests/functional/base.py @@ -47,19 +47,28 @@ class ClientTestBase(base.ClientTestBase): """ - def _get_clients(self): - self.creds = credentials() + def _get_clients_from_os_cloud_config(self, cloud='devstack-admin'): + creds = credentials(cloud) cli_dir = os.environ.get( 'OS_NEUTRONCLIENT_EXEC_DIR', os.path.join(os.path.abspath('.'), '.tox/functional/bin')) return base.CLIClient( - username=self.creds['username'], - password=self.creds['password'], - tenant_name=self.creds['project_name'], - uri=self.creds['auth_url'], + username=creds['username'], + password=creds['password'], + tenant_name=creds['project_name'], + uri=creds['auth_url'], cli_dir=cli_dir) + def _get_clients(self): + return self._get_clients_from_os_cloud_config() + def neutron(self, *args, **kwargs): return self.clients.neutron(*args, **kwargs) + + def neutron_non_admin(self, *args, **kwargs): + if not hasattr(self, '_non_admin_clients'): + self._non_admin_clients = self._get_clients_from_os_cloud_config( + cloud='devstack') + return self._non_admin_clients.neutron(*args, **kwargs) diff --git a/neutronclient/tests/functional/core/test_clientlib.py b/neutronclient/tests/functional/core/test_clientlib.py index 0fd968369..f95f519b8 100644 --- a/neutronclient/tests/functional/core/test_clientlib.py +++ b/neutronclient/tests/functional/core/test_clientlib.py @@ -106,6 +106,13 @@ class LibraryTestCase(object): with testtools.ExpectedException(exceptions.NetworkNotFoundClient): self.client.show_network(net_id) + def test_get_auth_ref(self): + # Call some API call to ensure the client is authenticated. + self.client.list_networks() + auth_ref = self.client.httpclient.get_auth_ref() + self.assertIsNotNone(auth_ref) + self.assertIsNotNone(auth_ref.role_names) + class LibraryHTTPClientTenantTest(LibraryTestCase, Libv2HTTPClientTenantTestBase): diff --git a/neutronclient/tests/functional/core/test_common.py b/neutronclient/tests/functional/core/test_common.py new file mode 100644 index 000000000..6bdadf96f --- /dev/null +++ b/neutronclient/tests/functional/core/test_common.py @@ -0,0 +1,33 @@ +# Copyright 2016 NEC Corporation +# +# 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. + +from neutronclient.tests.functional import base + + +class CLICommonFeatureTest(base.ClientTestBase): + + def test_tenant_id_shown_in_list_by_admin(self): + nets = self.parser.table(self.neutron('net-list')) + self.assertIn('tenant_id', nets['headers']) + + def test_tenant_id_not_shown_in_list_with_columns(self): + nets = self.parser.table(self.neutron('net-list -c id -c name')) + self.assertNotIn('tenant_id', nets['headers']) + self.assertListEqual(['id', 'name'], nets['headers']) + + def test_tenant_id_not_shown_in_list_by_non_admin(self): + output = self.neutron_non_admin('net-list') + self.assertNotIn('tenant_id', self.parser.table(output)['headers']) + self.assertTableStruct(self.parser.listing(output), + ['id', 'name']) diff --git a/neutronclient/tests/unit/test_cli20_network.py b/neutronclient/tests/unit/test_cli20_network.py index cbd41bd1c..f4464c33d 100644 --- a/neutronclient/tests/unit/test_cli20_network.py +++ b/neutronclient/tests/unit/test_cli20_network.py @@ -299,9 +299,8 @@ class CLITestV20NetworkJSON(test_cli20.CLITestV20Base): cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) self.mox.StubOutWithMock(cmd, 'get_client') self.mox.StubOutWithMock(self.client.httpclient, 'request') - cmd.get_client().AndReturn(self.client) + cmd.get_client().MultipleTimes().AndReturn(self.client) setup_list_stub('networks', data, '') - cmd.get_client().AndReturn(self.client) filters = '' for n in data: for s in n['subnets']: diff --git a/neutronclient/tests/unit/test_cli20_securitygroup.py b/neutronclient/tests/unit/test_cli20_securitygroup.py index 1af88ff79..bb4c3ddcb 100644 --- a/neutronclient/tests/unit/test_cli20_securitygroup.py +++ b/neutronclient/tests/unit/test_cli20_securitygroup.py @@ -385,13 +385,12 @@ class CLITestV20SecurityGroupsJSON(test_cli20.CLITestV20Base): test_cli20.MyApp(sys.stdout), None) self.mox.StubOutWithMock(cmd, 'get_client') self.mox.StubOutWithMock(self.client.httpclient, 'request') - cmd.get_client().AndReturn(self.client) + cmd.get_client().MultipleTimes().AndReturn(self.client) query = '' if query_fields: query = '&'.join(['fields=' + f for f in query_fields]) setup_list_stub('security_group_rules', api_data, query) if conv: - cmd.get_client().AndReturn(self.client) sec_ids = set() for n in api_data: sec_ids.add(n['security_group_id']) diff --git a/releasenotes/notes/show-tenant-id-admin-listing-dc13ee7eb889d418.yaml b/releasenotes/notes/show-tenant-id-admin-listing-dc13ee7eb889d418.yaml new file mode 100644 index 000000000..6b84e1843 --- /dev/null +++ b/releasenotes/notes/show-tenant-id-admin-listing-dc13ee7eb889d418.yaml @@ -0,0 +1,6 @@ +--- +features: + - Show tenant_id when ``*-list`` command is run by admin. In neutron + the list operations by admin retrieve all resources from all tenants. + It is not easy to distinguish resources without tenant_id. + This feature is useful for admin operations.