From 68d4c5e78db61ea401ee89c84a69676e26a636a1 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Thu, 27 Feb 2020 11:29:23 +0900 Subject: [PATCH] Add custom SSL CA Cert support for api.cinder.get_microversion This is a stable branch version of commit 285c51f7e1b27a786fa9684abdc489998285b4e2 in the master branch. custom SSL CA Cert support in python-cinderclient was introduced in 5.0.0, but horizon train supports python-cinderclient >=4.0.1, so we cannot backport it as-is. This commit borrowed the logic of get_server_version() from python-cinderclient 5.0.0 so that this can be backported to older releases than train. From the above reason, this is directly proposed to stable/train. Change-Id: I776d2c8c6864067e7b44ce4b2f107c80b8b6d7fb Closes-Bug: #1744670 --- openstack_dashboard/api/cinder.py | 52 +++++++++++- .../test/test_data/cinder_data.py | 36 +++++++++ .../test/unit/api/test_cinder.py | 79 +++++++++++++++++++ ...inder-ssl-deployment-e4dcd6fc0027c96a.yaml | 7 ++ 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/cinder-ssl-deployment-e4dcd6fc0027c96a.yaml diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py index 9d3154fa60..5eedb7653f 100644 --- a/openstack_dashboard/api/cinder.py +++ b/openstack_dashboard/api/cinder.py @@ -30,6 +30,7 @@ from cinderclient import api_versions from cinderclient import client as cinder_client from cinderclient import exceptions as cinder_exception from cinderclient.v2.contrib import list_extensions as cinder_list_extensions +from six.moves import urllib from horizon import exceptions from horizon.utils.memoized import memoized @@ -274,11 +275,60 @@ def get_microversion(request, features): continue else: return None - min_ver, max_ver = cinder_client.get_server_version(cinder_url) + min_ver, max_ver = _get_server_version(request, cinder_url) return microversions.get_microversion_for_features( 'cinder', features, api_versions.APIVersion, min_ver, max_ver) +# NOTE(amotoki): Borrowed from cinderclient.client.get_server_version() +# to support custom SSL CA Cert support with cinderclient<5. +def _get_server_version(request, url): + min_version = "2.0" + current_version = "2.0" + try: + u = urllib.parse.urlparse(url) + version_url = None + + # NOTE(andreykurilin): endpoint URL has at least 2 formats: + # 1. The classic (legacy) endpoint: + # http://{host}:{optional_port}/v{2 or 3}/{project-id} + # http://{host}:{optional_port}/v{2 or 3} + # 3. Under wsgi: + # http://{host}:{optional_port}/volume/v{2 or 3} + for ver in ['v2', 'v3']: + if u.path.endswith(ver) or "/{0}/".format(ver) in u.path: + path = u.path[:u.path.rfind(ver)] + version_url = '%s://%s%s' % (u.scheme, u.netloc, path) + break + + if not version_url: + # NOTE(andreykurilin): probably, it is one of the next cases: + # * https://volume.example.com/ + # * https://example.com/volume + # leave as is without cropping. + version_url = url + + c = cinderclient(request) + resp, data = c.client.request(version_url, 'GET') + + versions = data['versions'] + for version in versions: + if '3.' in version['version']: + min_version = version['min_version'] + current_version = version['version'] + break + else: + # Set the values, but don't break out the loop here in case v3 + # comes later + min_version = '2.0' + current_version = '2.0' + except cinder_exception.ClientException as e: + LOG.warning("Error in server version query:%s\n" + "Returning APIVersion 2.0", e) + return (api_versions.APIVersion(min_version), + api_versions.APIVersion(current_version)) + + def _cinderclient_with_features(request, features, raise_exc=False, message=False): version = get_microversion(request, features) diff --git a/openstack_dashboard/test/test_data/cinder_data.py b/openstack_dashboard/test/test_data/cinder_data.py index e71e1f755e..bcadfbcade 100644 --- a/openstack_dashboard/test/test_data/cinder_data.py +++ b/openstack_dashboard/test/test_data/cinder_data.py @@ -36,6 +36,7 @@ from openstack_dashboard.usage import quotas as usage_quotas def data(TEST): + TEST.cinder_versions = utils.TestDataContainer() TEST.cinder_services = utils.TestDataContainer() TEST.cinder_volumes = utils.TestDataContainer() TEST.cinder_volume_backups = utils.TestDataContainer() @@ -59,6 +60,41 @@ def data(TEST): TEST.cinder_group_volumes = utils.TestDataContainer() TEST.cinder_volume_snapshots_with_groups = utils.TestDataContainer() + ver2 = { + 'id': 'v2.0', + 'links': [{'href': 'http://docs.openstack.org/', + 'rel': 'describedby', + 'type': 'text/html'}, + {'href': 'http://192.168.50.25/volume/v2/', + 'rel': 'self'}], + 'media-types': [ + {'base': 'application/json', + 'type': 'application/vnd.openstack.volume+json;version=2'} + ], + 'min_version': '', + 'status': 'DEPRECATED', + 'updated': '2014-06-28T12:20:21Z', + 'version': '', + } + ver3 = { + 'id': 'v3.0', + 'links': [{'href': 'http://docs.openstack.org/', + 'rel': 'describedby', + 'type': 'text/html'}, + {'href': 'http://192.168.50.25/volume/v3/', + 'rel': 'self'}], + 'media-types': [ + {'base': 'application/json', + 'type': 'application/vnd.openstack.volume+json;version=3'} + ], + 'min_version': '3.0', + 'status': 'CURRENT', + 'updated': '2016-02-08T12:20:21Z', + 'version': '3.16', + } + TEST.cinder_versions.add(ver2) + TEST.cinder_versions.add(ver3) + # Services service_1 = services.Service(services.ServiceManager(None), { "service": "cinder-scheduler", diff --git a/openstack_dashboard/test/unit/api/test_cinder.py b/openstack_dashboard/test/unit/api/test_cinder.py index c25fd56142..46bb5a0c0e 100644 --- a/openstack_dashboard/test/unit/api/test_cinder.py +++ b/openstack_dashboard/test/unit/api/test_cinder.py @@ -16,6 +16,8 @@ from django.conf import settings from django.test.utils import override_settings import cinderclient as cinder_client +from cinderclient import api_versions +from cinderclient import exceptions as cinder_exception import mock from openstack_dashboard import api @@ -446,6 +448,83 @@ class CinderApiTests(test.APIMockTestCase): self.assertEqual(default_volume_type, volume_type) cinderclient.volume_types.default.assert_called_once() + @mock.patch.object(api.cinder, 'cinderclient') + def _check_get_server_version_v3(self, volume_url, version_url, expected, + mock_cinderclient): + versions = {'versions': self.cinder_versions.list()} + cinder_client = mock_cinderclient.return_value + cinder_client.client.request.return_value = (200, versions) + + versions = api.cinder._get_server_version(self.request, volume_url) + + self.assertEqual(expected, versions) + cinder_client.client.request.assert_called_once_with( + version_url, 'GET') + + def test_get_server_version_v3_dedicated_port_http(self): + volume_url = ('http://192.168.122.127:8776/v3/' + 'e5526285ebd741b1819393f772f11fc3') + version_url = 'http://192.168.122.127:8776/' + expected = (api_versions.APIVersion('3.0'), + api_versions.APIVersion('3.16')) + self._check_get_server_version_v3(volume_url, version_url, expected) + + def test_get_server_version_v3_dedicated_port_https(self): + volume_url = ('https://192.168.122.127:8776/v3/' + 'e55285ebd741b1819393f772f11fc3') + version_url = 'https://192.168.122.127:8776/' + expected = (api_versions.APIVersion('3.0'), + api_versions.APIVersion('3.16')) + self._check_get_server_version_v3(volume_url, version_url, expected) + + def test_get_server_version_v3_path(self): + volume_url = ('http://192.168.122.127/volumes/v3/' + 'e5526285ebd741b1819393f772f11fc3') + version_url = 'http://192.168.122.127/volumes/' + expected = (api_versions.APIVersion('3.0'), + api_versions.APIVersion('3.16')) + self._check_get_server_version_v3(volume_url, version_url, expected) + + def test_get_server_version_v3_without_project_id(self): + volume_url = 'http://192.168.122.127/volumes/v3/' + version_url = 'http://192.168.122.127/volumes/' + expected = (api_versions.APIVersion('3.0'), + api_versions.APIVersion('3.16')) + self._check_get_server_version_v3(volume_url, version_url, expected) + + @mock.patch.object(api.cinder, 'cinderclient') + def test_get_server_version_v2(self, mock_cinderclient): + versions = {'versions': [x for x in self.cinder_versions.list() + if x['id'] == 'v2.0']} + cinder_client = mock_cinderclient.return_value + cinder_client.client.request.return_value = (200, versions) + + versions = api.cinder._get_server_version( + self.request, + 'http://192.168.122.127:8776/v2/e5526285ebd741b1819393f772f11fc3') + + self.assertEqual((api_versions.APIVersion('2.0'), + api_versions.APIVersion('2.0')), + versions) + cinder_client.client.request.assert_called_once_with( + 'http://192.168.122.127:8776/', 'GET') + + @mock.patch.object(api.cinder, 'cinderclient') + def test_get_server_version_exception(self, mock_cinderclient): + cinder_client = mock_cinderclient.return_value + cinder_client.client.request.side_effect = \ + cinder_exception.ClientException(500) + + versions = api.cinder._get_server_version( + self.request, + 'http://192.168.122.127:8776/v3/') + + self.assertEqual((api_versions.APIVersion('2.0'), + api_versions.APIVersion('2.0')), + versions) + cinder_client.client.request.assert_called_once_with( + 'http://192.168.122.127:8776/', 'GET') + class CinderApiVersionTests(test.TestCase): diff --git a/releasenotes/notes/cinder-ssl-deployment-e4dcd6fc0027c96a.yaml b/releasenotes/notes/cinder-ssl-deployment-e4dcd6fc0027c96a.yaml new file mode 100644 index 0000000000..d429dd3337 --- /dev/null +++ b/releasenotes/notes/cinder-ssl-deployment-e4dcd6fc0027c96a.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + [:bug:`1744670`] + Previously when a custom SSL CA is used horizon cannot retrieve volume + and snapshot information from cinder. It is fixed now and a custom CA + is handled properly in horizon when communicating with cinder.