From d35ff68cc76f4adada2a19e5fc393f222476eb69 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Thu, 30 Jul 2026 10:00:57 +0200 Subject: [PATCH 1/3] Do not use service_name for endpoint selection. Fix: #2074 --- CHANGES.rst | 7 ++++++ libcloud/common/openstack.py | 2 +- libcloud/test/common/test_openstack.py | 33 ++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 88b983e4bf..c93121c0aa 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,13 @@ Common Compute ~~~~~~~ +- [OpenStack] Select service catalog endpoints by service type without filtering + by the default service name. The service name is only used as a filter when + explicitly provided via ``ex_force_service_name``. + + (#2074) + [Miguel Caballer - @micafer] + - [SSH] Support paramiko 4 RSA key support has been removed as of paramiko 4, so only import it diff --git a/libcloud/common/openstack.py b/libcloud/common/openstack.py index ad3ada1756..ea342e8810 100644 --- a/libcloud/common/openstack.py +++ b/libcloud/common/openstack.py @@ -308,7 +308,7 @@ def get_endpoint(self): :returns: url of the relevant endpoint for the driver """ service_type = self.service_type - service_name = self.service_name + service_name = None service_region = self.service_region if self._ex_force_service_type: diff --git a/libcloud/test/common/test_openstack.py b/libcloud/test/common/test_openstack.py index 2bfeb76401..b2a99fb9e9 100644 --- a/libcloud/test/common/test_openstack.py +++ b/libcloud/test/common/test_openstack.py @@ -57,6 +57,39 @@ def test_set_microversion(self): headers = self.connection.add_default_headers({}) self.assertEqual(headers["OpenStack-API-Version"], "volume 2.67") + def test_get_endpoint_does_not_filter_by_default_service_name(self): + self.connection.service_catalog = Mock() + self.connection.service_catalog.get_endpoint.return_value.url = ( + "https://compute.example.com" + ) + self.connection.service_type = "compute" + self.connection.service_name = "nova" + self.connection.service_region = "RegionOne" + + endpoint = self.connection.get_endpoint() + + self.assertEqual(endpoint, "https://compute.example.com") + self.connection.service_catalog.get_endpoint.assert_called_once_with( + service_type="compute", name=None, region="RegionOne" + ) + + def test_get_endpoint_filters_by_explicit_service_name(self): + self.connection.service_catalog = Mock() + self.connection.service_catalog.get_endpoint.return_value.url = ( + "https://compute.example.com" + ) + self.connection.service_type = "compute" + self.connection.service_name = "nova" + self.connection.service_region = "RegionOne" + self.connection._ex_force_service_name = "custom-nova" + + endpoint = self.connection.get_endpoint() + + self.assertEqual(endpoint, "https://compute.example.com") + self.connection.service_catalog.get_endpoint.assert_called_once_with( + service_type="compute", name="custom-nova", region="RegionOne" + ) + @patch("libcloud.common.base.ConnectionUserAndKey.request") def test_request(self, mock_request): OpenStackBaseConnection.conn_class._raw_data = "" From 7291b8e7b23b768b03d5b131fd2b5bbb8e36e887 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Fri, 31 Jul 2026 13:17:03 +0200 Subject: [PATCH 2/3] Fix possible error with more than one entry --- libcloud/common/openstack.py | 20 ++++++++++-- libcloud/test/common/test_openstack.py | 43 ++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/libcloud/common/openstack.py b/libcloud/common/openstack.py index ea342e8810..c361972f34 100644 --- a/libcloud/common/openstack.py +++ b/libcloud/common/openstack.py @@ -318,9 +318,23 @@ def get_endpoint(self): if self._ex_force_service_region: service_region = self._ex_force_service_region - endpoint = self.service_catalog.get_endpoint( - service_type=service_type, name=service_name, region=service_region - ) + try: + endpoint = self.service_catalog.get_endpoint( + service_type=service_type, name=service_name, region=service_region + ) + except ValueError: + # An explicitly provided service name is a strict filter. When no + # name was provided, first allow the service type and region to + # identify the endpoint on their own, and only use the driver's + # default service name to resolve an ambiguous result. + if service_name or not self.service_name: + raise + + endpoint = self.service_catalog.get_endpoint( + service_type=service_type, + name=self.service_name, + region=service_region, + ) url = endpoint.url diff --git a/libcloud/test/common/test_openstack.py b/libcloud/test/common/test_openstack.py index b2a99fb9e9..7b60366bd0 100644 --- a/libcloud/test/common/test_openstack.py +++ b/libcloud/test/common/test_openstack.py @@ -90,6 +90,49 @@ def test_get_endpoint_filters_by_explicit_service_name(self): service_type="compute", name="custom-nova", region="RegionOne" ) + def test_get_endpoint_uses_default_service_name_to_resolve_ambiguity(self): + self.connection.service_catalog = Mock() + endpoint = Mock(url="https://compute.example.com") + self.connection.service_catalog.get_endpoint.side_effect = [ + ValueError("Found more than 1 matching endpoint"), + endpoint, + ] + self.connection.service_type = "compute" + self.connection.service_name = "nova" + self.connection.service_region = "RegionOne" + + result = self.connection.get_endpoint() + + self.assertEqual(result, "https://compute.example.com") + self.assertEqual( + self.connection.service_catalog.get_endpoint.call_args_list, + [ + unittest.mock.call( + service_type="compute", name=None, region="RegionOne" + ), + unittest.mock.call( + service_type="compute", name="nova", region="RegionOne" + ), + ], + ) + + def test_get_endpoint_does_not_fallback_from_explicit_service_name(self): + self.connection.service_catalog = Mock() + self.connection.service_catalog.get_endpoint.side_effect = ValueError( + "Found more than 1 matching endpoint" + ) + self.connection.service_type = "compute" + self.connection.service_name = "nova" + self.connection.service_region = "RegionOne" + self.connection._ex_force_service_name = "custom-nova" + + with self.assertRaisesRegex(ValueError, "more than 1"): + self.connection.get_endpoint() + + self.connection.service_catalog.get_endpoint.assert_called_once_with( + service_type="compute", name="custom-nova", region="RegionOne" + ) + @patch("libcloud.common.base.ConnectionUserAndKey.request") def test_request(self, mock_request): OpenStackBaseConnection.conn_class._raw_data = "" From 108c9c190a02037a478340d26977644e83e13937 Mon Sep 17 00:00:00 2001 From: Miguel Caballer Date: Fri, 31 Jul 2026 13:24:35 +0200 Subject: [PATCH 3/3] Fix style in test --- libcloud/test/common/test_openstack.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/libcloud/test/common/test_openstack.py b/libcloud/test/common/test_openstack.py index 7b60366bd0..18d6eef28c 100644 --- a/libcloud/test/common/test_openstack.py +++ b/libcloud/test/common/test_openstack.py @@ -107,12 +107,8 @@ def test_get_endpoint_uses_default_service_name_to_resolve_ambiguity(self): self.assertEqual( self.connection.service_catalog.get_endpoint.call_args_list, [ - unittest.mock.call( - service_type="compute", name=None, region="RegionOne" - ), - unittest.mock.call( - service_type="compute", name="nova", region="RegionOne" - ), + unittest.mock.call(service_type="compute", name=None, region="RegionOne"), + unittest.mock.call(service_type="compute", name="nova", region="RegionOne"), ], )