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
This commit is contained in:
Samuel Merritt 2013-06-28 11:54:35 -07:00
parent e759dad84f
commit 885e02102d
4 changed files with 27 additions and 7 deletions

View File

@ -1856,6 +1856,14 @@ def public(func):
return wrapped 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): def rsync_ip(ip):
""" """
Transform ip string to an rsync-compatible form Transform ip string to an rsync-compatible form

View File

@ -37,7 +37,8 @@ from eventlet.timeout import Timeout
from swift.common.wsgi import make_pre_authed_env from swift.common.wsgi import make_pre_authed_env
from swift.common.utils import normalize_timestamp, config_true_value, \ 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.bufferedhttp import http_connect
from swift.common.exceptions import ChunkReadTimeout, ConnectionTimeout from swift.common.exceptions import ChunkReadTimeout, ConnectionTimeout
from swift.common.http import is_informational, is_success, is_redirection, \ 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): for hundred in (HTTP_OK, HTTP_MULTIPLE_CHOICES, HTTP_BAD_REQUEST):
hstatuses = \ hstatuses = \
[s for s in statuses if hundred <= s < hundred + 100] [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 = max(hstatuses)
status_index = statuses.index(status) status_index = statuses.index(status)
resp.status = '%s %s' % (status, reasons[status_index]) resp.status = '%s %s' % (status, reasons[status_index])

View File

@ -37,7 +37,8 @@ from eventlet.queue import Queue
from eventlet.timeout import Timeout from eventlet.timeout import Timeout
from swift.common.utils import ContextPool, normalize_timestamp, \ 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.bufferedhttp import http_connect
from swift.common.constraints import check_metadata, check_object_creation, \ from swift.common.constraints import check_metadata, check_object_creation, \
CONTAINER_LISTING_LIMIT, MAX_FILE_SIZE, MAX_BUFFERED_SLO_SEGMENTS 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) req.path_info, nheaders, self.app.logger.thread_locals)
conns = [conn for conn in pile if conn] 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( self.app.logger.error(
_('Object PUT returning 503, %(conns)s/%(nodes)s ' _('Object PUT returning 503, %(conns)s/%(nodes)s '
'required connections'), 'required connections'),
{'conns': len(conns), 'nodes': len(nodes) // 2 + 1}) {'conns': len(conns), 'nodes': min_conns})
return HTTPServiceUnavailable(request=req) return HTTPServiceUnavailable(request=req)
bytes_transferred = 0 bytes_transferred = 0
try: try:
@ -1047,11 +1049,11 @@ class ObjectController(Controller):
if chunked else chunk) if chunked else chunk)
else: else:
conns.remove(conn) conns.remove(conn)
if len(conns) <= len(nodes) / 2: if len(conns) < min_conns:
self.app.logger.error(_( self.app.logger.error(_(
'Object PUT exceptions during' 'Object PUT exceptions during'
' send, %(conns)s/%(nodes)s required connections'), ' 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) return HTTPServiceUnavailable(request=req)
for conn in conns: for conn in conns:
if conn.queue.unfinished_tasks: if conn.queue.unfinished_tasks:

View File

@ -1126,6 +1126,15 @@ log_name = %(yarr)s'''
self.assertFalse(utils.streq_const_time('a', 'aaaaa')) self.assertFalse(utils.streq_const_time('a', 'aaaaa'))
self.assertFalse(utils.streq_const_time('ABC123', 'abc123')) 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): def test_rsync_ip_ipv4_localhost(self):
self.assertEqual(utils.rsync_ip('127.0.0.1'), '127.0.0.1') self.assertEqual(utils.rsync_ip('127.0.0.1'), '127.0.0.1')