From 885e02102d13735ba0e07f55e64e7ed4a2ba9c50 Mon Sep 17 00:00:00 2001 From: Samuel Merritt Date: Fri, 28 Jun 2013 11:54:35 -0700 Subject: [PATCH] Extract quorum-size calculation to utility method. The behavior's the same as before; this just makes the code (IMHO) a little easier to read. Change-Id: Ie54d836d81af7410057219f60b72b840c9ce51b9 --- swift/common/utils.py | 8 ++++++++ swift/proxy/controllers/base.py | 5 +++-- swift/proxy/controllers/obj.py | 12 +++++++----- test/unit/common/test_utils.py | 9 +++++++++ 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/swift/common/utils.py b/swift/common/utils.py index 900dd642a5..77a651cd90 100644 --- a/swift/common/utils.py +++ b/swift/common/utils.py @@ -1856,6 +1856,14 @@ def public(func): return wrapped +def quorum_size(n): + """ + Number of successful backend requests needed for the proxy to consider + the client request successful. + """ + return (n // 2) + 1 + + def rsync_ip(ip): """ Transform ip string to an rsync-compatible form diff --git a/swift/proxy/controllers/base.py b/swift/proxy/controllers/base.py index 4de4408b1d..361b0cc532 100644 --- a/swift/proxy/controllers/base.py +++ b/swift/proxy/controllers/base.py @@ -37,7 +37,8 @@ from eventlet.timeout import Timeout from swift.common.wsgi import make_pre_authed_env from swift.common.utils import normalize_timestamp, config_true_value, \ - public, split_path, list_from_csv, GreenthreadSafeIterator + public, split_path, list_from_csv, GreenthreadSafeIterator, \ + quorum_size from swift.common.bufferedhttp import http_connect from swift.common.exceptions import ChunkReadTimeout, ConnectionTimeout from swift.common.http import is_informational, is_success, is_redirection, \ @@ -749,7 +750,7 @@ class Controller(object): for hundred in (HTTP_OK, HTTP_MULTIPLE_CHOICES, HTTP_BAD_REQUEST): hstatuses = \ [s for s in statuses if hundred <= s < hundred + 100] - if len(hstatuses) > len(statuses) / 2: + if len(hstatuses) >= quorum_size(len(statuses)): status = max(hstatuses) status_index = statuses.index(status) resp.status = '%s %s' % (status, reasons[status_index]) diff --git a/swift/proxy/controllers/obj.py b/swift/proxy/controllers/obj.py index 458d466bcb..5a0877c6f1 100644 --- a/swift/proxy/controllers/obj.py +++ b/swift/proxy/controllers/obj.py @@ -37,7 +37,8 @@ from eventlet.queue import Queue from eventlet.timeout import Timeout from swift.common.utils import ContextPool, normalize_timestamp, \ - config_true_value, public, json, csv_append, GreenthreadSafeIterator + config_true_value, public, json, csv_append, GreenthreadSafeIterator, \ + quorum_size from swift.common.bufferedhttp import http_connect from swift.common.constraints import check_metadata, check_object_creation, \ CONTAINER_LISTING_LIMIT, MAX_FILE_SIZE, MAX_BUFFERED_SLO_SEGMENTS @@ -1016,11 +1017,12 @@ class ObjectController(Controller): req.path_info, nheaders, self.app.logger.thread_locals) conns = [conn for conn in pile if conn] - if len(conns) <= len(nodes) / 2: + min_conns = quorum_size(len(nodes)) + if len(conns) < min_conns: self.app.logger.error( _('Object PUT returning 503, %(conns)s/%(nodes)s ' 'required connections'), - {'conns': len(conns), 'nodes': len(nodes) // 2 + 1}) + {'conns': len(conns), 'nodes': min_conns}) return HTTPServiceUnavailable(request=req) bytes_transferred = 0 try: @@ -1047,11 +1049,11 @@ class ObjectController(Controller): if chunked else chunk) else: conns.remove(conn) - if len(conns) <= len(nodes) / 2: + if len(conns) < min_conns: self.app.logger.error(_( 'Object PUT exceptions during' ' send, %(conns)s/%(nodes)s required connections'), - {'conns': len(conns), 'nodes': len(nodes) / 2 + 1}) + {'conns': len(conns), 'nodes': min_conns}) return HTTPServiceUnavailable(request=req) for conn in conns: if conn.queue.unfinished_tasks: diff --git a/test/unit/common/test_utils.py b/test/unit/common/test_utils.py index 59cd159f53..f4ce690720 100644 --- a/test/unit/common/test_utils.py +++ b/test/unit/common/test_utils.py @@ -1126,6 +1126,15 @@ log_name = %(yarr)s''' self.assertFalse(utils.streq_const_time('a', 'aaaaa')) self.assertFalse(utils.streq_const_time('ABC123', 'abc123')) + def test_quorum_size(self): + expected_sizes = {1: 1, + 2: 2, + 3: 2, + 4: 3, + 5: 3} + got_sizes = dict([(n, utils.quorum_size(n)) for n in expected_sizes]) + self.assertEqual(expected_sizes, got_sizes) + def test_rsync_ip_ipv4_localhost(self): self.assertEqual(utils.rsync_ip('127.0.0.1'), '127.0.0.1')