@@ -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 | |||
@@ -253,11 +254,60 @@ def get_microversion(request, features): | |||
version, cinder_url = _find_cinder_url(request) | |||
except exceptions.ServiceCatalogException: | |||
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) | |||
@@ -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", | |||
@@ -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): | |||
@@ -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. |