HTTP migrated

This commit is contained in:
Flavio Percoco 2014-02-12 16:15:32 +01:00
parent 95d24563c3
commit 8149a7d0dd
3 changed files with 75 additions and 120 deletions

View File

@ -243,7 +243,7 @@ def get_size_from_backend(context, uri):
"""Retrieves image size from backend specified by uri""" """Retrieves image size from backend specified by uri"""
loc = location.get_location_from_uri(uri) loc = location.get_location_from_uri(uri)
store = get_store_from_uri(context, uri, loc) store = get_store_from_uri(uri)
return store.get_size(loc) return store.get_size(loc)
@ -251,7 +251,7 @@ def get_size_from_backend(context, uri):
def delete_from_backend(context, uri, **kwargs): def delete_from_backend(context, uri, **kwargs):
"""Removes chunks of data from backend specified by uri""" """Removes chunks of data from backend specified by uri"""
loc = location.get_location_from_uri(uri) loc = location.get_location_from_uri(uri)
store = get_store_from_uri(context, uri, loc) store = get_store_from_uri(uri)
try: try:
return store.delete(loc) return store.delete(loc)

View File

@ -17,7 +17,8 @@ import httplib
import urlparse import urlparse
from glance.store.common import exception from glance.store.common import exception
import glance.store.openstack.common.log as logging from glance.store.openstack.common.gettextutils import _
from glance.store.openstack.common import log as logging
import glance.store.base import glance.store.base
import glance.store.location import glance.store.location
@ -84,9 +85,8 @@ class StoreLocation(glance.store.location.StoreLocation):
else: else:
self.user = None self.user = None
if netloc == '': if netloc == '':
reason = _("No address specified in HTTP URL") LOG.debug(_("No address specified in HTTP URL"))
LOG.debug(reason) raise exception.BadStoreUri(uri=uri)
raise exception.BadStoreUri(message=reason)
self.netloc = netloc self.netloc = netloc
self.path = path self.path = path
@ -153,7 +153,7 @@ class Store(glance.store.base.Store):
reason = (_("The HTTP URL exceeded %s maximum " reason = (_("The HTTP URL exceeded %s maximum "
"redirects.") % MAX_REDIRECTS) "redirects.") % MAX_REDIRECTS)
LOG.debug(reason) LOG.debug(reason)
raise exception.MaxRedirectsExceeded(redirects=MAX_REDIRECTS) raise exception.MaxRedirectsExceeded(message=reason)
loc = location.store_location loc = location.store_location
conn_class = self._get_conn_class(loc) conn_class = self._get_conn_class(loc)
conn = conn_class(loc.netloc) conn = conn_class(loc.netloc)
@ -162,17 +162,19 @@ class Store(glance.store.base.Store):
# Check for bad status codes # Check for bad status codes
if resp.status >= 400: if resp.status >= 400:
reason = _("HTTP URL returned a %s status code.") % resp.status reason = (_("HTTP URL %(url)s returned a %(status)s status code.") %
dict(url=loc.path, status=resp.status))
LOG.debug(reason) LOG.debug(reason)
raise exception.BadStoreUri(loc.path, reason) raise exception.BadStoreUri(message=reason)
location_header = resp.getheader("location") location_header = resp.getheader("location")
if location_header: if location_header:
if resp.status not in (301, 302): if resp.status not in (301, 302):
reason = (_("The HTTP URL attempted to redirect with an " reason = (_("The HTTP URL %(url)s attempted to redirect with an "
"invalid %s status code.") % resp.status) "invalid %(status)s status code.") %
dict(url=loc.path, status=resp.status))
LOG.debug(reason) LOG.debug(reason)
raise exception.BadStoreUri(loc.path, reason) raise exception.BadStoreUri(message=reason)
location_class = glance.store.location.Location location_class = glance.store.location.Location
new_loc = location_class(location.store_name, new_loc = location_class(location.store_name,
location.store_location.__class__, location.store_location.__class__,

View File

@ -13,87 +13,53 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from six.moves import xrange import httplib
import stubout import mock
import StringIO
from glance.store.common import exception from glance.store.common import exception
from glance import context from glance.store import delete_from_backend
from glance.db.sqlalchemy import api as db_api from glance.store import safe_delete_from_backend
from glance.registry.client.v1.api import configure_registry_client from glance.store._drivers import http
from glance.store import (delete_from_backend,
safe_delete_from_backend)
from glance.store.http import Store, MAX_REDIRECTS
from glance.store.location import get_location_from_uri from glance.store.location import get_location_from_uri
from glance.tests.unit import base from glance.store.tests import base
from glance.tests import utils, stubs as test_stubs
# The response stack is used to return designated responses in order; class FakeHTTPResponse(object):
# however when it's empty a default 200 OK response is returned from def __init__(self, status=200, headers=None, data=None, *args, **kwargs):
# FakeHTTPConnection below. data = data or 'I am a teapot, short and stout\n'
FAKE_RESPONSE_STACK = [] self.data = StringIO.StringIO(data)
self.read = self.data.read
self.status = status
self.headers = headers or {'content-length': len(data)}
def getheader(self, name, default=None):
return self.headers.get(name.lower(), default)
def getheaders(self):
return self.headers or {}
def read(self, amt):
self.data.read(amt)
def stub_out_http_backend(stubs): class TestHttpStore(base.StoreBaseTest):
"""
Stubs out the httplib.HTTPRequest.getresponse to return
faked-out data instead of grabbing actual contents of a resource
The stubbed getresponse() returns an iterator over
the data "I am a teapot, short and stout\n"
:param stubs: Set of stubout stubs
"""
class FakeHTTPConnection(object):
def __init__(self, *args, **kwargs):
pass
def getresponse(self):
if len(FAKE_RESPONSE_STACK):
return FAKE_RESPONSE_STACK.pop()
return utils.FakeHTTPResponse()
def request(self, *_args, **_kwargs):
pass
def close(self):
pass
def fake_get_conn_class(self, *args, **kwargs):
return FakeHTTPConnection
stubs.Set(Store, '_get_conn_class', fake_get_conn_class)
def stub_out_registry_image_update(stubs):
"""
Stubs an image update on the registry.
:param stubs: Set of stubout stubs
"""
test_stubs.stub_out_registry_server(stubs)
def fake_image_update(ctx, image_id, values, purge_props=False):
return {'properties': {}}
stubs.Set(db_api, 'image_update', fake_image_update)
class TestHttpStore(base.StoreClearingUnitTest):
def setUp(self): def setUp(self):
global FAKE_RESPONSE_STACK
FAKE_RESPONSE_STACK = []
self.config(default_store='http',
known_stores=['glance.store.http.Store'])
super(TestHttpStore, self).setUp() super(TestHttpStore, self).setUp()
self.stubs = stubout.StubOutForTesting() self.config(default_store='http', group='glance_store')
stub_out_http_backend(self.stubs) http.Store.CHUNKSIZE = 2
Store.CHUNKSIZE = 2 self.store = http.Store(self.conf)
self.store = Store()
configure_registry_client() response = mock.patch('httplib.HTTPConnection.getresponse')
self.response = response.start()
self.response.return_value = FakeHTTPResponse()
self.addCleanup(response.stop)
request = mock.patch('httplib.HTTPConnection.request')
self.request = request.start()
self.request.side_effect = lambda w, x, y, z: None
self.addCleanup(request.stop)
def test_http_get(self): def test_http_get(self):
uri = "http://netloc/path/to/file.tar.gz" uri = "http://netloc/path/to/file.tar.gz"
@ -109,18 +75,20 @@ class TestHttpStore(base.StoreClearingUnitTest):
# Add two layers of redirects to the response stack, which will # Add two layers of redirects to the response stack, which will
# return the default 200 OK with the expected data after resolving # return the default 200 OK with the expected data after resolving
# both redirects. # both redirects.
redirect_headers_1 = {"location": "http://example.com/teapot.img"} redirect1 = {"location": "http://example.com/teapot.img"}
redirect_resp_1 = utils.FakeHTTPResponse(status=302, redirect2 = {"location": "http://example.com/teapot_real.img"}
headers=redirect_headers_1) responses = [FakeHTTPResponse(status=302, headers=redirect1),
redirect_headers_2 = {"location": "http://example.com/teapot_real.img"} FakeHTTPResponse(status=301, headers=redirect2),
redirect_resp_2 = utils.FakeHTTPResponse(status=301, FakeHTTPResponse()]
headers=redirect_headers_2)
FAKE_RESPONSE_STACK.append(redirect_resp_1) def getresponse():
FAKE_RESPONSE_STACK.append(redirect_resp_2) return responses.pop()
self.response.side_effect = getresponse
uri = "http://netloc/path/to/file.tar.gz" uri = "http://netloc/path/to/file.tar.gz"
expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s', expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n'] 'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n']
loc = get_location_from_uri(uri) loc = get_location_from_uri(uri)
(image_file, image_size) = self.store.get(loc) (image_file, image_size) = self.store.get(loc)
self.assertEqual(image_size, 31) self.assertEqual(image_size, 31)
@ -129,60 +97,45 @@ class TestHttpStore(base.StoreClearingUnitTest):
self.assertEqual(chunks, expected_returns) self.assertEqual(chunks, expected_returns)
def test_http_get_max_redirects(self): def test_http_get_max_redirects(self):
# Add more than MAX_REDIRECTS redirects to the response stack redirect = {"location": "http://example.com/teapot.img"}
redirect_headers = {"location": "http://example.com/teapot.img"} responses = ([FakeHTTPResponse(status=302, headers=redirect)]
redirect_resp = utils.FakeHTTPResponse(status=302, * (http.MAX_REDIRECTS + 2))
headers=redirect_headers)
for i in xrange(MAX_REDIRECTS + 2): def getresponse():
FAKE_RESPONSE_STACK.append(redirect_resp) return responses.pop()
self.response.side_effect = getresponse
uri = "http://netloc/path/to/file.tar.gz" uri = "http://netloc/path/to/file.tar.gz"
loc = get_location_from_uri(uri) loc = get_location_from_uri(uri)
self.assertRaises(exception.MaxRedirectsExceeded, self.store.get, loc) self.assertRaises(exception.MaxRedirectsExceeded, self.store.get, loc)
def test_http_get_redirect_invalid(self): def test_http_get_redirect_invalid(self):
redirect_headers = {"location": "http://example.com/teapot.img"} redirect = {"location": "http://example.com/teapot.img"}
redirect_resp = utils.FakeHTTPResponse(status=307, redirect_resp = FakeHTTPResponse(status=307, headers=redirect)
headers=redirect_headers) self.response.return_value = redirect_resp
FAKE_RESPONSE_STACK.append(redirect_resp)
uri = "http://netloc/path/to/file.tar.gz" uri = "http://netloc/path/to/file.tar.gz"
loc = get_location_from_uri(uri) loc = get_location_from_uri(uri)
self.assertRaises(exception.BadStoreUri, self.store.get, loc) self.assertRaises(exception.BadStoreUri, self.store.get, loc)
def test_http_get_not_found(self): def test_http_get_not_found(self):
not_found_resp = utils.FakeHTTPResponse(status=404, fake = FakeHTTPResponse(status=404, data="404 Not Found")
data="404 Not Found") self.response.return_value = fake
FAKE_RESPONSE_STACK.append(not_found_resp)
uri = "http://netloc/path/to/file.tar.gz" uri = "http://netloc/path/to/file.tar.gz"
loc = get_location_from_uri(uri) loc = get_location_from_uri(uri)
self.assertRaises(exception.BadStoreUri, self.store.get, loc) self.assertRaises(exception.BadStoreUri, self.store.get, loc)
def test_https_get(self):
uri = "https://netloc/path/to/file.tar.gz"
expected_returns = ['I ', 'am', ' a', ' t', 'ea', 'po', 't,', ' s',
'ho', 'rt', ' a', 'nd', ' s', 'to', 'ut', '\n']
loc = get_location_from_uri(uri)
(image_file, image_size) = self.store.get(loc)
self.assertEqual(image_size, 31)
chunks = [c for c in image_file]
self.assertEqual(chunks, expected_returns)
def test_http_delete_raise_error(self): def test_http_delete_raise_error(self):
uri = "https://netloc/path/to/file.tar.gz" uri = "https://netloc/path/to/file.tar.gz"
loc = get_location_from_uri(uri) loc = get_location_from_uri(uri)
ctx = context.RequestContext()
self.assertRaises(NotImplementedError, self.store.delete, loc) self.assertRaises(NotImplementedError, self.store.delete, loc)
self.assertRaises(exception.StoreDeleteNotSupported, self.assertRaises(exception.StoreDeleteNotSupported,
delete_from_backend, ctx, uri) delete_from_backend, {}, uri)
def test_http_schedule_delete_swallows_error(self): def test_http_schedule_delete_swallows_error(self):
uri = "https://netloc/path/to/file.tar.gz" uri = "https://netloc/path/to/file.tar.gz"
ctx = context.RequestContext()
stub_out_registry_image_update(self.stubs)
try: try:
safe_delete_from_backend(ctx, uri, 'image_id') safe_delete_from_backend({}, uri, 'image_id')
except exception.StoreDeleteNotSupported: except exception.StoreDeleteNotSupported:
self.fail('StoreDeleteNotSupported should be swallowed') self.fail('StoreDeleteNotSupported should be swallowed')