From f1bd417861aaa584c85db1c97818d81c1309c6af Mon Sep 17 00:00:00 2001 From: Tobias Urdin Date: Wed, 12 Mar 2025 17:03:26 +0100 Subject: [PATCH 01/67] Add device ID and device owner to port unset This adds support to unset the device_id and device_owner property on a port. Change-Id: I43b1ea63e3a119f57162948e128a85f8ba323d41 --- openstackclient/network/v2/port.py | 16 ++++++++ .../tests/unit/network/v2/test_port.py | 40 +++++++++++++++++++ ...-device-id-and-owner-9fce242155c82992.yaml | 5 +++ 3 files changed, 61 insertions(+) create mode 100644 releasenotes/notes/port-unset-device-id-and-owner-9fce242155c82992.yaml diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 3579194bf..01e2e4faa 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -1317,6 +1317,18 @@ def get_parser(self, prog_name): default=False, help=_("Clear hints for the port."), ) + parser.add_argument( + '--device', + action='store_true', + default=False, + help=_("Clear device ID for the port."), + ) + parser.add_argument( + '--device-owner', + action='store_true', + default=False, + help=_("Clear device owner for the port."), + ) _tag.add_tag_option_to_parser_for_unset(parser, _('port')) parser.add_argument( 'port', @@ -1383,6 +1395,10 @@ def take_action(self, parsed_args): attrs['binding:host_id'] = None if parsed_args.hints: attrs['hints'] = None + if parsed_args.device: + attrs['device_id'] = '' + if parsed_args.device_owner: + attrs['device_owner'] = '' attrs.update( self._parse_extra_properties(parsed_args.extra_properties) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 3f521a42e..d66eb1bf4 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -3015,3 +3015,43 @@ def test_unset_hints(self): **{'hints': None}, ) self.assertIsNone(result) + + def test_unset_device(self): + testport = network_fakes.create_one_port() + self.network_client.find_port = mock.Mock(return_value=testport) + arglist = [ + '--device', + testport.name, + ] + verifylist = [ + ('device', True), + ('port', testport.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network_client.update_port.assert_called_once_with( + testport, + **{'device_id': ''}, + ) + self.assertIsNone(result) + + def test_unset_device_owner(self): + testport = network_fakes.create_one_port() + self.network_client.find_port = mock.Mock(return_value=testport) + arglist = [ + '--device-owner', + testport.name, + ] + verifylist = [ + ('device_owner', True), + ('port', testport.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network_client.update_port.assert_called_once_with( + testport, + **{'device_owner': ''}, + ) + self.assertIsNone(result) diff --git a/releasenotes/notes/port-unset-device-id-and-owner-9fce242155c82992.yaml b/releasenotes/notes/port-unset-device-id-and-owner-9fce242155c82992.yaml new file mode 100644 index 000000000..e14baacdd --- /dev/null +++ b/releasenotes/notes/port-unset-device-id-and-owner-9fce242155c82992.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Added ``--device`` and ``--device-owner`` parameter to the + ``port unset`` command. From 2e301857af7662520576133bbcde5ea9fa153a56 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 29 May 2025 19:08:18 +0100 Subject: [PATCH 02/67] docs: Add note about scoping on tokens Change-Id: I4df74eaa1aa82fb8666bc1e6728b55a3e81bc76a Signed-off-by: Stephen Finucane --- doc/source/cli/authentication.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/source/cli/authentication.rst b/doc/source/cli/authentication.rst index 8ed318f52..8c09fc364 100644 --- a/doc/source/cli/authentication.rst +++ b/doc/source/cli/authentication.rst @@ -295,6 +295,15 @@ or, using environment variables: $ TOKEN=$(openstack token issue -f value -c id) +.. note:: + + The above examples assume you require a project-scoped token. You can omit + the project-related configuration if your user has a default project ID set. + Conversely, if requesting domain-scoped or system-scoped, you should update + these examples accordingly. If the user does not have a default project + configured and no scoping information is provided, the resulting token will + be unscoped. + ``v3totp`` ~~~~~~~~~~ From edb17881d0c3ec631ed294f3b81689cf357672b9 Mon Sep 17 00:00:00 2001 From: psnew14 Date: Sun, 1 Jun 2025 22:50:55 +0900 Subject: [PATCH 03/67] Remove leading empty line from server create with --wait The "openstack server create" with "--wait" args was priting an extra empty line character before the server ID. This commit removes the extra empty line. story: 2010947 task: 48984 Change-Id: Ib5ba1c9f23e7655ddfae0e5b644ed167ecd6485e --- openstackclient/compute/v2/server.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ac29650f2..e14392a53 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2129,13 +2129,11 @@ def _match_image(image_api, wanted_properties): f.close() if parsed_args.wait: - if utils.wait_for_status( + if not utils.wait_for_status( compute_client.get_server, server.id, callback=_show_progress, ): - self.app.stdout.write('\n') - else: msg = _('Error creating server: %s') % parsed_args.server_name raise exceptions.CommandError(msg) From b6af7883b7f493f01142fe6774dab434454a080a Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Fri, 27 Jun 2025 23:46:22 +0900 Subject: [PATCH 04/67] Replace deprecated assertItemsEqual It has been provided by testtools to ease migration from python 2, but was deprecated in 2.7.2[1] and will be removed in 2.8.0[2]. [1] https://github.com/testing-cabal/testtools/commit/e0d56b7ce65ae5b3d [2] https://github.com/testing-cabal/testtools/commit/f01e86084e6a858d1 Change-Id: I8b68212a88553aff5c3b4182c246b3c0f7365cf6 --- openstackclient/tests/unit/network/v2/test_ndp_proxy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/unit/network/v2/test_ndp_proxy.py b/openstackclient/tests/unit/network/v2/test_ndp_proxy.py index 13d3f3d99..f9d9a3b3c 100644 --- a/openstackclient/tests/unit/network/v2/test_ndp_proxy.py +++ b/openstackclient/tests/unit/network/v2/test_ndp_proxy.py @@ -311,7 +311,7 @@ def test_ndp_proxy_list_project(self): **{'project_id': project.id} ) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) def test_ndp_proxy_list_project_domain(self): project = identity_fakes_v3.FakeProject.create_one_project() @@ -332,7 +332,7 @@ def test_ndp_proxy_list_project_domain(self): self.network_client.ndp_proxies.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) - self.assertItemsEqual(self.data, list(data)) + self.assertCountEqual(self.data, list(data)) class TestSetNDPProxy(TestNDPProxy): From e37148484c3db08200ed163d106a717b0effa1f0 Mon Sep 17 00:00:00 2001 From: Alexey Stupnikov Date: Fri, 25 Jul 2025 19:23:35 +0200 Subject: [PATCH 05/67] Remap custom named Image attributes when unsetting Some Image attributes defined in openstacksdk are named differently from actual properties managed by Glance. Because openstackclient checked property names to be unset against Image object properties, it was impossible to unset such properties. This patch introduces a IMAGE_ATTRIBUTES_CUSTOM_NAMES dictionary mapping real property names with custom attribute names. Closes-bug: #2115732 Change-Id: I7296fc293dff9208464c9a07f58ce3e9ffabd3e9 Signed-off-by: Alexey Stupnikov --- openstackclient/image/v2/image.py | 18 ++++++++++++++++++ .../tests/unit/image/v2/test_image.py | 10 +++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 6c7ca740e..d2a1a74ea 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -55,6 +55,19 @@ "iso", "ploop", ] +# A list of openstacksdk Image object attributes (values) that named +# differently from actual properties stored by Glance (keys). +IMAGE_ATTRIBUTES_CUSTOM_NAMES = { + 'os_hidden': 'is_hidden', + 'protected': 'is_protected', + 'os_hash_algo': 'hash_algo', + 'os_hash_value': 'hash_value', + 'img_config_drive': 'needs_config_drive', + 'os_secure_boot': 'needs_secure_boot', + 'hw_vif_multiqueue_enabled': 'is_hw_vif_multiqueue_enabled', + 'hw_boot_menu': 'is_hw_boot_menu_enabled', + 'auto_disk_config': 'has_auto_disk_config', +} MEMBER_STATUS_CHOICES = ["accepted", "pending", "rejected", "all"] LOG = logging.getLogger(__name__) @@ -1478,6 +1491,11 @@ def take_action(self, parsed_args): ) new_props.pop(k, None) kwargs['properties'] = new_props + elif ( + k in IMAGE_ATTRIBUTES_CUSTOM_NAMES + and IMAGE_ATTRIBUTES_CUSTOM_NAMES[k] in image + ): + delattr(image, IMAGE_ATTRIBUTES_CUSTOM_NAMES[k]) else: LOG.error( _( diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 95d384c24..4f5db9f00 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -1779,6 +1779,7 @@ def setUp(self): attrs['hw_rng_model'] = 'virtio' attrs['prop'] = 'test' attrs['prop2'] = 'fake' + attrs['os_secure_boot'] = 'required' self.image = image_fakes.create_one_image(attrs) self.image_client.find_image.return_value = self.image @@ -1822,11 +1823,18 @@ def test_image_unset_property_option(self): 'hw_rng_model', '--property', 'prop', + '--property', + 'os_secure_boot', self.image.id, ] + # openstacksdk translates 'os_secure_boot' property to + # 'needs_secure_boot' Image attribute. This is true for + # all IMAGE_ATTRIBUTES_CUSTOM_NAMES keys + self.assertEqual(self.image.needs_secure_boot, 'required') + self.assertFalse(hasattr(self.image, 'os_secure_boot')) verifylist = [ - ('properties', ['hw_rng_model', 'prop']), + ('properties', ['hw_rng_model', 'prop', 'os_secure_boot']), ('image', self.image.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) From 9f55b253a3fb9303dfe6b136fb0d19cfd58d6cf0 Mon Sep 17 00:00:00 2001 From: Mridula Joshi Date: Thu, 21 Dec 2023 07:38:18 +0000 Subject: [PATCH 06/67] Adds CLI support for ``glance md-namespace-objects-delete`` This patch adds operation which delete all metadef object inside a namespace. This can be implemented by `image metadef object delete` Change-Id: Ib196e295aad1921d8bc0c451522e0ad530389134 Depends-on: https://review.opendev.org/c/openstack/openstacksdk/+/901671 Signed-off-by: Cyril Roelandt --- doc/source/cli/data/glance.csv | 2 +- openstackclient/image/v2/metadef_objects.py | 10 +++++++-- .../unit/image/v2/test_metadef_objects.py | 22 +++++++++++++++++++ ...espace-object-delete-b6b2de24fc66e602.yaml | 4 ++++ 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/add-image-metadef-namespace-object-delete-b6b2de24fc66e602.yaml diff --git a/doc/source/cli/data/glance.csv b/doc/source/cli/data/glance.csv index c248904fe..61cd6c1e5 100644 --- a/doc/source/cli/data/glance.csv +++ b/doc/source/cli/data/glance.csv @@ -26,7 +26,7 @@ md-namespace-create,image metadef namespace create,Create a new metadata definit md-namespace-delete,image metadef namespace delete,Delete specified metadata definitions namespace with its contents. md-namespace-import,,Import a metadata definitions namespace from file or standard input. md-namespace-list,image metadef namespace list,List metadata definitions namespaces. -md-namespace-objects-delete,,Delete all metadata definitions objects inside a specific namespace. +md-namespace-objects-delete,image metadef object delete,Delete all metadata definitions objects inside a specific namespace. md-namespace-properties-delete,,Delete all metadata definitions property inside a specific namespace. md-namespace-resource-type-list,image metadef resource type association list,List resource types associated to specific namespace. md-namespace-show,image metadef namespace show,Describe a specific metadata definitions namespace. diff --git a/openstackclient/image/v2/metadef_objects.py b/openstackclient/image/v2/metadef_objects.py index 04c0fa55f..09fc249cb 100644 --- a/openstackclient/image/v2/metadef_objects.py +++ b/openstackclient/image/v2/metadef_objects.py @@ -123,8 +123,11 @@ def get_parser(self, prog_name): parser.add_argument( "objects", metavar="", - nargs="+", - help=_("Metadef object(s) to delete (name)"), + nargs="*", + help=_( + "Metadef object(s) to delete (name) " + "(omit this argument to delete all objects in the namespace)" + ), ) return parser @@ -133,6 +136,9 @@ def take_action(self, parsed_args): namespace = parsed_args.namespace + if not parsed_args.objects: + return image_client.delete_all_metadef_objects(namespace) + result = 0 for obj in parsed_args.objects: try: diff --git a/openstackclient/tests/unit/image/v2/test_metadef_objects.py b/openstackclient/tests/unit/image/v2/test_metadef_objects.py index 4996060d9..6306e23eb 100644 --- a/openstackclient/tests/unit/image/v2/test_metadef_objects.py +++ b/openstackclient/tests/unit/image/v2/test_metadef_objects.py @@ -113,6 +113,7 @@ def setUp(self): super().setUp() self.image_client.delete_metadef_object.return_value = None + self.image_client.delete_all_metadef_objects.return_value = None self.cmd = metadef_objects.DeleteMetadefObject(self.app, None) def test_object_delete(self): @@ -126,8 +127,29 @@ def test_object_delete(self): result = self.cmd.take_action(parsed_args) + self.image_client.delete_metadef_object.assert_called_once_with( + self.image_client.get_metadef_object(), + self._metadef_namespace.namespace, + ) + self.image_client.delete_all_metadef_objects.assert_not_called() self.assertIsNone(result) + def test_object_delete_all(self): + arglist = [ + self._metadef_namespace.namespace, + ] + + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + self.image_client.delete_all_metadef_objects.assert_called_with( + self._metadef_namespace.namespace, + ) + self.image_client.delete_metadef_object.assert_not_called() + class TestMetadefObjectList(fakes.TestImagev2): _metadef_namespace = fakes.create_one_metadef_namespace() diff --git a/releasenotes/notes/add-image-metadef-namespace-object-delete-b6b2de24fc66e602.yaml b/releasenotes/notes/add-image-metadef-namespace-object-delete-b6b2de24fc66e602.yaml new file mode 100644 index 000000000..7861a0c7a --- /dev/null +++ b/releasenotes/notes/add-image-metadef-namespace-object-delete-b6b2de24fc66e602.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Adds operation which deletes all metadef object inside a namespace. From e8a7db58589c446d2a9a1ed975115c531b6db055 Mon Sep 17 00:00:00 2001 From: dna Date: Wed, 21 May 2025 23:42:06 +0900 Subject: [PATCH 07/67] tests: Simplify mocking in server tests Replace assignments of `Mock` objects to methods that are already mocked in the class functions within test_server.py Change-Id: I446632301c1b9f94545a0b8314e54e761d5d296f Signed-off-by: dna Story: 2011459 Task: 52211 --- .../tests/unit/compute/v2/test_server.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 0898c67bf..f685d1615 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -408,7 +408,7 @@ def setUp(self): self.server = compute_fakes.create_one_server() self.compute_client.find_server.return_value = self.server - self.network_client.update_ip = mock.Mock(return_value=None) + self.network_client.update_ip.return_value = None # Get the command object to test self.cmd = server.AddFloatingIP(self.app, None) @@ -416,8 +416,8 @@ def setUp(self): def test_server_add_floating_ip(self): _port = network_fakes.create_one_port() _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() - self.network_client.find_ip = mock.Mock(return_value=_floating_ip) - self.network_client.ports = mock.Mock(return_value=[_port]) + self.network_client.find_ip.return_value = _floating_ip + self.network_client.ports.return_value = [_port] arglist = [ self.server.id, _floating_ip['floating_ip_address'], @@ -448,8 +448,8 @@ def test_server_add_floating_ip(self): def test_server_add_floating_ip_no_ports(self): floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() - self.network_client.find_ip = mock.Mock(return_value=floating_ip) - self.network_client.ports = mock.Mock(return_value=[]) + self.network_client.find_ip.return_value = floating_ip + self.network_client.ports.return_value = [] arglist = [ self.server.id, @@ -479,17 +479,17 @@ def test_server_add_floating_ip_no_ports(self): def test_server_add_floating_ip_no_external_gateway(self, success=False): _port = network_fakes.create_one_port() _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() - self.network_client.find_ip = mock.Mock(return_value=_floating_ip) + self.network_client.find_ip.return_value = _floating_ip return_value = [_port] # In the success case, we'll have two ports, where the first port is # not attached to an external gateway but the second port is. if success: return_value.append(_port) - self.network_client.ports = mock.Mock(return_value=return_value) + self.network_client.ports.return_value = return_value side_effect = [sdk_exceptions.NotFoundException()] if success: side_effect.append(None) - self.network_client.update_ip = mock.Mock(side_effect=side_effect) + self.network_client.update_ip.side_effect = side_effect arglist = [ self.server.id, _floating_ip['floating_ip_address'], @@ -535,8 +535,8 @@ def test_server_add_floating_ip_one_external_gateway(self): def test_server_add_floating_ip_with_fixed_ip(self): _port = network_fakes.create_one_port() _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() - self.network_client.find_ip = mock.Mock(return_value=_floating_ip) - self.network_client.ports = mock.Mock(return_value=[_port]) + self.network_client.find_ip.return_value = _floating_ip + self.network_client.ports.return_value = [_port] # The user has specified a fixed ip that matches one of the ports # already attached to the instance. arglist = [ @@ -575,8 +575,8 @@ def test_server_add_floating_ip_with_fixed_ip(self): def test_server_add_floating_ip_with_fixed_ip_no_port_found(self): _port = network_fakes.create_one_port() _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() - self.network_client.find_ip = mock.Mock(return_value=_floating_ip) - self.network_client.ports = mock.Mock(return_value=[_port]) + self.network_client.find_ip.return_value = _floating_ip + self.network_client.ports.return_value = [_port] # The user has specified a fixed ip that does not match any of the # ports already attached to the instance. nonexistent_ip = '10.0.0.9' @@ -7351,14 +7351,14 @@ class TestServerRemoveFloatingIPNetwork(network_fakes.TestNetworkV2): def setUp(self): super().setUp() - self.network_client.update_ip = mock.Mock(return_value=None) + self.network_client.update_ip.return_value = None # Get the command object to test self.cmd = server.RemoveFloatingIP(self.app, None) def test_server_remove_floating_ip_default(self): _floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip() - self.network_client.find_ip = mock.Mock(return_value=_floating_ip) + self.network_client.find_ip.return_value = _floating_ip arglist = [ 'fake_server', _floating_ip['ip'], From a99ae364fc094ccd6bbcdd2b7fc64f4395bef6ef Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 14 Aug 2025 12:01:03 +0100 Subject: [PATCH 08/67] tests: Avoid unnecessary mocks Change-Id: Ibb1bf5c29bf37d3f31889b091a055d0308e8cd85 Signed-off-by: Stephen Finucane --- .../tests/unit/compute/v2/test_server.py | 65 ++++++++----------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index f685d1615..86951360b 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -89,10 +89,6 @@ def setUp(self): # Get the command object to test self.cmd = server.AddFixedIP(self.app, None) - # Mock network methods - self.find_network = mock.Mock() - self.app.client_manager.network.find_network = self.find_network - def test_server_add_fixed_ip_pre_v249_with_tag(self): self.set_compute_api_version('2.48') @@ -614,9 +610,6 @@ def setUp(self): # Get the command object to test self.cmd = server.AddPort(self.app, None) - self.find_port = mock.Mock() - self.app.client_manager.network.find_port = self.find_port - def _test_server_add_port(self, port_id): servers = self.setup_sdk_servers_mock(count=1) port = 'fake-port' @@ -636,21 +629,23 @@ def _test_server_add_port(self, port_id): self.assertIsNone(result) def test_server_add_port(self): - self._test_server_add_port(self.find_port.return_value.id) - self.find_port.assert_called_once_with( + self._test_server_add_port( + self.network_client.find_port.return_value.id + ) + self.network_client.find_port.assert_called_once_with( 'fake-port', ignore_missing=False ) def test_server_add_port_no_neutron(self): self.app.client_manager.network_endpoint_enabled = False self._test_server_add_port('fake-port') - self.find_port.assert_not_called() + self.network_client.find_port.assert_not_called() def test_server_add_port_with_tag(self): self.set_compute_api_version('2.49') servers = self.setup_sdk_servers_mock(count=1) - self.find_port.return_value.id = 'fake-port' + self.network_client.find_port.return_value.id = 'fake-port' arglist = [ servers[0].id, 'fake-port', @@ -675,7 +670,7 @@ def test_server_add_port_with_tag_pre_v249(self): self.set_compute_api_version('2.48') servers = self.setup_sdk_servers_mock(count=1) - self.find_port.return_value.id = 'fake-port' + self.network_client.find_port.return_value.id = 'fake-port' arglist = [ servers[0].id, 'fake-port', @@ -1038,9 +1033,6 @@ def setUp(self): # Get the command object to test self.cmd = server.AddNetwork(self.app, None) - self.find_network = mock.Mock() - self.app.client_manager.network.find_network = self.find_network - def _test_server_add_network(self, net_id): servers = self.setup_sdk_servers_mock(count=1) network = 'fake-network' @@ -1060,21 +1052,23 @@ def _test_server_add_network(self, net_id): self.assertIsNone(result) def test_server_add_network(self): - self._test_server_add_network(self.find_network.return_value.id) - self.find_network.assert_called_once_with( + self._test_server_add_network( + self.network_client.find_network.return_value.id + ) + self.network_client.find_network.assert_called_once_with( 'fake-network', ignore_missing=False ) def test_server_add_network_no_neutron(self): self.app.client_manager.network_endpoint_enabled = False self._test_server_add_network('fake-network') - self.find_network.assert_not_called() + self.network_client.find_network.assert_not_called() def test_server_add_network_with_tag(self): self.set_compute_api_version('2.49') servers = self.setup_sdk_servers_mock(count=1) - self.find_network.return_value.id = 'fake-network' + self.network_client.find_network.return_value.id = 'fake-network' arglist = [ servers[0].id, @@ -1100,7 +1094,7 @@ def test_server_add_network_with_tag_pre_v249(self): self.set_compute_api_version('2.48') servers = self.setup_sdk_servers_mock(count=1) - self.find_network.return_value.id = 'fake-network' + self.network_client.find_network.return_value.id = 'fake-network' arglist = [ servers[0].id, @@ -7391,9 +7385,6 @@ def setUp(self): # Get the command object to test self.cmd = server.RemovePort(self.app, None) - self.find_port = mock.Mock() - self.app.client_manager.network.find_port = self.find_port - def _test_server_remove_port(self, port_id): servers = self.setup_sdk_servers_mock(count=1) port = 'fake-port' @@ -7416,15 +7407,17 @@ def _test_server_remove_port(self, port_id): self.assertIsNone(result) def test_server_remove_port(self): - self._test_server_remove_port(self.find_port.return_value.id) - self.find_port.assert_called_once_with( + self._test_server_remove_port( + self.network_client.find_port.return_value.id + ) + self.network_client.find_port.assert_called_once_with( 'fake-port', ignore_missing=False ) def test_server_remove_port_no_neutron(self): self.app.client_manager.network_endpoint_enabled = False self._test_server_remove_port('fake-port') - self.find_port.assert_not_called() + self.network_client.find_port.assert_not_called() class TestServerRemoveNetwork(TestServer): @@ -7434,16 +7427,12 @@ def setUp(self): # Get the command object to test self.cmd = server.RemoveNetwork(self.app, None) - # Set method to be tested. - self.fake_inf = mock.Mock() - - self.find_network = mock.Mock() - self.app.client_manager.network.find_network = self.find_network - self.compute_client.server_interfaces.return_value = [self.fake_inf] + self.fake_if = mock.Mock() + self.compute_client.server_interfaces.return_value = [self.fake_if] def _test_server_remove_network(self, network_id): - self.fake_inf.net_id = network_id - self.fake_inf.port_id = 'fake-port' + self.fake_if.net_id = network_id + self.fake_if.port_id = 'fake-port' servers = self.setup_sdk_servers_mock(count=1) network = 'fake-network' @@ -7468,15 +7457,17 @@ def _test_server_remove_network(self, network_id): self.assertIsNone(result) def test_server_remove_network(self): - self._test_server_remove_network(self.find_network.return_value.id) - self.find_network.assert_called_once_with( + self._test_server_remove_network( + self.network_client.find_network.return_value.id + ) + self.network_client.find_network.assert_called_once_with( 'fake-network', ignore_missing=False ) def test_server_remove_network_no_neutron(self): self.app.client_manager.network_endpoint_enabled = False self._test_server_remove_network('fake-network') - self.find_network.assert_not_called() + self.network_client.find_network.assert_not_called() class TestServerRemoveSecurityGroup(TestServer): From 81db99b32bd3532a764c2ece7187d0f5b04abf75 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 15 Aug 2025 11:47:09 +0100 Subject: [PATCH 09/67] doc: Indicate md-namespace-import as WONTFIX This is a meta command that can be easily achieved via some shell scripting. We don't need it in OSC. Change-Id: Ia3fc8d0458cb0c0dc4695347ef953028112a9c49 Signed-off-by: Stephen Finucane --- doc/source/cli/data/glance.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/cli/data/glance.csv b/doc/source/cli/data/glance.csv index 61cd6c1e5..b4758ffda 100644 --- a/doc/source/cli/data/glance.csv +++ b/doc/source/cli/data/glance.csv @@ -24,7 +24,7 @@ location-delete,,Remove locations (and related metadata) from an image. location-update,,Update metadata of an image's location. md-namespace-create,image metadef namespace create,Create a new metadata definitions namespace. md-namespace-delete,image metadef namespace delete,Delete specified metadata definitions namespace with its contents. -md-namespace-import,,Import a metadata definitions namespace from file or standard input. +md-namespace-import,WONTFIX,Import a metadata definitions namespace from file or standard input. md-namespace-list,image metadef namespace list,List metadata definitions namespaces. md-namespace-objects-delete,image metadef object delete,Delete all metadata definitions objects inside a specific namespace. md-namespace-properties-delete,,Delete all metadata definitions property inside a specific namespace. From 13fe80196827d38ad977458905a8741e1fc28848 Mon Sep 17 00:00:00 2001 From: Hang Yang Date: Fri, 23 Oct 2020 11:18:01 -0500 Subject: [PATCH 10/67] Support image save with chunk-size option Add '--chunk-size' option to 'image save' command to control the size of bytes to read at one time. Change-Id: I0a02323384433010b8c6804a4337040acb13da8f Signed-off-by: Hang Yang --- openstackclient/image/v1/image.py | 17 ++++++- openstackclient/image/v2/image.py | 17 ++++++- .../tests/unit/image/v1/test_image.py | 47 +++++++++++++++++++ .../tests/unit/image/v2/test_image.py | 24 +++++++++- ...k-size-to-image-save-37871f9e62693264.yaml | 5 ++ 5 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/add-chunk-size-to-image-save-37871f9e62693264.yaml diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 490b4122f..91b0db428 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -528,6 +528,16 @@ class SaveImage(command.Command): def get_parser(self, prog_name): parser = super().get_parser(prog_name) + parser.add_argument( + "--chunk-size", + type=int, + default=1024, + metavar="", + help=_( + "Size in bytes to read from the wire and buffer at one " + "time (default: 1024)" + ), + ) parser.add_argument( "--file", metavar="", @@ -550,7 +560,12 @@ def take_action(self, parsed_args): if output_file is None: output_file = getattr(sys.stdout, "buffer", sys.stdout) - image_client.download_image(image.id, stream=True, output=output_file) + image_client.download_image( + image.id, + stream=True, + output=output_file, + chunk_size=parsed_args.chunk_size, + ) class SetImage(command.Command): diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index d2a1a74ea..9cd24f2ed 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -1066,6 +1066,16 @@ class SaveImage(command.Command): def get_parser(self, prog_name): parser = super().get_parser(prog_name) + parser.add_argument( + "--chunk-size", + type=int, + default=1024, + metavar="", + help=_( + "Size in bytes to read from the wire and buffer at one " + "time (default: 1024)" + ), + ) parser.add_argument( "--file", metavar="", @@ -1090,7 +1100,12 @@ def take_action(self, parsed_args): if output_file is None: output_file = getattr(sys.stdout, "buffer", sys.stdout) - image_client.download_image(image.id, stream=True, output=output_file) + image_client.download_image( + image.id, + stream=True, + output=output_file, + chunk_size=parsed_args.chunk_size, + ) class SetImage(command.Command): diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index 11d083020..2d870ff89 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -757,3 +757,50 @@ def test_image_show_human_readable(self): size_index = columns.index('size') self.assertEqual(data[size_index].human_readable(), '2K') + + +class TestImageSave(image_fakes.TestImagev1): + image = image_fakes.create_one_image({}) + + def setUp(self): + super().setUp() + + self.image_client.find_image.return_value = self.image + self.image_client.download_image.return_value = self.image + + # Get the command object to test + self.cmd = image.SaveImage(self.app, None) + + def test_save_data(self): + arglist = ['--file', '/path/to/file', self.image.id] + + verifylist = [('file', '/path/to/file'), ('image', self.image.id)] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.image_client.download_image.assert_called_once_with( + self.image.id, output='/path/to/file', stream=True, chunk_size=1024 + ) + + def test_save_data_with_chunk_size(self): + arglist = [ + '--file', + '/path/to/file', + '--chunk-size', + '2048', + self.image.id, + ] + + verifylist = [ + ('file', '/path/to/file'), + ('chunk_size', 2048), + ('image', self.image.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.image_client.download_image.assert_called_once_with( + self.image.id, output='/path/to/file', stream=True, chunk_size=2048 + ) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 4f5db9f00..2389d685f 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -2228,7 +2228,29 @@ def test_save_data(self): self.cmd.take_action(parsed_args) self.image_client.download_image.assert_called_once_with( - self.image.id, stream=True, output='/path/to/file' + self.image.id, output='/path/to/file', stream=True, chunk_size=1024 + ) + + def test_save_data_with_chunk_size(self): + arglist = [ + '--file', + '/path/to/file', + '--chunk-size', + '2048', + self.image.id, + ] + + verifylist = [ + ('filename', '/path/to/file'), + ('chunk_size', 2048), + ('image', self.image.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.image_client.download_image.assert_called_once_with( + self.image.id, output='/path/to/file', stream=True, chunk_size=2048 ) diff --git a/releasenotes/notes/add-chunk-size-to-image-save-37871f9e62693264.yaml b/releasenotes/notes/add-chunk-size-to-image-save-37871f9e62693264.yaml new file mode 100644 index 000000000..433dc8773 --- /dev/null +++ b/releasenotes/notes/add-chunk-size-to-image-save-37871f9e62693264.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--chunk-size`` option to ``image save`` command to control the size + of bytes to read at one time. From a73698490ac0eb8e286fd055dfe3a4f89d04772a Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Tue, 26 Feb 2019 13:18:27 +0100 Subject: [PATCH 11/67] image: Add hashing-related fields Add support for the 'os_hash_algo' and 'os_hash_value' image attributes added with Image API 2.7. Change-Id: Id8fe6f3fecf77f537587e9088b207ef2077a9def Signed-off-by: Artem Goncharov --- openstackclient/image/v2/image.py | 7 +++ .../tests/functional/image/v2/test_image.py | 50 +++++++++++++------ .../tests/unit/image/v2/test_image.py | 24 ++++++--- .../add-image-options-dcbc4ead7822c495.yaml | 4 ++ 4 files changed, 64 insertions(+), 21 deletions(-) create mode 100644 releasenotes/notes/add-image-options-dcbc4ead7822c495.yaml diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index d2a1a74ea..0e484afdc 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -98,6 +98,9 @@ def _format_image(image, human_readable=False): 'virtual_size', 'min_ram', 'schema', + 'is_hidden', + 'hash_algo', + 'hash_value', ] # TODO(gtema/anybody): actually it should be possible to drop this method, @@ -903,6 +906,8 @@ def take_action(self, parsed_args): 'visibility', 'is_protected', 'owner_id', + 'hash_algo', + 'hash_value', 'tags', ) column_headers: tuple[str, ...] = ( @@ -916,6 +921,8 @@ def take_action(self, parsed_args): 'Visibility', 'Protected', 'Project', + 'Hash Algorithm', + 'Hash Value', 'Tags', ) else: diff --git a/openstackclient/tests/functional/image/v2/test_image.py b/openstackclient/tests/functional/image/v2/test_image.py index 4d51737c3..828488c42 100644 --- a/openstackclient/tests/functional/image/v2/test_image.py +++ b/openstackclient/tests/functional/image/v2/test_image.py @@ -218,17 +218,39 @@ def test_image_members(self): 'image remove project ' + self.name + ' ' + my_project_id ) - # else: - # # Test not shared - # self.assertRaises( - # image_exceptions.HTTPForbidden, - # self.openstack, - # 'image add project ' + - # self.name + ' ' + - # my_project_id - # ) - # self.openstack( - # 'image set ' + - # '--share ' + - # self.name - # ) + def test_image_hidden(self): + # Test image is shown in list + output = self.openstack( + 'image list', + parse_output=True, + ) + self.assertIn( + self.name, + [img['Name'] for img in output], + ) + + # Hide the image and test image not show in the list + self.openstack('image set ' + '--hidden ' + self.name) + output = self.openstack( + 'image list', + parse_output=True, + ) + self.assertNotIn(self.name, [img['Name'] for img in output]) + + # Test image show in the list with flag + output = self.openstack( + 'image list', + parse_output=True, + ) + self.assertNotIn(self.name, [img['Name'] for img in output]) + + # Unhide the image and test image is again visible in regular list + self.openstack('image set ' + '--unhidden ' + self.name) + output = self.openstack( + 'image list', + parse_output=True, + ) + self.assertIn( + self.name, + [img['Name'] for img in output], + ) diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 4f5db9f00..cc2b0ed83 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -822,6 +822,8 @@ def test_image_list_long_option(self): 'Visibility', 'Protected', 'Project', + 'Hash Algorithm', + 'Hash Value', 'Tags', ) @@ -830,14 +832,16 @@ def test_image_list_long_option(self): ( self._image.id, self._image.name, - None, - None, - None, - None, - None, + self._image.disk_format, + self._image.container_format, + self._image.size, + self._image.checksum, + self._image.status, self._image.visibility, self._image.is_protected, self._image.owner_id, + self._image.hash_algo, + self._image.hash_value, format_columns.ListColumn(self._image.tags), ), ) @@ -1356,15 +1360,17 @@ def test_image_set_with_unexist_project(self): exceptions.CommandError, self.cmd.take_action, parsed_args ) - def test_image_set_bools1(self): + def test_image_set_bools_true(self): arglist = [ '--protected', '--private', + '--hidden', 'graven', ] verifylist = [ ('is_protected', True), ('visibility', 'private'), + ('is_hidden', True), ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1374,6 +1380,7 @@ def test_image_set_bools1(self): kwargs = { 'is_protected': True, 'visibility': 'private', + 'is_hidden': True, } # ImageManager.update(image, **kwargs) self.image_client.update_image.assert_called_with( @@ -1381,15 +1388,17 @@ def test_image_set_bools1(self): ) self.assertIsNone(result) - def test_image_set_bools2(self): + def test_image_set_bools_false(self): arglist = [ '--unprotected', '--public', + '--unhidden', 'graven', ] verifylist = [ ('is_protected', False), ('visibility', 'public'), + ('is_hidden', False), ('image', 'graven'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1399,6 +1408,7 @@ def test_image_set_bools2(self): kwargs = { 'is_protected': False, 'visibility': 'public', + 'is_hidden': False, } # ImageManager.update(image, **kwargs) self.image_client.update_image.assert_called_with( diff --git a/releasenotes/notes/add-image-options-dcbc4ead7822c495.yaml b/releasenotes/notes/add-image-options-dcbc4ead7822c495.yaml new file mode 100644 index 000000000..7a528d7c9 --- /dev/null +++ b/releasenotes/notes/add-image-options-dcbc4ead7822c495.yaml @@ -0,0 +1,4 @@ +--- +features: + - The ``os_hash_algo`` and ``os_hash_value image`` attributes are now shown + in the ``image list --long`` output. From 2177f07dfb658fb72b572e24f1797ef01598df4d Mon Sep 17 00:00:00 2001 From: Mridula Joshi Date: Tue, 16 Jan 2024 17:37:11 +0000 Subject: [PATCH 12/67] Adds CLI support for ``glance md-namespace-properties-delete`` This patch modifies the command to delete all metadef properties inside a namespace. This operation can be called by `image metadef property delete` Change-Id: Iff9bda0dddfa157be0438a66d1d05da7b0b437c3 Signed-off-by: Mridula Joshi --- doc/source/cli/data/glance.csv | 2 +- .../image/v2/metadef_properties.py | 11 ++++-- .../unit/image/v2/test_metadef_properties.py | 34 +++++++++++++------ ...adef-property-delete-1e1bb8410130d901.yaml | 5 +++ 4 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/add-image-metadef-property-delete-1e1bb8410130d901.yaml diff --git a/doc/source/cli/data/glance.csv b/doc/source/cli/data/glance.csv index 61cd6c1e5..277a14a13 100644 --- a/doc/source/cli/data/glance.csv +++ b/doc/source/cli/data/glance.csv @@ -27,7 +27,7 @@ md-namespace-delete,image metadef namespace delete,Delete specified metadata def md-namespace-import,,Import a metadata definitions namespace from file or standard input. md-namespace-list,image metadef namespace list,List metadata definitions namespaces. md-namespace-objects-delete,image metadef object delete,Delete all metadata definitions objects inside a specific namespace. -md-namespace-properties-delete,,Delete all metadata definitions property inside a specific namespace. +md-namespace-properties-delete,image metadef property delete,Delete all metadata definitions property inside a specific namespace. md-namespace-resource-type-list,image metadef resource type association list,List resource types associated to specific namespace. md-namespace-show,image metadef namespace show,Describe a specific metadata definitions namespace. md-namespace-tags-delete,,Delete all metadata definitions tags inside a specific namespace. diff --git a/openstackclient/image/v2/metadef_properties.py b/openstackclient/image/v2/metadef_properties.py index 40440c029..602777331 100644 --- a/openstackclient/image/v2/metadef_properties.py +++ b/openstackclient/image/v2/metadef_properties.py @@ -124,14 +124,21 @@ def get_parser(self, prog_name): parser.add_argument( "properties", metavar="", - nargs="+", - help=_("Metadef propert(ies) to delete (name)"), + nargs="*", + help=_( + "Metadef properties to delete (name) " + "(omit this argument to delete all properties in the namespace)" + ), ) return parser def take_action(self, parsed_args): image_client = self.app.client_manager.image + if not parsed_args.properties: + image_client.delete_all_metadef_properties(parsed_args.namespace) + return + result = 0 for prop in parsed_args.properties: try: diff --git a/openstackclient/tests/unit/image/v2/test_metadef_properties.py b/openstackclient/tests/unit/image/v2/test_metadef_properties.py index 274ed6635..e2f83d629 100644 --- a/openstackclient/tests/unit/image/v2/test_metadef_properties.py +++ b/openstackclient/tests/unit/image/v2/test_metadef_properties.py @@ -91,6 +91,7 @@ def setUp(self): super().setUp() self.cmd = metadef_properties.DeleteMetadefProperty(self.app, None) + self.image_client.delete_all_metadef_properties.return_value = None def test_metadef_property_delete(self): arglist = ['namespace', 'property'] @@ -100,6 +101,10 @@ def test_metadef_property_delete(self): result = self.cmd.take_action(parsed_args) self.assertIsNone(result) + self.image_client.delete_metadef_property.assert_called_with( + 'property', 'namespace', ignore_missing=False + ) + self.image_client.delete_all_metadef_properties.assert_not_called() def test_metadef_property_delete_missing_arguments(self): arglist = [] @@ -110,21 +115,13 @@ def test_metadef_property_delete_missing_arguments(self): arglist, [], ) - - arglist = ['namespace'] - self.assertRaises( - tests_utils.ParserException, - self.check_parser, - self.cmd, - arglist, - [], - ) + self.image_client.delete_all_metadef_properties.assert_not_called() + self.image_client.delete_metadef_property.assert_not_called() def test_metadef_property_delete_exception(self): arglist = ['namespace', 'property'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.image_client.delete_metadef_property.side_effect = ( sdk_exceptions.ResourceNotFound ) @@ -132,6 +129,23 @@ def test_metadef_property_delete_exception(self): self.assertRaises( exceptions.CommandError, self.cmd.take_action, parsed_args ) + self.image_client.delete_metadef_property.assert_called_with( + 'property', 'namespace', ignore_missing=False + ) + self.image_client.delete_all_metadef_properties.assert_not_called() + + def test_metadef_property_delete_all(self): + arglist = ['namespace'] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + self.image_client.delete_all_metadef_properties.assert_called_with( + 'namespace' + ) + self.image_client.delete_metadef_property.assert_not_called() class TestMetadefPropertyList(image_fakes.TestImagev2): diff --git a/releasenotes/notes/add-image-metadef-property-delete-1e1bb8410130d901.yaml b/releasenotes/notes/add-image-metadef-property-delete-1e1bb8410130d901.yaml new file mode 100644 index 000000000..09aff5905 --- /dev/null +++ b/releasenotes/notes/add-image-metadef-property-delete-1e1bb8410130d901.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The ``image property delete`` command will now delete all properties in + the provided namespace if no property is provided. From 5feaa952ad8db78f0b1651dbfc3575d53d50adff Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 18 Aug 2025 11:56:18 +0100 Subject: [PATCH 13/67] compute: Fix flavor create --id auto This was inadvertently broken during the switch from novaclient to SDK. Fix it for now but also deprecate it since it is an unnecessary alias. Change-Id: Iaf136d82e00defc86e57ae4ea7e848246f2ade2c Signed-off-by: Stephen Finucane Closes-bug: #2120833 --- openstackclient/compute/v2/flavor.py | 15 ++++++++++++++- .../tests/unit/compute/v2/test_flavor.py | 2 +- .../notes/flavor-id-auto-e21157f97dc1d7f2.yaml | 6 ++++++ 3 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/flavor-id-auto-e21157f97dc1d7f2.yaml diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 76635e12b..c0e043e8f 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -156,12 +156,25 @@ def take_action(self, parsed_args): msg = _("--project is only allowed with --private") raise exceptions.CommandError(msg) + flavor_id = parsed_args.id + if parsed_args.id == 'auto': + # novaclient aliased 'auto' to mean "generate a UUID for me": we + # do the same to avoid breaking existing users + flavor_id = None + + msg = _( + "Passing '--id auto' is deprecated. Nova will automatically " + "assign a UUID-like ID if no ID is provided. Omit the '--id' " + "parameter instead." + ) + self.log.warning(msg) + args = { 'name': parsed_args.name, 'ram': parsed_args.ram, 'vcpus': parsed_args.vcpus, 'disk': parsed_args.disk, - 'id': parsed_args.id, + 'id': flavor_id, 'ephemeral': parsed_args.ephemeral, 'swap': parsed_args.swap, 'rxtx_factor': parsed_args.rxtx_factor, diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index fa8d5ad56..f7599e0a4 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -245,7 +245,7 @@ def test_flavor_create_other_options(self): 'ram': self.flavor.ram, 'vcpus': self.flavor.vcpus, 'disk': self.flavor.disk, - 'id': 'auto', + 'id': None, 'ephemeral': self.flavor.ephemeral, 'swap': self.flavor.swap, 'rxtx_factor': self.flavor.rxtx_factor, diff --git a/releasenotes/notes/flavor-id-auto-e21157f97dc1d7f2.yaml b/releasenotes/notes/flavor-id-auto-e21157f97dc1d7f2.yaml new file mode 100644 index 000000000..96b56b8df --- /dev/null +++ b/releasenotes/notes/flavor-id-auto-e21157f97dc1d7f2.yaml @@ -0,0 +1,6 @@ +--- +deprecations: + - | + The ``--id auto`` alias for the ``flavor create`` command is deprecated + for removal. Omit the option entirely to ensure the server creates the + ID for you. From a312e9cdad626add8ea40e92844f867a9e586751 Mon Sep 17 00:00:00 2001 From: Artem Goncharov Date: Wed, 7 Jun 2023 15:43:16 +0200 Subject: [PATCH 14/67] Adopt sdk_fakes for compute.test_flavor Use sdk_fakes inside test_flavor. The only left fake is for flavor_access, for which there is no resource in SDK. Change-Id: I8fcfb734eb45308b80aa1478c2935c9881fee928 Signed-off-by: Artem Goncharov --- .../tests/unit/compute/v2/test_flavor.py | 331 +++++++++--------- 1 file changed, 158 insertions(+), 173 deletions(-) diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index fa8d5ad56..e7e6bab70 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -16,12 +16,13 @@ from openstack.compute.v2 import flavor as _flavor from openstack import exceptions as sdk_exceptions +from openstack.identity.v3 import project as _project +from openstack.test import fakes as sdk_fakes from osc_lib.cli import format_columns from osc_lib import exceptions from openstackclient.compute.v2 import flavor from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes -from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes from openstackclient.tests.unit import utils as tests_utils @@ -34,59 +35,60 @@ def setUp(self): class TestFlavorCreate(TestFlavor): - flavor = compute_fakes.create_one_flavor(attrs={'links': 'flavor-links'}) - project = identity_fakes.FakeProject.create_one_project() - - columns = ( - 'OS-FLV-DISABLED:disabled', - 'OS-FLV-EXT-DATA:ephemeral', - 'description', - 'disk', - 'id', - 'name', - 'os-flavor-access:is_public', - 'properties', - 'ram', - 'rxtx_factor', - 'swap', - 'vcpus', - ) - - data = ( - flavor.is_disabled, - flavor.ephemeral, - flavor.description, - flavor.disk, - flavor.id, - flavor.name, - flavor.is_public, - format_columns.DictColumn(flavor.extra_specs), - flavor.ram, - flavor.rxtx_factor, - flavor.swap, - flavor.vcpus, - ) - data_private = ( - flavor.is_disabled, - flavor.ephemeral, - flavor.description, - flavor.disk, - flavor.id, - flavor.name, - False, - format_columns.DictColumn(flavor.extra_specs), - flavor.ram, - flavor.rxtx_factor, - flavor.swap, - flavor.vcpus, - ) - def setUp(self): super().setUp() - # Return a project + self.flavor = sdk_fakes.generate_fake_resource( + _flavor.Flavor, links='flavor-links' + ) + self.project = sdk_fakes.generate_fake_resource(_project.Project) + + self.columns = ( + 'OS-FLV-DISABLED:disabled', + 'OS-FLV-EXT-DATA:ephemeral', + 'description', + 'disk', + 'id', + 'name', + 'os-flavor-access:is_public', + 'properties', + 'ram', + 'rxtx_factor', + 'swap', + 'vcpus', + ) + self.data = ( + self.flavor.is_disabled, + self.flavor.ephemeral, + self.flavor.description, + self.flavor.disk, + self.flavor.id, + self.flavor.name, + self.flavor.is_public, + format_columns.DictColumn(self.flavor.extra_specs), + self.flavor.ram, + self.flavor.rxtx_factor, + self.flavor.swap, + self.flavor.vcpus, + ) + self.data_private = ( + self.flavor.is_disabled, + self.flavor.ephemeral, + self.flavor.description, + self.flavor.disk, + self.flavor.id, + self.flavor.name, + False, + format_columns.DictColumn(self.flavor.extra_specs), + self.flavor.ram, + self.flavor.rxtx_factor, + self.flavor.swap, + self.flavor.vcpus, + ) + self.projects_mock.get.return_value = self.project self.compute_client.create_flavor.return_value = self.flavor + self.cmd = flavor.CreateFlavor(self.app, None) def test_flavor_create_default_options(self): @@ -233,7 +235,7 @@ def test_flavor_create_other_options(self): ('vcpus', self.flavor.vcpus), ('rxtx_factor', self.flavor.rxtx_factor), ('public', False), - ('description', 'description'), + ('description', self.flavor.description), ('project', self.project.id), ('properties', {'key1': 'value1', 'key2': 'value2'}), ('name', self.flavor.name), @@ -328,7 +330,7 @@ def test_flavor_create_with_description(self): str(self.flavor.vcpus), '--rxtx-factor', str(self.flavor.rxtx_factor), - '--private', + '--public', '--description', 'fake description', self.flavor.name, @@ -341,7 +343,7 @@ def test_flavor_create_with_description(self): ('swap', self.flavor.swap), ('vcpus', self.flavor.vcpus), ('rxtx_factor', self.flavor.rxtx_factor), - ('public', False), + ('public', True), ('description', 'fake description'), ('name', self.flavor.name), ] @@ -358,14 +360,14 @@ def test_flavor_create_with_description(self): 'ephemeral': self.flavor.ephemeral, 'swap': self.flavor.swap, 'rxtx_factor': self.flavor.rxtx_factor, - 'is_public': self.flavor.is_public, + 'is_public': True, 'description': 'fake description', } self.compute_client.create_flavor.assert_called_once_with(**args) self.assertEqual(self.columns, columns) - self.assertCountEqual(self.data_private, data) + self.assertCountEqual(self.data, data) def test_flavor_create_with_description_pre_v255(self): self.set_compute_api_version('2.54') @@ -395,11 +397,13 @@ def test_flavor_create_with_description_pre_v255(self): class TestFlavorDelete(TestFlavor): - flavors = compute_fakes.create_flavors(count=2) - def setUp(self): super().setUp() + self.flavors = list( + sdk_fakes.generate_fake_resources(_flavor.Flavor, 2) + ) + self.compute_client.delete_flavor.return_value = None self.cmd = flavor.DeleteFlavor(self.app, None) @@ -474,51 +478,50 @@ def test_multi_flavors_delete_with_exception(self): class TestFlavorList(TestFlavor): - _flavor = compute_fakes.create_one_flavor() - - columns = ( - 'ID', - 'Name', - 'RAM', - 'Disk', - 'Ephemeral', - 'VCPUs', - 'Is Public', - ) - columns_long = columns + ('Swap', 'RXTX Factor', 'Properties') - - data = ( - ( - _flavor.id, - _flavor.name, - _flavor.ram, - _flavor.disk, - _flavor.ephemeral, - _flavor.vcpus, - _flavor.is_public, - ), - ) - data_long = ( - data[0] - + ( - _flavor.swap, - _flavor.rxtx_factor, - format_columns.DictColumn(_flavor.extra_specs), - ), - ) - def setUp(self): super().setUp() - self.api_mock = mock.Mock() - self.api_mock.side_effect = [ - [self._flavor], - [], - ] + self._flavor = sdk_fakes.generate_fake_resource( + _flavor.Flavor, extra_specs={'property': 'value'} + ) - self.compute_client.flavors = self.api_mock + self.columns = ( + 'ID', + 'Name', + 'RAM', + 'Disk', + 'Ephemeral', + 'VCPUs', + 'Is Public', + ) + self.columns_long = self.columns + ( + 'Swap', + 'RXTX Factor', + 'Properties', + ) + + self.data = ( + ( + self._flavor.id, + self._flavor.name, + self._flavor.ram, + self._flavor.disk, + self._flavor.ephemeral, + self._flavor.vcpus, + self._flavor.is_public, + ), + ) + self.data_long = ( + self.data[0] + + ( + self._flavor.swap, + self._flavor.rxtx_factor, + format_columns.DictColumn(self._flavor.extra_specs), + ), + ) + + self.compute_client.flavors.side_effect = [[self._flavor], []] - # Get the command object to test self.cmd = flavor.ListFlavor(self.app, None) def test_flavor_list_no_options(self): @@ -653,7 +656,9 @@ def test_flavor_list_long(self): def test_flavor_list_long_no_extra_specs(self): # use flavor with no extra specs for this test - flavor = compute_fakes.create_one_flavor(attrs={"extra_specs": {}}) + flavor = sdk_fakes.generate_fake_resource( + _flavor.Flavor, extra_specs={} + ) self.data = ( ( flavor.id, @@ -673,12 +678,8 @@ def test_flavor_list_long_no_extra_specs(self): format_columns.DictColumn(flavor.extra_specs), ), ) - self.api_mock.side_effect = [ - [flavor], - [], - ] - self.compute_client.flavors = self.api_mock + self.compute_client.flavors.side_effect = [[flavor], []] self.compute_client.fetch_flavor_extra_specs = mock.Mock( return_value=None ) @@ -744,17 +745,15 @@ def test_flavor_list_min_disk_min_ram(self): class TestFlavorSet(TestFlavor): - # Return value of self.compute_client.find_flavor(). - flavor = compute_fakes.create_one_flavor( - attrs={'os-flavor-access:is_public': False} - ) - project = identity_fakes.FakeProject.create_one_project() - def setUp(self): super().setUp() + self.flavor = sdk_fakes.generate_fake_resource( + _flavor.Flavor, is_public=False, extra_specs={'property': 'value'} + ) + self.project = sdk_fakes.generate_fake_resource(_project.Project) + self.compute_client.find_flavor.return_value = self.flavor - # Return a project self.projects_mock.get.return_value = self.project self.cmd = flavor.SetFlavor(self.app, None) @@ -960,46 +959,42 @@ def test_flavor_set_description_using_name_pre_v255(self): class TestFlavorShow(TestFlavor): - # Return value of self.compute_client.find_flavor(). - flavor_access = compute_fakes.create_one_flavor_access() - flavor = compute_fakes.create_one_flavor() - - columns = ( - 'OS-FLV-DISABLED:disabled', - 'OS-FLV-EXT-DATA:ephemeral', - 'access_project_ids', - 'description', - 'disk', - 'id', - 'name', - 'os-flavor-access:is_public', - 'properties', - 'ram', - 'rxtx_factor', - 'swap', - 'vcpus', - ) - - data = ( - flavor.is_disabled, - flavor.ephemeral, - None, - flavor.description, - flavor.disk, - flavor.id, - flavor.name, - flavor.is_public, - format_columns.DictColumn(flavor.extra_specs), - flavor.ram, - flavor.rxtx_factor, - flavor.swap, - flavor.vcpus, - ) - def setUp(self): super().setUp() - # Return value of _find_resource() + self.flavor_access = compute_fakes.create_one_flavor_access() + self.flavor = sdk_fakes.generate_fake_resource(_flavor.Flavor) + + self.columns = ( + 'OS-FLV-DISABLED:disabled', + 'OS-FLV-EXT-DATA:ephemeral', + 'access_project_ids', + 'description', + 'disk', + 'id', + 'name', + 'os-flavor-access:is_public', + 'properties', + 'ram', + 'rxtx_factor', + 'swap', + 'vcpus', + ) + self.data = ( + self.flavor.is_disabled, + self.flavor.ephemeral, + None, + self.flavor.description, + self.flavor.disk, + self.flavor.id, + self.flavor.name, + self.flavor.is_public, + format_columns.DictColumn(self.flavor.extra_specs), + self.flavor.ram, + self.flavor.rxtx_factor, + self.flavor.swap, + self.flavor.vcpus, + ) self.compute_client.find_flavor.return_value = self.flavor self.compute_client.get_flavor_access.return_value = [ self.flavor_access @@ -1035,10 +1030,8 @@ def test_public_flavor_show(self): self.assertCountEqual(self.data, data) def test_private_flavor_show(self): - private_flavor = compute_fakes.create_one_flavor( - attrs={ - 'os-flavor-access:is_public': False, - } + private_flavor = sdk_fakes.generate_fake_resource( + _flavor.Flavor, is_public=False ) self.compute_client.find_flavor.return_value = private_flavor @@ -1077,23 +1070,18 @@ def test_private_flavor_show(self): class TestFlavorUnset(TestFlavor): - # Return value of self.compute_client.find_flavor(). - flavor = compute_fakes.create_one_flavor( - attrs={'os-flavor-access:is_public': False} - ) - project = identity_fakes.FakeProject.create_one_project() - def setUp(self): super().setUp() + self.flavor = sdk_fakes.generate_fake_resource( + _flavor.Flavor, is_public=False + ) + self.project = sdk_fakes.generate_fake_resource(_project.Project) + self.compute_client.find_flavor.return_value = self.flavor - # Return a project self.projects_mock.get.return_value = self.project - self.cmd = flavor.UnsetFlavor(self.app, None) - self.mock_shortcut = ( - self.compute_client.delete_flavor_extra_specs_property - ) + self.cmd = flavor.UnsetFlavor(self.app, None) def test_flavor_unset_property(self): arglist = ['--property', 'property', 'baremetal'] @@ -1107,7 +1095,9 @@ def test_flavor_unset_property(self): self.compute_client.find_flavor.assert_called_with( parsed_args.flavor, get_extra_specs=True, ignore_missing=False ) - self.mock_shortcut.assert_called_with(self.flavor.id, 'property') + self.compute_client.delete_flavor_extra_specs_property.assert_called_with( + self.flavor.id, 'property' + ) self.compute_client.flavor_remove_tenant_access.assert_not_called() self.assertIsNone(result) @@ -1126,21 +1116,16 @@ def test_flavor_unset_properties(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) + self.compute_client.find_flavor.assert_called_with( parsed_args.flavor, get_extra_specs=True, ignore_missing=False ) - calls = [ - mock.call(self.flavor.id, 'property1'), - mock.call(self.flavor.id, 'property2'), - ] - self.mock_shortcut.assert_has_calls(calls) - - # A bit tricky way to ensure we do not unset other properties - calls.append(mock.call(self.flavor.id, 'property')) - self.assertRaises( - AssertionError, self.mock_shortcut.assert_has_calls, calls + self.compute_client.delete_flavor_extra_specs_property.assert_has_calls( + [ + mock.call(self.flavor.id, 'property1'), + mock.call(self.flavor.id, 'property2'), + ] ) - self.compute_client.flavor_remove_tenant_access.assert_not_called() def test_flavor_unset_project(self): From dbddbf976008cd8aba31a787cc1f043952e2ef94 Mon Sep 17 00:00:00 2001 From: Rajesh Tailor Date: Mon, 23 Jun 2025 13:17:30 +0530 Subject: [PATCH 15/67] Fix microversion 2.96 This change fixes missing conditional logic for microversion 2.96 which adds `pinned_availability_zone` field to `openstack server list` output. Change-Id: I1e398bb3379fa6443b0a44db76baaf6241a945e7 Signed-off-by: Rajesh Tailor --- openstackclient/compute/v2/server.py | 23 ++- .../tests/unit/compute/v2/test_server.py | 165 ++++++++++++++++-- 2 files changed, 168 insertions(+), 20 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e14392a53..28c28b194 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -183,9 +183,13 @@ def _prep_server_detail(compute_client, image_client, server, *, refresh=True): 'updated_at': 'updated', 'user_data': 'OS-EXT-SRV-ATTR:user_data', 'vm_state': 'OS-EXT-STS:vm_state', - 'pinned_availability_zone': 'pinned_availability_zone', 'scheduler_hints': 'scheduler_hints', } + # NOTE(ratailor): microversion 2.96 introduces + # pinned_availability_zone support + if sdk_utils.supports_microversion(compute_client, '2.96'): + column_map['pinned_availability_zone'] = 'pinned_availability_zone' + # Some columns returned by openstacksdk should not be shown because they're # either irrelevant or duplicates ignored_columns = { @@ -240,6 +244,11 @@ def _prep_server_detail(compute_client, image_client, server, *, refresh=True): if not sdk_utils.supports_microversion(compute_client, '2.100'): info.pop('scheduler_hints', None) + # NOTE(ratailor): microversion 2.96 introduces + # pinned_availability_zone support + if not sdk_utils.supports_microversion(compute_client, '2.96'): + info.pop('pinned_availability_zone', None) + # Convert the image blob to a name image_info = info.get('image', {}) if image_info and any(image_info.values()): @@ -2838,18 +2847,19 @@ def take_action(self, parsed_args): if parsed_args.long: columns += ( 'availability_zone', - 'pinned_availability_zone', 'hypervisor_hostname', 'metadata', 'scheduler_hints', ) column_headers += ( 'Availability Zone', - 'Pinned Availability Zone', 'Host', 'Properties', 'Scheduler Hints', ) + if sdk_utils.supports_microversion(compute_client, '2.96'): + columns += ('pinned_availability_zone',) + column_headers += ('Pinned Availability Zone',) if parsed_args.all_projects: columns += ('project_id',) @@ -2887,10 +2897,11 @@ def take_action(self, parsed_args): column_headers += ('Availability Zone',) if c in ( 'pinned_availability_zone', - "Pinned Availability Zone", + 'Pinned Availability Zone', ): - columns += ('Pinned Availability Zone',) - column_headers += ('Pinned Availability Zone',) + if sdk_utils.supports_microversion(compute_client, '2.96'): + columns += ('pinned_availability_zone',) + column_headers += ('Pinned Availability Zone',) if c in ('Host', "host"): columns += ('hypervisor_hostname',) column_headers += ('Host',) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 86951360b..82205bc18 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1212,7 +1212,6 @@ class TestServerCreate(TestServer): 'locked', 'locked_reason', 'name', - 'pinned_availability_zone', 'progress', 'project_id', 'properties', @@ -1261,7 +1260,6 @@ def datalist(self): None, # locked None, # locked_reason self.server.name, - None, # pinned_availability_zone None, # progress None, # project_id format_columns.DictColumn({}), # properties @@ -4583,7 +4581,6 @@ class _TestServerList(TestServer): 'Flavor Name', 'Flavor ID', 'Availability Zone', - 'Pinned Availability Zone', 'Host', 'Properties', 'Scheduler Hints', @@ -4732,7 +4729,6 @@ def test_server_list_long_option(self): self.flavor.name, s.flavor['id'], getattr(s, 'availability_zone'), - getattr(s, 'pinned_availability_zone', ''), server.HostColumn(getattr(s, 'hypervisor_hostname')), format_columns.DictColumn(s.metadata), format_columns.DictListColumn(None), @@ -4809,8 +4805,6 @@ def test_server_list_column_option(self): '-c', 'Availability Zone', '-c', - 'Pinned Availability Zone', - '-c', 'Host', '-c', 'Properties', @@ -4835,7 +4829,6 @@ def test_server_list_column_option(self): self.assertIn('Image ID', columns) self.assertIn('Flavor ID', columns) self.assertIn('Availability Zone', columns) - self.assertIn('Pinned Availability Zone', columns) self.assertIn('Host', columns) self.assertIn('Properties', columns) self.assertIn('Scheduler Hints', columns) @@ -5249,7 +5242,6 @@ def test_server_list_long_with_host_status_v216(self): self.flavor.name, s.flavor['id'], getattr(s, 'availability_zone'), - getattr(s, 'pinned_availability_zone', ''), server.HostColumn(getattr(s, 'hypervisor_hostname')), format_columns.DictColumn(s.metadata), format_columns.DictListColumn(s.scheduler_hints), @@ -5305,7 +5297,6 @@ def test_server_list_long_with_host_status_v216(self): self.flavor.name, s.flavor['id'], getattr(s, 'availability_zone'), - getattr(s, 'pinned_availability_zone', ''), server.HostColumn(getattr(s, 'hypervisor_hostname')), format_columns.DictColumn(s.metadata), format_columns.DictListColumn(s.scheduler_hints), @@ -5343,7 +5334,6 @@ class TestServerListV273(_TestServerList): 'Image ID', 'Flavor', 'Availability Zone', - 'Pinned Availability Zone', 'Host', 'Properties', 'Scheduler Hints', @@ -5541,6 +5531,157 @@ def test_server_list_v269_with_partial_constructs(self): self.assertEqual(expected_row, partial_server) +class TestServerListV296(_TestServerList): + columns = ( + 'ID', + 'Name', + 'Status', + 'Networks', + 'Image', + 'Flavor', + ) + columns_long = ( + 'ID', + 'Name', + 'Status', + 'Task State', + 'Power State', + 'Networks', + 'Image Name', + 'Image ID', + 'Flavor', + 'Availability Zone', + 'Host', + 'Properties', + 'Scheduler Hints', + 'Pinned Availability Zone', + ) + + def setUp(self): + super().setUp() + self.set_compute_api_version('2.96') + + Image = collections.namedtuple('Image', 'id name') + self.image_client.images.return_value = [ + Image(id=s.image['id'], name=self.image.name) + # Image will be an empty string if boot-from-volume + for s in self.servers + if s.image + ] + + Flavor = collections.namedtuple('Flavor', 'id name') + self.compute_client.flavors.return_value = [ + Flavor(id=s.flavor['id'], name=self.flavor.name) + for s in self.servers + ] + + self.data = tuple( + ( + s.id, + s.name, + s.status, + server.AddressesColumn(s.addresses), + # Image will be an empty string if boot-from-volume + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, + self.flavor.name, + ) + for s in self.servers + ) + + def test_server_list_long_option(self): + self.data = tuple( + ( + s.id, + s.name, + s.status, + getattr(s, 'task_state'), + server.PowerStateColumn(getattr(s, 'power_state')), + server.AddressesColumn(s.addresses), + # Image will be an empty string if boot-from-volume + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, + self.flavor.name, + getattr(s, 'availability_zone'), + server.HostColumn(getattr(s, 'hypervisor_hostname')), + format_columns.DictColumn(s.metadata), + format_columns.DictListColumn(None), + getattr(s, 'pinned_availability_zone', ''), + ) + for s in self.servers + ) + arglist = [ + '--long', + ] + verifylist = [ + ('all_projects', False), + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.compute_client.servers.assert_called_with(**self.kwargs) + image_ids = {s.image['id'] for s in self.servers if s.image} + self.image_client.images.assert_called_once_with( + id=f'in:{",".join(image_ids)}', + ) + self.compute_client.flavors.assert_called_once_with(is_public=None) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data, tuple(data)) + + def test_server_list_column_option(self): + arglist = [ + '-c', + 'Project ID', + '-c', + 'User ID', + '-c', + 'Created At', + '-c', + 'Security Groups', + '-c', + 'Task State', + '-c', + 'Power State', + '-c', + 'Image ID', + '-c', + 'Flavor ID', + '-c', + 'Availability Zone', + '-c', + 'Host', + '-c', + 'Properties', + '-c', + 'Scheduler Hints', + '-c', + 'Pinned Availability Zone', + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute_client.servers.assert_called_with(**self.kwargs) + self.assertIn('Project ID', columns) + self.assertIn('User ID', columns) + self.assertIn('Created At', columns) + self.assertIn('Security Groups', columns) + self.assertIn('Task State', columns) + self.assertIn('Power State', columns) + self.assertIn('Image ID', columns) + self.assertIn('Flavor ID', columns) + self.assertIn('Availability Zone', columns) + self.assertIn('Pinned Availability Zone', columns) + self.assertIn('Host', columns) + self.assertIn('Properties', columns) + self.assertIn('Scheduler Hints', columns) + self.assertCountEqual(columns, set(columns)) + + class TestServerAction(compute_fakes.TestComputev2): def run_method_with_sdk_servers(self, method_name, server_count): servers = compute_fakes.create_servers(count=server_count) @@ -8533,7 +8674,6 @@ def setUp(self): 'locked', 'locked_reason', 'name', - 'pinned_availability_zone', 'progress', 'project_id', 'properties', @@ -8583,7 +8723,6 @@ def setUp(self): None, # locked None, # locked_reason self.server.name, - None, # pinned_availability_zone None, # progress 'tenant-id-xxx', # project_id format_columns.DictColumn({}), # properties @@ -9522,7 +9661,6 @@ def test_prep_server_detail(self): 'locked': None, 'locked_reason': None, 'name': _server.name, - 'pinned_availability_zone': None, 'progress': None, 'project_id': 'tenant-id-xxx', 'properties': format_columns.DictColumn({}), @@ -9609,7 +9747,6 @@ def test_prep_server_detail_v247(self): 'locked': None, 'locked_reason': None, 'name': _server.name, - 'pinned_availability_zone': None, 'progress': None, 'project_id': 'tenant-id-xxx', 'properties': format_columns.DictColumn({}), From 94e447af80ee3e5197e588946e13b2574c49e752 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 20 Aug 2025 17:41:21 +0100 Subject: [PATCH 16/67] tests: Remove use of namedtuple Change-Id: I19a272ffd260bab263dd63cb920802b792e192eb Signed-off-by: Stephen Finucane --- .../tests/unit/compute/v2/test_server.py | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 82205bc18..1d55d1efe 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -13,7 +13,6 @@ # under the License. import base64 -import collections import getpass import json import tempfile @@ -21,9 +20,11 @@ import uuid import iso8601 +from openstack.compute.v2 import flavor as _flavor from openstack.compute.v2 import server as _server from openstack.compute.v2 import server_group as _server_group from openstack import exceptions as sdk_exceptions +from openstack.image.v2 import image as _image from openstack.test import fakes as sdk_fakes from osc_lib.cli import format_columns from osc_lib import exceptions @@ -4650,17 +4651,19 @@ class TestServerList(_TestServerList): def setUp(self): super().setUp() - Image = collections.namedtuple('Image', 'id name') self.image_client.images.return_value = [ - Image(id=s.image['id'], name=self.image.name) + sdk_fakes.generate_fake_resource( + _image.Image, id=s.image['id'], name=self.image.name + ) # Image will be an empty string if boot-from-volume for s in self.servers if s.image ] - Flavor = collections.namedtuple('Flavor', 'id name') self.compute_client.flavors.return_value = [ - Flavor(id=s.flavor['id'], name=self.flavor.name) + sdk_fakes.generate_fake_resource( + _flavor.Flavor, id=s.flavor['id'], name=self.flavor.name + ) for s in self.servers ] @@ -5273,9 +5276,10 @@ def test_server_list_long_with_host_status_v216(self): self.compute_client.servers.return_value = servers # Make sure the returned image and flavor IDs match the servers. - Image = collections.namedtuple('Image', 'id name') self.image_client.images.return_value = [ - Image(id=s.image['id'], name=self.image.name) + sdk_fakes.generate_fake_resource( + _image.Image, id=s.image['id'], name=self.image.name + ) # Image will be an empty string if boot-from-volume for s in servers if s.image @@ -5358,9 +5362,10 @@ def setUp(self): self.servers = self.setup_sdk_servers_mock(3) self.compute_client.servers.return_value = self.servers - Image = collections.namedtuple('Image', 'id name') self.image_client.images.return_value = [ - Image(id=s.image['id'], name=self.image.name) + sdk_fakes.generate_fake_resource( + _image.Image, id=s.image['id'], name=self.image.name + ) # Image will be an empty string if boot-from-volume for s in self.servers if s.image @@ -5561,17 +5566,19 @@ def setUp(self): super().setUp() self.set_compute_api_version('2.96') - Image = collections.namedtuple('Image', 'id name') self.image_client.images.return_value = [ - Image(id=s.image['id'], name=self.image.name) + sdk_fakes.generate_fake_resource( + _image.Image, id=s.image['id'], name=self.image.name + ) # Image will be an empty string if boot-from-volume for s in self.servers if s.image ] - Flavor = collections.namedtuple('Flavor', 'id name') self.compute_client.flavors.return_value = [ - Flavor(id=s.flavor['id'], name=self.flavor.name) + sdk_fakes.generate_fake_resource( + _flavor.Flavor, id=s.flavor['id'], name=self.flavor.name + ) for s in self.servers ] From 68d1d01b2ae5b5726a947e03dbc0c1b019a8898b Mon Sep 17 00:00:00 2001 From: minkyukim Date: Sun, 17 Aug 2025 22:09:46 +0900 Subject: [PATCH 17/67] tests: Simplify catalog functional tests Combine multiple test cases into a single test case, in order to speed up execution. Change-Id: Idcfd0c8c5b7418046601d222248c0cd16886e079 Signed-off-by: minkyukim --- .../functional/identity/v3/test_catalog.py | 65 ++++++++++++------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/openstackclient/tests/functional/identity/v3/test_catalog.py b/openstackclient/tests/functional/identity/v3/test_catalog.py index f4089b7eb..429f94ac3 100644 --- a/openstackclient/tests/functional/identity/v3/test_catalog.py +++ b/openstackclient/tests/functional/identity/v3/test_catalog.py @@ -10,35 +10,54 @@ # License for the specific language governing permissions and limitations # under the License. + from openstackclient.tests.functional.identity.v3 import common class CatalogTests(common.IdentityTests): - def test_catalog_list(self): + """Functional tests for catalog commands""" + + def test_catalog(self): + """Test catalog list and show functionality""" + # Create a test service for isolated testing + _dummy_service_name = self._create_dummy_service(add_clean_up=True) + + # list catalogs raw_output = self.openstack('catalog list') items = self.parse_listing(raw_output) self.assert_table_structure(items, ['Name', 'Type', 'Endpoints']) - def test_catalog_show(self): - """test catalog show command - - The output example: - +-----------+----------------------------------------+ - | Field | Value | - +-----------+----------------------------------------+ - | endpoints | test1 | - | | public: http://localhost:5000/v2.0 | - | | test1 | - | | internal: http://localhost:5000/v2.0 | - | | test1 | - | | admin: http://localhost:35357/v2.0 | - | | | - | id | e1e68b5ba21a43a39ff1cf58e736c3aa | - | name | keystone | - | type | identity | - +-----------+----------------------------------------+ - """ - raw_output = self.openstack('catalog show {}'.format('identity')) + # Verify created service appears in catalog + service_names = [ + item.get('Name') for item in items if item.get('Name') + ] + self.assertIn( + _dummy_service_name, + service_names, + "Created dummy service should be present in catalog", + ) + + # show service (by name) + raw_output = self.openstack(f'catalog show {_dummy_service_name}') items = self.parse_show(raw_output) - # items may have multiple endpoint urls with empty key - self.assert_show_fields(items, ['endpoints', 'name', 'type', '', 'id']) + self.assert_show_fields(items, ['endpoints', 'name', 'type', 'id']) + + # Extract the type from the dummy service + _dummy_service_type = next( + (item['type'] for item in items if 'type' in item), None + ) + + # show service (by type) + raw_output = self.openstack(f'catalog show {_dummy_service_type}') + items = self.parse_show(raw_output) + self.assert_show_fields(items, ['endpoints', 'name', 'type', 'id']) + + # show service (non-existent) + result = self.openstack( + 'catalog show nonexistent-service-xyz', fail_ok=True + ) + self.assertEqual( + '', + result.strip(), + "Non-existent service should return empty result", + ) From 5f1ffe742cd8afb2c93927ff606d88a90fd6073e Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 29 Aug 2025 13:31:55 +0100 Subject: [PATCH 18/67] volume: Temporarily ignore Volume.backup_id column We really need a better way to do this. Change-Id: I631748e2dfe3c136156d7987eab952370a88d35b Signed-off-by: Stephen Finucane Related: https://review.opendev.org/c/openstack/openstacksdk/+/958801 --- openstackclient/volume/v3/volume.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openstackclient/volume/v3/volume.py b/openstackclient/volume/v3/volume.py index 54dd5c754..d43a77d7b 100644 --- a/openstackclient/volume/v3/volume.py +++ b/openstackclient/volume/v3/volume.py @@ -107,6 +107,8 @@ def _format_volume(volume: _volume.Volume) -> dict[str, ty.Any]: 'os-volume-replication:extended_status', # unnecessary columns 'links', + # temporarily ignored columns + 'backup_id', } optional_columns = { # only present if part of a consistency group From e7554603acfb74a71386aa34e3c9bea3840a0861 Mon Sep 17 00:00:00 2001 From: Rajesh Tailor Date: Fri, 29 Aug 2025 12:15:39 +0530 Subject: [PATCH 19/67] Fix microversion 2.100 This change fixes missing conditional logic for microversion 2.100 which adds support for showing `scheduler_hints` field to `openstack server list --long` output. Change-Id: I2820e02a91deb73850f37dc737dbec79dea99e8d Signed-off-by: Rajesh Tailor --- openstackclient/compute/v2/server.py | 28 ++- .../tests/unit/compute/v2/test_server.py | 161 ++++++++++++++++-- 2 files changed, 170 insertions(+), 19 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 28c28b194..5ed122e9b 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -183,13 +183,17 @@ def _prep_server_detail(compute_client, image_client, server, *, refresh=True): 'updated_at': 'updated', 'user_data': 'OS-EXT-SRV-ATTR:user_data', 'vm_state': 'OS-EXT-STS:vm_state', - 'scheduler_hints': 'scheduler_hints', } # NOTE(ratailor): microversion 2.96 introduces # pinned_availability_zone support if sdk_utils.supports_microversion(compute_client, '2.96'): column_map['pinned_availability_zone'] = 'pinned_availability_zone' + # NOTE(ratailor): microversion 2.100 introduces + # scheduler_hints support + if sdk_utils.supports_microversion(compute_client, '2.100'): + column_map['scheduler_hints'] = 'scheduler_hints' + # Some columns returned by openstacksdk should not be shown because they're # either irrelevant or duplicates ignored_columns = { @@ -335,10 +339,11 @@ def _prep_server_detail(compute_client, image_client, server, *, refresh=True): info['OS-EXT-STS:power_state'] ) - if 'scheduler_hints' in info: - info['scheduler_hints'] = format_columns.DictListColumn( - info.pop('scheduler_hints', {}), - ) + if sdk_utils.supports_microversion(compute_client, '2.100'): + if 'scheduler_hints' in info: + info['scheduler_hints'] = format_columns.DictListColumn( + info.pop('scheduler_hints', {}), + ) return info @@ -2849,18 +2854,20 @@ def take_action(self, parsed_args): 'availability_zone', 'hypervisor_hostname', 'metadata', - 'scheduler_hints', ) column_headers += ( 'Availability Zone', 'Host', 'Properties', - 'Scheduler Hints', ) if sdk_utils.supports_microversion(compute_client, '2.96'): columns += ('pinned_availability_zone',) column_headers += ('Pinned Availability Zone',) + if sdk_utils.supports_microversion(compute_client, '2.100'): + columns += ('scheduler_hints',) + column_headers += ('Scheduler Hints',) + if parsed_args.all_projects: columns += ('project_id',) column_headers += ('Project ID',) @@ -2912,8 +2919,11 @@ def take_action(self, parsed_args): 'scheduler_hints', "Scheduler Hints", ): - columns += ('scheduler_hints',) - column_headers += ('Scheduler Hints',) + if sdk_utils.supports_microversion( + compute_client, '2.100' + ): + columns += ('scheduler_hints',) + column_headers += ('Scheduler Hints',) # remove duplicates column_headers = tuple(dict.fromkeys(column_headers)) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 1d55d1efe..1048394d3 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4584,7 +4584,6 @@ class _TestServerList(TestServer): 'Availability Zone', 'Host', 'Properties', - 'Scheduler Hints', ) columns_all_projects = ( 'ID', @@ -4734,7 +4733,6 @@ def test_server_list_long_option(self): getattr(s, 'availability_zone'), server.HostColumn(getattr(s, 'hypervisor_hostname')), format_columns.DictColumn(s.metadata), - format_columns.DictListColumn(None), ) for s in self.servers ) @@ -4811,8 +4809,6 @@ def test_server_list_column_option(self): 'Host', '-c', 'Properties', - '-c', - 'Scheduler Hints', '--long', ] verifylist = [ @@ -4834,7 +4830,6 @@ def test_server_list_column_option(self): self.assertIn('Availability Zone', columns) self.assertIn('Host', columns) self.assertIn('Properties', columns) - self.assertIn('Scheduler Hints', columns) self.assertCountEqual(columns, set(columns)) def test_server_list_no_name_lookup_option(self): @@ -5247,7 +5242,6 @@ def test_server_list_long_with_host_status_v216(self): getattr(s, 'availability_zone'), server.HostColumn(getattr(s, 'hypervisor_hostname')), format_columns.DictColumn(s.metadata), - format_columns.DictListColumn(s.scheduler_hints), ) for s in self.servers ) @@ -5303,7 +5297,6 @@ def test_server_list_long_with_host_status_v216(self): getattr(s, 'availability_zone'), server.HostColumn(getattr(s, 'hypervisor_hostname')), format_columns.DictColumn(s.metadata), - format_columns.DictListColumn(s.scheduler_hints), s.host_status, ) for s in servers @@ -5558,7 +5551,6 @@ class TestServerListV296(_TestServerList): 'Availability Zone', 'Host', 'Properties', - 'Scheduler Hints', 'Pinned Availability Zone', ) @@ -5611,7 +5603,6 @@ def test_server_list_long_option(self): getattr(s, 'availability_zone'), server.HostColumn(getattr(s, 'hypervisor_hostname')), format_columns.DictColumn(s.metadata), - format_columns.DictListColumn(None), getattr(s, 'pinned_availability_zone', ''), ) for s in self.servers @@ -5660,9 +5651,159 @@ def test_server_list_column_option(self): '-c', 'Properties', '-c', - 'Scheduler Hints', + 'Pinned Availability Zone', + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute_client.servers.assert_called_with(**self.kwargs) + self.assertIn('Project ID', columns) + self.assertIn('User ID', columns) + self.assertIn('Created At', columns) + self.assertIn('Security Groups', columns) + self.assertIn('Task State', columns) + self.assertIn('Power State', columns) + self.assertIn('Image ID', columns) + self.assertIn('Flavor ID', columns) + self.assertIn('Availability Zone', columns) + self.assertIn('Pinned Availability Zone', columns) + self.assertIn('Host', columns) + self.assertIn('Properties', columns) + self.assertCountEqual(columns, set(columns)) + + +class TestServerListV2100(_TestServerList): + columns = ( + 'ID', + 'Name', + 'Status', + 'Networks', + 'Image', + 'Flavor', + ) + columns_long = ( + 'ID', + 'Name', + 'Status', + 'Task State', + 'Power State', + 'Networks', + 'Image Name', + 'Image ID', + 'Flavor', + 'Availability Zone', + 'Host', + 'Properties', + 'Pinned Availability Zone', + 'Scheduler Hints', + ) + + def setUp(self): + super().setUp() + self.set_compute_api_version('2.100') + + self.image_client.images.return_value = [ + sdk_fakes.generate_fake_resource( + _image.Image, id=s.image['id'], name=self.image.name + ) + # Image will be an empty string if boot-from-volume + for s in self.servers + if s.image + ] + + self.compute_client.flavors.return_value = [ + sdk_fakes.generate_fake_resource( + _flavor.Flavor, id=s.flavor['id'], name=self.flavor.name + ) + for s in self.servers + ] + + self.data = tuple( + ( + s.id, + s.name, + s.status, + server.AddressesColumn(s.addresses), + # Image will be an empty string if boot-from-volume + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, + self.flavor.name, + ) + for s in self.servers + ) + + def test_server_list_long_option(self): + self.data = tuple( + ( + s.id, + s.name, + s.status, + getattr(s, 'task_state'), + server.PowerStateColumn(getattr(s, 'power_state')), + server.AddressesColumn(s.addresses), + # Image will be an empty string if boot-from-volume + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, + self.flavor.name, + getattr(s, 'availability_zone'), + server.HostColumn(getattr(s, 'hypervisor_hostname')), + format_columns.DictColumn(s.metadata), + getattr(s, 'pinned_availability_zone', ''), + format_columns.DictListColumn(None), + ) + for s in self.servers + ) + arglist = [ + '--long', + ] + verifylist = [ + ('all_projects', False), + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.compute_client.servers.assert_called_with(**self.kwargs) + image_ids = {s.image['id'] for s in self.servers if s.image} + self.image_client.images.assert_called_once_with( + id=f'in:{",".join(image_ids)}', + ) + self.compute_client.flavors.assert_called_once_with(is_public=None) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data, tuple(data)) + + def test_server_list_column_option(self): + arglist = [ + '-c', + 'Project ID', + '-c', + 'User ID', + '-c', + 'Created At', + '-c', + 'Security Groups', + '-c', + 'Task State', + '-c', + 'Power State', + '-c', + 'Image ID', + '-c', + 'Flavor ID', + '-c', + 'Availability Zone', + '-c', + 'Host', + '-c', + 'Properties', '-c', 'Pinned Availability Zone', + '-c', + 'Scheduler Hints', '--long', ] verifylist = [ From 3dfeb5ed08579a7a617f9c696ea3dea012f4a023 Mon Sep 17 00:00:00 2001 From: dlawton Date: Wed, 3 Sep 2025 16:43:04 +0100 Subject: [PATCH 20/67] Bug Fix: Skip invalid server ID during multi-server delete Change-Id: I8e5339f07b43dd0a9422eaf33346bbfdf2c9b328 Signed-off-by: Dan Lawton Closes-Bug: #2122056 --- openstackclient/compute/v2/server.py | 39 ++++++++++++--- .../tests/unit/compute/v2/test_server.py | 49 +++++++++++++++++++ 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 28c28b194..21a21974a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2218,24 +2218,49 @@ def _show_progress(progress): self.app.stdout.flush() compute_client = self.app.client_manager.compute + + deleted_servers = [] for server in parsed_args.server: - server_obj = compute_client.find_server( - server, - ignore_missing=False, - all_projects=parsed_args.all_projects, - ) + try: + server_obj = compute_client.find_server( + server, + ignore_missing=False, + all_projects=parsed_args.all_projects, + ) - compute_client.delete_server(server_obj, force=parsed_args.force) + compute_client.delete_server( + server_obj, force=parsed_args.force + ) + deleted_servers.append(server_obj) + except Exception as e: + LOG.error( + _( + "Failed to delete server with " + "name or ID '%(server)s': %(e)s" + ), + {'server': server, 'e': e}, + ) - if parsed_args.wait: + if parsed_args.wait: + for server_obj in deleted_servers: try: compute_client.wait_for_delete( server_obj, callback=_show_progress ) except sdk_exceptions.ResourceTimeout: msg = _('Error deleting server: %s') % server_obj.id + deleted_servers.remove(server_obj) raise exceptions.CommandError(msg) + fails = len(parsed_args.server) - len(deleted_servers) + if fails > 0: + total = len(parsed_args.server) + msg = _("%(fails)s of %(total)s servers failed to delete.") % { + 'fails': fails, + 'total': total, + } + raise exceptions.CommandError(msg) + class PercentAction(argparse.Action): def __init__( diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 1d55d1efe..68e1d9ac2 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -4449,6 +4449,55 @@ def test_server_delete_multi_servers(self): ) self.assertIsNone(result) + def test_server_delete_multi_servers_with_exceptions(self): + servers = compute_fakes.create_servers(count=2) + self.compute_client.find_server.side_effect = [ + servers[0], + sdk_exceptions.ResourceNotFound(), + servers[1], + ] + + arglist = [servers[0].id, 'unexist_server', servers[1].id] + + verifylist = [ + ('force', False), + ('all_projects', False), + ('wait', False), + ( + 'server', + [servers[0].id, 'unexist_server', servers[1].id], + ), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + exc = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + self.assertEqual('1 of 3 servers failed to delete.', str(exc)) + + self.compute_client.find_server.assert_has_calls( + [ + mock.call( + servers[0].id, ignore_missing=False, all_projects=False + ), + mock.call( + 'unexist_server', ignore_missing=False, all_projects=False + ), + mock.call( + servers[1].id, ignore_missing=False, all_projects=False + ), + ] + ) + + self.compute_client.delete_server.assert_has_calls( + [ + mock.call(servers[0], force=False), + mock.call(servers[1], force=False), + ] + ) + def test_server_delete_with_all_projects(self): arglist = [ self.server.id, From a5a6ec27e5c9e15c84109baa92cfedcdcb1f9503 Mon Sep 17 00:00:00 2001 From: doburn Date: Sat, 23 Aug 2025 21:25:55 +0900 Subject: [PATCH 21/67] Add functional tests for `role assignment list` Implements tests for `role assignment list` domain options. The options covered are: - `--user-domain` - `--group-domain` - `--project-domain` - `--role-domain` Change-Id: Ia42dcc337df0de7d5a93250696b807038a2d9d0e Signed-off-by: doburn --- .../identity/v3/test_role_assignment.py | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/openstackclient/tests/functional/identity/v3/test_role_assignment.py b/openstackclient/tests/functional/identity/v3/test_role_assignment.py index 76e33c286..1255841af 100644 --- a/openstackclient/tests/functional/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/functional/identity/v3/test_role_assignment.py @@ -62,6 +62,47 @@ def test_role_assignment_list_group(self): items = self.parse_listing(raw_output) self.assert_table_structure(items, self.ROLE_ASSIGNMENT_LIST_HEADERS) + def test_role_assignment_list_group_domain(self): + domain_name_A = self._create_dummy_domain() + domain_name_B = self._create_dummy_domain() + role_name = self._create_dummy_role() + group_name = 'group_name' + self.openstack(f'group create --domain {domain_name_A} {group_name}') + self.addCleanup( + self.openstack, + f'group delete --domain {domain_name_A} {group_name}', + ) + self.openstack(f'group create --domain {domain_name_B} {group_name}') + self.addCleanup( + self.openstack, + f'group delete --domain {domain_name_B} {group_name}', + ) + raw_output = self.openstack( + 'role add ' + f'--project {self.project_name} ' + f'--group {group_name} --group-domain {domain_name_A} ' + f'{role_name}' + ) + self.addCleanup( + self.openstack, + 'role remove ' + f'--project {self.project_name} ' + f'--group {group_name} --group-domain {domain_name_A} ' + f'{role_name}', + ) + self.assertEqual('', raw_output.strip()) + raw_output = self.openstack( + f'role assignment list ' + f'--group {group_name} --group-domain {domain_name_A} ' + ) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.ROLE_ASSIGNMENT_LIST_HEADERS) + raw_output = self.openstack( + f'role assignment list ' + f'--group {group_name} --group-domain {domain_name_B} ' + ) + self.assertEqual('', raw_output.strip()) + def test_role_assignment_list_domain(self): role_name = self._create_dummy_role() username = self._create_dummy_user() @@ -85,6 +126,89 @@ def test_role_assignment_list_domain(self): items = self.parse_listing(raw_output) self.assert_table_structure(items, self.ROLE_ASSIGNMENT_LIST_HEADERS) + def test_role_assignment_list_user_domain(self): + domain_name_A = self._create_dummy_domain() + domain_name_B = self._create_dummy_domain() + role_name = self._create_dummy_role() + username = 'username' + self.openstack(f'user create --domain {domain_name_A} {username}') + self.addCleanup( + self.openstack, f'user delete --domain {domain_name_A} {username}' + ) + self.openstack(f'user create --domain {domain_name_B} {username}') + self.addCleanup( + self.openstack, f'user delete --domain {domain_name_B} {username}' + ) + raw_output = self.openstack( + 'role add ' + f'--project {self.project_name} ' + f'--user {username} --user-domain {domain_name_A} ' + f'{role_name}' + ) + self.addCleanup( + self.openstack, + 'role remove ' + f'--project {self.project_name} ' + f'--user {username} --user-domain {domain_name_A} ' + f'{role_name}', + ) + self.assertEqual('', raw_output.strip()) + raw_output = self.openstack( + f'role assignment list ' + f'--user {username} --user-domain {domain_name_A} ' + ) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.ROLE_ASSIGNMENT_LIST_HEADERS) + raw_output = self.openstack( + f'role assignment list ' + f'--user {username} --user-domain {domain_name_B} ' + ) + self.assertEqual('', raw_output.strip()) + + def test_role_assignment_list_role_domain(self): + domain_name_A = self._create_dummy_domain() + domain_name_B = self._create_dummy_domain() + role_name = 'role_name' + username = 'username' + self.openstack(f'role create --domain {domain_name_A} {role_name}') + self.addCleanup( + self.openstack, f'role delete --domain {domain_name_A} {role_name}' + ) + self.openstack(f'role create --domain {domain_name_B} {role_name}') + self.addCleanup( + self.openstack, f'role delete --domain {domain_name_B} {role_name}' + ) + self.openstack(f'user create --domain {domain_name_A} {username}') + self.addCleanup( + self.openstack, f'user delete --domain {domain_name_A} {username}' + ) + raw_output = self.openstack( + 'role add ' + f'--user {username} --domain {domain_name_A} ' + f'--role-domain {domain_name_A} ' + f'{role_name}' + ) + self.addCleanup( + self.openstack, + 'role remove ' + f'--user {username} --domain {domain_name_A} ' + f'--role-domain {domain_name_A} ' + f'{role_name}', + ) + self.assertEqual('', raw_output.strip()) + raw_output = self.openstack( + f'role assignment list ' + f'--role {role_name} --role-domain {domain_name_A}' + ) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.ROLE_ASSIGNMENT_LIST_HEADERS) + raw_output = self.openstack( + f'role assignment list ' + f'--role {role_name} --role-domain {domain_name_B}' + ) + items = self.parse_listing(raw_output) + self.assertEqual('', raw_output.strip()) + def test_role_assignment_list_project(self): role_name = self._create_dummy_role() username = self._create_dummy_user() @@ -108,6 +232,56 @@ def test_role_assignment_list_project(self): items = self.parse_listing(raw_output) self.assert_table_structure(items, self.ROLE_ASSIGNMENT_LIST_HEADERS) + def test_role_assignment_list_project_domain(self): + domain_name_A = self._create_dummy_domain() + domain_name_B = self._create_dummy_domain() + role_name = self._create_dummy_role() + project_name = 'project_name' + username = 'username' + self.openstack( + f'project create --domain {domain_name_A} {project_name}' + ) + self.addCleanup( + self.openstack, + f'project delete --domain {domain_name_A} {project_name}', + ) + self.openstack( + f'project create --domain {domain_name_B} {project_name}' + ) + self.addCleanup( + self.openstack, + f'project delete --domain {domain_name_B} {project_name}', + ) + self.openstack(f'user create --domain {domain_name_A} {username}') + self.addCleanup( + self.openstack, f'user delete --domain {domain_name_A} {username}' + ) + raw_output = self.openstack( + 'role add ' + f'--project {project_name} --project-domain {domain_name_A} ' + f'--user {username} --user-domain {domain_name_A} ' + f'{role_name}' + ) + self.addCleanup( + self.openstack, + 'role remove ' + f'--project {project_name} --project-domain {domain_name_A} ' + f'--user {username} --user-domain {domain_name_A} ' + f'{role_name}', + ) + self.assertEqual('', raw_output.strip()) + raw_output = self.openstack( + f'role assignment list ' + f'--project {project_name} --project-domain {domain_name_A} ' + ) + items = self.parse_listing(raw_output) + self.assert_table_structure(items, self.ROLE_ASSIGNMENT_LIST_HEADERS) + raw_output = self.openstack( + f'role assignment list ' + f'--project {project_name} --project-domain {domain_name_B} ' + ) + self.assertEqual('', raw_output.strip()) + def test_role_assignment_list_effective(self): raw_output = self.openstack('role assignment list --effective') items = self.parse_listing(raw_output) From b808b82dfb7daf1343471b154f13f33ed89b9dfd Mon Sep 17 00:00:00 2001 From: dlawton Date: Thu, 11 Sep 2025 10:05:41 +0100 Subject: [PATCH 22/67] Validation: Cannot create network with segmentation id alone Change-Id: I7d98921fe6f2819a6427bc826d640a6685a00da7 Signed-off-by: Dan lawton Closes-bug: #1693106 --- openstackclient/network/v2/network.py | 10 ++++++++++ .../tests/unit/network/v2/test_network.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index e04efed61..33f7e04e4 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -401,6 +401,16 @@ def take_action_network(self, client, parsed_args): ) raise exceptions.CommandError(msg) + if ( + parsed_args.segmentation_id + and not parsed_args.provider_network_type + ): + msg = _( + "--provider-segment requires --provider-network-type " + "to be specified." + ) + raise exceptions.CommandError(msg) + attrs.update( self._parse_extra_properties(parsed_args.extra_properties) ) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index f809cad0a..b2e65f213 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -330,6 +330,23 @@ def test_create_with_vlan_qinq_and_transparency_enabled(self): exceptions.CommandError, self.cmd.take_action, parsed_args ) + def test_create_with_provider_segment_without_provider_type(self): + arglist = [ + "--provider-segment", + "123", + self._network.name, + ] + verifylist = [ + ('provider_network_type', None), + ('segmentation_id', "123"), + ('name', self._network.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises( + exceptions.CommandError, self.cmd.take_action, parsed_args + ) + class TestCreateNetworkIdentityV2( identity_fakes_v2.FakeClientMixin, From 3c3ea30bd3be7f310035ee94b96535273c2045b1 Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Tue, 16 Sep 2025 08:11:27 +0000 Subject: [PATCH 23/67] Update master for stable/2025.2 Add file to the reno documentation build to show release notes for stable/2025.2. Use pbr instruction to increment the minor version number automatically so that master versions are higher than the versions on stable/2025.2. Sem-Ver: feature Change-Id: I6aec2d1f91ed7fc2dba466574b4efb92b4bd7c88 Signed-off-by: OpenStack Release Bot Generated-By: openstack/project-config:roles/copy-release-tools-scripts/files/release-tools/add_release_note_page.sh --- releasenotes/source/2025.2.rst | 6 ++++++ releasenotes/source/index.rst | 1 + 2 files changed, 7 insertions(+) create mode 100644 releasenotes/source/2025.2.rst diff --git a/releasenotes/source/2025.2.rst b/releasenotes/source/2025.2.rst new file mode 100644 index 000000000..4dae18d86 --- /dev/null +++ b/releasenotes/source/2025.2.rst @@ -0,0 +1,6 @@ +=========================== +2025.2 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2025.2 diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index a9c057978..2b28cfb92 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + 2025.2 2025.1 2024.2 2024.1 From c0ada2d6ab3cf9caa1cc879f05ac89d4948e741d Mon Sep 17 00:00:00 2001 From: Alexey Stupnikov Date: Fri, 19 Sep 2025 19:37:28 +0200 Subject: [PATCH 24/67] Extend project delete command description "openstack project delete" command doesn't try to figure out if other services are using specified project somehow before trying to delete it. This patch extends command description to ensure that this is clearly communicated to users. Related-bug: #2118900 Change-Id: I3ae0b2a8f04d4f791cab46ccd89f400549d24ecd Signed-off-by: Alexey Stupnikov --- openstackclient/identity/v2_0/project.py | 9 ++++++++- openstackclient/identity/v3/project.py | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index a0c3c1e4d..8939aa12f 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -105,7 +105,14 @@ def take_action(self, parsed_args): class DeleteProject(command.Command): - _description = _("Delete project(s)") + _description = _( + "Delete project(s). This command will remove specified " + "existing project(s) if an active user is authorized to do " + "this. If there are resources managed by other services " + "(for example, Nova, Neutron, Cinder) associated with " + "specified project(s), delete operation will proceed " + "regardless." + ) def get_parser(self, prog_name): parser = super().get_parser(prog_name) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 383215461..dd6eb75af 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -146,7 +146,14 @@ def take_action(self, parsed_args): class DeleteProject(command.Command): - _description = _("Delete project(s)") + _description = _( + "Delete project(s). This command will remove specified " + "existing project(s) if an active user is authorized to do " + "this. If there are resources managed by other services " + "(for example, Nova, Neutron, Cinder) associated with " + "specified project(s), delete operation will proceed " + "regardless." + ) def get_parser(self, prog_name): parser = super().get_parser(prog_name) From 6b6a9bafd80a90ee9d42d756489350ea7a77ff69 Mon Sep 17 00:00:00 2001 From: jiwonjang Date: Tue, 26 Aug 2025 00:35:08 +0900 Subject: [PATCH 25/67] Add functional tests for image metadef resource type list Implements functional tests for 'image metadef resource type list' command. Change-Id: If645a04d4b8800da44041769f08b1e81332af33c Signed-off-by: jiwonjang --- .../image/v2/test_metadef_resource_type.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 openstackclient/tests/functional/image/v2/test_metadef_resource_type.py diff --git a/openstackclient/tests/functional/image/v2/test_metadef_resource_type.py b/openstackclient/tests/functional/image/v2/test_metadef_resource_type.py new file mode 100644 index 000000000..ab8dc13ef --- /dev/null +++ b/openstackclient/tests/functional/image/v2/test_metadef_resource_type.py @@ -0,0 +1,55 @@ +# 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 uuid + +from openstackclient.tests.functional.image import base + + +class ImageMetadefResourceTypeTests(base.BaseImageTests): + """Functional tests for image metadef resource type commands.""" + + def setUp(self): + super().setUp() + + # Create unique namespace name using UUID + self.namespace_name = 'test-mdef-ns-' + uuid.uuid4().hex + self.resource_type_name = 'test-mdef-rt-' + uuid.uuid4().hex + + # Create namespace + self.openstack('image metadef namespace create ' + self.namespace_name) + self.addCleanup( + self.openstack, + 'image metadef namespace delete ' + self.namespace_name, + ) + + def test_metadef_resource_type(self): + """Test image metadef resource type commands""" + + self.openstack( + 'image metadef resource type association create ' + f'{self.namespace_name} {self.resource_type_name}', + ) + self.addCleanup( + self.openstack, + 'image metadef resource type association delete ' + f'{self.namespace_name} {self.resource_type_name}', + ) + + output = self.openstack( + 'image metadef resource type list', + parse_output=True, + ) + + self.assertIn( + self.resource_type_name, [item['Name'] for item in output] + ) From 4cf70113d2398e432bae4ba897de15d8f93615b2 Mon Sep 17 00:00:00 2001 From: wonjun0120 Date: Sat, 16 Aug 2025 22:40:01 +0900 Subject: [PATCH 26/67] Add functional test for cached image command Implements tests for cache clear operations including queue, cache, and combined clearing functionality. Change-Id: I71056bb5db6c3de4f9294ac1b661ab927f59c867 Signed-off-by: wonjun0120 --- .../tests/functional/image/v2/test_cache.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 openstackclient/tests/functional/image/v2/test_cache.py diff --git a/openstackclient/tests/functional/image/v2/test_cache.py b/openstackclient/tests/functional/image/v2/test_cache.py new file mode 100644 index 000000000..58245e5ca --- /dev/null +++ b/openstackclient/tests/functional/image/v2/test_cache.py @@ -0,0 +1,54 @@ +# 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 uuid + +from openstackclient.tests.functional.image import base + + +class CacheTests(base.BaseImageTests): + """Functional tests for Cache commands""" + + def test_cached_image(self): + """Test cached image operations including queue and clear""" + # Create test image + name = uuid.uuid4().hex + output = self.openstack( + f'image create {name}', + parse_output=True, + ) + image_id = output["id"] + self.assertOutput(name, output['name']) + + # Register cleanup for created image + self.addCleanup( + self.openstack, 'cached image delete ' + image_id, fail_ok=True + ) + self.addCleanup(self.openstack, 'image delete ' + image_id) + + # Queue image for caching + self.openstack('cached image queue ' + image_id) + + # Verify queuing worked + cache_output = self.openstack('cached image list', parse_output=True) + self.assertIsInstance(cache_output, list) + image_ids = [img['ID'] for img in cache_output] + self.assertIn(image_id, image_ids) + + # Clear cached images + self.openstack('cached image clear') + + # Verify clearing worked + output = self.openstack('cached image list', parse_output=True) + if output: + image_ids = [img['ID'] for img in output] + self.assertNotIn(image_id, image_ids) From de88853de29d30ef6d1cc1966c93befd3e100cf3 Mon Sep 17 00:00:00 2001 From: Thomas Goirand Date: Mon, 29 Sep 2025 14:59:03 +0200 Subject: [PATCH 27/67] Fix openstack quota show without cinder Per this Debian bug [1], 'openstack quota show --default' fails when cinder is NOT installed. This is also true of other services. [1] https://bugs.debian.org/1109288 Change-Id: I361da44b9f1d09ba3a454632d41e2110a3815395 Signed-off-by: Svein-Erik Skjelbred Signed-off-by: Thomas Goirand Signed-off-by: Stephen Finucane --- openstackclient/common/quota.py | 27 +++++++++++++++---- .../tests/unit/common/test_quota.py | 20 ++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index f706a5974..41c57e63d 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -746,21 +746,32 @@ def take_action(self, parsed_args): # values if the project or class does not exist. This is expected # behavior. However, we have already checked for the presence of the # project above so it shouldn't be an issue. - if parsed_args.service in {'all', 'compute'}: + if parsed_args.service == 'compute' or ( + parsed_args.service == 'all' + and self.app.client_manager.is_compute_endpoint_enabled() + ): compute_quota_info = get_compute_quotas( self.app, project, detail=parsed_args.usage, default=parsed_args.default, ) - if parsed_args.service in {'all', 'volume'}: + + if parsed_args.service == 'volume' or ( + parsed_args.service == 'all' + and self.app.client_manager.is_volume_endpoint_enabled() + ): volume_quota_info = get_volume_quotas( self.app, project, detail=parsed_args.usage, default=parsed_args.default, ) - if parsed_args.service in {'all', 'network'}: + + if parsed_args.service == 'network' or ( + parsed_args.service == 'all' + and self.app.client_manager.is_network_endpoint_enabled() + ): network_quota_info = get_network_quotas( self.app, project, @@ -906,12 +917,18 @@ def take_action(self, parsed_args): ) # compute quotas - if parsed_args.service in {'all', 'compute'}: + if parsed_args.service == 'compute' or ( + parsed_args.service == 'all' + and self.app.client_manager.is_compute_endpoint_enabled() + ): compute_client = self.app.client_manager.compute compute_client.revert_quota_set(project.id) # volume quotas - if parsed_args.service in {'all', 'volume'}: + if parsed_args.service == 'volume' or ( + parsed_args.service == 'all' + and self.app.client_manager.is_volume_endpoint_enabled() + ): volume_client = self.app.client_manager.sdk_connection.volume volume_client.revert_quota_set(project.id) diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 53df4da84..a2418d01b 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -1041,6 +1041,26 @@ def test_quota_show(self): ) self.assertNotCalled(self.network_client.get_quota_default) + def test_quota_show__missing_services(self): + self.app.client_manager.compute_endpoint_enabled = False + self.app.client_manager.volume_endpoint_enabled = False + self.app.client_manager.network_endpoint_enabled = False + + arglist = [ + self.projects[0].name, + ] + verifylist = [ + ('service', 'all'), + ('project', self.projects[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.compute_client.get_quota_set.assert_not_called() + self.volume_sdk_client.get_quota_set.assert_not_called() + self.network_client.get_quota.assert_not_called() + def test_quota_show__with_compute(self): arglist = [ '--compute', From fb8cdd44414d0e16001d52d90d49d16dcc7a9509 Mon Sep 17 00:00:00 2001 From: Matt Anson Date: Wed, 1 Oct 2025 12:31:05 +0100 Subject: [PATCH 28/67] Ensure show on absent appcreds raises exception Currently, running ``application credential show`` on a non-existent appcred will exit normally and display a formatted application credential with no data, despite the Keystone API returning a 404. Ensure that querying a non-existent application credential raises an exception message and an exit-code 1 to the user. Closes-Bug: #2126565 Change-Id: I597d2d4064f1020c5ac40862ecc556f3c94b53eb Signed-off-by: Matt Anson --- openstackclient/identity/v3/application_credential.py | 2 +- .../tests/unit/identity/v3/test_application_credential.py | 2 +- releasenotes/notes/bug-2126565-a119ac242d9ac795.yaml | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/bug-2126565-a119ac242d9ac795.yaml diff --git a/openstackclient/identity/v3/application_credential.py b/openstackclient/identity/v3/application_credential.py index 4c4cd3f5a..93367f8a3 100644 --- a/openstackclient/identity/v3/application_credential.py +++ b/openstackclient/identity/v3/application_credential.py @@ -352,7 +352,7 @@ def take_action(self, parsed_args): user_id = conn.config.get_auth().get_user_id(conn.identity) application_credential = identity_client.find_application_credential( - user_id, parsed_args.application_credential + user_id, parsed_args.application_credential, ignore_missing=False ) return _format_application_credential(application_credential) diff --git a/openstackclient/tests/unit/identity/v3/test_application_credential.py b/openstackclient/tests/unit/identity/v3/test_application_credential.py index a5fda7c4f..3bc2d0c29 100644 --- a/openstackclient/tests/unit/identity/v3/test_application_credential.py +++ b/openstackclient/tests/unit/identity/v3/test_application_credential.py @@ -457,7 +457,7 @@ def test_application_credential_show(self): columns, data = self.cmd.take_action(parsed_args) self.identity_sdk_client.find_application_credential.assert_called_with( - user_id, self.application_credential.id + user_id, self.application_credential.id, ignore_missing=False ) self.assertEqual(self.columns, columns) diff --git a/releasenotes/notes/bug-2126565-a119ac242d9ac795.yaml b/releasenotes/notes/bug-2126565-a119ac242d9ac795.yaml new file mode 100644 index 000000000..87fe689b1 --- /dev/null +++ b/releasenotes/notes/bug-2126565-a119ac242d9ac795.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Running ``openstack application credential show`` on + a non-existent application credential does not + raise an exception. + + [Bug `2126565 `_] From 0ed122094ae480a4b3a02948e374aefe0eb3390a Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 1 Oct 2025 15:26:25 +0100 Subject: [PATCH 29/67] identity: Fix 'user list --project' option The 'role_assignments_filter' identity proxy method requires either a user or group, which defeats the entire purpose of the command when used with this option. Use 'role_assignments' instead. Change-Id: I8fb705c55fb4e81fa82d4a7dbe4c5bf7e1edd98a Signed-off-by: Stephen Finucane Closes-bug: #1616104 --- openstackclient/identity/v3/user.py | 8 +++----- openstackclient/tests/unit/identity/v3/test_user.py | 8 +++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index fee7dbe33..34d85fba9 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -467,15 +467,13 @@ def take_action(self, parsed_args): ignore_missing=False, ).id - assignments = identity_client.role_assignments_filter( - project=project - ) - # NOTE(stevemar): If a user has more than one role on a project # then they will have two entries in the returned data. Since we # are looking for any role, let's just track unique user IDs. user_ids = set() - for assignment in assignments: + for assignment in identity_client.role_assignments( + scope_project_id=project + ): if assignment.user: user_ids.add(assignment.user['id']) diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py index 1236b86e2..f0ed91405 100644 --- a/openstackclient/tests/unit/identity/v3/test_user.py +++ b/openstackclient/tests/unit/identity/v3/test_user.py @@ -891,7 +891,7 @@ def setUp(self): self.identity_sdk_client.find_domain.return_value = self.domain self.identity_sdk_client.find_group.return_value = self.group self.identity_sdk_client.find_project.return_value = self.project - self.identity_sdk_client.role_assignments_filter.return_value = [ + self.identity_sdk_client.role_assignments.return_value = [ self.role_assignment ] @@ -1029,12 +1029,10 @@ def test_user_list_project(self): columns, data = self.cmd.take_action(parsed_args) kwargs = { - 'project': self.project.id, + 'scope_project_id': self.project.id, } - self.identity_sdk_client.role_assignments_filter.assert_called_with( - **kwargs - ) + self.identity_sdk_client.role_assignments.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) From 20ad83bf84edf02496f4c41a732c8913b1525397 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 10 Oct 2025 10:39:41 +0100 Subject: [PATCH 30/67] pre-commit: Bump versions We need to rename two hooks. Change-Id: I15582a23da6ea6babf2b277ff443b7cdb764c9f9 Signed-off-by: Stephen Finucane --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 09a9172bd..47903a90d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,13 @@ --- repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: trailing-whitespace - id: mixed-line-ending args: ['--fix', 'lf'] exclude: '.*\.(svg)$' - - id: check-byte-order-marker + - id: fix-byte-order-marker - id: check-executables-have-shebangs - id: check-merge-conflict - id: debug-statements @@ -15,9 +15,9 @@ repos: files: .*\.(yaml|yml)$ args: ['--unsafe'] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.8 + rev: v0.14.0 hooks: - - id: ruff + - id: ruff-check args: ['--fix', '--unsafe-fixes'] - id: ruff-format - repo: https://opendev.org/openstack/hacking @@ -27,7 +27,7 @@ repos: additional_dependencies: [] exclude: '^(doc|releasenotes)/.*$' - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.15.0 + rev: v1.18.2 hooks: - id: mypy additional_dependencies: From 305e037df22c5ea3166df274cb9513b16680b711 Mon Sep 17 00:00:00 2001 From: Jan Ueberacker Date: Wed, 16 Jul 2025 16:01:09 +0200 Subject: [PATCH 31/67] Add option to filter for projects when listing volume backups Change-Id: Idb07c1be90a98b65b6c1b8f888d0ca5309f8cbc4 Signed-off-by: Jan Ueberacker --- .../tests/unit/volume/v3/test_volume_backup.py | 9 +++++++++ openstackclient/volume/v3/volume_backup.py | 17 ++++++++++++++++- ...-backup-project-filter-6c09b2c8aba83341.yaml | 5 +++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-volume-backup-project-filter-6c09b2c8aba83341.yaml diff --git a/openstackclient/tests/unit/volume/v3/test_volume_backup.py b/openstackclient/tests/unit/volume/v3/test_volume_backup.py index 5be0985a2..86bde785f 100644 --- a/openstackclient/tests/unit/volume/v3/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v3/test_volume_backup.py @@ -17,6 +17,7 @@ from openstack.block_storage.v3 import snapshot as _snapshot from openstack.block_storage.v3 import volume as _volume from openstack import exceptions as sdk_exceptions +from openstack.identity.v3 import project as _project from openstack.test import fakes as sdk_fakes from osc_lib import exceptions @@ -381,6 +382,7 @@ def test_backup_list_without_options(self): ("marker", None), ("limit", None), ('all_projects', False), + ("project", None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -395,11 +397,14 @@ def test_backup_list_without_options(self): all_tenants=False, marker=None, limit=None, + project_id=None, ) self.assertEqual(self.columns, columns) self.assertCountEqual(self.data, list(data)) def test_backup_list_with_options(self): + project = sdk_fakes.generate_fake_resource(_project.Project) + self.identity_sdk_client.find_project.return_value = project arglist = [ "--long", "--name", @@ -413,6 +418,8 @@ def test_backup_list_with_options(self): "--all-projects", "--limit", "3", + "--project", + project.id, ] verifylist = [ ("long", True), @@ -422,6 +429,7 @@ def test_backup_list_with_options(self): ("marker", self.backups[0].id), ('all_projects', True), ("limit", 3), + ("project", project.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -440,6 +448,7 @@ def test_backup_list_with_options(self): all_tenants=True, marker=self.backups[0].id, limit=3, + project_id=project.id, ) self.assertEqual(self.columns_long, columns) self.assertCountEqual(self.data_long, list(data)) diff --git a/openstackclient/volume/v3/volume_backup.py b/openstackclient/volume/v3/volume_backup.py index 7df46f8e6..3875f802a 100644 --- a/openstackclient/volume/v3/volume_backup.py +++ b/openstackclient/volume/v3/volume_backup.py @@ -236,6 +236,11 @@ class ListVolumeBackup(command.Lister): def get_parser(self, prog_name): parser = super().get_parser(prog_name) + parser.add_argument( + '--project', + metavar='', + help=_('Filter results by project (name or ID) (admin only)'), + ) parser.add_argument( "--long", action="store_true", @@ -296,6 +301,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.sdk_connection.volume + identity_client = self.app.client_manager.sdk_connection.identity columns: tuple[str, ...] = ( 'id', @@ -332,6 +338,14 @@ def take_action(self, parsed_args): VolumeIdColumn, volume_cache=volume_cache ) + all_tenants = parsed_args.all_projects + project_id = None + if parsed_args.project: + all_tenants = True + project_id = identity_client.find_project( + parsed_args.project, ignore_missing=False + ).id + filter_volume_id = None if parsed_args.volume: try: @@ -360,9 +374,10 @@ def take_action(self, parsed_args): name=parsed_args.name, status=parsed_args.status, volume_id=filter_volume_id, - all_tenants=parsed_args.all_projects, + all_tenants=all_tenants, marker=marker_backup_id, limit=parsed_args.limit, + project_id=project_id, ) return ( diff --git a/releasenotes/notes/add-volume-backup-project-filter-6c09b2c8aba83341.yaml b/releasenotes/notes/add-volume-backup-project-filter-6c09b2c8aba83341.yaml new file mode 100644 index 000000000..06bc7a4f0 --- /dev/null +++ b/releasenotes/notes/add-volume-backup-project-filter-6c09b2c8aba83341.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--project`` option to ``volume backup list`` command, + to allow filtering for projects when listing volume backups. From 3cc6b24bb5a9334ae5117a508d653dc9a727689d Mon Sep 17 00:00:00 2001 From: OpenStack Release Bot Date: Fri, 31 Oct 2025 12:05:25 +0000 Subject: [PATCH 32/67] reno: Update master for unmaintained/2024.1 Update the 2024.1 release notes configuration to build from unmaintained/2024.1. Change-Id: Ia4fff2a8e0f9bb083423c2e5c7339a46aaccd271 Signed-off-by: OpenStack Release Bot Generated-By: openstack/project-config:roles/copy-release-tools-scripts/files/release-tools/change_reno_branch_to_unmaintained.sh --- releasenotes/source/2024.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/releasenotes/source/2024.1.rst b/releasenotes/source/2024.1.rst index 4977a4f1a..6896656be 100644 --- a/releasenotes/source/2024.1.rst +++ b/releasenotes/source/2024.1.rst @@ -3,4 +3,4 @@ =========================== .. release-notes:: - :branch: stable/2024.1 + :branch: unmaintained/2024.1 From 44dfa157e4172616fb4ff75e1b3aa843c105efae Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 7 Nov 2025 12:39:35 +0000 Subject: [PATCH 33/67] tests: Remove duplicated fake network client This must have crept in some time after [1] merged. [1] Ic203964c7dede7dd80ae2d93b8fa1b7e6634a758 Change-Id: Ic0603db8b1a59b7704c51b0e0ffceb7db2e781d3 Signed-off-by: Stephen Finucane --- .../network/v2/default_security_group_rule.py | 8 +- .../v2/test_default_security_group_rule.py | 84 ++++++++----------- 2 files changed, 41 insertions(+), 51 deletions(-) diff --git a/openstackclient/network/v2/default_security_group_rule.py b/openstackclient/network/v2/default_security_group_rule.py index fddec6874..0a16a11a7 100644 --- a/openstackclient/network/v2/default_security_group_rule.py +++ b/openstackclient/network/v2/default_security_group_rule.py @@ -150,7 +150,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - client = self.app.client_manager.sdk_connection.network + client = self.app.client_manager.network # Build the create attributes. attrs = {} attrs['protocol'] = network_utils.get_protocol(parsed_args) @@ -248,7 +248,7 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): result = 0 - client = self.app.client_manager.sdk_connection.network + client = self.app.client_manager.network for r in parsed_args.rule: try: obj = client.find_default_security_group_rule( @@ -334,7 +334,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - client = self.app.client_manager.sdk_connection.network + client = self.app.client_manager.network column_headers = ( 'ID', 'IP Protocol', @@ -403,7 +403,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - client = self.app.client_manager.sdk_connection.network + client = self.app.client_manager.network obj = client.find_default_security_group_rule( parsed_args.rule, ignore_missing=False ) diff --git a/openstackclient/tests/unit/network/v2/test_default_security_group_rule.py b/openstackclient/tests/unit/network/v2/test_default_security_group_rule.py index 9b461269c..7032047e3 100644 --- a/openstackclient/tests/unit/network/v2/test_default_security_group_rule.py +++ b/openstackclient/tests/unit/network/v2/test_default_security_group_rule.py @@ -15,7 +15,6 @@ from unittest.mock import call import uuid -from openstack.network.v2 import _proxy from openstack.network.v2 import ( default_security_group_rule as _default_security_group_rule, ) @@ -28,18 +27,7 @@ from openstackclient.tests.unit import utils as tests_utils -class TestDefaultSecurityGroupRule(network_fakes.TestNetworkV2): - def setUp(self): - super().setUp() - - self.app.client_manager.sdk_connection = mock.Mock() - self.app.client_manager.sdk_connection.network = mock.Mock( - spec=_proxy.Proxy, - ) - self.sdk_client = self.app.client_manager.sdk_connection.network - - -class TestCreateDefaultSecurityGroupRule(TestDefaultSecurityGroupRule): +class TestCreateDefaultSecurityGroupRule(network_fakes.TestNetworkV2): expected_columns = ( 'description', 'direction', @@ -82,7 +70,7 @@ def _setup_default_security_group_rule(self, attrs=None): **default_security_group_rule_attrs, ) - self.sdk_client.create_default_security_group_rule.return_value = ( + self.network_client.create_default_security_group_rule.return_value = ( self._default_sg_rule ) self.expected_data = ( @@ -208,7 +196,7 @@ def test_create_default_rule(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'direction': self._default_sg_rule.direction, 'ethertype': self._default_sg_rule.ether_type, @@ -252,7 +240,7 @@ def _test_create_protocol_any_helper( columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'direction': self._default_sg_rule.direction, 'ethertype': self._default_sg_rule.ether_type, @@ -302,7 +290,7 @@ def test_create_remote_address_group(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'direction': self._default_sg_rule.direction, 'ethertype': self._default_sg_rule.ether_type, @@ -347,7 +335,7 @@ def test_create_remote_group(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'direction': self._default_sg_rule.direction, 'ethertype': self._default_sg_rule.ether_type, @@ -381,7 +369,7 @@ def test_create_source_group(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'direction': self._default_sg_rule.direction, 'ethertype': self._default_sg_rule.ether_type, @@ -415,7 +403,7 @@ def test_create_source_ip(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'direction': self._default_sg_rule.direction, 'ethertype': self._default_sg_rule.ether_type, @@ -449,7 +437,7 @@ def test_create_remote_ip(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'direction': self._default_sg_rule.direction, 'ethertype': self._default_sg_rule.ether_type, @@ -599,7 +587,7 @@ def test_create_icmp_type(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'direction': self._default_sg_rule.direction, 'ethertype': self._default_sg_rule.ether_type, @@ -637,7 +625,7 @@ def test_create_icmp_type_zero(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'direction': self._default_sg_rule.direction, 'ethertype': self._default_sg_rule.ether_type, @@ -675,7 +663,7 @@ def test_create_icmp_type_greater_than_zero(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'direction': self._default_sg_rule.direction, 'ethertype': self._default_sg_rule.ether_type, @@ -713,7 +701,7 @@ def test_create_icmp_type_negative_value(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'direction': self._default_sg_rule.direction, 'ethertype': self._default_sg_rule.ether_type, @@ -754,7 +742,7 @@ def test_create_ipv6_icmp_type_code(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'direction': self._default_sg_rule.direction, 'ethertype': self._default_sg_rule.ether_type, @@ -794,7 +782,7 @@ def test_create_icmpv6_type(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'direction': self._default_sg_rule.direction, 'ethertype': self._default_sg_rule.ether_type, @@ -825,7 +813,7 @@ def test_create_with_description(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.create_default_security_group_rule.assert_called_once_with( + self.network_client.create_default_security_group_rule.assert_called_once_with( **{ 'description': self._default_sg_rule.description, 'direction': self._default_sg_rule.direction, @@ -840,7 +828,7 @@ def test_create_with_description(self): self.assertEqual(self.expected_data, data) -class TestDeleteDefaultSecurityGroupRule(TestDefaultSecurityGroupRule): +class TestDeleteDefaultSecurityGroupRule(network_fakes.TestNetworkV2): # The default security group rules to be deleted. default_security_group_rule_attrs = { 'direction': 'ingress', @@ -866,7 +854,9 @@ class TestDeleteDefaultSecurityGroupRule(TestDefaultSecurityGroupRule): def setUp(self): super().setUp() - self.sdk_client.delete_default_security_group_rule.return_value = None + self.network_client.delete_default_security_group_rule.return_value = ( + None + ) # Get the command object to test self.cmd = default_security_group_rule.DeleteDefaultSecurityGroupRule( @@ -880,7 +870,7 @@ def test_default_security_group_rule_delete(self): verifylist = [ ('rule', [self._default_sg_rules[0].id]), ] - self.sdk_client.find_default_security_group_rule.return_value = ( + self.network_client.find_default_security_group_rule.return_value = ( self._default_sg_rules[0] ) @@ -888,7 +878,7 @@ def test_default_security_group_rule_delete(self): result = self.cmd.take_action(parsed_args) - self.sdk_client.delete_default_security_group_rule.assert_called_once_with( + self.network_client.delete_default_security_group_rule.assert_called_once_with( self._default_sg_rules[0] ) self.assertIsNone(result) @@ -902,7 +892,7 @@ def test_multi_default_security_group_rules_delete(self): verifylist = [ ('rule', arglist), ] - self.sdk_client.find_default_security_group_rule.side_effect = ( + self.network_client.find_default_security_group_rule.side_effect = ( self._default_sg_rules ) parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -912,7 +902,7 @@ def test_multi_default_security_group_rules_delete(self): calls = [] for s in self._default_sg_rules: calls.append(call(s)) - self.sdk_client.delete_default_security_group_rule.assert_has_calls( + self.network_client.delete_default_security_group_rule.assert_has_calls( calls ) self.assertIsNone(result) @@ -931,7 +921,7 @@ def test_multi_default_security_group_rules_delete_with_exception(self): self._default_sg_rules[0], exceptions.CommandError, ] - self.sdk_client.find_default_security_group_rule = mock.Mock( + self.network_client.find_default_security_group_rule = mock.Mock( side_effect=find_mock_result ) @@ -941,18 +931,18 @@ def test_multi_default_security_group_rules_delete_with_exception(self): except exceptions.CommandError as e: self.assertEqual('1 of 2 default rules failed to delete.', str(e)) - self.sdk_client.find_default_security_group_rule.assert_any_call( + self.network_client.find_default_security_group_rule.assert_any_call( self._default_sg_rules[0].id, ignore_missing=False ) - self.sdk_client.find_default_security_group_rule.assert_any_call( + self.network_client.find_default_security_group_rule.assert_any_call( 'unexist_rule', ignore_missing=False ) - self.sdk_client.delete_default_security_group_rule.assert_called_once_with( + self.network_client.delete_default_security_group_rule.assert_called_once_with( self._default_sg_rules[0] ) -class TestListDefaultSecurityGroupRule(TestDefaultSecurityGroupRule): +class TestListDefaultSecurityGroupRule(network_fakes.TestNetworkV2): # The security group rule to be listed. _default_sg_rule_tcp = sdk_fakes.generate_fake_resource( _default_security_group_rule.DefaultSecurityGroupRule, @@ -1001,7 +991,7 @@ class TestListDefaultSecurityGroupRule(TestDefaultSecurityGroupRule): def setUp(self): super().setUp() - self.sdk_client.default_security_group_rules.return_value = ( + self.network_client.default_security_group_rules.return_value = ( self._default_sg_rules ) @@ -1016,7 +1006,7 @@ def test_list_default(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.default_security_group_rules.assert_called_once_with( + self.network_client.default_security_group_rules.assert_called_once_with( **{} ) self.assertEqual(self.expected_columns, columns) @@ -1035,7 +1025,7 @@ def test_list_with_protocol(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.default_security_group_rules.assert_called_once_with( + self.network_client.default_security_group_rules.assert_called_once_with( **{ 'protocol': 'tcp', } @@ -1055,7 +1045,7 @@ def test_list_with_ingress(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.default_security_group_rules.assert_called_once_with( + self.network_client.default_security_group_rules.assert_called_once_with( **{ 'direction': 'ingress', } @@ -1075,7 +1065,7 @@ def test_list_with_wrong_egress(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.default_security_group_rules.assert_called_once_with( + self.network_client.default_security_group_rules.assert_called_once_with( **{ 'direction': 'egress', } @@ -1084,7 +1074,7 @@ def test_list_with_wrong_egress(self): self.assertEqual(self.expected_data, list(data)) -class TestShowDefaultSecurityGroupRule(TestDefaultSecurityGroupRule): +class TestShowDefaultSecurityGroupRule(network_fakes.TestNetworkV2): # The default security group rule to be shown. _default_sg_rule = sdk_fakes.generate_fake_resource( _default_security_group_rule.DefaultSecurityGroupRule @@ -1123,7 +1113,7 @@ class TestShowDefaultSecurityGroupRule(TestDefaultSecurityGroupRule): def setUp(self): super().setUp() - self.sdk_client.find_default_security_group_rule.return_value = ( + self.network_client.find_default_security_group_rule.return_value = ( self._default_sg_rule ) @@ -1148,7 +1138,7 @@ def test_show_all_options(self): columns, data = self.cmd.take_action(parsed_args) - self.sdk_client.find_default_security_group_rule.assert_called_once_with( + self.network_client.find_default_security_group_rule.assert_called_once_with( self._default_sg_rule.id, ignore_missing=False ) self.assertEqual(self.columns, columns) From 7116449190d9cbc144ede942163ed7678cde2edd Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 7 Nov 2025 13:40:36 +0000 Subject: [PATCH 34/67] tests: Avoid more unnecessary mocks Change-Id: I04672d46595e93b19f873a54d5be9363d262370b Signed-off-by: Stephen Finucane --- .../tests/unit/common/test_extension.py | 5 +- .../tests/unit/compute/v2/test_aggregate.py | 8 +- .../tests/unit/compute/v2/test_console.py | 5 +- .../tests/unit/compute/v2/test_flavor.py | 4 +- .../unit/compute/v2/test_hypervisor_stats.py | 10 +- .../unit/compute/v2/test_server_backup.py | 4 +- .../tests/unit/compute/v2/test_service.py | 4 +- .../tests/unit/network/test_common.py | 8 +- .../unit/network/v2/test_address_group.py | 41 ++-- .../unit/network/v2/test_address_scope.py | 25 +- .../v2/test_default_security_group_rule.py | 5 +- .../network/v2/test_floating_ip_network.py | 48 ++-- .../v2/test_floating_ip_port_forwarding.py | 39 +-- .../unit/network/v2/test_ip_availability.py | 14 +- .../network/v2/test_l3_conntrack_helper.py | 21 +- .../tests/unit/network/v2/test_local_ip.py | 35 +-- .../network/v2/test_local_ip_association.py | 31 +-- .../tests/unit/network/v2/test_ndp_proxy.py | 32 +-- .../tests/unit/network/v2/test_network.py | 61 ++--- .../unit/network/v2/test_network_agent.py | 33 +-- .../test_network_auto_allocated_topology.py | 13 +- .../unit/network/v2/test_network_flavor.py | 40 ++- .../network/v2/test_network_flavor_profile.py | 31 ++- .../unit/network/v2/test_network_meter.py | 24 +- .../network/v2/test_network_meter_rule.py | 30 +-- .../network/v2/test_network_qos_policy.py | 20 +- .../unit/network/v2/test_network_qos_rule.py | 98 ++++---- .../network/v2/test_network_qos_rule_type.py | 9 +- .../unit/network/v2/test_network_rbac.py | 57 ++--- .../unit/network/v2/test_network_segment.py | 42 +--- .../network/v2/test_network_segment_range.py | 39 +-- .../v2/test_network_service_provider.py | 5 +- .../unit/network/v2/test_network_trunk.py | 97 ++++---- .../tests/unit/network/v2/test_port.py | 161 ++++++------ .../tests/unit/network/v2/test_router.py | 230 ++++++++---------- .../network/v2/test_security_group_network.py | 45 ++-- .../v2/test_security_group_rule_network.py | 35 ++- .../tests/unit/network/v2/test_subnet.py | 81 +++--- .../tests/unit/network/v2/test_subnet_pool.py | 54 ++-- 39 files changed, 651 insertions(+), 893 deletions(-) diff --git a/openstackclient/tests/unit/common/test_extension.py b/openstackclient/tests/unit/common/test_extension.py index 53d9d65d9..dd684312c 100644 --- a/openstackclient/tests/unit/common/test_extension.py +++ b/openstackclient/tests/unit/common/test_extension.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from unittest import mock from openstackclient.common import extension from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes @@ -295,8 +294,8 @@ def setUp(self): self.cmd = extension.ShowExtension(self.app, None) - self.app.client_manager.network.find_extension = mock.Mock( - return_value=self.extension_details + self.app.client_manager.network.find_extension.return_value = ( + self.extension_details ) def test_show_no_options(self): diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index 813cbb95f..b68e76edc 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -165,9 +165,11 @@ def setUp(self): sdk_fakes.generate_fake_resources(_aggregate.Aggregate, 2) ) - self.compute_client.find_aggregate = mock.Mock( - side_effect=[self.fake_ags[0], self.fake_ags[1]] - ) + self.compute_client.find_aggregate.side_effect = [ + self.fake_ags[0], + self.fake_ags[1], + ] + self.cmd = aggregate.DeleteAggregate(self.app, None) def test_aggregate_delete(self): diff --git a/openstackclient/tests/unit/compute/v2/test_console.py b/openstackclient/tests/unit/compute/v2/test_console.py index 423290c11..8d9d36ccd 100644 --- a/openstackclient/tests/unit/compute/v2/test_console.py +++ b/openstackclient/tests/unit/compute/v2/test_console.py @@ -13,7 +13,6 @@ # under the License. # -from unittest import mock from openstack.compute.v2 import server as _server from openstack.test import fakes as sdk_fakes @@ -90,9 +89,7 @@ def setUp(self): 'protocol': 'fake_protocol', 'type': 'fake_type', } - self.compute_client.create_console = mock.Mock( - return_value=fake_console_data - ) + self.compute_client.create_console.return_value = fake_console_data self.columns = ( 'protocol', diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index 0b3a4ae8d..25bc8eaa7 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -680,9 +680,7 @@ def test_flavor_list_long_no_extra_specs(self): ) self.compute_client.flavors.side_effect = [[flavor], []] - self.compute_client.fetch_flavor_extra_specs = mock.Mock( - return_value=None - ) + self.compute_client.fetch_flavor_extra_specs.return_value = None arglist = [ '--long', diff --git a/openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py b/openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py index 045efda2d..89d4d459f 100644 --- a/openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py +++ b/openstackclient/tests/unit/compute/v2/test_hypervisor_stats.py @@ -12,20 +12,12 @@ # License for the specific language governing permissions and limitations # under the License. # -from unittest import mock from openstackclient.compute.v2 import hypervisor_stats from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit import fakes -class TestHypervisorStats(compute_fakes.TestComputev2): - def setUp(self): - super().setUp() - - self.compute_client.get = mock.Mock() - - # Not in fakes.py because hypervisor stats has been deprecated @@ -61,7 +53,7 @@ def create_one_hypervisor_stats(attrs=None): return stats_info -class TestHypervisorStatsShow(TestHypervisorStats): +class TestHypervisorStatsShow(compute_fakes.TestComputev2): _stats = create_one_hypervisor_stats() def setUp(self): diff --git a/openstackclient/tests/unit/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py index 0d039d210..cc8acfb34 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_backup.py +++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py @@ -161,9 +161,7 @@ def test_server_backup_wait_fail(self, mock_wait_for_status): @mock.patch.object(common_utils, 'wait_for_status', return_value=True) def test_server_backup_wait_ok(self, mock_wait_for_status): - self.image_client.get_image = mock.Mock( - side_effect=self.image, - ) + self.image_client.get_image.side_effect = (self.image,) arglist = [ '--name', diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py index c64a2b9c8..a47ea7298 100644 --- a/openstackclient/tests/unit/compute/v2/test_service.py +++ b/openstackclient/tests/unit/compute/v2/test_service.py @@ -77,9 +77,7 @@ def test_multi_services_delete_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) delete_mock_result = [None, exceptions.CommandError] - self.compute_client.delete_service = mock.Mock( - side_effect=delete_mock_result - ) + self.compute_client.delete_service.side_effect = delete_mock_result try: self.cmd.take_action(parsed_args) diff --git a/openstackclient/tests/unit/network/test_common.py b/openstackclient/tests/unit/network/test_common.py index c1ad9b28d..02cb021ef 100644 --- a/openstackclient/tests/unit/network/test_common.py +++ b/openstackclient/tests/unit/network/test_common.py @@ -128,15 +128,11 @@ def setUp(self): self.app.client_manager.network = mock.Mock() self.network_client = self.app.client_manager.network - self.network_client.network_action = mock.Mock( - return_value='take_action_network' - ) + self.network_client.network_action.return_value = 'take_action_network' self.app.client_manager.compute = mock.Mock() self.compute_client = self.app.client_manager.compute - self.compute_client.compute_action = mock.Mock( - return_value='take_action_compute' - ) + self.compute_client.compute_action.return_value = 'take_action_compute' self.cmd = FakeNetworkAndComputeCommand(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_address_group.py b/openstackclient/tests/unit/network/v2/test_address_group.py index 411d19923..48f706631 100644 --- a/openstackclient/tests/unit/network/v2/test_address_group.py +++ b/openstackclient/tests/unit/network/v2/test_address_group.py @@ -11,7 +11,6 @@ # under the License. # -from unittest import mock from unittest.mock import call from osc_lib import exceptions @@ -58,8 +57,8 @@ class TestCreateAddressGroup(TestAddressGroup): def setUp(self): super().setUp() - self.network_client.create_address_group = mock.Mock( - return_value=self.new_address_group + self.network_client.create_address_group.return_value = ( + self.new_address_group ) # Get the command object to test @@ -145,7 +144,7 @@ class TestDeleteAddressGroup(TestAddressGroup): def setUp(self): super().setUp() - self.network_client.delete_address_group = mock.Mock(return_value=None) + self.network_client.delete_address_group.return_value = None self.network_client.find_address_group = ( network_fakes.get_address_groups( address_groups=self._address_groups @@ -206,9 +205,7 @@ def test_multi_address_groups_delete_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) find_mock_result = [self._address_groups[0], exceptions.CommandError] - self.network_client.find_address_group = mock.Mock( - side_effect=find_mock_result - ) + self.network_client.find_address_group.side_effect = find_mock_result try: self.cmd.take_action(parsed_args) @@ -251,9 +248,7 @@ class TestListAddressGroup(TestAddressGroup): def setUp(self): super().setUp() - self.network_client.address_groups = mock.Mock( - return_value=self.address_groups - ) + self.network_client.address_groups.return_value = self.address_groups # Get the command object to test self.cmd = address_group.ListAddressGroup(self.app, None) @@ -333,13 +328,15 @@ class TestSetAddressGroup(TestAddressGroup): def setUp(self): super().setUp() - self.network_client.update_address_group = mock.Mock(return_value=None) - self.network_client.find_address_group = mock.Mock( - return_value=self._address_group + self.network_client.update_address_group.return_value = None + self.network_client.find_address_group.return_value = ( + self._address_group ) - self.network_client.add_addresses_to_address_group = mock.Mock( - return_value=self._address_group + + self.network_client.add_addresses_to_address_group.return_value = ( + self._address_group ) + # Get the command object to test self.cmd = address_group.SetAddressGroup(self.app, None) @@ -442,8 +439,8 @@ class TestShowAddressGroup(TestAddressGroup): def setUp(self): super().setUp() - self.network_client.find_address_group = mock.Mock( - return_value=self._address_group + self.network_client.find_address_group.return_value = ( + self._address_group ) # Get the command object to test @@ -486,12 +483,12 @@ class TestUnsetAddressGroup(TestAddressGroup): def setUp(self): super().setUp() - self.network_client.find_address_group = mock.Mock( - return_value=self._address_group - ) - self.network_client.remove_addresses_from_address_group = mock.Mock( - return_value=self._address_group + self.network_client.find_address_group.return_value = ( + self._address_group ) + + self.network_client.remove_addresses_from_address_group.return_value = self._address_group + # Get the command object to test self.cmd = address_group.UnsetAddressGroup(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_address_scope.py b/openstackclient/tests/unit/network/v2/test_address_scope.py index c3eb83d06..6e2c05ed9 100644 --- a/openstackclient/tests/unit/network/v2/test_address_scope.py +++ b/openstackclient/tests/unit/network/v2/test_address_scope.py @@ -11,7 +11,6 @@ # under the License. # -from unittest import mock from unittest.mock import call from osc_lib import exceptions @@ -52,8 +51,8 @@ class TestCreateAddressScope(TestAddressScope): def setUp(self): super().setUp() - self.network_client.create_address_scope = mock.Mock( - return_value=self.new_address_scope + self.network_client.create_address_scope.return_value = ( + self.new_address_scope ) # Get the command object to test @@ -160,7 +159,7 @@ class TestDeleteAddressScope(TestAddressScope): def setUp(self): super().setUp() - self.network_client.delete_address_scope = mock.Mock(return_value=None) + self.network_client.delete_address_scope.return_value = None self.network_client.find_address_scope = ( network_fakes.get_address_scopes( address_scopes=self._address_scopes @@ -222,9 +221,7 @@ def test_multi_address_scopes_delete_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) find_mock_result = [self._address_scopes[0], exceptions.CommandError] - self.network_client.find_address_scope = mock.Mock( - side_effect=find_mock_result - ) + self.network_client.find_address_scope.side_effect = find_mock_result try: self.cmd.take_action(parsed_args) @@ -267,9 +264,7 @@ class TestListAddressScope(TestAddressScope): def setUp(self): super().setUp() - self.network_client.address_scopes = mock.Mock( - return_value=self.address_scopes - ) + self.network_client.address_scopes.return_value = self.address_scopes # Get the command object to test self.cmd = address_scope.ListAddressScope(self.app, None) @@ -398,9 +393,9 @@ class TestSetAddressScope(TestAddressScope): def setUp(self): super().setUp() - self.network_client.update_address_scope = mock.Mock(return_value=None) - self.network_client.find_address_scope = mock.Mock( - return_value=self._address_scope + self.network_client.update_address_scope.return_value = None + self.network_client.find_address_scope.return_value = ( + self._address_scope ) # Get the command object to test @@ -488,8 +483,8 @@ class TestShowAddressScope(TestAddressScope): def setUp(self): super().setUp() - self.network_client.find_address_scope = mock.Mock( - return_value=self._address_scope + self.network_client.find_address_scope.return_value = ( + self._address_scope ) # Get the command object to test diff --git a/openstackclient/tests/unit/network/v2/test_default_security_group_rule.py b/openstackclient/tests/unit/network/v2/test_default_security_group_rule.py index 7032047e3..c44e553c7 100644 --- a/openstackclient/tests/unit/network/v2/test_default_security_group_rule.py +++ b/openstackclient/tests/unit/network/v2/test_default_security_group_rule.py @@ -11,7 +11,6 @@ # under the License. # -from unittest import mock from unittest.mock import call import uuid @@ -921,8 +920,8 @@ def test_multi_default_security_group_rules_delete_with_exception(self): self._default_sg_rules[0], exceptions.CommandError, ] - self.network_client.find_default_security_group_rule = mock.Mock( - side_effect=find_mock_result + self.network_client.find_default_security_group_rule.side_effect = ( + find_mock_result ) try: diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py index 605f4d99a..ab0ec176a 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -11,7 +11,6 @@ # under the License. # -from unittest import mock from unittest.mock import call from openstack.network.v2 import floating_ip as _floating_ip @@ -86,16 +85,14 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): def setUp(self): super().setUp() - self.network_client.create_ip = mock.Mock( - return_value=self.floating_ip - ) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.create_ip.return_value = self.floating_ip - self.network_client.find_network = mock.Mock( - return_value=self.floating_network - ) - self.network_client.find_subnet = mock.Mock(return_value=self.subnet) - self.network_client.find_port = mock.Mock(return_value=self.port) + self.network_client.set_tags.return_value = None + + self.network_client.find_network.return_value = self.floating_network + + self.network_client.find_subnet.return_value = self.subnet + self.network_client.find_port.return_value = self.port # Get the command object to test self.cmd = fip.CreateFloatingIP(self.app, None) @@ -304,7 +301,7 @@ class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): def setUp(self): super().setUp() - self.network_client.delete_ip = mock.Mock(return_value=None) + self.network_client.delete_ip.return_value = None # Get the command object to test self.cmd = fip.DeleteFloatingIP(self.app, None) @@ -470,14 +467,11 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): def setUp(self): super().setUp() - self.network_client.ips = mock.Mock(return_value=self.floating_ips) - self.network_client.find_network = mock.Mock( - return_value=self.fake_network - ) - self.network_client.find_port = mock.Mock(return_value=self.fake_port) - self.network_client.find_router = mock.Mock( - return_value=self.fake_router - ) + self.network_client.ips.return_value = self.floating_ips + self.network_client.find_network.return_value = self.fake_network + + self.network_client.find_port.return_value = self.fake_port + self.network_client.find_router.return_value = self.fake_router # Get the command object to test self.cmd = fip.ListFloatingIP(self.app, None) @@ -713,7 +707,7 @@ def setUp(self): self.floating_ip = sdk_fakes.generate_fake_resource( _floating_ip.FloatingIP ) - self.network_client.find_ip = mock.Mock(return_value=self.floating_ip) + self.network_client.find_ip.return_value = self.floating_ip self.columns = ( 'created_at', @@ -797,10 +791,10 @@ class TestSetFloatingIP(TestFloatingIPNetwork): def setUp(self): super().setUp() - self.network_client.find_ip = mock.Mock(return_value=self.floating_ip) - self.network_client.find_port = mock.Mock(return_value=self.port) - self.network_client.update_ip = mock.Mock(return_value=None) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.find_ip.return_value = self.floating_ip + self.network_client.find_port.return_value = self.port + self.network_client.update_ip.return_value = None + self.network_client.set_tags.return_value = None # Get the command object to test self.cmd = fip.SetFloatingIP(self.app, None) @@ -1044,9 +1038,9 @@ class TestUnsetFloatingIP(TestFloatingIPNetwork): def setUp(self): super().setUp() - self.network_client.find_ip = mock.Mock(return_value=self.floating_ip) - self.network_client.update_ip = mock.Mock(return_value=None) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.find_ip.return_value = self.floating_ip + self.network_client.update_ip.return_value = None + self.network_client.set_tags.return_value = None # Get the command object to test self.cmd = fip.UnsetFloatingIP(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py index 2e4c93f8a..33b9011c6 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py @@ -33,7 +33,7 @@ def setUp(self): ) self.port = network_fakes.create_one_port() self.project = identity_fakes_v2.FakeProject.create_one_project() - self.network_client.find_port = mock.Mock(return_value=self.port) + self.network_client.find_port.return_value = self.port class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding): @@ -54,11 +54,11 @@ def setUp(self): }, ) - self.network_client.create_floating_ip_port_forwarding = mock.Mock( - return_value=self.new_port_forwarding + self.network_client.create_floating_ip_port_forwarding.return_value = ( + self.new_port_forwarding ) - self.network_client.find_ip = mock.Mock(return_value=self.floating_ip) + self.network_client.find_ip.return_value = self.floating_ip # Get the command object to test self.cmd = floating_ip_port_forwarding.CreateFloatingIPPortForwarding( @@ -351,11 +351,11 @@ def setUp(self): }, ) ) - self.network_client.delete_floating_ip_port_forwarding = mock.Mock( - return_value=None + self.network_client.delete_floating_ip_port_forwarding.return_value = ( + None ) - self.network_client.find_ip = mock.Mock(return_value=self.floating_ip) + self.network_client.find_ip.return_value = self.floating_ip # Get the command object to test self.cmd = floating_ip_port_forwarding.DeleteFloatingIPPortForwarding( self.app, None @@ -490,10 +490,11 @@ def setUp(self): port_forwarding.description, ) ) - self.network_client.floating_ip_port_forwardings = mock.Mock( - return_value=self.port_forwardings + self.network_client.floating_ip_port_forwardings.return_value = ( + self.port_forwardings ) - self.network_client.find_ip = mock.Mock(return_value=self.floating_ip) + + self.network_client.find_ip.return_value = self.floating_ip # Get the command object to test self.cmd = floating_ip_port_forwarding.ListFloatingIPPortForwarding( self.app, None @@ -557,14 +558,15 @@ def setUp(self): 'floatingip_id': self.floating_ip.id, } ) - self.network_client.update_floating_ip_port_forwarding = mock.Mock( - return_value=None + self.network_client.update_floating_ip_port_forwarding.return_value = ( + None ) - self.network_client.find_floating_ip_port_forwarding = mock.Mock( - return_value=self._port_forwarding + self.network_client.find_floating_ip_port_forwarding.return_value = ( + self._port_forwarding ) - self.network_client.find_ip = mock.Mock(return_value=self.floating_ip) + + self.network_client.find_ip.return_value = self.floating_ip # Get the command object to test self.cmd = floating_ip_port_forwarding.SetFloatingIPPortForwarding( self.app, None @@ -690,10 +692,11 @@ def setUp(self): self._port_forwarding.internal_port_range, self._port_forwarding.protocol, ) - self.network_client.find_floating_ip_port_forwarding = mock.Mock( - return_value=self._port_forwarding + self.network_client.find_floating_ip_port_forwarding.return_value = ( + self._port_forwarding ) - self.network_client.find_ip = mock.Mock(return_value=self.floating_ip) + + self.network_client.find_ip.return_value = self.floating_ip # Get the command object to test self.cmd = floating_ip_port_forwarding.ShowFloatingIPPortForwarding( self.app, None diff --git a/openstackclient/tests/unit/network/v2/test_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py index 4224d1266..def3e17da 100644 --- a/openstackclient/tests/unit/network/v2/test_ip_availability.py +++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py @@ -11,7 +11,6 @@ # under the License. # -from unittest import mock from osc_lib.cli import format_columns @@ -55,8 +54,8 @@ def setUp(self): super().setUp() self.cmd = ip_availability.ListIPAvailability(self.app, None) - self.network_client.network_ip_availabilities = mock.Mock( - return_value=self._ip_availability + self.network_client.network_ip_availabilities.return_value = ( + self._ip_availability ) def test_list_no_options(self): @@ -134,13 +133,12 @@ class TestShowIPAvailability(TestIPAvailability): def setUp(self): super().setUp() - self.network_client.find_network_ip_availability = mock.Mock( - return_value=self._ip_availability - ) - self.network_client.find_network = mock.Mock( - return_value=self._network + self.network_client.find_network_ip_availability.return_value = ( + self._ip_availability ) + self.network_client.find_network.return_value = self._network + # Get the command object to test self.cmd = ip_availability.ShowIPAvailability(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py b/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py index b358024ff..0769e2e56 100644 --- a/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py +++ b/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py @@ -11,7 +11,6 @@ # under the License. # -from unittest import mock from osc_lib import exceptions @@ -46,8 +45,8 @@ def setUp(self): self.ct_helper.protocol, self.ct_helper.router_id, ) - self.network_client.create_conntrack_helper = mock.Mock( - return_value=self.ct_helper + self.network_client.create_conntrack_helper.return_value = ( + self.ct_helper ) # Get the command object to test @@ -119,9 +118,7 @@ def setUp(self): attrs ) ) - self.network_client.delete_conntrack_helper = mock.Mock( - return_value=None - ) + self.network_client.delete_conntrack_helper.return_value = None # Get the command object to test self.cmd = l3_conntrack_helper.DeleteConntrackHelper(self.app, None) @@ -181,9 +178,7 @@ def setUp(self): ct_helper.port, ) ) - self.network_client.conntrack_helpers = mock.Mock( - return_value=ct_helpers - ) + self.network_client.conntrack_helpers.return_value = ct_helpers # Get the command object to test self.cmd = l3_conntrack_helper.ListConntrackHelper(self.app, None) @@ -216,9 +211,7 @@ def setUp(self): attrs ) ) - self.network_client.update_conntrack_helper = mock.Mock( - return_value=None - ) + self.network_client.update_conntrack_helper.return_value = None # Get the command object to test self.cmd = l3_conntrack_helper.SetConntrackHelper(self.app, None) @@ -281,9 +274,7 @@ def setUp(self): self.ct_helper.protocol, self.ct_helper.router_id, ) - self.network_client.get_conntrack_helper = mock.Mock( - return_value=self.ct_helper - ) + self.network_client.get_conntrack_helper.return_value = self.ct_helper # Get the command object to test self.cmd = l3_conntrack_helper.ShowConntrackHelper(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_local_ip.py b/openstackclient/tests/unit/network/v2/test_local_ip.py index cf4105b02..585fec767 100644 --- a/openstackclient/tests/unit/network/v2/test_local_ip.py +++ b/openstackclient/tests/unit/network/v2/test_local_ip.py @@ -13,7 +13,6 @@ # under the License. # -from unittest import mock from unittest.mock import call from osc_lib import exceptions @@ -77,13 +76,11 @@ class TestCreateLocalIP(TestLocalIP): def setUp(self): super().setUp() - self.network_client.create_local_ip = mock.Mock( - return_value=self.new_local_ip - ) - self.network_client.find_network = mock.Mock( - return_value=self.local_ip_network - ) - self.network_client.find_port = mock.Mock(return_value=self.port) + self.network_client.create_local_ip.return_value = self.new_local_ip + + self.network_client.find_network.return_value = self.local_ip_network + + self.network_client.find_port.return_value = self.port # Get the command object to test self.cmd = local_ip.CreateLocalIP(self.app, None) @@ -149,7 +146,7 @@ class TestDeleteLocalIP(TestLocalIP): def setUp(self): super().setUp() - self.network_client.delete_local_ip = mock.Mock(return_value=None) + self.network_client.delete_local_ip.return_value = None self.network_client.find_local_ip = network_fakes.get_local_ips( local_ips=self._local_ips ) @@ -205,9 +202,7 @@ def test_multi_local_ips_delete_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) find_mock_result = [self._local_ips[0], exceptions.CommandError] - self.network_client.find_local_ip = mock.Mock( - side_effect=find_mock_result - ) + self.network_client.find_local_ip.side_effect = find_mock_result try: self.cmd.take_action(parsed_args) @@ -258,10 +253,8 @@ class TestListLocalIP(TestLocalIP): def setUp(self): super().setUp() - self.network_client.local_ips = mock.Mock(return_value=self.local_ips) - self.network_client.find_network = mock.Mock( - return_value=self.fake_network - ) + self.network_client.local_ips.return_value = self.local_ips + self.network_client.find_network.return_value = self.fake_network # Get the command object to test self.cmd = local_ip.ListLocalIP(self.app, None) @@ -402,10 +395,8 @@ class TestSetLocalIP(TestLocalIP): def setUp(self): super().setUp() - self.network_client.update_local_ip = mock.Mock(return_value=None) - self.network_client.find_local_ip = mock.Mock( - return_value=self._local_ip - ) + self.network_client.update_local_ip.return_value = None + self.network_client.find_local_ip.return_value = self._local_ip # Get the command object to test self.cmd = local_ip.SetLocalIP(self.app, None) @@ -482,9 +473,7 @@ class TestShowLocalIP(TestLocalIP): def setUp(self): super().setUp() - self.network_client.find_local_ip = mock.Mock( - return_value=self._local_ip - ) + self.network_client.find_local_ip.return_value = self._local_ip # Get the command object to test self.cmd = local_ip.ShowLocalIP(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_local_ip_association.py b/openstackclient/tests/unit/network/v2/test_local_ip_association.py index 2c1c0ff31..9efdc295f 100644 --- a/openstackclient/tests/unit/network/v2/test_local_ip_association.py +++ b/openstackclient/tests/unit/network/v2/test_local_ip_association.py @@ -29,7 +29,7 @@ def setUp(self): self.local_ip = network_fakes.create_one_local_ip() self.fixed_port = network_fakes.create_one_port() self.project = identity_fakes_v2.FakeProject.create_one_project() - self.network_client.find_port = mock.Mock(return_value=self.fixed_port) + self.network_client.find_port.return_value = self.fixed_port class TestCreateLocalIPAssociation(TestLocalIPAssociation): @@ -43,13 +43,11 @@ def setUp(self): } ) ) - self.network_client.create_local_ip_association = mock.Mock( - return_value=self.new_local_ip_association + self.network_client.create_local_ip_association.return_value = ( + self.new_local_ip_association ) - self.network_client.find_local_ip = mock.Mock( - return_value=self.local_ip - ) + self.network_client.find_local_ip.return_value = self.local_ip # Get the command object to test self.cmd = local_ip_association.CreateLocalIPAssociation( @@ -128,13 +126,10 @@ def setUp(self): }, ) ) - self.network_client.delete_local_ip_association = mock.Mock( - return_value=None - ) + self.network_client.delete_local_ip_association.return_value = None + + self.network_client.find_local_ip.return_value = self.local_ip - self.network_client.find_local_ip = mock.Mock( - return_value=self.local_ip - ) # Get the command object to test self.cmd = local_ip_association.DeleteLocalIPAssociation( self.app, None @@ -262,13 +257,13 @@ def setUp(self): lip_assoc.host, ) ) - self.network_client.local_ip_associations = mock.Mock( - return_value=self.local_ip_associations + self.network_client.local_ip_associations.return_value = ( + self.local_ip_associations ) - self.network_client.find_local_ip = mock.Mock( - return_value=self.local_ip - ) - self.network_client.find_port = mock.Mock(return_value=self.fixed_port) + + self.network_client.find_local_ip.return_value = self.local_ip + + self.network_client.find_port.return_value = self.fixed_port # Get the command object to test self.cmd = local_ip_association.ListLocalIPAssociation(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_ndp_proxy.py b/openstackclient/tests/unit/network/v2/test_ndp_proxy.py index f9d9a3b3c..0fe8740da 100644 --- a/openstackclient/tests/unit/network/v2/test_ndp_proxy.py +++ b/openstackclient/tests/unit/network/v2/test_ndp_proxy.py @@ -11,7 +11,6 @@ # under the License. # -from unittest import mock from unittest.mock import call from osc_lib import exceptions @@ -31,9 +30,9 @@ def setUp(self): self.domains_mock = self.identity_client.domains self.router = network_fakes.create_one_router({'id': 'fake-router-id'}) - self.network_client.find_router = mock.Mock(return_value=self.router) + self.network_client.find_router.return_value = self.router self.port = network_fakes.create_one_port() - self.network_client.find_port = mock.Mock(return_value=self.port) + self.network_client.find_port.return_value = self.port class TestCreateNDPProxy(TestNDPProxy): @@ -66,9 +65,7 @@ def setUp(self): self.ndp_proxy.router_id, self.ndp_proxy.updated_at, ) - self.network_client.create_ndp_proxy = mock.Mock( - return_value=self.ndp_proxy - ) + self.network_client.create_ndp_proxy.return_value = self.ndp_proxy # Get the command object to test self.cmd = ndp_proxy.CreateNDPProxy(self.app, None) @@ -127,10 +124,8 @@ def setUp(self): attrs = {'router_id': self.router.id, 'port_id': self.port.id} self.ndp_proxies = network_fakes.create_ndp_proxies(attrs) self.ndp_proxy = self.ndp_proxies[0] - self.network_client.delete_ndp_proxy = mock.Mock(return_value=None) - self.network_client.find_ndp_proxy = mock.Mock( - return_value=self.ndp_proxy - ) + self.network_client.delete_ndp_proxy.return_value = None + self.network_client.find_ndp_proxy.return_value = self.ndp_proxy # Get the command object to test self.cmd = ndp_proxy.DeleteNDPProxy(self.app, None) @@ -203,7 +198,7 @@ def setUp(self): ) ) - self.network_client.ndp_proxies = mock.Mock(return_value=ndp_proxies) + self.network_client.ndp_proxies.return_value = ndp_proxies # Get the command object to test self.cmd = ndp_proxy.ListNDPProxy(self.app, None) @@ -340,10 +335,8 @@ def setUp(self): super().setUp() attrs = {'router_id': self.router.id, 'port_id': self.port.id} self.ndp_proxy = network_fakes.create_one_ndp_proxy(attrs) - self.network_client.update_ndp_proxy = mock.Mock(return_value=None) - self.network_client.find_ndp_proxy = mock.Mock( - return_value=self.ndp_proxy - ) + self.network_client.update_ndp_proxy.return_value = None + self.network_client.find_ndp_proxy.return_value = self.ndp_proxy # Get the command object to test self.cmd = ndp_proxy.SetNDPProxy(self.app, None) @@ -434,12 +427,9 @@ def setUp(self): self.ndp_proxy.router_id, self.ndp_proxy.updated_at, ) - self.network_client.get_ndp_proxy = mock.Mock( - return_value=self.ndp_proxy - ) - self.network_client.find_ndp_proxy = mock.Mock( - return_value=self.ndp_proxy - ) + self.network_client.get_ndp_proxy.return_value = self.ndp_proxy + + self.network_client.find_ndp_proxy.return_value = self.ndp_proxy # Get the command object to test self.cmd = ndp_proxy.ShowNDPProxy(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index b2e65f213..1e923d053 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -12,7 +12,6 @@ # import random -from unittest import mock from unittest.mock import call from osc_lib.cli import format_columns @@ -116,19 +115,16 @@ class TestCreateNetworkIdentityV3(TestNetwork): def setUp(self): super().setUp() - self.network_client.create_network = mock.Mock( - return_value=self._network - ) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.create_network.return_value = self._network + + self.network_client.set_tags.return_value = None # Get the command object to test self.cmd = network.CreateNetwork(self.app, None) self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain - self.network_client.find_qos_policy = mock.Mock( - return_value=self.qos_policy - ) + self.network_client.find_qos_policy.return_value = self.qos_policy def test_create_no_options(self): arglist = [] @@ -423,10 +419,9 @@ class TestCreateNetworkIdentityV2( def setUp(self): super().setUp() - self.network_client.create_network = mock.Mock( - return_value=self._network - ) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.create_network.return_value = self._network + + self.network_client.set_tags.return_value = None # Get the command object to test self.cmd = network.CreateNetwork(self.app, None) @@ -498,7 +493,7 @@ def setUp(self): # The networks to delete self._networks = network_fakes.create_networks(count=3) - self.network_client.delete_network = mock.Mock(return_value=None) + self.network_client.delete_network.return_value = None self.network_client.find_network = network_fakes.get_networks( networks=self._networks @@ -557,14 +552,14 @@ def test_delete_multiple_networks_exception(self): exceptions.NotFound('404'), self._networks[1], ] - self.network_client.find_network = mock.Mock(side_effect=ret_find) + self.network_client.find_network.side_effect = ret_find # Fake exception in delete_network() ret_delete = [ None, exceptions.NotFound('404'), ] - self.network_client.delete_network = mock.Mock(side_effect=ret_delete) + self.network_client.delete_network.side_effect = ret_delete self.assertRaises( exceptions.CommandError, self.cmd.take_action, parsed_args @@ -636,13 +631,13 @@ def setUp(self): # Get the command object to test self.cmd = network.ListNetwork(self.app, None) - self.network_client.networks = mock.Mock(return_value=self._network) + self.network_client.networks.return_value = self._network self._agent = network_fakes.create_one_network_agent() self.network_client.get_agent.return_value = self._agent - self.network_client.dhcp_agent_hosting_networks = mock.Mock( - return_value=self._network + self.network_client.dhcp_agent_hosting_networks.return_value = ( + self._network ) # TestListTagMixin @@ -991,15 +986,12 @@ class TestSetNetwork(TestNetwork): def setUp(self): super().setUp() - self.network_client.update_network = mock.Mock(return_value=None) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.update_network.return_value = None + self.network_client.set_tags.return_value = None - self.network_client.find_network = mock.Mock( - return_value=self._network - ) - self.network_client.find_qos_policy = mock.Mock( - return_value=self.qos_policy - ) + self.network_client.find_network.return_value = self._network + + self.network_client.find_qos_policy.return_value = self.qos_policy # Get the command object to test self.cmd = network.SetNetwork(self.app, None) @@ -1242,9 +1234,7 @@ class TestShowNetwork(TestNetwork): def setUp(self): super().setUp() - self.network_client.find_network = mock.Mock( - return_value=self._network - ) + self.network_client.find_network.return_value = self._network # Get the command object to test self.cmd = network.ShowNetwork(self.app, None) @@ -1290,15 +1280,12 @@ class TestUnsetNetwork(TestNetwork): def setUp(self): super().setUp() - self.network_client.update_network = mock.Mock(return_value=None) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.update_network.return_value = None + self.network_client.set_tags.return_value = None - self.network_client.find_network = mock.Mock( - return_value=self._network - ) - self.network_client.find_qos_policy = mock.Mock( - return_value=self.qos_policy - ) + self.network_client.find_network.return_value = self._network + + self.network_client.find_qos_policy.return_value = self.qos_policy # Get the command object to test self.cmd = network.UnsetNetwork(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index bd79950d5..48b394d7a 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -11,7 +11,6 @@ # under the License. # -from unittest import mock from unittest.mock import call from osc_lib.cli import format_columns @@ -34,8 +33,8 @@ class TestAddNetworkToAgent(TestNetworkAgent): def setUp(self): super().setUp() - self.network_client.get_agent = mock.Mock(return_value=self.agent) - self.network_client.find_network = mock.Mock(return_value=self.net) + self.network_client.get_agent.return_value = self.agent + self.network_client.find_network.return_value = self.net self.network_client.name = self.network_client.find_network.name self.cmd = network_agent.AddNetworkToAgent(self.app, None) @@ -76,8 +75,8 @@ class TestAddRouterAgent(TestNetworkAgent): def setUp(self): super().setUp() - self.network_client.get_agent = mock.Mock(return_value=self._agent) - self.network_client.find_router = mock.Mock(return_value=self._router) + self.network_client.get_agent.return_value = self._agent + self.network_client.find_router.return_value = self._router self.cmd = network_agent.AddRouterToAgent(self.app, None) @@ -120,7 +119,7 @@ class TestDeleteNetworkAgent(TestNetworkAgent): def setUp(self): super().setUp() - self.network_client.delete_agent = mock.Mock(return_value=None) + self.network_client.delete_agent.return_value = None # Get the command object to test self.cmd = network_agent.DeleteNetworkAgent(self.app, None) @@ -173,9 +172,7 @@ def test_multi_network_agents_delete_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) delete_mock_result = [True, exceptions.CommandError] - self.network_client.delete_agent = mock.Mock( - side_effect=delete_mock_result - ) + self.network_client.delete_agent.side_effect = delete_mock_result try: self.cmd.take_action(parsed_args) @@ -357,8 +354,8 @@ class TestRemoveNetworkFromAgent(TestNetworkAgent): def setUp(self): super().setUp() - self.network_client.get_agent = mock.Mock(return_value=self.agent) - self.network_client.find_network = mock.Mock(return_value=self.net) + self.network_client.get_agent.return_value = self.agent + self.network_client.find_network.return_value = self.net self.network_client.name = self.network_client.find_network.name self.cmd = network_agent.RemoveNetworkFromAgent(self.app, None) @@ -414,8 +411,8 @@ class TestRemoveRouterAgent(TestNetworkAgent): def setUp(self): super().setUp() - self.network_client.get_agent = mock.Mock(return_value=self._agent) - self.network_client.find_router = mock.Mock(return_value=self._router) + self.network_client.get_agent.return_value = self._agent + self.network_client.find_router.return_value = self._router self.cmd = network_agent.RemoveRouterFromAgent(self.app, None) @@ -458,10 +455,8 @@ class TestSetNetworkAgent(TestNetworkAgent): def setUp(self): super().setUp() - self.network_client.update_agent = mock.Mock(return_value=None) - self.network_client.get_agent = mock.Mock( - return_value=self._network_agent - ) + self.network_client.update_agent.return_value = None + self.network_client.get_agent.return_value = self._network_agent # Get the command object to test self.cmd = network_agent.SetNetworkAgent(self.app, None) @@ -574,9 +569,7 @@ class TestShowNetworkAgent(TestNetworkAgent): def setUp(self): super().setUp() - self.network_client.get_agent = mock.Mock( - return_value=self._network_agent - ) + self.network_client.get_agent.return_value = self._network_agent # Get the command object to test self.cmd = network_agent.ShowNetworkAgent(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py index 20c645bd4..d13bd8cf9 100644 --- a/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py +++ b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from unittest import mock from openstackclient.network.v2 import network_auto_allocated_topology from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes @@ -50,8 +49,8 @@ def setUp(self): self.cmd = network_auto_allocated_topology.CreateAutoAllocatedTopology( self.app, None ) - self.network_client.get_auto_allocated_topology = mock.Mock( - return_value=self.topology + self.network_client.get_auto_allocated_topology.return_value = ( + self.topology ) def test_create_no_options(self): @@ -155,8 +154,8 @@ def setUp(self): self.cmd = network_auto_allocated_topology.CreateAutoAllocatedTopology( self.app, None ) - self.network_client.validate_auto_allocated_topology = mock.Mock( - return_value=self.topology + self.network_client.validate_auto_allocated_topology.return_value = ( + self.topology ) def test_show_dry_run_no_project(self): @@ -228,9 +227,7 @@ def setUp(self): self.cmd = network_auto_allocated_topology.DeleteAutoAllocatedTopology( self.app, None ) - self.network_client.delete_auto_allocated_topology = mock.Mock( - return_value=None - ) + self.network_client.delete_auto_allocated_topology.return_value = None def test_delete_no_project(self): arglist = [] diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor.py b/openstackclient/tests/unit/network/v2/test_network_flavor.py index cfbe1f7b0..10038e393 100644 --- a/openstackclient/tests/unit/network/v2/test_network_flavor.py +++ b/openstackclient/tests/unit/network/v2/test_network_flavor.py @@ -41,11 +41,9 @@ class TestAddNetworkFlavorToProfile(TestNetworkFlavor): def setUp(self): super().setUp() - self.network_client.find_flavor = mock.Mock( - return_value=self.network_flavor - ) - self.network_client.find_service_profile = mock.Mock( - return_value=self.service_profile + self.network_client.find_flavor.return_value = self.network_flavor + self.network_client.find_service_profile.return_value = ( + self.service_profile ) self.cmd = network_flavor.AddNetworkFlavorToProfile(self.app, None) @@ -102,8 +100,8 @@ class TestCreateNetworkFlavor(TestNetworkFlavor): def setUp(self): super().setUp() - self.network_client.create_flavor = mock.Mock( - return_value=self.new_network_flavor + self.network_client.create_flavor.return_value = ( + self.new_network_flavor ) # Get the command object to test @@ -218,7 +216,7 @@ class TestDeleteNetworkFlavor(TestNetworkFlavor): def setUp(self): super().setUp() - self.network_client.delete_flavor = mock.Mock(return_value=None) + self.network_client.delete_flavor.return_value = None self.network_client.find_flavor = network_fakes.get_flavor( network_flavors=self._network_flavors ) @@ -278,9 +276,7 @@ def test_multi_network_flavors_delete_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) find_mock_result = [self._network_flavors[0], exceptions.CommandError] - self.network_client.find_flavor = mock.Mock( - side_effect=find_mock_result - ) + self.network_client.find_flavor.side_effect = find_mock_result try: self.cmd.take_action(parsed_args) @@ -323,9 +319,7 @@ class TestListNetworkFlavor(TestNetworkFlavor): def setUp(self): super().setUp() - self.network_client.flavors = mock.Mock( - return_value=self._network_flavors - ) + self.network_client.flavors.return_value = self._network_flavors # Get the command object to test self.cmd = network_flavor.ListNetworkFlavor(self.app, None) @@ -348,11 +342,9 @@ class TestRemoveNetworkFlavorFromProfile(TestNetworkFlavor): def setUp(self): super().setUp() - self.network_client.find_flavor = mock.Mock( - return_value=self.network_flavor - ) - self.network_client.find_service_profile = mock.Mock( - return_value=self.service_profile + self.network_client.find_flavor.return_value = self.network_flavor + self.network_client.find_service_profile.return_value = ( + self.service_profile ) self.network_client.disassociate_flavor_from_service_profile = ( mock.Mock() @@ -412,9 +404,7 @@ class TestShowNetworkFlavor(TestNetworkFlavor): def setUp(self): super().setUp() - self.network_client.find_flavor = mock.Mock( - return_value=self.new_network_flavor - ) + self.network_client.find_flavor.return_value = self.new_network_flavor # Get the command object to test self.cmd = network_flavor.ShowNetworkFlavor(self.app, None) @@ -456,10 +446,8 @@ class TestSetNetworkFlavor(TestNetworkFlavor): def setUp(self): super().setUp() - self.network_client.update_flavor = mock.Mock(return_value=None) - self.network_client.find_flavor = mock.Mock( - return_value=self.new_network_flavor - ) + self.network_client.update_flavor.return_value = None + self.network_client.find_flavor.return_value = self.new_network_flavor # Get the command object to test self.cmd = network_flavor.SetNetworkFlavor(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py index d94926c4c..c8235bfef 100644 --- a/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py +++ b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py @@ -49,9 +49,10 @@ class TestCreateFlavorProfile(TestFlavorProfile): def setUp(self): super().setUp() - self.network_client.create_service_profile = mock.Mock( - return_value=self.new_flavor_profile + self.network_client.create_service_profile.return_value = ( + self.new_flavor_profile ) + # Get the command object to test self.cmd = network_flavor_profile.CreateNetworkFlavorProfile( self.app, None @@ -195,9 +196,8 @@ class TestDeleteFlavorProfile(TestFlavorProfile): def setUp(self): super().setUp() - self.network_client.delete_service_profile = mock.Mock( - return_value=None - ) + self.network_client.delete_service_profile.return_value = None + self.network_client.find_service_profile = ( network_fakes.get_service_profile( flavor_profile=self._network_flavor_profiles @@ -266,9 +266,7 @@ def test_multi_network_flavor_profiles_delete_with_exception(self): self._network_flavor_profiles[0], exceptions.CommandError, ] - self.network_client.find_service_profile = mock.Mock( - side_effect=find_mock_result - ) + self.network_client.find_service_profile.side_effect = find_mock_result try: self.cmd.take_action(parsed_args) @@ -314,8 +312,8 @@ class TestListFlavorProfile(TestFlavorProfile): def setUp(self): super().setUp() - self.network_client.service_profiles = mock.Mock( - return_value=self._network_flavor_profiles + self.network_client.service_profiles.return_value = ( + self._network_flavor_profiles ) # Get the command object to test @@ -355,8 +353,8 @@ class TestShowFlavorProfile(TestFlavorProfile): def setUp(self): super().setUp() - self.network_client.find_service_profile = mock.Mock( - return_value=self.network_flavor_profile + self.network_client.find_service_profile.return_value = ( + self.network_flavor_profile ) # Get the command object to test @@ -388,11 +386,10 @@ class TestSetFlavorProfile(TestFlavorProfile): def setUp(self): super().setUp() - self.network_client.update_service_profile = mock.Mock( - return_value=None - ) - self.network_client.find_service_profile = mock.Mock( - return_value=self.network_flavor_profile + self.network_client.update_service_profile.return_value = None + + self.network_client.find_service_profile.return_value = ( + self.network_flavor_profile ) # Get the command object to test diff --git a/openstackclient/tests/unit/network/v2/test_network_meter.py b/openstackclient/tests/unit/network/v2/test_network_meter.py index a92035d6d..f13839a62 100644 --- a/openstackclient/tests/unit/network/v2/test_network_meter.py +++ b/openstackclient/tests/unit/network/v2/test_network_meter.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from unittest import mock from unittest.mock import call from osc_lib import exceptions @@ -55,9 +54,8 @@ class TestCreateMeter(TestMeter): def setUp(self): super().setUp() - self.network_client.create_metering_label = mock.Mock( - return_value=self.new_meter - ) + self.network_client.create_metering_label.return_value = self.new_meter + self.projects_mock.get.return_value = self.project self.cmd = network_meter.CreateMeter(self.app, None) @@ -132,9 +130,7 @@ def setUp(self): self.meter_list = network_fakes.FakeNetworkMeter.create_meter(count=2) - self.network_client.delete_metering_label = mock.Mock( - return_value=None - ) + self.network_client.delete_metering_label.return_value = None self.network_client.find_metering_label = ( network_fakes.FakeNetworkMeter.get_meter(meter=self.meter_list) @@ -194,15 +190,13 @@ def test_delete_multiple_meter_exception(self): exceptions.NotFound('404'), self.meter_list[1], ] - self.network_client.find_meter = mock.Mock(side_effect=return_find) + self.network_client.find_metering_label.side_effect = return_find ret_delete = [ None, exceptions.NotFound('404'), ] - self.network_client.delete_metering_label = mock.Mock( - side_effect=ret_delete - ) + self.network_client.delete_metering_label.side_effect = ret_delete self.assertRaises( exceptions.CommandError, self.cmd.take_action, parsed_args @@ -240,9 +234,7 @@ class TestListMeter(TestMeter): def setUp(self): super().setUp() - self.network_client.metering_labels = mock.Mock( - return_value=self.meter_list - ) + self.network_client.metering_labels.return_value = self.meter_list self.cmd = network_meter.ListMeter(self.app, None) @@ -282,9 +274,7 @@ def setUp(self): self.cmd = network_meter.ShowMeter(self.app, None) - self.network_client.find_metering_label = mock.Mock( - return_value=self.new_meter - ) + self.network_client.find_metering_label.return_value = self.new_meter def test_show_no_options(self): arglist = [] diff --git a/openstackclient/tests/unit/network/v2/test_network_meter_rule.py b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py index e90ed05f3..407fb04bb 100644 --- a/openstackclient/tests/unit/network/v2/test_network_meter_rule.py +++ b/openstackclient/tests/unit/network/v2/test_network_meter_rule.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from unittest import mock from unittest.mock import call from osc_lib import exceptions @@ -65,14 +64,13 @@ def setUp(self): {'id': self.new_rule.metering_label_id} ) - self.network_client.create_metering_label_rule = mock.Mock( - return_value=self.new_rule + self.network_client.create_metering_label_rule.return_value = ( + self.new_rule ) + self.projects_mock.get.return_value = self.project self.cmd = network_meter_rule.CreateMeterRule(self.app, None) - self.network_client.find_metering_label = mock.Mock( - return_value=fake_meter - ) + self.network_client.find_metering_label.return_value = fake_meter def test_create_no_options(self): arglist = [] @@ -146,9 +144,7 @@ def setUp(self): self.rule_list = network_fakes.FakeNetworkMeterRule.create_meter_rule( count=2 ) - self.network_client.delete_metering_label_rule = mock.Mock( - return_value=None - ) + self.network_client.delete_metering_label_rule.return_value = None self.network_client.find_metering_label_rule = ( network_fakes.FakeNetworkMeterRule.get_meter_rule( @@ -210,17 +206,13 @@ def test_delete_multiple_rules_exception(self): exceptions.NotFound('404'), self.rule_list[1], ] - self.network_client.find_metering_label_rule = mock.Mock( - side_effect=return_find - ) + self.network_client.find_metering_label_rule.side_effect = return_find ret_delete = [ None, exceptions.NotFound('404'), ] - self.network_client.delete_metering_label_rule = mock.Mock( - side_effect=ret_delete - ) + self.network_client.delete_metering_label_rule.side_effect = ret_delete self.assertRaises( exceptions.CommandError, self.cmd.take_action, parsed_args @@ -262,9 +254,7 @@ class TestListMeterRule(TestMeterRule): def setUp(self): super().setUp() - self.network_client.metering_label_rules = mock.Mock( - return_value=self.rule_list - ) + self.network_client.metering_label_rules.return_value = self.rule_list self.cmd = network_meter_rule.ListMeterRule(self.app, None) @@ -311,8 +301,8 @@ def setUp(self): self.cmd = network_meter_rule.ShowMeterRule(self.app, None) - self.network_client.find_metering_label_rule = mock.Mock( - return_value=self.new_rule + self.network_client.find_metering_label_rule.return_value = ( + self.new_rule ) def test_show_no_options(self): diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py index fae5b69d5..17f40ef6b 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py @@ -63,8 +63,8 @@ class TestCreateNetworkQosPolicy(TestQosPolicy): def setUp(self): super().setUp() - self.network_client.create_qos_policy = mock.Mock( - return_value=self.new_qos_policy + self.network_client.create_qos_policy.return_value = ( + self.new_qos_policy ) # Get the command object to test @@ -163,7 +163,7 @@ class TestDeleteNetworkQosPolicy(TestQosPolicy): def setUp(self): super().setUp() - self.network_client.delete_qos_policy = mock.Mock(return_value=None) + self.network_client.delete_qos_policy.return_value = None self.network_client.find_qos_policy = network_fakes.get_qos_policies( qos_policies=self._qos_policies ) @@ -264,9 +264,7 @@ class TestListNetworkQosPolicy(TestQosPolicy): def setUp(self): super().setUp() - self.network_client.qos_policies = mock.Mock( - return_value=self.qos_policies - ) + self.network_client.qos_policies.return_value = self.qos_policies # Get the command object to test self.cmd = network_qos_policy.ListNetworkQosPolicy(self.app, None) @@ -344,10 +342,8 @@ class TestSetNetworkQosPolicy(TestQosPolicy): def setUp(self): super().setUp() - self.network_client.update_qos_policy = mock.Mock(return_value=None) - self.network_client.find_qos_policy = mock.Mock( - return_value=self._qos_policy - ) + self.network_client.update_qos_policy.return_value = None + self.network_client.find_qos_policy.return_value = self._qos_policy # Get the command object to test self.cmd = network_qos_policy.SetNetworkQosPolicy(self.app, None) @@ -447,9 +443,7 @@ class TestShowNetworkQosPolicy(TestQosPolicy): def setUp(self): super().setUp() - self.network_client.find_qos_policy = mock.Mock( - return_value=self._qos_policy - ) + self.network_client.find_qos_policy.return_value = self._qos_policy # Get the command object to test self.cmd = network_qos_policy.ShowNetworkQosPolicy(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py index e6586849e..430030402 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py @@ -56,9 +56,7 @@ class TestNetworkQosRule(network_fakes.TestNetworkV2): def setUp(self): super().setUp() self.qos_policy = network_fakes.create_one_qos_policy() - self.network_client.find_qos_policy = mock.Mock( - return_value=self.qos_policy - ) + self.network_client.find_qos_policy.return_value = self.qos_policy class TestCreateNetworkQosRuleMinimumBandwidth(TestNetworkQosRule): @@ -87,8 +85,8 @@ def setUp(self): self.new_rule.project_id, self.new_rule.type, ) - self.network_client.create_qos_minimum_bandwidth_rule = mock.Mock( - return_value=self.new_rule + self.network_client.create_qos_minimum_bandwidth_rule.return_value = ( + self.new_rule ) # Get the command object to test @@ -190,9 +188,7 @@ def setUp(self): self.new_rule.project_id, self.new_rule.type, ) - self.network_client.create_qos_minimum_packet_rate_rule = mock.Mock( - return_value=self.new_rule - ) + self.network_client.create_qos_minimum_packet_rate_rule.return_value = self.new_rule # Get the command object to test self.cmd = network_qos_rule.CreateNetworkQosRule(self.app, None) @@ -291,8 +287,8 @@ def setUp(self): self.new_rule.project_id, self.new_rule.type, ) - self.network_client.create_qos_dscp_marking_rule = mock.Mock( - return_value=self.new_rule + self.network_client.create_qos_dscp_marking_rule.return_value = ( + self.new_rule ) # Get the command object to test @@ -389,8 +385,8 @@ def setUp(self): self.new_rule.project_id, self.new_rule.type, ) - self.network_client.create_qos_bandwidth_limit_rule = mock.Mock( - return_value=self.new_rule + self.network_client.create_qos_bandwidth_limit_rule.return_value = ( + self.new_rule ) # Get the command object to test @@ -529,9 +525,10 @@ def setUp(self): } self.new_rule = network_fakes.create_one_qos_rule(attrs) self.qos_policy.rules = [self.new_rule] - self.network_client.delete_qos_minimum_bandwidth_rule = mock.Mock( - return_value=None + self.network_client.delete_qos_minimum_bandwidth_rule.return_value = ( + None ) + self.network_client.find_qos_minimum_bandwidth_rule = ( network_fakes.get_qos_rules(qos_rules=self.new_rule) ) @@ -592,9 +589,8 @@ def setUp(self): } self.new_rule = network_fakes.create_one_qos_rule(attrs) self.qos_policy.rules = [self.new_rule] - self.network_client.delete_qos_minimum_packet_rate_rule = mock.Mock( - return_value=None - ) + self.network_client.delete_qos_minimum_packet_rate_rule.return_value = None + self.network_client.find_qos_minimum_packet_rate_rule = ( network_fakes.get_qos_rules(qos_rules=self.new_rule) ) @@ -655,9 +651,8 @@ def setUp(self): } self.new_rule = network_fakes.create_one_qos_rule(attrs) self.qos_policy.rules = [self.new_rule] - self.network_client.delete_qos_dscp_marking_rule = mock.Mock( - return_value=None - ) + self.network_client.delete_qos_dscp_marking_rule.return_value = None + self.network_client.find_qos_dscp_marking_rule = ( network_fakes.get_qos_rules(qos_rules=self.new_rule) ) @@ -718,9 +713,8 @@ def setUp(self): } self.new_rule = network_fakes.create_one_qos_rule(attrs) self.qos_policy.rules = [self.new_rule] - self.network_client.delete_qos_bandwidth_limit_rule = mock.Mock( - return_value=None - ) + self.network_client.delete_qos_bandwidth_limit_rule.return_value = None + self.network_client.find_qos_bandwidth_limit_rule = ( network_fakes.get_qos_rules(qos_rules=self.new_rule) ) @@ -781,16 +775,16 @@ def setUp(self): } self.new_rule = network_fakes.create_one_qos_rule(attrs) self.qos_policy.rules = [self.new_rule] - self.network_client.update_qos_minimum_bandwidth_rule = mock.Mock( - return_value=None + self.network_client.update_qos_minimum_bandwidth_rule.return_value = ( + None ) - self.network_client.find_qos_minimum_bandwidth_rule = mock.Mock( - return_value=self.new_rule - ) - self.network_client.find_qos_policy = mock.Mock( - return_value=self.qos_policy + + self.network_client.find_qos_minimum_bandwidth_rule.return_value = ( + self.new_rule ) + self.network_client.find_qos_policy.return_value = self.qos_policy + # Get the command object to test self.cmd = network_qos_rule.SetNetworkQosRule(self.app, None) @@ -883,16 +877,14 @@ def setUp(self): } self.new_rule = network_fakes.create_one_qos_rule(attrs) self.qos_policy.rules = [self.new_rule] - self.network_client.update_qos_minimum_packet_rate_rule = mock.Mock( - return_value=None - ) - self.network_client.find_qos_minimum_packet_rate_rule = mock.Mock( - return_value=self.new_rule - ) - self.network_client.find_qos_policy = mock.Mock( - return_value=self.qos_policy + self.network_client.update_qos_minimum_packet_rate_rule.return_value = None + + self.network_client.find_qos_minimum_packet_rate_rule.return_value = ( + self.new_rule ) + self.network_client.find_qos_policy.return_value = self.qos_policy + # Get the command object to test self.cmd = network_qos_rule.SetNetworkQosRule(self.app, None) @@ -985,16 +977,14 @@ def setUp(self): } self.new_rule = network_fakes.create_one_qos_rule(attrs) self.qos_policy.rules = [self.new_rule] - self.network_client.update_qos_dscp_marking_rule = mock.Mock( - return_value=None - ) - self.network_client.find_qos_dscp_marking_rule = mock.Mock( - return_value=self.new_rule - ) - self.network_client.find_qos_policy = mock.Mock( - return_value=self.qos_policy + self.network_client.update_qos_dscp_marking_rule.return_value = None + + self.network_client.find_qos_dscp_marking_rule.return_value = ( + self.new_rule ) + self.network_client.find_qos_policy.return_value = self.qos_policy + # Get the command object to test self.cmd = network_qos_rule.SetNetworkQosRule(self.app, None) @@ -1351,8 +1341,8 @@ def setUp(self): self.new_rule.type, ) - self.network_client.get_qos_minimum_bandwidth_rule = mock.Mock( - return_value=self.new_rule + self.network_client.get_qos_minimum_bandwidth_rule.return_value = ( + self.new_rule ) # Get the command object to test @@ -1415,8 +1405,8 @@ def setUp(self): self.new_rule.type, ) - self.network_client.get_qos_minimum_packet_rate_rule = mock.Mock( - return_value=self.new_rule + self.network_client.get_qos_minimum_packet_rate_rule.return_value = ( + self.new_rule ) # Get the command object to test @@ -1477,8 +1467,8 @@ def setUp(self): self.new_rule.type, ) - self.network_client.get_qos_dscp_marking_rule = mock.Mock( - return_value=self.new_rule + self.network_client.get_qos_dscp_marking_rule.return_value = ( + self.new_rule ) # Get the command object to test @@ -1543,8 +1533,8 @@ def setUp(self): self.new_rule.type, ) - self.network_client.get_qos_bandwidth_limit_rule = mock.Mock( - return_value=self.new_rule + self.network_client.get_qos_bandwidth_limit_rule.return_value = ( + self.new_rule ) # Get the command object to test diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py index c0aafa0bc..1c50b9e93 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule_type.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from unittest import mock from openstackclient.network.v2 import network_qos_rule_type as _qos_rule_type from openstackclient.tests.unit.network.v2 import fakes as network_fakes @@ -35,9 +34,7 @@ class TestShowNetworkQosRuleType(TestNetworkQosRuleType): def setUp(self): super().setUp() - self.network_client.get_qos_rule_type = mock.Mock( - return_value=self.qos_rule_type - ) + self.network_client.get_qos_rule_type.return_value = self.qos_rule_type # Get the command object to test self.cmd = _qos_rule_type.ShowNetworkQosRuleType(self.app, None) @@ -84,9 +81,7 @@ class TestListNetworkQosRuleType(TestNetworkQosRuleType): def setUp(self): super().setUp() - self.network_client.qos_rule_types = mock.Mock( - return_value=self.qos_rule_types - ) + self.network_client.qos_rule_types.return_value = self.qos_rule_types # Get the command object to test self.cmd = _qos_rule_type.ListNetworkQosRuleType(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index 816e93237..d3a719245 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -11,7 +11,6 @@ # under the License. # -from unittest import mock from unittest.mock import call import ddt @@ -72,27 +71,20 @@ def setUp(self): # Get the command object to test self.cmd = network_rbac.CreateNetworkRBAC(self.app, None) - self.network_client.create_rbac_policy = mock.Mock( - return_value=self.rbac_policy - ) - self.network_client.find_network = mock.Mock( - return_value=self.network_object - ) - self.network_client.find_qos_policy = mock.Mock( - return_value=self.qos_object - ) - self.network_client.find_security_group = mock.Mock( - return_value=self.sg_object - ) - self.network_client.find_address_scope = mock.Mock( - return_value=self.as_object - ) - self.network_client.find_subnet_pool = mock.Mock( - return_value=self.snp_object - ) - self.network_client.find_address_group = mock.Mock( - return_value=self.ag_object - ) + self.network_client.create_rbac_policy.return_value = self.rbac_policy + + self.network_client.find_network.return_value = self.network_object + + self.network_client.find_qos_policy.return_value = self.qos_object + + self.network_client.find_security_group.return_value = self.sg_object + + self.network_client.find_address_scope.return_value = self.as_object + + self.network_client.find_subnet_pool.return_value = self.snp_object + + self.network_client.find_address_group.return_value = self.ag_object + self.projects_mock.get.return_value = self.project def test_network_rbac_create_no_type(self): @@ -343,7 +335,7 @@ class TestDeleteNetworkRBAC(TestNetworkRBAC): def setUp(self): super().setUp() - self.network_client.delete_rbac_policy = mock.Mock(return_value=None) + self.network_client.delete_rbac_policy.return_value = None self.network_client.find_rbac_policy = network_fakes.get_network_rbacs( rbac_policies=self.rbac_policies ) @@ -400,9 +392,7 @@ def test_multi_network_policies_delete_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) find_mock_result = [self.rbac_policies[0], exceptions.CommandError] - self.network_client.find_rbac_policy = mock.Mock( - side_effect=find_mock_result - ) + self.network_client.find_rbac_policy.side_effect = find_mock_result try: self.cmd.take_action(parsed_args) @@ -462,9 +452,7 @@ def setUp(self): # Get the command object to test self.cmd = network_rbac.ListNetworkRBAC(self.app, None) - self.network_client.rbac_policies = mock.Mock( - return_value=self.rbac_policies - ) + self.network_client.rbac_policies.return_value = self.rbac_policies self.project = identity_fakes_v3.FakeProject.create_one_project() self.projects_mock.get.return_value = self.project @@ -564,10 +552,9 @@ def setUp(self): # Get the command object to test self.cmd = network_rbac.SetNetworkRBAC(self.app, None) - self.network_client.find_rbac_policy = mock.Mock( - return_value=self.rbac_policy - ) - self.network_client.update_rbac_policy = mock.Mock(return_value=None) + self.network_client.find_rbac_policy.return_value = self.rbac_policy + + self.network_client.update_rbac_policy.return_value = None self.projects_mock.get.return_value = self.project def test_network_rbac_set_nothing(self): @@ -639,9 +626,7 @@ def setUp(self): # Get the command object to test self.cmd = network_rbac.ShowNetworkRBAC(self.app, None) - self.network_client.find_rbac_policy = mock.Mock( - return_value=self.rbac_policy - ) + self.network_client.find_rbac_policy.return_value = self.rbac_policy def test_show_no_options(self): arglist = [] diff --git a/openstackclient/tests/unit/network/v2/test_network_segment.py b/openstackclient/tests/unit/network/v2/test_network_segment.py index 936a20aef..ab71c3254 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment.py @@ -11,7 +11,6 @@ # under the License. # -from unittest import mock from unittest.mock import call from osc_lib import exceptions @@ -58,12 +57,9 @@ class TestCreateNetworkSegment(TestNetworkSegment): def setUp(self): super().setUp() - self.network_client.create_segment = mock.Mock( - return_value=self._network_segment - ) - self.network_client.find_network = mock.Mock( - return_value=self._network - ) + self.network_client.create_segment.return_value = self._network_segment + + self.network_client.find_network.return_value = self._network # Get the command object to test self.cmd = network_segment.CreateNetworkSegment(self.app, None) @@ -172,10 +168,8 @@ class TestDeleteNetworkSegment(TestNetworkSegment): def setUp(self): super().setUp() - self.network_client.delete_segment = mock.Mock(return_value=None) - self.network_client.find_segment = mock.Mock( - side_effect=self._network_segments - ) + self.network_client.delete_segment.return_value = None + self.network_client.find_segment.side_effect = self._network_segments # Get the command object to test self.cmd = network_segment.DeleteNetworkSegment(self.app, None) @@ -224,9 +218,7 @@ def test_delete_multiple_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) find_mock_result = [self._network_segments[0], exceptions.CommandError] - self.network_client.find_segment = mock.Mock( - side_effect=find_mock_result - ) + self.network_client.find_segment.side_effect = find_mock_result try: self.cmd.take_action(parsed_args) @@ -291,12 +283,9 @@ def setUp(self): # Get the command object to test self.cmd = network_segment.ListNetworkSegment(self.app, None) - self.network_client.find_network = mock.Mock( - return_value=self._network - ) - self.network_client.segments = mock.Mock( - return_value=self._network_segments - ) + self.network_client.find_network.return_value = self._network + + self.network_client.segments.return_value = self._network_segments def test_list_no_option(self): arglist = [] @@ -352,12 +341,9 @@ class TestSetNetworkSegment(TestNetworkSegment): def setUp(self): super().setUp() - self.network_client.find_segment = mock.Mock( - return_value=self._network_segment - ) - self.network_client.update_segment = mock.Mock( - return_value=self._network_segment - ) + self.network_client.find_segment.return_value = self._network_segment + + self.network_client.update_segment.return_value = self._network_segment # Get the command object to test self.cmd = network_segment.SetNetworkSegment(self.app, None) @@ -432,9 +418,7 @@ class TestShowNetworkSegment(TestNetworkSegment): def setUp(self): super().setUp() - self.network_client.find_segment = mock.Mock( - return_value=self._network_segment - ) + self.network_client.find_segment.return_value = self._network_segment # Get the command object to test self.cmd = network_segment.ShowNetworkSegment(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_network_segment_range.py b/openstackclient/tests/unit/network/v2/test_network_segment_range.py index d5e406f39..9c9c900e3 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment_range.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment_range.py @@ -78,8 +78,8 @@ class TestCreateNetworkSegmentRange(TestNetworkSegmentRange): def setUp(self): super().setUp() - self.network_client.create_network_segment_range = mock.Mock( - return_value=self._network_segment_range + self.network_client.create_network_segment_range.return_value = ( + self._network_segment_range ) # Get the command object to test @@ -351,11 +351,10 @@ class TestDeleteNetworkSegmentRange(TestNetworkSegmentRange): def setUp(self): super().setUp() - self.network_client.delete_network_segment_range = mock.Mock( - return_value=None - ) - self.network_client.find_network_segment_range = mock.Mock( - side_effect=self._network_segment_ranges + self.network_client.delete_network_segment_range.return_value = None + + self.network_client.find_network_segment_range.side_effect = ( + self._network_segment_ranges ) # Get the command object to test @@ -412,8 +411,8 @@ def test_delete_multiple_with_exception(self): self._network_segment_ranges[0], exceptions.CommandError, ] - self.network_client.find_network_segment_range = mock.Mock( - side_effect=find_mock_result + self.network_client.find_network_segment_range.side_effect = ( + find_mock_result ) try: @@ -493,8 +492,8 @@ class TestListNetworkSegmentRange(TestNetworkSegmentRange): def setUp(self): super().setUp() - self.network_client.network_segment_ranges = mock.Mock( - return_value=self._network_segment_ranges + self.network_client.network_segment_ranges.return_value = ( + self._network_segment_ranges ) # Get the command object to test @@ -567,8 +566,8 @@ class TestSetNetworkSegmentRange(TestNetworkSegmentRange): def setUp(self): super().setUp() - self.network_client.find_network_segment_range = mock.Mock( - return_value=self._network_segment_range + self.network_client.find_network_segment_range.return_value = ( + self._network_segment_range ) # Get the command object to test @@ -583,9 +582,10 @@ def test_set_no_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.network_client.update_network_segment_range = mock.Mock( - return_value=self._network_segment_range + self.network_client.update_network_segment_range.return_value = ( + self._network_segment_range ) + result = self.cmd.take_action(parsed_args) self.network_client.update_network_segment_range.assert_called_once_with( @@ -611,9 +611,10 @@ def test_set_all_options(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.network_client.update_network_segment_range = mock.Mock( - return_value=self._network_segment_range_updated + self.network_client.update_network_segment_range.return_value = ( + self._network_segment_range_updated ) + result = self.cmd.take_action(parsed_args) attrs = { @@ -662,8 +663,8 @@ class TestShowNetworkSegmentRange(TestNetworkSegmentRange): def setUp(self): super().setUp() - self.network_client.find_network_segment_range = mock.Mock( - return_value=self._network_segment_range + self.network_client.find_network_segment_range.return_value = ( + self._network_segment_range ) # Get the command object to test diff --git a/openstackclient/tests/unit/network/v2/test_network_service_provider.py b/openstackclient/tests/unit/network/v2/test_network_service_provider.py index 9f61bce59..f84bcd38d 100644 --- a/openstackclient/tests/unit/network/v2/test_network_service_provider.py +++ b/openstackclient/tests/unit/network/v2/test_network_service_provider.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from unittest import mock from openstackclient.network.v2 import ( network_service_provider as service_provider, @@ -52,9 +51,7 @@ class TestListNetworkServiceProvider(TestNetworkServiceProvider): def setUp(self): super().setUp() - self.network_client.service_providers = mock.Mock( - return_value=self.provider_list - ) + self.network_client.service_providers.return_value = self.provider_list self.cmd = service_provider.ListNetworkServiceProvider(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_network_trunk.py b/openstackclient/tests/unit/network/v2/test_network_trunk.py index 180e80de5..3b6c364d3 100644 --- a/openstackclient/tests/unit/network/v2/test_network_trunk.py +++ b/openstackclient/tests/unit/network/v2/test_network_trunk.py @@ -11,7 +11,6 @@ # under the License. import copy -from unittest import mock from unittest.mock import call from osc_lib.cli import format_columns @@ -84,12 +83,12 @@ class TestCreateNetworkTrunk(TestNetworkTrunk): def setUp(self): super().setUp() - self.network_client.create_trunk = mock.Mock( - return_value=self.new_trunk - ) - self.network_client.find_port = mock.Mock( - side_effect=[self.parent_port, self.sub_port] - ) + self.network_client.create_trunk.return_value = self.new_trunk + + self.network_client.find_port.side_effect = [ + self.parent_port, + self.sub_port, + ] # Get the command object to test self.cmd = network_trunk.CreateNetworkTrunk(self.app, None) @@ -313,13 +312,16 @@ class TestDeleteNetworkTrunk(TestNetworkTrunk): def setUp(self): super().setUp() - self.network_client.find_trunk = mock.Mock( - side_effect=[self.new_trunks[0], self.new_trunks[1]] - ) - self.network_client.delete_trunk = mock.Mock(return_value=None) - self.network_client.find_port = mock.Mock( - side_effect=[self.parent_port, self.sub_port] - ) + self.network_client.find_trunk.side_effect = [ + self.new_trunks[0], + self.new_trunks[1], + ] + + self.network_client.delete_trunk.return_value = None + self.network_client.find_port.side_effect = [ + self.parent_port, + self.sub_port, + ] self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain @@ -371,9 +373,11 @@ def test_delete_trunk_multiple_with_exception(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.network_client.find_trunk = mock.Mock( - side_effect=[self.new_trunks[0], exceptions.CommandError] - ) + self.network_client.find_trunk.side_effect = [ + self.new_trunks[0], + exceptions.CommandError, + ] + with testtools.ExpectedException(exceptions.CommandError) as e: self.cmd.take_action(parsed_args) self.assertEqual('1 of 2 trunks failed to delete.', str(e)) @@ -412,8 +416,8 @@ class TestShowNetworkTrunk(TestNetworkTrunk): def setUp(self): super().setUp() - self.network_client.find_trunk = mock.Mock(return_value=self.new_trunk) - self.network_client.get_trunk = mock.Mock(return_value=self.new_trunk) + self.network_client.find_trunk.return_value = self.new_trunk + self.network_client.get_trunk.return_value = self.new_trunk self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain @@ -485,7 +489,7 @@ class TestListNetworkTrunk(TestNetworkTrunk): def setUp(self): super().setUp() - self.network_client.trunks = mock.Mock(return_value=self.new_trunks) + self.network_client.trunks.return_value = self.new_trunks self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain @@ -564,14 +568,14 @@ class TestSetNetworkTrunk(TestNetworkTrunk): def setUp(self): super().setUp() - self.network_client.update_trunk = mock.Mock(return_value=self._trunk) - self.network_client.add_trunk_subports = mock.Mock( - return_value=self._trunk - ) - self.network_client.find_trunk = mock.Mock(return_value=self._trunk) - self.network_client.find_port = mock.Mock( - side_effect=[self.sub_port, self.sub_port] - ) + self.network_client.update_trunk.return_value = self._trunk + self.network_client.add_trunk_subports.return_value = self._trunk + + self.network_client.find_trunk.return_value = self._trunk + self.network_client.find_port.side_effect = [ + self.sub_port, + self.sub_port, + ] self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain @@ -763,9 +767,8 @@ def test_set_trunk_attrs_with_exception(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.network_client.update_trunk = mock.Mock( - side_effect=exceptions.CommandError - ) + self.network_client.update_trunk.side_effect = exceptions.CommandError + with testtools.ExpectedException(exceptions.CommandError) as e: self.cmd.take_action(parsed_args) self.assertEqual( @@ -790,12 +793,12 @@ def test_set_trunk_add_subport_with_exception(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.network_client.add_trunk_subports = mock.Mock( - side_effect=exceptions.CommandError - ) - self.network_client.find_port = mock.Mock( - return_value={'id': 'invalid_subport'} + self.network_client.add_trunk_subports.side_effect = ( + exceptions.CommandError ) + + self.network_client.find_port.side_effect = [{'id': 'invalid_subport'}] + with testtools.ExpectedException(exceptions.CommandError) as e: self.cmd.take_action(parsed_args) self.assertEqual( @@ -832,10 +835,10 @@ class TestListNetworkSubport(TestNetworkTrunk): def setUp(self): super().setUp() - self.network_client.find_trunk = mock.Mock(return_value=self._trunk) - self.network_client.get_trunk_subports = mock.Mock( - return_value={network_trunk.SUB_PORTS: self._subports} - ) + self.network_client.find_trunk.return_value = self._trunk + self.network_client.get_trunk_subports.return_value = { + network_trunk.SUB_PORTS: self._subports + } # Get the command object to test self.cmd = network_trunk.ListNetworkSubport(self.app, None) @@ -902,13 +905,13 @@ class TestUnsetNetworkTrunk(TestNetworkTrunk): def setUp(self): super().setUp() - self.network_client.find_trunk = mock.Mock(return_value=self._trunk) - self.network_client.find_port = mock.Mock( - side_effect=[self.sub_port, self.sub_port] - ) - self.network_client.delete_trunk_subports = mock.Mock( - return_value=None - ) + self.network_client.find_trunk.return_value = self._trunk + self.network_client.find_port.side_effect = [ + self.sub_port, + self.sub_port, + ] + + self.network_client.delete_trunk_subports.return_value = None # Get the command object to test self.cmd = network_trunk.UnsetNetworkTrunk(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 0611be478..11a8711c7 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -136,19 +136,18 @@ class TestCreatePort(TestPort): def setUp(self): super().setUp() - self.network_client.create_port = mock.Mock(return_value=self._port) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.create_port.return_value = self._port + self.network_client.set_tags.return_value = None fake_net = network_fakes.create_one_network( { 'id': self._port.network_id, } ) - self.network_client.find_network = mock.Mock(return_value=fake_net) + self.network_client.find_network.return_value = fake_net self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet() - self.network_client.find_subnet = mock.Mock( - return_value=self.fake_subnet - ) - self.network_client.find_extension = mock.Mock(return_value=[]) + self.network_client.find_subnet.return_value = self.fake_subnet + + self.network_client.find_extension.return_value = [] # Get the command object to test self.cmd = port.CreatePort(self.app, None) @@ -321,9 +320,8 @@ def test_create_json_binding_profile(self): def test_create_with_security_group(self): secgroup = network_fakes.create_one_security_group() - self.network_client.find_security_group = mock.Mock( - return_value=secgroup - ) + self.network_client.find_security_group.return_value = secgroup + arglist = [ '--network', self._port.network_id, @@ -393,9 +391,8 @@ def test_create_port_with_dns_name(self): def test_create_with_security_groups(self): sg_1 = network_fakes.create_one_security_group() sg_2 = network_fakes.create_one_security_group() - self.network_client.find_security_group = mock.Mock( - side_effect=[sg_1, sg_2] - ) + self.network_client.find_security_group.side_effect = [sg_1, sg_2] + arglist = [ '--network', self._port.network_id, @@ -583,9 +580,8 @@ def test_create_port_with_allowed_address_pair(self): def test_create_port_with_qos(self): qos_policy = network_fakes.create_one_qos_policy() - self.network_client.find_qos_policy = mock.Mock( - return_value=qos_policy - ) + self.network_client.find_qos_policy.return_value = qos_policy + arglist = [ '--network', self._port.network_id, @@ -701,9 +697,7 @@ def _test_create_with_tag(self, add_tags=True, add_tags_in_post=True): else: verifylist.append(('no_tag', True)) - self.network_client.find_extension = mock.Mock( - return_value=add_tags_in_post - ) + self.network_client.find_extension.return_value = add_tags_in_post parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -1170,7 +1164,7 @@ class TestDeletePort(TestPort): def setUp(self): super().setUp() - self.network_client.delete_port = mock.Mock(return_value=None) + self.network_client.delete_port.return_value = None self.network_client.find_port = network_fakes.get_ports( ports=self._ports ) @@ -1223,7 +1217,7 @@ def test_multi_ports_delete_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) find_mock_result = [self._ports[0], exceptions.CommandError] - self.network_client.find_port = mock.Mock(side_effect=find_mock_result) + self.network_client.find_port.side_effect = find_mock_result try: self.cmd.take_action(parsed_args) @@ -1324,7 +1318,7 @@ class TestListPort(compute_fakes.FakeClientMixin, TestPort): def setUp(self): super().setUp() - self.network_client.ports = mock.Mock(return_value=self._ports) + self.network_client.ports.return_value = self._ports fake_router = network_fakes.create_one_router( { 'id': 'fake-router-id', @@ -1335,8 +1329,8 @@ def setUp(self): 'id': 'fake-network-id', } ) - self.network_client.find_router = mock.Mock(return_value=fake_router) - self.network_client.find_network = mock.Mock(return_value=fake_network) + self.network_client.find_router.return_value = fake_router + self.network_client.find_network.return_value = fake_network # Get the command object to test self.cmd = port.ListPort(self.app, None) @@ -1550,9 +1544,8 @@ def test_port_list_fixed_ip_opt_subnet_id(self): self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( {'id': subnet_id} ) - self.network_client.find_subnet = mock.Mock( - return_value=self.fake_subnet - ) + self.network_client.find_subnet.return_value = self.fake_subnet + parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -1579,9 +1572,8 @@ def test_port_list_fixed_ip_opts(self): self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( {'id': subnet_id} ) - self.network_client.find_subnet = mock.Mock( - return_value=self.fake_subnet - ) + self.network_client.find_subnet.return_value = self.fake_subnet + parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -1616,9 +1608,8 @@ def test_port_list_fixed_ips(self): 'fields': LIST_FIELDS_TO_RETRIEVE, } ) - self.network_client.find_subnet = mock.Mock( - return_value=self.fake_subnet - ) + self.network_client.find_subnet.return_value = self.fake_subnet + parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -1825,12 +1816,11 @@ class TestSetPort(TestPort): def setUp(self): super().setUp() self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet() - self.network_client.find_subnet = mock.Mock( - return_value=self.fake_subnet - ) - self.network_client.find_port = mock.Mock(return_value=self._port) - self.network_client.update_port = mock.Mock(return_value=None) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.find_subnet.return_value = self.fake_subnet + + self.network_client.find_port.return_value = self._port + self.network_client.update_port.return_value = None + self.network_client.set_tags.return_value = None # Get the command object to test self.cmd = port.SetPort(self.app, None) @@ -1853,7 +1843,7 @@ def test_set_port_fixed_ip(self): _testport = network_fakes.create_one_port( {'fixed_ips': [{'ip_address': '0.0.0.1'}]} ) - self.network_client.find_port = mock.Mock(return_value=_testport) + self.network_client.find_port.return_value = _testport arglist = [ '--fixed-ip', 'ip-address=10.0.0.12', @@ -1881,7 +1871,7 @@ def test_set_port_fixed_ip_clear(self): _testport = network_fakes.create_one_port( {'fixed_ips': [{'ip_address': '0.0.0.1'}]} ) - self.network_client.find_port = mock.Mock(return_value=_testport) + self.network_client.find_port.return_value = _testport arglist = [ '--fixed-ip', 'ip-address=10.0.0.12', @@ -1931,7 +1921,7 @@ def test_set_port_overwrite_binding_profile(self): _testport = network_fakes.create_one_port( {'binding_profile': {'lok_i': 'visi_on'}} ) - self.network_client.find_port = mock.Mock(return_value=_testport) + self.network_client.find_port.return_value = _testport arglist = [ '--binding-profile', 'lok_i=than_os', @@ -1956,7 +1946,7 @@ def test_overwrite_mac_address(self): _testport = network_fakes.create_one_port( {'mac_address': '11:22:33:44:55:66'} ) - self.network_client.find_port = mock.Mock(return_value=_testport) + self.network_client.find_port.return_value = _testport arglist = [ '--mac-address', '66:55:44:33:22:11', @@ -2097,7 +2087,7 @@ def test_set_port_mixed_binding_profile(self): def test_set_port_security_group(self): sg = network_fakes.create_one_security_group() - self.network_client.find_security_group = mock.Mock(return_value=sg) + self.network_client.find_security_group.return_value = sg arglist = [ '--security-group', sg.id, @@ -2122,13 +2112,12 @@ def test_set_port_security_group_append(self): sg_1 = network_fakes.create_one_security_group() sg_2 = network_fakes.create_one_security_group() sg_3 = network_fakes.create_one_security_group() - self.network_client.find_security_group = mock.Mock( - side_effect=[sg_2, sg_3] - ) + self.network_client.find_security_group.side_effect = [sg_2, sg_3] + _testport = network_fakes.create_one_port( {'security_group_ids': [sg_1.id]} ) - self.network_client.find_port = mock.Mock(return_value=_testport) + self.network_client.find_port.return_value = _testport arglist = [ '--security-group', sg_2.id, @@ -2177,8 +2166,8 @@ def test_set_port_security_group_replace(self): _testport = network_fakes.create_one_port( {'security_group_ids': [sg1.id]} ) - self.network_client.find_port = mock.Mock(return_value=_testport) - self.network_client.find_security_group = mock.Mock(return_value=sg2) + self.network_client.find_port.return_value = _testport + self.network_client.find_security_group.return_value = sg2 arglist = [ '--security-group', sg2.id, @@ -2226,7 +2215,7 @@ def test_set_port_append_allowed_address_pair(self): _testport = network_fakes.create_one_port( {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]} ) - self.network_client.find_port = mock.Mock(return_value=_testport) + self.network_client.find_port.return_value = _testport arglist = [ '--allowed-address', 'ip-address=192.168.1.45', @@ -2255,7 +2244,7 @@ def test_set_port_overwrite_allowed_address_pair(self): _testport = network_fakes.create_one_port( {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]} ) - self.network_client.find_port = mock.Mock(return_value=_testport) + self.network_client.find_port.return_value = _testport arglist = [ '--allowed-address', 'ip-address=192.168.1.45', @@ -2372,11 +2361,10 @@ def test_set_port_security_disabled(self): def test_set_port_with_qos(self): qos_policy = network_fakes.create_one_qos_policy() - self.network_client.find_qos_policy = mock.Mock( - return_value=qos_policy - ) + self.network_client.find_qos_policy.return_value = qos_policy + _testport = network_fakes.create_one_port({'qos_policy_id': None}) - self.network_client.find_port = mock.Mock(return_value=_testport) + self.network_client.find_port.return_value = _testport arglist = [ '--qos-policy', qos_policy.id, @@ -2400,7 +2388,7 @@ def test_set_port_with_qos(self): def test_set_port_data_plane_status(self): _testport = network_fakes.create_one_port({'data_plane_status': None}) - self.network_client.find_port = mock.Mock(return_value=_testport) + self.network_client.find_port.return_value = _testport arglist = [ '--data-plane-status', 'ACTIVE', @@ -2546,10 +2534,12 @@ def test_set_hints_invalid_value(self): def test_set_hints_valid_alias_value(self): testport = network_fakes.create_one_port() - self.network_client.find_port = mock.Mock(return_value=testport) - self.network_client.find_extension = mock.Mock( - return_value=['port-hints', 'port-hint-ovs-tx-steering'] - ) + self.network_client.find_port.return_value = testport + self.network_client.find_extension.return_value = [ + 'port-hints', + 'port-hint-ovs-tx-steering', + ] + arglist = [ '--hint', 'ovs-tx-steering=hash', @@ -2574,10 +2564,12 @@ def test_set_hints_valid_alias_value(self): def test_set_hints_valid_json(self): testport = network_fakes.create_one_port() - self.network_client.find_port = mock.Mock(return_value=testport) - self.network_client.find_extension = mock.Mock( - return_value=['port-hints', 'port-hint-ovs-tx-steering'] - ) + self.network_client.find_port.return_value = testport + self.network_client.find_extension.return_value = [ + 'port-hints', + 'port-hint-ovs-tx-steering', + ] + arglist = [ '--hint', '{"openvswitch": {"other_config": {"tx-steering": "hash"}}}', @@ -2670,7 +2662,7 @@ class TestShowPort(TestPort): def setUp(self): super().setUp() - self.network_client.find_port = mock.Mock(return_value=self._port) + self.network_client.find_port.return_value = self._port # Get the command object to test self.cmd = port.ShowPort(self.app, None) @@ -2731,12 +2723,11 @@ def setUp(self): self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet( {'id': '042eb10a-3a18-4658-ab-cf47c8d03152'} ) - self.network_client.find_subnet = mock.Mock( - return_value=self.fake_subnet - ) - self.network_client.find_port = mock.Mock(return_value=self._testport) - self.network_client.update_port = mock.Mock(return_value=None) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.find_subnet.return_value = self.fake_subnet + + self.network_client.find_port.return_value = self._testport + self.network_client.update_port.return_value = None + self.network_client.set_tags.return_value = None # Get the command object to test self.cmd = port.UnsetPort(self.app, None) @@ -2826,10 +2817,9 @@ def test_unset_security_group(self): _fake_port = network_fakes.create_one_port( {'security_group_ids': [_fake_sg1.id, _fake_sg2.id]} ) - self.network_client.find_port = mock.Mock(return_value=_fake_port) - self.network_client.find_security_group = mock.Mock( - return_value=_fake_sg2 - ) + self.network_client.find_port.return_value = _fake_port + self.network_client.find_security_group.return_value = _fake_sg2 + arglist = [ '--security-group', _fake_sg2.id, @@ -2854,9 +2844,8 @@ def test_unset_port_security_group_not_existent(self): _fake_port = network_fakes.create_one_port( {'security_group_ids': [_fake_sg1.id]} ) - self.network_client.find_security_group = mock.Mock( - return_value=_fake_sg2 - ) + self.network_client.find_security_group.return_value = _fake_sg2 + arglist = [ '--security-group', _fake_sg2.id, @@ -2875,7 +2864,7 @@ def test_unset_port_allowed_address_pair(self): _fake_port = network_fakes.create_one_port( {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]} ) - self.network_client.find_port = mock.Mock(return_value=_fake_port) + self.network_client.find_port.return_value = _fake_port arglist = [ '--allowed-address', 'ip-address=192.168.1.123', @@ -2901,7 +2890,7 @@ def test_unset_port_allowed_address_pair_not_existent(self): _fake_port = network_fakes.create_one_port( {'allowed_address_pairs': [{'ip_address': '192.168.1.123'}]} ) - self.network_client.find_port = mock.Mock(return_value=_fake_port) + self.network_client.find_port.return_value = _fake_port arglist = [ '--allowed-address', 'ip-address=192.168.1.45', @@ -2920,7 +2909,7 @@ def test_unset_port_data_plane_status(self): _fake_port = network_fakes.create_one_port( {'data_plane_status': 'ACTIVE'} ) - self.network_client.find_port = mock.Mock(return_value=_fake_port) + self.network_client.find_port.return_value = _fake_port arglist = [ '--data-plane-status', _fake_port.name, @@ -2973,7 +2962,7 @@ def test_unset_numa_affinity_policy(self): _fake_port = network_fakes.create_one_port( {'numa_affinity_policy': 'required'} ) - self.network_client.find_port = mock.Mock(return_value=_fake_port) + self.network_client.find_port.return_value = _fake_port arglist = [ '--numa-policy', _fake_port.name, @@ -2997,7 +2986,7 @@ def test_unset_numa_affinity_policy(self): def test_unset_hints(self): testport = network_fakes.create_one_port() - self.network_client.find_port = mock.Mock(return_value=testport) + self.network_client.find_port.return_value = testport arglist = [ '--hints', testport.name, @@ -3017,7 +3006,7 @@ def test_unset_hints(self): def test_unset_device(self): testport = network_fakes.create_one_port() - self.network_client.find_port = mock.Mock(return_value=testport) + self.network_client.find_port.return_value = testport arglist = [ '--device', testport.name, @@ -3037,7 +3026,7 @@ def test_unset_device(self): def test_unset_device_owner(self): testport = network_fakes.create_one_port() - self.network_client.find_port = mock.Mock(return_value=testport) + self.network_client.find_port.return_value = testport arglist = [ '--device-owner', testport.name, diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 353658330..221fe67a9 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -39,8 +39,8 @@ class TestAddPortToRouter(TestRouter): def setUp(self): super().setUp() - self.network_client.find_router = mock.Mock(return_value=self._router) - self.network_client.find_port = mock.Mock(return_value=self._port) + self.network_client.find_router.return_value = self._router + self.network_client.find_port.return_value = self._port self.cmd = router.AddPortToRouter(self.app, None) @@ -87,8 +87,8 @@ class TestAddSubnetToRouter(TestRouter): def setUp(self): super().setUp() - self.network_client.find_router = mock.Mock(return_value=self._router) - self.network_client.find_subnet = mock.Mock(return_value=self._subnet) + self.network_client.find_router.return_value = self._router + self.network_client.find_subnet.return_value = self._subnet self.cmd = router.AddSubnetToRouter(self.app, None) @@ -172,12 +172,11 @@ class TestCreateRouter(TestRouter): def setUp(self): super().setUp() - self.network_client.create_router = mock.Mock( - return_value=self.new_router - ) - self.network_client.set_tags = mock.Mock(return_value=None) - self.network_client.find_extension = mock.Mock( - side_effect=lambda name: self._extensions.get(name) + self.network_client.create_router.return_value = self.new_router + + self.network_client.set_tags.return_value = None + self.network_client.find_extension.side_effect = ( + lambda name: self._extensions.get(name) ) # Get the command object to test self.cmd = router.CreateRouter(self.app, None) @@ -222,8 +221,8 @@ def test_create_default_options(self): def test_create_with_gateway(self): _network = network_fakes.create_one_network() _subnet = network_fakes.FakeSubnet.create_one_subnet() - self.network_client.find_network = mock.Mock(return_value=_network) - self.network_client.find_subnet = mock.Mock(return_value=_subnet) + self.network_client.find_network.return_value = _network + self.network_client.find_subnet.return_value = _subnet arglist = [ self.new_router.name, '--external-gateway', @@ -392,7 +391,7 @@ def test_create_with_no_tag(self): def test_create_with_flavor_id_id(self): _flavor = network_fakes.create_one_network_flavor() - self.network_client.find_flavor = mock.Mock(return_value=_flavor) + self.network_client.find_flavor.return_value = _flavor arglist = [ self.new_router.name, '--flavor-id', @@ -419,7 +418,7 @@ def test_create_with_flavor_id_id(self): def test_create_with_flavor_id_name(self): _flavor = network_fakes.create_one_network_flavor() - self.network_client.find_flavor = mock.Mock(return_value=_flavor) + self.network_client.find_flavor.return_value = _flavor arglist = [self.new_router.name, '--flavor-id', _flavor.name] verifylist = [ ('name', self.new_router.name), @@ -442,7 +441,7 @@ def test_create_with_flavor_id_name(self): def test_create_with_flavor_id(self): _flavor = network_fakes.create_one_network_flavor() - self.network_client.find_flavor = mock.Mock(return_value=_flavor) + self.network_client.find_flavor.return_value = _flavor arglist = [ self.new_router.name, '--flavor', @@ -469,7 +468,7 @@ def test_create_with_flavor_id(self): def test_create_with_flavor_name(self): _flavor = network_fakes.create_one_network_flavor() - self.network_client.find_flavor = mock.Mock(return_value=_flavor) + self.network_client.find_flavor.return_value = _flavor arglist = [self.new_router.name, '--flavor', _flavor.name] verifylist = [ ('name', self.new_router.name), @@ -491,11 +490,12 @@ def test_create_with_flavor_name(self): self.assertCountEqual(self.data, data) def test_create_with_enable_default_route_bfd(self): - self.network_client.find_extension = mock.Mock( - return_value=network_fakes.create_one_extension( + self._extensions = { + 'external-gateway-multihoming': network_fakes.create_one_extension( attrs={'name': 'external-gateway-multihoming'} - ) - ) + ), + } + arglist = [self.new_router.name, '--enable-default-route-bfd'] verifylist = [ ('name', self.new_router.name), @@ -525,11 +525,12 @@ def test_create_with_enable_default_route_bfd_no_extension(self): ) def test_create_with_enable_default_route_ecmp(self): - self.network_client.find_extension = mock.Mock( - return_value=network_fakes.create_one_extension( + self._extensions = { + 'external-gateway-multihoming': network_fakes.create_one_extension( attrs={'name': 'external-gateway-multihoming'} - ) - ) + ), + } + arglist = [self.new_router.name, '--enable-default-route-ecmp'] verifylist = [ ('name', self.new_router.name), @@ -560,11 +561,10 @@ def test_create_with_enable_default_route_ecmp_no_extension(self): def test_create_with_qos_policy(self): _network = network_fakes.create_one_network() - self.network_client.find_network = mock.Mock(return_value=_network) + self.network_client.find_network.return_value = _network _qos_policy = network_fakes.create_one_qos_policy() - self.network_client.find_qos_policy = mock.Mock( - return_value=_qos_policy - ) + self.network_client.find_qos_policy.return_value = _qos_policy + arglist = [ self.new_router.name, '--external-gateway', @@ -595,9 +595,8 @@ def test_create_with_qos_policy(self): def test_create_with_qos_policy_no_external_gateway(self): _qos_policy = network_fakes.create_one_qos_policy() - self.network_client.find_qos_policy = mock.Mock( - return_value=_qos_policy - ) + self.network_client.find_qos_policy.return_value = _qos_policy + arglist = [ self.new_router.name, '--qos-policy', @@ -625,7 +624,7 @@ class TestDeleteRouter(TestRouter): def setUp(self): super().setUp() - self.network_client.delete_router = mock.Mock(return_value=None) + self.network_client.delete_router.return_value = None self.network_client.find_router = network_fakes.get_routers( self._routers @@ -679,9 +678,7 @@ def test_multi_routers_delete_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) find_mock_result = [self._routers[0], exceptions.CommandError] - self.network_client.find_router = mock.Mock( - side_effect=find_mock_result - ) + self.network_client.find_router.side_effect = find_mock_result try: self.cmd.take_action(parsed_args) @@ -786,21 +783,16 @@ def setUp(self): # Get the command object to test self.cmd = router.ListRouter(self.app, None) - self.network_client.agent_hosted_routers = mock.Mock( - return_value=self.routers - ) - self.network_client.routers = mock.Mock(return_value=self.routers) - self.network_client.find_extension = mock.Mock( - return_value=self.extensions - ) - self.network_client.find_router = mock.Mock( - return_value=self.routers[0] - ) + self.network_client.agent_hosted_routers.return_value = self.routers + + self.network_client.routers.return_value = self.routers + self.network_client.find_extension.return_value = self.extensions + + self.network_client.find_router.return_value = self.routers[0] + self._testagent = network_fakes.create_one_network_agent() - self.network_client.get_agent = mock.Mock(return_value=self._testagent) - self.network_client.get_router = mock.Mock( - return_value=self.routers[0] - ) + self.network_client.get_agent.return_value = self._testagent + self.network_client.get_router.return_value = self.routers[0] def test_router_list_no_options(self): arglist = [] @@ -865,7 +857,7 @@ def test_router_list_long_no_az(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) # to mock, that no availability zone - self.network_client.find_extension = mock.Mock(return_value=None) + self.network_client.find_extension.return_value = None # In base command class Lister in cliff, abstract method take_action() # returns a tuple containing the column names and an iterable @@ -1044,8 +1036,8 @@ class TestRemovePortFromRouter(TestRouter): def setUp(self): super().setUp() - self.network_client.find_router = mock.Mock(return_value=self._router) - self.network_client.find_port = mock.Mock(return_value=self._port) + self.network_client.find_router.return_value = self._router + self.network_client.find_port.return_value = self._port self.cmd = router.RemovePortFromRouter(self.app, None) @@ -1089,8 +1081,8 @@ class TestRemoveSubnetFromRouter(TestRouter): def setUp(self): super().setUp() - self.network_client.find_router = mock.Mock(return_value=self._router) - self.network_client.find_subnet = mock.Mock(return_value=self._subnet) + self.network_client.find_router.return_value = self._router + self.network_client.find_subnet.return_value = self._subnet self.cmd = router.RemoveSubnetFromRouter(self.app, None) @@ -1129,11 +1121,12 @@ class TestAddExtraRoutesToRouter(TestRouter): def setUp(self): super().setUp() - self.network_client.add_extra_routes_to_router = mock.Mock( - return_value=self._router + self.network_client.add_extra_routes_to_router.return_value = ( + self._router ) + self.cmd = router.AddExtraRoutesToRouter(self.app, None) - self.network_client.find_router = mock.Mock(return_value=self._router) + self.network_client.find_router.return_value = self._router def test_add_no_extra_route(self): arglist = [ @@ -1218,11 +1211,12 @@ class TestRemoveExtraRoutesFromRouter(TestRouter): def setUp(self): super().setUp() - self.network_client.remove_extra_routes_from_router = mock.Mock( - return_value=self._router + self.network_client.remove_extra_routes_from_router.return_value = ( + self._router ) + self.cmd = router.RemoveExtraRoutesFromRouter(self.app, None) - self.network_client.find_router = mock.Mock(return_value=self._router) + self.network_client.find_router.return_value = self._router def test_remove_no_extra_route(self): arglist = [ @@ -1316,15 +1310,14 @@ class TestSetRouter(TestRouter): def setUp(self): super().setUp() - self.network_client.update_router = mock.Mock(return_value=None) - self.network_client.set_tags = mock.Mock(return_value=None) - self.network_client.find_router = mock.Mock(return_value=self._router) - self.network_client.find_network = mock.Mock( - return_value=self._network - ) - self.network_client.find_subnet = mock.Mock(return_value=self._subnet) - self.network_client.find_extension = mock.Mock( - side_effect=lambda name: self._extensions.get(name) + self.network_client.update_router.return_value = None + self.network_client.set_tags.return_value = None + self.network_client.find_router.return_value = self._router + self.network_client.find_network.return_value = self._network + + self.network_client.find_subnet.return_value = self._subnet + self.network_client.find_extension.side_effect = ( + lambda name: self._extensions.get(name) ) # Get the command object to test self.cmd = router.SetRouter(self.app, None) @@ -1459,7 +1452,7 @@ def test_set_route_overwrite_route(self): _testrouter = network_fakes.create_one_router( {'routes': [{"destination": "10.0.0.2", "nexthop": "1.1.1.1"}]} ) - self.network_client.find_router = mock.Mock(return_value=_testrouter) + self.network_client.find_router.return_value = _testrouter arglist = [ _testrouter.name, '--route', @@ -1667,9 +1660,8 @@ def test_set_with_no_tag(self): def test_set_gateway_ip_qos(self): qos_policy = network_fakes.create_one_qos_policy() - self.network_client.find_qos_policy = mock.Mock( - return_value=qos_policy - ) + self.network_client.find_qos_policy.return_value = qos_policy + arglist = [ "--external-gateway", self._network.id, @@ -1724,9 +1716,8 @@ def test_unset_gateway_ip_qos(self): def test_set_unset_gateway_ip_qos(self): qos_policy = network_fakes.create_one_qos_policy() - self.network_client.find_qos_policy = mock.Mock( - return_value=qos_policy - ) + self.network_client.find_qos_policy.return_value = qos_policy + arglist = [ "--external-gateway", self._network.id, @@ -1752,11 +1743,10 @@ def test_set_unset_gateway_ip_qos(self): def test_set_gateway_ip_qos_no_gateway(self): qos_policy = network_fakes.create_one_qos_policy() - self.network_client.find_qos_policy = mock.Mock( - return_value=qos_policy - ) + self.network_client.find_qos_policy.return_value = qos_policy + router = network_fakes.create_one_router() - self.network_client.find_router = mock.Mock(return_value=router) + self.network_client.find_router.return_value = router arglist = [ "--qos-policy", qos_policy.id, @@ -1774,11 +1764,10 @@ def test_set_gateway_ip_qos_no_gateway(self): def test_unset_gateway_ip_qos_no_gateway(self): qos_policy = network_fakes.create_one_qos_policy() - self.network_client.find_qos_policy = mock.Mock( - return_value=qos_policy - ) + self.network_client.find_qos_policy.return_value = qos_policy + router = network_fakes.create_one_router() - self.network_client.find_router = mock.Mock(return_value=router) + self.network_client.find_router.return_value = router arglist = [ "--no-qos-policy", router.id, @@ -1857,8 +1846,8 @@ class TestShowRouter(TestRouter): def setUp(self): super().setUp() - self.network_client.find_router = mock.Mock(return_value=self._router) - self.network_client.ports = mock.Mock(return_value=[self._port]) + self.network_client.find_router.return_value = self._router + self.network_client.ports.return_value = [self._port] # Get the command object to test self.cmd = router.ShowRouter(self.app, None) @@ -1961,18 +1950,16 @@ def setUp(self): } ) self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet() - self.network_client.find_router = mock.Mock( - return_value=self._testrouter - ) - self.network_client.update_router = mock.Mock(return_value=None) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.find_router.return_value = self._testrouter + + self.network_client.update_router.return_value = None + self.network_client.set_tags.return_value = None self._extensions = {'fake': network_fakes.create_one_extension()} - self.network_client.find_extension = mock.Mock( - side_effect=lambda name: self._extensions.get(name) - ) - self.network_client.remove_external_gateways = mock.Mock( - return_value=None + self.network_client.find_extension.side_effect = ( + lambda name: self._extensions.get(name) ) + self.network_client.remove_external_gateways.return_value = None + # Get the command object to test self.cmd = router.UnsetRouter(self.app, None) @@ -2109,11 +2096,10 @@ def test_unset_router_qos_policy(self): def test_unset_gateway_ip_qos_no_network(self): qos_policy = network_fakes.create_one_qos_policy() - self.network_client.find_qos_policy = mock.Mock( - return_value=qos_policy - ) + self.network_client.find_qos_policy.return_value = qos_policy + router = network_fakes.create_one_router() - self.network_client.find_router = mock.Mock(return_value=router) + self.network_client.find_router.return_value = router arglist = [ "--qos-policy", router.id, @@ -2129,13 +2115,12 @@ def test_unset_gateway_ip_qos_no_network(self): def test_unset_gateway_ip_qos_no_qos(self): qos_policy = network_fakes.create_one_qos_policy() - self.network_client.find_qos_policy = mock.Mock( - return_value=qos_policy - ) + self.network_client.find_qos_policy.return_value = qos_policy + router = network_fakes.create_one_router( {"external_gateway_info": {"network_id": "fake-id"}} ) - self.network_client.find_router = mock.Mock(return_value=router) + self.network_client.find_router.return_value = router arglist = [ "--qos-policy", router.id, @@ -2172,10 +2157,10 @@ def setUp(self): attrs={'name': 'external-gateway-multihoming'} ) } - self.network_client.find_extension = mock.Mock( - side_effect=lambda name: self._extensions.get(name) + self.network_client.find_extension.side_effect = ( + lambda name: self._extensions.get(name) ) - self.network_client.find_router = mock.Mock(return_value=self._router) + self.network_client.find_router.return_value = self._router def _find_network(name_or_id, ignore_missing): for network in self._networks: @@ -2185,15 +2170,12 @@ def _find_network(name_or_id, ignore_missing): return None raise Exception('Test resource not found') - self.network_client.find_network = mock.Mock(side_effect=_find_network) + self.network_client.find_network.side_effect = _find_network - self.network_client.find_subnet = mock.Mock(return_value=self._subnet) - self.network_client.add_external_gateways = mock.Mock( - return_value=None - ) - self.network_client.remove_external_gateways = mock.Mock( - return_value=None - ) + self.network_client.find_subnet.return_value = self._subnet + self.network_client.add_external_gateways.return_value = None + + self.network_client.remove_external_gateways.return_value = None class TestCreateMultipleGateways(TestGatewayOps): @@ -2223,13 +2205,10 @@ def setUp(self): self._second_network = network_fakes.create_one_network() self._networks.append(self._second_network) - self.network_client.create_router = mock.Mock( - return_value=self._router - ) - self.network_client.update_router = mock.Mock(return_value=None) - self.network_client.update_external_gateways = mock.Mock( - return_value=None - ) + self.network_client.create_router.return_value = self._router + + self.network_client.update_router.return_value = None + self.network_client.update_external_gateways.return_value = None self._data = ( router.AdminStateColumn(self._router.is_admin_state_up), @@ -2353,10 +2332,9 @@ def setUp(self): self._second_network = network_fakes.create_one_network() self._networks.append(self._second_network) - self.network_client.update_router = mock.Mock(return_value=None) - self.network_client.update_external_gateways = mock.Mock( - return_value=None - ) + self.network_client.update_router.return_value = None + self.network_client.update_external_gateways.return_value = None + self.cmd = router.SetRouter(self.app, None) def test_update_one_gateway(self): diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py index 9bf1f672f..c5ccf628e 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -11,7 +11,6 @@ # under the License. # -from unittest import mock from unittest.mock import call from osc_lib import exceptions @@ -69,13 +68,13 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): def setUp(self): super().setUp() - self.network_client.create_security_group = mock.Mock( - return_value=self._security_group + self.network_client.create_security_group.return_value = ( + self._security_group ) self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.set_tags.return_value = None # Get the command object to test self.cmd = security_group.CreateSecurityGroup(self.app, None) @@ -185,9 +184,7 @@ class TestDeleteSecurityGroupNetwork(TestSecurityGroupNetwork): def setUp(self): super().setUp() - self.network_client.delete_security_group = mock.Mock( - return_value=None - ) + self.network_client.delete_security_group.return_value = None self.network_client.find_security_group = ( network_fakes.get_security_groups(self._security_groups) @@ -245,9 +242,7 @@ def test_multi_security_groups_delete_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) find_mock_result = [self._security_groups[0], exceptions.CommandError] - self.network_client.find_security_group = mock.Mock( - side_effect=find_mock_result - ) + self.network_client.find_security_group.side_effect = find_mock_result try: self.cmd.take_action(parsed_args) @@ -295,8 +290,8 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): def setUp(self): super().setUp() - self.network_client.security_groups = mock.Mock( - return_value=self._security_groups + self.network_client.security_groups.return_value = ( + self._security_groups ) # Get the command object to test @@ -423,14 +418,13 @@ class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): def setUp(self): super().setUp() - self.network_client.update_security_group = mock.Mock( - return_value=None - ) + self.network_client.update_security_group.return_value = None - self.network_client.find_security_group = mock.Mock( - return_value=self._security_group + self.network_client.find_security_group.return_value = ( + self._security_group ) - self.network_client.set_tags = mock.Mock(return_value=None) + + self.network_client.set_tags.return_value = None # Get the command object to test self.cmd = security_group.SetSecurityGroup(self.app, None) @@ -557,8 +551,8 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): def setUp(self): super().setUp() - self.network_client.find_security_group = mock.Mock( - return_value=self._security_group + self.network_client.find_security_group.return_value = ( + self._security_group ) # Get the command object to test @@ -596,14 +590,13 @@ class TestUnsetSecurityGroupNetwork(TestSecurityGroupNetwork): def setUp(self): super().setUp() - self.network_client.update_security_group = mock.Mock( - return_value=None - ) + self.network_client.update_security_group.return_value = None - self.network_client.find_security_group = mock.Mock( - return_value=self._security_group + self.network_client.find_security_group.return_value = ( + self._security_group ) - self.network_client.set_tags = mock.Mock(return_value=None) + + self.network_client.set_tags.return_value = None # Get the command object to test self.cmd = security_group.UnsetSecurityGroup(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index d5630c368..920e89141 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -11,7 +11,6 @@ # under the License. # -from unittest import mock from unittest.mock import call from osc_lib import exceptions @@ -69,9 +68,10 @@ def _setup_security_group_rule(self, attrs=None): self._security_group_rule = ( network_fakes.create_one_security_group_rule(attrs) ) - self.network_client.create_security_group_rule = mock.Mock( - return_value=self._security_group_rule + self.network_client.create_security_group_rule.return_value = ( + self._security_group_rule ) + self.expected_data = ( self._security_group_rule.created_at, self._security_group_rule.description, @@ -93,12 +93,12 @@ def _setup_security_group_rule(self, attrs=None): def setUp(self): super().setUp() - self.network_client.find_security_group = mock.Mock( - return_value=self._security_group + self.network_client.find_security_group.return_value = ( + self._security_group ) - self.network_client.find_address_group = mock.Mock( - return_value=self._address_group + self.network_client.find_address_group.return_value = ( + self._address_group ) self.projects_mock.get.return_value = self.project @@ -970,9 +970,7 @@ class TestDeleteSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): def setUp(self): super().setUp() - self.network_client.delete_security_group_rule = mock.Mock( - return_value=None - ) + self.network_client.delete_security_group_rule.return_value = None self.network_client.find_security_group_rule = ( network_fakes.get_security_group_rules(self._security_group_rules) @@ -1030,8 +1028,8 @@ def test_multi_security_group_rules_delete_with_exception(self): self._security_group_rules[0], exceptions.CommandError, ] - self.network_client.find_security_group_rule = mock.Mock( - side_effect=find_mock_result + self.network_client.find_security_group_rule.side_effect = ( + find_mock_result ) try: @@ -1134,11 +1132,12 @@ class TestListSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): def setUp(self): super().setUp() - self.network_client.find_security_group = mock.Mock( - return_value=self._security_group + self.network_client.find_security_group.return_value = ( + self._security_group ) - self.network_client.security_group_rules = mock.Mock( - return_value=self._security_group_rules + + self.network_client.security_group_rules.return_value = ( + self._security_group_rules ) # Get the command object to test @@ -1344,8 +1343,8 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): def setUp(self): super().setUp() - self.network_client.find_security_group_rule = mock.Mock( - return_value=self._security_group_rule + self.network_client.find_security_group_rule.return_value = ( + self._security_group_rule ) # Get the command object to test diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 8b8207411..e59168e51 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -11,7 +11,6 @@ # under the License. # -from unittest import mock from unittest.mock import call from osc_lib.cli import format_columns @@ -266,19 +265,14 @@ def setUp(self): self.domains_mock.get.return_value = self.domain # Mock SDK calls for all tests. - self.network_client.create_subnet = mock.Mock( - return_value=self._subnet - ) - self.network_client.set_tags = mock.Mock(return_value=None) - self.network_client.find_network = mock.Mock( - return_value=self._network - ) - self.network_client.find_segment = mock.Mock( - return_value=self._network_segment - ) - self.network_client.find_subnet_pool = mock.Mock( - return_value=self._subnet_pool - ) + self.network_client.create_subnet.return_value = self._subnet + + self.network_client.set_tags.return_value = None + self.network_client.find_network.return_value = self._network + + self.network_client.find_segment.return_value = self._network_segment + + self.network_client.find_subnet_pool.return_value = self._subnet_pool def test_create_no_options(self): arglist = [] @@ -333,7 +327,7 @@ def test_create_default_options(self): def test_create_from_subnet_pool_options(self): # Mock SDK calls for this test. self.network_client.create_subnet.return_value = self._subnet_from_pool - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.set_tags.return_value = None self._network.id = self._subnet_from_pool.network_id arglist = [ @@ -721,7 +715,7 @@ class TestDeleteSubnet(TestSubnet): def setUp(self): super().setUp() - self.network_client.delete_subnet = mock.Mock(return_value=None) + self.network_client.delete_subnet.return_value = None self.network_client.find_subnet = network_fakes.FakeSubnet.get_subnets( self._subnets @@ -775,9 +769,7 @@ def test_multi_subnets_delete_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) find_mock_result = [self._subnets[0], exceptions.CommandError] - self.network_client.find_subnet = mock.Mock( - side_effect=find_mock_result - ) + self.network_client.find_subnet.side_effect = find_mock_result try: self.cmd.take_action(parsed_args) @@ -855,7 +847,7 @@ def setUp(self): # Get the command object to test self.cmd = subnet_v2.ListSubnet(self.app, None) - self.network_client.subnets = mock.Mock(return_value=self._subnet) + self.network_client.subnets.return_value = self._subnet def test_subnet_list_no_options(self): arglist = [] @@ -1019,7 +1011,7 @@ def test_subnet_list_project_domain(self): def test_subnet_list_network(self): network = network_fakes.create_one_network() - self.network_client.find_network = mock.Mock(return_value=network) + self.network_client.find_network.return_value = network arglist = [ '--network', network.id, @@ -1038,7 +1030,7 @@ def test_subnet_list_network(self): def test_subnet_list_gateway(self): subnet = network_fakes.FakeSubnet.create_one_subnet() - self.network_client.find_network = mock.Mock(return_value=subnet) + self.network_client.find_network.return_value = subnet arglist = [ '--gateway', subnet.gateway_ip, @@ -1057,7 +1049,7 @@ def test_subnet_list_gateway(self): def test_subnet_list_name(self): subnet = network_fakes.FakeSubnet.create_one_subnet() - self.network_client.find_network = mock.Mock(return_value=subnet) + self.network_client.find_network.return_value = subnet arglist = [ '--name', subnet.name, @@ -1076,7 +1068,7 @@ def test_subnet_list_name(self): def test_subnet_list_subnet_range(self): subnet = network_fakes.FakeSubnet.create_one_subnet() - self.network_client.find_network = mock.Mock(return_value=subnet) + self.network_client.find_network.return_value = subnet arglist = [ '--subnet-range', subnet.cidr, @@ -1098,10 +1090,9 @@ def test_subnet_list_subnetpool_by_name(self): subnet = network_fakes.FakeSubnet.create_one_subnet( {'subnetpool_id': subnet_pool.id} ) - self.network_client.find_network = mock.Mock(return_value=subnet) - self.network_client.find_subnet_pool = mock.Mock( - return_value=subnet_pool - ) + self.network_client.find_network.return_value = subnet + self.network_client.find_subnet_pool.return_value = subnet_pool + arglist = [ '--subnet-pool', subnet_pool.name, @@ -1123,10 +1114,9 @@ def test_subnet_list_subnetpool_by_id(self): subnet = network_fakes.FakeSubnet.create_one_subnet( {'subnetpool_id': subnet_pool.id} ) - self.network_client.find_network = mock.Mock(return_value=subnet) - self.network_client.find_subnet_pool = mock.Mock( - return_value=subnet_pool - ) + self.network_client.find_network.return_value = subnet + self.network_client.find_subnet_pool.return_value = subnet_pool + arglist = [ '--subnet-pool', subnet_pool.id, @@ -1182,9 +1172,9 @@ class TestSetSubnet(TestSubnet): def setUp(self): super().setUp() - self.network_client.update_subnet = mock.Mock(return_value=None) - self.network_client.set_tags = mock.Mock(return_value=None) - self.network_client.find_subnet = mock.Mock(return_value=self._subnet) + self.network_client.update_subnet.return_value = None + self.network_client.set_tags.return_value = None + self.network_client.find_subnet.return_value = self._subnet self.cmd = subnet_v2.SetSubnet(self.app, None) def test_set_this(self): @@ -1263,7 +1253,7 @@ def test_append_options(self): 'service_types': ["network:router_gateway"], } ) - self.network_client.find_subnet = mock.Mock(return_value=_testsubnet) + self.network_client.find_subnet.return_value = _testsubnet arglist = [ '--dns-nameserver', '10.0.0.2', @@ -1329,7 +1319,7 @@ def test_overwrite_options(self): 'dns_nameservers': ["10.0.0.1"], } ) - self.network_client.find_subnet = mock.Mock(return_value=_testsubnet) + self.network_client.find_subnet.return_value = _testsubnet arglist = [ '--host-route', 'destination=10.30.30.30/24,gateway=10.30.30.1', @@ -1379,7 +1369,7 @@ def test_clear_options(self): 'dns_nameservers': ['10.0.0.1'], } ) - self.network_client.find_subnet = mock.Mock(return_value=_testsubnet) + self.network_client.find_subnet.return_value = _testsubnet arglist = [ '--no-host-route', '--no-allocation-pool', @@ -1448,8 +1438,8 @@ def test_set_segment(self): 'segment_id': None, } ) - self.network_client.find_subnet = mock.Mock(return_value=_subnet) - self.network_client.find_segment = mock.Mock(return_value=_segment) + self.network_client.find_subnet.return_value = _subnet + self.network_client.find_segment.return_value = _segment arglist = ['--network-segment', _segment.id, _subnet.name] verifylist = [('network_segment', _segment.id)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1514,7 +1504,7 @@ def setUp(self): # Get the command object to test self.cmd = subnet_v2.ShowSubnet(self.app, None) - self.network_client.find_subnet = mock.Mock(return_value=self._subnet) + self.network_client.find_subnet.return_value = self._subnet def test_show_no_options(self): arglist = [] @@ -1572,11 +1562,10 @@ def setUp(self): 'tags': ['green', 'red'], } ) - self.network_client.find_subnet = mock.Mock( - return_value=self._testsubnet - ) - self.network_client.update_subnet = mock.Mock(return_value=None) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.find_subnet.return_value = self._testsubnet + + self.network_client.update_subnet.return_value = None + self.network_client.set_tags.return_value = None # Get the command object to test self.cmd = subnet_v2.UnsetSubnet(self.app, None) diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index 40aa1e990..013550ec1 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -from unittest import mock from unittest.mock import call from osc_lib.cli import format_columns @@ -76,16 +75,15 @@ class TestCreateSubnetPool(TestSubnetPool): def setUp(self): super().setUp() - self.network_client.create_subnet_pool = mock.Mock( - return_value=self._subnet_pool - ) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.create_subnet_pool.return_value = self._subnet_pool + + self.network_client.set_tags.return_value = None # Get the command object to test self.cmd = subnet_pool.CreateSubnetPool(self.app, None) - self.network_client.find_address_scope = mock.Mock( - return_value=self._address_scope + self.network_client.find_address_scope.return_value = ( + self._address_scope ) self.projects_mock.get.return_value = self.project @@ -387,7 +385,7 @@ class TestDeleteSubnetPool(TestSubnetPool): def setUp(self): super().setUp() - self.network_client.delete_subnet_pool = mock.Mock(return_value=None) + self.network_client.delete_subnet_pool.return_value = None self.network_client.find_subnet_pool = ( network_fakes.FakeSubnetPool.get_subnet_pools(self._subnet_pools) @@ -445,9 +443,7 @@ def test_multi_subnet_pools_delete_with_exception(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) find_mock_result = [self._subnet_pools[0], exceptions.CommandError] - self.network_client.find_subnet_pool = mock.Mock( - side_effect=find_mock_result - ) + self.network_client.find_subnet_pool.side_effect = find_mock_result try: self.cmd.take_action(parsed_args) @@ -514,9 +510,7 @@ def setUp(self): # Get the command object to test self.cmd = subnet_pool.ListSubnetPool(self.app, None) - self.network_client.subnet_pools = mock.Mock( - return_value=self._subnet_pools - ) + self.network_client.subnet_pools.return_value = self._subnet_pools def test_subnet_pool_list_no_option(self): arglist = [] @@ -653,7 +647,7 @@ def test_subnet_pool_list_project_domain(self): def test_subnet_pool_list_name(self): subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() - self.network_client.find_network = mock.Mock(return_value=subnet_pool) + self.network_client.find_network.return_value = subnet_pool arglist = [ '--name', subnet_pool.name, @@ -672,9 +666,8 @@ def test_subnet_pool_list_name(self): def test_subnet_pool_list_address_scope(self): addr_scope = network_fakes.create_one_address_scope() - self.network_client.find_address_scope = mock.Mock( - return_value=addr_scope - ) + self.network_client.find_address_scope.return_value = addr_scope + arglist = [ '--address-scope', addr_scope.id, @@ -734,15 +727,13 @@ class TestSetSubnetPool(TestSubnetPool): def setUp(self): super().setUp() - self.network_client.update_subnet_pool = mock.Mock(return_value=None) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.update_subnet_pool.return_value = None + self.network_client.set_tags.return_value = None - self.network_client.find_subnet_pool = mock.Mock( - return_value=self._subnet_pool - ) + self.network_client.find_subnet_pool.return_value = self._subnet_pool - self.network_client.find_address_scope = mock.Mock( - return_value=self._address_scope + self.network_client.find_address_scope.return_value = ( + self._address_scope ) # Get the command object to test @@ -1079,9 +1070,7 @@ class TestShowSubnetPool(TestSubnetPool): def setUp(self): super().setUp() - self.network_client.find_subnet_pool = mock.Mock( - return_value=self._subnet_pool - ) + self.network_client.find_subnet_pool.return_value = self._subnet_pool # Get the command object to test self.cmd = subnet_pool.ShowSubnetPool(self.app, None) @@ -1122,11 +1111,10 @@ def setUp(self): self._subnetpool = network_fakes.FakeSubnetPool.create_one_subnet_pool( {'tags': ['green', 'red']} ) - self.network_client.find_subnet_pool = mock.Mock( - return_value=self._subnetpool - ) - self.network_client.update_subnet_pool = mock.Mock(return_value=None) - self.network_client.set_tags = mock.Mock(return_value=None) + self.network_client.find_subnet_pool.return_value = self._subnetpool + + self.network_client.update_subnet_pool.return_value = None + self.network_client.set_tags.return_value = None # Get the command object to test self.cmd = subnet_pool.UnsetSubnetPool(self.app, None) From 33d34bdfe8f31f422075e5ff5d8ae57de1469f23 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 7 Nov 2025 14:14:05 +0000 Subject: [PATCH 35/67] Remove tests for other osc-lib These are already found in osc-lib itself. Change-Id: I51114a5a79d6cd6ea46f60284066132b2e54a1a5 Signed-off-by: Stephen Finucane --- .../tests/unit/common/test_logs.py | 221 ------------------ 1 file changed, 221 deletions(-) delete mode 100644 openstackclient/tests/unit/common/test_logs.py diff --git a/openstackclient/tests/unit/common/test_logs.py b/openstackclient/tests/unit/common/test_logs.py deleted file mode 100644 index 234ba427e..000000000 --- a/openstackclient/tests/unit/common/test_logs.py +++ /dev/null @@ -1,221 +0,0 @@ -# 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. -# - -# NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release -# or Jun 2017. - -import logging -from unittest import mock - -from osc_lib import logs - -from openstackclient.tests.unit import utils - - -class TestContext(utils.TestCase): - def test_log_level_from_options(self): - opts = mock.Mock() - opts.verbose_level = 0 - self.assertEqual(logging.ERROR, logs.log_level_from_options(opts)) - opts.verbose_level = 1 - self.assertEqual(logging.WARNING, logs.log_level_from_options(opts)) - opts.verbose_level = 2 - self.assertEqual(logging.INFO, logs.log_level_from_options(opts)) - opts.verbose_level = 3 - self.assertEqual(logging.DEBUG, logs.log_level_from_options(opts)) - - def test_log_level_from_config(self): - cfg = {'verbose_level': 0} - self.assertEqual(logging.ERROR, logs.log_level_from_config(cfg)) - cfg = {'verbose_level': 1} - self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg)) - cfg = {'verbose_level': 2} - self.assertEqual(logging.INFO, logs.log_level_from_config(cfg)) - cfg = {'verbose_level': 3} - self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg)) - cfg = {'verbose_level': 1, 'log_level': 'critical'} - self.assertEqual(logging.CRITICAL, logs.log_level_from_config(cfg)) - cfg = {'verbose_level': 1, 'log_level': 'error'} - self.assertEqual(logging.ERROR, logs.log_level_from_config(cfg)) - cfg = {'verbose_level': 1, 'log_level': 'warning'} - self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg)) - cfg = {'verbose_level': 1, 'log_level': 'info'} - self.assertEqual(logging.INFO, logs.log_level_from_config(cfg)) - cfg = {'verbose_level': 1, 'log_level': 'debug'} - self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg)) - cfg = {'verbose_level': 1, 'log_level': 'bogus'} - self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg)) - cfg = {'verbose_level': 1, 'log_level': 'info', 'debug': True} - self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg)) - - @mock.patch('warnings.simplefilter') - def test_set_warning_filter(self, simplefilter): - logs.set_warning_filter(logging.ERROR) - simplefilter.assert_called_with("ignore") - logs.set_warning_filter(logging.WARNING) - simplefilter.assert_called_with("ignore") - logs.set_warning_filter(logging.INFO) - simplefilter.assert_called_with("once") - - -class TestFileFormatter(utils.TestCase): - def test_nothing(self): - formatter = logs._FileFormatter() - self.assertEqual( - ( - '%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' - '%(name)s %(message)s' - ), - formatter.fmt, - ) - - def test_options(self): - class Opts: - cloud = 'cloudy' - os_project_name = 'projecty' - username = 'usernamey' - - options = Opts() - formatter = logs._FileFormatter(options=options) - self.assertEqual( - ( - '%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' - '%(name)s [cloudy usernamey projecty] %(message)s' - ), - formatter.fmt, - ) - - def test_config(self): - config = mock.Mock() - config.config = {'cloud': 'cloudy'} - config.auth = {'project_name': 'projecty', 'username': 'usernamey'} - formatter = logs._FileFormatter(config=config) - self.assertEqual( - ( - '%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' - '%(name)s [cloudy usernamey projecty] %(message)s' - ), - formatter.fmt, - ) - - -class TestLogConfigurator(utils.TestCase): - def setUp(self): - super().setUp() - self.options = mock.Mock() - self.options.verbose_level = 1 - self.options.log_file = None - self.options.debug = False - self.root_logger = mock.Mock() - self.root_logger.setLevel = mock.Mock() - self.root_logger.addHandler = mock.Mock() - self.requests_log = mock.Mock() - self.requests_log.setLevel = mock.Mock() - self.cliff_log = mock.Mock() - self.cliff_log.setLevel = mock.Mock() - self.stevedore_log = mock.Mock() - self.stevedore_log.setLevel = mock.Mock() - self.iso8601_log = mock.Mock() - self.iso8601_log.setLevel = mock.Mock() - self.loggers = [ - self.root_logger, - self.requests_log, - self.cliff_log, - self.stevedore_log, - self.iso8601_log, - ] - - @mock.patch('logging.StreamHandler') - @mock.patch('logging.getLogger') - @mock.patch('osc_lib.logs.set_warning_filter') - def test_init(self, warning_filter, getLogger, handle): - getLogger.side_effect = self.loggers - console_logger = mock.Mock() - console_logger.setFormatter = mock.Mock() - console_logger.setLevel = mock.Mock() - handle.return_value = console_logger - - configurator = logs.LogConfigurator(self.options) - - getLogger.assert_called_with('iso8601') # last call - warning_filter.assert_called_with(logging.WARNING) - self.root_logger.setLevel.assert_called_with(logging.DEBUG) - self.root_logger.addHandler.assert_called_with(console_logger) - self.requests_log.setLevel.assert_called_with(logging.ERROR) - self.cliff_log.setLevel.assert_called_with(logging.ERROR) - self.stevedore_log.setLevel.assert_called_with(logging.ERROR) - self.iso8601_log.setLevel.assert_called_with(logging.ERROR) - self.assertFalse(configurator.dump_trace) - - @mock.patch('logging.getLogger') - @mock.patch('osc_lib.logs.set_warning_filter') - def test_init_no_debug(self, warning_filter, getLogger): - getLogger.side_effect = self.loggers - self.options.debug = True - - configurator = logs.LogConfigurator(self.options) - - warning_filter.assert_called_with(logging.DEBUG) - self.requests_log.setLevel.assert_called_with(logging.DEBUG) - self.assertTrue(configurator.dump_trace) - - @mock.patch('logging.FileHandler') - @mock.patch('logging.getLogger') - @mock.patch('osc_lib.logs.set_warning_filter') - @mock.patch('osc_lib.logs._FileFormatter') - def test_init_log_file(self, formatter, warning_filter, getLogger, handle): - getLogger.side_effect = self.loggers - self.options.log_file = '/tmp/log_file' - file_logger = mock.Mock() - file_logger.setFormatter = mock.Mock() - file_logger.setLevel = mock.Mock() - handle.return_value = file_logger - mock_formatter = mock.Mock() - formatter.return_value = mock_formatter - - logs.LogConfigurator(self.options) - - handle.assert_called_with(filename=self.options.log_file) - self.root_logger.addHandler.assert_called_with(file_logger) - file_logger.setFormatter.assert_called_with(mock_formatter) - file_logger.setLevel.assert_called_with(logging.WARNING) - - @mock.patch('logging.FileHandler') - @mock.patch('logging.getLogger') - @mock.patch('osc_lib.logs.set_warning_filter') - @mock.patch('osc_lib.logs._FileFormatter') - def test_configure(self, formatter, warning_filter, getLogger, handle): - getLogger.side_effect = self.loggers - configurator = logs.LogConfigurator(self.options) - cloud_config = mock.Mock() - config_log = '/tmp/config_log' - cloud_config.config = { - 'log_file': config_log, - 'verbose_level': 1, - 'log_level': 'info', - } - file_logger = mock.Mock() - file_logger.setFormatter = mock.Mock() - file_logger.setLevel = mock.Mock() - handle.return_value = file_logger - mock_formatter = mock.Mock() - formatter.return_value = mock_formatter - - configurator.configure(cloud_config) - - warning_filter.assert_called_with(logging.INFO) - handle.assert_called_with(filename=config_log) - self.root_logger.addHandler.assert_called_with(file_logger) - file_logger.setFormatter.assert_called_with(mock_formatter) - file_logger.setLevel.assert_called_with(logging.INFO) - self.assertFalse(configurator.dump_trace) From eb7c4c61a9253b99e9e658d27b4f0170c183d6ce Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 7 Nov 2025 12:41:29 +0000 Subject: [PATCH 36/67] Add new hacking rules To catch some obvious issues. Change-Id: Ic0ddc95100811e7b313b519aad7d687a1415020b Signed-off-by: Stephen Finucane --- .pre-commit-config.yaml | 3 +- hacking/checks.py | 108 ++++++++++++++++++ .../tests/unit/network/test_common.py | 14 +-- pyproject.toml | 1 + tox.ini | 11 +- 5 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 hacking/checks.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 47903a90d..b277bb059 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,10 +32,11 @@ repos: - id: mypy additional_dependencies: - types-requests - # keep this in-sync with '[mypy] exclude' in 'setup.cfg' + # keep this in-sync with '[tool.mypy] exclude' in 'pyproject.toml' exclude: | (?x)( doc/.* | examples/.* + | hacking/.* | releasenotes/.* ) diff --git a/hacking/checks.py b/hacking/checks.py new file mode 100644 index 000000000..98b40370d --- /dev/null +++ b/hacking/checks.py @@ -0,0 +1,108 @@ +# 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 os +import re + +from hacking import core + +""" +Guidelines for writing new hacking checks + + - Use only for python-openstackclient specific tests. OpenStack general tests + should be submitted to the common 'hacking' module. + - Pick numbers in the range O4xx. Find the current test with the highest + allocated number and then pick the next value. +""" + + +@core.flake8ext +def assert_no_oslo(logical_line): + """Check for use of oslo libraries. + + O400 + """ + if re.match(r'(from|import) oslo_.*', logical_line): + yield (0, "0400: oslo libraries should not be used in SDK projects") + + +@core.flake8ext +def assert_no_duplicated_setup(logical_line, filename): + """Check for use of various unnecessary test duplications. + + O401 + """ + if os.path.join('openstackclient', 'tests', 'unit') not in filename: + return + + if re.match(r'self.app = .*\(self.app, self.namespace\)', logical_line): + yield ( + 0, + 'O401: It is not necessary to create dummy Namespace objects', + ) + + if os.path.basename(filename) != 'fakes.py': + if re.match( + r'self.[a-z_]+_client = self.app.client_manager.*', logical_line + ): + yield ( + 0, + "O401: Aliases for mocks of the service client are already " + "provided by the respective service's FakeClientMixin class", + ) + + if match := re.match( + r'self.app.client_manager.([a-z_]+) = mock.Mock', logical_line + ): + service = match.group(1) + if service == 'auth_ref': + return + yield ( + 0, + f"O401: client_manager.{service} mocks are already provided by " + f"the {service} service's FakeClientMixin class", + ) + + +@core.flake8ext +def assert_use_of_client_aliases(logical_line, filename): + """Ensure we use $service_client instead of $sdk_connection.service. + + O402 + """ + # we should expand the list of services as we drop legacy clients + if match := re.match( + r'self\.app\.client_manager\.sdk_connnection\.(compute|network|image)', + logical_line, + ): + service = match.group(1) + yield (0, f"0402: prefer {service}_client to sdk_connection.{service}") + + if match := re.match( + r'(self\.app\.client_manager\.(compute|network|image)+\.[a-z_]+) = mock.Mock', + logical_line, + ): + yield ( + 0, + f"O402: {match.group(1)} is already a mock: there's no need to " + f"assign a new mock.Mock instance.", + ) + + if match := re.match( + r'(self\.(compute|network|image)_client\.[a-z_]+) = mock.Mock', + logical_line, + ): + yield ( + 0, + f"O402: {match.group(1)} is already a mock: there's no need to " + f"assign a new mock.Mock instance.", + ) diff --git a/openstackclient/tests/unit/network/test_common.py b/openstackclient/tests/unit/network/test_common.py index 02cb021ef..cc84c8bdf 100644 --- a/openstackclient/tests/unit/network/test_common.py +++ b/openstackclient/tests/unit/network/test_common.py @@ -126,12 +126,12 @@ def setUp(self): # Create client mocks. Note that we intentionally do not use specced # mocks since we want to test fake methods. - self.app.client_manager.network = mock.Mock() - self.network_client = self.app.client_manager.network + self.app.client_manager.network = mock.Mock() # noqa: O401 + self.network_client = self.app.client_manager.network # noqa: O401 self.network_client.network_action.return_value = 'take_action_network' - self.app.client_manager.compute = mock.Mock() - self.compute_client = self.app.client_manager.compute + self.app.client_manager.compute = mock.Mock() # noqa: O401 + self.compute_client = self.app.client_manager.compute # noqa: O401 self.compute_client.compute_action.return_value = 'take_action_compute' self.cmd = FakeNetworkAndComputeCommand(self.app, None) @@ -201,9 +201,9 @@ def setUp(self): # Create client mocks. Note that we intentionally do not use specced # mocks since we want to test fake methods. - self.app.client_manager.network = mock.Mock() - self.network_client = self.app.client_manager.network - self.network_client.test_create_action = mock.Mock() + self.app.client_manager.network = mock.Mock() # noqa: O401 + self.network_client = self.app.client_manager.network # noqa: O401 + self.network_client.test_create_action = mock.Mock() # noqa: O402 # Subclasses can override the command object to test. self.cmd = FakeCreateNeutronCommandWithExtraArgs(self.app, None) diff --git a/pyproject.toml b/pyproject.toml index 74f93ccb2..2ebe0a52c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -732,6 +732,7 @@ exclude = ''' (?x)( doc | examples + | hacking | releasenotes ) ''' diff --git a/tox.ini b/tox.ini index 7b3d951d2..2ee2dc2ca 100644 --- a/tox.ini +++ b/tox.ini @@ -113,9 +113,16 @@ commands = [flake8] show-source = true exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools,releasenotes -# We only enable the hacking (H) checks -select = H +# We only enable the hacking (H) and openstackclient (O) checks +select = H,O # H301 Black will put commas after imports that can't fit on one line ignore = H301 import-order-style = pep8 application_import_names = openstackclient + +[flake8:local-plugins] +extension = + O400 = checks:assert_no_oslo + O401 = checks:assert_no_duplicated_setup + O402 = checks:assert_use_of_client_aliases +paths = ./hacking From 188737c69c498a9b8736ff9c73183896ff5d2060 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Wed, 12 Nov 2025 01:41:37 +0900 Subject: [PATCH 37/67] ruff: Use more specific name to enable pyupgrade rule UP is the exact name of the rule, instead of U. Use the exact name to avoid potential problems caused by any UX rules which can be added in the future. Change-Id: I5fa59181fcd3e28bf3c87ce2a5e610561b2ee8a8 Signed-off-by: Takashi Kajinami --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 74f93ccb2..673da0dae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -748,7 +748,7 @@ quote-style = "preserve" docstring-code-format = true [tool.ruff.lint] -select = ["E4", "E7", "E9", "F", "S", "U"] +select = ["E4", "E7", "E9", "F", "S", "UP"] [tool.ruff.lint.per-file-ignores] "openstackclient/tests/*" = ["S"] From db2c1a5e2b1eec0afd7bbd87a5026b4df7ceb184 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 13 Nov 2025 11:57:55 +0000 Subject: [PATCH 38/67] trivial: Normalize some client usage Ahead of rework in this area. Change-Id: I1b1c2370967381903970870da8cbe0868b1e23e1 Signed-off-by: Stephen Finucane --- openstackclient/common/project_cleanup.py | 16 +++-- openstackclient/compute/v2/aggregate.py | 6 +- openstackclient/network/v2/network_trunk.py | 73 +++++++++++---------- 3 files changed, 50 insertions(+), 45 deletions(-) diff --git a/openstackclient/common/project_cleanup.py b/openstackclient/common/project_cleanup.py index 84a07353d..5b2d89c1a 100644 --- a/openstackclient/common/project_cleanup.py +++ b/openstackclient/common/project_cleanup.py @@ -90,17 +90,19 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - sdk = self.app.client_manager.sdk_connection + connection = self.app.client_manager.sdk_connection if parsed_args.auth_project: - project_connect = sdk + # is we've got a project already configured, use the connection + # as-is + pass elif parsed_args.project: - project = sdk.identity.find_project( + project = connection.identity.find_project( name_or_id=parsed_args.project, ignore_missing=False ) - project_connect = sdk.connect_as_project(project) + connection = connection.connect_as_project(project) - if project_connect: + if connection: status_queue: queue.Queue[ty.Any] = queue.Queue() parsed_args.max_width = int( os.environ.get('CLIFF_MAX_TERM_WIDTH', 0) @@ -120,7 +122,7 @@ def take_action(self, parsed_args): if parsed_args.updated_before: filters['updated_at'] = parsed_args.updated_before - project_connect.project_cleanup( + connection.project_cleanup( dry_run=True, status_queue=status_queue, filters=filters, @@ -150,7 +152,7 @@ def take_action(self, parsed_args): self.log.warning(_('Deleting resources')) - project_connect.project_cleanup( + connection.project_cleanup( dry_run=False, status_queue=status_queue, filters=filters, diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index e64960721..cc5817a0f 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -438,15 +438,15 @@ def take_action(self, parsed_args): ) raise exceptions.CommandError(msg) + image_client = self.app.client_manager.sdk_connection.image + aggregate = compute_client.find_aggregate( parsed_args.aggregate, ignore_missing=False ) images = [] for img in parsed_args.image: - image = self.app.client_manager.sdk_connection.image.find_image( - img, ignore_missing=False - ) + image = image_client.find_image(img, ignore_missing=False) images.append(image.id) compute_client.aggregate_precache_images(aggregate.id, images) diff --git a/openstackclient/network/v2/network_trunk.py b/openstackclient/network/v2/network_trunk.py index f8f1c5bcb..61d378371 100644 --- a/openstackclient/network/v2/network_trunk.py +++ b/openstackclient/network/v2/network_trunk.py @@ -88,9 +88,12 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - client = self.app.client_manager.network - attrs = _get_attrs_for_trunk(self.app.client_manager, parsed_args) - obj = client.create_trunk(**attrs) + network_client = self.app.client_manager.network + identity_client = self.app.client_manager.identity + attrs = _get_attrs_for_trunk( + network_client, identity_client, parsed_args + ) + obj = network_client.create_trunk(**attrs) display_columns, columns = _get_columns(obj) data = osc_utils.get_dict_properties( obj, columns, formatters=_formatters @@ -112,12 +115,12 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - client = self.app.client_manager.network + network_client = self.app.client_manager.network result = 0 for trunk in parsed_args.trunk: try: - trunk_id = client.find_trunk(trunk).id - client.delete_trunk(trunk_id) + trunk_id = network_client.find_trunk(trunk).id + network_client.delete_trunk(trunk_id) except Exception as e: result += 1 LOG.error( @@ -150,8 +153,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - client = self.app.client_manager.network - data = client.trunks() + network_client = self.app.client_manager.network + data = network_client.trunks() headers: tuple[str, ...] = ('ID', 'Name', 'Parent Port', 'Description') columns: tuple[str, ...] = ('id', 'name', 'port_id', 'description') if parsed_args.long: @@ -215,11 +218,14 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - client = self.app.client_manager.network - trunk_id = client.find_trunk(parsed_args.trunk) - attrs = _get_attrs_for_trunk(self.app.client_manager, parsed_args) + network_client = self.app.client_manager.network + identity_client = self.app.client_manager.identity + trunk_id = network_client.find_trunk(parsed_args.trunk) + attrs = _get_attrs_for_trunk( + network_client, identity_client, parsed_args + ) try: - client.update_trunk(trunk_id, **attrs) + network_client.update_trunk(trunk_id, **attrs) except Exception as e: msg = _("Failed to set trunk '%(t)s': %(e)s") % { 't': parsed_args.trunk, @@ -228,10 +234,10 @@ def take_action(self, parsed_args): raise exceptions.CommandError(msg) if parsed_args.set_subports: subport_attrs = _get_attrs_for_subports( - self.app.client_manager, parsed_args + network_client, parsed_args ) try: - client.add_trunk_subports(trunk_id, subport_attrs) + network_client.add_trunk_subports(trunk_id, subport_attrs) except Exception as e: msg = _("Failed to add subports to trunk '%(t)s': %(e)s") % { 't': parsed_args.trunk, @@ -251,9 +257,9 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - client = self.app.client_manager.network - trunk_id = client.find_trunk(parsed_args.trunk).id - obj = client.get_trunk(trunk_id) + network_client = self.app.client_manager.network + trunk_id = network_client.find_trunk(parsed_args.trunk).id + obj = network_client.get_trunk(trunk_id) display_columns, columns = _get_columns(obj) data = osc_utils.get_dict_properties( obj, columns, formatters=_formatters @@ -275,9 +281,9 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - client = self.app.client_manager.network - trunk_id = client.find_trunk(parsed_args.trunk) - data = client.get_trunk_subports(trunk_id) + network_client = self.app.client_manager.network + trunk_id = network_client.find_trunk(parsed_args.trunk) + data = network_client.get_trunk_subports(trunk_id) headers: tuple[str, ...] = ( 'Port', 'Segmentation Type', @@ -324,10 +330,10 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - client = self.app.client_manager.network - attrs = _get_attrs_for_subports(self.app.client_manager, parsed_args) - trunk_id = client.find_trunk(parsed_args.trunk) - client.delete_trunk_subports(trunk_id, attrs) + network_client = self.app.client_manager.network + attrs = _get_attrs_for_subports(network_client, parsed_args) + trunk_id = network_client.find_trunk(parsed_args.trunk) + network_client.delete_trunk_subports(trunk_id, attrs) _formatters = { @@ -343,7 +349,7 @@ def _get_columns(item): ) -def _get_attrs_for_trunk(client_manager, parsed_args): +def _get_attrs_for_trunk(network_client, identity_client, parsed_args): attrs: dict[str, ty.Any] = {} if parsed_args.name is not None: attrs['name'] = str(parsed_args.name) @@ -354,18 +360,15 @@ def _get_attrs_for_trunk(client_manager, parsed_args): if parsed_args.disable: attrs['admin_state_up'] = False if 'parent_port' in parsed_args and parsed_args.parent_port is not None: - port_id = client_manager.network.find_port(parsed_args.parent_port)[ - 'id' - ] + port_id = network_client.find_port(parsed_args.parent_port)['id'] attrs['port_id'] = port_id if 'add_subports' in parsed_args and parsed_args.add_subports is not None: attrs[SUB_PORTS] = _format_subports( - client_manager, parsed_args.add_subports + network_client, parsed_args.add_subports ) # "trunk set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: - identity_client = client_manager.identity project_id = identity_utils.find_project( identity_client, parsed_args.project, @@ -376,12 +379,12 @@ def _get_attrs_for_trunk(client_manager, parsed_args): return attrs -def _format_subports(client_manager, subports): +def _format_subports(network_client, subports): attrs = [] for subport in subports: subport_attrs = {} if subport.get('port'): - port_id = client_manager.network.find_port(subport['port'])['id'] + port_id = network_client.find_port(subport['port'])['id'] subport_attrs['port_id'] = port_id if subport.get('segmentation-id'): try: @@ -400,17 +403,17 @@ def _format_subports(client_manager, subports): return attrs -def _get_attrs_for_subports(client_manager, parsed_args): +def _get_attrs_for_subports(network_client, parsed_args): attrs = [] if 'set_subports' in parsed_args and parsed_args.set_subports is not None: - attrs = _format_subports(client_manager, parsed_args.set_subports) + attrs = _format_subports(network_client, parsed_args.set_subports) if ( 'unset_subports' in parsed_args and parsed_args.unset_subports is not None ): subports_list = [] for subport in parsed_args.unset_subports: - port_id = client_manager.network.find_port(subport)['id'] + port_id = network_client.find_port(subport)['id'] subports_list.append({'port_id': port_id}) attrs = subports_list return attrs From a5e4d5f0fa6cf19fce610508fa00128102613cfa Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 14 Nov 2025 11:47:15 +0000 Subject: [PATCH 39/67] identity: Fix filtering endpoints by project with domain We were incorrectly passing domain_id as a positional argument, causing it to get picked up as the ignore_missing argument instead. Correct this, fixing another bug where the look of projects or domains could be forbidden by policy, in the process. The latter is unlikely to happen, given endpoint lookup is typically an admin-only operation, but it's better to be safe. Change-Id: Idd3300040967d781b7743accd62298cb24c62872 Signed-off-by: Stephen Finucane --- openstackclient/identity/v3/endpoint.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 1dfa88120..ee5900d71 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -231,11 +231,22 @@ def take_action(self, parsed_args): endpoint = None if parsed_args.endpoint: endpoint = identity_client.find_endpoint(parsed_args.endpoint) - project = None + + project_domain_id = None + if parsed_args.project_domain: + project_domain_id = common._find_sdk_id( + identity_client.find_domain, + name_or_id=parsed_args.project_domain, + ) + + project_id = None if parsed_args.project: - project = identity_client.find_project( - parsed_args.project, - parsed_args.project_domain, + project_id = common._find_sdk_id( + identity_client.find_project, + name_or_id=common._get_token_resource( + identity_client, 'project', parsed_args.project + ), + domain_id=project_domain_id, ) if endpoint: @@ -273,9 +284,9 @@ def take_action(self, parsed_args): region = identity_client.get_region(parsed_args.region) kwargs['region_id'] = region.id - if project: + if project_id: data = list( - identity_client.project_endpoints(project=project.id) + identity_client.project_endpoints(project=project_id) ) else: data = list(identity_client.endpoints(**kwargs)) From 55fd501657417518626a68eb8c6ceaa6f5377652 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 14 Nov 2025 11:48:01 +0000 Subject: [PATCH 40/67] identity: Remove duplicated _find_sdk_id method We have a few instances of this. Settle on one. Change-Id: Id115fea1c59ad75ec8e00d665e587020f7177a55 Signed-off-by: Stephen Finucane --- openstackclient/identity/v3/role.py | 56 ++++------ .../identity/v3/role_assignment.py | 32 ++---- .../tests/unit/identity/test_common.py | 100 ++++++++++++++++++ .../tests/unit/identity/v3/test_role.py | 83 +-------------- 4 files changed, 130 insertions(+), 141 deletions(-) create mode 100644 openstackclient/tests/unit/identity/test_common.py diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 3301c3f79..26af11a95 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -82,32 +82,12 @@ def _add_identity_and_resource_options_to_parser(parser): common.add_inherited_option_to_parser(parser) -def _find_sdk_id( - find_command, name_or_id, validate_actor_existence=True, **kwargs -): - try: - resource = find_command( - name_or_id=name_or_id, ignore_missing=False, **kwargs - ) - - # Mimic the behavior of - # openstackclient.identity.common._find_identity_resource() - # and ignore if we don't have permission to find a resource. - except sdk_exc.ForbiddenException: - return name_or_id - except sdk_exc.ResourceNotFound as exc: - if not validate_actor_existence: - return name_or_id - raise exceptions.CommandError from exc - return resource.id - - def _process_identity_and_resource_options( parsed_args, identity_client, validate_actor_existence=True ): def _find_user(): domain_id = ( - _find_sdk_id( + common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.user_domain, validate_actor_existence=validate_actor_existence, @@ -115,7 +95,7 @@ def _find_user(): if parsed_args.user_domain else None ) - return _find_sdk_id( + return common._find_sdk_id( identity_client.find_user, name_or_id=parsed_args.user, validate_actor_existence=validate_actor_existence, @@ -124,7 +104,7 @@ def _find_user(): def _find_group(): domain_id = ( - _find_sdk_id( + common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.group_domain, validate_actor_existence=validate_actor_existence, @@ -132,7 +112,7 @@ def _find_group(): if parsed_args.group_domain else None ) - return _find_sdk_id( + return common._find_sdk_id( identity_client.find_group, name_or_id=parsed_args.group, validate_actor_existence=validate_actor_existence, @@ -141,7 +121,7 @@ def _find_group(): def _find_project(): domain_id = ( - _find_sdk_id( + common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.project_domain, validate_actor_existence=validate_actor_existence, @@ -149,7 +129,7 @@ def _find_project(): if parsed_args.project_domain else None ) - return _find_sdk_id( + return common._find_sdk_id( identity_client.find_project, name_or_id=parsed_args.project, validate_actor_existence=validate_actor_existence, @@ -162,7 +142,7 @@ def _find_project(): kwargs['system'] = parsed_args.system elif parsed_args.user and parsed_args.domain: kwargs['user'] = _find_user() - kwargs['domain'] = _find_sdk_id( + kwargs['domain'] = common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.domain, validate_actor_existence=validate_actor_existence, @@ -175,7 +155,7 @@ def _find_project(): kwargs['system'] = parsed_args.system elif parsed_args.group and parsed_args.domain: kwargs['group'] = _find_group() - kwargs['domain'] = _find_sdk_id( + kwargs['domain'] = common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.domain, validate_actor_existence=validate_actor_existence, @@ -228,10 +208,10 @@ def take_action(self, parsed_args): domain_id = None if parsed_args.role_domain: - domain_id = _find_sdk_id( + domain_id = common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.role_domain ) - role = _find_sdk_id( + role = common._find_sdk_id( identity_client.find_role, name_or_id=parsed_args.role, domain_id=domain_id, @@ -328,7 +308,7 @@ def take_action(self, parsed_args): create_kwargs = {} if parsed_args.domain: - create_kwargs['domain_id'] = _find_sdk_id( + create_kwargs['domain_id'] = common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.domain ) @@ -381,13 +361,13 @@ def take_action(self, parsed_args): domain_id = None if parsed_args.domain: - domain_id = _find_sdk_id( + domain_id = common._find_sdk_id( identity_client.find_domain, parsed_args.domain ) errors = 0 for role in parsed_args.roles: try: - role_id = _find_sdk_id( + role_id = common._find_sdk_id( identity_client.find_role, name_or_id=role, domain_id=domain_id, @@ -482,11 +462,11 @@ def take_action(self, parsed_args): domain_id = None if parsed_args.role_domain: - domain_id = _find_sdk_id( + domain_id = common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.role_domain, ) - role = _find_sdk_id( + role = common._find_sdk_id( identity_client.find_role, name_or_id=parsed_args.role, domain_id=domain_id, @@ -582,7 +562,7 @@ def take_action(self, parsed_args): domain_id = None if parsed_args.domain: - domain_id = _find_sdk_id( + domain_id = common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.domain, ) @@ -591,7 +571,7 @@ def take_action(self, parsed_args): if parsed_args.immutable is not None: update_kwargs["options"] = {"immutable": parsed_args.immutable} - role = _find_sdk_id( + role = common._find_sdk_id( identity_client.find_role, name_or_id=parsed_args.role, domain_id=domain_id, @@ -623,7 +603,7 @@ def take_action(self, parsed_args): domain_id = None if parsed_args.domain: - domain_id = _find_sdk_id( + domain_id = common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.domain, ) diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 12e1527b6..ed9e8c0f7 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -13,7 +13,6 @@ """Identity v3 Assignment action implementations""" -from openstack import exceptions as sdk_exceptions from osc_lib.command import command from openstackclient.i18n import _ @@ -51,15 +50,6 @@ def _get_ids(attr): ) -def _find_sdk_id(find_command, name_or_id, **kwargs): - try: - return find_command( - name_or_id=name_or_id, ignore_missing=False, **kwargs - ).id - except sdk_exceptions.ForbiddenException: - return name_or_id - - class ListRoleAssignment(command.Lister): _description = _("List role assignments") @@ -135,12 +125,12 @@ def take_action(self, parsed_args): role_id = None role_domain_id = None if parsed_args.role_domain: - role_domain_id = _find_sdk_id( + role_domain_id = common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.role_domain, ) if parsed_args.role: - role_id = _find_sdk_id( + role_id = common._find_sdk_id( identity_client.find_role, name_or_id=parsed_args.role, domain_id=role_domain_id, @@ -148,21 +138,21 @@ def take_action(self, parsed_args): user_domain_id = None if parsed_args.user_domain: - user_domain_id = _find_sdk_id( + user_domain_id = common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.user_domain, ) user_id = None if parsed_args.user: - user_id = _find_sdk_id( + user_id = common._find_sdk_id( identity_client.find_user, name_or_id=parsed_args.user, domain_id=user_domain_id, ) elif parsed_args.authuser: if auth_ref: - user_id = _find_sdk_id( + user_id = common._find_sdk_id( identity_client.find_user, name_or_id=auth_ref.user_id, ) @@ -173,21 +163,21 @@ def take_action(self, parsed_args): domain_id = None if parsed_args.domain: - domain_id = _find_sdk_id( + domain_id = common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.domain, ) project_domain_id = None if parsed_args.project_domain: - project_domain_id = _find_sdk_id( + project_domain_id = common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.project_domain, ) project_id = None if parsed_args.project: - project_id = _find_sdk_id( + project_id = common._find_sdk_id( identity_client.find_project, name_or_id=common._get_token_resource( identity_client, 'project', parsed_args.project @@ -196,21 +186,21 @@ def take_action(self, parsed_args): ) elif parsed_args.authproject: if auth_ref: - project_id = _find_sdk_id( + project_id = common._find_sdk_id( identity_client.find_project, name_or_id=auth_ref.project_id, ) group_domain_id = None if parsed_args.group_domain: - group_domain_id = _find_sdk_id( + group_domain_id = common._find_sdk_id( identity_client.find_domain, name_or_id=parsed_args.group_domain, ) group_id = None if parsed_args.group: - group_id = _find_sdk_id( + group_id = common._find_sdk_id( identity_client.find_group, name_or_id=parsed_args.group, domain_id=group_domain_id, diff --git a/openstackclient/tests/unit/identity/test_common.py b/openstackclient/tests/unit/identity/test_common.py new file mode 100644 index 000000000..ae85262dc --- /dev/null +++ b/openstackclient/tests/unit/identity/test_common.py @@ -0,0 +1,100 @@ +# 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 unittest import mock + +from openstack import exceptions as sdk_exc +from openstack.identity.v3 import user as _user +from openstack.test import fakes as sdk_fakes +from osc_lib import exceptions + +from openstackclient.identity import common +from openstackclient.tests.unit import utils as test_utils + + +class TestFindSDKId(test_utils.TestCase): + def setUp(self): + super().setUp() + self.user = sdk_fakes.generate_fake_resource(_user.User) + self.identity_sdk_client = mock.Mock() + self.identity_sdk_client.find_user = mock.Mock() + + def test_find_sdk_id_validate(self): + self.identity_sdk_client.find_user.side_effect = [self.user] + + result = common._find_sdk_id( + self.identity_sdk_client.find_user, + name_or_id=self.user.id, + validate_actor_existence=True, + ) + self.assertEqual(self.user.id, result) + + def test_find_sdk_id_no_validate(self): + self.identity_sdk_client.find_user.side_effect = [self.user] + + result = common._find_sdk_id( + self.identity_sdk_client.find_user, + name_or_id=self.user.id, + validate_actor_existence=False, + ) + self.assertEqual(self.user.id, result) + + def test_find_sdk_id_not_found_validate(self): + self.identity_sdk_client.find_user.side_effect = [ + sdk_exc.ResourceNotFound, + ] + + self.assertRaises( + exceptions.CommandError, + common._find_sdk_id, + self.identity_sdk_client.find_user, + name_or_id=self.user.id, + validate_actor_existence=True, + ) + + def test_find_sdk_id_not_found_no_validate(self): + self.identity_sdk_client.find_user.side_effect = [ + sdk_exc.ResourceNotFound, + ] + + result = common._find_sdk_id( + self.identity_sdk_client.find_user, + name_or_id=self.user.id, + validate_actor_existence=False, + ) + self.assertEqual(self.user.id, result) + + def test_find_sdk_id_forbidden_validate(self): + self.identity_sdk_client.find_user.side_effect = [ + sdk_exc.ForbiddenException, + ] + + result = common._find_sdk_id( + self.identity_sdk_client.find_user, + name_or_id=self.user.id, + validate_actor_existence=True, + ) + + self.assertEqual(self.user.id, result) + + def test_find_sdk_id_forbidden_no_validate(self): + self.identity_sdk_client.find_user.side_effect = [ + sdk_exc.ForbiddenException, + ] + + result = common._find_sdk_id( + self.identity_sdk_client.find_user, + name_or_id=self.user.id, + validate_actor_existence=False, + ) + + self.assertEqual(self.user.id, result) diff --git a/openstackclient/tests/unit/identity/v3/test_role.py b/openstackclient/tests/unit/identity/v3/test_role.py index 3ed1d447a..90b2d7121 100644 --- a/openstackclient/tests/unit/identity/v3/test_role.py +++ b/openstackclient/tests/unit/identity/v3/test_role.py @@ -15,8 +15,6 @@ from unittest import mock -from osc_lib import exceptions - from openstack import exceptions as sdk_exc from openstack.identity.v3 import domain as _domain from openstack.identity.v3 import group as _group @@ -25,10 +23,10 @@ from openstack.identity.v3 import system as _system from openstack.identity.v3 import user as _user from openstack.test import fakes as sdk_fakes +from osc_lib import exceptions from openstackclient.identity.v3 import role from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes -from openstackclient.tests.unit import utils as test_utils class TestRoleInherited(identity_fakes.TestIdentityv3): @@ -36,85 +34,6 @@ def _is_inheritance_testcase(self): return True -class TestFindSDKId(test_utils.TestCase): - def setUp(self): - super().setUp() - self.user = sdk_fakes.generate_fake_resource(_user.User) - self.identity_sdk_client = mock.Mock() - self.identity_sdk_client.find_user = mock.Mock() - - def test_find_sdk_id_validate(self): - self.identity_sdk_client.find_user.side_effect = [self.user] - - result = role._find_sdk_id( - self.identity_sdk_client.find_user, - name_or_id=self.user.id, - validate_actor_existence=True, - ) - self.assertEqual(self.user.id, result) - - def test_find_sdk_id_no_validate(self): - self.identity_sdk_client.find_user.side_effect = [self.user] - - result = role._find_sdk_id( - self.identity_sdk_client.find_user, - name_or_id=self.user.id, - validate_actor_existence=False, - ) - self.assertEqual(self.user.id, result) - - def test_find_sdk_id_not_found_validate(self): - self.identity_sdk_client.find_user.side_effect = [ - sdk_exc.ResourceNotFound, - ] - - self.assertRaises( - exceptions.CommandError, - role._find_sdk_id, - self.identity_sdk_client.find_user, - name_or_id=self.user.id, - validate_actor_existence=True, - ) - - def test_find_sdk_id_not_found_no_validate(self): - self.identity_sdk_client.find_user.side_effect = [ - sdk_exc.ResourceNotFound, - ] - - result = role._find_sdk_id( - self.identity_sdk_client.find_user, - name_or_id=self.user.id, - validate_actor_existence=False, - ) - self.assertEqual(self.user.id, result) - - def test_find_sdk_id_forbidden_validate(self): - self.identity_sdk_client.find_user.side_effect = [ - sdk_exc.ForbiddenException, - ] - - result = role._find_sdk_id( - self.identity_sdk_client.find_user, - name_or_id=self.user.id, - validate_actor_existence=True, - ) - - self.assertEqual(self.user.id, result) - - def test_find_sdk_id_forbidden_no_validate(self): - self.identity_sdk_client.find_user.side_effect = [ - sdk_exc.ForbiddenException, - ] - - result = role._find_sdk_id( - self.identity_sdk_client.find_user, - name_or_id=self.user.id, - validate_actor_existence=False, - ) - - self.assertEqual(self.user.id, result) - - class TestRoleAdd(identity_fakes.TestIdentityv3): def _is_inheritance_testcase(self): return False From 73021165ff486bed3a747051ce3db8e4270d6547 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 13 Nov 2025 12:08:27 +0000 Subject: [PATCH 41/67] trivial: Add missing ignore_missing arguments This prevents a class of bugs. Change-Id: I96e1cd8ed4a682ef5c95f67f3d1246f7026eada9 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 4 +- .../identity/v3/application_credential.py | 2 +- openstackclient/identity/v3/domain.py | 12 +++-- openstackclient/identity/v3/endpoint.py | 20 ++++++--- openstackclient/identity/v3/group.py | 9 +++- openstackclient/identity/v3/role.py | 1 + openstackclient/identity/v3/trust.py | 4 +- openstackclient/identity/v3/user.py | 1 + openstackclient/network/v2/network_trunk.py | 44 ++++++++++++++----- openstackclient/network/v2/port.py | 28 ++++++------ openstackclient/network/v2/router.py | 11 ++++- .../v3/test_application_credential.py | 2 +- .../tests/unit/identity/v3/test_domain.py | 2 +- .../tests/unit/identity/v3/test_endpoint.py | 2 +- .../tests/unit/identity/v3/test_group.py | 6 ++- .../unit/network/v2/test_network_trunk.py | 4 +- .../tests/unit/network/v2/test_router.py | 8 ++-- .../unit/volume/v2/test_volume_backup.py | 4 +- openstackclient/volume/v2/volume_backup.py | 4 +- 19 files changed, 116 insertions(+), 52 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 33a0bb226..500395114 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -2180,7 +2180,9 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for name_or_id in parsed_args.server: - server = compute_client.find_server(name_or_id) + server = compute_client.find_server( + name_or_id, ignore_missing=False + ) server.trigger_crash_dump(compute_client) diff --git a/openstackclient/identity/v3/application_credential.py b/openstackclient/identity/v3/application_credential.py index 93367f8a3..c6bad267d 100644 --- a/openstackclient/identity/v3/application_credential.py +++ b/openstackclient/identity/v3/application_credential.py @@ -278,7 +278,7 @@ def take_action(self, parsed_args): for ac in parsed_args.application_credential: try: app_cred = identity_client.find_application_credential( - user_id, ac + user_id, ac, ignore_missing=False ) identity_client.delete_application_credential( user_id, app_cred.id diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 536243a70..06c7191cb 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -107,7 +107,9 @@ def take_action(self, parsed_args): ) except sdk_exceptions.ConflictException: if parsed_args.or_show: - domain = identity_client.find_domain(parsed_args.name) + domain = identity_client.find_domain( + parsed_args.name, ignore_missing=False + ) LOG.info(_('Returning existing domain %s'), domain.name) else: raise @@ -238,7 +240,9 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.sdk_connection.identity - domain = identity_client.find_domain(parsed_args.domain) + domain = identity_client.find_domain( + parsed_args.domain, ignore_missing=False + ) kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name @@ -266,6 +270,8 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.sdk_connection.identity - domain = identity_client.find_domain(parsed_args.domain) + domain = identity_client.find_domain( + parsed_args.domain, ignore_missing=False + ) return _format_domain(domain) diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index ee5900d71..bc690939d 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -169,7 +169,9 @@ def take_action(self, parsed_args): result = 0 for i in parsed_args.endpoint: try: - endpoint_id = identity_client.find_endpoint(i).id + endpoint_id = identity_client.find_endpoint( + i, ignore_missing=False + ).id identity_client.delete_endpoint(endpoint_id) except Exception as e: result += 1 @@ -230,7 +232,9 @@ def take_action(self, parsed_args): endpoint = None if parsed_args.endpoint: - endpoint = identity_client.find_endpoint(parsed_args.endpoint) + endpoint = identity_client.find_endpoint( + parsed_args.endpoint, ignore_missing=False + ) project_domain_id = None if parsed_args.project_domain: @@ -292,7 +296,9 @@ def take_action(self, parsed_args): data = list(identity_client.endpoints(**kwargs)) for ep in data: - service = identity_client.find_service(ep.service_id) + service = identity_client.find_service( + ep.service_id, ignore_missing=False + ) ep.service_name = getattr(service, 'name', '') ep.service_type = service.type @@ -393,7 +399,9 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.sdk_connection.identity - endpoint = identity_client.find_endpoint(parsed_args.endpoint) + endpoint = identity_client.find_endpoint( + parsed_args.endpoint, ignore_missing=False + ) kwargs = {} @@ -440,7 +448,9 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.sdk_connection.identity - endpoint = identity_client.find_endpoint(parsed_args.endpoint) + endpoint = identity_client.find_endpoint( + parsed_args.endpoint, ignore_missing=False + ) service = common.find_service_sdk(identity_client, endpoint.service_id) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 92980acc6..2e3d9f9b0 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -211,10 +211,15 @@ def take_action(self, parsed_args): if parsed_args.or_show: if parsed_args.domain: group = identity_client.find_group( - parsed_args.name, domain_id=parsed_args.domain + parsed_args.name, + domain_id=parsed_args.domain, + ignore_missing=False, ) else: - group = identity_client.find_group(parsed_args.name) + group = identity_client.find_group( + parsed_args.name, + ignore_missing=False, + ) LOG.info(_('Returning existing group %s'), group.name) else: raise diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 26af11a95..b4e7bd175 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -410,6 +410,7 @@ def take_action(self, parsed_args): if parsed_args.domain: domain = identity_client.find_domain( name_or_id=parsed_args.domain, + ignore_missing=False, ) data = identity_client.roles(domain_id=domain.id) return ( diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index df8e2f4ea..255f7877d 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -179,7 +179,9 @@ def take_action(self, parsed_args): roles = [] for role in parsed_args.roles: try: - role_id = identity_client.find_role(role).id + role_id = identity_client.find_role( + role, ignore_missing=False + ).id except sdk_exceptions.ForbiddenException: role_id = role roles.append({"id": role_id}) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 34d85fba9..6b3b7217e 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -441,6 +441,7 @@ def take_action(self, parsed_args): if parsed_args.domain: domain = identity_client.find_domain( name_or_id=parsed_args.domain, + ignore_missing=False, ).id group = None diff --git a/openstackclient/network/v2/network_trunk.py b/openstackclient/network/v2/network_trunk.py index 61d378371..53681b774 100644 --- a/openstackclient/network/v2/network_trunk.py +++ b/openstackclient/network/v2/network_trunk.py @@ -119,7 +119,10 @@ def take_action(self, parsed_args): result = 0 for trunk in parsed_args.trunk: try: - trunk_id = network_client.find_trunk(trunk).id + trunk_id = network_client.find_trunk( + trunk, + ignore_missing=False, + ).id network_client.delete_trunk(trunk_id) except Exception as e: result += 1 @@ -220,7 +223,10 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): network_client = self.app.client_manager.network identity_client = self.app.client_manager.identity - trunk_id = network_client.find_trunk(parsed_args.trunk) + trunk_id = network_client.find_trunk( + parsed_args.trunk, + ignore_missing=False, + ) attrs = _get_attrs_for_trunk( network_client, identity_client, parsed_args ) @@ -258,7 +264,10 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): network_client = self.app.client_manager.network - trunk_id = network_client.find_trunk(parsed_args.trunk).id + trunk_id = network_client.find_trunk( + parsed_args.trunk, + ignore_missing=False, + ).id obj = network_client.get_trunk(trunk_id) display_columns, columns = _get_columns(obj) data = osc_utils.get_dict_properties( @@ -282,7 +291,10 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): network_client = self.app.client_manager.network - trunk_id = network_client.find_trunk(parsed_args.trunk) + trunk_id = network_client.find_trunk( + parsed_args.trunk, + ignore_missing=False, + ) data = network_client.get_trunk_subports(trunk_id) headers: tuple[str, ...] = ( 'Port', @@ -332,7 +344,10 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): network_client = self.app.client_manager.network attrs = _get_attrs_for_subports(network_client, parsed_args) - trunk_id = network_client.find_trunk(parsed_args.trunk) + trunk_id = network_client.find_trunk( + parsed_args.trunk, + ignore_missing=False, + ) network_client.delete_trunk_subports(trunk_id, attrs) @@ -360,7 +375,10 @@ def _get_attrs_for_trunk(network_client, identity_client, parsed_args): if parsed_args.disable: attrs['admin_state_up'] = False if 'parent_port' in parsed_args and parsed_args.parent_port is not None: - port_id = network_client.find_port(parsed_args.parent_port)['id'] + port_id = network_client.find_port( + parsed_args.parent_port, + ignore_missing=False, + ).id attrs['port_id'] = port_id if 'add_subports' in parsed_args and parsed_args.add_subports is not None: attrs[SUB_PORTS] = _format_subports( @@ -384,7 +402,10 @@ def _format_subports(network_client, subports): for subport in subports: subport_attrs = {} if subport.get('port'): - port_id = network_client.find_port(subport['port'])['id'] + port_id = network_client.find_port( + subport['port'], + ignore_missing=False, + ).id subport_attrs['port_id'] = port_id if subport.get('segmentation-id'): try: @@ -413,11 +434,10 @@ def _get_attrs_for_subports(network_client, parsed_args): ): subports_list = [] for subport in parsed_args.unset_subports: - port_id = network_client.find_port(subport)['id'] + port_id = network_client.find_port( + subport, + ignore_missing=False, + )['id'] subports_list.append({'port_id': port_id}) attrs = subports_list return attrs - - -def _get_id(client, id_or_name, resource): - return client.find_resource(resource, str(id_or_name))['id'] diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 5cfc41db0..ff0e42a87 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -636,11 +636,11 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - client = self.app.client_manager.network - _network = client.find_network( + network_client = self.app.client_manager.network + network = network_client.find_network( parsed_args.network, ignore_missing=False ) - parsed_args.network = _network.id + parsed_args.network = network.id _prepare_fixed_ips(self.app.client_manager, parsed_args) attrs = _get_attrs(self.app.client_manager, parsed_args) @@ -654,7 +654,7 @@ def take_action(self, parsed_args): if parsed_args.security_groups is not None: attrs['security_group_ids'] = [ - client.find_security_group(sg, ignore_missing=False).id + network_client.find_security_group(sg, ignore_missing=False).id for sg in parsed_args.security_groups ] @@ -667,7 +667,7 @@ def take_action(self, parsed_args): attrs["extra_dhcp_opts"] = _convert_extra_dhcp_options(parsed_args) if parsed_args.qos_policy: - attrs['qos_policy_id'] = client.find_qos_policy( + attrs['qos_policy_id'] = network_client.find_qos_policy( parsed_args.qos_policy, ignore_missing=False ).id @@ -675,7 +675,9 @@ def take_action(self, parsed_args): _validate_port_hints(parsed_args.hint) expanded_hints = _expand_port_hint_aliases(parsed_args.hint) try: - client.find_extension('port-hints', ignore_missing=False) + network_client.find_extension( + 'port-hints', ignore_missing=False + ) except Exception as e: msg = _('Not supported by Network API: %(e)s') % {'e': e} raise exceptions.CommandError(msg) @@ -686,7 +688,7 @@ def take_action(self, parsed_args): in expanded_hints['openvswitch']['other_config'] ): try: - client.find_extension( + network_client.find_extension( 'port-hint-ovs-tx-steering', ignore_missing=False ) except Exception as e: @@ -695,7 +697,9 @@ def take_action(self, parsed_args): attrs['hints'] = expanded_hints set_tags_in_post = bool( - client.find_extension('tag-ports-during-bulk-creation') + network_client.find_extension( + 'tag-ports-during-bulk-creation', ignore_missing=True + ) ) if set_tags_in_post: if parsed_args.no_tag: @@ -707,14 +711,12 @@ def take_action(self, parsed_args): self._parse_extra_properties(parsed_args.extra_properties) ) - with common.check_missing_extension_if_error( - self.app.client_manager.network, attrs - ): - obj = client.create_port(**attrs) + with common.check_missing_extension_if_error(network_client, attrs): + obj = network_client.create_port(**attrs) if not set_tags_in_post: # tags cannot be set when created, so tags need to be set later. - _tag.update_tags_for_set(client, obj, parsed_args) + _tag.update_tags_for_set(network_client, obj, parsed_args) display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 67e3de06f..29dfcce89 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -89,7 +89,12 @@ def _get_columns(item): def is_multiple_gateways_supported(n_client): - return n_client.find_extension("external-gateway-multihoming") is not None + return ( + n_client.find_extension( + "external-gateway-multihoming", ignore_missing=True + ) + is not None + ) def _passed_multiple_gateways(extension_supported, external_gateways): @@ -236,7 +241,9 @@ def _get_attrs(client_manager, parsed_args): # "router set" command doesn't support setting flavor_id. if 'flavor_id' in parsed_args and parsed_args.flavor_id is not None: - flavor = n_client.find_flavor(parsed_args.flavor_id) + flavor = n_client.find_flavor( + parsed_args.flavor_id, ignore_missing=False + ) attrs['flavor_id'] = flavor.id elif 'flavor' in parsed_args and parsed_args.flavor is not None: flavor = n_client.find_flavor(parsed_args.flavor, ignore_missing=False) diff --git a/openstackclient/tests/unit/identity/v3/test_application_credential.py b/openstackclient/tests/unit/identity/v3/test_application_credential.py index 3bc2d0c29..a7307d6ee 100644 --- a/openstackclient/tests/unit/identity/v3/test_application_credential.py +++ b/openstackclient/tests/unit/identity/v3/test_application_credential.py @@ -302,7 +302,7 @@ def test_delete_multi_app_creds_with_exception(self): calls = [] for a in arglist: - calls.append(call(user_id, a)) + calls.append(call(user_id, a, ignore_missing=False)) self.identity_sdk_client.find_application_credential.assert_has_calls( calls diff --git a/openstackclient/tests/unit/identity/v3/test_domain.py b/openstackclient/tests/unit/identity/v3/test_domain.py index abe8076af..cc0593d1f 100644 --- a/openstackclient/tests/unit/identity/v3/test_domain.py +++ b/openstackclient/tests/unit/identity/v3/test_domain.py @@ -520,7 +520,7 @@ def test_domain_show(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.identity_sdk_client.find_domain.assert_called_with( - self.domain.id, + self.domain.id, ignore_missing=False ) self.assertEqual(self.columns, columns) diff --git a/openstackclient/tests/unit/identity/v3/test_endpoint.py b/openstackclient/tests/unit/identity/v3/test_endpoint.py index 84e10e55a..ba69317f5 100644 --- a/openstackclient/tests/unit/identity/v3/test_endpoint.py +++ b/openstackclient/tests/unit/identity/v3/test_endpoint.py @@ -678,7 +678,7 @@ def test_endpoint_show(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) self.identity_sdk_client.find_endpoint.assert_called_with( - self.endpoint.id, + self.endpoint.id, ignore_missing=False ) collist = ( diff --git a/openstackclient/tests/unit/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py index d212e4565..598402e07 100644 --- a/openstackclient/tests/unit/identity/v3/test_group.py +++ b/openstackclient/tests/unit/identity/v3/test_group.py @@ -253,7 +253,7 @@ def test_group_create_or_show(self): columns, data = self.cmd.take_action(parsed_args) self.identity_sdk_client.find_group.assert_called_once_with( - self.group.name + self.group.name, ignore_missing=False ) self.assertEqual(self.columns, columns) datalist = ( @@ -286,7 +286,9 @@ def test_group_create_or_show_with_domain(self): columns, data = self.cmd.take_action(parsed_args) self.identity_sdk_client.find_group.assert_called_once_with( - self.group_with_options.name, domain_id=self.domain.id + self.group_with_options.name, + domain_id=self.domain.id, + ignore_missing=False, ) self.assertEqual(self.columns, columns) datalist = ( diff --git a/openstackclient/tests/unit/network/v2/test_network_trunk.py b/openstackclient/tests/unit/network/v2/test_network_trunk.py index 3b6c364d3..1056c21c3 100644 --- a/openstackclient/tests/unit/network/v2/test_network_trunk.py +++ b/openstackclient/tests/unit/network/v2/test_network_trunk.py @@ -797,7 +797,9 @@ def test_set_trunk_add_subport_with_exception(self): exceptions.CommandError ) - self.network_client.find_port.side_effect = [{'id': 'invalid_subport'}] + self.network_client.find_port.side_effect = [ + network_fakes.create_one_port({'id': 'invalid_subport'}) + ] with testtools.ExpectedException(exceptions.CommandError) as e: self.cmd.take_action(parsed_args) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 221fe67a9..6ebb7809e 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -176,7 +176,7 @@ def setUp(self): self.network_client.set_tags.return_value = None self.network_client.find_extension.side_effect = ( - lambda name: self._extensions.get(name) + lambda name, ignore_missing=True: self._extensions.get(name) ) # Get the command object to test self.cmd = router.CreateRouter(self.app, None) @@ -1317,7 +1317,7 @@ def setUp(self): self.network_client.find_subnet.return_value = self._subnet self.network_client.find_extension.side_effect = ( - lambda name: self._extensions.get(name) + lambda name, ignore_missing=True: self._extensions.get(name) ) # Get the command object to test self.cmd = router.SetRouter(self.app, None) @@ -1956,7 +1956,7 @@ def setUp(self): self.network_client.set_tags.return_value = None self._extensions = {'fake': network_fakes.create_one_extension()} self.network_client.find_extension.side_effect = ( - lambda name: self._extensions.get(name) + lambda name, ignore_missing=True: self._extensions.get(name) ) self.network_client.remove_external_gateways.return_value = None @@ -2158,7 +2158,7 @@ def setUp(self): ) } self.network_client.find_extension.side_effect = ( - lambda name: self._extensions.get(name) + lambda name, ignore_missing=True: self._extensions.get(name) ) self.network_client.find_router.return_value = self._router diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py index 7d4fba92d..e7bbb6999 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -565,7 +565,9 @@ def test_backup_show(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.volume_sdk_client.find_backup.assert_called_with(self.backup.id) + self.volume_sdk_client.find_backup.assert_called_with( + self.backup.id, ignore_missing=False + ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/volume/v2/volume_backup.py b/openstackclient/volume/v2/volume_backup.py index 55cde32f1..30e67e296 100644 --- a/openstackclient/volume/v2/volume_backup.py +++ b/openstackclient/volume/v2/volume_backup.py @@ -453,7 +453,9 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): volume_client = self.app.client_manager.sdk_connection.volume - backup = volume_client.find_backup(parsed_args.backup) + backup = volume_client.find_backup( + parsed_args.backup, ignore_missing=False + ) columns: tuple[str, ...] = ( "availability_zone", "container", From db6c34c2c78ad4c5939afce8e1fa954de9dcbf5e Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 13 Nov 2025 11:58:51 +0000 Subject: [PATCH 42/67] hacking: Check for missing ignore_missing calls This comes up in reviews frequently. Let's automate it. Change-Id: Ia7ebd7cf29fe4550b22921e898bebaaa5f7bb4f6 Signed-off-by: Stephen Finucane --- hacking/checks.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++- tox.ini | 1 + 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/hacking/checks.py b/hacking/checks.py index 98b40370d..facb0cc6a 100644 --- a/hacking/checks.py +++ b/hacking/checks.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import ast import os import re @@ -74,7 +75,7 @@ def assert_no_duplicated_setup(logical_line, filename): @core.flake8ext -def assert_use_of_client_aliases(logical_line, filename): +def assert_use_of_client_aliases(logical_line): """Ensure we use $service_client instead of $sdk_connection.service. O402 @@ -106,3 +107,73 @@ def assert_use_of_client_aliases(logical_line, filename): f"O402: {match.group(1)} is already a mock: there's no need to " f"assign a new mock.Mock instance.", ) + + +class SDKProxyFindChecker(ast.NodeVisitor): + """NodeVisitor to find ``*_client.find_*`` statements.""" + + def __init__(self): + self.error = False + + def visit_Call(self, node): + # No need to keep visiting the AST if we already found something. + if self.error: + return + + self.generic_visit(node) + + if not ( + isinstance(node.func, ast.Attribute) + and node.func.attr.startswith('find_') # and + # isinstance(node.func.value, ast.Attribute) and + # node.func.value.attr.endswith('_client') + ): + # print(f'skipping: got {node.func}') + return + + if not ( + ( + # handle calls like 'identity_client.find_project' + isinstance(node.func.value, ast.Name) + and node.func.value.id.endswith('client') + ) + or ( + # handle calls like 'self.app.client_manager.image.find_image' + isinstance(node.func.value, ast.Attribute) + and node.func.value.attr + in ('identity', 'network', 'image', 'compute') + ) + ): + return + + if not any(kw.arg == 'ignore_missing' for kw in node.keywords): + self.error = True + + +@core.flake8ext +def assert_find_ignore_missing_kwargs(logical_line, filename): + """Ensure ignore_missing is always used for ``find_*`` SDK proxy calls. + + Okay: self.compute_client.find_server(foo, ignore_missing=True) + Okay: self.image_client.find_server(foo, ignore_missing=False) + Okay: self.volume_client.volumes.find(name='foo') + O403: self.network_client.find_network(parsed_args.network) + O403: self.compute_client.find_flavor(flavor_id, get_extra_specs=True) + """ + if 'tests' in filename: + return + + checker = SDKProxyFindChecker() + try: + parsed_logical_line = ast.parse(logical_line) + except SyntaxError: + # let flake8 catch this itself + # https://github.com/PyCQA/flake8/issues/1948 + return + checker.visit(parsed_logical_line) + if checker.error: + yield ( + 0, + 'O403: Calls to find_* proxy methods must explicitly set ' + 'ignore_missing', + ) diff --git a/tox.ini b/tox.ini index 2ee2dc2ca..4392a36a8 100644 --- a/tox.ini +++ b/tox.ini @@ -125,4 +125,5 @@ extension = O400 = checks:assert_no_oslo O401 = checks:assert_no_duplicated_setup O402 = checks:assert_use_of_client_aliases + O403 = checks:assert_find_ignore_missing_kwargs paths = ./hacking From aa87db6a932461581c4b55b21f6718c9df45809f Mon Sep 17 00:00:00 2001 From: seheonnn Date: Tue, 16 Sep 2025 11:10:07 +0900 Subject: [PATCH 43/67] tests: Update functional test for image metadef object - Add functional tests for 'openstack image metadef object' - Drop '-f json' where parse_output=True - Address review comments Change-Id: Ibbb8bcdebc7e751f2bd220240eb47b4780e331f6 Signed-off-by: seheonnn --- .../image/v2/test_metadef_objects.py | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 openstackclient/tests/functional/image/v2/test_metadef_objects.py diff --git a/openstackclient/tests/functional/image/v2/test_metadef_objects.py b/openstackclient/tests/functional/image/v2/test_metadef_objects.py new file mode 100644 index 000000000..5216c933f --- /dev/null +++ b/openstackclient/tests/functional/image/v2/test_metadef_objects.py @@ -0,0 +1,69 @@ +# 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 openstackclient.tests.functional import base + + +class MetadefObjectTests(base.TestCase): + def setUp(self): + super().setUp() + self.obj_name = self.getUniqueString('metadef-obj') + self.ns_name = self.getUniqueString('metadef-ns') + self.openstack(f"image metadef namespace create {self.ns_name}") + self.addCleanup( + lambda: self.openstack( + f"image metadef namespace delete {self.ns_name}" + ) + ) + + def test_metadef_objects(self): + # CREATE + created = self.openstack( + ( + "image metadef object create " + f"--namespace {self.ns_name} " + f"{self.obj_name}" + ), + parse_output=True, + ) + self.addCleanup( + lambda: self.openstack( + f"image metadef object delete {self.ns_name} {self.obj_name}" + ) + ) + self.assertEqual(self.obj_name, created["name"]) + self.assertEqual(self.ns_name, created["namespace_name"]) + + # UPDATE + new_name = f"{self.obj_name}-updated" + self.openstack( + "image metadef object update " + f"{self.ns_name} {self.obj_name} " + f"--name {new_name}" + ) + self.obj_name = new_name + + # READ (get) + shown = self.openstack( + f"image metadef object show {self.ns_name} {self.obj_name}", + parse_output=True, + ) + self.assertEqual(self.obj_name, shown["name"]) + self.assertEqual(self.ns_name, shown["namespace_name"]) + + # READ (list) + rows = self.openstack( + f"image metadef object list {self.ns_name}", + parse_output=True, + ) + names = {row["name"] for row in rows} + self.assertIn(self.obj_name, names) From c17c5f0df61ad36876c63192c888f27141905af0 Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Tue, 18 Nov 2025 19:36:28 -0500 Subject: [PATCH 44/67] Try to make help text of network code consistent Just change all text to be as consistent as possible. TrivialFix Change-Id: I959cda9b0688f0fcec0f55ce4c8cadf209d3537f Signed-off-by: Brian Haley --- openstackclient/network/v2/address_group.py | 5 +-- openstackclient/network/v2/address_scope.py | 12 ++++--- .../network/v2/default_security_group_rule.py | 7 ++-- openstackclient/network/v2/floating_ip.py | 25 ++++++++----- .../network/v2/floating_ip_port_forwarding.py | 13 ++++--- openstackclient/network/v2/ip_availability.py | 9 +++-- .../network/v2/l3_conntrack_helper.py | 13 +++++-- openstackclient/network/v2/local_ip.py | 18 ++++++---- .../network/v2/local_ip_association.py | 9 +++-- openstackclient/network/v2/ndp_proxy.py | 17 ++++++--- openstackclient/network/v2/network.py | 35 +++++++++++-------- openstackclient/network/v2/network_agent.py | 4 +-- .../network/v2/network_qos_policy.py | 7 ++-- openstackclient/network/v2/network_rbac.py | 11 +++--- openstackclient/network/v2/network_segment.py | 2 +- .../network/v2/network_segment_range.py | 15 +++++--- openstackclient/network/v2/network_trunk.py | 2 +- openstackclient/network/v2/port.py | 10 +++--- openstackclient/network/v2/router.py | 8 +++-- openstackclient/network/v2/security_group.py | 9 +++-- .../network/v2/security_group_rule.py | 16 ++++++--- openstackclient/network/v2/subnet.py | 27 +++++++------- openstackclient/network/v2/subnet_pool.py | 18 +++++----- 23 files changed, 182 insertions(+), 110 deletions(-) diff --git a/openstackclient/network/v2/address_group.py b/openstackclient/network/v2/address_group.py index d47c34cdd..53a91561a 100644 --- a/openstackclient/network/v2/address_group.py +++ b/openstackclient/network/v2/address_group.py @@ -151,13 +151,14 @@ def get_parser(self, prog_name): parser.add_argument( '--name', metavar='', - help=_("List only address groups of given name in output"), + help=_("List only address groups with the specified name"), ) parser.add_argument( '--project', metavar="", help=_( - "List address groups according to their project (name or ID)" + "List only address groups with the specified project " + "(name or ID)" ), ) identity_common.add_project_domain_option_to_parser(parser) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index a7f396b73..f61dcff55 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -160,7 +160,7 @@ def get_parser(self, prog_name): parser.add_argument( '--name', metavar='', - help=_("List only address scopes of given name in output"), + help=_("List only address scopes with the specified name"), ) parser.add_argument( '--ip-version', @@ -169,14 +169,16 @@ def get_parser(self, prog_name): metavar='', dest='ip_version', help=_( - "List address scopes of given IP version networks (4 or 6)" + "List only address scopes with the specified IP version " + "networks (4 or 6)" ), ) parser.add_argument( '--project', metavar="", help=_( - "List address scopes according to their project (name or ID)" + "List only address scopes with the specified project " + "(name or ID)" ), ) identity_common.add_project_domain_option_to_parser(parser) @@ -185,12 +187,12 @@ def get_parser(self, prog_name): shared_group.add_argument( '--share', action='store_true', - help=_("List address scopes shared between projects"), + help=_("List only address scopes shared between projects"), ) shared_group.add_argument( '--no-share', action='store_true', - help=_("List address scopes not shared between projects"), + help=_("List only address scopes not shared between projects"), ) return parser diff --git a/openstackclient/network/v2/default_security_group_rule.py b/openstackclient/network/v2/default_security_group_rule.py index 0a16a11a7..0f9441488 100644 --- a/openstackclient/network/v2/default_security_group_rule.py +++ b/openstackclient/network/v2/default_security_group_rule.py @@ -300,7 +300,8 @@ def get_parser(self, prog_name): metavar='', type=network_utils.convert_to_lowercase, help=_( - "List rules by the IP protocol (ah, dhcp, egp, esp, gre, " + "List only default rules with the specified IP protocol " + "(ah, dhcp, egp, esp, gre, " "icmp, igmp, ipv6-encap, ipv6-frag, ipv6-icmp, " "ipv6-nonxt, ipv6-opts, ipv6-route, ospf, pgm, rsvp, " "sctp, tcp, udp, udplite, vrrp and integer " @@ -319,7 +320,7 @@ def get_parser(self, prog_name): '--ingress', action='store_true', help=_( - "List default rules which will be applied to incoming " + "List only default rules which will be applied to incoming " "network traffic" ), ) @@ -327,7 +328,7 @@ def get_parser(self, prog_name): '--egress', action='store_true', help=_( - "List default rules which will be applied to outgoing " + "List only default rules which will be applied to outgoing " "network traffic" ), ) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 0b2f7fa98..76b91a0ec 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -247,7 +247,7 @@ def update_parser_network(self, parser): action='append', help=self.enhance_help_neutron( _( - "List floating IP(s) according to given network " + "List only floating IP(s) with the specified network " "(name or ID) " "(repeat option to fiter on multiple networks)" ) @@ -260,7 +260,8 @@ def update_parser_network(self, parser): action='append', help=self.enhance_help_neutron( _( - "List floating IP(s) according to given port (name or ID) " + "List only floating IP(s) with the specified port " + "(name or ID) " "(repeat option to fiter on multiple ports)" ) ), @@ -269,14 +270,20 @@ def update_parser_network(self, parser): '--fixed-ip-address', metavar='', help=self.enhance_help_neutron( - _("List floating IP(s) according to given fixed IP address") + _( + "List only floating IP(s) with the specified fixed IP " + "address" + ) ), ) parser.add_argument( '--floating-ip-address', metavar='', help=self.enhance_help_neutron( - _("List floating IP(s) according to given floating IP address") + _( + "List only floating IP(s) with the specified floating IP " + "address" + ) ), ) parser.add_argument( @@ -285,8 +292,8 @@ def update_parser_network(self, parser): choices=['ACTIVE', 'DOWN'], help=self.enhance_help_neutron( _( - "List floating IP(s) according to given status ('ACTIVE', " - "'DOWN')" + "List only floating IP(s) with the specified status " + "('ACTIVE', 'DOWN')" ) ), ) @@ -295,8 +302,8 @@ def update_parser_network(self, parser): metavar='', help=self.enhance_help_neutron( _( - "List floating IP(s) according to given project " - "(name or ID) " + "List only floating IP(s) with the specified project " + "(name or ID)" ) ), ) @@ -308,7 +315,7 @@ def update_parser_network(self, parser): action='append', help=self.enhance_help_neutron( _( - "List floating IP(s) according to given router " + "List only floating IP(s) with the specified router " "(name or ID) " "(repeat option to fiter on multiple routers)" ) diff --git a/openstackclient/network/v2/floating_ip_port_forwarding.py b/openstackclient/network/v2/floating_ip_port_forwarding.py index 3810deddf..99546a1d5 100644 --- a/openstackclient/network/v2/floating_ip_port_forwarding.py +++ b/openstackclient/network/v2/floating_ip_port_forwarding.py @@ -265,8 +265,8 @@ def get_parser(self, prog_name): '--port', metavar='', help=_( - "Filter the list result by the ID or name of " - "the internal network port" + "List only floating IP port forwardings with the " + "specified internal network port (name or ID)" ), ) parser.add_argument( @@ -274,14 +274,17 @@ def get_parser(self, prog_name): metavar='', dest='external_protocol_port', help=_( - "Filter the list result by the " - "protocol port number of the floating IP" + "List only floating IP port forwardings with the " + "specified external protocol port number" ), ) parser.add_argument( '--protocol', metavar='', - help=_("Filter the list result by the port protocol"), + help=_( + "List only floating IP port forwardings with the " + "specified protocol number" + ), ) return parser diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index 51ea01199..e36b89771 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -47,14 +47,17 @@ def get_parser(self, prog_name): metavar='', dest='ip_version', help=_( - "List IP availability of given IP version " - "networks (default is 4)" + "List only IP availability with the specified IP version " + "networks (4 or 6, default is 4)" ), ) parser.add_argument( '--project', metavar='', - help=_("List IP availability of given project (name or ID)"), + help=_( + "List only IP availability with the specified project " + "(name or ID)" + ), ) identity_common.add_project_domain_option_to_parser(parser) return parser diff --git a/openstackclient/network/v2/l3_conntrack_helper.py b/openstackclient/network/v2/l3_conntrack_helper.py index 011db39e0..982021ec6 100644 --- a/openstackclient/network/v2/l3_conntrack_helper.py +++ b/openstackclient/network/v2/l3_conntrack_helper.py @@ -150,19 +150,26 @@ def get_parser(self, prog_name): parser.add_argument( '--helper', metavar='', - help=_('The netfilter conntrack helper module'), + help=_( + 'List only helpers using the specified netfilter conntrack ' + 'helper module' + ), ) parser.add_argument( '--protocol', metavar='', help=_( - 'The network protocol for the netfilter conntrack target rule' + 'List only helpers with the specified network protocol for ' + 'the netfilter conntrack target rule' ), ) parser.add_argument( '--port', metavar='', - help=_('The network port for the netfilter conntrack target rule'), + help=_( + 'List only helpers with the specified network port for ' + 'the netfilter conntrack target rule (name or ID)' + ), ) return parser diff --git a/openstackclient/network/v2/local_ip.py b/openstackclient/network/v2/local_ip.py index 7fd58818c..a2937937e 100644 --- a/openstackclient/network/v2/local_ip.py +++ b/openstackclient/network/v2/local_ip.py @@ -198,32 +198,38 @@ def get_parser(self, prog_name): parser.add_argument( '--name', metavar='', - help=_("List only Local IPs of given name in output"), + help=_("List only local IP(s) with the specified name"), ) parser.add_argument( '--project', metavar="", - help=_("List Local IPs according to their project (name or ID)"), + help=_( + "List only local IP(s) with the specified project (name or ID)" + ), ) parser.add_argument( '--network', metavar='', - help=_("List Local IP(s) according to given network (name or ID)"), + help=_( + "List only local IP(s) with the specified network (name or ID)" + ), ) parser.add_argument( '--local-port', metavar='', - help=_("List Local IP(s) according to given port (name or ID)"), + help=_( + "List only local IP(s) with the specified port (name or ID)" + ), ) parser.add_argument( '--local-ip-address', metavar='', - help=_("List Local IP(s) according to given Local IP Address"), + help=_("List only local IP(s) with the specified IP address"), ) parser.add_argument( '--ip-mode', metavar='', - help=_("List Local IP(s) according to given IP mode"), + help=_("List only local IP(s) with the specified IP mode"), ) identity_common.add_project_domain_option_to_parser(parser) diff --git a/openstackclient/network/v2/local_ip_association.py b/openstackclient/network/v2/local_ip_association.py index c6b59184b..814bbdb0b 100644 --- a/openstackclient/network/v2/local_ip_association.py +++ b/openstackclient/network/v2/local_ip_association.py @@ -150,18 +150,21 @@ def get_parser(self, prog_name): '--fixed-port', metavar='', help=_( - "Filter the list result by the ID or name of the fixed port" + "List only local IP assocations with the specified fixed IP " + "port (name or ID)" ), ) parser.add_argument( '--fixed-ip', metavar='', - help=_("Filter the list result by fixed ip"), + help=_( + "List only local IP associations with the specified fixed IP" + ), ) parser.add_argument( '--host', metavar='', - help=_("Filter the list result by given host"), + help=_("List only local IP associations with the specified host"), ) identity_common.add_project_domain_option_to_parser(parser) diff --git a/openstackclient/network/v2/ndp_proxy.py b/openstackclient/network/v2/ndp_proxy.py index fe97cd078..95e39ce15 100644 --- a/openstackclient/network/v2/ndp_proxy.py +++ b/openstackclient/network/v2/ndp_proxy.py @@ -143,30 +143,37 @@ def get_parser(self, prog_name): '--router', metavar='', help=_( - "List only NDP proxies belonging to this router (name or ID)" + "List only NDP proxies associated with the specifed router " + "(name or ID)" ), ) parser.add_argument( '--port', metavar='', help=_( - "List only NDP proxies associated to this port (name or ID)" + "List only NDP proxies associated with the specified port " + "(name or ID)" ), ) parser.add_argument( '--ip-address', metavar='', - help=_("List only NDP proxies associated to this IPv6 address"), + help=_( + "List only NDP proxies associated with the specified " + "IPv6 address" + ), ) parser.add_argument( '--project', metavar='', - help=_("List only NDP proxies of given project (name or ID)"), + help=_( + "List only NDP proxies with the specified project (name or ID)" + ), ) parser.add_argument( '--name', metavar='', - help=_("List only NDP proxies of given name"), + help=_("List only NDP proxies with the specified name"), ) identity_common.add_project_domain_option_to_parser(parser) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 33f7e04e4..7aefe50c3 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -469,12 +469,12 @@ def update_parser_network(self, parser): router_ext_group.add_argument( '--external', action='store_true', - help=self.enhance_help_neutron(_("List external networks")), + help=self.enhance_help_neutron(_("List only external networks")), ) router_ext_group.add_argument( '--internal', action='store_true', - help=self.enhance_help_neutron(_("List internal networks")), + help=self.enhance_help_neutron(_("List only internal networks")), ) parser.add_argument( '--long', @@ -487,24 +487,26 @@ def update_parser_network(self, parser): '--name', metavar='', help=self.enhance_help_neutron( - _("List networks according to their name") + _("List only networks with the specified name") ), ) admin_state_group = parser.add_mutually_exclusive_group() admin_state_group.add_argument( '--enable', action='store_true', - help=self.enhance_help_neutron(_("List enabled networks")), + help=self.enhance_help_neutron(_("List only enabled networks")), ) admin_state_group.add_argument( '--disable', action='store_true', - help=self.enhance_help_neutron(_("List disabled networks")), + help=self.enhance_help_neutron(_("List only disabled networks")), ) parser.add_argument( '--project', metavar='', - help=_("List networks according to their project (name or ID)"), + help=_( + "List only networks with the specified project (name or ID)" + ), ) identity_common.add_project_domain_option_to_parser( parser, enhance_help=self.enhance_help_neutron @@ -514,14 +516,14 @@ def update_parser_network(self, parser): '--share', action='store_true', help=self.enhance_help_neutron( - _("List networks shared between projects") + _("List only networks shared between projects") ), ) shared_group.add_argument( '--no-share', action='store_true', help=self.enhance_help_neutron( - _("List networks not shared between projects") + _("List only networks not shared between projects") ), ) parser.add_argument( @@ -530,7 +532,7 @@ def update_parser_network(self, parser): choices=['ACTIVE', 'BUILD', 'DOWN', 'ERROR'], help=self.enhance_help_neutron( _( - "List networks according to their status " + "List only networks with the specified status " "('ACTIVE', 'BUILD', 'DOWN', 'ERROR')" ) ), @@ -541,7 +543,8 @@ def update_parser_network(self, parser): choices=['flat', 'geneve', 'gre', 'local', 'vlan', 'vxlan'], help=self.enhance_help_neutron( _( - "List networks according to their physical mechanisms. " + "List only networks with the specified physical " + "mechanisms. " "The supported options are: flat, geneve, gre, local, " "vlan and vxlan." ) @@ -552,7 +555,10 @@ def update_parser_network(self, parser): metavar='', dest='physical_network', help=self.enhance_help_neutron( - _("List networks according to name of the physical network") + _( + "List only networks with the specified physical network " + "name" + ) ), ) parser.add_argument( @@ -561,8 +567,9 @@ def update_parser_network(self, parser): dest='segmentation_id', help=self.enhance_help_neutron( _( - "List networks according to VLAN ID for VLAN networks or " - "Tunnel ID for GENEVE/GRE/VXLAN networks" + "List only networks with the specified provider segment " + "ID (VLAN ID for VLAN networks or " + "Tunnel ID for GENEVE/GRE/VXLAN networks)" ) ), ) @@ -571,7 +578,7 @@ def update_parser_network(self, parser): metavar='', dest='agent_id', help=self.enhance_help_neutron( - _('List networks hosted by agent (ID only)') + _('List only networks hosted the specified agent (ID only)') ), ) _tag.add_tag_filtering_option_to_parser( diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index 35b0fab66..cc4de2b42 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -205,12 +205,12 @@ def get_parser(self, prog_name): agent_type_group.add_argument( '--network', metavar='', - help=_('List agents hosting a network (name or ID)'), + help=_('List agents hosting the specified network (name or ID)'), ) agent_type_group.add_argument( '--router', metavar='', - help=_('List agents hosting this router (name or ID)'), + help=_('List agents hosting the specified router (name or ID)'), ) parser.add_argument( '--long', diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index 1a96f939e..abdd044af 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -190,7 +190,8 @@ def get_parser(self, prog_name): '--project', metavar='', help=_( - "List QoS policies according to their project (name or ID)" + "List only QoS policies with the specified project " + "(name or ID)" ), ) identity_common.add_project_domain_option_to_parser(parser) @@ -198,12 +199,12 @@ def get_parser(self, prog_name): shared_group.add_argument( '--share', action='store_true', - help=_("List QoS policies shared between projects"), + help=_("List only QoS policies shared between projects"), ) shared_group.add_argument( '--no-share', action='store_true', - help=_("List QoS policies not shared between projects"), + help=_("List only QoS policies not shared between projects"), ) return parser diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index 0df74375d..d266f352d 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -234,8 +234,8 @@ def get_parser(self, prog_name): 'network', ], help=_( - 'List network RBAC policies according to ' - 'given object type ("address_group", "address_scope", ' + 'List only network RBAC policies with the specified ' + 'object type ("address_group", "address_scope", ' '"security_group", "subnetpool", "qos_policy" or ' '"network")' ), @@ -245,14 +245,17 @@ def get_parser(self, prog_name): metavar='', choices=['access_as_external', 'access_as_shared'], help=_( - 'List network RBAC policies according to given ' + 'List only network RBAC policies with the specified ' 'action ("access_as_external" or "access_as_shared")' ), ) parser.add_argument( '--target-project', metavar='', - help=_('List network RBAC policies for a specific target project'), + help=_( + 'List only network RBAC policies with the specified ' + 'target project (name or ID)' + ), ) parser.add_argument( '--long', diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index 5e40108ca..76a967ecc 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -160,7 +160,7 @@ def get_parser(self, prog_name): '--network', metavar='', help=_( - 'List network segments that belong to this ' + 'List only network segments associated with the specified ' 'network (name or ID)' ), ) diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index e2716f13f..c9b9ccc23 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -315,25 +315,32 @@ def get_parser(self, prog_name): used_group.add_argument( '--used', action='store_true', - help=_('List network segment ranges that have segments in use'), + help=_( + 'List only network segment ranges that have segments in use' + ), ) used_group.add_argument( '--unused', action='store_true', help=_( - 'List network segment ranges that have segments not in use' + 'List only network segment ranges that have segments ' + 'not in use' ), ) available_group = parser.add_mutually_exclusive_group() available_group.add_argument( '--available', action='store_true', - help=_('List network segment ranges that have available segments'), + help=_( + 'List only network segment ranges that have available segments' + ), ) available_group.add_argument( '--unavailable', action='store_true', - help=_('List network segment ranges without available segments'), + help=_( + 'List only network segment ranges without available segments' + ), ) return parser diff --git a/openstackclient/network/v2/network_trunk.py b/openstackclient/network/v2/network_trunk.py index 53681b774..cd0d47ffd 100644 --- a/openstackclient/network/v2/network_trunk.py +++ b/openstackclient/network/v2/network_trunk.py @@ -285,7 +285,7 @@ def get_parser(self, prog_name): '--trunk', required=True, metavar="", - help=_("List subports belonging to this trunk (name or ID)"), + help=_("List only subports belonging to this trunk (name or ID)"), ) return parser diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index ff0e42a87..c28131440 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -810,7 +810,7 @@ def get_parser(self, prog_name): parser.add_argument( '--mac-address', metavar='', - help=_("List only ports with this MAC address"), + help=_("List only ports with the specified MAC address"), ) parser.add_argument( '--long', @@ -821,12 +821,12 @@ def get_parser(self, prog_name): parser.add_argument( '--project', metavar='', - help=_("List ports according to their project (name or ID)"), + help=_("List only ports with the specified project (name or ID)"), ) parser.add_argument( '--name', metavar='', - help=_("List ports according to their name"), + help=_("List only ports with the specified name"), ) parser.add_argument( '--security-group', @@ -844,7 +844,7 @@ def get_parser(self, prog_name): metavar='', choices=('ACTIVE', 'BUILD', 'DOWN', 'ERROR'), help=_( - "List ports according to their status " + "List only ports with the specified status " "('ACTIVE', 'BUILD', 'DOWN', 'ERROR')" ), ) @@ -861,7 +861,7 @@ def get_parser(self, prog_name): "Desired IP and/or subnet for filtering ports " "(name or ID): subnet=,ip-address=," "ip-substring= " - "(repeat option to set multiple fixed IP addresses)" + "(repeat option to filter multiple fixed IP addresses)" ), ) _tag.add_tag_filtering_option_to_parser(parser, _('ports')) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 29dfcce89..1ea85331c 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -726,13 +726,17 @@ def get_parser(self, prog_name): parser.add_argument( '--project', metavar='', - help=_("List routers according to their project (name or ID)"), + help=_( + "List only routers with the specified project (name or ID)" + ), ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--agent', metavar='', - help=_("List routers hosted by an agent (ID only)"), + help=_( + "List only routers hosted by the specified agent (ID only)" + ), ) _tag.add_tag_filtering_option_to_parser(parser, _('routers')) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 6fb98c6f6..c4b56a216 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -246,7 +246,10 @@ def update_parser_network(self, parser): '--project', metavar='', help=self.enhance_help_neutron( - _("List security groups according to the project (name or ID)") + _( + "List only security groups with the specified project " + "(name or ID)" + ) ), ) identity_common.add_project_domain_option_to_parser( @@ -259,14 +262,14 @@ def update_parser_network(self, parser): action='store_true', dest='shared', default=None, - help=_("List security groups shared between projects"), + help=_("List only security groups shared between projects"), ) shared_group.add_argument( '--no-share', action='store_false', dest='shared', default=None, - help=_("List security groups not shared between projects"), + help=_("List only security groups not shared between projects"), ) _tag.add_tag_filtering_option_to_parser( diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 6cb0f7fa3..f6baac3cb 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -387,7 +387,8 @@ def update_parser_network(self, parser): type=network_utils.convert_to_lowercase, help=self.enhance_help_neutron( _( - "List rules by the IP protocol (ah, dhcp, egp, esp, gre, " + "List only rules with the specified IP protocol " + "(ah, dhcp, egp, esp, gre, " "icmp, igmp, ipv6-encap, ipv6-frag, ipv6-icmp, " "ipv6-nonxt, ipv6-opts, ipv6-route, ospf, pgm, rsvp, " "sctp, tcp, udp, udplite, vrrp and integer " @@ -401,7 +402,10 @@ def update_parser_network(self, parser): metavar='', type=network_utils.convert_to_lowercase, help=self.enhance_help_neutron( - _("List rules by the Ethertype (IPv4 or IPv6)") + _( + "List only rules with the specified Ethertype " + "(IPv4 or IPv6)" + ) ), ) direction_group = parser.add_mutually_exclusive_group() @@ -409,14 +413,14 @@ def update_parser_network(self, parser): '--ingress', action='store_true', help=self.enhance_help_neutron( - _("List rules applied to incoming network traffic") + _("List only rules applied to incoming network traffic") ), ) direction_group.add_argument( '--egress', action='store_true', help=self.enhance_help_neutron( - _("List rules applied to outgoing network traffic") + _("List only rules applied to outgoing network traffic") ), ) parser.add_argument( @@ -430,7 +434,9 @@ def update_parser_network(self, parser): parser.add_argument( '--project', metavar='', - help=self.enhance_help_neutron(_("Owner's project (name or ID)")), + help=self.enhance_help_neutron( + _("List only rules with the specified project (name or ID)") + ), ) identity_common.add_project_domain_option_to_parser( parser, enhance_help=self.enhance_help_neutron diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 30196bf86..7dc1df087 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -493,7 +493,7 @@ def get_parser(self, prog_name): metavar='', dest='ip_version', help=_( - "List only subnets of given IP version in output. " + "List only subnets with the specified IP version. " "Allowed values for IP version are 4 and 6." ), ) @@ -501,12 +501,12 @@ def get_parser(self, prog_name): dhcp_enable_group.add_argument( '--dhcp', action='store_true', - help=_("List subnets which have DHCP enabled"), + help=_("List only subnets which have DHCP enabled"), ) dhcp_enable_group.add_argument( '--no-dhcp', action='store_true', - help=_("List subnets which have DHCP disabled"), + help=_("List only subnets which have DHCP disabled"), ) parser.add_argument( '--service-type', @@ -514,7 +514,7 @@ def get_parser(self, prog_name): action='append', dest='service_types', help=_( - "List only subnets of a given service type in output, " + "List only subnets with the specified service type, " "for example, network:floatingip_agent_gateway. " "Must be a valid device owner value for a network port " "(repeat option to list multiple service types)." @@ -524,8 +524,7 @@ def get_parser(self, prog_name): '--project', metavar='', help=_( - "List only subnets which belong to a given project " - "in output (name or ID)" + "List only subnets with the specified project (name or ID)" ), ) identity_common.add_project_domain_option_to_parser(parser) @@ -533,26 +532,26 @@ def get_parser(self, prog_name): '--network', metavar='', help=_( - "List only subnets which belong to a given network " - "in output (name or ID)" + "List only subnets which belong to the specified network " + "(name or ID)" ), ) parser.add_argument( '--gateway', metavar='', - help=_("List only subnets of given gateway IP in output"), + help=_("List only subnets with the specified gateway IP"), ) parser.add_argument( '--name', metavar='', - help=_("List only subnets of given name in output"), + help=_("List only subnets with the specified name"), ) parser.add_argument( '--subnet-range', metavar='', help=_( - "List only subnets of given subnet range " - "(in CIDR notation) in output. " + "List only subnets with the specified subnet range " + "(in CIDR notation). " "For example, --subnet-range 10.10.0.0/16" ), ) @@ -560,8 +559,8 @@ def get_parser(self, prog_name): '--subnet-pool', metavar='', help=_( - "List only subnets which belong to a given subnet pool " - "in output (name or ID)" + "List only subnets which belong to the specified subnet pool " + "(name or ID)" ), ) _tag.add_tag_filtering_option_to_parser(parser, _('subnets')) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 2917c4026..2f900a61d 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -278,26 +278,27 @@ def get_parser(self, prog_name): shared_group.add_argument( '--share', action='store_true', - help=_("List subnet pools shared between projects"), + help=_("List only subnet pools shared between projects"), ) shared_group.add_argument( '--no-share', action='store_true', - help=_("List subnet pools not shared between projects"), + help=_("List only subnet pools not shared between projects"), ) default_group = parser.add_mutually_exclusive_group() default_group.add_argument( '--default', action='store_true', help=_( - "List subnet pools used as the default external subnet pool" + "List only subnet pools used as the default external " + "subnet pool" ), ) default_group.add_argument( '--no-default', action='store_true', help=_( - "List subnet pools not used as the default external " + "List only subnet pools not used as the default external " "subnet pool" ), ) @@ -305,21 +306,22 @@ def get_parser(self, prog_name): '--project', metavar='', help=_( - "List subnet pools according to their project (name or ID)" + "List only subnet pools with the specified project " + "(name or ID)" ), ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--name', metavar='', - help=_("List only subnet pools of given name in output"), + help=_("List only subnet pools with the specified name"), ) parser.add_argument( '--address-scope', metavar='', help=_( - "List only subnet pools of given address scope " - "in output (name or ID)" + "List only subnet pools with the specified address scope " + "(name or ID)" ), ) _tag.add_tag_filtering_option_to_parser(parser, _('subnet pools')) From 04118056085042ca3359d7cf0600d43c4cacd87f Mon Sep 17 00:00:00 2001 From: 0weng Date: Tue, 18 Nov 2025 09:36:38 -0800 Subject: [PATCH 45/67] Change metavar name for `registered limit delete` Change registered limit argument to plural and remove `id` suffix for `registered limit delete` command. Also, note that service can be specified by name or ID in help description. Change-Id: I16950a5ac1a197761592304dcb71dcb09d608d78 Signed-off-by: 0weng --- .../identity/v3/registered_limit.py | 25 +++++++++++-------- .../unit/identity/v3/test_registered_limit.py | 4 +-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/openstackclient/identity/v3/registered_limit.py b/openstackclient/identity/v3/registered_limit.py index 34ce2101f..41b8cdac0 100644 --- a/openstackclient/identity/v3/registered_limit.py +++ b/openstackclient/identity/v3/registered_limit.py @@ -44,7 +44,10 @@ def get_parser(self, prog_name): '--service', metavar='', required=True, - help=_('Service responsible for the resource to limit (required)'), + help=_( + 'Service responsible for the resource to limit (required) ' + '(name or ID)' + ), ) parser.add_argument( '--default-limit', @@ -106,10 +109,10 @@ class DeleteRegisteredLimit(command.Command): def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( - 'registered_limit_id', - metavar='', + 'registered_limits', + metavar='', nargs="+", - help=_('Registered limit to delete (ID)'), + help=_('Registered limit(s) to delete (ID)'), ) return parser @@ -117,7 +120,7 @@ def take_action(self, parsed_args): identity_client = self.app.client_manager.identity errors = 0 - for registered_limit_id in parsed_args.registered_limit_id: + for registered_limit_id in parsed_args.registered_limits: try: identity_client.registered_limits.delete(registered_limit_id) except Exception as e: @@ -134,7 +137,7 @@ def take_action(self, parsed_args): ) if errors > 0: - total = len(parsed_args.registered_limit_id) + total = len(parsed_args.registered_limits) msg = _( "%(errors)s of %(total)s registered limits failed to delete." ) % {'errors': errors, 'total': total} @@ -149,7 +152,9 @@ def get_parser(self, prog_name): parser.add_argument( '--service', metavar='', - help=_('Service responsible for the resource to limit'), + help=_( + 'Service responsible for the resource to limit (name or ID)' + ), ) parser.add_argument( '--resource-name', @@ -228,9 +233,9 @@ def get_parser(self, prog_name): '--service', metavar='', help=_( - 'Service to be updated responsible for the resource to ' - 'limit. Either --service, --resource-name or --region must ' - 'be different than existing value otherwise it will be ' + 'Service to be updated responsible for the resource to limit ' + '(name or ID). Either --service, --resource-name or --region ' + 'must be different than existing value otherwise it will be ' 'duplicate entry' ), ) diff --git a/openstackclient/tests/unit/identity/v3/test_registered_limit.py b/openstackclient/tests/unit/identity/v3/test_registered_limit.py index 792096cde..a120714ec 100644 --- a/openstackclient/tests/unit/identity/v3/test_registered_limit.py +++ b/openstackclient/tests/unit/identity/v3/test_registered_limit.py @@ -168,7 +168,7 @@ def test_registered_limit_delete(self): arglist = [identity_fakes.registered_limit_id] verifylist = [ - ('registered_limit_id', [identity_fakes.registered_limit_id]) + ('registered_limits', [identity_fakes.registered_limit_id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -184,7 +184,7 @@ def test_registered_limit_delete_with_exception(self): self.registered_limit_mock.delete.side_effect = return_value arglist = ['fake-registered-limit-id'] - verifylist = [('registered_limit_id', ['fake-registered-limit-id'])] + verifylist = [('registered_limits', ['fake-registered-limit-id'])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) try: From 6366763ce4b73ad5b13e3cca18319ae1d8dfb330 Mon Sep 17 00:00:00 2001 From: asdasd7183 Date: Wed, 20 Aug 2025 21:26:38 +0900 Subject: [PATCH 46/67] Add functional test for volume snapshot This patch adds functional test support unset and show commands from volume snapshot and refactors existing test methods by combining them into a single method. Change-Id: I567bdfad6ce8ee6098d6e4c270bc200ff53ae4f7 Signed-off-by: asdasd7183 --- .../volume/v3/test_volume_snapshot.py | 178 +++++------------- 1 file changed, 46 insertions(+), 132 deletions(-) diff --git a/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py b/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py index 2c9b72ba1..b84bb0368 100644 --- a/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py +++ b/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py @@ -23,7 +23,7 @@ class VolumeSnapshotTests(common.BaseVolumeTests): @classmethod def setUpClass(cls): super().setUpClass() - # create a volume for all tests to create snapshot + # create a test volume used by all snapshot tests cmd_output = cls.openstack( 'volume create ' + '--size 1 ' + cls.VOLLY, parse_output=True, @@ -40,147 +40,57 @@ def tearDownClass(cls): finally: super().tearDownClass() - def test_volume_snapshot_delete(self): - """Test create, delete multiple""" - name1 = uuid.uuid4().hex - cmd_output = self.openstack( - 'volume snapshot create ' + name1 + ' --volume ' + self.VOLLY, - parse_output=True, - ) - self.assertEqual( - name1, - cmd_output["name"], - ) + def test_volume_snapshot(self): + # create volume snapshot + name = uuid.uuid4().hex - name2 = uuid.uuid4().hex cmd_output = self.openstack( - 'volume snapshot create ' + name2 + ' --volume ' + self.VOLLY, + 'volume snapshot create ' + + '--volume ' + + self.VOLLY + + ' --description aaaa ' + + '--property Alpha=a ' + + name, parse_output=True, ) - self.assertEqual( - name2, - cmd_output["name"], - ) - - self.wait_for_status('volume snapshot', name1, 'available') - self.wait_for_status('volume snapshot', name2, 'available') + snap_id = cmd_output['id'] - del_output = self.openstack( - 'volume snapshot delete ' + name1 + ' ' + name2 + self.addCleanup(self.wait_for_delete, 'volume snapshot', snap_id) + # delete volume snapshot + self.addCleanup( + self.openstack, + 'volume snapshot delete ' + snap_id, ) - self.assertOutput('', del_output) - self.wait_for_delete('volume snapshot', name1) - self.wait_for_delete('volume snapshot', name2) + self.wait_for_status('volume snapshot', snap_id, 'available') - def test_volume_snapshot_list(self): - """Test create, list filter""" - name1 = uuid.uuid4().hex - cmd_output = self.openstack( - 'volume snapshot create ' + name1 + ' --volume ' + self.VOLLY, + # show volume snapshot + snapshot_info = self.openstack( + 'volume snapshot show ' + name, parse_output=True, ) - self.addCleanup(self.wait_for_delete, 'volume snapshot', name1) - self.addCleanup(self.openstack, 'volume snapshot delete ' + name1) - self.assertEqual( - name1, - cmd_output["name"], - ) - self.assertEqual( - self.VOLUME_ID, - cmd_output["volume_id"], - ) - self.assertEqual( - 1, - cmd_output["size"], - ) - self.wait_for_status('volume snapshot', name1, 'available') - name2 = uuid.uuid4().hex - cmd_output = self.openstack( - 'volume snapshot create ' + name2 + ' --volume ' + self.VOLLY, - parse_output=True, - ) - self.addCleanup(self.wait_for_delete, 'volume snapshot', name2) - self.addCleanup(self.openstack, 'volume snapshot delete ' + name2) - self.assertEqual( - name2, - cmd_output["name"], - ) - self.assertEqual( - self.VOLUME_ID, - cmd_output["volume_id"], - ) - self.assertEqual( - 1, - cmd_output["size"], - ) - self.wait_for_status('volume snapshot', name2, 'available') - raw_output = self.openstack( - 'volume snapshot set ' + '--state error ' + name2 - ) - self.assertOutput('', raw_output) + self.assertEqual(name, snapshot_info['name']) + self.assertEqual('aaaa', snapshot_info["description"]) + self.assertEqual({'Alpha': 'a'}, snapshot_info["properties"]) - # Test list --long, --status + # list volume snapshot --name cmd_output = self.openstack( - 'volume snapshot list ' + '--long ' + '--status error', + 'volume snapshot list --name ' + name, parse_output=True, ) - names = [x["Name"] for x in cmd_output] - self.assertNotIn(name1, names) - self.assertIn(name2, names) + names = [x['Name'] for x in cmd_output] + self.assertIn(name, names) - # Test list --volume + # list volume snapshot --volume cmd_output = self.openstack( 'volume snapshot list ' + '--volume ' + self.VOLLY, parse_output=True, ) names = [x["Name"] for x in cmd_output] - self.assertIn(name1, names) - self.assertIn(name2, names) + self.assertIn(name, names) - # Test list --name - cmd_output = self.openstack( - 'volume snapshot list ' + '--name ' + name1, - parse_output=True, - ) - names = [x["Name"] for x in cmd_output] - self.assertIn(name1, names) - self.assertNotIn(name2, names) - - def test_volume_snapshot_set(self): - """Test create, set, unset, show, delete volume snapshot""" - name = uuid.uuid4().hex + # set volume snapshot new_name = name + "_" - cmd_output = self.openstack( - 'volume snapshot create ' - + '--volume ' - + self.VOLLY - + ' --description aaaa ' - + '--property Alpha=a ' - + name, - parse_output=True, - ) - self.addCleanup(self.wait_for_delete, 'volume snapshot', new_name) - self.addCleanup(self.openstack, 'volume snapshot delete ' + new_name) - self.assertEqual( - name, - cmd_output["name"], - ) - self.assertEqual( - 1, - cmd_output["size"], - ) - self.assertEqual( - 'aaaa', - cmd_output["description"], - ) - self.assertEqual( - {'Alpha': 'a'}, - cmd_output["properties"], - ) - self.wait_for_status('volume snapshot', name, 'available') - - # Test volume snapshot set raw_output = self.openstack( 'volume snapshot set ' + '--name ' @@ -188,11 +98,10 @@ def test_volume_snapshot_set(self): + ' --description bbbb ' + '--property Alpha=c ' + '--property Beta=b ' - + name, + + snap_id, ) self.assertOutput('', raw_output) - # Show snapshot set result cmd_output = self.openstack( 'volume snapshot show ' + new_name, parse_output=True, @@ -201,10 +110,6 @@ def test_volume_snapshot_set(self): new_name, cmd_output["name"], ) - self.assertEqual( - 1, - cmd_output["size"], - ) self.assertEqual( 'bbbb', cmd_output["description"], @@ -214,7 +119,7 @@ def test_volume_snapshot_set(self): cmd_output["properties"], ) - # Test volume snapshot unset + # unset volume snapshot raw_output = self.openstack( 'volume snapshot unset ' + '--property Alpha ' + new_name, ) @@ -229,16 +134,25 @@ def test_volume_snapshot_set(self): cmd_output["properties"], ) - # Test volume snapshot set --no-property + # set volume snapshot --no-property, --state error raw_output = self.openstack( - 'volume snapshot set ' + '--no-property ' + new_name, + 'volume snapshot set ' + + '--no-property ' + + '--state error ' + + new_name, ) self.assertOutput('', raw_output) + cmd_output = self.openstack( 'volume snapshot show ' + new_name, parse_output=True, ) - self.assertNotIn( - {'Beta': 'b'}, - cmd_output["properties"], + self.assertEqual({}, cmd_output["properties"]) + + # list volume snapshot --long --status + cmd_output = self.openstack( + 'volume snapshot list ' + '--long ' + '--status error', + parse_output=True, ) + names = [x["Name"] for x in cmd_output] + self.assertIn(new_name, names) From 4132ca1818e894c9e5ca9482b4a82b8baaed4377 Mon Sep 17 00:00:00 2001 From: Luan Utimura Date: Thu, 28 Aug 2025 16:11:05 -0300 Subject: [PATCH 47/67] volume: Add missing backup_id field in tests This change also reverts commit: * 5f1ffe742cd8afb2c93927ff606d88a90fd6073e Depends-On: https://review.opendev.org/c/openstack/openstacksdk/+/958801 Change-Id: Icac78179bc324e6fbe762f8095f2cba490ef6aea Signed-off-by: Luan Utimura --- openstackclient/tests/unit/volume/v3/test_volume.py | 4 ++++ openstackclient/volume/v3/volume.py | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openstackclient/tests/unit/volume/v3/test_volume.py b/openstackclient/tests/unit/volume/v3/test_volume.py index 5fb35a067..33dcfe5a4 100644 --- a/openstackclient/tests/unit/volume/v3/test_volume.py +++ b/openstackclient/tests/unit/volume/v3/test_volume.py @@ -37,6 +37,7 @@ class TestVolumeCreate(volume_fakes.TestVolume): columns = ( 'attachments', 'availability_zone', + 'backup_id', 'bootable', 'cluster_name', 'consistencygroup_id', @@ -78,6 +79,7 @@ def setUp(self): self.datalist = ( self.volume.attachments, self.volume.availability_zone, + self.volume.backup_id, self.volume.is_bootable, self.volume.cluster_name, self.volume.consistency_group_id, @@ -2011,6 +2013,7 @@ def setUp(self): self.columns = ( 'attachments', 'availability_zone', + 'backup_id', 'bootable', 'cluster_name', 'consistencygroup_id', @@ -2045,6 +2048,7 @@ def setUp(self): self.data = ( self.volume.attachments, self.volume.availability_zone, + self.volume.backup_id, self.volume.is_bootable, self.volume.cluster_name, self.volume.consistency_group_id, diff --git a/openstackclient/volume/v3/volume.py b/openstackclient/volume/v3/volume.py index d43a77d7b..54dd5c754 100644 --- a/openstackclient/volume/v3/volume.py +++ b/openstackclient/volume/v3/volume.py @@ -107,8 +107,6 @@ def _format_volume(volume: _volume.Volume) -> dict[str, ty.Any]: 'os-volume-replication:extended_status', # unnecessary columns 'links', - # temporarily ignored columns - 'backup_id', } optional_columns = { # only present if part of a consistency group From fb6dad48db5a802be8d36bba6d7d34618bdaa12a Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Tue, 4 Nov 2025 17:33:26 +0000 Subject: [PATCH 48/67] Remove duplicate test utilities We cannot remove them fully, but we can remove a lot of them. Further cleanup is needed here to remove the references but that will be done once a version of osc_lib with fixes is included. Change-Id: Ifd200bd3d3e5c02c239a8ad0e6cee6d823e26544 Signed-off-by: Stephen Finucane --- .../tests/unit/common/test_module.py | 21 ++- openstackclient/tests/unit/fakes.py | 169 ++++-------------- .../tests/unit/identity/v3/fakes.py | 12 +- 3 files changed, 59 insertions(+), 143 deletions(-) diff --git a/openstackclient/tests/unit/common/test_module.py b/openstackclient/tests/unit/common/test_module.py index 476538a19..839620368 100644 --- a/openstackclient/tests/unit/common/test_module.py +++ b/openstackclient/tests/unit/common/test_module.py @@ -11,7 +11,6 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -# """Test module module""" @@ -19,10 +18,18 @@ from unittest import mock from openstackclient.common import module as osc_module -from openstackclient.tests.unit import fakes from openstackclient.tests.unit import utils +class FakeModule: + def __init__(self, name, version): + self.name = name + self.__version__ = version + # Workaround for openstacksdk case + self.version = mock.Mock() + self.version.__version__ = version + + # NOTE(dtroyer): module_1 must match the version list filter (not --all) # currently == '*client*' module_name_1 = 'fakeclient' @@ -45,11 +52,11 @@ MODULES = { 'sys': sys, - module_name_1: fakes.FakeModule(module_name_1, module_version_1), - module_name_2: fakes.FakeModule(module_name_2, module_version_2), - module_name_3: fakes.FakeModule(module_name_3, module_version_3), - module_name_4: fakes.FakeModule(module_name_4, module_version_4), - module_name_5: fakes.FakeModule(module_name_5, module_version_5), + module_name_1: FakeModule(module_name_1, module_version_1), + module_name_2: FakeModule(module_name_2, module_version_2), + module_name_3: FakeModule(module_name_3, module_version_3), + module_name_4: FakeModule(module_name_4, module_version_4), + module_name_5: FakeModule(module_name_5, module_version_5), } diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index 0d378ff8d..be9d7f218 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -12,13 +12,43 @@ # License for the specific language governing permissions and limitations # under the License. +# TODO(stephenfin): Remove the contents of this module in favour of the osc_lib +# version once our min version is bumped to 4.3.0 + import json -import sys from unittest import mock from keystoneauth1 import fixture +from osc_lib.tests.fakes import ( + FakeApp, + FakeClientManager as BaseFakeClientManager, + FakeLog, + FakeOptions, + FakeResource as BaseFakeResource, + FakeStdout, +) import requests +__all__ = [ + 'AUTH_TOKEN', + 'AUTH_URL', + 'INTERFACE', + 'PASSWORD', + 'PROJECT_NAME', + 'REGION_NAME', + 'TEST_RESPONSE_DICT', + 'TEST_RESPONSE_DICT_V3', + 'TEST_VERSIONS', + 'USERNAME', + 'VERSION', + 'FakeApp', + 'FakeClientManager', + 'FakeLog', + 'FakeOptions', + 'FakeResource', + 'FakeResponse', + 'FakeStdout', +] AUTH_TOKEN = "foobar" AUTH_URL = "http://0.0.0.0" @@ -47,79 +77,15 @@ TEST_VERSIONS = fixture.DiscoveryList(href=AUTH_URL) -class FakeStdout: - def __init__(self): - self.content = [] - - def write(self, text): - self.content.append(text) - - def make_string(self): - result = '' - for line in self.content: - result = result + line - return result - - -class FakeLog: - def __init__(self): - self.messages = {} - - def debug(self, msg): - self.messages['debug'] = msg - - def info(self, msg): - self.messages['info'] = msg - - def warning(self, msg): - self.messages['warning'] = msg - - def error(self, msg): - self.messages['error'] = msg - - def critical(self, msg): - self.messages['critical'] = msg - - -class FakeApp: - def __init__(self, _stdout, _log): - self.stdout = _stdout - self.client_manager = None - self.api_version = {} - self.stdin = sys.stdin - self.stdout = _stdout or sys.stdout - self.stderr = sys.stderr - self.log = _log - - -class FakeOptions: - def __init__(self, **kwargs): - self.os_beta_command = False - - -class FakeClient: - def __init__(self, **kwargs): - self.endpoint = kwargs['endpoint'] - self.token = kwargs['token'] - - -class FakeClientManager: +class FakeClientManager(BaseFakeClientManager): _api_version = { 'image': '2', } def __init__(self): - self.compute = None - self.identity = None - self.image = None - self.object_store = None - self.volume = None - self.network = None - self.sdk_connection = mock.Mock() + super().__init__() - self.session = None - self.auth_ref = None - self.auth_plugin_name = None + self.sdk_connection = mock.Mock() self.network_endpoint_enabled = True self.compute_endpoint_enabled = True @@ -158,64 +124,7 @@ def is_volume_endpoint_enabled(self, client=None): return self.volume_endpoint_enabled -class FakeModule: - def __init__(self, name, version): - self.name = name - self.__version__ = version - # Workaround for openstacksdk case - self.version = mock.Mock() - self.version.__version__ = version - - -class FakeResource: - def __init__(self, manager=None, info=None, loaded=False, methods=None): - """Set attributes and methods for a resource. - - :param manager: - The resource manager - :param Dictionary info: - A dictionary with all attributes - :param bool loaded: - True if the resource is loaded in memory - :param Dictionary methods: - A dictionary with all methods - """ - info = info or {} - methods = methods or {} - - self.__name__ = type(self).__name__ - self.manager = manager - self._info = info - self._add_details(info) - self._add_methods(methods) - self._loaded = loaded - - def _add_details(self, info): - for k, v in info.items(): - setattr(self, k, v) - - def _add_methods(self, methods): - """Fake methods with MagicMock objects. - - For each <@key, @value> pairs in methods, add an callable MagicMock - object named @key as an attribute, and set the mock's return_value to - @value. When users access the attribute with (), @value will be - returned, which looks like a function call. - """ - for name, ret in methods.items(): - method = mock.Mock(return_value=ret) - setattr(self, name, method) - - def __repr__(self): - reprkeys = sorted( - k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager' - ) - info = ", ".join(f"{k}={getattr(self, k)}" for k in reprkeys) - return f"<{self.__class__.__name__} {info}>" - - def keys(self): - return self._info.keys() - +class FakeResource(BaseFakeResource): def to_dict(self): return self._info @@ -247,11 +156,3 @@ def __init__( self._content = json.dumps(data) if not isinstance(self._content, bytes): self._content = self._content.encode() - - -class FakeModel(dict): - def __getattr__(self, key): - try: - return self[key] - except KeyError: - raise AttributeError(key) diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index ad5ceb284..c42ed4731 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -693,6 +693,14 @@ class TestIdentityv3( ): ... +class FakeModel(dict): + def __getattr__(self, key): + try: + return self[key] + except KeyError: + raise AttributeError(key) + + # We don't use FakeClientMixin since we want a different fake legacy client class TestFederatedIdentity(utils.TestCommand): def setUp(self): @@ -1075,7 +1083,7 @@ def create_one_endpoint_filter(attrs=None): # Overwrite default attributes if there are some attributes set endpoint_filter_info.update(attrs) - endpoint_filter = fakes.FakeModel(copy.deepcopy(endpoint_filter_info)) + endpoint_filter = FakeModel(copy.deepcopy(endpoint_filter_info)) return endpoint_filter @@ -1133,7 +1141,7 @@ def create_one_endpointgroup_filter(attrs=None): # Overwrite default attributes if there are some attributes set endpointgroup_filter_info.update(attrs) - endpointgroup_filter = fakes.FakeModel( + endpointgroup_filter = FakeModel( copy.deepcopy(endpointgroup_filter_info) ) From 97c2238df17e7e586f00a5fb06da4ec008ad834d Mon Sep 17 00:00:00 2001 From: Miro Tomaska Date: Wed, 8 Oct 2025 15:27:38 -0400 Subject: [PATCH 49/67] Moving tapas osc client code from neutronclient Proposal to move all stadium projects from neutronclient to openstackclient repo. Tap-as-a-service is the first example. The tapas osc client code was recently moved to neutronclient see https://review.opendev.org/c/openstack/tap-as-a-service/+/960849 but proposal is to make openstackclient its final destination. This change also includes automatic lint fixes required in this repo. Change-Id: Ied47f40c6947600d40bf675ec06f0bf88fd15f1f Signed-off-by: Miro Tomaska --- openstackclient/network/v2/taas/__init__.py | 0 openstackclient/network/v2/taas/tap_flow.py | 245 +++++++++++++++ openstackclient/network/v2/taas/tap_mirror.py | 238 +++++++++++++++ .../network/v2/taas/tap_service.py | 212 +++++++++++++ .../tests/unit/network/v2/taas/__init__.py | 0 .../tests/unit/network/v2/taas/fakes.py | 118 ++++++++ .../unit/network/v2/taas/test_osc_tap_flow.py | 283 ++++++++++++++++++ .../network/v2/taas/test_osc_tap_mirror.py | 283 ++++++++++++++++++ .../network/v2/taas/test_osc_tap_service.py | 266 ++++++++++++++++ pyproject.toml | 17 ++ 10 files changed, 1662 insertions(+) create mode 100644 openstackclient/network/v2/taas/__init__.py create mode 100644 openstackclient/network/v2/taas/tap_flow.py create mode 100644 openstackclient/network/v2/taas/tap_mirror.py create mode 100644 openstackclient/network/v2/taas/tap_service.py create mode 100644 openstackclient/tests/unit/network/v2/taas/__init__.py create mode 100644 openstackclient/tests/unit/network/v2/taas/fakes.py create mode 100644 openstackclient/tests/unit/network/v2/taas/test_osc_tap_flow.py create mode 100644 openstackclient/tests/unit/network/v2/taas/test_osc_tap_mirror.py create mode 100644 openstackclient/tests/unit/network/v2/taas/test_osc_tap_service.py diff --git a/openstackclient/network/v2/taas/__init__.py b/openstackclient/network/v2/taas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openstackclient/network/v2/taas/tap_flow.py b/openstackclient/network/v2/taas/tap_flow.py new file mode 100644 index 000000000..441f559bf --- /dev/null +++ b/openstackclient/network/v2/taas/tap_flow.py @@ -0,0 +1,245 @@ +# All Rights Reserved 2020 +# +# 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 logging + +from osc_lib.cli import format_columns +from osc_lib.cli import identity as identity_utils +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils as osc_utils +from osc_lib.utils import columns as column_util + +from openstackclient.i18n import _ +from openstackclient.identity import common +from openstackclient.network.v2.taas import tap_service + + +LOG = logging.getLogger(__name__) + +TAP_FLOW = 'tap_flow' +TAP_FLOWS = f'{TAP_FLOW}s' + +_attr_map = ( + ('id', 'ID', column_util.LIST_BOTH), + ('tenant_id', 'Tenant', column_util.LIST_LONG_ONLY), + ('name', 'Name', column_util.LIST_BOTH), + ('status', 'Status', column_util.LIST_BOTH), + ('source_port', 'source_port', column_util.LIST_BOTH), + ('tap_service_id', 'tap_service_id', column_util.LIST_BOTH), + ('direction', 'Direction', column_util.LIST_BOTH), +) + +_formatters = { + 'vlan_filter': format_columns.ListColumn, +} + + +def _add_updatable_args(parser): + parser.add_argument('--name', help=_('Name of this Tap service.')) + parser.add_argument( + '--description', help=_('Description for this Tap service.') + ) + + +class CreateTapFlow(command.ShowOne): + _description = _("Create a tap flow") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + identity_utils.add_project_owner_option_to_parser(parser) + _add_updatable_args(parser) + parser.add_argument( + '--port', + required=True, + metavar="SOURCE_PORT", + help=_('Source port to which the Tap Flow is connected.'), + ) + parser.add_argument( + '--tap-service', + required=True, + metavar="TAP_SERVICE", + help=_('Tap Service to which the Tap Flow belongs.'), + ) + parser.add_argument( + '--direction', + required=True, + metavar="DIRECTION", + choices=['IN', 'OUT', 'BOTH'], + type=lambda s: s.upper(), + help=_( + 'Direction of the Tap flow. Possible options are: ' + 'IN, OUT, BOTH' + ), + ) + parser.add_argument( + '--vlan-filter', + required=False, + metavar="VLAN_FILTER", + help=_('VLAN Ids to be mirrored in the form of range string.'), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.port is not None: + source_port = client.find_port( + parsed_args.port, ignore_missing=False + )['id'] + attrs['source_port'] = source_port + if parsed_args.tap_service is not None: + tap_service_id = client.find_tap_service( + parsed_args.tap_service, ignore_missing=False + )['id'] + attrs['tap_service_id'] = tap_service_id + if parsed_args.direction is not None: + attrs['direction'] = parsed_args.direction + if parsed_args.vlan_filter is not None: + attrs['vlan_filter'] = parsed_args.vlan_filter + if 'project' in parsed_args and parsed_args.project is not None: + attrs['project_id'] = common.find_project( + self.app.client_manager.identity, + parsed_args.project, + parsed_args.project_domain, + ).id + obj = client.create_tap_flow(**attrs) + display_columns, columns = tap_service._get_columns(obj) + data = osc_utils.get_dict_properties(obj, columns) + return display_columns, data + + +class ListTapFlow(command.Lister): + _description = _("List tap flows that belong to a given tenant") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + identity_utils.add_project_owner_option_to_parser(parser) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + params = {} + if parsed_args.project is not None: + params['project_id'] = common.find_project( + self.app.client_manager.identity, + parsed_args.project, + parsed_args.project_domain, + ).id + objs = client.tap_flows(retrieve_all=True, params=params) + headers, columns = column_util.get_column_definitions( + _attr_map, long_listing=True + ) + return ( + headers, + ( + osc_utils.get_dict_properties( + s, columns, formatters=_formatters + ) + for s in objs + ), + ) + + +class ShowTapFlow(command.ShowOne): + _description = _("Show information of a given tap flow") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + TAP_FLOW, + metavar=f"<{TAP_FLOW}>", + help=_("ID or name of tap flow to look up."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + id = client.find_tap_flow( + parsed_args.tap_flow, ignore_missing=False + ).id + obj = client.get_tap_flow(id) + display_columns, columns = tap_service._get_columns(obj) + data = osc_utils.get_dict_properties(obj, columns) + return display_columns, data + + +class DeleteTapFlow(command.Command): + _description = _("Delete a tap flow") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + TAP_FLOW, + metavar=f"<{TAP_FLOW}>", + nargs="+", + help=_("ID(s) or name(s) of tap flow to delete."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + fails = 0 + for id_or_name in parsed_args.tap_flow: + try: + id = client.find_tap_flow(id_or_name, ignore_missing=False).id + client.delete_tap_flow(id) + LOG.warning("Tap flow %(id)s deleted", {'id': id}) + except Exception as e: + fails += 1 + LOG.error( + "Failed to delete tap flow with name or ID " + "'%(id_or_name)s': %(e)s", + {'id_or_name': id_or_name, 'e': e}, + ) + if fails > 0: + msg = _("Failed to delete %(fails)s of %(total)s tap flow.") % { + 'fails': fails, + 'total': len(parsed_args.tap_flow), + } + raise exceptions.CommandError(msg) + + +class UpdateTapFlow(command.ShowOne): + _description = _("Update a tap flow.") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + TAP_FLOW, + metavar=f"<{TAP_FLOW}>", + help=_("ID or name of tap flow to update."), + ) + _add_updatable_args(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + original_t_f = client.find_tap_flow( + parsed_args.tap_flow, ignore_missing=False + ).id + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + obj = client.update_tap_flow(original_t_f, **attrs) + columns, display_columns = column_util.get_columns(obj, _attr_map) + data = osc_utils.get_dict_properties(obj, columns) + return display_columns, data diff --git a/openstackclient/network/v2/taas/tap_mirror.py b/openstackclient/network/v2/taas/tap_mirror.py new file mode 100644 index 000000000..a11ea91d9 --- /dev/null +++ b/openstackclient/network/v2/taas/tap_mirror.py @@ -0,0 +1,238 @@ +# 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 logging + +from osc_lib.cli import identity as identity_utils +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils as osc_utils +from osc_lib.utils import columns as column_util + +from openstackclient.i18n import _ +from openstackclient.identity import common +from openstackclient.network.v2 import port as osc_port +from openstackclient.network.v2.taas import tap_service + + +LOG = logging.getLogger(__name__) + +TAP_MIRROR = 'tap_mirror' +TAP_MIRRORS = f'{TAP_MIRROR}s' + +_attr_map = ( + ('id', 'ID', column_util.LIST_BOTH), + ('tenant_id', 'Tenant', column_util.LIST_LONG_ONLY), + ('name', 'Name', column_util.LIST_BOTH), + ('port_id', 'Port', column_util.LIST_BOTH), + ('directions', 'Directions', column_util.LIST_LONG_ONLY), + ('remote_ip', 'Remote IP', column_util.LIST_BOTH), + ('mirror_type', 'Mirror Type', column_util.LIST_LONG_ONLY), +) + + +def _get_columns(item): + column_map: dict[str, str] = {} + hidden_columns = ['location', 'tenant_id'] + return osc_utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns + ) + + +class CreateTapMirror(command.ShowOne): + _description = _("Create a Tap Mirror") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + identity_utils.add_project_owner_option_to_parser(parser) + tap_service._add_updatable_args(parser) + parser.add_argument( + '--port', + dest='port_id', + required=True, + metavar="PORT", + help=_('Port to which the Tap Mirror is connected.'), + ) + parser.add_argument( + '--directions', + dest='directions', + action=osc_port.JSONKeyValueAction, + required=True, + help=_( + 'A dictionary of direction and tunnel_id. Direction can ' + 'be IN and OUT.' + ), + ) + parser.add_argument( + '--remote-ip', + dest='remote_ip', + required=True, + help=_( + 'The remote IP of the Tap Mirror, this will be the ' + 'remote end of the GRE or ERSPAN v1 tunnel' + ), + ) + parser.add_argument( + '--mirror-type', + dest='mirror_type', + required=True, + help=_('The type of the mirroring, it can be gre or erspanv1'), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.port_id is not None: + port_id = client.find_port( + parsed_args.port_id, ignore_missing=False + )['id'] + attrs['port_id'] = port_id + if parsed_args.directions is not None: + attrs['directions'] = parsed_args.directions + if parsed_args.remote_ip is not None: + attrs['remote_ip'] = parsed_args.remote_ip + if parsed_args.mirror_type is not None: + attrs['mirror_type'] = parsed_args.mirror_type + if 'project' in parsed_args and parsed_args.project is not None: + attrs['project_id'] = common.find_project( + self.app.client_manager.identity, + parsed_args.project, + parsed_args.project_domain, + ).id + obj = client.create_tap_mirror(**attrs) + display_columns, columns = tap_service._get_columns(obj) + data = osc_utils.get_dict_properties(obj, columns) + return display_columns, data + + +class ListTapMirror(command.Lister): + _description = _("List Tap Mirrors that belong to a given tenant") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + identity_utils.add_project_owner_option_to_parser(parser) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + params = {} + if parsed_args.project is not None: + params['project_id'] = common.find_project( + self.app.client_manager.identity, + parsed_args.project, + parsed_args.project_domain, + ).id + objs = client.tap_mirrors(retrieve_all=True, params=params) + headers, columns = column_util.get_column_definitions( + _attr_map, long_listing=True + ) + return ( + headers, + (osc_utils.get_dict_properties(s, columns) for s in objs), + ) + + +class ShowTapMirror(command.ShowOne): + _description = _("Show information of a given Tap Mirror") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + TAP_MIRROR, + metavar=f"<{TAP_MIRROR}>", + help=_("ID or name of Tap Mirror to look up."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + id = client.find_tap_mirror( + parsed_args.tap_mirror, ignore_missing=False + ).id + obj = client.get_tap_mirror(id) + display_columns, columns = tap_service._get_columns(obj) + data = osc_utils.get_dict_properties(obj, columns) + return display_columns, data + + +class DeleteTapMirror(command.Command): + _description = _("Delete a Tap Mirror") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + TAP_MIRROR, + metavar=f"<{TAP_MIRROR}>", + nargs="+", + help=_("ID(s) or name(s) of the Tap Mirror to delete."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + fails = 0 + for id_or_name in parsed_args.tap_mirror: + try: + id = client.find_tap_mirror( + id_or_name, ignore_missing=False + ).id + + client.delete_tap_mirror(id) + LOG.warning("Tap Mirror %(id)s deleted", {'id': id}) + except Exception as e: + fails += 1 + LOG.error( + "Failed to delete Tap Mirror with name or ID " + "'%(id_or_name)s': %(e)s", + {'id_or_name': id_or_name, 'e': e}, + ) + if fails > 0: + msg = _("Failed to delete %(fails)s of %(total)s Tap Mirror.") % { + 'fails': fails, + 'total': len(parsed_args.tap_mirror), + } + raise exceptions.CommandError(msg) + + +class UpdateTapMirror(command.ShowOne): + _description = _("Update a Tap Mirror.") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + TAP_MIRROR, + metavar=f"<{TAP_MIRROR}>", + help=_("ID or name of the Tap Mirror to update."), + ) + tap_service._add_updatable_args(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + original_t_s = client.find_tap_mirror( + parsed_args.tap_mirror, ignore_missing=False + ).id + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + obj = client.update_tap_mirror(original_t_s, **attrs) + display_columns, columns = tap_service._get_columns(obj) + data = osc_utils.get_dict_properties(obj, columns) + return display_columns, data diff --git a/openstackclient/network/v2/taas/tap_service.py b/openstackclient/network/v2/taas/tap_service.py new file mode 100644 index 000000000..392d76c51 --- /dev/null +++ b/openstackclient/network/v2/taas/tap_service.py @@ -0,0 +1,212 @@ +# All Rights Reserved 2020 +# +# 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 logging + +from osc_lib.cli import identity as identity_utils +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils as osc_utils +from osc_lib.utils import columns as column_util + +from openstackclient.i18n import _ +from openstackclient.identity import common + + +LOG = logging.getLogger(__name__) + +TAP_SERVICE = 'tap_service' +TAP_SERVICES = f'{TAP_SERVICE}s' + +_attr_map = ( + ('id', 'ID', column_util.LIST_BOTH), + ('tenant_id', 'Tenant', column_util.LIST_LONG_ONLY), + ('name', 'Name', column_util.LIST_BOTH), + ('port_id', 'Port', column_util.LIST_BOTH), + ('status', 'Status', column_util.LIST_BOTH), +) + + +def _add_updatable_args(parser): + parser.add_argument('--name', help=_('Name of this Tap service.')) + parser.add_argument( + '--description', help=_('Description for this Tap service.') + ) + + +def _get_columns(item): + column_map: dict[str, str] = {} + hidden_columns = ['location', 'tenant_id'] + return osc_utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns + ) + + +class CreateTapService(command.ShowOne): + _description = _("Create a tap service") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + identity_utils.add_project_owner_option_to_parser(parser) + _add_updatable_args(parser) + parser.add_argument( + '--port', + dest='port_id', + required=True, + metavar="PORT", + help=_('Port to which the Tap service is connected.'), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if parsed_args.port_id is not None: + port_id = client.find_port( + parsed_args.port_id, ignore_missing=False + )['id'] + attrs['port_id'] = port_id + if 'project' in parsed_args and parsed_args.project is not None: + attrs['project_id'] = common.find_project( + self.app.client_manager.identity, + parsed_args.project, + parsed_args.project_domain, + ).id + obj = client.create_tap_service(**attrs) + display_columns, columns = _get_columns(obj) + data = osc_utils.get_dict_properties(obj, columns) + return display_columns, data + + +class ListTapService(command.Lister): + _description = _("List tap services that belong to a given project") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + identity_utils.add_project_owner_option_to_parser(parser) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + params = {} + if parsed_args.project is not None: + params['project_id'] = common.find_project( + self.app.client_manager.identity, + parsed_args.project, + parsed_args.project_domain, + ).id + objs = client.tap_services(retrieve_all=True, params=params) + headers, columns = column_util.get_column_definitions( + _attr_map, long_listing=True + ) + return ( + headers, + (osc_utils.get_dict_properties(s, columns) for s in objs), + ) + + +class ShowTapService(command.ShowOne): + _description = _("Show information of a given tap service") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + TAP_SERVICE, + metavar=f"<{TAP_SERVICE}>", + help=_("ID or name of tap service to look up."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + id = client.find_tap_service( + parsed_args.tap_service, ignore_missing=False + ).id + obj = client.get_tap_service(id) + display_columns, columns = _get_columns(obj) + data = osc_utils.get_dict_properties(obj, columns) + return display_columns, data + + +class DeleteTapService(command.Command): + _description = _("Delete a tap service") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + TAP_SERVICE, + metavar=f"<{TAP_SERVICE}>", + nargs="+", + help=_("ID(s) or name(s) of tap service to delete."), + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + fails = 0 + for id_or_name in parsed_args.tap_service: + try: + id = client.find_tap_service( + id_or_name, ignore_missing=False + ).id + + client.delete_tap_service(id) + LOG.warning("Tap service %(id)s deleted", {'id': id}) + except Exception as e: + fails += 1 + LOG.error( + "Failed to delete tap service with name or ID " + "'%(id_or_name)s': %(e)s", + {'id_or_name': id_or_name, 'e': e}, + ) + if fails > 0: + msg = _("Failed to delete %(fails)s of %(total)s tap service.") % { + 'fails': fails, + 'total': len(parsed_args.tap_service), + } + raise exceptions.CommandError(msg) + + +class UpdateTapService(command.ShowOne): + _description = _("Update a tap service.") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + TAP_SERVICE, + metavar=f"<{TAP_SERVICE}>", + help=_("ID or name of tap service to update."), + ) + _add_updatable_args(parser) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + original_t_s = client.find_tap_service( + parsed_args.tap_service, ignore_missing=False + ).id + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + obj = client.update_tap_service(original_t_s, **attrs) + display_columns, columns = _get_columns(obj) + data = osc_utils.get_dict_properties(obj, columns) + return display_columns, data diff --git a/openstackclient/tests/unit/network/v2/taas/__init__.py b/openstackclient/tests/unit/network/v2/taas/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/openstackclient/tests/unit/network/v2/taas/fakes.py b/openstackclient/tests/unit/network/v2/taas/fakes.py new file mode 100644 index 000000000..0480a0d90 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/taas/fakes.py @@ -0,0 +1,118 @@ +# All Rights Reserved 2020 +# +# 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 copy + +from oslo_utils import uuidutils + + +class FakeTapService: + @staticmethod + def create_tap_service(attrs=None): + """Create a fake tap service.""" + attrs = attrs or {} + tap_service_attrs = { + 'id': uuidutils.generate_uuid(), + 'tenant_id': uuidutils.generate_uuid(), + 'name': 'test_tap_service' + uuidutils.generate_uuid(), + 'status': 'ACTIVE', + } + tap_service_attrs.update(attrs) + return copy.deepcopy(tap_service_attrs) + + @staticmethod + def create_tap_services(attrs=None, count=1): + """Create multiple fake tap services.""" + + tap_services = [] + for i in range(0, count): + if attrs is None: + attrs = {'id': f'fake_id{i}'} + elif getattr(attrs, 'id', None) is None: + attrs['id'] = f'fake_id{i}' + tap_services.append(FakeTapService.create_tap_service(attrs=attrs)) + + return tap_services + + +class FakeTapFlow: + @staticmethod + def create_tap_flow(attrs=None): + """Create a fake tap service.""" + attrs = attrs or {} + tap_flow_attrs = { + 'id': uuidutils.generate_uuid(), + 'tenant_id': uuidutils.generate_uuid(), + 'name': 'test_tap_flow' + uuidutils.generate_uuid(), + 'status': 'ACTIVE', + 'direction': 'BOTH', + } + tap_flow_attrs.update(attrs) + return copy.deepcopy(tap_flow_attrs) + + @staticmethod + def create_tap_flows(attrs=None, count=1): + """Create multiple fake tap flows.""" + + tap_flows = [] + for i in range(0, count): + if attrs is None: + attrs = { + 'id': f'fake_id{i}', + 'source_port': uuidutils.generate_uuid(), + 'tap_service_id': uuidutils.generate_uuid(), + } + elif getattr(attrs, 'id', None) is None: + attrs['id'] = f'fake_id{i}' + tap_flows.append(FakeTapFlow.create_tap_flow(attrs=attrs)) + + return tap_flows + + +class FakeTapMirror: + @staticmethod + def create_tap_mirror(attrs=None): + """Create a fake tap mirror.""" + attrs = attrs or {} + tap_mirror_attrs = { + 'id': uuidutils.generate_uuid(), + 'tenant_id': uuidutils.generate_uuid(), + 'name': 'test_tap_mirror' + uuidutils.generate_uuid(), + 'port_id': uuidutils.generate_uuid(), + 'directions': 'IN=99', + 'remote_ip': '192.10.10.2', + 'mirror_type': 'gre', + } + tap_mirror_attrs.update(attrs) + return copy.deepcopy(tap_mirror_attrs) + + @staticmethod + def create_tap_mirrors(attrs=None, count=1): + """Create multiple fake tap mirrors.""" + + tap_mirrors = [] + for i in range(0, count): + if attrs is None: + attrs = { + 'id': f'fake_id{i}', + 'port_id': uuidutils.generate_uuid(), + 'name': f'test_tap_mirror_{i}', + 'directions': f'IN={99 + i}', + 'remote_ip': f'192.10.10.{i + 3}', + } + elif getattr(attrs, 'id', None) is None: + attrs['id'] = f'fake_id{i}' + tap_mirrors.append(FakeTapMirror.create_tap_mirror(attrs=attrs)) + + return tap_mirrors diff --git a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_flow.py b/openstackclient/tests/unit/network/v2/taas/test_osc_tap_flow.py new file mode 100644 index 000000000..e401a0f97 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/taas/test_osc_tap_flow.py @@ -0,0 +1,283 @@ +# All Rights Reserved 2020 +# +# 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 copy +import operator +import uuid + +from openstack.network.v2 import tap_flow as _tap_flow +from osc_lib import utils as osc_utils +from osc_lib.utils import columns as column_util + +from openstackclient.network.v2.taas import tap_flow as osc_tap_flow +from openstackclient.network.v2.taas import tap_service as osc_tap_service +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit.network.v2.taas import fakes + + +columns_long = tuple( + col + for col, _, listing_mode in osc_tap_flow._attr_map + if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY) +) +headers_long = tuple( + head + for _, head, listing_mode in osc_tap_flow._attr_map + if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY) +) +sorted_attr_map = sorted(osc_tap_flow._attr_map, key=operator.itemgetter(1)) +sorted_columns = tuple(col for col, _, _ in sorted_attr_map) +sorted_headers = tuple(head for _, head, _ in sorted_attr_map) + + +def _get_data(attrs, columns=sorted_columns): + return osc_utils.get_dict_properties(attrs, columns) + + +class TestCreateTapFlow(network_fakes.TestNetworkV2): + columns = ( + 'direction', + 'id', + 'name', + 'source_port', + 'status', + 'tap_service_id', + ) + + def setUp(self): + super().setUp() + self.cmd = osc_tap_flow.CreateTapFlow(self.app, None) + + def test_create_tap_flow(self): + """Test Create Tap Flow.""" + fake_tap_service = fakes.FakeTapService.create_tap_service( + attrs={'port_id': str(uuid.uuid4())} + ) + port_id = str(uuid.uuid4()) + fake_tap_flow = fakes.FakeTapFlow.create_tap_flow( + attrs={ + 'source_port': port_id, + 'tap_service_id': fake_tap_service['id'], + } + ) + self.app.client_manager.network.create_tap_flow.return_value = ( + fake_tap_flow + ) + self.app.client_manager.network.find_port.return_value = { + 'id': port_id + } + self.app.client_manager.network.find_tap_service.return_value = ( + fake_tap_service + ) + arg_list = [ + '--name', + fake_tap_flow['name'], + '--port', + fake_tap_flow['source_port'], + '--tap-service', + fake_tap_flow['tap_service_id'], + '--direction', + fake_tap_flow['direction'], + ] + + verify_list = [ + ('name', fake_tap_flow['name']), + ('port', fake_tap_flow['source_port']), + ('tap_service', fake_tap_flow['tap_service_id']), + ] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + columns, data = self.cmd.take_action(parsed_args) + mock_create_t_f = self.app.client_manager.network.create_tap_flow + mock_create_t_f.assert_called_once_with( + **{ + 'name': fake_tap_flow['name'], + 'source_port': fake_tap_flow['source_port'], + 'tap_service_id': fake_tap_flow['tap_service_id'], + 'direction': fake_tap_flow['direction'], + } + ) + self.assertEqual(self.columns, columns) + fake_data = _get_data( + fake_tap_flow, osc_tap_service._get_columns(fake_tap_flow)[1] + ) + self.assertEqual(fake_data, data) + + +class TestListTapFlow(network_fakes.TestNetworkV2): + def setUp(self): + super().setUp() + self.cmd = osc_tap_flow.ListTapFlow(self.app, None) + + def test_list_tap_flows(self): + """Test List Tap Flow.""" + fake_tap_flows = fakes.FakeTapFlow.create_tap_flows( + attrs={ + 'source_port': str(uuid.uuid4()), + 'tap_service_id': str(uuid.uuid4()), + }, + count=2, + ) + self.app.client_manager.network.tap_flows.return_value = fake_tap_flows + arg_list = [] + verify_list = [] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + + headers, data = self.cmd.take_action(parsed_args) + + self.app.client_manager.network.tap_flows.assert_called_once() + self.assertEqual(headers, list(headers_long)) + self.assertCountEqual( + list(data), + [ + _get_data(fake_tap_flow, columns_long) + for fake_tap_flow in fake_tap_flows + ], + ) + + +class TestDeleteTapFlow(network_fakes.TestNetworkV2): + def setUp(self): + super().setUp() + self.app.client_manager.network.find_tap_flow.side_effect = ( + lambda name_or_id, ignore_missing: _tap_flow.TapFlow(id=name_or_id) + ) + self.cmd = osc_tap_flow.DeleteTapFlow(self.app, None) + + def test_delete_tap_flow(self): + """Test Delete tap flow.""" + + fake_tap_flow = fakes.FakeTapFlow.create_tap_flow( + attrs={ + 'source_port': str(uuid.uuid4()), + 'tap_service_id': str(uuid.uuid4()), + } + ) + + arg_list = [ + fake_tap_flow['id'], + ] + verify_list = [ + (osc_tap_flow.TAP_FLOW, [fake_tap_flow['id']]), + ] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + + result = self.cmd.take_action(parsed_args) + + mock_delete_tap_flow = self.app.client_manager.network.delete_tap_flow + mock_delete_tap_flow.assert_called_once_with(fake_tap_flow['id']) + self.assertIsNone(result) + + +class TestShowTapFlow(network_fakes.TestNetworkV2): + columns = ( + 'direction', + 'id', + 'name', + 'source_port', + 'status', + 'tap_service_id', + ) + + def setUp(self): + super().setUp() + self.app.client_manager.network.find_tap_flow.side_effect = ( + lambda name_or_id, ignore_missing: _tap_flow.TapFlow(id=name_or_id) + ) + self.cmd = osc_tap_flow.ShowTapFlow(self.app, None) + + def test_show_tap_flow(self): + """Test Show tap flow.""" + fake_tap_flow = fakes.FakeTapFlow.create_tap_flow( + attrs={ + 'source_port': str(uuid.uuid4()), + 'tap_service_id': str(uuid.uuid4()), + } + ) + self.app.client_manager.network.get_tap_flow.return_value = ( + fake_tap_flow + ) + arg_list = [ + fake_tap_flow['id'], + ] + verify_list = [ + (osc_tap_flow.TAP_FLOW, fake_tap_flow['id']), + ] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + + headers, data = self.cmd.take_action(parsed_args) + + self.app.client_manager.network.get_tap_flow.assert_called_once_with( + fake_tap_flow['id'] + ) + self.assertEqual(self.columns, headers) + fake_data = _get_data( + fake_tap_flow, osc_tap_service._get_columns(fake_tap_flow)[1] + ) + self.assertEqual(fake_data, data) + + +class TestUpdateTapFlow(network_fakes.TestNetworkV2): + _new_name = 'new_name' + + columns = ( + 'Direction', + 'ID', + 'Name', + 'Status', + 'Tenant', + 'source_port', + 'tap_service_id', + ) + + def setUp(self): + super().setUp() + self.cmd = osc_tap_flow.UpdateTapFlow(self.app, None) + self.app.client_manager.network.find_tap_flow.side_effect = ( + lambda name_or_id, ignore_missing: _tap_flow.TapFlow(id=name_or_id) + ) + + def test_update_tap_flow(self): + """Test update tap service""" + fake_tap_flow = fakes.FakeTapFlow.create_tap_flow( + attrs={ + 'source_port': str(uuid.uuid4()), + 'tap_service_id': str(uuid.uuid4()), + } + ) + new_tap_flow = copy.deepcopy(fake_tap_flow) + new_tap_flow['name'] = self._new_name + + self.app.client_manager.network.update_tap_flow.return_value = ( + new_tap_flow + ) + + arg_list = [ + fake_tap_flow['id'], + '--name', + self._new_name, + ] + verify_list = [('name', self._new_name)] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + columns, data = self.cmd.take_action(parsed_args) + attrs = {'name': self._new_name} + + mock_update_t_f = self.app.client_manager.network.update_tap_flow + mock_update_t_f.assert_called_once_with(new_tap_flow['id'], **attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(_get_data(new_tap_flow), data) diff --git a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_mirror.py b/openstackclient/tests/unit/network/v2/taas/test_osc_tap_mirror.py new file mode 100644 index 000000000..451d6a25b --- /dev/null +++ b/openstackclient/tests/unit/network/v2/taas/test_osc_tap_mirror.py @@ -0,0 +1,283 @@ +# 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 copy +import operator +import uuid + +from openstack.network.v2 import tap_mirror +from osc_lib import utils as osc_utils +from osc_lib.utils import columns as column_util + +from openstackclient.network.v2.taas import tap_mirror as osc_tap_mirror +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit.network.v2.taas import fakes + + +columns_long = tuple( + col + for col, _, listing_mode in osc_tap_mirror._attr_map + if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY) +) +headers_long = tuple( + head + for _, head, listing_mode in osc_tap_mirror._attr_map + if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY) +) +sorted_attr_map = sorted(osc_tap_mirror._attr_map, key=operator.itemgetter(1)) +sorted_columns = tuple(col for col, _, _ in sorted_attr_map) +sorted_headers = tuple(head for _, head, _ in sorted_attr_map) + + +def _get_data(attrs, columns=sorted_columns): + return osc_utils.get_dict_properties(attrs, columns) + + +class TestCreateTapMirror(network_fakes.TestNetworkV2): + columns = ( + 'directions', + 'id', + 'mirror_type', + 'name', + 'port_id', + 'remote_ip', + ) + + def setUp(self): + super().setUp() + self.cmd = osc_tap_mirror.CreateTapMirror(self.app, None) + + def test_create_tap_mirror(self): + port_id = str(uuid.uuid4()) + fake_tap_mirror = fakes.FakeTapMirror.create_tap_mirror( + attrs={'port_id': port_id} + ) + self.app.client_manager.network.create_tap_mirror.return_value = ( + fake_tap_mirror + ) + self.app.client_manager.network.find_port.return_value = { + 'id': port_id + } + self.app.client_manager.network.find_tap_mirror.side_effect = ( + lambda _, name_or_id: {'id': name_or_id} + ) + arg_list = [ + '--name', + fake_tap_mirror['name'], + '--port', + fake_tap_mirror['port_id'], + '--directions', + fake_tap_mirror['directions'], + '--remote-ip', + fake_tap_mirror['remote_ip'], + '--mirror-type', + fake_tap_mirror['mirror_type'], + ] + + verify_directions = fake_tap_mirror['directions'].split('=') + verify_directions_dict = {verify_directions[0]: verify_directions[1]} + + verify_list = [ + ('name', fake_tap_mirror['name']), + ('port_id', fake_tap_mirror['port_id']), + ('directions', verify_directions_dict), + ('remote_ip', fake_tap_mirror['remote_ip']), + ('mirror_type', fake_tap_mirror['mirror_type']), + ] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + self.app.client_manager.network.find_tap_mirror.return_value = ( + fake_tap_mirror + ) + + columns, data = self.cmd.take_action(parsed_args) + create_tap_m_mock = self.app.client_manager.network.create_tap_mirror + create_tap_m_mock.assert_called_once_with( + **{ + 'name': fake_tap_mirror['name'], + 'port_id': fake_tap_mirror['port_id'], + 'directions': verify_directions_dict, + 'remote_ip': fake_tap_mirror['remote_ip'], + 'mirror_type': fake_tap_mirror['mirror_type'], + } + ) + self.assertEqual(self.columns, columns) + fake_data = _get_data( + fake_tap_mirror, osc_tap_mirror._get_columns(fake_tap_mirror)[1] + ) + self.assertEqual(fake_data, data) + + +class TestListTapMirror(network_fakes.TestNetworkV2): + def setUp(self): + super().setUp() + self.cmd = osc_tap_mirror.ListTapMirror(self.app, None) + + def test_list_tap_mirror(self): + """Test List Tap Mirror.""" + fake_tap_mirrors = fakes.FakeTapMirror.create_tap_mirrors( + attrs={'port_id': str(uuid.uuid4())}, count=4 + ) + self.app.client_manager.network.tap_mirrors.return_value = ( + fake_tap_mirrors + ) + + arg_list = [] + verify_list = [] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + + headers, data = self.cmd.take_action(parsed_args) + + self.app.client_manager.network.tap_mirrors.assert_called_once() + self.assertEqual(headers, list(headers_long)) + self.assertCountEqual( + list(data), + [ + _get_data(fake_tap_mirror, columns_long) + for fake_tap_mirror in fake_tap_mirrors + ], + ) + + +class TestDeleteTapMirror(network_fakes.TestNetworkV2): + def setUp(self): + super().setUp() + self.app.client_manager.network.find_tap_mirror.side_effect = ( + lambda name_or_id, ignore_missing: tap_mirror.TapMirror( + id=name_or_id + ) + ) + self.cmd = osc_tap_mirror.DeleteTapMirror(self.app, None) + + def test_delete_tap_mirror(self): + """Test Delete Tap Mirror.""" + + fake_tap_mirror = fakes.FakeTapMirror.create_tap_mirror( + attrs={'port_id': str(uuid.uuid4())} + ) + + arg_list = [ + fake_tap_mirror['id'], + ] + verify_list = [ + (osc_tap_mirror.TAP_MIRROR, [fake_tap_mirror['id']]), + ] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + result = self.cmd.take_action(parsed_args) + + mock_delete_tap_m = self.app.client_manager.network.delete_tap_mirror + mock_delete_tap_m.assert_called_once_with(fake_tap_mirror['id']) + self.assertIsNone(result) + + +class TestShowTapMirror(network_fakes.TestNetworkV2): + columns = ( + 'directions', + 'id', + 'mirror_type', + 'name', + 'port_id', + 'remote_ip', + ) + + def setUp(self): + super().setUp() + self.app.client_manager.network.find_tap_mirror.side_effect = ( + lambda name_or_id, ignore_missing: tap_mirror.TapMirror( + id=name_or_id + ) + ) + self.cmd = osc_tap_mirror.ShowTapMirror(self.app, None) + + def test_show_tap_mirror(self): + """Test Show Tap Mirror.""" + + fake_tap_mirror = fakes.FakeTapMirror.create_tap_mirror( + attrs={'port_id': str(uuid.uuid4())} + ) + self.app.client_manager.network.get_tap_mirror.return_value = ( + fake_tap_mirror + ) + arg_list = [ + fake_tap_mirror['id'], + ] + verify_list = [ + (osc_tap_mirror.TAP_MIRROR, fake_tap_mirror['id']), + ] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + + headers, data = self.cmd.take_action(parsed_args) + + mock_get_tap_m = self.app.client_manager.network.get_tap_mirror + mock_get_tap_m.assert_called_once_with(fake_tap_mirror['id']) + self.assertEqual(self.columns, headers) + fake_data = _get_data( + fake_tap_mirror, osc_tap_mirror._get_columns(fake_tap_mirror)[1] + ) + self.assertEqual(fake_data, data) + + +class TestUpdateTapMirror(network_fakes.TestNetworkV2): + _new_name = 'new_name' + columns = ( + 'directions', + 'id', + 'mirror_type', + 'name', + 'port_id', + 'remote_ip', + ) + + def setUp(self): + super().setUp() + self.cmd = osc_tap_mirror.UpdateTapMirror(self.app, None) + self.app.client_manager.network.find_tap_mirror.side_effect = ( + lambda name_or_id, ignore_missing: tap_mirror.TapMirror( + id=name_or_id + ) + ) + + def test_update_tap_mirror(self): + """Test update Tap Mirror""" + fake_tap_mirror = fakes.FakeTapMirror.create_tap_mirror( + attrs={'port_id': str(uuid.uuid4())} + ) + new_tap_mirror = copy.deepcopy(fake_tap_mirror) + new_tap_mirror['name'] = self._new_name + + self.app.client_manager.network.update_tap_mirror.return_value = ( + new_tap_mirror + ) + + arg_list = [ + fake_tap_mirror['id'], + '--name', + self._new_name, + ] + verify_list = [('name', self._new_name)] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + columns, data = self.cmd.take_action(parsed_args) + attrs = {'name': self._new_name} + + mock_update_tap_m = self.app.client_manager.network.update_tap_mirror + mock_update_tap_m.assert_called_once_with( + fake_tap_mirror['id'], **attrs + ) + self.assertEqual(self.columns, columns) + fake_data = _get_data( + new_tap_mirror, osc_tap_mirror._get_columns(new_tap_mirror)[1] + ) + self.assertEqual(fake_data, data) diff --git a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_service.py b/openstackclient/tests/unit/network/v2/taas/test_osc_tap_service.py new file mode 100644 index 000000000..0b8180151 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/taas/test_osc_tap_service.py @@ -0,0 +1,266 @@ +# All Rights Reserved 2020 +# +# 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 copy +import operator +import uuid + +from openstack.network.v2 import tap_service +from osc_lib import utils as osc_utils +from osc_lib.utils import columns as column_util + +from openstackclient.network.v2.taas import tap_service as osc_tap_service +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit.network.v2.taas import fakes + + +columns_long = tuple( + col + for col, _, listing_mode in osc_tap_service._attr_map + if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY) +) +headers_long = tuple( + head + for _, head, listing_mode in osc_tap_service._attr_map + if listing_mode in (column_util.LIST_BOTH, column_util.LIST_LONG_ONLY) +) +sorted_attr_map = sorted(osc_tap_service._attr_map, key=operator.itemgetter(1)) +sorted_columns = tuple(col for col, _, _ in sorted_attr_map) +sorted_headers = tuple(head for _, head, _ in sorted_attr_map) + + +def _get_data(attrs, columns=sorted_columns): + return osc_utils.get_dict_properties(attrs, columns) + + +class TestCreateTapService(network_fakes.TestNetworkV2): + columns = ( + 'id', + 'name', + 'port_id', + 'status', + ) + + def setUp(self): + super().setUp() + self.cmd = osc_tap_service.CreateTapService(self.app, None) + + def test_create_tap_service(self): + """Test Create Tap Service.""" + port_id = str(uuid.uuid4()) + fake_tap_service = fakes.FakeTapService.create_tap_service( + attrs={'port_id': port_id} + ) + self.app.client_manager.network.create_tap_service.return_value = ( + fake_tap_service + ) + self.app.client_manager.network.find_port.return_value = { + 'id': port_id + } + self.app.client_manager.network.find_tap_service.side_effect = ( + lambda _, name_or_id: {'id': name_or_id} + ) + arg_list = [ + '--name', + fake_tap_service['name'], + '--port', + fake_tap_service['port_id'], + ] + + verify_list = [ + ('name', fake_tap_service['name']), + ('port_id', fake_tap_service['port_id']), + ] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + self.app.client_manager.network.find_tap_service.return_value = ( + fake_tap_service + ) + + columns, data = self.cmd.take_action(parsed_args) + create_tap_s_mock = self.app.client_manager.network.create_tap_service + create_tap_s_mock.assert_called_once_with( + **{ + 'name': fake_tap_service['name'], + 'port_id': fake_tap_service['port_id'], + } + ) + self.assertEqual(self.columns, columns) + fake_data = _get_data( + fake_tap_service, osc_tap_service._get_columns(fake_tap_service)[1] + ) + self.assertEqual(fake_data, data) + + +class TestListTapService(network_fakes.TestNetworkV2): + def setUp(self): + super().setUp() + self.cmd = osc_tap_service.ListTapService(self.app, None) + + def test_list_tap_service(self): + """Test List Tap Service.""" + fake_tap_services = fakes.FakeTapService.create_tap_services( + attrs={'port_id': str(uuid.uuid4())}, count=4 + ) + self.app.client_manager.network.tap_services.return_value = ( + fake_tap_services + ) + + arg_list = [] + verify_list = [] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + + headers, data = self.cmd.take_action(parsed_args) + + self.app.client_manager.network.tap_services.assert_called_once() + self.assertEqual(headers, list(headers_long)) + self.assertCountEqual( + list(data), + [ + _get_data(fake_tap_service, columns_long) + for fake_tap_service in fake_tap_services + ], + ) + + +class TestDeleteTapService(network_fakes.TestNetworkV2): + def setUp(self): + super().setUp() + self.app.client_manager.network.find_tap_service.side_effect = ( + lambda name_or_id, ignore_missing: tap_service.TapService( + id=name_or_id + ) + ) + self.cmd = osc_tap_service.DeleteTapService(self.app, None) + + def test_delete_tap_service(self): + """Test Delete tap service.""" + + fake_tap_service = fakes.FakeTapService.create_tap_service( + attrs={'port_id': str(uuid.uuid4())} + ) + + arg_list = [ + fake_tap_service['id'], + ] + verify_list = [ + (osc_tap_service.TAP_SERVICE, [fake_tap_service['id']]), + ] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + result = self.cmd.take_action(parsed_args) + + mock_delete_tap_s = self.app.client_manager.network.delete_tap_service + mock_delete_tap_s.assert_called_once_with(fake_tap_service['id']) + self.assertIsNone(result) + + +class TestShowTapService(network_fakes.TestNetworkV2): + columns = ( + 'id', + 'name', + 'port_id', + 'status', + ) + + def setUp(self): + super().setUp() + self.app.client_manager.network.find_tap_service.side_effect = ( + lambda name_or_id, ignore_missing: tap_service.TapService( + id=name_or_id + ) + ) + self.cmd = osc_tap_service.ShowTapService(self.app, None) + + def test_show_tap_service(self): + """Test Show tap service.""" + + fake_tap_service = fakes.FakeTapService.create_tap_service( + attrs={'port_id': str(uuid.uuid4())} + ) + self.app.client_manager.network.get_tap_service.return_value = ( + fake_tap_service + ) + arg_list = [ + fake_tap_service['id'], + ] + verify_list = [ + (osc_tap_service.TAP_SERVICE, fake_tap_service['id']), + ] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + + headers, data = self.cmd.take_action(parsed_args) + + mock_get_tap_s = self.app.client_manager.network.get_tap_service + mock_get_tap_s.assert_called_once_with(fake_tap_service['id']) + self.assertEqual(self.columns, headers) + fake_data = _get_data( + fake_tap_service, osc_tap_service._get_columns(fake_tap_service)[1] + ) + self.assertEqual(fake_data, data) + + +class TestUpdateTapService(network_fakes.TestNetworkV2): + _new_name = 'new_name' + + columns = ( + 'id', + 'name', + 'port_id', + 'status', + ) + + def setUp(self): + super().setUp() + self.cmd = osc_tap_service.UpdateTapService(self.app, None) + self.app.client_manager.network.find_tap_service.side_effect = ( + lambda name_or_id, ignore_missing: tap_service.TapService( + id=name_or_id + ) + ) + + def test_update_tap_service(self): + """Test update tap service""" + fake_tap_service = fakes.FakeTapService.create_tap_service( + attrs={'port_id': str(uuid.uuid4())} + ) + new_tap_service = copy.deepcopy(fake_tap_service) + new_tap_service['name'] = self._new_name + + self.app.client_manager.network.update_tap_service.return_value = ( + new_tap_service + ) + + arg_list = [ + fake_tap_service['id'], + '--name', + self._new_name, + ] + verify_list = [('name', self._new_name)] + + parsed_args = self.check_parser(self.cmd, arg_list, verify_list) + columns, data = self.cmd.take_action(parsed_args) + attrs = {'name': self._new_name} + + mock_update_tap_s = self.app.client_manager.network.update_tap_service + mock_update_tap_s.assert_called_once_with( + fake_tap_service['id'], **attrs + ) + self.assertEqual(self.columns, columns) + fake_data = _get_data( + new_tap_service, osc_tap_service._get_columns(new_tap_service)[1] + ) + self.assertEqual(fake_data, data) diff --git a/pyproject.toml b/pyproject.toml index aeb5ab495..dc2cbf386 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -540,6 +540,23 @@ subnet_pool_set = "openstackclient.network.v2.subnet_pool:SetSubnetPool" subnet_pool_show = "openstackclient.network.v2.subnet_pool:ShowSubnetPool" subnet_pool_unset = "openstackclient.network.v2.subnet_pool:UnsetSubnetPool" +# Tap-as-a-Service +tap_flow_create = "openstackclient.network.v2.taas.tap_flow:CreateTapFlow" +tap_flow_delete = "openstackclient.network.v2.taas.tap_flow:DeleteTapFlow" +tap_flow_list = "openstackclient.network.v2.taas.tap_flow:ListTapFlow" +tap_flow_show = "openstackclient.network.v2.taas.tap_flow:ShowTapFlow" +tap_flow_update = "openstackclient.network.v2.taas.tap_flow:UpdateTapFlow" +tap_mirror_create = "openstackclient.network.v2.taas.tap_mirror:CreateTapMirror" +tap_mirror_delete = "openstackclient.network.v2.taas.tap_mirror:DeleteTapMirror" +tap_mirror_list = "openstackclient.network.v2.taas.tap_mirror:ListTapMirror" +tap_mirror_show = "openstackclient.network.v2.taas.tap_mirror:ShowTapMirror" +tap_mirror_update = "openstackclient.network.v2.taas.tap_mirror:UpdateTapMirror" +tap_service_create = "openstackclient.network.v2.taas.tap_service:CreateTapService" +tap_service_delete = "openstackclient.network.v2.taas.tap_service:DeleteTapService" +tap_service_list = "openstackclient.network.v2.taas.tap_service:ListTapService" +tap_service_show = "openstackclient.network.v2.taas.tap_service:ShowTapService" +tap_service_update = "openstackclient.network.v2.taas.tap_service:UpdateTapService" + [project.entry-points."openstack.object_store.v1"] object_store_account_set = "openstackclient.object.v1.account:SetAccount" object_store_account_show = "openstackclient.object.v1.account:ShowAccount" From 9e49047ed11a790a025d94c0d3f61cd5a684fb07 Mon Sep 17 00:00:00 2001 From: Miro Tomaska Date: Wed, 26 Nov 2025 13:56:05 -0500 Subject: [PATCH 50/67] Improve help strings for tap services This is a follow up patch to feedback from[1] [1] https://review.opendev.org/c/openstack/python-openstackclient/+/963445/comment/8f9576d4_938391ea/ Change-Id: I1c1ee68b37ef4c87c13d18e773c19b4ca5814ead Signed-off-by: Miro Tomaska --- openstackclient/network/v2/taas/tap_flow.py | 31 ++++++++++--------- openstackclient/network/v2/taas/tap_mirror.py | 28 ++++++++--------- .../network/v2/taas/tap_service.py | 20 ++++++------ 3 files changed, 40 insertions(+), 39 deletions(-) diff --git a/openstackclient/network/v2/taas/tap_flow.py b/openstackclient/network/v2/taas/tap_flow.py index 441f559bf..92eb36d3e 100644 --- a/openstackclient/network/v2/taas/tap_flow.py +++ b/openstackclient/network/v2/taas/tap_flow.py @@ -47,14 +47,14 @@ def _add_updatable_args(parser): - parser.add_argument('--name', help=_('Name of this Tap service.')) + parser.add_argument('--name', help=_('Name of the tap flow.')) parser.add_argument( - '--description', help=_('Description for this Tap service.') + '--description', help=_('Description of the tap flow.') ) class CreateTapFlow(command.ShowOne): - _description = _("Create a tap flow") + _description = _("Create a new tap flow.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) @@ -64,13 +64,15 @@ def get_parser(self, prog_name): '--port', required=True, metavar="SOURCE_PORT", - help=_('Source port to which the Tap Flow is connected.'), + help=_('Source port (name or ID) to monitor.'), ) parser.add_argument( '--tap-service', required=True, metavar="TAP_SERVICE", - help=_('Tap Service to which the Tap Flow belongs.'), + help=_( + 'Tap service (name or ID) to associate with this tap flow.' + ), ) parser.add_argument( '--direction', @@ -79,15 +81,15 @@ def get_parser(self, prog_name): choices=['IN', 'OUT', 'BOTH'], type=lambda s: s.upper(), help=_( - 'Direction of the Tap flow. Possible options are: ' - 'IN, OUT, BOTH' + 'Direction of the Tap flow. Valid options are: ' + 'IN, OUT and BOTH' ), ) parser.add_argument( '--vlan-filter', required=False, metavar="VLAN_FILTER", - help=_('VLAN Ids to be mirrored in the form of range string.'), + help=_('VLAN IDs to mirror in the form of range string.'), ) return parser @@ -125,7 +127,7 @@ def take_action(self, parsed_args): class ListTapFlow(command.Lister): - _description = _("List tap flows that belong to a given tenant") + _description = _("List tap flows.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) @@ -158,14 +160,14 @@ def take_action(self, parsed_args): class ShowTapFlow(command.ShowOne): - _description = _("Show information of a given tap flow") + _description = _("Show tap flow details.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( TAP_FLOW, metavar=f"<{TAP_FLOW}>", - help=_("ID or name of tap flow to look up."), + help=_("Tap flow to display (name or ID)."), ) return parser @@ -181,7 +183,7 @@ def take_action(self, parsed_args): class DeleteTapFlow(command.Command): - _description = _("Delete a tap flow") + _description = _("Delete a tap flow.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) @@ -189,7 +191,7 @@ def get_parser(self, prog_name): TAP_FLOW, metavar=f"<{TAP_FLOW}>", nargs="+", - help=_("ID(s) or name(s) of tap flow to delete."), + help=_("Tap flow to delete (name or ID)."), ) return parser @@ -200,7 +202,6 @@ def take_action(self, parsed_args): try: id = client.find_tap_flow(id_or_name, ignore_missing=False).id client.delete_tap_flow(id) - LOG.warning("Tap flow %(id)s deleted", {'id': id}) except Exception as e: fails += 1 LOG.error( @@ -224,7 +225,7 @@ def get_parser(self, prog_name): parser.add_argument( TAP_FLOW, metavar=f"<{TAP_FLOW}>", - help=_("ID or name of tap flow to update."), + help=_("Tap flow to modify (name or ID)."), ) _add_updatable_args(parser) return parser diff --git a/openstackclient/network/v2/taas/tap_mirror.py b/openstackclient/network/v2/taas/tap_mirror.py index a11ea91d9..e48a3ae2b 100644 --- a/openstackclient/network/v2/taas/tap_mirror.py +++ b/openstackclient/network/v2/taas/tap_mirror.py @@ -49,7 +49,7 @@ def _get_columns(item): class CreateTapMirror(command.ShowOne): - _description = _("Create a Tap Mirror") + _description = _("Create a new tap mirror.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) @@ -60,7 +60,7 @@ def get_parser(self, prog_name): dest='port_id', required=True, metavar="PORT", - help=_('Port to which the Tap Mirror is connected.'), + help=_('Port (name or ID) to which the Tap Mirror is connected.'), ) parser.add_argument( '--directions', @@ -68,8 +68,8 @@ def get_parser(self, prog_name): action=osc_port.JSONKeyValueAction, required=True, help=_( - 'A dictionary of direction and tunnel_id. Direction can ' - 'be IN and OUT.' + 'Dictionary of direction and tunnel_id. Valid directions are: ' + 'IN and OUT.' ), ) parser.add_argument( @@ -77,15 +77,15 @@ def get_parser(self, prog_name): dest='remote_ip', required=True, help=_( - 'The remote IP of the Tap Mirror, this will be the ' - 'remote end of the GRE or ERSPAN v1 tunnel' + 'Remote IP address for the tap mirror (remote end of the ' + 'GRE or ERSPAN v1 tunnel).' ), ) parser.add_argument( '--mirror-type', dest='mirror_type', required=True, - help=_('The type of the mirroring, it can be gre or erspanv1'), + help=_('Mirror type. Valid values are: gre and erspanv1.'), ) return parser @@ -120,7 +120,7 @@ def take_action(self, parsed_args): class ListTapMirror(command.Lister): - _description = _("List Tap Mirrors that belong to a given tenant") + _description = _("List tap mirrors.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) @@ -148,14 +148,14 @@ def take_action(self, parsed_args): class ShowTapMirror(command.ShowOne): - _description = _("Show information of a given Tap Mirror") + _description = _("Show tap mirror details.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( TAP_MIRROR, metavar=f"<{TAP_MIRROR}>", - help=_("ID or name of Tap Mirror to look up."), + help=_("Tap mirror to display (name or ID)."), ) return parser @@ -171,7 +171,7 @@ def take_action(self, parsed_args): class DeleteTapMirror(command.Command): - _description = _("Delete a Tap Mirror") + _description = _("Delete a tap mirror.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) @@ -179,7 +179,7 @@ def get_parser(self, prog_name): TAP_MIRROR, metavar=f"<{TAP_MIRROR}>", nargs="+", - help=_("ID(s) or name(s) of the Tap Mirror to delete."), + help=_("Tap mirror to delete (name or ID)."), ) return parser @@ -210,14 +210,14 @@ def take_action(self, parsed_args): class UpdateTapMirror(command.ShowOne): - _description = _("Update a Tap Mirror.") + _description = _("Update a tap mirror.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( TAP_MIRROR, metavar=f"<{TAP_MIRROR}>", - help=_("ID or name of the Tap Mirror to update."), + help=_("Tap mirror to modify (name or ID)."), ) tap_service._add_updatable_args(parser) return parser diff --git a/openstackclient/network/v2/taas/tap_service.py b/openstackclient/network/v2/taas/tap_service.py index 392d76c51..486388ec1 100644 --- a/openstackclient/network/v2/taas/tap_service.py +++ b/openstackclient/network/v2/taas/tap_service.py @@ -39,9 +39,9 @@ def _add_updatable_args(parser): - parser.add_argument('--name', help=_('Name of this Tap service.')) + parser.add_argument('--name', help=_('Name of the tap service.')) parser.add_argument( - '--description', help=_('Description for this Tap service.') + '--description', help=_('Description of the tap service.') ) @@ -54,7 +54,7 @@ def _get_columns(item): class CreateTapService(command.ShowOne): - _description = _("Create a tap service") + _description = _("Create a new tap service.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) @@ -65,7 +65,7 @@ def get_parser(self, prog_name): dest='port_id', required=True, metavar="PORT", - help=_('Port to which the Tap service is connected.'), + help=_('Port (name or ID) to connect to the tap service.'), ) return parser @@ -94,7 +94,7 @@ def take_action(self, parsed_args): class ListTapService(command.Lister): - _description = _("List tap services that belong to a given project") + _description = _("List tap services.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) @@ -122,14 +122,14 @@ def take_action(self, parsed_args): class ShowTapService(command.ShowOne): - _description = _("Show information of a given tap service") + _description = _("Show tap service details.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) parser.add_argument( TAP_SERVICE, metavar=f"<{TAP_SERVICE}>", - help=_("ID or name of tap service to look up."), + help=_("Tap service to display (name or ID)."), ) return parser @@ -145,7 +145,7 @@ def take_action(self, parsed_args): class DeleteTapService(command.Command): - _description = _("Delete a tap service") + _description = _("Delete a tap service.") def get_parser(self, prog_name): parser = super().get_parser(prog_name) @@ -153,7 +153,7 @@ def get_parser(self, prog_name): TAP_SERVICE, metavar=f"<{TAP_SERVICE}>", nargs="+", - help=_("ID(s) or name(s) of tap service to delete."), + help=_("Tap service to delete (name or ID)."), ) return parser @@ -191,7 +191,7 @@ def get_parser(self, prog_name): parser.add_argument( TAP_SERVICE, metavar=f"<{TAP_SERVICE}>", - help=_("ID or name of tap service to update."), + help=_("Tap service to modify (name or ID)."), ) _add_updatable_args(parser) return parser From 92a277ff4cb5cdf6caed2d16777e6eb0e7474511 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Thu, 11 Dec 2025 02:42:46 +0900 Subject: [PATCH 51/67] ruff: Enable E5 check ... to enforce maximum line length, to keep consistent code format. Note that E501 check is disabled in test code now, until we decide how to update ~50 lines violating the limit due to too long names. Change-Id: I122c8b9035d6381dafb34438517c26b01e5201f5 Signed-off-by: Takashi Kajinami --- hacking/checks.py | 6 +++--- openstackclient/api/object_store_v1.py | 5 ++++- openstackclient/identity/common.py | 3 ++- openstackclient/identity/v3/group.py | 5 +++-- openstackclient/identity/v3/user.py | 6 ++++-- openstackclient/image/v2/metadef_properties.py | 3 ++- openstackclient/volume/v2/volume_type.py | 12 ++++++++---- openstackclient/volume/v3/volume_attachment.py | 2 +- openstackclient/volume/v3/volume_type.py | 18 ++++++++++++------ pyproject.toml | 4 ++-- 10 files changed, 41 insertions(+), 23 deletions(-) diff --git a/hacking/checks.py b/hacking/checks.py index facb0cc6a..0eb485e7e 100644 --- a/hacking/checks.py +++ b/hacking/checks.py @@ -69,8 +69,8 @@ def assert_no_duplicated_setup(logical_line, filename): return yield ( 0, - f"O401: client_manager.{service} mocks are already provided by " - f"the {service} service's FakeClientMixin class", + f"O401: client_manager.{service} mocks are already provided " + f"by the {service} service's FakeClientMixin class", ) @@ -89,7 +89,7 @@ def assert_use_of_client_aliases(logical_line): yield (0, f"0402: prefer {service}_client to sdk_connection.{service}") if match := re.match( - r'(self\.app\.client_manager\.(compute|network|image)+\.[a-z_]+) = mock.Mock', + r'(self\.app\.client_manager\.(compute|network|image)+\.[a-z_]+) = mock.Mock', # noqa: E501 logical_line, ): yield ( diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index fd941e483..933b01b83 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -256,7 +256,10 @@ def object_create( # object's name in the container. object_name_str = name if name else object - full_url = f"{urllib.parse.quote(container)}/{urllib.parse.quote(object_name_str)}" + full_url = ( + f"{urllib.parse.quote(container)}/" + f"{urllib.parse.quote(object_name_str)}" + ) with open(object, 'rb') as f: response = self.create( full_url, diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index c6e6cfca2..068476470 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -99,7 +99,8 @@ def find_service_sdk(identity_client, name_type_or_id): if next(services, None): msg = _( - "Multiple service matches found for '%(query)s', use an ID to be more specific." + "Multiple service matches found for '%(query)s', " + "use an ID to be more specific." ) % {"query": name_type_or_id} raise exceptions.CommandError(msg) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 2e3d9f9b0..a1dff6ee4 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -315,8 +315,9 @@ def take_action(self, parsed_args): parsed_args.user_domain, ) if domain: - # NOTE(0weng): The API doesn't actually support filtering additionally by domain_id, - # so this doesn't really do anything. + # NOTE(0weng): The API doesn't actually support filtering + # additionally by domain_id, so this doesn't really do + # anything. data = identity_client.user_groups(user, domain_id=domain) else: data = identity_client.user_groups(user) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 6b3b7217e..c6c759096 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -420,7 +420,8 @@ def get_parser(self, prog_name): dest='is_enabled', default=None, help=_( - 'List only enabled users, does nothing with --project and --group' + 'List only enabled users, does nothing with ' + '--project and --group' ), ) parser.add_argument( @@ -429,7 +430,8 @@ def get_parser(self, prog_name): dest='is_enabled', default=None, help=_( - 'List only disabled users, does nothing with --project and --group' + 'List only disabled users, does nothing with ' + '--project and --group' ), ) return parser diff --git a/openstackclient/image/v2/metadef_properties.py b/openstackclient/image/v2/metadef_properties.py index 602777331..440d4010a 100644 --- a/openstackclient/image/v2/metadef_properties.py +++ b/openstackclient/image/v2/metadef_properties.py @@ -127,7 +127,8 @@ def get_parser(self, prog_name): nargs="*", help=_( "Metadef properties to delete (name) " - "(omit this argument to delete all properties in the namespace)" + "(omit this argument to delete all properties " + "in the namespace)" ), ) return parser diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 9e2ac20cb..8df39d654 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -171,7 +171,8 @@ def get_parser(self, prog_name): default=False, help=_( "Enabled replication for this volume type " - "(this is an alias for '--property replication_enabled= True') " + "(this is an alias for " + "'--property replication_enabled= True') " "(requires driver support)" ), ) @@ -181,7 +182,8 @@ def get_parser(self, prog_name): dest='availability_zones', help=_( "Set an availability zone for this volume type " - "(this is an alias for '--property RESKEY:availability_zones:') " + "(this is an alias for " + "'--property RESKEY:availability_zones:') " "(repeat option to set multiple availability zones)" ), ) @@ -534,7 +536,8 @@ def get_parser(self, prog_name): default=False, help=_( "Enabled replication for this volume type " - "(this is an alias for '--property replication_enabled= True') " + "(this is an alias for " + "'--property replication_enabled= True') " "(requires driver support)" ), ) @@ -544,7 +547,8 @@ def get_parser(self, prog_name): dest='availability_zones', help=_( "Set an availability zone for this volume type " - "(this is an alias for '--property RESKEY:availability_zones:') " + "(this is an alias for " + "'--property RESKEY:availability_zones:') " "(repeat option to set multiple availability zones)" ), ) diff --git a/openstackclient/volume/v3/volume_attachment.py b/openstackclient/volume/v3/volume_attachment.py index 5773a121c..6d36e63fb 100644 --- a/openstackclient/volume/v3/volume_attachment.py +++ b/openstackclient/volume/v3/volume_attachment.py @@ -458,7 +458,7 @@ def take_action(self, parsed_args): } # Update search option with `filters` # if AppendFilters.filters: - # search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) + # search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) # noqa: E501 # TODO(stephenfin): Implement sorting attachments = volume_client.attachments( diff --git a/openstackclient/volume/v3/volume_type.py b/openstackclient/volume/v3/volume_type.py index b5b328cb8..bf193ec60 100644 --- a/openstackclient/volume/v3/volume_type.py +++ b/openstackclient/volume/v3/volume_type.py @@ -172,7 +172,8 @@ def get_parser(self, prog_name): default=False, help=_( "Enabled replication for this volume type " - "(this is an alias for '--property replication_enabled= True') " + "(this is an alias for " + "'--property replication_enabled= True') " "(requires driver support)" ), ) @@ -182,7 +183,8 @@ def get_parser(self, prog_name): dest='availability_zones', help=_( "Set an availability zone for this volume type " - "(this is an alias for '--property RESKEY:availability_zones:') " + "(this is an alias for " + "'--property RESKEY:availability_zones:') " "(repeat option to set multiple availability zones)" ), ) @@ -447,7 +449,8 @@ def get_parser(self, prog_name): default=False, help=_( "List only volume types with replication enabled " - "(this is an alias for '--property replication_enabled= True') " + "(this is an alias for " + "'--property replication_enabled= True') " "(supported by --os-volume-api-version 3.52 or above)" ), ) @@ -457,7 +460,8 @@ def get_parser(self, prog_name): dest='availability_zones', help=_( "List only volume types with this availability configured " - "(this is an alias for '--property RESKEY:availability_zones:') " + "(this is an alias for " + "'--property RESKEY:availability_zones:') " "(repeat option to filter on multiple availability zones)" ), ) @@ -616,7 +620,8 @@ def get_parser(self, prog_name): default=False, help=_( "Enabled replication for this volume type " - "(this is an alias for '--property replication_enabled= True') " + "(this is an alias for " + "'--property replication_enabled= True') " "(requires driver support)" ), ) @@ -626,7 +631,8 @@ def get_parser(self, prog_name): dest='availability_zones', help=_( "Set an availability zone for this volume type " - "(this is an alias for '--property RESKEY:availability_zones:') " + "(this is an alias for " + "'--property RESKEY:availability_zones:') " "(repeat option to set multiple availability zones)" ), ) diff --git a/pyproject.toml b/pyproject.toml index aeb5ab495..566289a7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -749,7 +749,7 @@ quote-style = "preserve" docstring-code-format = true [tool.ruff.lint] -select = ["E4", "E7", "E9", "F", "S", "UP"] +select = ["E4", "E5", "E7", "E9", "F", "S", "UP"] [tool.ruff.lint.per-file-ignores] -"openstackclient/tests/*" = ["S"] +"openstackclient/tests/*" = ["E501", "S"] From 060299c749ce455937111e83494f6f20fa559f64 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 24 Nov 2025 12:28:26 +0000 Subject: [PATCH 52/67] Implement conflict resolution Take advantage of functionality recently introduced in cliff to allow us to prefer commands that are in-tree over those provided via plugin packages. This will allow us to move the extensions themselves in-tree. Change-Id: I5dd9bc9743bea779ea1b4a71264c9a77c80033b3 Signed-off-by: Stephen Finucane --- openstackclient/shell.py | 13 +++++++++++-- requirements.txt | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 6bbbb5f7b..dfc559a04 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -26,16 +26,25 @@ import openstackclient from openstackclient.common import clientmanager - DEFAULT_DOMAIN = 'default' +# list of modules that were originally out-of-tree and are now in +# core OSC +IGNORED_MODULES = ( + 'neutron_taas.taas_client.osc', + 'neutronclient.osc.v2.taas', +) class OpenStackShell(shell.OpenStackShell): def __init__(self): + command_manager = commandmanager.CommandManager( + 'openstack.cli', ignored_modules=IGNORED_MODULES + ) + super().__init__( description=__doc__.strip(), version=openstackclient.__version__, - command_manager=commandmanager.CommandManager('openstack.cli'), + command_manager=command_manager, deferred_help=True, ) diff --git a/requirements.txt b/requirements.txt index 9a9c79a98..fc31d7820 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 cryptography>=2.7 # BSD/Apache-2.0 -cliff>=4.8.0 # Apache-2.0 +cliff>=4.13.0 # Apache-2.0 iso8601>=0.1.11 # MIT openstacksdk>=4.6.0 # Apache-2.0 osc-lib>=2.3.0 # Apache-2.0 From dedc1a342c861a6061e51e135a53c8ecb8a5db45 Mon Sep 17 00:00:00 2001 From: Miro Tomaska Date: Thu, 6 Nov 2025 16:11:59 -0500 Subject: [PATCH 53/67] Use openstacksdk test generate_fake_resources factory Instead of building fake test objects in the local fakes.py file, use existing generate_fake_resource(s) factory methods to automatically populate class attributes. Doing this ensures that fake objects are always build with actual attributes of the class. Change-Id: If424b87c79e7dab102cbd8a7938df85411c9465d Signed-off-by: Miro Tomaska --- openstackclient/network/v2/taas/tap_flow.py | 4 +- openstackclient/network/v2/taas/tap_mirror.py | 2 +- .../network/v2/taas/tap_service.py | 2 +- .../tests/unit/network/v2/taas/fakes.py | 118 ------------------ .../unit/network/v2/taas/test_osc_tap_flow.py | 63 +++++----- .../network/v2/taas/test_osc_tap_mirror.py | 33 ++--- .../network/v2/taas/test_osc_tap_service.py | 33 ++--- 7 files changed, 70 insertions(+), 185 deletions(-) delete mode 100644 openstackclient/tests/unit/network/v2/taas/fakes.py diff --git a/openstackclient/network/v2/taas/tap_flow.py b/openstackclient/network/v2/taas/tap_flow.py index 441f559bf..92037f037 100644 --- a/openstackclient/network/v2/taas/tap_flow.py +++ b/openstackclient/network/v2/taas/tap_flow.py @@ -101,12 +101,12 @@ def take_action(self, parsed_args): if parsed_args.port is not None: source_port = client.find_port( parsed_args.port, ignore_missing=False - )['id'] + ).id attrs['source_port'] = source_port if parsed_args.tap_service is not None: tap_service_id = client.find_tap_service( parsed_args.tap_service, ignore_missing=False - )['id'] + ).id attrs['tap_service_id'] = tap_service_id if parsed_args.direction is not None: attrs['direction'] = parsed_args.direction diff --git a/openstackclient/network/v2/taas/tap_mirror.py b/openstackclient/network/v2/taas/tap_mirror.py index a11ea91d9..4d0937787 100644 --- a/openstackclient/network/v2/taas/tap_mirror.py +++ b/openstackclient/network/v2/taas/tap_mirror.py @@ -99,7 +99,7 @@ def take_action(self, parsed_args): if parsed_args.port_id is not None: port_id = client.find_port( parsed_args.port_id, ignore_missing=False - )['id'] + ).id attrs['port_id'] = port_id if parsed_args.directions is not None: attrs['directions'] = parsed_args.directions diff --git a/openstackclient/network/v2/taas/tap_service.py b/openstackclient/network/v2/taas/tap_service.py index 392d76c51..8651a9d73 100644 --- a/openstackclient/network/v2/taas/tap_service.py +++ b/openstackclient/network/v2/taas/tap_service.py @@ -79,7 +79,7 @@ def take_action(self, parsed_args): if parsed_args.port_id is not None: port_id = client.find_port( parsed_args.port_id, ignore_missing=False - )['id'] + ).id attrs['port_id'] = port_id if 'project' in parsed_args and parsed_args.project is not None: attrs['project_id'] = common.find_project( diff --git a/openstackclient/tests/unit/network/v2/taas/fakes.py b/openstackclient/tests/unit/network/v2/taas/fakes.py deleted file mode 100644 index 0480a0d90..000000000 --- a/openstackclient/tests/unit/network/v2/taas/fakes.py +++ /dev/null @@ -1,118 +0,0 @@ -# All Rights Reserved 2020 -# -# 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 copy - -from oslo_utils import uuidutils - - -class FakeTapService: - @staticmethod - def create_tap_service(attrs=None): - """Create a fake tap service.""" - attrs = attrs or {} - tap_service_attrs = { - 'id': uuidutils.generate_uuid(), - 'tenant_id': uuidutils.generate_uuid(), - 'name': 'test_tap_service' + uuidutils.generate_uuid(), - 'status': 'ACTIVE', - } - tap_service_attrs.update(attrs) - return copy.deepcopy(tap_service_attrs) - - @staticmethod - def create_tap_services(attrs=None, count=1): - """Create multiple fake tap services.""" - - tap_services = [] - for i in range(0, count): - if attrs is None: - attrs = {'id': f'fake_id{i}'} - elif getattr(attrs, 'id', None) is None: - attrs['id'] = f'fake_id{i}' - tap_services.append(FakeTapService.create_tap_service(attrs=attrs)) - - return tap_services - - -class FakeTapFlow: - @staticmethod - def create_tap_flow(attrs=None): - """Create a fake tap service.""" - attrs = attrs or {} - tap_flow_attrs = { - 'id': uuidutils.generate_uuid(), - 'tenant_id': uuidutils.generate_uuid(), - 'name': 'test_tap_flow' + uuidutils.generate_uuid(), - 'status': 'ACTIVE', - 'direction': 'BOTH', - } - tap_flow_attrs.update(attrs) - return copy.deepcopy(tap_flow_attrs) - - @staticmethod - def create_tap_flows(attrs=None, count=1): - """Create multiple fake tap flows.""" - - tap_flows = [] - for i in range(0, count): - if attrs is None: - attrs = { - 'id': f'fake_id{i}', - 'source_port': uuidutils.generate_uuid(), - 'tap_service_id': uuidutils.generate_uuid(), - } - elif getattr(attrs, 'id', None) is None: - attrs['id'] = f'fake_id{i}' - tap_flows.append(FakeTapFlow.create_tap_flow(attrs=attrs)) - - return tap_flows - - -class FakeTapMirror: - @staticmethod - def create_tap_mirror(attrs=None): - """Create a fake tap mirror.""" - attrs = attrs or {} - tap_mirror_attrs = { - 'id': uuidutils.generate_uuid(), - 'tenant_id': uuidutils.generate_uuid(), - 'name': 'test_tap_mirror' + uuidutils.generate_uuid(), - 'port_id': uuidutils.generate_uuid(), - 'directions': 'IN=99', - 'remote_ip': '192.10.10.2', - 'mirror_type': 'gre', - } - tap_mirror_attrs.update(attrs) - return copy.deepcopy(tap_mirror_attrs) - - @staticmethod - def create_tap_mirrors(attrs=None, count=1): - """Create multiple fake tap mirrors.""" - - tap_mirrors = [] - for i in range(0, count): - if attrs is None: - attrs = { - 'id': f'fake_id{i}', - 'port_id': uuidutils.generate_uuid(), - 'name': f'test_tap_mirror_{i}', - 'directions': f'IN={99 + i}', - 'remote_ip': f'192.10.10.{i + 3}', - } - elif getattr(attrs, 'id', None) is None: - attrs['id'] = f'fake_id{i}' - tap_mirrors.append(FakeTapMirror.create_tap_mirror(attrs=attrs)) - - return tap_mirrors diff --git a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_flow.py b/openstackclient/tests/unit/network/v2/taas/test_osc_tap_flow.py index e401a0f97..8e4f185c8 100644 --- a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_flow.py +++ b/openstackclient/tests/unit/network/v2/taas/test_osc_tap_flow.py @@ -17,13 +17,14 @@ import uuid from openstack.network.v2 import tap_flow as _tap_flow +from openstack.network.v2 import tap_service as _tap_service +from openstack.test import fakes as sdk_fakes from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util from openstackclient.network.v2.taas import tap_flow as osc_tap_flow from openstackclient.network.v2.taas import tap_service as osc_tap_service from openstackclient.tests.unit.network.v2 import fakes as network_fakes -from openstackclient.tests.unit.network.v2.taas import fakes columns_long = tuple( @@ -47,9 +48,11 @@ def _get_data(attrs, columns=sorted_columns): class TestCreateTapFlow(network_fakes.TestNetworkV2): columns = ( + 'description', 'direction', 'id', 'name', + 'project_id', 'source_port', 'status', 'tap_service_id', @@ -61,22 +64,23 @@ def setUp(self): def test_create_tap_flow(self): """Test Create Tap Flow.""" - fake_tap_service = fakes.FakeTapService.create_tap_service( - attrs={'port_id': str(uuid.uuid4())} + fake_tap_service = sdk_fakes.generate_fake_resource( + _tap_service.TapService ) port_id = str(uuid.uuid4()) - fake_tap_flow = fakes.FakeTapFlow.create_tap_flow( - attrs={ + fake_port = network_fakes.create_one_port(attrs={'id': port_id}) + fake_tap_flow = sdk_fakes.generate_fake_resource( + _tap_flow.TapFlow, + **{ 'source_port': port_id, 'tap_service_id': fake_tap_service['id'], - } + 'direction': 'BOTH', + }, ) self.app.client_manager.network.create_tap_flow.return_value = ( fake_tap_flow ) - self.app.client_manager.network.find_port.return_value = { - 'id': port_id - } + self.app.client_manager.network.find_port.return_value = fake_port self.app.client_manager.network.find_tap_service.return_value = ( fake_tap_service ) @@ -122,12 +126,8 @@ def setUp(self): def test_list_tap_flows(self): """Test List Tap Flow.""" - fake_tap_flows = fakes.FakeTapFlow.create_tap_flows( - attrs={ - 'source_port': str(uuid.uuid4()), - 'tap_service_id': str(uuid.uuid4()), - }, - count=2, + fake_tap_flows = list( + sdk_fakes.generate_fake_resources(_tap_flow.TapFlow, count=2) ) self.app.client_manager.network.tap_flows.return_value = fake_tap_flows arg_list = [] @@ -159,13 +159,7 @@ def setUp(self): def test_delete_tap_flow(self): """Test Delete tap flow.""" - fake_tap_flow = fakes.FakeTapFlow.create_tap_flow( - attrs={ - 'source_port': str(uuid.uuid4()), - 'tap_service_id': str(uuid.uuid4()), - } - ) - + fake_tap_flow = sdk_fakes.generate_fake_resource(_tap_flow.TapFlow) arg_list = [ fake_tap_flow['id'], ] @@ -184,9 +178,11 @@ def test_delete_tap_flow(self): class TestShowTapFlow(network_fakes.TestNetworkV2): columns = ( + 'description', 'direction', 'id', 'name', + 'project_id', 'source_port', 'status', 'tap_service_id', @@ -201,12 +197,7 @@ def setUp(self): def test_show_tap_flow(self): """Test Show tap flow.""" - fake_tap_flow = fakes.FakeTapFlow.create_tap_flow( - attrs={ - 'source_port': str(uuid.uuid4()), - 'tap_service_id': str(uuid.uuid4()), - } - ) + fake_tap_flow = sdk_fakes.generate_fake_resource(_tap_flow.TapFlow) self.app.client_manager.network.get_tap_flow.return_value = ( fake_tap_flow ) @@ -234,12 +225,19 @@ def test_show_tap_flow(self): class TestUpdateTapFlow(network_fakes.TestNetworkV2): _new_name = 'new_name' + # NOTE(mtomaska): The Resource class from which TapFlow inherits from + # returns duplicate `ID and `Name` keys. columns = ( 'Direction', 'ID', + 'ID', + 'Name', 'Name', 'Status', 'Tenant', + 'description', + 'location', + 'project_id', 'source_port', 'tap_service_id', ) @@ -253,12 +251,7 @@ def setUp(self): def test_update_tap_flow(self): """Test update tap service""" - fake_tap_flow = fakes.FakeTapFlow.create_tap_flow( - attrs={ - 'source_port': str(uuid.uuid4()), - 'tap_service_id': str(uuid.uuid4()), - } - ) + fake_tap_flow = sdk_fakes.generate_fake_resource(_tap_flow.TapFlow) new_tap_flow = copy.deepcopy(fake_tap_flow) new_tap_flow['name'] = self._new_name @@ -280,4 +273,4 @@ def test_update_tap_flow(self): mock_update_t_f = self.app.client_manager.network.update_tap_flow mock_update_t_f.assert_called_once_with(new_tap_flow['id'], **attrs) self.assertEqual(self.columns, columns) - self.assertEqual(_get_data(new_tap_flow), data) + self.assertEqual(_get_data(new_tap_flow, self.columns), data) diff --git a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_mirror.py b/openstackclient/tests/unit/network/v2/taas/test_osc_tap_mirror.py index 451d6a25b..10f3251c3 100644 --- a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_mirror.py +++ b/openstackclient/tests/unit/network/v2/taas/test_osc_tap_mirror.py @@ -15,12 +15,12 @@ import uuid from openstack.network.v2 import tap_mirror +from openstack.test import fakes as sdk_fakes from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util from openstackclient.network.v2.taas import tap_mirror as osc_tap_mirror from openstackclient.tests.unit.network.v2 import fakes as network_fakes -from openstackclient.tests.unit.network.v2.taas import fakes columns_long = tuple( @@ -44,11 +44,13 @@ def _get_data(attrs, columns=sorted_columns): class TestCreateTapMirror(network_fakes.TestNetworkV2): columns = ( + 'description', 'directions', 'id', 'mirror_type', 'name', 'port_id', + 'project_id', 'remote_ip', ) @@ -58,15 +60,14 @@ def setUp(self): def test_create_tap_mirror(self): port_id = str(uuid.uuid4()) - fake_tap_mirror = fakes.FakeTapMirror.create_tap_mirror( - attrs={'port_id': port_id} + fake_port = network_fakes.create_one_port(attrs={'id': port_id}) + fake_tap_mirror = sdk_fakes.generate_fake_resource( + tap_mirror.TapMirror, **{'port_id': port_id, 'directions': 'IN=99'} ) self.app.client_manager.network.create_tap_mirror.return_value = ( fake_tap_mirror ) - self.app.client_manager.network.find_port.return_value = { - 'id': port_id - } + self.app.client_manager.network.find_port.return_value = fake_port self.app.client_manager.network.find_tap_mirror.side_effect = ( lambda _, name_or_id: {'id': name_or_id} ) @@ -124,8 +125,8 @@ def setUp(self): def test_list_tap_mirror(self): """Test List Tap Mirror.""" - fake_tap_mirrors = fakes.FakeTapMirror.create_tap_mirrors( - attrs={'port_id': str(uuid.uuid4())}, count=4 + fake_tap_mirrors = list( + sdk_fakes.generate_fake_resources(tap_mirror.TapMirror, count=4) ) self.app.client_manager.network.tap_mirrors.return_value = ( fake_tap_mirrors @@ -162,8 +163,8 @@ def setUp(self): def test_delete_tap_mirror(self): """Test Delete Tap Mirror.""" - fake_tap_mirror = fakes.FakeTapMirror.create_tap_mirror( - attrs={'port_id': str(uuid.uuid4())} + fake_tap_mirror = sdk_fakes.generate_fake_resource( + tap_mirror.TapMirror ) arg_list = [ @@ -183,11 +184,13 @@ def test_delete_tap_mirror(self): class TestShowTapMirror(network_fakes.TestNetworkV2): columns = ( + 'description', 'directions', 'id', 'mirror_type', 'name', 'port_id', + 'project_id', 'remote_ip', ) @@ -203,8 +206,8 @@ def setUp(self): def test_show_tap_mirror(self): """Test Show Tap Mirror.""" - fake_tap_mirror = fakes.FakeTapMirror.create_tap_mirror( - attrs={'port_id': str(uuid.uuid4())} + fake_tap_mirror = sdk_fakes.generate_fake_resource( + tap_mirror.TapMirror ) self.app.client_manager.network.get_tap_mirror.return_value = ( fake_tap_mirror @@ -232,11 +235,13 @@ def test_show_tap_mirror(self): class TestUpdateTapMirror(network_fakes.TestNetworkV2): _new_name = 'new_name' columns = ( + 'description', 'directions', 'id', 'mirror_type', 'name', 'port_id', + 'project_id', 'remote_ip', ) @@ -251,8 +256,8 @@ def setUp(self): def test_update_tap_mirror(self): """Test update Tap Mirror""" - fake_tap_mirror = fakes.FakeTapMirror.create_tap_mirror( - attrs={'port_id': str(uuid.uuid4())} + fake_tap_mirror = sdk_fakes.generate_fake_resource( + tap_mirror.TapMirror ) new_tap_mirror = copy.deepcopy(fake_tap_mirror) new_tap_mirror['name'] = self._new_name diff --git a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_service.py b/openstackclient/tests/unit/network/v2/taas/test_osc_tap_service.py index 0b8180151..fa766891e 100644 --- a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_service.py +++ b/openstackclient/tests/unit/network/v2/taas/test_osc_tap_service.py @@ -17,12 +17,12 @@ import uuid from openstack.network.v2 import tap_service +from openstack.test import fakes as sdk_fakes from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util from openstackclient.network.v2.taas import tap_service as osc_tap_service from openstackclient.tests.unit.network.v2 import fakes as network_fakes -from openstackclient.tests.unit.network.v2.taas import fakes columns_long = tuple( @@ -46,9 +46,11 @@ def _get_data(attrs, columns=sorted_columns): class TestCreateTapService(network_fakes.TestNetworkV2): columns = ( + 'description', 'id', 'name', 'port_id', + 'project_id', 'status', ) @@ -59,15 +61,14 @@ def setUp(self): def test_create_tap_service(self): """Test Create Tap Service.""" port_id = str(uuid.uuid4()) - fake_tap_service = fakes.FakeTapService.create_tap_service( - attrs={'port_id': port_id} + fake_port = network_fakes.create_one_port(attrs={'id': port_id}) + fake_tap_service = sdk_fakes.generate_fake_resource( + tap_service.TapService, **{'port_id': port_id} ) self.app.client_manager.network.create_tap_service.return_value = ( fake_tap_service ) - self.app.client_manager.network.find_port.return_value = { - 'id': port_id - } + self.app.client_manager.network.find_port.return_value = fake_port self.app.client_manager.network.find_tap_service.side_effect = ( lambda _, name_or_id: {'id': name_or_id} ) @@ -110,8 +111,8 @@ def setUp(self): def test_list_tap_service(self): """Test List Tap Service.""" - fake_tap_services = fakes.FakeTapService.create_tap_services( - attrs={'port_id': str(uuid.uuid4())}, count=4 + fake_tap_services = list( + sdk_fakes.generate_fake_resources(tap_service.TapService, count=4) ) self.app.client_manager.network.tap_services.return_value = ( fake_tap_services @@ -148,8 +149,8 @@ def setUp(self): def test_delete_tap_service(self): """Test Delete tap service.""" - fake_tap_service = fakes.FakeTapService.create_tap_service( - attrs={'port_id': str(uuid.uuid4())} + fake_tap_service = sdk_fakes.generate_fake_resource( + tap_service.TapService ) arg_list = [ @@ -169,9 +170,11 @@ def test_delete_tap_service(self): class TestShowTapService(network_fakes.TestNetworkV2): columns = ( + 'description', 'id', 'name', 'port_id', + 'project_id', 'status', ) @@ -187,8 +190,8 @@ def setUp(self): def test_show_tap_service(self): """Test Show tap service.""" - fake_tap_service = fakes.FakeTapService.create_tap_service( - attrs={'port_id': str(uuid.uuid4())} + fake_tap_service = sdk_fakes.generate_fake_resource( + tap_service.TapService ) self.app.client_manager.network.get_tap_service.return_value = ( fake_tap_service @@ -217,9 +220,11 @@ class TestUpdateTapService(network_fakes.TestNetworkV2): _new_name = 'new_name' columns = ( + 'description', 'id', 'name', 'port_id', + 'project_id', 'status', ) @@ -234,8 +239,8 @@ def setUp(self): def test_update_tap_service(self): """Test update tap service""" - fake_tap_service = fakes.FakeTapService.create_tap_service( - attrs={'port_id': str(uuid.uuid4())} + fake_tap_service = sdk_fakes.generate_fake_resource( + tap_service.TapService ) new_tap_service = copy.deepcopy(fake_tap_service) new_tap_service['name'] = self._new_name From 3fbe41cd52cd140c4a6382dea155ff35bf86d40d Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 21 Nov 2025 16:23:37 +0000 Subject: [PATCH 54/67] clientmanager: Remove legacy cruft No has used Initialize functions in years, while the _auth_required attribute has long since been handled by the base class in osc-lib. Change-Id: I3af9a6d8c339b2170a13346b009392d51e044443 Signed-off-by: Stephen Finucane --- openstackclient/common/clientmanager.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index a0455bf1a..94c25e3f9 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -40,12 +40,6 @@ class ClientManager(clientmanager.ClientManager): in osc-lib so we need to maintain a transition period. """ - # A simple incrementing version for the plugin to know what is available - PLUGIN_INTERFACE_VERSION = "2" - - # Let the commands set this - _auth_required = False - def __init__( self, cli_options=None, @@ -185,9 +179,6 @@ def get_plugin_modules(group): continue mod_list.append(module) - init_func = getattr(module, 'Initialize', None) - if init_func: - init_func('x') # Add the plugin to the ClientManager setattr( From 3cd544df53e7c282092512dc7380840ac254a5c5 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 11 Dec 2025 13:42:39 +0000 Subject: [PATCH 55/67] Add custom command classes These are effectively identical to the osc-lib variants except they include the attributes that the OSC shell implementation will set on this during shell init. This helps from a typing perspective. Change-Id: I53d9058273748ecd4d4eecec5f7291d5f38ce5ab Signed-off-by: Stephen Finucane --- openstackclient/command.py | 27 +++++++++++++++++++ openstackclient/common/availability_zone.py | 2 +- openstackclient/common/configuration.py | 2 +- openstackclient/common/extension.py | 2 +- openstackclient/common/limits.py | 2 +- openstackclient/common/module.py | 2 +- openstackclient/common/project_cleanup.py | 2 +- openstackclient/common/quota.py | 2 +- openstackclient/common/versions.py | 3 +-- openstackclient/compute/v2/agent.py | 2 +- openstackclient/compute/v2/aggregate.py | 2 +- openstackclient/compute/v2/console.py | 2 +- .../compute/v2/console_connection.py | 2 +- openstackclient/compute/v2/flavor.py | 2 +- openstackclient/compute/v2/host.py | 2 +- openstackclient/compute/v2/hypervisor.py | 2 +- .../compute/v2/hypervisor_stats.py | 2 +- openstackclient/compute/v2/keypair.py | 2 +- openstackclient/compute/v2/server.py | 2 +- openstackclient/compute/v2/server_backup.py | 2 +- openstackclient/compute/v2/server_event.py | 2 +- openstackclient/compute/v2/server_group.py | 2 +- openstackclient/compute/v2/server_image.py | 2 +- .../compute/v2/server_migration.py | 2 +- openstackclient/compute/v2/server_volume.py | 2 +- openstackclient/compute/v2/service.py | 2 +- openstackclient/compute/v2/usage.py | 2 +- openstackclient/identity/v2_0/catalog.py | 2 +- openstackclient/identity/v2_0/ec2creds.py | 2 +- openstackclient/identity/v2_0/endpoint.py | 2 +- openstackclient/identity/v2_0/project.py | 2 +- openstackclient/identity/v2_0/role.py | 2 +- .../identity/v2_0/role_assignment.py | 2 +- openstackclient/identity/v2_0/service.py | 2 +- openstackclient/identity/v2_0/token.py | 2 +- openstackclient/identity/v2_0/user.py | 2 +- openstackclient/identity/v3/access_rule.py | 2 +- .../identity/v3/application_credential.py | 2 +- openstackclient/identity/v3/catalog.py | 2 +- openstackclient/identity/v3/consumer.py | 2 +- openstackclient/identity/v3/credential.py | 2 +- openstackclient/identity/v3/domain.py | 2 +- openstackclient/identity/v3/ec2creds.py | 2 +- openstackclient/identity/v3/endpoint.py | 2 +- openstackclient/identity/v3/endpoint_group.py | 2 +- .../identity/v3/federation_protocol.py | 2 +- openstackclient/identity/v3/group.py | 2 +- .../identity/v3/identity_provider.py | 2 +- openstackclient/identity/v3/implied_role.py | 2 +- openstackclient/identity/v3/limit.py | 2 +- openstackclient/identity/v3/mapping.py | 2 +- openstackclient/identity/v3/policy.py | 2 +- openstackclient/identity/v3/project.py | 2 +- openstackclient/identity/v3/region.py | 2 +- .../identity/v3/registered_limit.py | 2 +- openstackclient/identity/v3/role.py | 2 +- .../identity/v3/role_assignment.py | 3 +-- openstackclient/identity/v3/service.py | 2 +- .../identity/v3/service_provider.py | 2 +- openstackclient/identity/v3/token.py | 2 +- openstackclient/identity/v3/trust.py | 2 +- openstackclient/identity/v3/unscoped_saml.py | 2 +- openstackclient/identity/v3/user.py | 2 +- openstackclient/image/v1/image.py | 2 +- openstackclient/image/v2/cache.py | 2 +- openstackclient/image/v2/image.py | 2 +- openstackclient/image/v2/info.py | 2 +- .../image/v2/metadef_namespaces.py | 2 +- openstackclient/image/v2/metadef_objects.py | 2 +- .../image/v2/metadef_properties.py | 2 +- .../v2/metadef_resource_type_association.py | 2 +- .../image/v2/metadef_resource_types.py | 2 +- openstackclient/image/v2/task.py | 2 +- openstackclient/network/common.py | 12 +++++---- openstackclient/network/v2/address_group.py | 2 +- openstackclient/network/v2/address_scope.py | 2 +- .../network/v2/default_security_group_rule.py | 2 +- .../network/v2/floating_ip_port_forwarding.py | 2 +- openstackclient/network/v2/ip_availability.py | 2 +- .../network/v2/l3_conntrack_helper.py | 2 +- openstackclient/network/v2/local_ip.py | 2 +- .../network/v2/local_ip_association.py | 2 +- openstackclient/network/v2/ndp_proxy.py | 2 +- openstackclient/network/v2/network_agent.py | 2 +- .../v2/network_auto_allocated_topology.py | 2 +- openstackclient/network/v2/network_flavor.py | 2 +- .../network/v2/network_flavor_profile.py | 2 +- openstackclient/network/v2/network_meter.py | 2 +- .../network/v2/network_meter_rule.py | 2 +- .../network/v2/network_qos_policy.py | 2 +- .../network/v2/network_qos_rule.py | 2 +- .../network/v2/network_qos_rule_type.py | 2 +- openstackclient/network/v2/network_rbac.py | 2 +- openstackclient/network/v2/network_segment.py | 2 +- .../network/v2/network_segment_range.py | 4 +-- .../network/v2/network_service_provider.py | 2 +- openstackclient/network/v2/network_trunk.py | 2 +- openstackclient/network/v2/port.py | 2 +- openstackclient/network/v2/router.py | 2 +- openstackclient/network/v2/security_group.py | 2 +- openstackclient/network/v2/subnet.py | 2 +- openstackclient/network/v2/subnet_pool.py | 2 +- openstackclient/object/v1/account.py | 2 +- openstackclient/object/v1/container.py | 2 +- openstackclient/object/v1/object.py | 2 +- .../tests/unit/common/test_command.py | 2 +- openstackclient/volume/v2/backup_record.py | 2 +- .../volume/v2/consistency_group.py | 2 +- .../volume/v2/consistency_group_snapshot.py | 2 +- openstackclient/volume/v2/qos_specs.py | 2 +- openstackclient/volume/v2/service.py | 2 +- openstackclient/volume/v2/volume.py | 2 +- openstackclient/volume/v2/volume_backend.py | 2 +- openstackclient/volume/v2/volume_backup.py | 2 +- openstackclient/volume/v2/volume_host.py | 3 +-- openstackclient/volume/v2/volume_snapshot.py | 2 +- .../volume/v2/volume_transfer_request.py | 2 +- openstackclient/volume/v2/volume_type.py | 2 +- .../volume/v3/block_storage_cleanup.py | 2 +- .../volume/v3/block_storage_cluster.py | 2 +- .../volume/v3/block_storage_log_level.py | 2 +- .../volume/v3/block_storage_manage.py | 2 +- .../v3/block_storage_resource_filter.py | 2 +- openstackclient/volume/v3/service.py | 2 +- openstackclient/volume/v3/volume.py | 2 +- .../volume/v3/volume_attachment.py | 2 +- openstackclient/volume/v3/volume_backup.py | 2 +- openstackclient/volume/v3/volume_group.py | 2 +- .../volume/v3/volume_group_snapshot.py | 2 +- .../volume/v3/volume_group_type.py | 2 +- openstackclient/volume/v3/volume_message.py | 2 +- openstackclient/volume/v3/volume_snapshot.py | 2 +- .../volume/v3/volume_transfer_request.py | 2 +- openstackclient/volume/v3/volume_type.py | 2 +- 134 files changed, 167 insertions(+), 141 deletions(-) create mode 100644 openstackclient/command.py diff --git a/openstackclient/command.py b/openstackclient/command.py new file mode 100644 index 000000000..124d755aa --- /dev/null +++ b/openstackclient/command.py @@ -0,0 +1,27 @@ +# 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 cliff import lister +from cliff import show +from osc_lib.command import command + +from openstackclient import shell + + +class Command(command.Command): + app: shell.OpenStackShell + + +class Lister(Command, lister.Lister): ... + + +class ShowOne(Command, show.ShowOne): ... diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index 61f77cc06..6f5e4fd45 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -17,9 +17,9 @@ import logging from openstack import exceptions as sdk_exceptions -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/common/configuration.py b/openstackclient/common/configuration.py index 418f7fdd0..4637ad22b 100644 --- a/openstackclient/common/configuration.py +++ b/openstackclient/common/configuration.py @@ -14,8 +14,8 @@ """Configuration action implementations""" from keystoneauth1.loading import base -from osc_lib.command import command +from openstackclient import command from openstackclient.i18n import _ REDACTED = "" diff --git a/openstackclient/common/extension.py b/openstackclient/common/extension.py index 42c2e9b9f..3f9b257bf 100644 --- a/openstackclient/common/extension.py +++ b/openstackclient/common/extension.py @@ -17,9 +17,9 @@ import logging -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ LOG = logging.getLogger(__name__) diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index 0c9280593..6512e0fcd 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -17,9 +17,9 @@ import itertools -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index d4a5b955a..997b5bc9a 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -17,9 +17,9 @@ import sys -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/common/project_cleanup.py b/openstackclient/common/project_cleanup.py index 5b2d89c1a..444f23ec2 100644 --- a/openstackclient/common/project_cleanup.py +++ b/openstackclient/common/project_cleanup.py @@ -20,8 +20,8 @@ import typing as ty from cliff.formatters import table -from osc_lib.command import command +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 41c57e63d..efe1ce203 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -21,10 +21,10 @@ import typing as ty from openstack import exceptions as sdk_exceptions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.network import common diff --git a/openstackclient/common/versions.py b/openstackclient/common/versions.py index 662233875..dfd84e059 100644 --- a/openstackclient/common/versions.py +++ b/openstackclient/common/versions.py @@ -14,8 +14,7 @@ """Versions Action Implementation""" -from osc_lib.command import command - +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/agent.py b/openstackclient/compute/v2/agent.py index 5bc6603f4..71b68d4c6 100644 --- a/openstackclient/compute/v2/agent.py +++ b/openstackclient/compute/v2/agent.py @@ -18,10 +18,10 @@ import logging from openstack import exceptions as sdk_exceptions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index cc5817a0f..48f585b5f 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -22,10 +22,10 @@ from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index bcabcd2d8..ac8e10d99 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -16,9 +16,9 @@ """Compute v2 Console action implementations""" from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/console_connection.py b/openstackclient/compute/v2/console_connection.py index d90b2ceb9..97eb1a80e 100644 --- a/openstackclient/compute/v2/console_connection.py +++ b/openstackclient/compute/v2/console_connection.py @@ -13,9 +13,9 @@ """Compute v2 Console auth token implementations.""" -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index c0e043e8f..de3a71029 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -21,10 +21,10 @@ from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/compute/v2/host.py b/openstackclient/compute/v2/host.py index aa1d1a5eb..58023676d 100644 --- a/openstackclient/compute/v2/host.py +++ b/openstackclient/compute/v2/host.py @@ -16,9 +16,9 @@ """Host action implementations""" from openstack import exceptions as sdk_exceptions -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 0a6cc5326..9e1b265b1 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -21,10 +21,10 @@ from openstack import exceptions as sdk_exceptions from openstack import utils as sdk_utils from osc_lib.cli import format_columns -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/hypervisor_stats.py b/openstackclient/compute/v2/hypervisor_stats.py index 3b9a68614..d151b4161 100644 --- a/openstackclient/compute/v2/hypervisor_stats.py +++ b/openstackclient/compute/v2/hypervisor_stats.py @@ -13,9 +13,9 @@ """Hypervisor Stats action implementations""" -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 97dd04f4e..b7744698b 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -22,10 +22,10 @@ from cryptography.hazmat.primitives.asymmetric import ed25519 from cryptography.hazmat.primitives import serialization from openstack import utils as sdk_utils -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 500395114..250eacc77 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -28,11 +28,11 @@ from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils from openstackclient.api import compute_v2 +from openstackclient import command from openstackclient.common import envvars from openstackclient.common import pagination from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py index b0395db26..bb06f761c 100644 --- a/openstackclient/compute/v2/server_backup.py +++ b/openstackclient/compute/v2/server_backup.py @@ -17,10 +17,10 @@ import importlib -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/server_event.py b/openstackclient/compute/v2/server_event.py index 3275df993..0e1e2b463 100644 --- a/openstackclient/compute/v2/server_event.py +++ b/openstackclient/compute/v2/server_event.py @@ -22,10 +22,10 @@ import iso8601 from openstack import exceptions as sdk_exceptions from openstack import utils as sdk_utils -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 73567488c..f64460111 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -20,10 +20,10 @@ from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index cb19173bc..26dbb4ccc 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -19,10 +19,10 @@ import logging from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/server_migration.py b/openstackclient/compute/v2/server_migration.py index c356bd81f..f2ea68343 100644 --- a/openstackclient/compute/v2/server_migration.py +++ b/openstackclient/compute/v2/server_migration.py @@ -15,10 +15,10 @@ import uuid from openstack import utils as sdk_utils -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/compute/v2/server_volume.py b/openstackclient/compute/v2/server_volume.py index a27c63f53..d92d137b7 100644 --- a/openstackclient/compute/v2/server_volume.py +++ b/openstackclient/compute/v2/server_volume.py @@ -15,10 +15,10 @@ """Compute v2 Server action implementations""" from openstack import utils as sdk_utils -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 3660ddd49..b911835f8 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -18,10 +18,10 @@ import logging from openstack import utils as sdk_utils -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index d045c1efe..3fe7f419a 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -19,9 +19,9 @@ import functools from cliff import columns as cliff_columns -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index ccac5d04b..a184f97df 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -16,10 +16,10 @@ import logging from cliff import columns as cliff_columns -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/ec2creds.py b/openstackclient/identity/v2_0/ec2creds.py index 25ee8a75d..360a09024 100644 --- a/openstackclient/identity/v2_0/ec2creds.py +++ b/openstackclient/identity/v2_0/ec2creds.py @@ -18,10 +18,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/endpoint.py b/openstackclient/identity/v2_0/endpoint.py index db8efede1..38f7f4e56 100644 --- a/openstackclient/identity/v2_0/endpoint.py +++ b/openstackclient/identity/v2_0/endpoint.py @@ -17,10 +17,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 8939aa12f..a72c40b07 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -20,10 +20,10 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index 1faab2e5a..e54c07af0 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -18,10 +18,10 @@ import logging from keystoneauth1 import exceptions as ks_exc -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/role_assignment.py b/openstackclient/identity/v2_0/role_assignment.py index 54d01b4e5..093df6a85 100644 --- a/openstackclient/identity/v2_0/role_assignment.py +++ b/openstackclient/identity/v2_0/role_assignment.py @@ -13,10 +13,10 @@ """Identity v2 Assignment action implementations""" -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ # noqa diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 978e926f3..5e8dca735 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -17,10 +17,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index fe9436d8b..ebb2269a9 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -15,9 +15,9 @@ """Identity v2 Token action implementations""" -from osc_lib.command import command from osc_lib import exceptions +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index c4cd52b8d..d80d0e8d0 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -20,10 +20,10 @@ from cliff import columns as cliff_columns from keystoneauth1 import exceptions as ks_exc -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/access_rule.py b/openstackclient/identity/v3/access_rule.py index 367c64901..94e1b0ae8 100644 --- a/openstackclient/identity/v3/access_rule.py +++ b/openstackclient/identity/v3/access_rule.py @@ -17,10 +17,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/application_credential.py b/openstackclient/identity/v3/application_credential.py index c6bad267d..bc3e9ad04 100644 --- a/openstackclient/identity/v3/application_credential.py +++ b/openstackclient/identity/v3/application_credential.py @@ -21,10 +21,10 @@ import uuid from cliff import columns as cliff_columns -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index a161461ce..e9f03a31f 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -16,10 +16,10 @@ import logging from cliff import columns as cliff_columns -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/consumer.py b/openstackclient/identity/v3/consumer.py index 933f48aa7..c58441ca8 100644 --- a/openstackclient/identity/v3/consumer.py +++ b/openstackclient/identity/v3/consumer.py @@ -17,10 +17,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index 7d9c7c46c..02eef649c 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -17,10 +17,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index 06c7191cb..28481c00d 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -18,10 +18,10 @@ import logging from openstack import exceptions as sdk_exceptions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/ec2creds.py b/openstackclient/identity/v3/ec2creds.py index 84700f496..dbbf7a247 100644 --- a/openstackclient/identity/v3/ec2creds.py +++ b/openstackclient/identity/v3/ec2creds.py @@ -14,10 +14,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index bc690939d..9083fdc70 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -17,10 +17,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/endpoint_group.py b/openstackclient/identity/v3/endpoint_group.py index e4611a167..3965f3197 100644 --- a/openstackclient/identity/v3/endpoint_group.py +++ b/openstackclient/identity/v3/endpoint_group.py @@ -16,10 +16,10 @@ import json import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 4e980860d..850ec0ac7 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -16,10 +16,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index a1dff6ee4..a2c2fd336 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -18,10 +18,10 @@ import logging from openstack import exceptions as sdk_exc -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index d0c324362..77d2bf339 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -16,10 +16,10 @@ 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 +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/implied_role.py b/openstackclient/identity/v3/implied_role.py index 3958896b0..c1236ad01 100644 --- a/openstackclient/identity/v3/implied_role.py +++ b/openstackclient/identity/v3/implied_role.py @@ -17,8 +17,8 @@ import logging -from osc_lib.command import command +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/limit.py b/openstackclient/identity/v3/limit.py index 7f2fe885d..15da04369 100644 --- a/openstackclient/identity/v3/limit.py +++ b/openstackclient/identity/v3/limit.py @@ -15,10 +15,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as common_utils diff --git a/openstackclient/identity/v3/mapping.py b/openstackclient/identity/v3/mapping.py index 8c2d0bf8d..a041f19e5 100644 --- a/openstackclient/identity/v3/mapping.py +++ b/openstackclient/identity/v3/mapping.py @@ -18,10 +18,10 @@ import json import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/policy.py b/openstackclient/identity/v3/policy.py index d5b8fec6b..355490395 100644 --- a/openstackclient/identity/v3/policy.py +++ b/openstackclient/identity/v3/policy.py @@ -17,10 +17,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index dd6eb75af..cb91609fa 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -19,10 +19,10 @@ from keystoneauth1 import exceptions as ks_exc from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common from openstackclient.identity.v3 import tag diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index 40d381981..4882c9e9c 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -15,10 +15,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/registered_limit.py b/openstackclient/identity/v3/registered_limit.py index 41b8cdac0..e0afb4133 100644 --- a/openstackclient/identity/v3/registered_limit.py +++ b/openstackclient/identity/v3/registered_limit.py @@ -15,10 +15,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as common_utils diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index b4e7bd175..3c580d6f8 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -18,10 +18,10 @@ import logging from openstack import exceptions as sdk_exc -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index ed9e8c0f7..78c010f0e 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -13,8 +13,7 @@ """Identity v3 Assignment action implementations""" -from osc_lib.command import command - +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index e8f483266..53a706299 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -17,10 +17,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/service_provider.py b/openstackclient/identity/v3/service_provider.py index 5b49cffaf..02aae66bf 100644 --- a/openstackclient/identity/v3/service_provider.py +++ b/openstackclient/identity/v3/service_provider.py @@ -15,10 +15,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index 5500ce1d8..cc6d31e77 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -15,10 +15,10 @@ """Identity v3 Token action implementations""" -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/trust.py b/openstackclient/identity/v3/trust.py index 255f7877d..80808aa12 100644 --- a/openstackclient/identity/v3/trust.py +++ b/openstackclient/identity/v3/trust.py @@ -18,10 +18,10 @@ import logging from openstack import exceptions as sdk_exceptions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/identity/v3/unscoped_saml.py b/openstackclient/identity/v3/unscoped_saml.py index d26035d35..e1efc15cf 100644 --- a/openstackclient/identity/v3/unscoped_saml.py +++ b/openstackclient/identity/v3/unscoped_saml.py @@ -17,9 +17,9 @@ the user can list domains and projects they are allowed to access, and request a scoped token.""" -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index c6c759096..7a2e8d6ca 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -20,10 +20,10 @@ import typing as ty from openstack import exceptions as sdk_exc -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 91b0db428..3f3d3a1a2 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -24,10 +24,10 @@ from osc_lib.api import utils as api_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ CONTAINER_CHOICES = ["ami", "ari", "aki", "bare", "docker", "ova", "ovf"] diff --git a/openstackclient/image/v2/cache.py b/openstackclient/image/v2/cache.py index 9fbce84e4..952d9ed01 100644 --- a/openstackclient/image/v2/cache.py +++ b/openstackclient/image/v2/cache.py @@ -17,10 +17,10 @@ import datetime import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index f35c57231..fa66c7736 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -30,10 +30,10 @@ from osc_lib.api import utils as api_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.common import progressbar from openstackclient.i18n import _ diff --git a/openstackclient/image/v2/info.py b/openstackclient/image/v2/info.py index 469b15ac2..68848136e 100644 --- a/openstackclient/image/v2/info.py +++ b/openstackclient/image/v2/info.py @@ -12,8 +12,8 @@ from osc_lib.cli import format_columns -from osc_lib.command import command +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/image/v2/metadef_namespaces.py b/openstackclient/image/v2/metadef_namespaces.py index f753286c4..af30f718f 100644 --- a/openstackclient/image/v2/metadef_namespaces.py +++ b/openstackclient/image/v2/metadef_namespaces.py @@ -18,10 +18,10 @@ 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 +from openstackclient import command from openstackclient.i18n import _ _formatters = { diff --git a/openstackclient/image/v2/metadef_objects.py b/openstackclient/image/v2/metadef_objects.py index 09fc249cb..d5cbec1cd 100644 --- a/openstackclient/image/v2/metadef_objects.py +++ b/openstackclient/image/v2/metadef_objects.py @@ -17,10 +17,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/image/v2/metadef_properties.py b/openstackclient/image/v2/metadef_properties.py index 440d4010a..3a923c522 100644 --- a/openstackclient/image/v2/metadef_properties.py +++ b/openstackclient/image/v2/metadef_properties.py @@ -15,10 +15,10 @@ import json import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/image/v2/metadef_resource_type_association.py b/openstackclient/image/v2/metadef_resource_type_association.py index 306783cdd..4d3ee4668 100644 --- a/openstackclient/image/v2/metadef_resource_type_association.py +++ b/openstackclient/image/v2/metadef_resource_type_association.py @@ -12,10 +12,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ LOG = logging.getLogger(__name__) diff --git a/openstackclient/image/v2/metadef_resource_types.py b/openstackclient/image/v2/metadef_resource_types.py index 3477df35e..88001b7e1 100644 --- a/openstackclient/image/v2/metadef_resource_types.py +++ b/openstackclient/image/v2/metadef_resource_types.py @@ -12,9 +12,9 @@ """Image V2 Action Implementations""" -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/image/v2/task.py b/openstackclient/image/v2/task.py index 1b7170f63..a0f1d35de 100644 --- a/openstackclient/image/v2/task.py +++ b/openstackclient/image/v2/task.py @@ -11,9 +11,9 @@ # under the License. from osc_lib.cli import format_columns -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ _formatters = { diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 9bcd16583..373fd72af 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -12,19 +12,19 @@ # import abc -import argparse import contextlib import logging import typing as ty -import cliff.app +from cliff import _argparse import openstack.exceptions from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions +from openstackclient import command from openstackclient.i18n import _ from openstackclient.network import utils +from openstackclient import shell LOG = logging.getLogger(__name__) @@ -68,7 +68,7 @@ class NetDetectionMixin(metaclass=abc.ABCMeta): present the options for both network types, often qualified accordingly. """ - app: cliff.app.App + app: shell.OpenStackShell @property def _network_type(self): @@ -136,7 +136,7 @@ def split_help(network_help, compute_help): ) ) - def get_parser(self, prog_name: str) -> argparse.ArgumentParser: + def get_parser(self, prog_name: str) -> _argparse.ArgumentParser: LOG.debug('get_parser(%s)', prog_name) parser = super().get_parser(prog_name) # type: ignore parser = self.update_parser_common(parser) @@ -203,6 +203,8 @@ class NetworkAndComputeDelete(NetworkAndComputeCommand, metaclass=abc.ABCMeta): following the rules in doc/source/command-errors.rst. """ + resource: str + def take_action(self, parsed_args): ret = 0 resources = getattr(parsed_args, self.resource, []) diff --git a/openstackclient/network/v2/address_group.py b/openstackclient/network/v2/address_group.py index 53a91561a..178c3afbf 100644 --- a/openstackclient/network/v2/address_group.py +++ b/openstackclient/network/v2/address_group.py @@ -16,10 +16,10 @@ import logging import netaddr -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index f61dcff55..8a38dab4d 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -15,10 +15,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/default_security_group_rule.py b/openstackclient/network/v2/default_security_group_rule.py index 0f9441488..24475852f 100644 --- a/openstackclient/network/v2/default_security_group_rule.py +++ b/openstackclient/network/v2/default_security_group_rule.py @@ -16,10 +16,10 @@ import logging from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.network import common from openstackclient.network import utils as network_utils diff --git a/openstackclient/network/v2/floating_ip_port_forwarding.py b/openstackclient/network/v2/floating_ip_port_forwarding.py index 99546a1d5..cf770c2cd 100644 --- a/openstackclient/network/v2/floating_ip_port_forwarding.py +++ b/openstackclient/network/v2/floating_ip_port_forwarding.py @@ -16,10 +16,10 @@ import logging import typing as ty -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.network import common diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index e36b89771..f78c7ec86 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -14,9 +14,9 @@ """IP Availability Info implementations""" from osc_lib.cli import format_columns -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/l3_conntrack_helper.py b/openstackclient/network/v2/l3_conntrack_helper.py index 982021ec6..742c26396 100644 --- a/openstackclient/network/v2/l3_conntrack_helper.py +++ b/openstackclient/network/v2/l3_conntrack_helper.py @@ -15,10 +15,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ LOG = logging.getLogger(__name__) diff --git a/openstackclient/network/v2/local_ip.py b/openstackclient/network/v2/local_ip.py index a2937937e..dde6ccd0c 100644 --- a/openstackclient/network/v2/local_ip.py +++ b/openstackclient/network/v2/local_ip.py @@ -17,10 +17,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/local_ip_association.py b/openstackclient/network/v2/local_ip_association.py index 814bbdb0b..123faa67c 100644 --- a/openstackclient/network/v2/local_ip_association.py +++ b/openstackclient/network/v2/local_ip_association.py @@ -17,10 +17,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/ndp_proxy.py b/openstackclient/network/v2/ndp_proxy.py index 95e39ce15..78a7ae911 100644 --- a/openstackclient/network/v2/ndp_proxy.py +++ b/openstackclient/network/v2/ndp_proxy.py @@ -17,10 +17,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index cc4de2b42..b2b4eaa73 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -17,10 +17,10 @@ from cliff import columns as cliff_columns from osc_lib.cli import format_columns -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ LOG = logging.getLogger(__name__) diff --git a/openstackclient/network/v2/network_auto_allocated_topology.py b/openstackclient/network/v2/network_auto_allocated_topology.py index 3f5795e6a..1107cb709 100644 --- a/openstackclient/network/v2/network_auto_allocated_topology.py +++ b/openstackclient/network/v2/network_auto_allocated_topology.py @@ -15,9 +15,9 @@ import logging -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/network/v2/network_flavor.py b/openstackclient/network/v2/network_flavor.py index c4d262dea..99993f64e 100644 --- a/openstackclient/network/v2/network_flavor.py +++ b/openstackclient/network/v2/network_flavor.py @@ -15,10 +15,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/network_flavor_profile.py b/openstackclient/network/v2/network_flavor_profile.py index eeb4ce899..9792b010a 100644 --- a/openstackclient/network/v2/network_flavor_profile.py +++ b/openstackclient/network/v2/network_flavor_profile.py @@ -13,10 +13,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.network import common diff --git a/openstackclient/network/v2/network_meter.py b/openstackclient/network/v2/network_meter.py index 625b7422b..bb3bf94a1 100644 --- a/openstackclient/network/v2/network_meter.py +++ b/openstackclient/network/v2/network_meter.py @@ -15,10 +15,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py index c7551543e..7a7c231dd 100644 --- a/openstackclient/network/v2/network_meter_rule.py +++ b/openstackclient/network/v2/network_meter_rule.py @@ -16,10 +16,10 @@ import logging import typing as ty -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index abdd044af..05f6d11be 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -16,10 +16,10 @@ import logging from cliff import columns as cliff_columns -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index 86f342f6e..b2ddd823a 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -15,10 +15,10 @@ import itertools -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.network import common diff --git a/openstackclient/network/v2/network_qos_rule_type.py b/openstackclient/network/v2/network_qos_rule_type.py index fd4da84af..c79c5fe01 100644 --- a/openstackclient/network/v2/network_qos_rule_type.py +++ b/openstackclient/network/v2/network_qos_rule_type.py @@ -13,9 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index d266f352d..200175ac9 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -15,10 +15,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index 76a967ecc..c2e7becfa 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -15,10 +15,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.network import common diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index c9b9ccc23..c8b9a0e4c 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -20,10 +20,10 @@ import logging import typing as ty -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common @@ -402,7 +402,7 @@ def take_action(self, parsed_args): 'available', ) - display_props: tuple[str, ...] = tuple() + display_props: tuple[ty.Any, ...] = tuple() for s in data: props = utils.get_item_properties(s, columns) if ( diff --git a/openstackclient/network/v2/network_service_provider.py b/openstackclient/network/v2/network_service_provider.py index f743bfa6e..1433c097a 100644 --- a/openstackclient/network/v2/network_service_provider.py +++ b/openstackclient/network/v2/network_service_provider.py @@ -13,9 +13,9 @@ """Network Service Providers Implementation""" -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/network/v2/network_trunk.py b/openstackclient/network/v2/network_trunk.py index cd0d47ffd..5d1389b9b 100644 --- a/openstackclient/network/v2/network_trunk.py +++ b/openstackclient/network/v2/network_trunk.py @@ -23,10 +23,10 @@ from osc_lib.cli import format_columns from osc_lib.cli import identity as identity_utils from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils +from openstackclient import command from openstackclient.i18n import _ LOG = logging.getLogger(__name__) diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index c28131440..080499d4b 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -22,11 +22,11 @@ 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 from osc_lib import utils from osc_lib.utils import tags as _tag +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 1ea85331c..1d21a8023 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -23,11 +23,11 @@ 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 from osc_lib import utils from osc_lib.utils import tags as _tag +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index c4b56a216..ee56f7236 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -16,11 +16,11 @@ import argparse from cliff import columns as cliff_columns -from osc_lib.command import command from osc_lib import utils from osc_lib.utils import tags as _tag from openstackclient.api import compute_v2 +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 7dc1df087..4ff7ea8cb 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -20,11 +20,11 @@ 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 from osc_lib import utils from osc_lib.utils import tags as _tag +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 2f900a61d..399ce483f 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -17,11 +17,11 @@ from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils from osc_lib.utils import tags as _tag +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 686fdcc62..199e5222f 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -15,8 +15,8 @@ from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index f4250dbba..b0f92c761 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -19,9 +19,9 @@ from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index cb8e4ffeb..e8ee0fc69 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -19,10 +19,10 @@ from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ diff --git a/openstackclient/tests/unit/common/test_command.py b/openstackclient/tests/unit/common/test_command.py index 8233f8994..1f1efcead 100644 --- a/openstackclient/tests/unit/common/test_command.py +++ b/openstackclient/tests/unit/common/test_command.py @@ -14,9 +14,9 @@ from unittest import mock -from osc_lib.command import command from osc_lib import exceptions +from openstackclient import command from openstackclient.tests.unit import fakes as test_fakes from openstackclient.tests.unit import utils as test_utils diff --git a/openstackclient/volume/v2/backup_record.py b/openstackclient/volume/v2/backup_record.py index 98f7c7171..93492f87f 100644 --- a/openstackclient/volume/v2/backup_record.py +++ b/openstackclient/volume/v2/backup_record.py @@ -16,9 +16,9 @@ import logging -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 347724f92..4910bb129 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -18,10 +18,10 @@ 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 +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/consistency_group_snapshot.py b/openstackclient/volume/v2/consistency_group_snapshot.py index 3c582d6b1..23c3f1034 100644 --- a/openstackclient/volume/v2/consistency_group_snapshot.py +++ b/openstackclient/volume/v2/consistency_group_snapshot.py @@ -16,10 +16,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 72706a740..39aa99eb4 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -19,10 +19,10 @@ from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/service.py b/openstackclient/volume/v2/service.py index 48cdba721..7777e7e63 100644 --- a/openstackclient/volume/v2/service.py +++ b/openstackclient/volume/v2/service.py @@ -14,10 +14,10 @@ """Service action implementations""" -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index fa76d776e..6b97d9b7e 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -25,11 +25,11 @@ from openstack import exceptions as sdk_exceptions from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils from openstackclient.api import volume_v2 +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/volume/v2/volume_backend.py b/openstackclient/volume/v2/volume_backend.py index 2fbbed64a..e51e37bb9 100644 --- a/openstackclient/volume/v2/volume_backend.py +++ b/openstackclient/volume/v2/volume_backend.py @@ -15,9 +15,9 @@ """Storage backend action implementations""" from osc_lib.cli import format_columns -from osc_lib.command import command from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/volume_backup.py b/openstackclient/volume/v2/volume_backup.py index 30e67e296..e698af676 100644 --- a/openstackclient/volume/v2/volume_backup.py +++ b/openstackclient/volume/v2/volume_backup.py @@ -18,10 +18,10 @@ import logging from cliff import columns as cliff_columns -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/volume_host.py b/openstackclient/volume/v2/volume_host.py index 2b0df0aa2..44fd58a5c 100644 --- a/openstackclient/volume/v2/volume_host.py +++ b/openstackclient/volume/v2/volume_host.py @@ -14,8 +14,7 @@ """Volume v2 host action implementations""" -from osc_lib.command import command - +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index fb774611f..113b8badd 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -22,10 +22,10 @@ from openstack.block_storage.v2 import snapshot as _snapshot from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 3bd91d052..dcdc52762 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -16,10 +16,10 @@ import logging -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 8df39d654..7b6dc0392 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -20,10 +20,10 @@ 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 from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/volume/v3/block_storage_cleanup.py b/openstackclient/volume/v3/block_storage_cleanup.py index 361440c99..5208504a3 100644 --- a/openstackclient/volume/v3/block_storage_cleanup.py +++ b/openstackclient/volume/v3/block_storage_cleanup.py @@ -11,9 +11,9 @@ # under the License. from cinderclient import api_versions -from osc_lib.command import command from osc_lib import exceptions +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v3/block_storage_cluster.py b/openstackclient/volume/v3/block_storage_cluster.py index 8cf01b247..d99ec52b0 100644 --- a/openstackclient/volume/v3/block_storage_cluster.py +++ b/openstackclient/volume/v3/block_storage_cluster.py @@ -11,10 +11,10 @@ # under the License. from cinderclient import api_versions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v3/block_storage_log_level.py b/openstackclient/volume/v3/block_storage_log_level.py index 1f8d5b330..2e2fdc513 100644 --- a/openstackclient/volume/v3/block_storage_log_level.py +++ b/openstackclient/volume/v3/block_storage_log_level.py @@ -15,9 +15,9 @@ """Block Storage Service action implementations""" from openstack import utils as sdk_utils -from osc_lib.command import command from osc_lib import exceptions +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v3/block_storage_manage.py b/openstackclient/volume/v3/block_storage_manage.py index 17c699b99..78756385c 100644 --- a/openstackclient/volume/v3/block_storage_manage.py +++ b/openstackclient/volume/v3/block_storage_manage.py @@ -16,10 +16,10 @@ import argparse from cinderclient import api_versions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v3/block_storage_resource_filter.py b/openstackclient/volume/v3/block_storage_resource_filter.py index 494d9cbe1..fc564386e 100644 --- a/openstackclient/volume/v3/block_storage_resource_filter.py +++ b/openstackclient/volume/v3/block_storage_resource_filter.py @@ -14,10 +14,10 @@ from openstack import utils as sdk_utils from osc_lib.cli import format_columns -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v3/service.py b/openstackclient/volume/v3/service.py index 0f14e472c..eecd8e0d0 100644 --- a/openstackclient/volume/v3/service.py +++ b/openstackclient/volume/v3/service.py @@ -15,10 +15,10 @@ """Service action implementations""" from openstack import utils as sdk_utils -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v3/volume.py b/openstackclient/volume/v3/volume.py index 54dd5c754..4e689a37c 100644 --- a/openstackclient/volume/v3/volume.py +++ b/openstackclient/volume/v3/volume.py @@ -26,11 +26,11 @@ from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils from openstackclient.api import volume_v3 +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/volume/v3/volume_attachment.py b/openstackclient/volume/v3/volume_attachment.py index 6d36e63fb..7e32b0c2a 100644 --- a/openstackclient/volume/v3/volume_attachment.py +++ b/openstackclient/volume/v3/volume_attachment.py @@ -14,10 +14,10 @@ from openstack import utils as sdk_utils from osc_lib.cli import format_columns -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import envvars from openstackclient.common import pagination from openstackclient.i18n import _ diff --git a/openstackclient/volume/v3/volume_backup.py b/openstackclient/volume/v3/volume_backup.py index 3875f802a..f836eb39b 100644 --- a/openstackclient/volume/v3/volume_backup.py +++ b/openstackclient/volume/v3/volume_backup.py @@ -21,10 +21,10 @@ from cliff import columns as cliff_columns from openstack import utils as sdk_utils from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ diff --git a/openstackclient/volume/v3/volume_group.py b/openstackclient/volume/v3/volume_group.py index 8433e5736..1810feef5 100644 --- a/openstackclient/volume/v3/volume_group.py +++ b/openstackclient/volume/v3/volume_group.py @@ -13,10 +13,10 @@ import argparse from cinderclient import api_versions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import envvars from openstackclient.i18n import _ diff --git a/openstackclient/volume/v3/volume_group_snapshot.py b/openstackclient/volume/v3/volume_group_snapshot.py index 5a760ac9b..530b7d5d1 100644 --- a/openstackclient/volume/v3/volume_group_snapshot.py +++ b/openstackclient/volume/v3/volume_group_snapshot.py @@ -13,10 +13,10 @@ import logging from openstack import utils as sdk_utils -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import envvars from openstackclient.i18n import _ diff --git a/openstackclient/volume/v3/volume_group_type.py b/openstackclient/volume/v3/volume_group_type.py index f1ef61740..bdedd25a1 100644 --- a/openstackclient/volume/v3/volume_group_type.py +++ b/openstackclient/volume/v3/volume_group_type.py @@ -15,10 +15,10 @@ from cinderclient import api_versions from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ LOG = logging.getLogger(__name__) diff --git a/openstackclient/volume/v3/volume_message.py b/openstackclient/volume/v3/volume_message.py index 0fc724a58..b39c57946 100644 --- a/openstackclient/volume/v3/volume_message.py +++ b/openstackclient/volume/v3/volume_message.py @@ -17,10 +17,10 @@ import logging as LOG from cinderclient import api_versions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/volume/v3/volume_snapshot.py b/openstackclient/volume/v3/volume_snapshot.py index 9cbc198b7..78e0f116a 100644 --- a/openstackclient/volume/v3/volume_snapshot.py +++ b/openstackclient/volume/v3/volume_snapshot.py @@ -22,10 +22,10 @@ from openstack.block_storage.v3 import snapshot as _snapshot from osc_lib.cli import format_columns from osc_lib.cli import parseractions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.common import pagination from openstackclient.i18n import _ from openstackclient.identity import common as identity_common diff --git a/openstackclient/volume/v3/volume_transfer_request.py b/openstackclient/volume/v3/volume_transfer_request.py index b7add1228..afd462603 100644 --- a/openstackclient/volume/v3/volume_transfer_request.py +++ b/openstackclient/volume/v3/volume_transfer_request.py @@ -17,10 +17,10 @@ import logging from cinderclient import api_versions -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ diff --git a/openstackclient/volume/v3/volume_type.py b/openstackclient/volume/v3/volume_type.py index bf193ec60..562283ecd 100644 --- a/openstackclient/volume/v3/volume_type.py +++ b/openstackclient/volume/v3/volume_type.py @@ -21,10 +21,10 @@ 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 from osc_lib import utils +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common as identity_common From e799a4a676a81f29a8a77ffb13378700cb338d33 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 11 Dec 2025 15:20:21 +0000 Subject: [PATCH 56/67] typing: Add types to custom formatters We make a lot of use of typing.Any just to get this over the line. We can come back to this later. Change-Id: I03c18b0b44f210b2ad3e4012344d521fb85cae97 Signed-off-by: Stephen Finucane --- openstackclient/compute/v2/server.py | 7 ++++--- openstackclient/compute/v2/usage.py | 8 +++++--- openstackclient/identity/v2_0/catalog.py | 3 ++- openstackclient/identity/v2_0/role_assignment.py | 2 +- openstackclient/identity/v2_0/user.py | 2 +- openstackclient/identity/v3/application_credential.py | 5 +++-- openstackclient/identity/v3/catalog.py | 4 ++-- openstackclient/image/v1/image.py | 4 ++-- openstackclient/network/v2/network.py | 4 ++-- openstackclient/network/v2/network_agent.py | 4 ++-- openstackclient/network/v2/network_qos_policy.py | 3 ++- openstackclient/network/v2/network_trunk.py | 2 +- openstackclient/network/v2/port.py | 2 +- openstackclient/network/v2/router.py | 6 +++--- openstackclient/network/v2/security_group.py | 5 +++-- openstackclient/network/v2/subnet.py | 6 +++--- openstackclient/volume/v2/volume.py | 2 +- openstackclient/volume/v2/volume_backup.py | 2 +- openstackclient/volume/v2/volume_snapshot.py | 2 +- openstackclient/volume/v2/volume_type.py | 3 ++- openstackclient/volume/v3/volume.py | 2 +- openstackclient/volume/v3/volume_backup.py | 2 +- openstackclient/volume/v3/volume_snapshot.py | 2 +- openstackclient/volume/v3/volume_type.py | 4 ++-- 24 files changed, 47 insertions(+), 39 deletions(-) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 250eacc77..1eee828d9 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -21,6 +21,7 @@ import json import logging import os +import typing as ty from cliff import columns as cliff_columns import iso8601 @@ -44,7 +45,7 @@ IMAGE_STRING_FOR_BFV = 'N/A (booted from volume)' -class PowerStateColumn(cliff_columns.FormattableColumn): +class PowerStateColumn(cliff_columns.FormattableColumn[int]): """Generate a formatted string of a server's power state.""" power_states = [ @@ -65,7 +66,7 @@ def human_readable(self): return 'N/A' -class AddressesColumn(cliff_columns.FormattableColumn): +class AddressesColumn(cliff_columns.FormattableColumn[ty.Any]): """Generate a formatted string of a server's addresses.""" def human_readable(self): @@ -86,7 +87,7 @@ def machine_readable(self): } -class HostColumn(cliff_columns.FormattableColumn): +class HostColumn(cliff_columns.FormattableColumn[str | None]): """Generate a formatted string of a hostname.""" def human_readable(self): diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 3fe7f419a..3015a9e70 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -15,8 +15,10 @@ """Usage action implementations""" +from collections.abc import Collection import datetime import functools +import typing as ty from cliff import columns as cliff_columns from osc_lib import utils @@ -27,7 +29,7 @@ # TODO(stephenfin): This exists in a couple of places and should be moved to a # common module -class ProjectColumn(cliff_columns.FormattableColumn): +class ProjectColumn(cliff_columns.FormattableColumn[str]): """Formattable column for project column. Unlike the parent FormattableColumn class, the initializer of the class @@ -53,12 +55,12 @@ def human_readable(self): return project -class CountColumn(cliff_columns.FormattableColumn): +class CountColumn(cliff_columns.FormattableColumn[Collection[ty.Any]]): def human_readable(self): return len(self._value) if self._value is not None else None -class FloatColumn(cliff_columns.FormattableColumn): +class FloatColumn(cliff_columns.FormattableColumn[float]): def human_readable(self): return float(f"{self._value:.2f}") diff --git a/openstackclient/identity/v2_0/catalog.py b/openstackclient/identity/v2_0/catalog.py index a184f97df..437cad2fc 100644 --- a/openstackclient/identity/v2_0/catalog.py +++ b/openstackclient/identity/v2_0/catalog.py @@ -14,6 +14,7 @@ """Identity v2 Service Catalog action implementations""" import logging +import typing as ty from cliff import columns as cliff_columns from osc_lib import exceptions @@ -26,7 +27,7 @@ LOG = logging.getLogger(__name__) -class EndpointsColumn(cliff_columns.FormattableColumn): +class EndpointsColumn(cliff_columns.FormattableColumn[ty.Any]): def human_readable(self): if not self._value: return "" diff --git a/openstackclient/identity/v2_0/role_assignment.py b/openstackclient/identity/v2_0/role_assignment.py index 093df6a85..d616725df 100644 --- a/openstackclient/identity/v2_0/role_assignment.py +++ b/openstackclient/identity/v2_0/role_assignment.py @@ -80,7 +80,7 @@ def take_action(self, parsed_args): parsed_args.project, ) elif parsed_args.authproject: - if auth_ref: + if auth_ref and auth_ref.project_id: project = utils.find_resource( identity_client.projects, auth_ref.project_id ) diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index d80d0e8d0..244b9e9da 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -30,7 +30,7 @@ LOG = logging.getLogger(__name__) -class ProjectColumn(cliff_columns.FormattableColumn): +class ProjectColumn(cliff_columns.FormattableColumn[str]): """Formattable column for project column. Unlike the parent FormattableColumn class, the initializer of the diff --git a/openstackclient/identity/v3/application_credential.py b/openstackclient/identity/v3/application_credential.py index bc3e9ad04..2894d6ee9 100644 --- a/openstackclient/identity/v3/application_credential.py +++ b/openstackclient/identity/v3/application_credential.py @@ -18,6 +18,7 @@ import datetime import json import logging +import typing as ty import uuid from cliff import columns as cliff_columns @@ -31,11 +32,11 @@ LOG = logging.getLogger(__name__) -class RolesColumn(cliff_columns.FormattableColumn): +class RolesColumn(cliff_columns.FormattableColumn[ty.Any]): """Generate a formatted string of role names.""" def human_readable(self): - return utils.format_list(r['name'] for r in self._value) + return utils.format_list(list(r['name'] for r in self._value)) def _format_application_credential( diff --git a/openstackclient/identity/v3/catalog.py b/openstackclient/identity/v3/catalog.py index e9f03a31f..7d37e6cbd 100644 --- a/openstackclient/identity/v3/catalog.py +++ b/openstackclient/identity/v3/catalog.py @@ -9,11 +9,11 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -# """Identity v3 Service Catalog action implementations""" import logging +import typing as ty from cliff import columns as cliff_columns from osc_lib import exceptions @@ -26,7 +26,7 @@ LOG = logging.getLogger(__name__) -class EndpointsColumn(cliff_columns.FormattableColumn): +class EndpointsColumn(cliff_columns.FormattableColumn[ty.Any]): def human_readable(self): if not self._value: return "" diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 3f3d3a1a2..58fc3f4dd 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -70,7 +70,7 @@ def _get_columns(item): _formatters = {} -class HumanReadableSizeColumn(cliff_columns.FormattableColumn): +class HumanReadableSizeColumn(cliff_columns.FormattableColumn[int]): def human_readable(self): """Return a formatted visibility string @@ -84,7 +84,7 @@ def human_readable(self): return '' -class VisibilityColumn(cliff_columns.FormattableColumn): +class VisibilityColumn(cliff_columns.FormattableColumn[bool]): def human_readable(self): """Return a formatted visibility string diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 7aefe50c3..c8e1f360a 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -24,12 +24,12 @@ from openstackclient.network import common -class AdminStateColumn(cliff_columns.FormattableColumn): +class AdminStateColumn(cliff_columns.FormattableColumn[bool]): def human_readable(self): return 'UP' if self._value else 'DOWN' -class RouterExternalColumn(cliff_columns.FormattableColumn): +class RouterExternalColumn(cliff_columns.FormattableColumn[bool]): def human_readable(self): return 'External' if self._value else 'Internal' diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index b2b4eaa73..806422b10 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -26,12 +26,12 @@ LOG = logging.getLogger(__name__) -class AliveColumn(cliff_columns.FormattableColumn): +class AliveColumn(cliff_columns.FormattableColumn[bool]): def human_readable(self): return ":-)" if self._value else "XXX" -class AdminStateColumn(cliff_columns.FormattableColumn): +class AdminStateColumn(cliff_columns.FormattableColumn[bool]): def human_readable(self): return 'UP' if self._value else 'DOWN' diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index 05f6d11be..ab620b849 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -14,6 +14,7 @@ # under the License. import logging +import typing as ty from cliff import columns as cliff_columns from osc_lib import exceptions @@ -27,7 +28,7 @@ LOG = logging.getLogger(__name__) -class RulesColumn(cliff_columns.FormattableColumn): +class RulesColumn(cliff_columns.FormattableColumn[ty.Any]): def human_readable(self): return '\n'.join(str(v) for v in self._value) diff --git a/openstackclient/network/v2/network_trunk.py b/openstackclient/network/v2/network_trunk.py index 5d1389b9b..974a99763 100644 --- a/openstackclient/network/v2/network_trunk.py +++ b/openstackclient/network/v2/network_trunk.py @@ -36,7 +36,7 @@ SUB_PORTS = 'sub_ports' -class AdminStateColumn(cliff_columns.FormattableColumn): +class AdminStateColumn(cliff_columns.FormattableColumn[bool]): def human_readable(self): return 'UP' if self._value else 'DOWN' diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 080499d4b..e1205153e 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -34,7 +34,7 @@ LOG = logging.getLogger(__name__) -class AdminStateColumn(cliff_columns.FormattableColumn): +class AdminStateColumn(cliff_columns.FormattableColumn[bool]): def human_readable(self): return 'UP' if self._value else 'DOWN' diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 1d21a8023..939167d85 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -35,12 +35,12 @@ LOG = logging.getLogger(__name__) -class AdminStateColumn(cliff_columns.FormattableColumn): +class AdminStateColumn(cliff_columns.FormattableColumn[bool]): def human_readable(self): return 'UP' if self._value else 'DOWN' -class RouterInfoColumn(cliff_columns.FormattableColumn): +class RouterInfoColumn(cliff_columns.FormattableColumn[ty.Any]): def human_readable(self): try: return json.dumps(self._value) @@ -48,7 +48,7 @@ def human_readable(self): return '' -class RoutesColumn(cliff_columns.FormattableColumn): +class RoutesColumn(cliff_columns.FormattableColumn[ty.Any]): def human_readable(self): # Map the route keys to match --route option. for route in self._value or []: diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index ee56f7236..c6930de78 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -14,6 +14,7 @@ """Security Group action implementations""" import argparse +import typing as ty from cliff import columns as cliff_columns from osc_lib import utils @@ -65,12 +66,12 @@ def _format_compute_security_group_rules(sg_rules): return utils.format_list(rules, separator='\n') -class NetworkSecurityGroupRulesColumn(cliff_columns.FormattableColumn): +class NetworkSecurityGroupRulesColumn(cliff_columns.FormattableColumn[ty.Any]): def human_readable(self): return _format_network_security_group_rules(self._value) -class ComputeSecurityGroupRulesColumn(cliff_columns.FormattableColumn): +class ComputeSecurityGroupRulesColumn(cliff_columns.FormattableColumn[ty.Any]): def human_readable(self): return _format_compute_security_group_rules(self._value) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 4ff7ea8cb..3357f8930 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -44,7 +44,7 @@ def _update_arguments(obj_list, parsed_args_list, option): raise exceptions.CommandError(msg) -class AllocationPoolsColumn(cliff_columns.FormattableColumn): +class AllocationPoolsColumn(cliff_columns.FormattableColumn[ty.Any]): def human_readable(self): pool_formatted = [ '{}-{}'.format(pool.get('start', ''), pool.get('end', '')) @@ -53,7 +53,7 @@ def human_readable(self): return ','.join(pool_formatted) -class HostRoutesColumn(cliff_columns.FormattableColumn): +class HostRoutesColumn(cliff_columns.FormattableColumn[ty.Any]): def human_readable(self): # Map the host route keys to match --host-route option. return utils.format_list_of_dicts( @@ -61,7 +61,7 @@ def human_readable(self): ) -class UnsortedListColumn(cliff_columns.FormattableColumn): +class UnsortedListColumn(cliff_columns.FormattableColumn[list[ty.Any]]): # format_columns.ListColumn sorts the output, but for things like # DNS server addresses the order matters def human_readable(self): diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 6b97d9b7e..b4761ffab 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -61,7 +61,7 @@ def __call__(self, parser, namespace, values, option_string=None): ) -class AttachmentsColumn(cliff_columns.FormattableColumn): +class AttachmentsColumn(cliff_columns.FormattableColumn[list[str]]): """Formattable column for attachments column. Unlike the parent FormattableColumn class, the initializer of the diff --git a/openstackclient/volume/v2/volume_backup.py b/openstackclient/volume/v2/volume_backup.py index e698af676..7dbe92c96 100644 --- a/openstackclient/volume/v2/volume_backup.py +++ b/openstackclient/volume/v2/volume_backup.py @@ -28,7 +28,7 @@ LOG = logging.getLogger(__name__) -class VolumeIdColumn(cliff_columns.FormattableColumn): +class VolumeIdColumn(cliff_columns.FormattableColumn[str]): """Formattable column for volume ID column. Unlike the parent FormattableColumn class, the initializer of the diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index 113b8badd..3b1dbbabf 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -34,7 +34,7 @@ LOG = logging.getLogger(__name__) -class VolumeIdColumn(cliff_columns.FormattableColumn): +class VolumeIdColumn(cliff_columns.FormattableColumn[str]): """Formattable column for volume ID column. Unlike the parent FormattableColumn class, the initializer of the diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 7b6dc0392..e7b90af95 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -16,6 +16,7 @@ import functools import logging +import typing as ty from cliff import columns as cliff_columns from osc_lib.cli import format_columns @@ -31,7 +32,7 @@ LOG = logging.getLogger(__name__) -class EncryptionInfoColumn(cliff_columns.FormattableColumn): +class EncryptionInfoColumn(cliff_columns.FormattableColumn[ty.Any]): """Formattable column for encryption info column. Unlike the parent FormattableColumn class, the initializer of the diff --git a/openstackclient/volume/v3/volume.py b/openstackclient/volume/v3/volume.py index 4e689a37c..50ea77fb5 100644 --- a/openstackclient/volume/v3/volume.py +++ b/openstackclient/volume/v3/volume.py @@ -62,7 +62,7 @@ def __call__(self, parser, namespace, values, option_string=None): ) -class AttachmentsColumn(cliff_columns.FormattableColumn): +class AttachmentsColumn(cliff_columns.FormattableColumn[list[ty.Any]]): """Formattable column for attachments column. Unlike the parent FormattableColumn class, the initializer of the diff --git a/openstackclient/volume/v3/volume_backup.py b/openstackclient/volume/v3/volume_backup.py index f836eb39b..df9a17eb0 100644 --- a/openstackclient/volume/v3/volume_backup.py +++ b/openstackclient/volume/v3/volume_backup.py @@ -31,7 +31,7 @@ LOG = logging.getLogger(__name__) -class VolumeIdColumn(cliff_columns.FormattableColumn): +class VolumeIdColumn(cliff_columns.FormattableColumn[str]): """Formattable column for volume ID column. Unlike the parent FormattableColumn class, the initializer of the diff --git a/openstackclient/volume/v3/volume_snapshot.py b/openstackclient/volume/v3/volume_snapshot.py index 78e0f116a..f89174c3d 100644 --- a/openstackclient/volume/v3/volume_snapshot.py +++ b/openstackclient/volume/v3/volume_snapshot.py @@ -33,7 +33,7 @@ LOG = logging.getLogger(__name__) -class VolumeIdColumn(cliff_columns.FormattableColumn): +class VolumeIdColumn(cliff_columns.FormattableColumn[str]): """Formattable column for volume ID column. Unlike the parent FormattableColumn class, the initializer of the diff --git a/openstackclient/volume/v3/volume_type.py b/openstackclient/volume/v3/volume_type.py index 562283ecd..fbce2f2c9 100644 --- a/openstackclient/volume/v3/volume_type.py +++ b/openstackclient/volume/v3/volume_type.py @@ -10,12 +10,12 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -# """Volume v3 Type action implementations""" import functools import logging +import typing as ty from cinderclient import api_versions from cliff import columns as cliff_columns @@ -32,7 +32,7 @@ LOG = logging.getLogger(__name__) -class EncryptionInfoColumn(cliff_columns.FormattableColumn): +class EncryptionInfoColumn(cliff_columns.FormattableColumn[ty.Any]): """Formattable column for encryption info column. Unlike the parent FormattableColumn class, the initializer of the From a7e2f31ecc32ff939799c09d41234c2a991fc87b Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 28 Nov 2025 16:47:52 +0000 Subject: [PATCH 57/67] volume: Remove negotiation for v1 API Change Ibe1cd6461d2cb78826467078aa17272f171746aa removed support for the v1 volume API. We should have removed this check at the same time. We also remove some god-awful monkey patching that references v1 cinderclient but in practice modified all clients. Change-Id: I3727fd9238df966b3bc59812c5efcf3398da5c72 Signed-off-by: Stephen Finucane --- .../tests/unit/volume/test_find_resource.py | 17 ++++--------- openstackclient/volume/client.py | 24 ++++++------------- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/openstackclient/tests/unit/volume/test_find_resource.py b/openstackclient/tests/unit/volume/test_find_resource.py index df087dd5d..614fa9a51 100644 --- a/openstackclient/tests/unit/volume/test_find_resource.py +++ b/openstackclient/tests/unit/volume/test_find_resource.py @@ -21,15 +21,6 @@ from osc_lib import utils from openstackclient.tests.unit import utils as test_utils -from openstackclient.volume import client # noqa - - -# Monkey patch for v1 cinderclient -# NOTE(dtroyer): Do here because openstackclient.volume.client -# doesn't do it until the client object is created now. -volumes.Volume.NAME_ATTR = 'display_name' -volume_snapshots.Snapshot.NAME_ATTR = 'display_name' - ID = '1after909' NAME = 'PhilSpector' @@ -42,14 +33,14 @@ def setUp(self): api.client = mock.Mock() api.client.get = mock.Mock() resp = mock.Mock() - body = {"volumes": [{"id": ID, 'display_name': NAME}]} + body = {"volumes": [{"id": ID, 'name': NAME}]} api.client.get.side_effect = [Exception("Not found"), (resp, body)] self.manager = volumes.VolumeManager(api) def test_find(self): result = utils.find_resource(self.manager, NAME) self.assertEqual(ID, result.id) - self.assertEqual(NAME, result.display_name) + self.assertEqual(NAME, result.name) def test_not_find(self): self.assertRaises( @@ -67,14 +58,14 @@ def setUp(self): api.client = mock.Mock() api.client.get = mock.Mock() resp = mock.Mock() - body = {"snapshots": [{"id": ID, 'display_name': NAME}]} + body = {"snapshots": [{"id": ID, 'name': NAME}]} api.client.get.side_effect = [Exception("Not found"), (resp, body)] self.manager = volume_snapshots.SnapshotManager(api) def test_find(self): result = utils.find_resource(self.manager, NAME) self.assertEqual(ID, result.id) - self.assertEqual(NAME, result.display_name) + self.assertEqual(NAME, result.name) def test_not_find(self): self.assertRaises( diff --git a/openstackclient/volume/client.py b/openstackclient/volume/client.py index d3a3406a3..dbef055fa 100644 --- a/openstackclient/volume/client.py +++ b/openstackclient/volume/client.py @@ -20,16 +20,14 @@ from openstackclient.i18n import _ - LOG = logging.getLogger(__name__) DEFAULT_API_VERSION = '3' API_VERSION_OPTION = 'os_volume_api_version' -API_NAME = "volume" +API_NAME = 'volume' API_VERSIONS = { - "1": "cinderclient.v1.client.Client", - "2": "cinderclient.v2.client.Client", - "3": "cinderclient.v3.client.Client", + '2': 'cinderclient.v2.client.Client', + '3': 'cinderclient.v3.client.Client', } # Save the microversion if in use @@ -45,11 +43,6 @@ def make_client(instance): from cinderclient.v3 import volume_snapshots from cinderclient.v3 import volumes - # Check whether the available cinderclient supports v1 or v2 - try: - from cinderclient.v1 import services # noqa - except Exception: - del API_VERSIONS['1'] try: from cinderclient.v2 import services # noqa except Exception: @@ -127,21 +120,18 @@ def check_api_version(check_version): global _volume_api_version - # Copy some logic from novaclient 3.3.0 for basic version detection - # NOTE(dtroyer): This is only enough to resume operations using API - # version 3.0 or any valid version supplied by the user. _volume_api_version = api_versions.get_api_version(check_version) # Bypass X.latest format microversion if not _volume_api_version.is_latest(): - if _volume_api_version > api_versions.APIVersion("3.0"): + if _volume_api_version > api_versions.APIVersion('3.0'): if not _volume_api_version.matches( api_versions.MIN_VERSION, api_versions.MAX_VERSION, ): - msg = _("versions supported by client: %(min)s - %(max)s") % { - "min": api_versions.MIN_VERSION, - "max": api_versions.MAX_VERSION, + msg = _('versions supported by client: %(min)s - %(max)s') % { + 'min': api_versions.MIN_VERSION, + 'max': api_versions.MAX_VERSION, } raise exceptions.CommandError(msg) From 841d95b095105a934a4860a1a6e4b96d5b0555da Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 28 Nov 2025 17:40:20 +0000 Subject: [PATCH 58/67] common: Remove references to pkg_resources Even though the comment here attributed this to stevedore, it was in fact the use of pkg_resources that changed things. Change-Id: I35377dd7d773024aa6423b72b1412e11b1b6f2e4 Signed-off-by: Stephen Finucane --- openstackclient/common/clientmanager.py | 11 +---------- openstackclient/shell.py | 5 +---- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 94c25e3f9..2213f1e46 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -158,16 +158,7 @@ def get_plugin_modules(group): for ep in mgr: LOG.debug('Found plugin %s', ep.name) - # Different versions of stevedore use different - # implementations of EntryPoint from other libraries, which - # are not API-compatible. - try: - module_name = ep.entry_point.module_name - except AttributeError: - try: - module_name = ep.entry_point.module - except AttributeError: - module_name = ep.entry_point.value + module_name = ep.entry_point.module try: module = importlib.import_module(module_name) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index dfc559a04..a494d7438 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -74,10 +74,7 @@ def _final_defaults(self): self._auth_type = 'password' def _load_plugins(self): - """Load plugins via stevedore - - osc-lib has no opinion on what plugins should be loaded - """ + """Load plugins via stevedore.""" # Loop through extensions to get API versions for mod in clientmanager.PLUGIN_MODULES: default_version = getattr(mod, 'DEFAULT_API_VERSION', None) From 748cff59142e716ef81f349de5f7a3f29d8e9e27 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 11 Dec 2025 13:16:30 +0000 Subject: [PATCH 59/67] zuul: Make openstackclient-check-plugins voting This will ensure we do not forget to ignore a module when migrating plugins in-tree. Change-Id: Id4dd657746f7c5f8ebf5ef55964593123303b996 Signed-off-by: Stephen Finucane Depends-on: https://review.opendev.org/c/openstack/openstackclient/+/970618 --- .zuul.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.zuul.yaml b/.zuul.yaml index 74ca67d57..d63ee7c5f 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -173,6 +173,8 @@ - release-notes-jobs-python3 check: jobs: + - openstackclient-check-plugins: + voting: true - osc-build-image: voting: false - osc-functional-devstack From f2f0f92d419d04422c241e0c1d77a48fa2e64013 Mon Sep 17 00:00:00 2001 From: Koya Watanabe Date: Sat, 22 Nov 2025 18:02:27 +0900 Subject: [PATCH 60/67] Remove functional testenv for py38/py39 Python 3.8 and 3.9 are no longer supported. Refer to pyproject.toml for the current supported versions. Change-Id: Ie7f917c26299509050294037cc27e1fd9c20e78b Signed-off-by: Koya Watanabe --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 4392a36a8..1988ec823 100644 --- a/tox.ini +++ b/tox.ini @@ -44,7 +44,7 @@ commands = python -m pip freeze stestr run {posargs} -[testenv:functional{,-tips,-py38,-py39,-py310,-py311,-py312}] +[testenv:functional{,-tips,-py310,-py311,-py312,-py313,-py314}] description = Run functional tests. setenv = From 8dbb7126c68f0885c5dcab12613444885bbbf6e2 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 15 Dec 2025 10:45:17 +0000 Subject: [PATCH 61/67] identity: Use plural dest for append opts Change-Id: I73a263a309e022b7606ced43a814a1d1914bc751 Signed-off-by: Stephen Finucane --- openstackclient/identity/v2_0/project.py | 13 +++++++----- .../identity/v3/application_credential.py | 3 ++- .../identity/v3/identity_provider.py | 15 +++++++------- openstackclient/identity/v3/project.py | 2 +- openstackclient/identity/v3/tag.py | 5 +++-- openstackclient/identity/v3/token.py | 3 ++- openstackclient/identity/v3/user.py | 7 ++++--- .../tests/unit/identity/v2_0/test_project.py | 8 ++++---- .../v3/test_application_credential.py | 2 +- .../identity/v3/test_identity_provider.py | 20 +++++++++---------- .../tests/unit/identity/v3/test_oauth.py | 2 +- .../tests/unit/identity/v3/test_project.py | 2 +- .../tests/unit/identity/v3/test_user.py | 8 ++++---- 13 files changed, 49 insertions(+), 41 deletions(-) diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index a72c40b07..bf19d7d09 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -59,6 +59,7 @@ def get_parser(self, prog_name): parser.add_argument( '--property', metavar='', + dest='properties', action=parseractions.KeyValueAction, help=_( 'Add a property to ' @@ -79,8 +80,8 @@ def take_action(self, parsed_args): if parsed_args.disable: enabled = False kwargs = {} - if parsed_args.property: - kwargs = parsed_args.property.copy() + if parsed_args.properties: + kwargs.update(parsed_args.properties) try: project = identity_client.tenants.create( @@ -230,6 +231,7 @@ def get_parser(self, prog_name): parser.add_argument( '--property', metavar='', + dest='properties', action=parseractions.KeyValueAction, help=_( 'Set a project property ' @@ -255,8 +257,8 @@ def take_action(self, parsed_args): kwargs['enabled'] = True if parsed_args.disable: kwargs['enabled'] = False - if parsed_args.property: - kwargs.update(parsed_args.property) + if parsed_args.properties: + kwargs.update(parsed_args.properties) if 'id' in kwargs: del kwargs['id'] if 'name' in kwargs: @@ -338,6 +340,7 @@ def get_parser(self, prog_name): parser.add_argument( '--property', metavar='', + dest='properties', action='append', default=[], help=_( @@ -354,7 +357,7 @@ def take_action(self, parsed_args): parsed_args.project, ) kwargs = project._info - for key in parsed_args.property: + for key in parsed_args.properties: if key in kwargs: kwargs[key] = None identity_client.tenants.update(project.id, **kwargs) diff --git a/openstackclient/identity/v3/application_credential.py b/openstackclient/identity/v3/application_credential.py index 2894d6ee9..86e2ea4e8 100644 --- a/openstackclient/identity/v3/application_credential.py +++ b/openstackclient/identity/v3/application_credential.py @@ -149,6 +149,7 @@ def get_parser(self, prog_name): parser.add_argument( '--role', metavar='', + dest='roles', action='append', default=[], help=_( @@ -208,7 +209,7 @@ def take_action(self, parsed_args): user_id = conn.config.get_auth().get_user_id(conn.identity) role_ids = [] - for role in parsed_args.role: + for role in parsed_args.roles: if is_uuid_like(role): role_ids.append({'id': role}) else: diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 77d2bf339..7d90636e3 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -41,6 +41,7 @@ def get_parser(self, prog_name): identity_remote_id_provider.add_argument( '--remote-id', metavar='', + dest='remote_ids', action='append', help=_( 'Remote IDs to associate with the Identity Provider ' @@ -99,16 +100,15 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity + remote_ids: list[str] | None = None if parsed_args.remote_id_file: file_content = utils.read_blob_file_contents( parsed_args.remote_id_file ) remote_ids = file_content.splitlines() remote_ids = list(map(str.strip, remote_ids)) - else: - remote_ids = ( - parsed_args.remote_id if parsed_args.remote_id else None - ) + elif parsed_args.remote_ids: + remote_ids = parsed_args.remote_ids domain_id = None if parsed_args.domain: @@ -240,6 +240,7 @@ def get_parser(self, prog_name): identity_remote_id_provider.add_argument( '--remote-id', metavar='', + dest='remote_ids', action='append', help=_( 'Remote IDs to associate with the Identity Provider ' @@ -287,8 +288,8 @@ def take_action(self, parsed_args): ) remote_ids = file_content.splitlines() remote_ids = list(map(str.strip, remote_ids)) - elif parsed_args.remote_id: - remote_ids = parsed_args.remote_id + elif parsed_args.remote_ids: + remote_ids = parsed_args.remote_ids # Setup keyword args for the client kwargs = {} @@ -298,7 +299,7 @@ def take_action(self, parsed_args): kwargs['enabled'] = True if parsed_args.disable: kwargs['enabled'] = False - if parsed_args.remote_id_file or parsed_args.remote_id: + if parsed_args.remote_id_file or parsed_args.remote_ids: kwargs['remote_ids'] = remote_ids # TODO(pas-ha) actually check for 3.14 microversion diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index cb91609fa..e70a8a501 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -73,8 +73,8 @@ def get_parser(self, prog_name): parser.add_argument( '--property', metavar='', - action=parseractions.KeyValueAction, dest='properties', + action=parseractions.KeyValueAction, help=_( 'Add a property to ' '(repeat option to set multiple properties)' diff --git a/openstackclient/identity/v3/tag.py b/openstackclient/identity/v3/tag.py index 0909fd122..41493c993 100644 --- a/openstackclient/identity/v3/tag.py +++ b/openstackclient/identity/v3/tag.py @@ -114,6 +114,7 @@ def add_tag_option_to_parser_for_set(parser, resource_name): parser.add_argument( '--remove-tag', action='append', + dest='remove_tags', metavar='', default=[], help=_( @@ -128,8 +129,8 @@ def update_tags_in_args(parsed_args, obj, args): if parsed_args.clear_tags: args['tags'] = [] obj.tags = [] - if parsed_args.remove_tag: - args['tags'] = sorted(set(obj.tags) - set(parsed_args.remove_tag)) + if parsed_args.remove_tags: + args['tags'] = sorted(set(obj.tags) - set(parsed_args.remove_tags)) return if parsed_args.tags: args['tags'] = sorted(set(obj.tags).union(set(parsed_args.tags))) diff --git a/openstackclient/identity/v3/token.py b/openstackclient/identity/v3/token.py index cc6d31e77..05e374caf 100644 --- a/openstackclient/identity/v3/token.py +++ b/openstackclient/identity/v3/token.py @@ -37,6 +37,7 @@ def get_parser(self, prog_name): parser.add_argument( '--role', metavar='', + dest='roles', action='append', default=[], required=True, @@ -52,7 +53,7 @@ def take_action(self, parsed_args): # NOTE(stevemar): We want a list of role ids roles = [] - for role in parsed_args.role: + for role in parsed_args.roles: role_id = utils.find_resource( identity_client.roles, role, diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 7a2e8d6ca..196a7e062 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -82,9 +82,9 @@ def _get_options_for_user(identity_client, parsed_args): options['multi_factor_auth_enabled'] = True if parsed_args.disable_multi_factor_auth: options['multi_factor_auth_enabled'] = False - if parsed_args.multi_factor_auth_rule: + if parsed_args.multi_factor_auth_rules: auth_rules = [ - rule.split(",") for rule in parsed_args.multi_factor_auth_rule + rule.split(",") for rule in parsed_args.multi_factor_auth_rules ] if auth_rules: options['multi_factor_auth_rules'] = auth_rules @@ -175,7 +175,8 @@ def _add_user_options(parser): parser.add_argument( '--multi-factor-auth-rule', metavar='', - action="append", + dest='multi_factor_auth_rules', + action='append', default=[], help=_( 'Set multi-factor auth rules. For example, to set a rule ' diff --git a/openstackclient/tests/unit/identity/v2_0/test_project.py b/openstackclient/tests/unit/identity/v2_0/test_project.py index 9b203b22b..bb6a64374 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_project.py +++ b/openstackclient/tests/unit/identity/v2_0/test_project.py @@ -195,7 +195,7 @@ def test_project_create_property(self): self.fake_project.name, ] verifylist = [ - ('property', {'fee': 'fi', 'fo': 'fum'}), + ('properties', {'fee': 'fi', 'fo': 'fum'}), ('name', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -464,7 +464,7 @@ def test_project_set_unexist_project(self): ('description', None), ('enable', False), ('disable', False), - ('property', None), + ('properties', None), ] self.projects_mock.get.side_effect = exceptions.NotFound(None) self.projects_mock.find.side_effect = exceptions.NotFound(None) @@ -588,7 +588,7 @@ def test_project_set_property(self): self.fake_project.name, ] verifylist = [ - ('property', {'fee': 'fi', 'fo': 'fum'}), + ('properties', {'fee': 'fi', 'fo': 'fum'}), ('project', self.fake_project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -683,7 +683,7 @@ def test_project_unset_key(self): self.fake_proj.name, ] verifylist = [ - ('property', ['fee', 'fo']), + ('properties', ['fee', 'fo']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/unit/identity/v3/test_application_credential.py b/openstackclient/tests/unit/identity/v3/test_application_credential.py index a7307d6ee..3a3a80e4a 100644 --- a/openstackclient/tests/unit/identity/v3/test_application_credential.py +++ b/openstackclient/tests/unit/identity/v3/test_application_credential.py @@ -120,7 +120,7 @@ def test_application_credential_create_with_options(self): verifylist = [ ('name', self.application_credential.name), ('secret', 'moresecuresecret'), - ('role', [self.roles.id]), + ('roles', [self.roles.id]), ('expiration', '2024-01-01T00:00:00'), ('description', 'credential for testing'), ] diff --git a/openstackclient/tests/unit/identity/v3/test_identity_provider.py b/openstackclient/tests/unit/identity/v3/test_identity_provider.py index 20e7f497a..c65e947ef 100644 --- a/openstackclient/tests/unit/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/unit/identity/v3/test_identity_provider.py @@ -127,7 +127,7 @@ def test_create_identity_provider_remote_id(self): ] verifylist = [ ('identity_provider_id', identity_fakes.idp_id), - ('remote_id', identity_fakes.idp_remote_ids[:1]), + ('remote_ids', identity_fakes.idp_remote_ids[:1]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -157,7 +157,7 @@ def test_create_identity_provider_remote_ids_multiple(self): ] verifylist = [ ('identity_provider_id', identity_fakes.idp_id), - ('remote_id', identity_fakes.idp_remote_ids), + ('remote_ids', identity_fakes.idp_remote_ids), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -561,7 +561,7 @@ def prepare(self): ('description', new_description), ('enable', False), ('disable', False), - ('remote_id', None), + ('remote_ids', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) @@ -597,7 +597,7 @@ def prepare(self): ('description', None), ('enable', False), ('disable', True), - ('remote_id', identity_fakes.idp_remote_ids), + ('remote_ids', identity_fakes.idp_remote_ids), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -637,7 +637,7 @@ def prepare(self): ('description', None), ('enable', True), ('disable', False), - ('remote_id', identity_fakes.idp_remote_ids), + ('remote_ids', identity_fakes.idp_remote_ids), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -675,7 +675,7 @@ def prepare(self): ('description', None), ('enable', True), ('disable', False), - ('remote_id', [self.new_remote_id]), + ('remote_ids', [self.new_remote_id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -756,7 +756,7 @@ def prepare(self): ('identity_provider', identity_fakes.idp_id), ('enable', False), ('disable', False), - ('remote_id', None), + ('remote_ids', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -776,7 +776,7 @@ def prepare(self): ('identity_provider', identity_fakes.idp_id), ('enable', False), ('disable', False), - ('remote_id', None), + ('remote_ids', None), ('authorization_ttl', 60), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -800,7 +800,7 @@ def prepare(self): ('identity_provider', identity_fakes.idp_id), ('enable', False), ('disable', False), - ('remote_id', None), + ('remote_ids', None), ('authorization_ttl', 0), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -816,7 +816,7 @@ def test_identity_provider_set_authttl_negative(self): ('identity_provider', identity_fakes.idp_id), ('enable', False), ('disable', False), - ('remote_id', None), + ('remote_ids', None), ('authorization_ttl', -1), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/unit/identity/v3/test_oauth.py b/openstackclient/tests/unit/identity/v3/test_oauth.py index 576f8b20e..9dcf0be89 100644 --- a/openstackclient/tests/unit/identity/v3/test_oauth.py +++ b/openstackclient/tests/unit/identity/v3/test_oauth.py @@ -109,7 +109,7 @@ def test_authorize_request_tokens(self): ] verifylist = [ ('request_key', identity_fakes.request_token_id), - ('role', [identity_fakes.role_name]), + ('roles', [identity_fakes.role_name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 024b74f65..065a65cb1 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -1142,7 +1142,7 @@ def test_project_remove_tags(self): verifylist = [ ('enabled', None), ('project', self.project.name), - ('remove_tag', ['tag1', 'tag2']), + ('remove_tags', ['tag1', 'tag2']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py index f0ed91405..2dacc72a8 100644 --- a/openstackclient/tests/unit/identity/v3/test_user.py +++ b/openstackclient/tests/unit/identity/v3/test_user.py @@ -730,7 +730,7 @@ def test_user_create_option_with_multi_factor_auth_rule(self): ] verifylist = [ ( - 'multi_factor_auth_rule', + 'multi_factor_auth_rules', [identity_fakes.mfa_opt1, identity_fakes.mfa_opt2], ), ('enable', False), @@ -769,7 +769,7 @@ def test_user_create_with_multiple_options(self): verifylist = [ ('ignore_password_expiry', True), ('disable_multi_factor_auth', True), - ('multi_factor_auth_rule', [identity_fakes.mfa_opt1]), + ('multi_factor_auth_rules', [identity_fakes.mfa_opt1]), ('enable', False), ('disable', False), ('name', self.user.name), @@ -1667,7 +1667,7 @@ def test_user_set_option_multi_factor_auth_rule(self): ('name', None), ('password', None), ('email', None), - ('multi_factor_auth_rule', [identity_fakes.mfa_opt1]), + ('multi_factor_auth_rules', [identity_fakes.mfa_opt1]), ('project', None), ('enable', False), ('disable', False), @@ -1701,7 +1701,7 @@ def test_user_set_with_multiple_options(self): ('email', None), ('ignore_password_expiry', True), ('enable_multi_factor_auth', True), - ('multi_factor_auth_rule', [identity_fakes.mfa_opt1]), + ('multi_factor_auth_rules', [identity_fakes.mfa_opt1]), ('project', None), ('enable', False), ('disable', False), From 7246a07834563c042a439a8207d79027631c98a3 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 15 Dec 2025 11:27:53 +0000 Subject: [PATCH 62/67] taas: Use custom command classes In change I53d9058273748ecd4d4eecec5f7291d5f38ce5ab we added custom Command classes for typing purposes. However, the Tap-as-a-Service code merged around the same time and was missed. Correct this. Change-Id: I3a9fe20b5b8eb54708644527538f27396f29b476 Signed-off-by: Stephen Finucane --- openstackclient/network/v2/taas/tap_flow.py | 2 +- openstackclient/network/v2/taas/tap_mirror.py | 2 +- openstackclient/network/v2/taas/tap_service.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openstackclient/network/v2/taas/tap_flow.py b/openstackclient/network/v2/taas/tap_flow.py index 309c4caa0..ef95edb24 100644 --- a/openstackclient/network/v2/taas/tap_flow.py +++ b/openstackclient/network/v2/taas/tap_flow.py @@ -16,11 +16,11 @@ from osc_lib.cli import format_columns from osc_lib.cli import identity as identity_utils -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common from openstackclient.network.v2.taas import tap_service diff --git a/openstackclient/network/v2/taas/tap_mirror.py b/openstackclient/network/v2/taas/tap_mirror.py index 52e45e4a9..40e74da1b 100644 --- a/openstackclient/network/v2/taas/tap_mirror.py +++ b/openstackclient/network/v2/taas/tap_mirror.py @@ -13,11 +13,11 @@ import logging from osc_lib.cli import identity as identity_utils -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common from openstackclient.network.v2 import port as osc_port diff --git a/openstackclient/network/v2/taas/tap_service.py b/openstackclient/network/v2/taas/tap_service.py index e0022b99d..41dda41e3 100644 --- a/openstackclient/network/v2/taas/tap_service.py +++ b/openstackclient/network/v2/taas/tap_service.py @@ -15,11 +15,11 @@ import logging from osc_lib.cli import identity as identity_utils -from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils as osc_utils from osc_lib.utils import columns as column_util +from openstackclient import command from openstackclient.i18n import _ from openstackclient.identity import common From e8ae075c386511c0b7176aecb961115def2ca0b8 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 21 Nov 2025 15:42:40 +0000 Subject: [PATCH 63/67] typing: Fixups for typed osc-lib Change-Id: I436983a13e8812d704af2f1eb3f600277ef8a531 Signed-off-by: Stephen Finucane --- openstackclient/common/clientmanager.py | 62 ++++++++++++++++++- openstackclient/common/module.py | 4 +- openstackclient/compute/v2/aggregate.py | 3 +- openstackclient/compute/v2/console.py | 6 +- openstackclient/compute/v2/server_group.py | 4 +- .../identity/v2_0/role_assignment.py | 2 +- openstackclient/identity/v3/access_rule.py | 18 +++++- .../identity/v3/application_credential.py | 26 ++++++-- .../identity/v3/identity_provider.py | 5 +- openstackclient/identity/v3/user.py | 7 ++- openstackclient/image/v1/image.py | 29 +++++---- openstackclient/image/v2/image.py | 18 +++--- openstackclient/network/common.py | 11 ++-- openstackclient/network/v2/taas/tap_flow.py | 5 +- openstackclient/network/v2/taas/tap_mirror.py | 5 +- .../network/v2/taas/tap_service.py | 5 +- openstackclient/shell.py | 8 ++- openstackclient/volume/v2/volume.py | 2 +- .../volume/v3/volume_attachment.py | 7 ++- 19 files changed, 165 insertions(+), 62 deletions(-) diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 2213f1e46..51911aaf9 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -15,15 +15,25 @@ """Manage access to the clients, including authenticating when needed.""" +import argparse +from collections.abc import Callable import importlib import logging import sys import typing as ty +from osc_lib.cli import client_config from osc_lib import clientmanager from osc_lib import shell import stevedore +if ty.TYPE_CHECKING: + from keystoneauth1 import access as ksa_access + from openstack.compute.v2 import _proxy as compute_proxy + from openstack.image.v2 import _proxy as image_proxy + from openstack.network.v2 import _proxy as network_proxy + + from openstackclient.api import object_store_v1 LOG = logging.getLogger(__name__) @@ -40,6 +50,24 @@ class ClientManager(clientmanager.ClientManager): in osc-lib so we need to maintain a transition period. """ + if ty.TYPE_CHECKING: + # we know this will be set by us and will not be nullable + auth_ref: ksa_access.AccessInfo + + # this is a hack to keep mypy happy: the actual attributes are set in + # get_plugin_modules below + # TODO(stephenfin): Change the types of identity and volume once we've + # migrated everything to SDK. Hopefully by then we'll have figured out + # how to statically distinguish between the v2 and v3 versions of both + # services... + # TODO(stephenfin): We also need to migrate object storage... + compute: compute_proxy.Proxy + identity: ty.Any + image: image_proxy.Proxy + network: network_proxy.Proxy + object_store: object_store_v1.APIv1 + volume: ty.Any + def __init__( self, cli_options=None, @@ -75,6 +103,12 @@ def setup_auth(self): self._auth_required and self._cli_options._openstack_config is not None ): + if not isinstance( + self._cli_options._openstack_config, client_config.OSC_Config + ): + # programmer error + raise TypeError('unexpected type for _openstack_config') + self._cli_options._openstack_config._pw_callback = ( shell.prompt_for_password ) @@ -101,6 +135,13 @@ def _fallback_load_auth_plugin(self, e): self._cli_options.config['auth_type'] = self._original_auth_type del self._cli_options.config['auth']['token'] del self._cli_options.config['auth']['endpoint'] + + if not isinstance( + self._cli_options._openstack_config, client_config.OSC_Config + ): + # programmer error + raise TypeError('unexpected type for _openstack_config') + self._cli_options._auth = ( self._cli_options._openstack_config.load_auth_plugin( self._cli_options.config, @@ -138,11 +179,25 @@ def is_volume_endpoint_enabled(self, volume_client=None): # Plugin Support +ArgumentParserT = ty.TypeVar('ArgumentParserT', bound=argparse.ArgumentParser) + + +@ty.runtime_checkable # Optional: allows usage with isinstance() +class PluginModule(ty.Protocol): + DEFAULT_API_VERSION: str + API_VERSION_OPTION: str + API_NAME: str + API_VERSIONS: tuple[str] + + make_client: Callable[..., ty.Any] + build_option_parser: Callable[[ArgumentParserT], ArgumentParserT] + check_api_version: Callable[[str], bool] + def _on_load_failure_callback( manager: stevedore.ExtensionManager, ep: importlib.metadata.EntryPoint, - err: Exception, + err: BaseException, ) -> None: sys.stderr.write( f"WARNING: Failed to import plugin {ep.group}:{ep.name}: {err}.\n" @@ -152,6 +207,7 @@ def _on_load_failure_callback( def get_plugin_modules(group): """Find plugin entry points""" mod_list = [] + mgr: stevedore.ExtensionManager[PluginModule] mgr = stevedore.ExtensionManager( group, on_load_failure_callback=_on_load_failure_callback ) @@ -164,8 +220,8 @@ def get_plugin_modules(group): module = importlib.import_module(module_name) except Exception as err: sys.stderr.write( - f"WARNING: Failed to import plugin {ep.group}:{ep.name}: " - f"{err}.\n" + f"WARNING: Failed to import plugin " + f"{ep.module_name}:{ep.name}: {err}.\n" ) continue diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 997b5bc9a..6ca5dc231 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -48,7 +48,9 @@ def take_action(self, parsed_args): columns = ('Command Group', 'Commands') if parsed_args.group: - groups = (group for group in groups if parsed_args.group in group) + groups = sorted( + group for group in groups if parsed_args.group in group + ) commands = [] for group in groups: diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index 48f585b5f..f8c3d9677 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -19,6 +19,7 @@ import logging import typing as ty +from cliff import columns from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions @@ -32,7 +33,7 @@ LOG = logging.getLogger(__name__) -_aggregate_formatters = { +_aggregate_formatters: dict[str, type[columns.FormattableColumn[ty.Any]]] = { 'Hosts': format_columns.ListColumn, 'Metadata': format_columns.DictColumn, 'hosts': format_columns.ListColumn, diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index ac8e10d99..cbcce4b09 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -64,13 +64,15 @@ def take_action(self, parsed_args): output = compute_client.get_server_console_output( server.id, length=parsed_args.lines ) - data = None + data: str | None = None if output: data = output.get('output', None) if data and data[-1] != '\n': data += '\n' - self.app.stdout.write(data) + + if data: + self.app.stdout.write(data) class ShowConsoleURL(command.ShowOne): diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index f64460111..b74c25626 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -16,7 +16,9 @@ """Compute v2 Server Group action implementations""" import logging +import typing as ty +from cliff import columns from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib.cli import parseractions @@ -30,7 +32,7 @@ LOG = logging.getLogger(__name__) -_formatters = { +_formatters: dict[str, type[columns.FormattableColumn[ty.Any]]] = { 'member_ids': format_columns.ListColumn, 'policies': format_columns.ListColumn, 'rules': format_columns.DictColumn, diff --git a/openstackclient/identity/v2_0/role_assignment.py b/openstackclient/identity/v2_0/role_assignment.py index d616725df..0aa800ef8 100644 --- a/openstackclient/identity/v2_0/role_assignment.py +++ b/openstackclient/identity/v2_0/role_assignment.py @@ -68,7 +68,7 @@ def take_action(self, parsed_args): parsed_args.user, ) elif parsed_args.authuser: - if auth_ref: + if auth_ref and auth_ref.user_id: user = utils.find_resource( identity_client.users, auth_ref.user_id ) diff --git a/openstackclient/identity/v3/access_rule.py b/openstackclient/identity/v3/access_rule.py index 94e1b0ae8..1859ef6fa 100644 --- a/openstackclient/identity/v3/access_rule.py +++ b/openstackclient/identity/v3/access_rule.py @@ -44,7 +44,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.sdk_connection.identity conn = self.app.client_manager.sdk_connection - user_id = conn.config.get_auth().get_user_id(conn.identity) + auth = conn.config.get_auth() + if auth is None: + # this will never happen + raise exceptions.CommandError('invalid authentication info') + user_id = auth.get_user_id(conn.identity) errors = 0 for ac in parsed_args.access_rule: @@ -87,7 +91,11 @@ def take_action(self, parsed_args): ).id else: conn = self.app.client_manager.sdk_connection - user_id = conn.config.get_auth().get_user_id(conn.identity) + auth = conn.config.get_auth() + if auth is None: + # this will never happen + raise exceptions.CommandError('invalid authentication info') + user_id = auth.get_user_id(conn.identity) columns = ('ID', 'Service', 'Method', 'Path') data = identity_client.access_rules(user=user_id) @@ -119,7 +127,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.sdk_connection.identity conn = self.app.client_manager.sdk_connection - user_id = conn.config.get_auth().get_user_id(conn.identity) + auth = conn.config.get_auth() + if auth is None: + # this will never happen + raise exceptions.CommandError('invalid authentication info') + user_id = auth.get_user_id(conn.identity) access_rule = identity_client.get_access_rule( user_id, parsed_args.access_rule diff --git a/openstackclient/identity/v3/application_credential.py b/openstackclient/identity/v3/application_credential.py index 86e2ea4e8..3b38f17b5 100644 --- a/openstackclient/identity/v3/application_credential.py +++ b/openstackclient/identity/v3/application_credential.py @@ -206,7 +206,12 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.sdk_connection.identity conn = self.app.client_manager.sdk_connection - user_id = conn.config.get_auth().get_user_id(conn.identity) + auth = conn.config.get_auth() + if auth is None: + # this will never happen + raise exceptions.CommandError('invalid authentication info') + + user_id = auth.get_user_id(conn.identity) role_ids = [] for role in parsed_args.roles: @@ -274,7 +279,12 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.sdk_connection.identity conn = self.app.client_manager.sdk_connection - user_id = conn.config.get_auth().get_user_id(conn.identity) + auth = conn.config.get_auth() + if auth is None: + # this will never happen + raise exceptions.CommandError('invalid authentication info') + + user_id = auth.get_user_id(conn.identity) errors = 0 for ac in parsed_args.application_credential: @@ -327,7 +337,11 @@ def take_action(self, parsed_args): ) else: conn = self.app.client_manager.sdk_connection - user_id = conn.config.get_auth().get_user_id(conn.identity) + auth = conn.config.get_auth() + if auth is None: + # this will never happen + raise exceptions.CommandError('invalid authentication info') + user_id = auth.get_user_id(conn.identity) application_credentials = identity_client.application_credentials( user=user_id @@ -351,7 +365,11 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.sdk_connection.identity conn = self.app.client_manager.sdk_connection - user_id = conn.config.get_auth().get_user_id(conn.identity) + auth = conn.config.get_auth() + if auth is None: + # this will never happen + raise exceptions.CommandError('invalid authentication info') + user_id = auth.get_user_id(conn.identity) application_credential = identity_client.find_application_credential( user_id, parsed_args.application_credential, ignore_missing=False diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 7d90636e3..f1af03f05 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -137,8 +137,9 @@ def take_action(self, parsed_args): ) idp._info.pop('links', None) - remote_ids = format_columns.ListColumn(idp._info.pop('remote_ids', [])) - idp._info['remote_ids'] = remote_ids + idp._info['remote_ids'] = format_columns.ListColumn( + idp._info.pop('remote_ids', []) + ) return zip(*sorted(idp._info.items())) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 196a7e062..b0ed1d205 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -691,7 +691,12 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.sdk_connection.identity conn = self.app.client_manager.sdk_connection - user_id = conn.config.get_auth().get_user_id(conn.identity) + auth = conn.config.get_auth() + if auth is None: + # this will never happen + raise exceptions.CommandError('invalid authentication info') + + user_id = auth.get_user_id(conn.identity) # FIXME(gyee): there are two scenarios: # diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 58fc3f4dd..0ea7eca71 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -19,6 +19,7 @@ import logging import os import sys +import typing as ty from cliff import columns as cliff_columns from osc_lib.api import utils as api_utils @@ -67,9 +68,6 @@ def _get_columns(item): ) -_formatters = {} - - class HumanReadableSizeColumn(cliff_columns.FormattableColumn[int]): def human_readable(self): """Return a formatted visibility string @@ -340,9 +338,12 @@ def take_action(self, parsed_args): if image: display_columns, columns = _get_columns(image) - _formatters['properties'] = format_columns.DictColumn data = utils.get_item_properties( - image, columns, formatters=_formatters + image, + columns, + formatters={ + 'properties': format_columns.DictColumn, + }, ) return (display_columns, data) elif info: @@ -493,19 +494,19 @@ def take_action(self, parsed_args): column_headers = columns # List of image data received - data = list(image_client.images(**kwargs)) + images = list(image_client.images(**kwargs)) if parsed_args.property: # NOTE(dtroyer): coerce to a list to subscript it in py3 attr, value = list(parsed_args.property.items())[0] api_utils.simple_filter( - data, + images, attr=attr, value=value, property_field='properties', ) - data = utils.sort_items(data, parsed_args.sort) + data = utils.sort_items(images, parsed_args.sort) return ( column_headers, @@ -839,11 +840,13 @@ def take_action(self, parsed_args): parsed_args.image, ignore_missing=False ) + formatters: dict[ + str, type[cliff_columns.FormattableColumn[ty.Any]] + ] = { + 'properties': format_columns.DictColumn, + } if parsed_args.human_readable: - _formatters['size'] = HumanReadableSizeColumn + formatters['size'] = HumanReadableSizeColumn display_columns, columns = _get_columns(image) - _formatters['properties'] = format_columns.DictColumn - data = utils.get_item_properties( - image, columns, formatters=_formatters - ) + data = utils.get_item_properties(image, columns, formatters=formatters) return (display_columns, data) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index fa66c7736..c5dd8aeb7 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -551,7 +551,7 @@ def _take_action_image(self, parsed_args): sign_cert_id = parsed_args.sign_cert_id signer = image_signer.ImageSigner() try: - pw = utils.get_password( + pw: str | None = utils.get_password( self.app.stdin, prompt=( "Please enter private key password, leave " @@ -562,12 +562,11 @@ def _take_action_image(self, parsed_args): if not pw or len(pw) < 1: pw = None - else: - # load_private_key() requires the password to be - # passed as bytes - pw = pw.encode() - signer.load_private_key(sign_key_path, password=pw) + signer.load_private_key( + sign_key_path, + password=pw.encode() if pw else None, + ) except Exception: msg = _( "Error during sign operation: private key " @@ -933,18 +932,19 @@ def take_action(self, parsed_args): if 'limit' in kwargs: # Disable automatic pagination in SDK kwargs['paginated'] = False - data = list(image_client.images(**kwargs)) + + images = list(image_client.images(**kwargs)) if parsed_args.property: for attr, value in parsed_args.property.items(): api_utils.simple_filter( - data, + images, attr=attr, value=value, property_field='properties', ) - data = utils.sort_items(data, parsed_args.sort, str) + data = utils.sort_items(images, parsed_args.sort, str) return ( column_headers, diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index 373fd72af..bc110e0b1 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -24,7 +24,6 @@ from openstackclient import command from openstackclient.i18n import _ from openstackclient.network import utils -from openstackclient import shell LOG = logging.getLogger(__name__) @@ -68,8 +67,6 @@ class NetDetectionMixin(metaclass=abc.ABCMeta): present the options for both network types, often qualified accordingly. """ - app: shell.OpenStackShell - @property def _network_type(self): """Discover whether the running cloud is using neutron or nova-network. @@ -84,7 +81,7 @@ def _network_type(self): # Have we set it up yet for this command? if not hasattr(self, '_net_type'): try: - if self.app.client_manager.is_network_endpoint_enabled(): + if self.app.client_manager.is_network_endpoint_enabled(): # type: ignore net_type = _NET_TYPE_NEUTRON else: net_type = _NET_TYPE_COMPUTE @@ -163,11 +160,13 @@ def update_parser_compute(self, parser): def take_action(self, parsed_args): if self.is_neutron: return self.take_action_network( - self.app.client_manager.network, parsed_args + self.app.client_manager.network, # type: ignore + parsed_args, ) elif self.is_nova_network: return self.take_action_compute( - self.app.client_manager.compute, parsed_args + self.app.client_manager.compute, # type: ignore + parsed_args, ) def take_action_network(self, client, parsed_args): diff --git a/openstackclient/network/v2/taas/tap_flow.py b/openstackclient/network/v2/taas/tap_flow.py index ef95edb24..206fbde7f 100644 --- a/openstackclient/network/v2/taas/tap_flow.py +++ b/openstackclient/network/v2/taas/tap_flow.py @@ -25,13 +25,12 @@ from openstackclient.identity import common from openstackclient.network.v2.taas import tap_service - LOG = logging.getLogger(__name__) TAP_FLOW = 'tap_flow' TAP_FLOWS = f'{TAP_FLOW}s' -_attr_map = ( +_attr_map = [ ('id', 'ID', column_util.LIST_BOTH), ('tenant_id', 'Tenant', column_util.LIST_LONG_ONLY), ('name', 'Name', column_util.LIST_BOTH), @@ -39,7 +38,7 @@ ('source_port', 'source_port', column_util.LIST_BOTH), ('tap_service_id', 'tap_service_id', column_util.LIST_BOTH), ('direction', 'Direction', column_util.LIST_BOTH), -) +] _formatters = { 'vlan_filter': format_columns.ListColumn, diff --git a/openstackclient/network/v2/taas/tap_mirror.py b/openstackclient/network/v2/taas/tap_mirror.py index 40e74da1b..876109dc4 100644 --- a/openstackclient/network/v2/taas/tap_mirror.py +++ b/openstackclient/network/v2/taas/tap_mirror.py @@ -23,13 +23,12 @@ from openstackclient.network.v2 import port as osc_port from openstackclient.network.v2.taas import tap_service - LOG = logging.getLogger(__name__) TAP_MIRROR = 'tap_mirror' TAP_MIRRORS = f'{TAP_MIRROR}s' -_attr_map = ( +_attr_map = [ ('id', 'ID', column_util.LIST_BOTH), ('tenant_id', 'Tenant', column_util.LIST_LONG_ONLY), ('name', 'Name', column_util.LIST_BOTH), @@ -37,7 +36,7 @@ ('directions', 'Directions', column_util.LIST_LONG_ONLY), ('remote_ip', 'Remote IP', column_util.LIST_BOTH), ('mirror_type', 'Mirror Type', column_util.LIST_LONG_ONLY), -) +] def _get_columns(item): diff --git a/openstackclient/network/v2/taas/tap_service.py b/openstackclient/network/v2/taas/tap_service.py index 41dda41e3..df27658f5 100644 --- a/openstackclient/network/v2/taas/tap_service.py +++ b/openstackclient/network/v2/taas/tap_service.py @@ -23,19 +23,18 @@ from openstackclient.i18n import _ from openstackclient.identity import common - LOG = logging.getLogger(__name__) TAP_SERVICE = 'tap_service' TAP_SERVICES = f'{TAP_SERVICE}s' -_attr_map = ( +_attr_map = [ ('id', 'ID', column_util.LIST_BOTH), ('tenant_id', 'Tenant', column_util.LIST_LONG_ONLY), ('name', 'Name', column_util.LIST_BOTH), ('port_id', 'Port', column_util.LIST_BOTH), ('status', 'Status', column_util.LIST_BOTH), -) +] def _add_updatable_args(parser): diff --git a/openstackclient/shell.py b/openstackclient/shell.py index a494d7438..743ed2bc8 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -36,6 +36,8 @@ class OpenStackShell(shell.OpenStackShell): + client_manager: clientmanager.ClientManager + def __init__(self): command_manager = commandmanager.CommandManager( 'openstack.cli', ignored_modules=IGNORED_MODULES @@ -57,8 +59,10 @@ def __init__(self): # about them warnings.filterwarnings('ignore', module='openstack') - def build_option_parser(self, description, version): - parser = super().build_option_parser(description, version) + def build_option_parser(self, description, version, argparse_kwargs=None): + parser = super().build_option_parser( + description, version, argparse_kwargs + ) parser = clientmanager.build_plugin_option_parser(parser) parser = auth.build_auth_plugins_option_parser(parser) return parser diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index b4761ffab..61cce04f7 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -61,7 +61,7 @@ def __call__(self, parser, namespace, values, option_string=None): ) -class AttachmentsColumn(cliff_columns.FormattableColumn[list[str]]): +class AttachmentsColumn(cliff_columns.FormattableColumn[list[ty.Any]]): """Formattable column for attachments column. Unlike the parent FormattableColumn class, the initializer of the diff --git a/openstackclient/volume/v3/volume_attachment.py b/openstackclient/volume/v3/volume_attachment.py index 7e32b0c2a..3201da34b 100644 --- a/openstackclient/volume/v3/volume_attachment.py +++ b/openstackclient/volume/v3/volume_attachment.py @@ -11,6 +11,7 @@ # under the License. import logging +import typing as ty from openstack import utils as sdk_utils from osc_lib.cli import format_columns @@ -56,12 +57,12 @@ def _format_attachment(attachment): # VolumeAttachmentManager.create returns a dict while everything else # returns a VolumeAttachment object if isinstance(attachment, dict): - data = [] + data: tuple[ty.Any, ...] = () for column in columns: if column == 'connection_info': - data.append(format_columns.DictColumn(attachment[column])) + data += (format_columns.DictColumn(attachment[column]),) continue - data.append(attachment[column]) + data += (attachment[column],) else: data = utils.get_item_properties( attachment, From 0b05fd89687b9e0da3d7bd04c30bddff195d00b0 Mon Sep 17 00:00:00 2001 From: Doug Goldstein Date: Mon, 15 Dec 2025 10:14:41 -0600 Subject: [PATCH 64/67] fix(keystone): correct the args submitted on user creation When a user is created without a password then no parameter called 'password' should be submitted to the keystone API. This removes the incorrect 'password' with null being supplied. Closes-Bug: 2136148 Change-Id: If1c2eb5db360764a5f7660ce4e5353da85b6d3da Signed-off-by: Doug Goldstein --- openstackclient/identity/v3/user.py | 4 +- .../tests/unit/identity/v3/test_user.py | 42 ------------------- ...ate-user-no-password-619bcddcd046dda8.yaml | 6 +++ 3 files changed, 9 insertions(+), 43 deletions(-) create mode 100644 releasenotes/notes/keystone-create-user-no-password-619bcddcd046dda8.yaml diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 7a2e8d6ca..919939605 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -298,6 +298,9 @@ def take_action(self, parsed_args): "when a user does not have a password." ) ) + else: + kwargs['password'] = password + options = _get_options_for_user(identity_client, parsed_args) if options: kwargs['options'] = options @@ -306,7 +309,6 @@ def take_action(self, parsed_args): user = identity_client.create_user( is_enabled=is_enabled, name=parsed_args.name, - password=password, **kwargs, ) except sdk_exc.ConflictException: diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py index f0ed91405..ab904e545 100644 --- a/openstackclient/tests/unit/identity/v3/test_user.py +++ b/openstackclient/tests/unit/identity/v3/test_user.py @@ -94,7 +94,6 @@ def test_user_create_no_options(self): kwargs = { 'name': self.user.name, 'is_enabled': True, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -138,7 +137,6 @@ def test_user_create_password_prompt(self): self.user.name, ] verifylist = [ - ('password', None), ('password_prompt', True), ('enable', False), ('disable', False), @@ -171,7 +169,6 @@ def test_user_create_password_prompt_no_warning(self): self.user.name, ] verifylist = [ - ('password', None), ('password_prompt', True), ('enable', False), ('disable', False), @@ -236,7 +233,6 @@ def test_user_create_email(self): 'name': self.user.name, 'email': 'barney@example.com', 'is_enabled': True, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -267,7 +263,6 @@ def test_user_create_project(self): 'name': self.user.name, 'default_project_id': self.project.id, 'is_enabled': True, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -312,7 +307,6 @@ def test_user_create_project_domain(self): 'name': self.user.name, 'default_project_id': self.project.id, 'is_enabled': True, - 'password': None, } self.identity_sdk_client.create_user.assert_called_once_with(**kwargs) self.identity_sdk_client.find_domain.assert_called_once_with( @@ -357,7 +351,6 @@ def test_user_create_domain(self): 'name': self.user.name, 'domain_id': self.domain.id, 'is_enabled': True, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -385,7 +378,6 @@ def test_user_create_enable(self): kwargs = { 'name': self.user.name, 'is_enabled': True, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -413,7 +405,6 @@ def test_user_create_disable(self): kwargs = { 'name': self.user.name, 'is_enabled': False, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -443,7 +434,6 @@ def test_user_create_ignore_lockout_failure_attempts(self): 'name': self.user.name, 'is_enabled': True, 'options': {'ignore_lockout_failure_attempts': True}, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -473,7 +463,6 @@ def test_user_create_no_ignore_lockout_failure_attempts(self): 'name': self.user.name, 'is_enabled': True, 'options': {'ignore_lockout_failure_attempts': False}, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -503,7 +492,6 @@ def test_user_create_ignore_password_expiry(self): 'name': self.user.name, 'is_enabled': True, 'options': {'ignore_password_expiry': True}, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -533,7 +521,6 @@ def test_user_create_no_ignore_password_expiry(self): 'name': self.user.name, 'is_enabled': True, 'options': {'ignore_password_expiry': False}, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -563,7 +550,6 @@ def test_user_create_ignore_change_password_upon_first_use(self): 'name': self.user.name, 'is_enabled': True, 'options': {'ignore_change_password_upon_first_use': True}, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -593,7 +579,6 @@ def test_user_create_no_ignore_change_password_upon_first_use(self): 'name': self.user.name, 'is_enabled': True, 'options': {'ignore_change_password_upon_first_use': False}, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -623,7 +608,6 @@ def test_user_create_enables_lock_password(self): 'name': self.user.name, 'is_enabled': True, 'options': {'lock_password': True}, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -653,7 +637,6 @@ def test_user_create_disables_lock_password(self): 'name': self.user.name, 'is_enabled': True, 'options': {'lock_password': False}, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -683,7 +666,6 @@ def test_user_create_enable_multi_factor_auth(self): 'name': self.user.name, 'is_enabled': True, 'options': {'multi_factor_auth_enabled': True}, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -713,7 +695,6 @@ def test_user_create_disable_multi_factor_auth(self): 'name': self.user.name, 'is_enabled': True, 'options': {'multi_factor_auth_enabled': False}, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -751,7 +732,6 @@ def test_user_create_option_with_multi_factor_auth_rule(self): 'options': { 'multi_factor_auth_rules': [["password", "totp"], ["password"]] }, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -790,7 +770,6 @@ def test_user_create_with_multiple_options(self): 'multi_factor_auth_enabled': False, 'multi_factor_auth_rules': [["password", "totp"]], }, - 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -1084,7 +1063,6 @@ def test_user_set_no_options(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('project', None), ('enable', False), @@ -1105,7 +1083,6 @@ def test_user_set_name(self): ] verifylist = [ ('name', 'qwerty'), - ('password', None), ('email', None), ('project', None), ('enable', False), @@ -1136,7 +1113,6 @@ def test_user_set_specify_domain(self): ] verifylist = [ ('name', 'qwerty'), - ('password', None), ('domain', self.domain.id), ('email', None), ('project', None), @@ -1192,7 +1168,6 @@ def test_user_set_password_prompt(self): ] verifylist = [ ('name', None), - ('password', None), ('password_prompt', True), ('email', None), ('project', None), @@ -1225,7 +1200,6 @@ def test_user_set_email(self): ] verifylist = [ ('name', None), - ('password', None), ('email', 'barney@example.com'), ('project', None), ('enable', False), @@ -1254,7 +1228,6 @@ def test_user_set_project(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('project', self.project.id), ('enable', False), @@ -1296,7 +1269,6 @@ def test_user_set_project_domain(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('project', self.project.id), ('project_domain', self.project.domain_id), @@ -1330,7 +1302,6 @@ def test_user_set_enable(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('project', None), ('enable', True), @@ -1357,7 +1328,6 @@ def test_user_set_disable(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('project', None), ('enable', False), @@ -1384,7 +1354,6 @@ def test_user_set_ignore_lockout_failure_attempts(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('ignore_lockout_failure_attempts', True), ('project', None), @@ -1412,7 +1381,6 @@ def test_user_set_no_ignore_lockout_failure_attempts(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('no_ignore_lockout_failure_attempts', True), ('project', None), @@ -1440,7 +1408,6 @@ def test_user_set_ignore_password_expiry(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('ignore_password_expiry', True), ('project', None), @@ -1468,7 +1435,6 @@ def test_user_set_no_ignore_password_expiry(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('no_ignore_password_expiry', True), ('project', None), @@ -1496,7 +1462,6 @@ def test_user_set_ignore_change_password_upon_first_use(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('ignore_change_password_upon_first_use', True), ('project', None), @@ -1524,7 +1489,6 @@ def test_user_set_no_ignore_change_password_upon_first_use(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('no_ignore_change_password_upon_first_use', True), ('project', None), @@ -1552,7 +1516,6 @@ def test_user_set_enable_lock_password(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('enable_lock_password', True), ('project', None), @@ -1580,7 +1543,6 @@ def test_user_set_disable_lock_password(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('disable_lock_password', True), ('project', None), @@ -1608,7 +1570,6 @@ def test_user_set_enable_multi_factor_auth(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('enable_multi_factor_auth', True), ('project', None), @@ -1636,7 +1597,6 @@ def test_user_set_disable_multi_factor_auth(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('disable_multi_factor_auth', True), ('project', None), @@ -1665,7 +1625,6 @@ def test_user_set_option_multi_factor_auth_rule(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('multi_factor_auth_rule', [identity_fakes.mfa_opt1]), ('project', None), @@ -1697,7 +1656,6 @@ def test_user_set_with_multiple_options(self): ] verifylist = [ ('name', None), - ('password', None), ('email', None), ('ignore_password_expiry', True), ('enable_multi_factor_auth', True), diff --git a/releasenotes/notes/keystone-create-user-no-password-619bcddcd046dda8.yaml b/releasenotes/notes/keystone-create-user-no-password-619bcddcd046dda8.yaml new file mode 100644 index 000000000..7cd6acba0 --- /dev/null +++ b/releasenotes/notes/keystone-create-user-no-password-619bcddcd046dda8.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + [Bug `2136148 `_] Keystone allows + users to be created with no password but no value should be submitted for + the password instead of a ``null`` value. From ed2dc692ddaf0b5b7fd62d2c7e0eb03c2e4b1287 Mon Sep 17 00:00:00 2001 From: Abhishek Kekane Date: Thu, 18 Dec 2025 06:43:10 +0000 Subject: [PATCH 65/67] Fix image owner change when accepting membership with --project When using 'openstack image set --project --accept ', the command incorrectly changed the image owner. The --project parameter when used with membership flags should only identify which member's status to update, not change ownership. Closes-Bug: #2136795 Change-Id: I1044b51f38000fb5339740bc40c7f8645c794402 Signed-off-by: Abhishek Kekane --- openstackclient/image/v2/image.py | 5 +- .../tests/unit/image/v2/test_image.py | 111 ++++++++++++++++++ ...-set-project-accept-owner-bug-2136795.yaml | 10 ++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/fix-image-set-project-accept-owner-bug-2136795.yaml diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index c5dd8aeb7..cbb6d874d 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -1393,7 +1393,10 @@ def take_action(self, parsed_args): if parsed_args.visibility is not None: kwargs['visibility'] = parsed_args.visibility - if parsed_args.project: + # Only set owner_id if --project is used WITHOUT membership flags + # When --project is used with --accept/--reject/--pending, it should + # only identify which member's status to update, not change ownership + if parsed_args.project and not parsed_args.membership: # We already did the project lookup above kwargs['owner_id'] = project_id diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index 7eb7331f3..e6de9f2eb 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -1295,6 +1295,117 @@ def test_image_set_membership_option_pending(self): # the 'update membership' route. self.image_client.update_image.assert_called_with(self._image.id) + def test_image_set_membership_accept_with_project_no_owner_change(self): + """Test that --project with --accept doesn't change image owner.""" + membership = image_fakes.create_one_image_member( + attrs={ + 'image_id': '0f41529e-7c12-4de8-be2d-181abb825b3c', + 'member_id': self.project.id, + } + ) + self.image_client.update_member.return_value = membership + + arglist = [ + '--project', + self.project.name, + '--accept', + self._image.id, + ] + verifylist = [ + ('project', self.project.name), + ('membership', 'accepted'), + ('image', self._image.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.image_client.update_member.assert_called_once_with( + image=self._image.id, + member=self.project.id, + status='accepted', + ) + + self.image_client.update_image.assert_called() + call_args = self.image_client.update_image.call_args + if call_args: + args, kwargs = call_args + self.assertNotIn('owner_id', kwargs) + + def test_image_set_membership_reject_with_project_no_owner_change(self): + """Test that --project with --reject doesn't change image owner.""" + membership = image_fakes.create_one_image_member( + attrs={ + 'image_id': '0f41529e-7c12-4de8-be2d-181abb825b3c', + 'member_id': self.project.id, + } + ) + self.image_client.update_member.return_value = membership + + arglist = [ + '--project', + self.project.name, + '--reject', + self._image.id, + ] + verifylist = [ + ('project', self.project.name), + ('membership', 'rejected'), + ('image', self._image.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.image_client.update_member.assert_called_once_with( + image=self._image.id, + member=self.project.id, + status='rejected', + ) + + self.image_client.update_image.assert_called() + call_args = self.image_client.update_image.call_args + if call_args: + args, kwargs = call_args + self.assertNotIn('owner_id', kwargs) + + def test_image_set_membership_pending_with_project_no_owner_change(self): + """Test that --project with --pending doesn't change image owner.""" + membership = image_fakes.create_one_image_member( + attrs={ + 'image_id': '0f41529e-7c12-4de8-be2d-181abb825b3c', + 'member_id': self.project.id, + } + ) + self.image_client.update_member.return_value = membership + + arglist = [ + '--project', + self.project.name, + '--pending', + self._image.id, + ] + verifylist = [ + ('project', self.project.name), + ('membership', 'pending'), + ('image', self._image.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.image_client.update_member.assert_called_once_with( + image=self._image.id, + member=self.project.id, + status='pending', + ) + + self.image_client.update_image.assert_called() + call_args = self.image_client.update_image.call_args + if call_args: + args, kwargs = call_args + self.assertNotIn('owner_id', kwargs) + def test_image_set_options(self): arglist = [ '--name', diff --git a/releasenotes/notes/fix-image-set-project-accept-owner-bug-2136795.yaml b/releasenotes/notes/fix-image-set-project-accept-owner-bug-2136795.yaml new file mode 100644 index 000000000..4cd755b3f --- /dev/null +++ b/releasenotes/notes/fix-image-set-project-accept-owner-bug-2136795.yaml @@ -0,0 +1,10 @@ +--- +fixes: + - | + Fix a bug where using ``openstack image set --project + --accept `` incorrectly changed the image owner to the specified + project instead of only updating the member status. The ``--project`` + parameter when used with ``--accept``, ``--reject``, or ``--pending`` + should only identify which member's status to update, not change the + image ownership. + [Bug `2136795 `_] From 0a937332932b2a0263b12d307026fee43d125418 Mon Sep 17 00:00:00 2001 From: Andriy Kurilin Date: Wed, 7 Jan 2026 13:26:47 +0100 Subject: [PATCH 66/67] Fix quota usage and reservation display Fix `openstack quota show --usage` to correctly display resource usage and reservations by applying proper name normalization for corresponding sections of data. Previously, name normalization was applied only for "limits" which is the root section, leaving 'usage' and 'reservation' sections untouched. Change-Id: Id14fe894b30a74b9b8d78b00c3d4ff151f8b4210 Closes-bug: #2137636 Signed-off-by: Andriy Kurilin --- openstackclient/common/quota.py | 33 ++++++----- .../tests/unit/common/test_quota.py | 59 +++++++++++++++++++ ...-quota-usage-display-2d8f07dccc21f79c.yaml | 5 ++ 3 files changed, 83 insertions(+), 14 deletions(-) create mode 100644 releasenotes/notes/bug-2137636-fix-quota-usage-display-2d8f07dccc21f79c.yaml diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index efe1ce203..6d0025a75 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -797,20 +797,25 @@ def take_action(self, parsed_args): info.update(volume_quota_info) info.update(network_quota_info) - # Map the internal quota names to the external ones - # COMPUTE_QUOTAS and NETWORK_QUOTAS share floating-ips, - # secgroup-rules and secgroups as dict value, so when - # neutron is enabled, quotas of these three resources - # in nova will be replaced by neutron's. - for k, v in itertools.chain( - COMPUTE_QUOTAS.items(), - NOVA_NETWORK_QUOTAS.items(), - VOLUME_QUOTAS.items(), - NETWORK_QUOTAS.items(), - ): - if not k == v and info.get(k) is not None: - info[v] = info[k] - info.pop(k) + def _normalize_names(section: dict) -> None: + # Map the internal quota names to the external ones + # COMPUTE_QUOTAS and NETWORK_QUOTAS share floating-ips, + # secgroup-rules and secgroups as dict value, so when + # neutron is enabled, quotas of these three resources + # in nova will be replaced by neutron's. + for k, v in itertools.chain( + COMPUTE_QUOTAS.items(), + NOVA_NETWORK_QUOTAS.items(), + VOLUME_QUOTAS.items(), + NETWORK_QUOTAS.items(), + ): + if not k == v and section.get(k) is not None: + section[v] = section.pop(k) + + _normalize_names(info) + if parsed_args.usage: + _normalize_names(info["reservation"]) + _normalize_names(info["usage"]) # Remove the 'id' field since it's not very useful if 'id' in info: diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index a2418d01b..7bfb2e7f2 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import copy from unittest import mock from openstack.block_storage.v3 import quota_set as _volume_quota_set @@ -1122,6 +1123,64 @@ def test_quota_show__with_network(self): ) self.assertNotCalled(self.network_client.get_quota_default) + def test_quota_show__with_network_and_usage(self): + # ensure we do not interfere with other tests + self._network_quota_details = copy.deepcopy( + self._network_quota_details + ) + # set a couple of resources + self._network_quota_details["floating_ips"].update( + limit=30, reserved=20, used=7 + ) + self._network_quota_details["security_group_rules"].update( + limit=9, reserved=7, used=5 + ) + + arglist = [ + '--network', + '--usage', + self.projects[0].name, + ] + verifylist = [ + ('service', 'network'), + ('project', self.projects[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + headers, result_gen = self.cmd.take_action(parsed_args) + + self.assertEqual(('Resource', 'Limit', 'In Use', 'Reserved'), headers) + + result = sorted(result_gen) + + self.assertEqual( + [ + ('floating-ips', 30, 7, 20), + ('health_monitors', 0, 0, 0), + ('l7_policies', 0, 0, 0), + ('listeners', 0, 0, 0), + ('load_balancers', 0, 0, 0), + ('networks', 0, 0, 0), + ('pools', 0, 0, 0), + ('ports', 0, 0, 0), + ('rbac_policies', 0, 0, 0), + ('routers', 0, 0, 0), + ('secgroup-rules', 9, 5, 7), + ('secgroups', 0, 0, 0), + ('subnet_pools', 0, 0, 0), + ('subnets', 0, 0, 0), + ], + result, + ) + + self.compute_client.get_quota_set.assert_not_called() + self.volume_sdk_client.get_quota_set.assert_not_called() + self.network_client.get_quota.assert_called_once_with( + self.projects[0].id, + details=True, + ) + self.assertNotCalled(self.network_client.get_quota_default) + def test_quota_show__with_default(self): arglist = [ '--default', diff --git a/releasenotes/notes/bug-2137636-fix-quota-usage-display-2d8f07dccc21f79c.yaml b/releasenotes/notes/bug-2137636-fix-quota-usage-display-2d8f07dccc21f79c.yaml new file mode 100644 index 000000000..818e29ae8 --- /dev/null +++ b/releasenotes/notes/bug-2137636-fix-quota-usage-display-2d8f07dccc21f79c.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix ``openstack quota show --usage`` to correctly display resource usage + and reservation. From 68f22c209d05baa0985dcd7a8c7dfecd01839897 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Wed, 21 Jan 2026 08:48:06 +0900 Subject: [PATCH 67/67] Replace obsolete PCRE packages pcre3 was removed from recent debian-based releases (eg. Trixie[1]), while RHEL10/CentOS Stream 10 no longer ships pcre in favor of pcre2. Use the latest whereto library release (0.5.0) which uses pcre2 instead. [1] https://lists.debian.org/debian-devel/2021/11/msg00176.html Depends-on: https://review.opendev.org/c/openstack/requirements/+/97142 Change-Id: Ide59346a03f4aea2d6ec4b410e102faeddf2bac4 Signed-off-by: Takashi Kajinami --- bindep.txt | 2 +- doc/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bindep.txt b/bindep.txt index 83ebf2d43..8402431ae 100644 --- a/bindep.txt +++ b/bindep.txt @@ -8,4 +8,4 @@ libffi-dev [compile test platform:dpkg] libssl-dev [compile test platform:dpkg] python3-dev [compile test platform:dpkg] python3-devel [compile test platform:rpm] -libpcre3-dev [test platform:dpkg] +libpcre2-dev [test platform:dpkg] diff --git a/doc/requirements.txt b/doc/requirements.txt index 79e3ded4f..05a9bfa87 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -4,7 +4,7 @@ sphinx>=2.0.0,!=2.1.0 # BSD sphinxcontrib-apidoc>=0.2.0 # BSD # redirect tests in docs -whereto>=0.4.0 # Apache-2.0 +whereto>=0.5.0 # Apache-2.0 # Install these to generate sphinx autodocs aodhclient>=0.9.0 # Apache-2.0