diff options
author | Jenkins <jenkins@review.openstack.org> | 2016-02-26 13:37:24 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2016-02-26 13:37:24 +0000 |
commit | 0379fa4c5b567db45b54ecb989de7b42c013799d (patch) | |
tree | c3ff5fe66cac81c2d149c6c9ab7ab0f30cc4efdc | |
parent | 4d355a1a69a5da069b54de85b0352d97a1dff58e (diff) | |
parent | 91636e8b85de680ea1347b60b1c2a27022c0f26f (diff) | |
download | glance_store-0379fa4c5b567db45b54ecb989de7b42c013799d.tar.gz |
Merge "Switch VMWare Datastore to use Requests"
-rw-r--r-- | glance_store/_drivers/vmware_datastore.py | 220 | ||||
-rw-r--r-- | glance_store/tests/unit/test_vmware_store.py | 195 | ||||
-rw-r--r-- | glance_store/tests/utils.py | 4 | ||||
-rw-r--r-- | releasenotes/notes/vmware-store-requests-369485d2cfdb6175.yaml | 6 |
4 files changed, 206 insertions, 219 deletions
diff --git a/glance_store/_drivers/vmware_datastore.py b/glance_store/_drivers/vmware_datastore.py index 98bd0c8..819fc56 100644 --- a/glance_store/_drivers/vmware_datastore.py +++ b/glance_store/_drivers/vmware_datastore.py @@ -31,15 +31,20 @@ try: from oslo_vmware import vim_util except ImportError: api = None -from six.moves import http_client + from six.moves import urllib +import six.moves.urllib.parse as urlparse +import requests +from requests import adapters +from requests.packages.urllib3.util import retry import six # NOTE(jokke): simplified transition to py3, behaves like py2 xrange from six.moves import range import glance_store from glance_store import capabilities +from glance_store.common import utils from glance_store import exceptions from glance_store.i18n import _ from glance_store.i18n import _LE @@ -48,6 +53,7 @@ from glance_store import location LOG = logging.getLogger(__name__) +CHUNKSIZE = 1024 * 64 # 64kB MAX_REDIRECTS = 5 DEFAULT_STORE_IMAGE_DIR = '/openstack_glance' DS_URL_PREFIX = '/folder' @@ -138,49 +144,6 @@ class _Reader(object): return self._size -class _ChunkReader(_Reader): - - def __init__(self, data, verifier=None, blocksize=8192): - self.blocksize = blocksize - self.current_chunk = b"" - self.closed = False - super(_ChunkReader, self).__init__(data, verifier) - - def read(self, size=None): - ret = b"" - while size is None or size >= len(self.current_chunk): - ret += self.current_chunk - if size is not None: - size -= len(self.current_chunk) - if self.closed: - self.current_chunk = b"" - break - self._get_chunk() - else: - ret += self.current_chunk[:size] - self.current_chunk = self.current_chunk[size:] - return ret - - def _get_chunk(self): - if not self.closed: - chunk = self.data.read(self.blocksize) - chunk_len = len(chunk) - self._size += chunk_len - self.checksum.update(chunk) - if self.verifier: - self.verifier.update(chunk) - if chunk: - if six.PY3: - size_header = ('%x\r\n' % chunk_len).encode('ascii') - self.current_chunk = b''.join((size_header, chunk, - b'\r\n')) - else: - self.current_chunk = b'%x\r\n%s\r\n' % (chunk_len, chunk) - else: - self.current_chunk = b'0\r\n\r\n' - self.closed = True - - class StoreLocation(location.StoreLocation): """Class describing an VMware URI. @@ -248,6 +211,16 @@ class StoreLocation(location.StoreLocation): if ds_name: self.datastore_name = ds_name[0] + @property + def https_url(self): + """ + Creates a https url that can be used to upload/download data from a + vmware store. + """ + parsed_url = urlparse.urlparse(self.get_uri()) + new_url = parsed_url._replace(scheme='https') + return urlparse.urlunparse(new_url) + class Store(glance_store.Store): """An implementation of the VMware datastore adapter.""" @@ -445,15 +418,13 @@ class Store(glance_store.Store): are 201 Created and 200 OK. """ ds = self.select_datastore(image_size) + image_file = _Reader(image_file, verifier) + headers = {} if image_size > 0: - headers = {'Content-Length': image_size} - image_file = _Reader(image_file, verifier) + headers.update({'Content-Length': image_size}) + data = image_file else: - # NOTE (arnaud): use chunk encoding when the image is still being - # generated by the server (ex: stream optimized disks generated by - # Nova). - headers = {'Transfer-Encoding': 'chunked'} - image_file = _ChunkReader(image_file, verifier) + data = utils.chunkiter(image_file, CHUNKSIZE) loc = StoreLocation({'scheme': self.scheme, 'server_host': self.server_host, 'image_dir': self.store_image_dir, @@ -463,13 +434,15 @@ class Store(glance_store.Store): # NOTE(arnaud): use a decorator when the config is not tied to self cookie = self._build_vim_cookie_header(True) headers = dict(headers) - headers['Cookie'] = cookie - conn_class = self._get_http_conn_class() - conn = conn_class(loc.server_host) - url = urllib.parse.quote('%s?%s' % (loc.path, loc.query)) + headers.update({'Cookie': cookie}) + session = new_session(self.api_insecure) + + url = loc.https_url try: - conn.request('PUT', url, image_file, headers) + response = session.put(url, data=data, headers=headers) except IOError as e: + # TODO(sigmavirus24): Figure out what the new exception type would + # be in requests. # When a session is not authenticated, the socket is closed by # the server after sending the response. http_client has an open # issue with https that raises Broken Pipe @@ -482,17 +455,19 @@ class Store(glance_store.Store): 'url': url, 'e': e} LOG.error(msg) + raise exceptions.BackendException(msg) except Exception: with excutils.save_and_reraise_exception(): LOG.exception(_LE('Failed to upload content of image ' '%(image)s'), {'image': image_id}) - res = conn.getresponse() - if res.status == http_client.CONFLICT: + + res = response.raw + if res.status == requests.codes.conflict: raise exceptions.Duplicate(_("Image file %(image_id)s already " "exists!") % {'image_id': image_id}) - if res.status not in (http_client.CREATED, http_client.OK): + if res.status not in (requests.codes.created, requests.codes.ok): msg = (_LE('Failed to upload content of image %(image)s. ' 'The request returned an unexpected status: %(status)s.' '\nThe response body:\n%(body)s') % @@ -534,7 +509,15 @@ class Store(glance_store.Store): :param location: `glance_store.location.Location` object, supplied from glance_store.location.get_location_from_uri() """ - return self._query(location, 'HEAD')[2] + conn = None + try: + conn, resp, size = self._query(location, 'HEAD') + return size + finally: + # NOTE(sabari): Close the connection as the request was made with + # stream=True. + if conn is not None: + conn.close() @capabilities.check def delete(self, location, context=None): @@ -566,30 +549,59 @@ class Store(glance_store.Store): LOG.exception(_LE('Failed to delete image %(image)s ' 'content.') % {'image': location.image_id}) - def _query(self, location, method, depth=0): - if depth > MAX_REDIRECTS: + def _query(self, location, method): + session = new_session(self.api_insecure) + loc = location.store_location + redirects_followed = 0 + # TODO(sabari): The redirect logic was added to handle cases when the + # backend redirects http url's to https. But the store never makes a + # http request and hence this can be safely removed. + while redirects_followed < MAX_REDIRECTS: + conn, resp = self._retry_request(session, method, location) + + # NOTE(sigmavirus24): _retry_request handles 4xx and 5xx errors so + # if the response is not a redirect, we can return early. + if not conn.is_redirect: + break + + redirects_followed += 1 + + location_header = conn.headers.get('location') + if location_header: + if resp.status not in (301, 302): + reason = (_("The HTTP URL %(path)s attempted to redirect " + "with an invalid %(status)s status code.") + % {'path': loc.path, 'status': resp.status}) + LOG.info(reason) + raise exceptions.BadStoreUri(message=reason) + conn.close() + location = self._new_location(location, location_header) + else: + # NOTE(sigmavirus24): We exceeded the maximum number of redirects msg = ("The HTTP URL exceeded %(max_redirects)s maximum " "redirects.", {'max_redirects': MAX_REDIRECTS}) LOG.debug(msg) raise exceptions.MaxRedirectsExceeded(redirects=MAX_REDIRECTS) + + content_length = int(resp.getheader('content-length', 0)) + + return (conn, resp, content_length) + + def _retry_request(self, session, method, location): loc = location.store_location # NOTE(arnaud): use a decorator when the config is not tied to self for i in range(self.api_retry_count + 1): cookie = self._build_vim_cookie_header() headers = {'Cookie': cookie} - try: - conn = self._get_http_conn(method, loc, headers) - resp = conn.getresponse() - except Exception: - with excutils.save_and_reraise_exception(): - LOG.exception(_LE('Failed to access image %(image)s ' - 'content.') % {'image': - location.image_id}) + conn = session.request(method, loc.https_url, headers=headers, + stream=True) + resp = conn.raw + if resp.status >= 400: - if resp.status == http_client.UNAUTHORIZED: + if resp.status == requests.codes.unauthorized: self.reset_session() continue - if resp.status == http_client.NOT_FOUND: + if resp.status == requests.codes.not_found: reason = _('VMware datastore could not find image at URI.') LOG.info(reason) raise exceptions.NotFound(message=reason) @@ -598,34 +610,36 @@ class Store(glance_store.Store): LOG.debug(msg) raise exceptions.BadStoreUri(msg) break - location_header = resp.getheader('location') - if location_header: - if resp.status not in (301, 302): - reason = (_("The HTTP URL %(path)s attempted to redirect " - "with an invalid %(status)s status code.") - % {'path': loc.path, 'status': resp.status}) - LOG.info(reason) - raise exceptions.BadStoreUri(message=reason) - location_class = glance_store.location.Location - new_loc = location_class(location.store_name, - location.store_location.__class__, - uri=location_header, - image_id=location.image_id, - store_specs=location.store_specs) - return self._query(new_loc, method, depth + 1) - content_length = int(resp.getheader('content-length', 0)) - - return (conn, resp, content_length) - - def _get_http_conn(self, method, loc, headers, content=None): - conn_class = self._get_http_conn_class() - conn = conn_class(loc.server_host) - url = urllib.parse.quote('%s?%s' % (loc.path, loc.query)) - conn.request(method, url, content, headers) - - return conn - - def _get_http_conn_class(self): - if self.api_insecure: - return http_client.HTTPConnection - return http_client.HTTPSConnection + return conn, resp + + def _new_location(self, old_location, url): + store_name = old_location.store_name + store_class = old_location.store_location.__class__ + image_id = old_location.image_id + store_specs = old_location.store_specs + # Note(sabari): The redirect url will have a scheme 'http(s)', but the + # store only accepts url with scheme 'vsphere'. Thus, replacing with + # store's scheme. + parsed_url = urlparse.urlparse(url) + new_url = parsed_url._replace(scheme='vsphere') + vsphere_url = urlparse.urlunparse(new_url) + return glance_store.location.Location(store_name, + store_class, + self.conf, + uri=vsphere_url, + image_id=image_id, + store_specs=store_specs) + + +def new_session(insecure=False, total_retries=None): + session = requests.Session() + if total_retries is not None: + http_adapter = adapters.HTTPAdapter( + max_retries=retry.Retry(total=total_retries)) + https_adapter = adapters.HTTPAdapter( + max_retries=retry.Retry(total=total_retries)) + session.mount('http://', http_adapter) + session.mount('https://', https_adapter) + if insecure: + session.verify = False + return session diff --git a/glance_store/tests/unit/test_vmware_store.py b/glance_store/tests/unit/test_vmware_store.py index 35507a1..3f95adb 100644 --- a/glance_store/tests/unit/test_vmware_store.py +++ b/glance_store/tests/unit/test_vmware_store.py @@ -65,24 +65,6 @@ def format_location(host_ip, folder_name, image_id, datastores): image_id, datacenter_path, datastore_name)) -class FakeHTTPConnection(object): - - def __init__(self, status=200, *args, **kwargs): - self.status = status - self.no_response_body = kwargs.get('no_response_body', False) - pass - - def getresponse(self): - return utils.FakeHTTPResponse(status=self.status, - no_response_body=self.no_response_body) - - def request(self, *_args, **_kwargs): - pass - - def close(self): - pass - - def fake_datastore_obj(*args, **kwargs): dc_obj = oslo_datacenter.Datacenter(ref='fake-ref', name='fake-name') @@ -130,8 +112,8 @@ class TestStore(base.StoreBaseTest, loc = location.get_location_from_uri( "vsphere://127.0.0.1/folder/openstack_glance/%s" "?dsName=ds1&dcPath=dc1" % FAKE_UUID, conf=self.conf) - with self._mock_http_connection() as HttpConn: - HttpConn.return_value = FakeHTTPConnection() + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.return_value = utils.fake_response() (image_file, image_size) = self.store.get(loc) self.assertEqual(image_size, expected_image_size) chunks = [c for c in image_file] @@ -146,8 +128,8 @@ class TestStore(base.StoreBaseTest, loc = location.get_location_from_uri( "vsphere://127.0.0.1/folder/openstack_glan" "ce/%s?dsName=ds1&dcPath=dc1" % FAKE_UUID, conf=self.conf) - with self._mock_http_connection() as HttpConn: - HttpConn.return_value = FakeHTTPConnection(status=404) + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.return_value = utils.fake_response(status_code=404) self.assertRaises(exceptions.NotFound, self.store.get, loc) @mock.patch.object(vm_store.Store, 'select_datastore') @@ -170,8 +152,8 @@ class TestStore(base.StoreBaseTest, expected_image_id, VMWARE_DS['vmware_datastores']) image = six.BytesIO(expected_contents) - with self._mock_http_connection() as HttpConn: - HttpConn.return_value = FakeHTTPConnection() + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.return_value = utils.fake_response() location, size, checksum, _ = self.store.add(expected_image_id, image, expected_size) @@ -204,8 +186,8 @@ class TestStore(base.StoreBaseTest, expected_image_id, VMWARE_DS['vmware_datastores']) image = six.BytesIO(expected_contents) - with self._mock_http_connection() as HttpConn: - HttpConn.return_value = FakeHTTPConnection() + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.return_value = utils.fake_response() location, size, checksum, _ = self.store.add(expected_image_id, image, 0) self.assertEqual(utils.sort_url_by_qs_keys(expected_location), @@ -222,14 +204,14 @@ class TestStore(base.StoreBaseTest, size = FIVE_KB contents = b"*" * size image = six.BytesIO(contents) - with self._mock_http_connection() as HttpConn: - HttpConn.return_value = FakeHTTPConnection() + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.return_value = utils.fake_response() self.store.add(image_id, image, size, verifier=verifier) fake_reader.assert_called_with(image, verifier) @mock.patch.object(vm_store.Store, 'select_datastore') - @mock.patch('glance_store._drivers.vmware_datastore._ChunkReader') + @mock.patch('glance_store._drivers.vmware_datastore._Reader') def test_add_with_verifier_size_zero(self, fake_reader, fake_select_ds): """Test that the verifier is passed to the _ChunkReader during add.""" verifier = mock.MagicMock(name='mock_verifier') @@ -237,8 +219,8 @@ class TestStore(base.StoreBaseTest, size = FIVE_KB contents = b"*" * size image = six.BytesIO(contents) - with self._mock_http_connection() as HttpConn: - HttpConn.return_value = FakeHTTPConnection() + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.return_value = utils.fake_response() self.store.add(image_id, image, 0, verifier=verifier) fake_reader.assert_called_with(image, verifier) @@ -249,12 +231,12 @@ class TestStore(base.StoreBaseTest, loc = location.get_location_from_uri( "vsphere://127.0.0.1/folder/openstack_glance/%s?" "dsName=ds1&dcPath=dc1" % FAKE_UUID, conf=self.conf) - with self._mock_http_connection() as HttpConn: - HttpConn.return_value = FakeHTTPConnection() + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.return_value = utils.fake_response() vm_store.Store._service_content = mock.Mock() self.store.delete(loc) - with self._mock_http_connection() as HttpConn: - HttpConn.return_value = FakeHTTPConnection(status=404) + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.return_value = utils.fake_response(status_code=404) self.assertRaises(exceptions.NotFound, self.store.get, loc) @mock.patch('oslo_vmware.api.VMwareAPISession') @@ -278,8 +260,8 @@ class TestStore(base.StoreBaseTest, loc = location.get_location_from_uri( "vsphere://127.0.0.1/folder/openstack_glance/%s" "?dsName=ds1&dcPath=dc1" % FAKE_UUID, conf=self.conf) - with self._mock_http_connection() as HttpConn: - HttpConn.return_value = FakeHTTPConnection() + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.return_value = utils.fake_response() image_size = self.store.get_size(loc) self.assertEqual(image_size, 31) @@ -292,8 +274,8 @@ class TestStore(base.StoreBaseTest, loc = location.get_location_from_uri( "vsphere://127.0.0.1/folder/openstack_glan" "ce/%s?dsName=ds1&dcPath=dc1" % FAKE_UUID, conf=self.conf) - with self._mock_http_connection() as HttpConn: - HttpConn.return_value = FakeHTTPConnection(status=404) + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.return_value = utils.fake_response(status_code=404) self.assertRaises(exceptions.NotFound, self.store.get_size, loc) def test_reader_full(self): @@ -324,76 +306,6 @@ class TestStore(base.StoreBaseTest, reader.read() verifier.update.assert_called_with(content) - def test_chunkreader_image_fits_in_blocksize(self): - """ - Test that the image file reader returns the expected chunk of data - when the block size is larger than the image. - """ - content = b'XXX' - image = six.BytesIO(content) - expected_checksum = hashlib.md5(content).hexdigest() - reader = vm_store._ChunkReader(image) - ret = reader.read() - if six.PY3: - expected_chunk = ('%x\r\n%s\r\n' - % (len(content), content.decode('ascii'))) - expected_chunk = expected_chunk.encode('ascii') - else: - expected_chunk = b'%x\r\n%s\r\n' % (len(content), content) - last_chunk = b'0\r\n\r\n' - self.assertEqual(expected_chunk + last_chunk, ret) - self.assertEqual(len(content), reader.size) - self.assertEqual(expected_checksum, reader.checksum.hexdigest()) - self.assertTrue(reader.closed) - ret = reader.read() - self.assertEqual(len(content), reader.size) - self.assertEqual(expected_checksum, reader.checksum.hexdigest()) - self.assertTrue(reader.closed) - self.assertEqual(b'', ret) - - def test_chunkreader_image_larger_blocksize(self): - """ - Test that the image file reader returns the expected chunks when - the block size specified is smaller than the image. - """ - content = b'XXX' - image = six.BytesIO(content) - expected_checksum = hashlib.md5(content).hexdigest() - last_chunk = b'0\r\n\r\n' - reader = vm_store._ChunkReader(image, blocksize=1) - ret = reader.read() - expected_chunk = b'1\r\nX\r\n' - expected = (expected_chunk + expected_chunk + expected_chunk - + last_chunk) - self.assertEqual(expected, - ret) - self.assertEqual(expected_checksum, reader.checksum.hexdigest()) - self.assertEqual(len(content), reader.size) - self.assertTrue(reader.closed) - - def test_chunkreader_size(self): - """Test that the image reader takes into account the specified size.""" - content = b'XXX' - image = six.BytesIO(content) - expected_checksum = hashlib.md5(content).hexdigest() - reader = vm_store._ChunkReader(image, blocksize=1) - ret = reader.read(size=3) - self.assertEqual(b'1\r\n', ret) - ret = reader.read(size=1) - self.assertEqual(b'X', ret) - ret = reader.read() - self.assertEqual(expected_checksum, reader.checksum.hexdigest()) - self.assertEqual(len(content), reader.size) - self.assertTrue(reader.closed) - - def test_chunkreader_with_verifier(self): - content = b'XXX' - image = six.BytesIO(content) - verifier = mock.MagicMock(name='mock_verifier') - reader = vm_store._ChunkReader(image, verifier) - reader.read(size=3) - verifier.update.assert_called_with(content) - def test_sanity_check_api_retry_count(self): """Test that sanity check raises if api_retry_count is <= 0.""" self.store.conf.glance_store.vmware_api_retry_count = -1 @@ -475,8 +387,8 @@ class TestStore(base.StoreBaseTest, expected_contents = b"*" * expected_size image = six.BytesIO(expected_contents) self.session = mock.Mock() - with self._mock_http_connection() as HttpConn: - HttpConn.return_value = FakeHTTPConnection(status=401) + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.return_value = utils.fake_response(status_code=401) self.assertRaises(exceptions.BackendException, self.store.add, expected_image_id, image, expected_size) @@ -491,8 +403,8 @@ class TestStore(base.StoreBaseTest, image = six.BytesIO(expected_contents) self.session = mock.Mock() with self._mock_http_connection() as HttpConn: - HttpConn.return_value = FakeHTTPConnection(status=500, - no_response_body=True) + HttpConn.return_value = utils.fake_response(status_code=500, + no_response_body=True) self.assertRaises(exceptions.BackendException, self.store.add, expected_image_id, image, expected_size) @@ -532,7 +444,7 @@ class TestStore(base.StoreBaseTest, expected_contents = b"*" * expected_size image = six.BytesIO(expected_contents) self.session = mock.Mock() - with self._mock_http_connection() as HttpConn: + with mock.patch('requests.Session.request') as HttpConn: HttpConn.request.side_effect = IOError self.assertRaises(exceptions.BackendException, self.store.add, @@ -660,3 +572,58 @@ class TestStore(base.StoreBaseTest, 'FindByInventoryPath', self.store.session.vim.service_content.searchIndex, inventoryPath=datacenter_path) + + @mock.patch('oslo_vmware.api.VMwareAPISession') + def test_http_get_redirect(self, mock_api_session): + # Add two layers of redirects to the response stack, which will + # return the default 200 OK with the expected data after resolving + # both redirects. + redirect1 = {"location": "https://example.com?dsName=ds1&dcPath=dc1"} + redirect2 = {"location": "https://example.com?dsName=ds2&dcPath=dc2"} + responses = [utils.fake_response(), + utils.fake_response(status_code=302, headers=redirect1), + utils.fake_response(status_code=301, headers=redirect2)] + + def getresponse(*args, **kwargs): + return responses.pop() + + expected_image_size = 31 + expected_returns = ['I am a teapot, short and stout\n'] + loc = location.get_location_from_uri( + "vsphere://127.0.0.1/folder/openstack_glance/%s" + "?dsName=ds1&dcPath=dc1" % FAKE_UUID, conf=self.conf) + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.side_effect = getresponse + (image_file, image_size) = self.store.get(loc) + self.assertEqual(image_size, expected_image_size) + chunks = [c for c in image_file] + self.assertEqual(expected_returns, chunks) + + @mock.patch('oslo_vmware.api.VMwareAPISession') + def test_http_get_max_redirects(self, mock_api_session): + redirect = {"location": "https://example.com?dsName=ds1&dcPath=dc1"} + responses = ([utils.fake_response(status_code=302, headers=redirect)] + * (vm_store.MAX_REDIRECTS + 1)) + + def getresponse(*args, **kwargs): + return responses.pop() + + loc = location.get_location_from_uri( + "vsphere://127.0.0.1/folder/openstack_glance/%s" + "?dsName=ds1&dcPath=dc1" % FAKE_UUID, conf=self.conf) + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.side_effect = getresponse + self.assertRaises(exceptions.MaxRedirectsExceeded, self.store.get, + loc) + + @mock.patch('oslo_vmware.api.VMwareAPISession') + def test_http_get_redirect_invalid(self, mock_api_session): + redirect = {"location": "https://example.com?dsName=ds1&dcPath=dc1"} + + loc = location.get_location_from_uri( + "vsphere://127.0.0.1/folder/openstack_glance/%s" + "?dsName=ds1&dcPath=dc1" % FAKE_UUID, conf=self.conf) + with mock.patch('requests.Session.request') as HttpConn: + HttpConn.return_value = utils.fake_response(status_code=307, + headers=redirect) + self.assertRaises(exceptions.BadStoreUri, self.store.get, loc) diff --git a/glance_store/tests/utils.py b/glance_store/tests/utils.py index 09e0d35..2f3a90f 100644 --- a/glance_store/tests/utils.py +++ b/glance_store/tests/utils.py @@ -67,9 +67,9 @@ class FakeHTTPResponse(object): self.data.close() -def fake_response(status_code=200, headers=None, content=None): +def fake_response(status_code=200, headers=None, content=None, **kwargs): r = requests.models.Response() r.status_code = status_code r.headers = headers or {} - r.raw = FakeHTTPResponse(status_code, headers, content) + r.raw = FakeHTTPResponse(status_code, headers, content, kwargs) return r diff --git a/releasenotes/notes/vmware-store-requests-369485d2cfdb6175.yaml b/releasenotes/notes/vmware-store-requests-369485d2cfdb6175.yaml new file mode 100644 index 0000000..060f3e5 --- /dev/null +++ b/releasenotes/notes/vmware-store-requests-369485d2cfdb6175.yaml @@ -0,0 +1,6 @@ +--- +security: + - Previously the VMWare Datastore was using HTTPS Connections from httplib + which do not verify the connection. By switching to using requests library + the VMware storage backend now verifies HTTPS connection to vCenter server + and thus addresses the vulnerabilities described in OSSN-0033. |