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"""
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)
@ -251,7 +251,7 @@ def get_size_from_backend(context, uri):
def delete_from_backend(context, uri, **kwargs):
"""Removes chunks of data from backend specified by uri"""
loc = location.get_location_from_uri(uri)
store = get_store_from_uri(context, uri, loc)
store = get_store_from_uri(uri)
try:
return store.delete(loc)

View File

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

View File

@ -13,87 +13,53 @@
# License for the specific language governing permissions and limitations
# under the License.
from six.moves import xrange
import stubout
import httplib
import mock
import StringIO
from glance.store.common import exception
from glance import context
from glance.db.sqlalchemy import api as db_api
from glance.registry.client.v1.api import configure_registry_client
from glance.store import (delete_from_backend,
safe_delete_from_backend)
from glance.store.http import Store, MAX_REDIRECTS
from glance.store import delete_from_backend
from glance.store import safe_delete_from_backend
from glance.store._drivers import http
from glance.store.location import get_location_from_uri
from glance.tests.unit import base
from glance.tests import utils, stubs as test_stubs
from glance.store.tests import base
# The response stack is used to return designated responses in order;
# however when it's empty a default 200 OK response is returned from
# FakeHTTPConnection below.
FAKE_RESPONSE_STACK = []
class FakeHTTPResponse(object):
def __init__(self, status=200, headers=None, data=None, *args, **kwargs):
data = data or 'I am a teapot, short and stout\n'
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):
"""
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):
class TestHttpStore(base.StoreBaseTest):
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()
self.stubs = stubout.StubOutForTesting()
stub_out_http_backend(self.stubs)
Store.CHUNKSIZE = 2
self.store = Store()
configure_registry_client()
self.config(default_store='http', group='glance_store')
http.Store.CHUNKSIZE = 2
self.store = http.Store(self.conf)
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):
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
# return the default 200 OK with the expected data after resolving
# both redirects.
redirect_headers_1 = {"location": "http://example.com/teapot.img"}
redirect_resp_1 = utils.FakeHTTPResponse(status=302,
headers=redirect_headers_1)
redirect_headers_2 = {"location": "http://example.com/teapot_real.img"}
redirect_resp_2 = utils.FakeHTTPResponse(status=301,
headers=redirect_headers_2)
FAKE_RESPONSE_STACK.append(redirect_resp_1)
FAKE_RESPONSE_STACK.append(redirect_resp_2)
redirect1 = {"location": "http://example.com/teapot.img"}
redirect2 = {"location": "http://example.com/teapot_real.img"}
responses = [FakeHTTPResponse(status=302, headers=redirect1),
FakeHTTPResponse(status=301, headers=redirect2),
FakeHTTPResponse()]
def getresponse():
return responses.pop()
self.response.side_effect = getresponse
uri = "http://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)
@ -129,60 +97,45 @@ class TestHttpStore(base.StoreClearingUnitTest):
self.assertEqual(chunks, expected_returns)
def test_http_get_max_redirects(self):
# Add more than MAX_REDIRECTS redirects to the response stack
redirect_headers = {"location": "http://example.com/teapot.img"}
redirect_resp = utils.FakeHTTPResponse(status=302,
headers=redirect_headers)
for i in xrange(MAX_REDIRECTS + 2):
FAKE_RESPONSE_STACK.append(redirect_resp)
redirect = {"location": "http://example.com/teapot.img"}
responses = ([FakeHTTPResponse(status=302, headers=redirect)]
* (http.MAX_REDIRECTS + 2))
def getresponse():
return responses.pop()
self.response.side_effect = getresponse
uri = "http://netloc/path/to/file.tar.gz"
loc = get_location_from_uri(uri)
self.assertRaises(exception.MaxRedirectsExceeded, self.store.get, loc)
def test_http_get_redirect_invalid(self):
redirect_headers = {"location": "http://example.com/teapot.img"}
redirect_resp = utils.FakeHTTPResponse(status=307,
headers=redirect_headers)
FAKE_RESPONSE_STACK.append(redirect_resp)
redirect = {"location": "http://example.com/teapot.img"}
redirect_resp = FakeHTTPResponse(status=307, headers=redirect)
self.response.return_value = redirect_resp
uri = "http://netloc/path/to/file.tar.gz"
loc = get_location_from_uri(uri)
self.assertRaises(exception.BadStoreUri, self.store.get, loc)
def test_http_get_not_found(self):
not_found_resp = utils.FakeHTTPResponse(status=404,
data="404 Not Found")
FAKE_RESPONSE_STACK.append(not_found_resp)
fake = FakeHTTPResponse(status=404, data="404 Not Found")
self.response.return_value = fake
uri = "http://netloc/path/to/file.tar.gz"
loc = get_location_from_uri(uri)
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):
uri = "https://netloc/path/to/file.tar.gz"
loc = get_location_from_uri(uri)
ctx = context.RequestContext()
self.assertRaises(NotImplementedError, self.store.delete, loc)
self.assertRaises(exception.StoreDeleteNotSupported,
delete_from_backend, ctx, uri)
delete_from_backend, {}, uri)
def test_http_schedule_delete_swallows_error(self):
uri = "https://netloc/path/to/file.tar.gz"
ctx = context.RequestContext()
stub_out_registry_image_update(self.stubs)
try:
safe_delete_from_backend(ctx, uri, 'image_id')
safe_delete_from_backend({}, uri, 'image_id')
except exception.StoreDeleteNotSupported:
self.fail('StoreDeleteNotSupported should be swallowed')