diff options
author | venkata edara <redara@redhat.com> | 2017-05-10 13:27:38 +0530 |
---|---|---|
committer | Prashanth Pai <ppai@redhat.com> | 2017-05-11 05:48:27 +0000 |
commit | 513988915aa1af13a989d062b021fe1562cbf18d (patch) | |
tree | 1c281911e3a9bfa97f8a7285f20691cb77c45c1d /test/unit | |
parent | e9c2c5eb55e1012ccce0ce51ac48bed0c0f1d4b7 (diff) |
Rebase to Swift 2.10.1 (newton)
Change-Id: I53a962c9a301089c8aed0b43c50f944c30225944
Signed-off-by: venkata edara <redara@redhat.com>
Reviewed-on: https://review.gluster.org/16653
Reviewed-by: Prashanth Pai <ppai@redhat.com>
Tested-by: Prashanth Pai <ppai@redhat.com>
Diffstat (limited to 'test/unit')
-rw-r--r-- | test/unit/__init__.py | 296 | ||||
-rw-r--r-- | test/unit/obj/test_diskfile.py | 2 | ||||
-rw-r--r-- | test/unit/obj/test_expirer.py | 162 | ||||
-rw-r--r-- | test/unit/proxy/controllers/test_account.py | 218 | ||||
-rw-r--r-- | test/unit/proxy/controllers/test_base.py | 741 | ||||
-rw-r--r-- | test/unit/proxy/controllers/test_container.py | 190 | ||||
-rw-r--r-- | test/unit/proxy/test_server.py | 20 |
7 files changed, 1184 insertions, 445 deletions
diff --git a/test/unit/__init__.py b/test/unit/__init__.py index 372fb58..ee2a262 100644 --- a/test/unit/__init__.py +++ b/test/unit/__init__.py @@ -15,10 +15,12 @@ """ Swift tests """ +from __future__ import print_function import os import copy import logging import errno +from six.moves import range import sys from contextlib import contextmanager, closing from collections import defaultdict, Iterable @@ -30,20 +32,28 @@ import eventlet from eventlet.green import socket from tempfile import mkdtemp from shutil import rmtree -from swift.common.utils import Timestamp +import signal +import json + + +from swift.common.utils import Timestamp, NOTICE from test import get_config -from swift.common import swob, utils +from swift.common import utils +from swift.common.header_key_dict import HeaderKeyDict from swift.common.ring import Ring, RingData from hashlib import md5 import logging.handlers -from httplib import HTTPException + +from six.moves.http_client import HTTPException from swift.common import storage_policy -from swift.common.storage_policy import StoragePolicy, ECStoragePolicy +from swift.common.storage_policy import (StoragePolicy, ECStoragePolicy, + VALID_EC_TYPES) import functools -import cPickle as pickle +import six.moves.cPickle as pickle from gzip import GzipFile import mock as mocklib import inspect +from nose import SkipTest EMPTY_ETAG = md5().hexdigest() @@ -53,6 +63,22 @@ if not os.path.basename(sys.argv[0]).startswith('swift'): utils.HASH_PATH_SUFFIX = 'endcap' +EC_TYPE_PREFERENCE = [ + 'liberasurecode_rs_vand', + 'jerasure_rs_vand', +] +for eclib_name in EC_TYPE_PREFERENCE: + if eclib_name in VALID_EC_TYPES: + break +else: + raise SystemExit('ERROR: unable to find suitable PyECLib type' + ' (none of %r found in %r)' % ( + EC_TYPE_PREFERENCE, + VALID_EC_TYPES, + )) +DEFAULT_TEST_EC_TYPE = eclib_name + + def patch_policies(thing_or_policies=None, legacy_only=False, with_ec_default=False, fake_ring_args=None): if isinstance(thing_or_policies, ( @@ -67,7 +93,7 @@ def patch_policies(thing_or_policies=None, legacy_only=False, elif with_ec_default: default_policies = [ ECStoragePolicy(0, name='ec', is_default=True, - ec_type='jerasure_rs_vand', ec_ndata=10, + ec_type=DEFAULT_TEST_EC_TYPE, ec_ndata=10, ec_nparity=4, ec_segment_size=4096), StoragePolicy(1, name='unu'), ] @@ -183,13 +209,6 @@ class FakeRing(Ring): def __init__(self, replicas=3, max_more_nodes=0, part_power=0, base_port=1000): - """ - :param part_power: make part calculation based on the path - - If you set a part_power when you setup your FakeRing the parts you get - out of ring methods will actually be based on the path - otherwise we - exercise the real ring code, but ignore the result and return 1. - """ self._base_port = base_port self.max_more_nodes = max_more_nodes self._part_shift = 32 - part_power @@ -207,7 +226,8 @@ class FakeRing(Ring): for x in range(self.replicas): ip = '10.0.0.%s' % x port = self._base_port + x - self._devs.append({ + # round trip through json to ensure unicode like real rings + self._devs.append(json.loads(json.dumps({ 'ip': ip, 'replication_ip': ip, 'port': port, @@ -216,7 +236,7 @@ class FakeRing(Ring): 'zone': x % 3, 'region': x % 2, 'id': x, - }) + }))) @property def replica_count(self): @@ -226,9 +246,7 @@ class FakeRing(Ring): return [dict(node, index=i) for i, node in enumerate(list(self._devs))] def get_more_nodes(self, part): - # replicas^2 is the true cap - for x in xrange(self.replicas, min(self.replicas + self.max_more_nodes, - self.replicas * self.replicas)): + for x in range(self.replicas, (self.replicas + self.max_more_nodes)): yield {'ip': '10.0.0.%s' % x, 'replication_ip': '10.0.0.%s' % x, 'port': self._base_port + x, @@ -244,9 +262,9 @@ def write_fake_ring(path, *devs): Pretty much just a two node, two replica, 2 part power ring... """ dev1 = {'id': 0, 'zone': 0, 'device': 'sda1', 'ip': '127.0.0.1', - 'port': 6000} + 'port': 6200} dev2 = {'id': 0, 'zone': 0, 'device': 'sdb1', 'ip': '127.0.0.1', - 'port': 6000} + 'port': 6200} dev1_updates, dev2_updates = devs or ({}, {}) @@ -266,7 +284,7 @@ class FabricatedRing(Ring): your tests needs. """ - def __init__(self, replicas=6, devices=8, nodes=4, port=6000, + def __init__(self, replicas=6, devices=8, nodes=4, port=6200, part_power=4): self.devices = devices self.nodes = nodes @@ -459,6 +477,12 @@ class UnmockTimeModule(object): logging.time = UnmockTimeModule() +class WARN_DEPRECATED(Exception): + def __init__(self, msg): + self.msg = msg + print(self.msg) + + class FakeLogger(logging.Logger, object): # a thread safe fake logger @@ -478,8 +502,21 @@ class FakeLogger(logging.Logger, object): logging.INFO: 'info', logging.DEBUG: 'debug', logging.CRITICAL: 'critical', + NOTICE: 'notice', } + def warn(self, *args, **kwargs): + raise WARN_DEPRECATED("Deprecated Method warn use warning instead") + + def notice(self, msg, *args, **kwargs): + """ + Convenience function for syslog priority LOG_NOTICE. The python + logging lvl is set to 25, just above info. SysLogHandler is + monkey patched to map this log lvl to the LOG_NOTICE syslog + priority. + """ + self.log(NOTICE, msg, *args, **kwargs) + def _log(self, level, msg, *args, **kwargs): store_name = self.store_in[level] cargs = [msg] @@ -495,7 +532,9 @@ class FakeLogger(logging.Logger, object): def _clear(self): self.log_dict = defaultdict(list) self.lines_dict = {'critical': [], 'error': [], 'info': [], - 'warning': [], 'debug': []} + 'warning': [], 'debug': [], 'notice': []} + + clear = _clear # this is a public interface def get_lines_for_level(self, level): if level not in self.lines_dict: @@ -560,8 +599,8 @@ class FakeLogger(logging.Logger, object): try: line = record.getMessage() except TypeError: - print 'WARNING: unable to format log message %r %% %r' % ( - record.msg, record.args) + print('WARNING: unable to format log message %r %% %r' % ( + record.msg, record.args)) raise self.lines_dict[record.levelname.lower()].append(line) @@ -575,17 +614,24 @@ class FakeLogger(logging.Logger, object): pass +class DebugSwiftLogFormatter(utils.SwiftLogFormatter): + + def format(self, record): + msg = super(DebugSwiftLogFormatter, self).format(record) + return msg.replace('#012', '\n') + + class DebugLogger(FakeLogger): """A simple stdout logging version of FakeLogger""" def __init__(self, *args, **kwargs): FakeLogger.__init__(self, *args, **kwargs) - self.formatter = logging.Formatter( + self.formatter = DebugSwiftLogFormatter( "%(server)s %(levelname)s: %(message)s") def handle(self, record): self._handle(record) - print self.formatter.format(record) + print(self.formatter.format(record)) class DebugLogAdapter(utils.LogAdapter): @@ -704,6 +750,74 @@ def mock(update): delattr(module, attr) +class FakeStatus(object): + """ + This will work with our fake_http_connect, if you hand in one of these + instead of a status int or status int tuple to the "codes" iter you can + add some eventlet sleep to the expect and response stages of the + connection. + """ + + def __init__(self, status, expect_sleep=None, response_sleep=None): + """ + :param status: the response status int, or a tuple of + ([expect_status, ...], response_status) + :param expect_sleep: float, time to eventlet sleep during expect, can + be a iter of floats + :param response_sleep: float, time to eventlet sleep during response + """ + # connect exception + if isinstance(status, (Exception, eventlet.Timeout)): + raise status + if isinstance(status, tuple): + self.expect_status = list(status[:-1]) + self.status = status[-1] + self.explicit_expect_list = True + else: + self.expect_status, self.status = ([], status) + self.explicit_expect_list = False + if not self.expect_status: + # when a swift backend service returns a status before reading + # from the body (mostly an error response) eventlet.wsgi will + # respond with that status line immediately instead of 100 + # Continue, even if the client sent the Expect 100 header. + # BufferedHttp and the proxy both see these error statuses + # when they call getexpect, so our FakeConn tries to act like + # our backend services and return certain types of responses + # as expect statuses just like a real backend server would do. + if self.status in (507, 412, 409): + self.expect_status = [status] + else: + self.expect_status = [100, 100] + + # setup sleep attributes + if not isinstance(expect_sleep, (list, tuple)): + expect_sleep = [expect_sleep] * len(self.expect_status) + self.expect_sleep_list = list(expect_sleep) + while len(self.expect_sleep_list) < len(self.expect_status): + self.expect_sleep_list.append(None) + self.response_sleep = response_sleep + + def get_response_status(self): + if self.response_sleep is not None: + eventlet.sleep(self.response_sleep) + if self.expect_status and self.explicit_expect_list: + raise Exception('Test did not consume all fake ' + 'expect status: %r' % (self.expect_status,)) + if isinstance(self.status, (Exception, eventlet.Timeout)): + raise self.status + return self.status + + def get_expect_status(self): + expect_sleep = self.expect_sleep_list.pop(0) + if expect_sleep is not None: + eventlet.sleep(expect_sleep) + expect_status = self.expect_status.pop(0) + if isinstance(expect_status, (Exception, eventlet.Timeout)): + raise expect_status + return expect_status + + class SlowBody(object): """ This will work with our fake_http_connect, if you hand in these @@ -740,30 +854,10 @@ def fake_http_connect(*code_iter, **kwargs): def __init__(self, status, etag=None, body='', timestamp='1', headers=None, expect_headers=None, connection_id=None, - give_send=None): - # connect exception - if isinstance(status, (Exception, eventlet.Timeout)): - raise status - if isinstance(status, tuple): - self.expect_status = list(status[:-1]) - self.status = status[-1] - self.explicit_expect_list = True - else: - self.expect_status, self.status = ([], status) - self.explicit_expect_list = False - if not self.expect_status: - # when a swift backend service returns a status before reading - # from the body (mostly an error response) eventlet.wsgi will - # respond with that status line immediately instead of 100 - # Continue, even if the client sent the Expect 100 header. - # BufferedHttp and the proxy both see these error statuses - # when they call getexpect, so our FakeConn tries to act like - # our backend services and return certain types of responses - # as expect statuses just like a real backend server would do. - if self.status in (507, 412, 409): - self.expect_status = [status] - else: - self.expect_status = [100, 100] + give_send=None, give_expect=None): + if not isinstance(status, FakeStatus): + status = FakeStatus(status) + self._status = status self.reason = 'Fake' self.host = '1.2.3.4' self.port = '1234' @@ -776,6 +870,8 @@ def fake_http_connect(*code_iter, **kwargs): self.timestamp = timestamp self.connection_id = connection_id self.give_send = give_send + self.give_expect = give_expect + self.closed = False if 'slow' in kwargs and isinstance(kwargs['slow'], list): try: self._next_sleep = kwargs['slow'].pop(0) @@ -785,11 +881,6 @@ def fake_http_connect(*code_iter, **kwargs): eventlet.sleep() def getresponse(self): - if self.expect_status and self.explicit_expect_list: - raise Exception('Test did not consume all fake ' - 'expect status: %r' % (self.expect_status,)) - if isinstance(self.status, (Exception, eventlet.Timeout)): - raise self.status exc = kwargs.get('raise_exc') if exc: if isinstance(exc, (Exception, eventlet.Timeout)): @@ -797,16 +888,21 @@ def fake_http_connect(*code_iter, **kwargs): raise Exception('test') if kwargs.get('raise_timeout_exc'): raise eventlet.Timeout() + self.status = self._status.get_response_status() return self def getexpect(self): - expect_status = self.expect_status.pop(0) - if isinstance(self.expect_status, (Exception, eventlet.Timeout)): - raise self.expect_status + if self.give_expect: + self.give_expect(self) + expect_status = self._status.get_expect_status() headers = dict(self.expect_headers) if expect_status == 409: headers['X-Backend-Timestamp'] = self.timestamp - return FakeConn(expect_status, headers=headers) + response = FakeConn(expect_status, + timestamp=self.timestamp, + headers=headers) + response.status = expect_status + return response def getheaders(self): etag = self.etag @@ -816,7 +912,7 @@ def fake_http_connect(*code_iter, **kwargs): else: etag = '"68b329da9893e34099c7d8ad5cb9c940"' - headers = swob.HeaderKeyDict({ + headers = HeaderKeyDict({ 'content-length': len(self.body), 'content-type': 'x-application/test', 'x-timestamp': self.timestamp, @@ -834,7 +930,7 @@ def fake_http_connect(*code_iter, **kwargs): # when timestamp is None, HeaderKeyDict raises KeyError headers.pop('x-timestamp', None) try: - if container_ts_iter.next() is False: + if next(container_ts_iter) is False: headers['x-container-timestamp'] = '1' except StopIteration: pass @@ -865,9 +961,9 @@ def fake_http_connect(*code_iter, **kwargs): self.body = self.body[amt:] return rv - def send(self, amt=None): + def send(self, data=None): if self.give_send: - self.give_send(self.connection_id, amt) + self.give_send(self, data) am_slow, value = self.get_slow() if am_slow: if self.received < 4: @@ -875,10 +971,10 @@ def fake_http_connect(*code_iter, **kwargs): eventlet.sleep(value) def getheader(self, name, default=None): - return swob.HeaderKeyDict(self.getheaders()).get(name, default) + return HeaderKeyDict(self.getheaders()).get(name, default) def close(self): - pass + self.closed = True timestamps_iter = iter(kwargs.get('timestamps') or ['1'] * len(code_iter)) etag_iter = iter(kwargs.get('etags') or [None] * len(code_iter)) @@ -911,27 +1007,28 @@ def fake_http_connect(*code_iter, **kwargs): kwargs['give_content_type'](args[6]['Content-Type']) else: kwargs['give_content_type']('') - i, status = conn_id_and_code_iter.next() + i, status = next(conn_id_and_code_iter) if 'give_connect' in kwargs: give_conn_fn = kwargs['give_connect'] argspec = inspect.getargspec(give_conn_fn) if argspec.keywords or 'connection_id' in argspec.args: ckwargs['connection_id'] = i give_conn_fn(*args, **ckwargs) - etag = etag_iter.next() - headers = headers_iter.next() - expect_headers = expect_headers_iter.next() - timestamp = timestamps_iter.next() + etag = next(etag_iter) + headers = next(headers_iter) + expect_headers = next(expect_headers_iter) + timestamp = next(timestamps_iter) if status <= 0: raise HTTPException() if body_iter is None: body = static_body or '' else: - body = body_iter.next() + body = next(body_iter) return FakeConn(status, etag, body=body, timestamp=timestamp, headers=headers, expect_headers=expect_headers, - connection_id=i, give_send=kwargs.get('give_send')) + connection_id=i, give_send=kwargs.get('give_send'), + give_expect=kwargs.get('give_expect')) connect.code_iter = code_iter @@ -966,3 +1063,58 @@ def mocked_http_conn(*args, **kwargs): def make_timestamp_iter(): return iter(Timestamp(t) for t in itertools.count(int(time.time()))) + + +class Timeout(object): + def __init__(self, seconds): + self.seconds = seconds + + def __enter__(self): + signal.signal(signal.SIGALRM, self._exit) + signal.alarm(self.seconds) + + def __exit__(self, type, value, traceback): + signal.alarm(0) + + def _exit(self, signum, frame): + class TimeoutException(Exception): + pass + raise TimeoutException + + +def requires_o_tmpfile_support(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + if not utils.o_tmpfile_supported(): + raise SkipTest('Requires O_TMPFILE support') + return func(*args, **kwargs) + return wrapper + + +def encode_frag_archive_bodies(policy, body): + """ + Given a stub body produce a list of complete frag_archive bodies as + strings in frag_index order. + + :param policy: a StoragePolicy instance, with policy_type EC_POLICY + :param body: a string, the body to encode into frag archives + + :returns: list of strings, the complete frag_archive bodies for the given + plaintext + """ + segment_size = policy.ec_segment_size + # split up the body into buffers + chunks = [body[x:x + segment_size] + for x in range(0, len(body), segment_size)] + # encode the buffers into fragment payloads + fragment_payloads = [] + for chunk in chunks: + fragments = policy.pyeclib_driver.encode(chunk) + if not fragments: + break + fragment_payloads.append(fragments) + + # join up the fragment payloads per node + ec_archive_bodies = [''.join(frags) + for frags in zip(*fragment_payloads)] + return ec_archive_bodies diff --git a/test/unit/obj/test_diskfile.py b/test/unit/obj/test_diskfile.py index 9701f44..2aed737 100644 --- a/test/unit/obj/test_diskfile.py +++ b/test/unit/obj/test_diskfile.py @@ -31,7 +31,7 @@ from gluster.swift.common.exceptions import AlreadyExistsAsDir, \ AlreadyExistsAsFile from swift.common.exceptions import DiskFileNoSpace, DiskFileNotOpen, \ DiskFileNotExist, DiskFileExpired -from swift.common.utils import ThreadPool +from gluster.swift.common.utils import ThreadPool import gluster.swift.common.utils from gluster.swift.common.utils import normalize_timestamp diff --git a/test/unit/obj/test_expirer.py b/test/unit/obj/test_expirer.py index 9701027..4830a90 100644 --- a/test/unit/obj/test_expirer.py +++ b/test/unit/obj/test_expirer.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import urllib from time import time from unittest import main, TestCase from test.unit import FakeRing, mocked_http_conn, debug_logger @@ -22,8 +21,10 @@ from tempfile import mkdtemp from shutil import rmtree import mock +import six +from six.moves import urllib -from swift.common import internal_client, utils +from swift.common import internal_client, utils, swob from swift.obj import expirer @@ -54,7 +55,7 @@ class TestObjectExpirer(TestCase): self.rcache = mkdtemp() self.conf = {'recon_cache_path': self.rcache} - self.logger = debug_logger('test-recon') + self.logger = debug_logger('test-expirer') def tearDown(self): rmtree(self.rcache) @@ -88,10 +89,16 @@ class TestObjectExpirer(TestCase): } # from config x = expirer.ObjectExpirer(vals) - self.assertRaises(ValueError, x.get_process_values, {}) + expected_msg = 'process must be an integer greater' \ + ' than or equal to 0' + with self.assertRaises(ValueError) as ctx: + x.get_process_values({}) + self.assertEqual(str(ctx.exception), expected_msg) # from kwargs x = expirer.ObjectExpirer({}) - self.assertRaises(ValueError, x.get_process_values, vals) + with self.assertRaises(ValueError) as ctx: + x.get_process_values(vals) + self.assertEqual(str(ctx.exception), expected_msg) def test_get_process_values_negative_processes(self): vals = { @@ -100,10 +107,16 @@ class TestObjectExpirer(TestCase): } # from config x = expirer.ObjectExpirer(vals) - self.assertRaises(ValueError, x.get_process_values, {}) + expected_msg = 'processes must be an integer greater' \ + ' than or equal to 0' + with self.assertRaises(ValueError) as ctx: + x.get_process_values({}) + self.assertEqual(str(ctx.exception), expected_msg) # from kwargs x = expirer.ObjectExpirer({}) - self.assertRaises(ValueError, x.get_process_values, vals) + with self.assertRaises(ValueError) as ctx: + x.get_process_values(vals) + self.assertEqual(str(ctx.exception), expected_msg) def test_get_process_values_process_greater_than_processes(self): vals = { @@ -112,10 +125,32 @@ class TestObjectExpirer(TestCase): } # from config x = expirer.ObjectExpirer(vals) - self.assertRaises(ValueError, x.get_process_values, {}) + expected_msg = 'process must be less than processes' + with self.assertRaises(ValueError) as ctx: + x.get_process_values({}) + self.assertEqual(str(ctx.exception), expected_msg) # from kwargs x = expirer.ObjectExpirer({}) - self.assertRaises(ValueError, x.get_process_values, vals) + with self.assertRaises(ValueError) as ctx: + x.get_process_values(vals) + self.assertEqual(str(ctx.exception), expected_msg) + + def test_get_process_values_process_equal_to_processes(self): + vals = { + 'processes': 5, + 'process': 5, + } + # from config + x = expirer.ObjectExpirer(vals) + expected_msg = 'process must be less than processes' + with self.assertRaises(ValueError) as ctx: + x.get_process_values({}) + self.assertEqual(str(ctx.exception), expected_msg) + # from kwargs + x = expirer.ObjectExpirer({}) + with self.assertRaises(ValueError) as ctx: + x.get_process_values(vals) + self.assertEqual(str(ctx.exception), expected_msg) def test_init_concurrency_too_small(self): conf = { @@ -153,10 +188,11 @@ class TestObjectExpirer(TestCase): sum([len(self.containers[x]) for x in self.containers]) def iter_containers(self, *a, **kw): - return [{'name': unicode(x)} for x in self.containers.keys()] + return [{'name': six.text_type(x)} + for x in self.containers.keys()] def iter_objects(self, account, container): - return [{'name': unicode(x)} + return [{'name': six.text_type(x)} for x in self.containers[container]] def delete_container(*a, **kw): @@ -172,7 +208,7 @@ class TestObjectExpirer(TestCase): x.swift = InternalClient(containers) deleted_objects = {} - for i in xrange(3): + for i in range(3): x.process = i x.run_once() self.assertNotEqual(deleted_objects, x.deleted_objects) @@ -183,52 +219,55 @@ class TestObjectExpirer(TestCase): self.assertEqual(len(set(x.obj_containers_in_order[:4])), 4) def test_delete_object(self): - class InternalClient(object): - - container_ring = None - - def __init__(self, test, account, container, obj): - self.test = test - self.account = account - self.container = container - self.obj = obj - self.delete_object_called = False - - class DeleteActualObject(object): - def __init__(self, test, actual_obj, timestamp): - self.test = test - self.actual_obj = actual_obj - self.timestamp = timestamp - self.called = False - - def __call__(self, actual_obj, timestamp): - self.test.assertEqual(self.actual_obj, actual_obj) - self.test.assertEqual(self.timestamp, timestamp) - self.called = True - + x = expirer.ObjectExpirer({}, logger=self.logger) + actual_obj = 'actual_obj' + timestamp = int(time()) + reclaim_ts = timestamp - x.reclaim_age container = 'container' obj = 'obj' - actual_obj = 'actual_obj' - timestamp = 'timestamp' - - x = expirer.ObjectExpirer({}, logger=self.logger) - x.swift = \ - InternalClient(self, x.expiring_objects_account, container, obj) - x.delete_actual_object = \ - DeleteActualObject(self, actual_obj, timestamp) - delete_object_called = [] - - def pop_queue(c, o): - self.assertEqual(container, c) - self.assertEqual(obj, o) - delete_object_called[:] = [True] - - x.pop_queue = pop_queue - - x.delete_object(actual_obj, timestamp, container, obj) - self.assertTrue(delete_object_called) - self.assertTrue(x.delete_actual_object.called) + http_exc = { + resp_code: + internal_client.UnexpectedResponse( + str(resp_code), swob.HTTPException(status=resp_code)) + for resp_code in {404, 412, 500} + } + exc_other = Exception() + + def check_call_to_delete_object(exc, ts, should_pop): + x.logger.clear() + start_reports = x.report_objects + with mock.patch.object(x, 'delete_actual_object', + side_effect=exc) as delete_actual: + with mock.patch.object(x, 'pop_queue') as pop_queue: + x.delete_object(actual_obj, ts, container, obj) + + delete_actual.assert_called_once_with(actual_obj, ts) + log_lines = x.logger.get_lines_for_level('error') + if should_pop: + pop_queue.assert_called_once_with(container, obj) + self.assertEqual(start_reports + 1, x.report_objects) + self.assertFalse(log_lines) + else: + self.assertFalse(pop_queue.called) + self.assertEqual(start_reports, x.report_objects) + self.assertEqual(1, len(log_lines)) + self.assertIn('Exception while deleting object container obj', + log_lines[0]) + + # verify pop_queue logic on exceptions + for exc, ts, should_pop in [(None, timestamp, True), + (http_exc[404], timestamp, False), + (http_exc[412], timestamp, False), + (http_exc[500], reclaim_ts, False), + (exc_other, reclaim_ts, False), + (http_exc[404], reclaim_ts, True), + (http_exc[412], reclaim_ts, True)]: + + try: + check_call_to_delete_object(exc, ts, should_pop) + except AssertionError as err: + self.fail("Failed on %r at %f: %s" % (exc, ts, err)) def test_report(self): x = expirer.ObjectExpirer({}, logger=self.logger) @@ -525,7 +564,7 @@ class TestObjectExpirer(TestCase): got_unicode = [False] def delete_actual_object_test_for_unicode(actual_obj, timestamp): - if isinstance(actual_obj, unicode): + if isinstance(actual_obj, six.text_type): got_unicode[0] = True fake_swift = InternalClient( @@ -673,6 +712,8 @@ class TestObjectExpirer(TestCase): ts = '1234' x.delete_actual_object('/path/to/object', ts) self.assertEqual(got_env[0]['HTTP_X_IF_DELETE_AT'], ts) + self.assertEqual(got_env[0]['HTTP_X_TIMESTAMP'], + got_env[0]['HTTP_X_IF_DELETE_AT']) def test_delete_actual_object_nourlquoting(self): # delete_actual_object should not do its own url quoting because @@ -690,6 +731,8 @@ class TestObjectExpirer(TestCase): ts = '1234' x.delete_actual_object('/path/to/object name', ts) self.assertEqual(got_env[0]['HTTP_X_IF_DELETE_AT'], ts) + self.assertEqual(got_env[0]['HTTP_X_TIMESTAMP'], + got_env[0]['HTTP_X_IF_DELETE_AT']) self.assertEqual(got_env[0]['PATH_INFO'], '/v1/path/to/object name') def test_delete_actual_object_raises_404(self): @@ -704,7 +747,7 @@ class TestObjectExpirer(TestCase): self.assertRaises(internal_client.UnexpectedResponse, x.delete_actual_object, '/path/to/object', '1234') - def test_delete_actual_object_handles_412(self): + def test_delete_actual_object_raises_412(self): def fake_app(env, start_response): start_response('412 Precondition Failed', @@ -714,7 +757,8 @@ class TestObjectExpirer(TestCase): internal_client.loadapp = lambda *a, **kw: fake_app x = expirer.ObjectExpirer({}) - x.delete_actual_object('/path/to/object', '1234') + self.assertRaises(internal_client.UnexpectedResponse, + x.delete_actual_object, '/path/to/object', '1234') def test_delete_actual_object_does_not_handle_odd_stuff(self): @@ -744,7 +788,7 @@ class TestObjectExpirer(TestCase): x.delete_actual_object(name, timestamp) self.assertEqual(x.swift.make_request.call_count, 1) self.assertEqual(x.swift.make_request.call_args[0][1], - '/v1/' + urllib.quote(name)) + '/v1/' + urllib.parse.quote(name)) def test_pop_queue(self): class InternalClient(object): diff --git a/test/unit/proxy/controllers/test_account.py b/test/unit/proxy/controllers/test_account.py index 23ad0a1..a46dcc9 100644 --- a/test/unit/proxy/controllers/test_account.py +++ b/test/unit/proxy/controllers/test_account.py @@ -25,6 +25,7 @@ from test.unit import fake_http_connect, FakeRing, FakeMemcache from swift.common.storage_policy import StoragePolicy from swift.common.request_helpers import get_sys_meta_prefix import swift.proxy.controllers.base +from swift.proxy.controllers.base import get_account_info from test.unit import patch_policies @@ -36,6 +37,31 @@ class TestAccountController(unittest.TestCase): None, FakeMemcache(), account_ring=FakeRing(), container_ring=FakeRing()) + def _make_callback_func(self, context): + def callback(ipaddr, port, device, partition, method, path, + headers=None, query_string=None, ssl=False): + context['method'] = method + context['path'] = path + context['headers'] = headers or {} + return callback + + def _assert_responses(self, method, test_cases): + if method in ('PUT', 'DELETE'): + self.app.allow_account_management = True + controller = proxy_server.AccountController(self.app, 'AUTH_bob') + + for responses, expected in test_cases: + with mock.patch( + 'swift.proxy.controllers.base.http_connect', + fake_http_connect(*responses)): + req = Request.blank('/v1/AUTH_bob') + resp = getattr(controller, method)(req) + + self.assertEqual(expected, + resp.status_int, + 'Expected %s but got %s. Failed case: %s' % + (expected, resp.status_int, str(responses))) + def test_account_info_in_response_env(self): controller = proxy_server.AccountController(self.app, 'AUTH_bob') with mock.patch('swift.proxy.controllers.base.http_connect', @@ -43,9 +69,10 @@ class TestAccountController(unittest.TestCase): req = Request.blank('/v1/AUTH_bob', {'PATH_INFO': '/v1/AUTH_bob'}) resp = controller.HEAD(req) self.assertEqual(2, resp.status_int // 100) - self.assertTrue('swift.account/AUTH_bob' in resp.environ) - self.assertEqual(headers_to_account_info(resp.headers), - resp.environ['swift.account/AUTH_bob']) + self.assertIn('account/AUTH_bob', resp.environ['swift.infocache']) + self.assertEqual( + headers_to_account_info(resp.headers), + resp.environ['swift.infocache']['account/AUTH_bob']) def test_swift_owner(self): owner_headers = { @@ -57,17 +84,17 @@ class TestAccountController(unittest.TestCase): with mock.patch('swift.proxy.controllers.base.http_connect', fake_http_connect(200, headers=owner_headers)): resp = controller.HEAD(req) - self.assertEquals(2, resp.status_int // 100) + self.assertEqual(2, resp.status_int // 100) for key in owner_headers: - self.assertTrue(key not in resp.headers) + self.assertNotIn(key, resp.headers) req = Request.blank('/v1/a', environ={'swift_owner': True}) with mock.patch('swift.proxy.controllers.base.http_connect', fake_http_connect(200, headers=owner_headers)): resp = controller.HEAD(req) - self.assertEquals(2, resp.status_int // 100) + self.assertEqual(2, resp.status_int // 100) for key in owner_headers: - self.assertTrue(key in resp.headers) + self.assertIn(key, resp.headers) def test_get_deleted_account(self): resp_headers = { @@ -79,7 +106,7 @@ class TestAccountController(unittest.TestCase): with mock.patch('swift.proxy.controllers.base.http_connect', fake_http_connect(404, headers=resp_headers)): resp = controller.HEAD(req) - self.assertEquals(410, resp.status_int) + self.assertEqual(410, resp.status_int) def test_long_acct_names(self): long_acct_name = '%sLongAccountName' % ( @@ -90,25 +117,17 @@ class TestAccountController(unittest.TestCase): with mock.patch('swift.proxy.controllers.base.http_connect', fake_http_connect(200)): resp = controller.HEAD(req) - self.assertEquals(400, resp.status_int) + self.assertEqual(400, resp.status_int) with mock.patch('swift.proxy.controllers.base.http_connect', fake_http_connect(200)): resp = controller.GET(req) - self.assertEquals(400, resp.status_int) + self.assertEqual(400, resp.status_int) with mock.patch('swift.proxy.controllers.base.http_connect', fake_http_connect(200)): resp = controller.POST(req) - self.assertEquals(400, resp.status_int) - - def _make_callback_func(self, context): - def callback(ipaddr, port, device, partition, method, path, - headers=None, query_string=None, ssl=False): - context['method'] = method - context['path'] = path - context['headers'] = headers or {} - return callback + self.assertEqual(400, resp.status_int) def test_sys_meta_headers_PUT(self): # check that headers in sys meta namespace make it through @@ -129,9 +148,9 @@ class TestAccountController(unittest.TestCase): fake_http_connect(200, 200, give_connect=callback)): controller.PUT(req) self.assertEqual(context['method'], 'PUT') - self.assertTrue(sys_meta_key in context['headers']) + self.assertIn(sys_meta_key, context['headers']) self.assertEqual(context['headers'][sys_meta_key], 'foo') - self.assertTrue(user_meta_key in context['headers']) + self.assertIn(user_meta_key, context['headers']) self.assertEqual(context['headers'][user_meta_key], 'bar') self.assertNotEqual(context['headers']['x-timestamp'], '1.0') @@ -152,9 +171,9 @@ class TestAccountController(unittest.TestCase): fake_http_connect(200, 200, give_connect=callback)): controller.POST(req) self.assertEqual(context['method'], 'POST') - self.assertTrue(sys_meta_key in context['headers']) + self.assertIn(sys_meta_key, context['headers']) self.assertEqual(context['headers'][sys_meta_key], 'foo') - self.assertTrue(user_meta_key in context['headers']) + self.assertIn(user_meta_key, context['headers']) self.assertEqual(context['headers'][user_meta_key], 'bar') self.assertNotEqual(context['headers']['x-timestamp'], '1.0') @@ -193,7 +212,7 @@ class TestAccountController(unittest.TestCase): self.assertEqual(resp.headers.get(header), value) else: # blank ACLs should result in no header - self.assert_(header not in resp.headers) + self.assertNotIn(header, resp.headers) def test_add_acls_impossible_cases(self): # For test coverage: verify that defensive coding does defend, in cases @@ -208,19 +227,25 @@ class TestAccountController(unittest.TestCase): self.assertEqual(1, len(resp.headers)) # we always get Content-Type self.assertEqual(2, len(resp.environ)) - def test_memcache_key_impossible_cases(self): + def test_cache_key_impossible_cases(self): # For test coverage: verify that defensive coding does defend, in cases # that shouldn't arise naturally - self.assertRaises( - ValueError, - lambda: swift.proxy.controllers.base.get_container_memcache_key( - '/a', None)) + with self.assertRaises(ValueError): + # Container needs account + swift.proxy.controllers.base.get_cache_key(None, 'c') + + with self.assertRaises(ValueError): + # Object needs account + swift.proxy.controllers.base.get_cache_key(None, 'c', 'o') + + with self.assertRaises(ValueError): + # Object needs container + swift.proxy.controllers.base.get_cache_key('a', None, 'o') def test_stripping_swift_admin_headers(self): # Verify that a GET/HEAD which receives privileged headers from the # account server will strip those headers for non-swift_owners - hdrs_ext, hdrs_int = self._make_user_and_sys_acl_headers_data() headers = { 'x-account-meta-harmless': 'hi mom', 'x-account-meta-temp-url-key': 's3kr1t', @@ -243,6 +268,139 @@ class TestAccountController(unittest.TestCase): 'x-account-meta-temp-url-key' in resp.headers) self.assertEqual(privileged_header_present, env['swift_owner']) + def test_response_code_for_PUT(self): + PUT_TEST_CASES = [ + ((201, 201, 201), 201), + ((201, 201, 404), 201), + ((201, 201, 503), 201), + ((201, 404, 404), 404), + ((201, 404, 503), 503), + ((201, 503, 503), 503), + ((404, 404, 404), 404), + ((404, 404, 503), 404), + ((404, 503, 503), 503), + ((503, 503, 503), 503) + ] + self._assert_responses('PUT', PUT_TEST_CASES) + + def test_response_code_for_DELETE(self): + DELETE_TEST_CASES = [ + ((204, 204, 204), 204), + ((204, 204, 404), 204), + ((204, 204, 503), 204), + ((204, 404, 404), 404), + ((204, 404, 503), 503), + ((204, 503, 503), 503), + ((404, 404, 404), 404), + ((404, 404, 503), 404), + ((404, 503, 503), 503), + ((503, 503, 503), 503) + ] + self._assert_responses('DELETE', DELETE_TEST_CASES) + + def test_response_code_for_POST(self): + POST_TEST_CASES = [ + ((204, 204, 204), 204), + ((204, 204, 404), 204), + ((204, 204, 503), 204), + ((204, 404, 404), 404), + ((204, 404, 503), 503), + ((204, 503, 503), 503), + ((404, 404, 404), 404), + ((404, 404, 503), 404), + ((404, 503, 503), 503), + ((503, 503, 503), 503) + ] + self._assert_responses('POST', POST_TEST_CASES) + + +@patch_policies( + [StoragePolicy(0, 'zero', True, object_ring=FakeRing(replicas=4))]) +class TestAccountController4Replicas(TestAccountController): + def setUp(self): + self.app = proxy_server.Application( + None, + FakeMemcache(), + account_ring=FakeRing(replicas=4), + container_ring=FakeRing(replicas=4)) + + def test_response_code_for_PUT(self): + PUT_TEST_CASES = [ + ((201, 201, 201, 201), 201), + ((201, 201, 201, 404), 201), + ((201, 201, 201, 503), 201), + ((201, 201, 404, 404), 201), + ((201, 201, 404, 503), 201), + ((201, 201, 503, 503), 201), + ((201, 404, 404, 404), 404), + ((201, 404, 404, 503), 404), + ((201, 404, 503, 503), 503), + ((201, 503, 503, 503), 503), + ((404, 404, 404, 404), 404), + ((404, 404, 404, 503), 404), + ((404, 404, 503, 503), 404), + ((404, 503, 503, 503), 503), + ((503, 503, 503, 503), 503) + ] + self._assert_responses('PUT', PUT_TEST_CASES) + + def test_response_code_for_DELETE(self): + DELETE_TEST_CASES = [ + ((204, 204, 204, 204), 204), + ((204, 204, 204, 404), 204), + ((204, 204, 204, 503), 204), + ((204, 204, 404, 404), 204), + ((204, 204, 404, 503), 204), + ((204, 204, 503, 503), 204), + ((204, 404, 404, 404), 404), + ((204, 404, 404, 503), 404), + ((204, 404, 503, 503), 503), + ((204, 503, 503, 503), 503), + ((404, 404, 404, 404), 404), + ((404, 404, 404, 503), 404), + ((404, 404, 503, 503), 404), + ((404, 503, 503, 503), 503), + ((503, 503, 503, 503), 503) + ] + self._assert_responses('DELETE', DELETE_TEST_CASES) + + def test_response_code_for_POST(self): + POST_TEST_CASES = [ + ((204, 204, 204, 204), 204), + ((204, 204, 204, 404), 204), + ((204, 204, 204, 503), 204), + ((204, 204, 404, 404), 204), + ((204, 204, 404, 503), 204), + ((204, 204, 503, 503), 204), + ((204, 404, 404, 404), 404), + ((204, 404, 404, 503), 404), + ((204, 404, 503, 503), 503), + ((204, 503, 503, 503), 503), + ((404, 404, 404, 404), 404), + ((404, 404, 404, 503), 404), + ((404, 404, 503, 503), 404), + ((404, 503, 503, 503), 503), + ((503, 503, 503, 503), 503) + ] + self._assert_responses('POST', POST_TEST_CASES) + + +@patch_policies([StoragePolicy(0, 'zero', True, object_ring=FakeRing())]) +class TestGetAccountInfo(unittest.TestCase): + def setUp(self): + self.app = proxy_server.Application( + None, FakeMemcache(), + account_ring=FakeRing(), container_ring=FakeRing()) + + def test_get_deleted_account_410(self): + resp_headers = {'x-account-status': 'deleted'} + + req = Request.blank('/v1/a') + with mock.patch('swift.proxy.controllers.base.http_connect', + fake_http_connect(404, headers=resp_headers)): + info = get_account_info(req.environ, self.app) + self.assertEqual(410, info.get('status')) + if __name__ == '__main__': unittest.main() diff --git a/test/unit/proxy/controllers/test_base.py b/test/unit/proxy/controllers/test_base.py index 037e28b..1ab0037 100644 --- a/test/unit/proxy/controllers/test_base.py +++ b/test/unit/proxy/controllers/test_base.py @@ -16,22 +16,22 @@ import itertools from collections import defaultdict import unittest -from mock import patch +import mock from swift.proxy.controllers.base import headers_to_container_info, \ headers_to_account_info, headers_to_object_info, get_container_info, \ - get_container_memcache_key, get_account_info, get_account_memcache_key, \ - get_object_env_key, get_info, get_object_info, \ - Controller, GetOrHeadHandler, _set_info_cache, _set_object_info_cache, \ - bytes_to_skip -from swift.common.swob import Request, HTTPException, HeaderKeyDict, \ - RESPONSE_REASONS + get_cache_key, get_account_info, get_info, get_object_info, \ + Controller, GetOrHeadHandler, bytes_to_skip +from swift.common.swob import Request, HTTPException, RESPONSE_REASONS from swift.common import exceptions from swift.common.utils import split_path +from swift.common.header_key_dict import HeaderKeyDict from swift.common.http import is_success from swift.common.storage_policy import StoragePolicy from test.unit import fake_http_connect, FakeRing, FakeMemcache from swift.proxy import server as proxy_server -from swift.common.request_helpers import get_sys_meta_prefix +from swift.common.request_helpers import ( + get_sys_meta_prefix, get_object_transient_sysmeta +) from test.unit import patch_policies @@ -95,7 +95,7 @@ class DynamicResponseFactory(object): def _get_response(self, type_): self.stats[type_] += 1 class_ = self.response_type[type_] - return class_(self.statuses.next()) + return class_(next(self.statuses)) def get_response(self, environ): (version, account, container, obj) = split_path( @@ -112,6 +112,31 @@ class DynamicResponseFactory(object): return resp +class ZeroCacheAccountResponse(FakeResponse): + base_headers = { + 'X-Backend-Recheck-Account-Existence': '0', + 'x-account-container-count': 333, + 'x-account-object-count': 1000, + 'x-account-bytes-used': 6666, + } + + +class ZeroCacheContainerResponse(FakeResponse): + base_headers = { + 'X-Backend-Recheck-Container-Existence': '0', + 'x-container-object-count': 1000, + 'x-container-bytes-used': 6666, + } + + +class ZeroCacheDynamicResponseFactory(DynamicResponseFactory): + response_type = { + 'obj': ObjectResponse, + 'container': ZeroCacheContainerResponse, + 'account': ZeroCacheAccountResponse, + } + + class FakeApp(object): recheck_container_existence = 30 @@ -120,23 +145,14 @@ class FakeApp(object): def __init__(self, response_factory=None, statuses=None): self.responses = response_factory or \ DynamicResponseFactory(*statuses or []) - self.sources = [] + self.captured_envs = [] def __call__(self, environ, start_response): - self.sources.append(environ.get('swift.source')) + self.captured_envs.append(environ) response = self.responses.get_response(environ) reason = RESPONSE_REASONS[response.status_int][0] start_response('%d %s' % (response.status_int, reason), [(k, v) for k, v in response.headers.items()]) - # It's a bit strnage, but the get_info cache stuff relies on the - # app setting some keys in the environment as it makes requests - # (in particular GETorHEAD_base) - so our fake does the same - _set_info_cache(self, environ, response.account, - response.container, response) - if response.obj: - _set_object_info_cache(self, environ, response.account, - response.container, response.obj, - response) return iter(response.body) @@ -158,40 +174,80 @@ class TestFuncs(unittest.TestCase): account_ring=FakeRing(), container_ring=FakeRing()) - def test_GETorHEAD_base(self): - base = Controller(self.app) - req = Request.blank('/v1/a/c/o/with/slashes') - ring = FakeRing() - nodes = list(ring.get_part_nodes(0)) + list(ring.get_more_nodes(0)) - with patch('swift.proxy.controllers.base.' - 'http_connect', fake_http_connect(200)): - resp = base.GETorHEAD_base(req, 'object', iter(nodes), 'part', - '/a/c/o/with/slashes') - self.assertTrue('swift.object/a/c/o/with/slashes' in resp.environ) - self.assertEqual( - resp.environ['swift.object/a/c/o/with/slashes']['status'], 200) - req = Request.blank('/v1/a/c/o') - with patch('swift.proxy.controllers.base.' - 'http_connect', fake_http_connect(200)): - resp = base.GETorHEAD_base(req, 'object', iter(nodes), 'part', - '/a/c/o') - self.assertTrue('swift.object/a/c/o' in resp.environ) - self.assertEqual(resp.environ['swift.object/a/c/o']['status'], 200) - req = Request.blank('/v1/a/c') - with patch('swift.proxy.controllers.base.' - 'http_connect', fake_http_connect(200)): - resp = base.GETorHEAD_base(req, 'container', iter(nodes), 'part', - '/a/c') - self.assertTrue('swift.container/a/c' in resp.environ) - self.assertEqual(resp.environ['swift.container/a/c']['status'], 200) - - req = Request.blank('/v1/a') - with patch('swift.proxy.controllers.base.' - 'http_connect', fake_http_connect(200)): - resp = base.GETorHEAD_base(req, 'account', iter(nodes), 'part', - '/a') - self.assertTrue('swift.account/a' in resp.environ) - self.assertEqual(resp.environ['swift.account/a']['status'], 200) + def test_get_info_zero_recheck(self): + mock_cache = mock.Mock() + mock_cache.get.return_value = None + app = FakeApp(ZeroCacheDynamicResponseFactory()) + env = {'swift.cache': mock_cache} + info_a = get_info(app, env, 'a') + # Check that you got proper info + self.assertEqual(info_a['status'], 200) + self.assertEqual(info_a['bytes'], 6666) + self.assertEqual(info_a['total_object_count'], 1000) + self.assertEqual(info_a['container_count'], 333) + # Make sure the env cache is set + exp_cached_info_a = { + k: str(v) if k in ( + 'bytes', 'container_count', 'total_object_count') else v + for k, v in info_a.items()} + self.assertEqual(env['swift.infocache'].get('account/a'), + exp_cached_info_a) + # Make sure the app was called + self.assertEqual(app.responses.stats['account'], 1) + self.assertEqual(app.responses.stats['container'], 0) + # Make sure memcache was called + self.assertEqual(mock_cache.mock_calls, [ + mock.call.get('account/a'), + mock.call.set('account/a', exp_cached_info_a, time=0), + ]) + + mock_cache.reset_mock() + info_c = get_info(app, env, 'a', 'c') + # Check that you got proper info + self.assertEqual(info_c['status'], 200) + self.assertEqual(info_c['bytes'], 6666) + self.assertEqual(info_c['object_count'], 1000) + # Make sure the env cache is set + exp_cached_info_c = { + k: str(v) if k in ('bytes', 'object_count') else v + for k, v in info_c.items()} + self.assertEqual(env['swift.infocache'].get('account/a'), + exp_cached_info_a) + self.assertEqual(env['swift.infocache'].get('container/a/c'), + exp_cached_info_c) + # Check app call for container, but no new calls for account + self.assertEqual(app.responses.stats['account'], 1) + self.assertEqual(app.responses.stats['container'], 1) + # Make sure container info was cached + self.assertEqual(mock_cache.mock_calls, [ + mock.call.get('container/a/c'), + mock.call.set('container/a/c', exp_cached_info_c, time=0), + ]) + + # reset call counts + app = FakeApp(ZeroCacheDynamicResponseFactory()) + env = {'swift.cache': mock_cache} + mock_cache.reset_mock() + info_c = get_info(app, env, 'a', 'c') + # Check that you got proper info + self.assertEqual(info_c['status'], 200) + self.assertEqual(info_c['bytes'], 6666) + self.assertEqual(info_c['object_count'], 1000) + # Make sure the env cache is set + self.assertEqual(env['swift.infocache'].get('account/a'), + exp_cached_info_a) + self.assertEqual(env['swift.infocache'].get('container/a/c'), + exp_cached_info_c) + # check app calls both account and container + self.assertEqual(app.responses.stats['account'], 1) + self.assertEqual(app.responses.stats['container'], 1) + # Make sure account info was cached but container was not + self.assertEqual(mock_cache.mock_calls, [ + mock.call.get('container/a/c'), + mock.call.get('account/a'), + mock.call.set('account/a', exp_cached_info_a, time=0), + mock.call.set('container/a/c', exp_cached_info_c, time=0), + ]) def test_get_info(self): app = FakeApp() @@ -199,196 +255,177 @@ class TestFuncs(unittest.TestCase): env = {} info_a = get_info(app, env, 'a') # Check that you got proper info - self.assertEquals(info_a['status'], 200) - self.assertEquals(info_a['bytes'], 6666) - self.assertEquals(info_a['total_object_count'], 1000) - # Make sure the env cache is set - self.assertEquals(env.get('swift.account/a'), info_a) + self.assertEqual(info_a['status'], 200) + self.assertEqual(info_a['bytes'], 6666) + self.assertEqual(info_a['total_object_count'], 1000) + # Make sure the app was called self.assertEqual(app.responses.stats['account'], 1) + # Make sure the return value matches get_account_info + account_info = get_account_info({'PATH_INFO': '/v1/a'}, app) + self.assertEqual(info_a, account_info) + # Do an env cached call to account + app.responses.stats['account'] = 0 + app.responses.stats['container'] = 0 + info_a = get_info(app, env, 'a') # Check that you got proper info - self.assertEquals(info_a['status'], 200) - self.assertEquals(info_a['bytes'], 6666) - self.assertEquals(info_a['total_object_count'], 1000) - # Make sure the env cache is set - self.assertEquals(env.get('swift.account/a'), info_a) + self.assertEqual(info_a['status'], 200) + self.assertEqual(info_a['bytes'], 6666) + self.assertEqual(info_a['total_object_count'], 1000) + # Make sure the app was NOT called AGAIN - self.assertEqual(app.responses.stats['account'], 1) + self.assertEqual(app.responses.stats['account'], 0) # This time do env cached call to account and non cached to container + app.responses.stats['account'] = 0 + app.responses.stats['container'] = 0 + info_c = get_info(app, env, 'a', 'c') # Check that you got proper info - self.assertEquals(info_c['status'], 200) - self.assertEquals(info_c['bytes'], 6666) - self.assertEquals(info_c['object_count'], 1000) - # Make sure the env cache is set - self.assertEquals(env.get('swift.account/a'), info_a) - self.assertEquals(env.get('swift.container/a/c'), info_c) - # Make sure the app was called for container + self.assertEqual(info_c['status'], 200) + self.assertEqual(info_c['bytes'], 6666) + self.assertEqual(info_c['object_count'], 1000) + # Make sure the app was called for container but not account + self.assertEqual(app.responses.stats['account'], 0) self.assertEqual(app.responses.stats['container'], 1) - # This time do a non cached call to account than non cached to + # This time do a non-cached call to account then non-cached to # container + app.responses.stats['account'] = 0 + app.responses.stats['container'] = 0 app = FakeApp() env = {} # abandon previous call to env info_c = get_info(app, env, 'a', 'c') # Check that you got proper info - self.assertEquals(info_c['status'], 200) - self.assertEquals(info_c['bytes'], 6666) - self.assertEquals(info_c['object_count'], 1000) - # Make sure the env cache is set - self.assertEquals(env.get('swift.account/a'), info_a) - self.assertEquals(env.get('swift.container/a/c'), info_c) + self.assertEqual(info_c['status'], 200) + self.assertEqual(info_c['bytes'], 6666) + self.assertEqual(info_c['object_count'], 1000) # check app calls both account and container self.assertEqual(app.responses.stats['account'], 1) self.assertEqual(app.responses.stats['container'], 1) - # This time do an env cached call to container while account is not + # This time do an env-cached call to container while account is not # cached - del(env['swift.account/a']) + app.responses.stats['account'] = 0 + app.responses.stats['container'] = 0 info_c = get_info(app, env, 'a', 'c') # Check that you got proper info - self.assertEquals(info_a['status'], 200) - self.assertEquals(info_c['bytes'], 6666) - self.assertEquals(info_c['object_count'], 1000) - # Make sure the env cache is set and account still not cached - self.assertEquals(env.get('swift.container/a/c'), info_c) - # no additional calls were made - self.assertEqual(app.responses.stats['account'], 1) - self.assertEqual(app.responses.stats['container'], 1) - - # Do a non cached call to account not found with ret_not_found - app = FakeApp(statuses=(404,)) - env = {} - info_a = get_info(app, env, 'a', ret_not_found=True) - # Check that you got proper info - self.assertEquals(info_a['status'], 404) - self.assertEquals(info_a['bytes'], None) - self.assertEquals(info_a['total_object_count'], None) - # Make sure the env cache is set - self.assertEquals(env.get('swift.account/a'), info_a) - # and account was called - self.assertEqual(app.responses.stats['account'], 1) + self.assertEqual(info_a['status'], 200) + self.assertEqual(info_c['bytes'], 6666) + self.assertEqual(info_c['object_count'], 1000) - # Do a cached call to account not found with ret_not_found - info_a = get_info(app, env, 'a', ret_not_found=True) - # Check that you got proper info - self.assertEquals(info_a['status'], 404) - self.assertEquals(info_a['bytes'], None) - self.assertEquals(info_a['total_object_count'], None) - # Make sure the env cache is set - self.assertEquals(env.get('swift.account/a'), info_a) - # add account was NOT called AGAIN - self.assertEqual(app.responses.stats['account'], 1) - - # Do a non cached call to account not found without ret_not_found - app = FakeApp(statuses=(404,)) - env = {} - info_a = get_info(app, env, 'a') - # Check that you got proper info - self.assertEquals(info_a, None) - self.assertEquals(env['swift.account/a']['status'], 404) - # and account was called - self.assertEqual(app.responses.stats['account'], 1) - - # Do a cached call to account not found without ret_not_found - info_a = get_info(None, env, 'a') - # Check that you got proper info - self.assertEquals(info_a, None) - self.assertEquals(env['swift.account/a']['status'], 404) - # add account was NOT called AGAIN - self.assertEqual(app.responses.stats['account'], 1) + # no additional calls were made + self.assertEqual(app.responses.stats['account'], 0) + self.assertEqual(app.responses.stats['container'], 0) def test_get_container_info_swift_source(self): app = FakeApp() req = Request.blank("/v1/a/c", environ={'swift.cache': FakeCache()}) get_container_info(req.environ, app, swift_source='MC') - self.assertEqual(app.sources, ['GET_INFO', 'MC']) + self.assertEqual([e['swift.source'] for e in app.captured_envs], + ['MC', 'MC']) def test_get_object_info_swift_source(self): app = FakeApp() req = Request.blank("/v1/a/c/o", environ={'swift.cache': FakeCache()}) get_object_info(req.environ, app, swift_source='LU') - self.assertEqual(app.sources, ['LU']) + self.assertEqual([e['swift.source'] for e in app.captured_envs], + ['LU']) def test_get_container_info_no_cache(self): req = Request.blank("/v1/AUTH_account/cont", environ={'swift.cache': FakeCache({})}) resp = get_container_info(req.environ, FakeApp()) - self.assertEquals(resp['storage_policy'], '0') - self.assertEquals(resp['bytes'], 6666) - self.assertEquals(resp['object_count'], 1000) + self.assertEqual(resp['storage_policy'], '0') + self.assertEqual(resp['bytes'], 6666) + self.assertEqual(resp['object_count'], 1000) def test_get_container_info_no_account(self): - responses = DynamicResponseFactory(404, 200) - app = FakeApp(responses) + app = FakeApp(statuses=[404, 200]) req = Request.blank("/v1/AUTH_does_not_exist/cont") info = get_container_info(req.environ, app) self.assertEqual(info['status'], 0) def test_get_container_info_no_auto_account(self): - responses = DynamicResponseFactory(404, 200) - app = FakeApp(responses) + app = FakeApp(statuses=[200]) req = Request.blank("/v1/.system_account/cont") info = get_container_info(req.environ, app) self.assertEqual(info['status'], 200) - self.assertEquals(info['bytes'], 6666) - self.assertEquals(info['object_count'], 1000) + self.assertEqual(info['bytes'], 6666) + self.assertEqual(info['object_count'], 1000) def test_get_container_info_cache(self): cache_stub = { 'status': 404, 'bytes': 3333, 'object_count': 10, - # simplejson sometimes hands back strings, sometimes unicodes 'versions': u"\u1F4A9"} req = Request.blank("/v1/account/cont", environ={'swift.cache': FakeCache(cache_stub)}) resp = get_container_info(req.environ, FakeApp()) - self.assertEquals(resp['storage_policy'], '0') - self.assertEquals(resp['bytes'], 3333) - self.assertEquals(resp['object_count'], 10) - self.assertEquals(resp['status'], 404) - self.assertEquals(resp['versions'], "\xe1\xbd\x8a\x39") + self.assertEqual(resp['storage_policy'], '0') + self.assertEqual(resp['bytes'], 3333) + self.assertEqual(resp['object_count'], 10) + self.assertEqual(resp['status'], 404) + self.assertEqual(resp['versions'], "\xe1\xbd\x8a\x39") def test_get_container_info_env(self): - cache_key = get_container_memcache_key("account", "cont") - env_key = 'swift.%s' % cache_key - req = Request.blank("/v1/account/cont", - environ={env_key: {'bytes': 3867}, - 'swift.cache': FakeCache({})}) + cache_key = get_cache_key("account", "cont") + req = Request.blank( + "/v1/account/cont", + environ={'swift.infocache': {cache_key: {'bytes': 3867}}, + 'swift.cache': FakeCache({})}) resp = get_container_info(req.environ, 'xxx') - self.assertEquals(resp['bytes'], 3867) + self.assertEqual(resp['bytes'], 3867) def test_get_account_info_swift_source(self): app = FakeApp() req = Request.blank("/v1/a", environ={'swift.cache': FakeCache()}) get_account_info(req.environ, app, swift_source='MC') - self.assertEqual(app.sources, ['MC']) + self.assertEqual([e['swift.source'] for e in app.captured_envs], + ['MC']) + + def test_get_account_info_swift_owner(self): + app = FakeApp() + req = Request.blank("/v1/a", environ={'swift.cache': FakeCache()}) + get_account_info(req.environ, app) + self.assertEqual([e['swift_owner'] for e in app.captured_envs], + [True]) + + def test_get_account_info_infocache(self): + app = FakeApp() + ic = {} + req = Request.blank("/v1/a", environ={'swift.cache': FakeCache(), + 'swift.infocache': ic}) + get_account_info(req.environ, app) + got_infocaches = [e['swift.infocache'] for e in app.captured_envs] + self.assertEqual(1, len(got_infocaches)) + self.assertIs(ic, got_infocaches[0]) def test_get_account_info_no_cache(self): app = FakeApp() req = Request.blank("/v1/AUTH_account", environ={'swift.cache': FakeCache({})}) resp = get_account_info(req.environ, app) - self.assertEquals(resp['bytes'], 6666) - self.assertEquals(resp['total_object_count'], 1000) + self.assertEqual(resp['bytes'], 6666) + self.assertEqual(resp['total_object_count'], 1000) def test_get_account_info_cache(self): - # The original test that we prefer to preserve + # Works with fake apps that return ints in the headers cached = {'status': 404, 'bytes': 3333, 'total_object_count': 10} req = Request.blank("/v1/account/cont", environ={'swift.cache': FakeCache(cached)}) resp = get_account_info(req.environ, FakeApp()) - self.assertEquals(resp['bytes'], 3333) - self.assertEquals(resp['total_object_count'], 10) - self.assertEquals(resp['status'], 404) + self.assertEqual(resp['bytes'], 3333) + self.assertEqual(resp['total_object_count'], 10) + self.assertEqual(resp['status'], 404) - # Here is a more realistic test + # Works with strings too, like you get when parsing HTTP headers + # that came in through a socket from the account server cached = {'status': 404, 'bytes': '3333', 'container_count': '234', @@ -397,33 +434,34 @@ class TestFuncs(unittest.TestCase): req = Request.blank("/v1/account/cont", environ={'swift.cache': FakeCache(cached)}) resp = get_account_info(req.environ, FakeApp()) - self.assertEquals(resp['status'], 404) - self.assertEquals(resp['bytes'], '3333') - self.assertEquals(resp['container_count'], 234) - self.assertEquals(resp['meta'], {}) - self.assertEquals(resp['total_object_count'], '10') + self.assertEqual(resp['status'], 404) + self.assertEqual(resp['bytes'], 3333) + self.assertEqual(resp['container_count'], 234) + self.assertEqual(resp['meta'], {}) + self.assertEqual(resp['total_object_count'], 10) def test_get_account_info_env(self): - cache_key = get_account_memcache_key("account") - env_key = 'swift.%s' % cache_key - req = Request.blank("/v1/account", - environ={env_key: {'bytes': 3867}, - 'swift.cache': FakeCache({})}) + cache_key = get_cache_key("account") + req = Request.blank( + "/v1/account", + environ={'swift.infocache': {cache_key: {'bytes': 3867}}, + 'swift.cache': FakeCache({})}) resp = get_account_info(req.environ, 'xxx') - self.assertEquals(resp['bytes'], 3867) + self.assertEqual(resp['bytes'], 3867) def test_get_object_info_env(self): cached = {'status': 200, 'length': 3333, 'type': 'application/json', 'meta': {}} - env_key = get_object_env_key("account", "cont", "obj") - req = Request.blank("/v1/account/cont/obj", - environ={env_key: cached, - 'swift.cache': FakeCache({})}) + cache_key = get_cache_key("account", "cont", "obj") + req = Request.blank( + "/v1/account/cont/obj", + environ={'swift.infocache': {cache_key: cached}, + 'swift.cache': FakeCache({})}) resp = get_object_info(req.environ, 'xxx') - self.assertEquals(resp['length'], 3333) - self.assertEquals(resp['type'], 'application/json') + self.assertEqual(resp['length'], 3333) + self.assertEqual(resp['type'], 'application/json') def test_get_object_info_no_env(self): app = FakeApp() @@ -433,31 +471,84 @@ class TestFuncs(unittest.TestCase): self.assertEqual(app.responses.stats['account'], 0) self.assertEqual(app.responses.stats['container'], 0) self.assertEqual(app.responses.stats['obj'], 1) - self.assertEquals(resp['length'], 5555) - self.assertEquals(resp['type'], 'text/plain') + self.assertEqual(resp['length'], 5555) + self.assertEqual(resp['type'], 'text/plain') + + def test_options(self): + base = Controller(self.app) + base.account_name = 'a' + base.container_name = 'c' + origin = 'http://m.com' + self.app.cors_allow_origin = [origin] + req = Request.blank('/v1/a/c/o', + environ={'swift.cache': FakeCache()}, + headers={'Origin': origin, + 'Access-Control-Request-Method': 'GET'}) + + with mock.patch('swift.proxy.controllers.base.' + 'http_connect', fake_http_connect(200)): + resp = base.OPTIONS(req) + self.assertEqual(resp.status_int, 200) + + def test_options_with_null_allow_origin(self): + base = Controller(self.app) + base.account_name = 'a' + base.container_name = 'c' + + def my_container_info(*args): + return { + 'cors': { + 'allow_origin': '*', + } + } + base.container_info = my_container_info + req = Request.blank('/v1/a/c/o', + environ={'swift.cache': FakeCache()}, + headers={'Origin': '*', + 'Access-Control-Request-Method': 'GET'}) + + with mock.patch('swift.proxy.controllers.base.' + 'http_connect', fake_http_connect(200)): + resp = base.OPTIONS(req) + self.assertEqual(resp.status_int, 200) + + def test_options_unauthorized(self): + base = Controller(self.app) + base.account_name = 'a' + base.container_name = 'c' + self.app.cors_allow_origin = ['http://NOT_IT'] + req = Request.blank('/v1/a/c/o', + environ={'swift.cache': FakeCache()}, + headers={'Origin': 'http://m.com', + 'Access-Control-Request-Method': 'GET'}) + + with mock.patch('swift.proxy.controllers.base.' + 'http_connect', fake_http_connect(200)): + resp = base.OPTIONS(req) + self.assertEqual(resp.status_int, 401) def test_headers_to_container_info_missing(self): resp = headers_to_container_info({}, 404) - self.assertEquals(resp['status'], 404) - self.assertEquals(resp['read_acl'], None) - self.assertEquals(resp['write_acl'], None) + self.assertEqual(resp['status'], 404) + self.assertIsNone(resp['read_acl']) + self.assertIsNone(resp['write_acl']) def test_headers_to_container_info_meta(self): headers = {'X-Container-Meta-Whatevs': 14, 'x-container-meta-somethingelse': 0} resp = headers_to_container_info(headers.items(), 200) - self.assertEquals(len(resp['meta']), 2) - self.assertEquals(resp['meta']['whatevs'], 14) - self.assertEquals(resp['meta']['somethingelse'], 0) + self.assertEqual(len(resp['meta']), 2) + self.assertEqual(resp['meta']['whatevs'], 14) + self.assertEqual(resp['meta']['somethingelse'], 0) def test_headers_to_container_info_sys_meta(self): prefix = get_sys_meta_prefix('container') headers = {'%sWhatevs' % prefix: 14, '%ssomethingelse' % prefix: 0} resp = headers_to_container_info(headers.items(), 200) - self.assertEquals(len(resp['sysmeta']), 2) - self.assertEquals(resp['sysmeta']['whatevs'], 14) - self.assertEquals(resp['sysmeta']['somethingelse'], 0) + self.assertEqual(len(resp['sysmeta']), 2) + self.assertEqual(resp['sysmeta']['whatevs'], 14) + self.assertEqual(resp['sysmeta']['somethingelse'], 0) def test_headers_to_container_info_values(self): headers = { @@ -467,37 +558,47 @@ class TestFuncs(unittest.TestCase): 'x-container-meta-access-control-allow-origin': 'here', } resp = headers_to_container_info(headers.items(), 200) - self.assertEquals(resp['read_acl'], 'readvalue') - self.assertEquals(resp['write_acl'], 'writevalue') - self.assertEquals(resp['cors']['allow_origin'], 'here') + self.assertEqual(resp['read_acl'], 'readvalue') + self.assertEqual(resp['write_acl'], 'writevalue') + self.assertEqual(resp['cors']['allow_origin'], 'here') headers['x-unused-header'] = 'blahblahblah' - self.assertEquals( + self.assertEqual( resp, headers_to_container_info(headers.items(), 200)) + def test_container_info_without_req(self): + base = Controller(self.app) + base.account_name = 'a' + base.container_name = 'c' + + container_info = \ + base.container_info(base.account_name, + base.container_name) + self.assertEqual(container_info['status'], 0) + def test_headers_to_account_info_missing(self): resp = headers_to_account_info({}, 404) - self.assertEquals(resp['status'], 404) - self.assertEquals(resp['bytes'], None) - self.assertEquals(resp['container_count'], None) + self.assertEqual(resp['status'], 404) + self.assertIsNone(resp['bytes']) + self.assertIsNone(resp['container_count']) def test_headers_to_account_info_meta(self): headers = {'X-Account-Meta-Whatevs': 14, 'x-account-meta-somethingelse': 0} resp = headers_to_account_info(headers.items(), 200) - self.assertEquals(len(resp['meta']), 2) - self.assertEquals(resp['meta']['whatevs'], 14) - self.assertEquals(resp['meta']['somethingelse'], 0) + self.assertEqual(len(resp['meta']), 2) + self.assertEqual(resp['meta']['whatevs'], 14) + self.assertEqual(resp['meta']['somethingelse'], 0) def test_headers_to_account_info_sys_meta(self): prefix = get_sys_meta_prefix('account') headers = {'%sWhatevs' % prefix: 14, '%ssomethingelse' % prefix: 0} resp = headers_to_account_info(headers.items(), 200) - self.assertEquals(len(resp['sysmeta']), 2) - self.assertEquals(resp['sysmeta']['whatevs'], 14) - self.assertEquals(resp['sysmeta']['somethingelse'], 0) + self.assertEqual(len(resp['sysmeta']), 2) + self.assertEqual(resp['sysmeta']['whatevs'], 14) + self.assertEqual(resp['sysmeta']['somethingelse'], 0) def test_headers_to_account_info_values(self): headers = { @@ -505,36 +606,44 @@ class TestFuncs(unittest.TestCase): 'x-account-container-count': '20', } resp = headers_to_account_info(headers.items(), 200) - self.assertEquals(resp['total_object_count'], '10') - self.assertEquals(resp['container_count'], '20') + self.assertEqual(resp['total_object_count'], '10') + self.assertEqual(resp['container_count'], '20') headers['x-unused-header'] = 'blahblahblah' - self.assertEquals( + self.assertEqual( resp, headers_to_account_info(headers.items(), 200)) def test_headers_to_object_info_missing(self): resp = headers_to_object_info({}, 404) - self.assertEquals(resp['status'], 404) - self.assertEquals(resp['length'], None) - self.assertEquals(resp['etag'], None) + self.assertEqual(resp['status'], 404) + self.assertIsNone(resp['length']) + self.assertIsNone(resp['etag']) def test_headers_to_object_info_meta(self): headers = {'X-Object-Meta-Whatevs': 14, 'x-object-meta-somethingelse': 0} resp = headers_to_object_info(headers.items(), 200) - self.assertEquals(len(resp['meta']), 2) - self.assertEquals(resp['meta']['whatevs'], 14) - self.assertEquals(resp['meta']['somethingelse'], 0) + self.assertEqual(len(resp['meta']), 2) + self.assertEqual(resp['meta']['whatevs'], 14) + self.assertEqual(resp['meta']['somethingelse'], 0) def test_headers_to_object_info_sys_meta(self): prefix = get_sys_meta_prefix('object') headers = {'%sWhatevs' % prefix: 14, '%ssomethingelse' % prefix: 0} resp = headers_to_object_info(headers.items(), 200) - self.assertEquals(len(resp['sysmeta']), 2) - self.assertEquals(resp['sysmeta']['whatevs'], 14) - self.assertEquals(resp['sysmeta']['somethingelse'], 0) + self.assertEqual(len(resp['sysmeta']), 2) + self.assertEqual(resp['sysmeta']['whatevs'], 14) + self.assertEqual(resp['sysmeta']['somethingelse'], 0) + + def test_headers_to_object_info_transient_sysmeta(self): + headers = {get_object_transient_sysmeta('Whatevs'): 14, + get_object_transient_sysmeta('somethingelse'): 0} + resp = headers_to_object_info(headers.items(), 200) + self.assertEqual(len(resp['transient_sysmeta']), 2) + self.assertEqual(resp['transient_sysmeta']['whatevs'], 14) + self.assertEqual(resp['transient_sysmeta']['somethingelse'], 0) def test_headers_to_object_info_values(self): headers = { @@ -542,26 +651,29 @@ class TestFuncs(unittest.TestCase): 'content-type': 'application/json', } resp = headers_to_object_info(headers.items(), 200) - self.assertEquals(resp['length'], '1024') - self.assertEquals(resp['type'], 'application/json') + self.assertEqual(resp['length'], '1024') + self.assertEqual(resp['type'], 'application/json') headers['x-unused-header'] = 'blahblahblah' - self.assertEquals( + self.assertEqual( resp, headers_to_object_info(headers.items(), 200)) def test_base_have_quorum(self): base = Controller(self.app) # just throw a bunch of test cases at it - self.assertEqual(base.have_quorum([201, 404], 3), False) - self.assertEqual(base.have_quorum([201, 201], 4), False) - self.assertEqual(base.have_quorum([201, 201, 404, 404], 4), False) - self.assertEqual(base.have_quorum([201, 503, 503, 201], 4), False) - self.assertEqual(base.have_quorum([201, 201], 3), True) - self.assertEqual(base.have_quorum([404, 404], 3), True) - self.assertEqual(base.have_quorum([201, 201], 2), True) - self.assertEqual(base.have_quorum([404, 404], 2), True) - self.assertEqual(base.have_quorum([201, 404, 201, 201], 4), True) + self.assertFalse(base.have_quorum([201, 404], 3)) + self.assertTrue(base.have_quorum([201, 201], 4)) + self.assertFalse(base.have_quorum([201], 4)) + self.assertTrue(base.have_quorum([201, 201, 404, 404], 4)) + self.assertFalse(base.have_quorum([201, 302, 418, 503], 4)) + self.assertTrue(base.have_quorum([201, 503, 503, 201], 4)) + self.assertTrue(base.have_quorum([201, 201], 3)) + self.assertTrue(base.have_quorum([404, 404], 3)) + self.assertTrue(base.have_quorum([201, 201], 2)) + self.assertTrue(base.have_quorum([201, 404], 2)) + self.assertTrue(base.have_quorum([404, 404], 2)) + self.assertTrue(base.have_quorum([201, 404, 201, 201], 4)) def test_best_response_overrides(self): base = Controller(self.app) @@ -579,7 +691,7 @@ class TestFuncs(unittest.TestCase): overrides = {302: 204, 100: 204} resp = base.best_response(req, statuses, reasons, bodies, server_type, headers=headers, overrides=overrides) - self.assertEqual(resp.status, '503 Internal Server Error') + self.assertEqual(resp.status, '503 Service Unavailable') # next make a 404 quorum and make sure the last delete (real) 404 # status is the one returned. @@ -593,24 +705,61 @@ class TestFuncs(unittest.TestCase): req = Request.blank('/') handler = GetOrHeadHandler(None, req, None, None, None, None, {}) handler.fast_forward(50) - self.assertEquals(handler.backend_headers['Range'], 'bytes=50-') + self.assertEqual(handler.backend_headers['Range'], 'bytes=50-') handler = GetOrHeadHandler(None, req, None, None, None, None, {'Range': 'bytes=23-50'}) handler.fast_forward(20) - self.assertEquals(handler.backend_headers['Range'], 'bytes=43-50') + self.assertEqual(handler.backend_headers['Range'], 'bytes=43-50') self.assertRaises(HTTPException, handler.fast_forward, 80) + self.assertRaises(exceptions.RangeAlreadyComplete, + handler.fast_forward, 8) handler = GetOrHeadHandler(None, req, None, None, None, None, {'Range': 'bytes=23-'}) handler.fast_forward(20) - self.assertEquals(handler.backend_headers['Range'], 'bytes=43-') + self.assertEqual(handler.backend_headers['Range'], 'bytes=43-') handler = GetOrHeadHandler(None, req, None, None, None, None, {'Range': 'bytes=-100'}) handler.fast_forward(20) - self.assertEquals(handler.backend_headers['Range'], 'bytes=-80') + self.assertEqual(handler.backend_headers['Range'], 'bytes=-80') + self.assertRaises(HTTPException, + handler.fast_forward, 100) + self.assertRaises(exceptions.RangeAlreadyComplete, + handler.fast_forward, 80) + + handler = GetOrHeadHandler(None, req, None, None, None, None, + {'Range': 'bytes=0-0'}) + self.assertRaises(exceptions.RangeAlreadyComplete, + handler.fast_forward, 1) + + def test_range_fast_forward_after_data_timeout(self): + req = Request.blank('/') + + # We get a 200 and learn that it's a 1000-byte object, but receive 0 + # bytes of data, so then we get a new node, fast_forward(0), and + # send out a new request. That new request must be for all 1000 + # bytes. + handler = GetOrHeadHandler(None, req, None, None, None, None, {}) + handler.learn_size_from_content_range(0, 999, 1000) + handler.fast_forward(0) + self.assertEqual(handler.backend_headers['Range'], 'bytes=0-999') + + # Same story as above, but a 1-byte object so we can have our byte + # indices be 0. + handler = GetOrHeadHandler(None, req, None, None, None, None, {}) + handler.learn_size_from_content_range(0, 0, 1) + handler.fast_forward(0) + self.assertEqual(handler.backend_headers['Range'], 'bytes=0-0') + + # last 100 bytes + handler = GetOrHeadHandler(None, req, None, None, None, None, + {'Range': 'bytes=-100'}) + handler.learn_size_from_content_range(900, 999, 1000) + handler.fast_forward(0) + self.assertEqual(handler.backend_headers['Range'], 'bytes=900-999') def test_transfer_headers_with_sysmeta(self): base = Controller(self.app) @@ -633,10 +782,10 @@ class TestFuncs(unittest.TestCase): expected_headers = {'x-base-meta-owner': '', 'x-base-meta-size': '151M', 'connection': 'close'} - for k, v in expected_headers.iteritems(): - self.assertTrue(k in dst_headers) + for k, v in expected_headers.items(): + self.assertIn(k, dst_headers) self.assertEqual(v, dst_headers[k]) - self.assertFalse('new-owner' in dst_headers) + self.assertNotIn('new-owner', dst_headers) def test_generate_request_headers_with_sysmeta(self): base = Controller(self.app) @@ -647,17 +796,32 @@ class TestFuncs(unittest.TestCase): hdrs.update(bad_hdrs) req = Request.blank('/v1/a/c/o', headers=hdrs) dst_headers = base.generate_request_headers(req, transfer=True) - for k, v in good_hdrs.iteritems(): - self.assertTrue(k.lower() in dst_headers) + for k, v in good_hdrs.items(): + self.assertIn(k.lower(), dst_headers) self.assertEqual(v, dst_headers[k.lower()]) - for k, v in bad_hdrs.iteritems(): - self.assertFalse(k.lower() in dst_headers) + for k, v in bad_hdrs.items(): + self.assertNotIn(k.lower(), dst_headers) + + def test_generate_request_headers_with_no_orig_req(self): + base = Controller(self.app) + src_headers = {'x-remove-base-meta-owner': 'x', + 'x-base-meta-size': '151M', + 'new-owner': 'Kun'} + dst_headers = base.generate_request_headers(None, + additional=src_headers) + expected_headers = {'x-base-meta-size': '151M', + 'connection': 'close'} + for k, v in expected_headers.items(): + self.assertIn(k, dst_headers) + self.assertEqual(v, dst_headers[k]) + self.assertEqual('', dst_headers['Referer']) def test_client_chunk_size(self): class TestSource(object): def __init__(self, chunks): self.chunks = list(chunks) + self.status = 200 def read(self, _read_size): if self.chunks: @@ -665,6 +829,13 @@ class TestFuncs(unittest.TestCase): else: return '' + def getheader(self, header): + if header.lower() == "content-length": + return str(sum(len(c) for c in self.chunks)) + + def getheaders(self): + return [('content-length', self.getheader('content-length'))] + source = TestSource(( 'abcd', '1234', 'abc', 'd1', '234abcd1234abcd1', '2')) req = Request.blank('/v1/a/c/o') @@ -682,6 +853,7 @@ class TestFuncs(unittest.TestCase): class TestSource(object): def __init__(self, chunks): self.chunks = list(chunks) + self.status = 200 def read(self, _read_size): if self.chunks: @@ -693,7 +865,15 @@ class TestFuncs(unittest.TestCase): else: return '' - node = {'ip': '1.2.3.4', 'port': 6000, 'device': 'sda'} + def getheader(self, header): + if header.lower() == "content-length": + return str(sum(len(c) for c in self.chunks + if c is not None)) + + def getheaders(self): + return [('content-length', self.getheader('content-length'))] + + node = {'ip': '1.2.3.4', 'port': 6200, 'device': 'sda'} source1 = TestSource(['abcd', '1234', 'abc', None]) source2 = TestSource(['efgh5678']) @@ -703,11 +883,88 @@ class TestFuncs(unittest.TestCase): client_chunk_size=8) app_iter = handler._make_app_iter(req, node, source1) - with patch.object(handler, '_get_source_and_node', - lambda: (source2, node)): + with mock.patch.object(handler, '_get_source_and_node', + lambda: (source2, node)): + client_chunks = list(app_iter) + self.assertEqual(client_chunks, ['abcd1234', 'efgh5678']) + + def test_client_chunk_size_resuming_chunked(self): + + class TestChunkedSource(object): + def __init__(self, chunks): + self.chunks = list(chunks) + self.status = 200 + self.headers = {'transfer-encoding': 'chunked', + 'content-type': 'text/plain'} + + def read(self, _read_size): + if self.chunks: + chunk = self.chunks.pop(0) + if chunk is None: + raise exceptions.ChunkReadTimeout() + else: + return chunk + else: + return '' + + def getheader(self, header): + return self.headers.get(header.lower()) + + def getheaders(self): + return self.headers + + node = {'ip': '1.2.3.4', 'port': 6200, 'device': 'sda'} + + source1 = TestChunkedSource(['abcd', '1234', 'abc', None]) + source2 = TestChunkedSource(['efgh5678']) + req = Request.blank('/v1/a/c/o') + handler = GetOrHeadHandler( + self.app, req, 'Object', None, None, None, {}, + client_chunk_size=8) + + app_iter = handler._make_app_iter(req, node, source1) + with mock.patch.object(handler, '_get_source_and_node', + lambda: (source2, node)): client_chunks = list(app_iter) self.assertEqual(client_chunks, ['abcd1234', 'efgh5678']) - self.assertEqual(handler.backend_headers['Range'], 'bytes=8-') + + def test_disconnected_warning(self): + self.app.logger = mock.Mock() + req = Request.blank('/v1/a/c/o') + + class TestSource(object): + def __init__(self): + self.headers = {'content-type': 'text/plain', + 'content-length': len(self.read(-1))} + self.status = 200 + + def read(self, _read_size): + return 'the cake is a lie' + + def getheader(self, header): + return self.headers.get(header.lower()) + + def getheaders(self): + return self.headers + + source = TestSource() + + node = {'ip': '1.2.3.4', 'port': 6200, 'device': 'sda'} + handler = GetOrHeadHandler( + self.app, req, 'Object', None, None, None, {}) + app_iter = handler._make_app_iter(req, node, source) + app_iter.close() + self.app.logger.warning.assert_called_once_with( + 'Client disconnected on read') + + self.app.logger = mock.Mock() + node = {'ip': '1.2.3.4', 'port': 6200, 'device': 'sda'} + handler = GetOrHeadHandler( + self.app, req, 'Object', None, None, None, {}) + app_iter = handler._make_app_iter(req, node, source) + next(app_iter) + app_iter.close() + self.app.logger.warning.assert_not_called() def test_bytes_to_skip(self): # if you start at the beginning, skip nothing diff --git a/test/unit/proxy/controllers/test_container.py b/test/unit/proxy/controllers/test_container.py index 715cd94..b5367aa 100644 --- a/test/unit/proxy/controllers/test_container.py +++ b/test/unit/proxy/controllers/test_container.py @@ -24,22 +24,22 @@ from swift.proxy.controllers.base import headers_to_container_info from test.unit import fake_http_connect, FakeRing, FakeMemcache from swift.common.storage_policy import StoragePolicy from swift.common.request_helpers import get_sys_meta_prefix -from swift.common import utils from test.unit import patch_policies, mocked_http_conn, debug_logger +#from test.unit.common.ring.test_ring import TestRingBase from test.unit.proxy.test_server import node_error_count @patch_policies([StoragePolicy(0, 'zero', True, object_ring=FakeRing())]) class TestContainerController(unittest.TestCase): + + CONTAINER_REPLICAS = 3 + def setUp(self): - # SOF - self._orig_hash_suffix = utils.HASH_PATH_SUFFIX - self._orig_hash_prefix = utils.HASH_PATH_PREFIX - utils.HASH_PATH_SUFFIX = 'endcap' - utils.HASH_PATH_PREFIX = '' + TestRingBase.setUp(self) self.logger = debug_logger() - self.container_ring = FakeRing(max_more_nodes=9) + self.container_ring = FakeRing(replicas=self.CONTAINER_REPLICAS, + max_more_nodes=9) self.app = proxy_server.Application(None, FakeMemcache(), logger=self.logger, account_ring=FakeRing(), @@ -58,7 +58,7 @@ class TestContainerController(unittest.TestCase): proxy_server.ContainerController): def account_info(controller, *args, **kwargs): - patch_path = 'swift.proxy.controllers.base.get_info' + patch_path = 'swift.proxy.controllers.base.get_account_info' with mock.patch(patch_path) as mock_get_info: mock_get_info.return_value = dict(self.account_info) return super(FakeAccountInfoContainerController, @@ -72,16 +72,43 @@ class TestContainerController(unittest.TestCase): return _orig_get_controller(*args, **kwargs) self.app.get_controller = wrapped_get_controller - def test_container_info_in_response_env(self): + def _make_callback_func(self, context): + def callback(ipaddr, port, device, partition, method, path, + headers=None, query_string=None, ssl=False): + context['method'] = method + context['path'] = path + context['headers'] = headers or {} + return callback + + def _assert_responses(self, method, test_cases): + controller = proxy_server.ContainerController(self.app, 'a', 'c') + + for responses, expected in test_cases: + with mock.patch( + 'swift.proxy.controllers.base.http_connect', + fake_http_connect(*responses)): + req = Request.blank('/v1/a/c') + resp = getattr(controller, method)(req) + + self.assertEqual(expected, + resp.status_int, + 'Expected %s but got %s. Failed case: %s' % + (expected, resp.status_int, str(responses))) + + def test_container_info_got_cached(self): controller = proxy_server.ContainerController(self.app, 'a', 'c') with mock.patch('swift.proxy.controllers.base.http_connect', fake_http_connect(200, 200, body='')): req = Request.blank('/v1/a/c', {'PATH_INFO': '/v1/a/c'}) resp = controller.HEAD(req) self.assertEqual(2, resp.status_int // 100) - self.assertTrue("swift.container/a/c" in resp.environ) - self.assertEqual(headers_to_container_info(resp.headers), - resp.environ['swift.container/a/c']) + # Make sure it's in both swift.infocache and memcache + self.assertIn("container/a/c", resp.environ['swift.infocache']) + self.assertEqual( + headers_to_container_info(resp.headers), + resp.environ['swift.infocache']['container/a/c']) + from_memcache = self.app.memcache.get('container/a/c') + self.assertTrue(from_memcache) def test_swift_owner(self): owner_headers = { @@ -93,25 +120,17 @@ class TestContainerController(unittest.TestCase): with mock.patch('swift.proxy.controllers.base.http_connect', fake_http_connect(200, 200, headers=owner_headers)): resp = controller.HEAD(req) - self.assertEquals(2, resp.status_int // 100) + self.assertEqual(2, resp.status_int // 100) for key in owner_headers: - self.assertTrue(key not in resp.headers) + self.assertNotIn(key, resp.headers) req = Request.blank('/v1/a/c', environ={'swift_owner': True}) with mock.patch('swift.proxy.controllers.base.http_connect', fake_http_connect(200, 200, headers=owner_headers)): resp = controller.HEAD(req) - self.assertEquals(2, resp.status_int // 100) + self.assertEqual(2, resp.status_int // 100) for key in owner_headers: - self.assertTrue(key in resp.headers) - - def _make_callback_func(self, context): - def callback(ipaddr, port, device, partition, method, path, - headers=None, query_string=None, ssl=False): - context['method'] = method - context['path'] = path - context['headers'] = headers or {} - return callback + self.assertIn(key, resp.headers) def test_sys_meta_headers_PUT(self): # check that headers in sys meta namespace make it through @@ -131,9 +150,9 @@ class TestContainerController(unittest.TestCase): fake_http_connect(200, 200, give_connect=callback)): controller.PUT(req) self.assertEqual(context['method'], 'PUT') - self.assertTrue(sys_meta_key in context['headers']) + self.assertIn(sys_meta_key, context['headers']) self.assertEqual(context['headers'][sys_meta_key], 'foo') - self.assertTrue(user_meta_key in context['headers']) + self.assertIn(user_meta_key, context['headers']) self.assertEqual(context['headers'][user_meta_key], 'bar') self.assertNotEqual(context['headers']['x-timestamp'], '1.0') @@ -154,9 +173,9 @@ class TestContainerController(unittest.TestCase): fake_http_connect(200, 200, give_connect=callback)): controller.POST(req) self.assertEqual(context['method'], 'POST') - self.assertTrue(sys_meta_key in context['headers']) + self.assertIn(sys_meta_key, context['headers']) self.assertEqual(context['headers'][sys_meta_key], 'foo') - self.assertTrue(user_meta_key in context['headers']) + self.assertIn(user_meta_key, context['headers']) self.assertEqual(context['headers'][user_meta_key], 'bar') self.assertNotEqual(context['headers']['x-timestamp'], '1.0') @@ -168,12 +187,11 @@ class TestContainerController(unittest.TestCase): self.app._error_limiting = {} req = Request.blank('/v1/a/c', method=method) with mocked_http_conn(*statuses) as fake_conn: - print 'a' * 50 resp = req.get_response(self.app) self.assertEqual(resp.status_int, expected) for req in fake_conn.requests: self.assertEqual(req['method'], method) - self.assert_(req['path'].endswith('/a/c')) + self.assertTrue(req['path'].endswith('/a/c')) base_status = [201] * 3 # test happy path @@ -207,6 +225,118 @@ class TestContainerController(unittest.TestCase): self.app, self.container_ring.devs[2]), self.app.error_suppression_limit + 1) + def test_response_code_for_PUT(self): + PUT_TEST_CASES = [ + ((201, 201, 201), 201), + ((201, 201, 404), 201), + ((201, 201, 503), 201), + ((201, 404, 404), 404), + ((201, 404, 503), 503), + ((201, 503, 503), 503), + ((404, 404, 404), 404), + ((404, 404, 503), 404), + ((404, 503, 503), 503), + ((503, 503, 503), 503) + ] + self._assert_responses('PUT', PUT_TEST_CASES) + + def test_response_code_for_DELETE(self): + DELETE_TEST_CASES = [ + ((204, 204, 204), 204), + ((204, 204, 404), 204), + ((204, 204, 503), 204), + ((204, 404, 404), 404), + ((204, 404, 503), 503), + ((204, 503, 503), 503), + ((404, 404, 404), 404), + ((404, 404, 503), 404), + ((404, 503, 503), 503), + ((503, 503, 503), 503) + ] + self._assert_responses('DELETE', DELETE_TEST_CASES) + + def test_response_code_for_POST(self): + POST_TEST_CASES = [ + ((204, 204, 204), 204), + ((204, 204, 404), 204), + ((204, 204, 503), 204), + ((204, 404, 404), 404), + ((204, 404, 503), 503), + ((204, 503, 503), 503), + ((404, 404, 404), 404), + ((404, 404, 503), 404), + ((404, 503, 503), 503), + ((503, 503, 503), 503) + ] + self._assert_responses('POST', POST_TEST_CASES) + + +@patch_policies( + [StoragePolicy(0, 'zero', True, object_ring=FakeRing(replicas=4))]) +class TestContainerController4Replicas(TestContainerController): + + CONTAINER_REPLICAS = 4 + + def test_response_code_for_PUT(self): + PUT_TEST_CASES = [ + ((201, 201, 201, 201), 201), + ((201, 201, 201, 404), 201), + ((201, 201, 201, 503), 201), + ((201, 201, 404, 404), 201), + ((201, 201, 404, 503), 201), + ((201, 201, 503, 503), 201), + ((201, 404, 404, 404), 404), + ((201, 404, 404, 503), 404), + ((201, 404, 503, 503), 503), + ((201, 503, 503, 503), 503), + ((404, 404, 404, 404), 404), + ((404, 404, 404, 503), 404), + ((404, 404, 503, 503), 404), + ((404, 503, 503, 503), 503), + ((503, 503, 503, 503), 503) + ] + self._assert_responses('PUT', PUT_TEST_CASES) + + def test_response_code_for_DELETE(self): + DELETE_TEST_CASES = [ + ((204, 204, 204, 204), 204), + ((204, 204, 204, 404), 204), + ((204, 204, 204, 503), 204), + ((204, 204, 404, 404), 204), + ((204, 204, 404, 503), 204), + ((204, 204, 503, 503), 204), + ((204, 404, 404, 404), 404), + ((204, 404, 404, 503), 404), + ((204, 404, 503, 503), 503), + ((204, 503, 503, 503), 503), + ((404, 404, 404, 404), 404), + ((404, 404, 404, 503), 404), + ((404, 404, 503, 503), 404), + ((404, 503, 503, 503), 503), + ((503, 503, 503, 503), 503) + ] + self._assert_responses('DELETE', DELETE_TEST_CASES) + + def test_response_code_for_POST(self): + POST_TEST_CASES = [ + ((204, 204, 204, 204), 204), + ((204, 204, 204, 404), 204), + ((204, 204, 204, 503), 204), + ((204, 204, 404, 404), 204), + ((204, 204, 404, 503), 204), + ((204, 204, 503, 503), 204), + ((204, 404, 404, 404), 404), + ((204, 404, 404, 503), 404), + ((204, 404, 503, 503), 503), + ((204, 503, 503, 503), 503), + ((404, 404, 404, 404), 404), + ((404, 404, 404, 503), 404), + ((404, 404, 503, 503), 404), + ((404, 503, 503, 503), 503), + ((503, 503, 503, 503), 503) + ] + self._assert_responses('POST', POST_TEST_CASES) + if __name__ == '__main__': unittest.main() diff --git a/test/unit/proxy/test_server.py b/test/unit/proxy/test_server.py index bdce41f..64c5825 100644 --- a/test/unit/proxy/test_server.py +++ b/test/unit/proxy/test_server.py @@ -66,8 +66,7 @@ from swift.common.ring import RingData from swift.common.utils import mkdirs, normalize_timestamp, NullLogger from swift.common.wsgi import monkey_patch_mimetools, loadapp from swift.proxy.controllers import base as proxy_base -from swift.proxy.controllers.base import get_container_memcache_key, \ - get_account_memcache_key, cors_validation +from swift.proxy.controllers.base import get_cache_key,cors_validation import swift.proxy.controllers import swift.proxy.controllers.obj from swift.common.swob import Request, Response, HTTPUnauthorized, \ @@ -474,14 +473,14 @@ class TestController(unittest.TestCase): self.controller.account_info(self.account) self.assertEquals(count, 123) with save_globals(): - cache_key = get_account_memcache_key(self.account) + cache_key = get_cache_key(self.account) account_info = {'status': 200, 'container_count': 1234} self.memcache.set(cache_key, account_info) partition, nodes, count = \ self.controller.account_info(self.account) self.assertEquals(count, 1234) with save_globals(): - cache_key = get_account_memcache_key(self.account) + cache_key = get_cache_key(self.account) account_info = {'status': 200, 'container_count': '1234'} self.memcache.set(cache_key, account_info) partition, nodes, count = \ @@ -509,7 +508,7 @@ class TestController(unittest.TestCase): # Test the internal representation in memcache # 'container_count' changed from int to str - cache_key = get_account_memcache_key(self.account) + cache_key = get_cache_key(self.account) container_info = {'status': 200, 'container_count': '12345', 'total_object_count': None, @@ -536,7 +535,7 @@ class TestController(unittest.TestCase): # Test the internal representation in memcache # 'container_count' changed from 0 to None - cache_key = get_account_memcache_key(self.account) + cache_key = get_cache_key(self.account) account_info = {'status': 404, 'container_count': None, # internally keep None 'total_object_count': None, @@ -613,8 +612,7 @@ class TestController(unittest.TestCase): self.account, self.container, self.request) self.check_container_info_return(ret) - cache_key = get_container_memcache_key(self.account, - self.container) + cache_key = get_cache_key(self.account,self.container) cache_value = self.memcache.get(cache_key) self.assertTrue(isinstance(cache_value, dict)) self.assertEquals(200, cache_value.get('status')) @@ -636,8 +634,8 @@ class TestController(unittest.TestCase): self.account, self.container, self.request) self.check_container_info_return(ret, True) - cache_key = get_container_memcache_key(self.account, - self.container) + cache_key = get_cache_key(self.account, + self.container) cache_value = self.memcache.get(cache_key) self.assertTrue(isinstance(cache_value, dict)) self.assertEquals(404, cache_value.get('status')) @@ -652,7 +650,7 @@ class TestController(unittest.TestCase): self.account, self.container, self.request) self.check_container_info_return(ret, True) - cache_key = get_container_memcache_key(self.account, + cache_key = get_cache_key(self.account, self.container) cache_value = self.memcache.get(cache_key) self.assertTrue(isinstance(cache_value, dict)) |