diff --git a/manila/share/drivers/netapp/dataontap/client/api.py b/manila/share/drivers/netapp/dataontap/client/api.py index 833a501416..4c7691d540 100644 --- a/manila/share/drivers/netapp/dataontap/client/api.py +++ b/manila/share/drivers/netapp/dataontap/client/api.py @@ -23,8 +23,9 @@ import re from lxml import etree from oslo_log import log +import requests +from requests import auth import six -from six.moves import urllib from manila import exception from manila.i18n import _ @@ -61,6 +62,7 @@ class NaServer(object): TRANSPORT_TYPE_HTTP = 'http' TRANSPORT_TYPE_HTTPS = 'https' + SSL_CERT_DEFAULT = "/etc/ssl/certs/" SERVER_TYPE_FILER = 'filer' SERVER_TYPE_DFM = 'dfm' URL_FILER = 'servlets/netapp.servlets.admin.XMLrequest_filer' @@ -232,8 +234,8 @@ class NaServer(object): """Invoke the API on the server.""" if na_element and not isinstance(na_element, NaElement): ValueError('NaElement must be supplied to invoke API') - request, request_element = self._create_request(na_element, - enable_tunneling) + request_element = self._create_request(na_element, enable_tunneling) + request_d = request_element.to_string() api_name = na_element.get_name() api_name_matches_regex = (re.match(self._api_trace_pattern, api_name) @@ -242,23 +244,26 @@ class NaServer(object): if self._trace and api_name_matches_regex: LOG.debug("Request: %s", request_element.to_string(pretty=True)) - if (not hasattr(self, '_opener') or not self._opener + if (not hasattr(self, '_session') or not self._session or self._refresh_conn): - self._build_opener() + self._build_session() try: if hasattr(self, '_timeout'): - response = self._opener.open(request, timeout=self._timeout) + response = self._session.post( + self._get_url(), data=request_d, timeout=self._timeout) else: - response = self._opener.open(request) - except urllib.error.HTTPError as e: - raise NaApiError(e.code, e.msg) - except urllib.error.URLError as e: + response = self._session.post( + self._get_url(), data=request_d) + except requests.HTTPError as e: + raise NaApiError(e.errno, e.strerror) + except requests.URLRequired as e: raise exception.StorageCommunicationException(six.text_type(e)) except Exception as e: raise NaApiError(message=e) - response_xml = response.read() - response_element = self._get_result(response_xml) + response_xml = response.text + response_element = self._get_result( + bytes(bytearray(response_xml, encoding='utf-8'))) if self._trace and api_name_matches_regex: LOG.debug("Response: %s", response_element.to_string(pretty=True)) @@ -296,11 +301,7 @@ class NaServer(object): if enable_tunneling: self._enable_tunnel_request(netapp_elem) netapp_elem.add_child_elem(na_element) - request_d = netapp_elem.to_string() - request = urllib.request.Request( - self._get_url(), data=request_d, - headers={'Content-Type': 'text/xml', 'charset': 'utf-8'}) - return request, netapp_elem + return netapp_elem def _enable_tunnel_request(self, netapp_elem): """Enables vserver or vfiler tunneling.""" @@ -341,20 +342,20 @@ class NaServer(object): host = '[%s]' % host return '%s://%s:%s/%s' % (self._protocol, host, self._port, self._url) - def _build_opener(self): + def _build_session(self): if self._auth_style == NaServer.STYLE_LOGIN_PASSWORD: auth_handler = self._create_basic_auth_handler() else: auth_handler = self._create_certificate_auth_handler() - opener = urllib.request.build_opener(auth_handler) - self._opener = opener + + self._session = requests.Session() + self._session.auth = auth_handler + self._session.verify = NaServer.SSL_CERT_DEFAULT + self._session.headers = { + 'Content-Type': 'text/xml', 'charset': 'utf-8'} def _create_basic_auth_handler(self): - password_man = urllib.request.HTTPPasswordMgrWithDefaultRealm() - password_man.add_password(None, self._get_url(), self._username, - self._password) - auth_handler = urllib.request.HTTPBasicAuthHandler(password_man) - return auth_handler + return auth.HTTPBasicAuth(self._username, self._password) def _create_certificate_auth_handler(self): raise NotImplementedError() diff --git a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py index 2135931c2e..5211941ecf 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/fakes.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/fakes.py @@ -15,7 +15,7 @@ from unittest import mock from lxml import etree -from six.moves import urllib +import requests from manila.share.drivers.netapp.dataontap.client import api @@ -2638,7 +2638,7 @@ FAKE_RESULT_API_ERRNO_VALID.add_attr('errno', '14956') FAKE_RESULT_SUCCESS = api.NaElement('result') FAKE_RESULT_SUCCESS.add_attr('status', 'passed') -FAKE_HTTP_OPENER = urllib.request.build_opener() +FAKE_HTTP_SESSION = requests.Session() FAKE_MANAGE_VOLUME = { 'aggregate': SHARE_AGGREGATE_NAME, diff --git a/manila/tests/share/drivers/netapp/dataontap/client/test_api.py b/manila/tests/share/drivers/netapp/dataontap/client/test_api.py index eaf88a782a..d92fca4fc9 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/test_api.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/test_api.py @@ -22,7 +22,7 @@ Tests for NetApp API layer from unittest import mock import ddt -from six.moves import urllib +import requests from manila import exception from manila.share.drivers.netapp.dataontap.client import api @@ -190,14 +190,12 @@ class NetAppApiServerTests(test.TestCase): """Tests handling of HTTPError""" na_element = fake.FAKE_NA_ELEMENT self.mock_object(self.root, '_create_request', mock.Mock( - return_value=('abc', fake.FAKE_NA_ELEMENT))) + return_value=fake.FAKE_NA_ELEMENT)) self.mock_object(api, 'LOG') - self.root._opener = fake.FAKE_HTTP_OPENER - self.mock_object(self.root, '_build_opener') - self.mock_object(self.root._opener, 'open', mock.Mock( - side_effect=urllib.error.HTTPError(url='', hdrs='', - fp=None, code='401', - msg='httperror'))) + self.root._session = fake.FAKE_HTTP_SESSION + self.mock_object(self.root, '_build_session') + self.mock_object(self.root._session, 'post', mock.Mock( + side_effect=requests.HTTPError())) self.assertRaises(api.NaApiError, self.root.invoke_elem, na_element) @@ -206,12 +204,12 @@ class NetAppApiServerTests(test.TestCase): """Tests handling of URLError""" na_element = fake.FAKE_NA_ELEMENT self.mock_object(self.root, '_create_request', mock.Mock( - return_value=('abc', fake.FAKE_NA_ELEMENT))) + return_value=fake.FAKE_NA_ELEMENT)) self.mock_object(api, 'LOG') - self.root._opener = fake.FAKE_HTTP_OPENER - self.mock_object(self.root, '_build_opener') - self.mock_object(self.root._opener, 'open', mock.Mock( - side_effect=urllib.error.URLError(reason='urlerror'))) + self.root._session = fake.FAKE_HTTP_SESSION + self.mock_object(self.root, '_build_session') + self.mock_object(self.root._session, 'post', mock.Mock( + side_effect=requests.URLRequired())) self.assertRaises(exception.StorageCommunicationException, self.root.invoke_elem, @@ -221,11 +219,11 @@ class NetAppApiServerTests(test.TestCase): """Tests handling of Unknown Exception""" na_element = fake.FAKE_NA_ELEMENT self.mock_object(self.root, '_create_request', mock.Mock( - return_value=('abc', fake.FAKE_NA_ELEMENT))) + return_value=fake.FAKE_NA_ELEMENT)) self.mock_object(api, 'LOG') - self.root._opener = fake.FAKE_HTTP_OPENER - self.mock_object(self.root, '_build_opener') - self.mock_object(self.root._opener, 'open', mock.Mock( + self.root._session = fake.FAKE_HTTP_SESSION + self.mock_object(self.root, '_build_session') + self.mock_object(self.root._session, 'post', mock.Mock( side_effect=Exception)) exception = self.assertRaises(api.NaApiError, self.root.invoke_elem, @@ -247,15 +245,18 @@ class NetAppApiServerTests(test.TestCase): self.root._trace = trace_enabled self.root._api_trace_pattern = trace_pattern self.mock_object(self.root, '_create_request', mock.Mock( - return_value=('abc', fake.FAKE_NA_ELEMENT))) + return_value=fake.FAKE_NA_ELEMENT)) self.mock_object(api, 'LOG') - self.root._opener = fake.FAKE_HTTP_OPENER - self.mock_object(self.root, '_build_opener') + self.root._session = fake.FAKE_HTTP_SESSION + self.mock_object(self.root, '_build_session') self.mock_object(self.root, '_get_result', mock.Mock( return_value=fake.FAKE_NA_ELEMENT)) - opener_mock = self.mock_object( - self.root._opener, 'open', mock.Mock()) - opener_mock.read.side_effect = ['resp1', 'resp2'] + + response = mock.Mock() + response.text = 'res1' + self.mock_object( + self.root._session, 'post', mock.Mock( + return_value=response)) self.root.invoke_elem(na_element) diff --git a/releasenotes/notes/bug-1878993-netapp-fix-https-3eddf9eb5b762f3a.yaml b/releasenotes/notes/bug-1878993-netapp-fix-https-3eddf9eb5b762f3a.yaml new file mode 100644 index 0000000000..0aa009660c --- /dev/null +++ b/releasenotes/notes/bug-1878993-netapp-fix-https-3eddf9eb5b762f3a.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed `bug #1878993 `_ + that caused a failure on HTTPS connections within NetApp backend using + python 3.7.