Skip to content

Commit 92eebe2

Browse files
Improve test coverage for proxy object DELETE and POST
Add unit tests for the proxy object controller to cover object DELETE and POST scenarios. Change-Id: I625a4cf03ee9d4a270d60fa2dc9795b36bb36bf1
1 parent d830703 commit 92eebe2

File tree

2 files changed

+209
-4
lines changed

2 files changed

+209
-4
lines changed

test/unit/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,8 +1050,11 @@ def capture_requests(ip, port, method, path, headers, qs, ssl):
10501050
if left_over_status:
10511051
raise AssertionError('left over status %r' % left_over_status)
10521052
if fake_conn.unexpected_requests:
1053-
raise AssertionError('unexpected requests:\n%s' % '\n '.join(
1054-
'%r' % (req,) for req in fake_conn.unexpected_requests))
1053+
raise AssertionError(
1054+
'%d unexpected requests:\n%s' %
1055+
(len(fake_conn.unexpected_requests),
1056+
'\n '.join('%r' % (req,)
1057+
for req in fake_conn.unexpected_requests)))
10551058

10561059

10571060
def make_timestamp_iter(offset=0):

test/unit/proxy/controllers/test_obj.py

Lines changed: 204 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
from swift.common.exceptions import ChunkWriteTimeout, ShortReadError, \
4444
ChunkReadTimeout, RangeAlreadyComplete
4545
from swift.common.utils import Timestamp, list_from_csv, md5, FileLikeIter, \
46-
ShardRange, Namespace, NamespaceBoundList
46+
ShardRange, Namespace, NamespaceBoundList, quorum_size
4747
from swift.proxy import server as proxy_server
4848
from swift.proxy.controllers import obj
4949
from swift.proxy.controllers.base import \
@@ -524,6 +524,30 @@ def test_repl_object_DELETE_backend_update_container_repl_ip(self):
524524
for n in container_nodes}
525525
self.assertEqual(container_hosts, expected_container_hosts)
526526

527+
def test_DELETE_all_found(self):
528+
req = swift.common.swob.Request.blank('/v1/a/c/o', method='DELETE')
529+
codes = [204] * self.replicas()
530+
headers = []
531+
ts = self.ts()
532+
for _ in codes:
533+
headers.append({'x-backend-timestamp': ts.internal})
534+
with mocked_http_conn(*codes, headers=headers):
535+
resp = req.get_response(self.app)
536+
self.assertEqual(resp.status_int, 204)
537+
self.assertEqual(ts.internal, resp.headers.get('X-Backend-Timestamp'))
538+
539+
def test_DELETE_none_found(self):
540+
req = swift.common.swob.Request.blank('/v1/a/c/o', method='DELETE')
541+
codes = [404] * self.replicas()
542+
headers = []
543+
ts = self.ts()
544+
for _ in codes:
545+
headers.append({'x-backend-timestamp': ts.internal})
546+
with mocked_http_conn(*codes, headers=headers):
547+
resp = req.get_response(self.app)
548+
self.assertEqual(resp.status_int, 404)
549+
self.assertEqual(ts.internal, resp.headers.get('X-Backend-Timestamp'))
550+
527551
def test_DELETE_missing_one(self):
528552
# Obviously this test doesn't work if we're testing 1 replica.
529553
# In that case, we don't have any failovers to check.
@@ -536,7 +560,7 @@ def test_DELETE_missing_one(self):
536560
resp = req.get_response(self.app)
537561
self.assertEqual(resp.status_int, 204)
538562

539-
def test_DELETE_not_found(self):
563+
def test_DELETE_one_found(self):
540564
# Obviously this test doesn't work if we're testing 1 replica.
541565
# In that case, we don't have any failovers to check.
542566
if self.replicas() == 1:
@@ -565,6 +589,94 @@ def test_DELETE_mostly_not_found(self):
565589
resp = req.get_response(self.app)
566590
self.assertEqual(resp.status_int, 404)
567591

592+
def test_DELETE_insufficient_found_plus_404_507(self):
593+
# one less 204 than a quorum...
594+
primary_success = quorum_size(self.replicas()) - 1
595+
primary_failure = self.replicas() - primary_success - 1
596+
primary_codes = [204] * primary_success + [404] + \
597+
[507] * primary_failure
598+
handoff_codes = [404] * primary_failure
599+
ts = self.ts()
600+
headers = []
601+
for status in primary_codes + handoff_codes:
602+
if status in (204, 404):
603+
headers.append({'x-backend-timestamp': ts.internal})
604+
else:
605+
headers.append({})
606+
req = swift.common.swob.Request.blank('/v1/a/c/o', method='DELETE')
607+
with mocked_http_conn(*(primary_codes + handoff_codes),
608+
headers=headers):
609+
resp = req.get_response(self.app)
610+
# primary and handoff 404s form a quorum...
611+
self.assertEqual(resp.status_int, 404,
612+
'replicas = %s' % self.replicas())
613+
self.assertEqual(ts.internal, resp.headers.get('X-Backend-Timestamp'))
614+
615+
def test_DELETE_insufficient_found_plus_timeouts(self):
616+
req = swift.common.swob.Request.blank('/v1/a/c/o')
617+
req.method = 'DELETE'
618+
primary_success = quorum_size(self.replicas()) - 1
619+
primary_failure = self.replicas() - primary_success
620+
primary_codes = [204] * primary_success + [Timeout()] * primary_failure
621+
handoff_codes = [404] * primary_failure
622+
ts = self.ts()
623+
headers = []
624+
for status in primary_codes + handoff_codes:
625+
if status in (204, 404):
626+
headers.append({'x-backend-timestamp': ts.internal})
627+
else:
628+
headers.append({})
629+
with mocked_http_conn(*(primary_codes + handoff_codes),
630+
headers=headers):
631+
resp = req.get_response(self.app)
632+
# handoff 404s form a quorum...
633+
self.assertEqual(404, resp.status_int,
634+
'replicas = %s' % self.replicas())
635+
self.assertEqual(ts.internal, resp.headers.get('X-Backend-Timestamp'))
636+
637+
def test_DELETE_insufficient_found_plus_404_507_and_handoffs_fail(self):
638+
if self.replicas() < 3:
639+
return
640+
primary_success = quorum_size(self.replicas()) - 1
641+
primary_failure = self.replicas() - primary_success - 1
642+
primary_codes = [204] * primary_success + [404] + \
643+
[507] * primary_failure
644+
handoff_codes = [507] * self.replicas()
645+
req = swift.common.swob.Request.blank('/v1/a/c/o', method='DELETE')
646+
ts = self.ts()
647+
headers = []
648+
for status in primary_codes + handoff_codes:
649+
if status in (204, 404):
650+
headers.append({'x-backend-timestamp': ts.internal})
651+
else:
652+
headers.append({})
653+
with mocked_http_conn(*(primary_codes + handoff_codes),
654+
headers=headers):
655+
resp = req.get_response(self.app)
656+
# overrides convert the 404 to a 204 so a quorum is formed...
657+
self.assertEqual(resp.status_int, 204,
658+
'replicas = %s' % self.replicas())
659+
660+
def test_DELETE_insufficient_found_plus_507_and_handoffs_fail(self):
661+
primary_success = quorum_size(self.replicas()) - 1
662+
primary_failure = self.replicas() - primary_success
663+
primary_codes = [204] * primary_success + [507] * primary_failure
664+
handoff_codes = [507] * self.replicas()
665+
req = swift.common.swob.Request.blank('/v1/a/c/o', method='DELETE')
666+
ts = self.ts()
667+
headers = []
668+
for status in primary_codes + handoff_codes:
669+
if status in (204, 404):
670+
headers.append({'x-backend-timestamp': ts.internal})
671+
else:
672+
headers.append({})
673+
with mocked_http_conn(*(primary_codes + handoff_codes),
674+
headers=headers):
675+
resp = req.get_response(self.app)
676+
# no quorum...
677+
self.assertEqual(resp.status_int, 503,
678+
'replicas = %s' % self.replicas())
679+
568680
def test_DELETE_half_not_found_statuses(self):
569681
self.obj_ring.set_replicas(4)
570682

@@ -1300,6 +1412,96 @@ def test_write_affinity_per_policy_config_overrides_and_inherits(self):
13001412
self._check_write_affinity(conf, policy_conf, POLICIES[1], [0],
13011413
3 * self.replicas(POLICIES[1]))
13021414

1415+
def test_POST_all_primaries_succeed(self):
1416+
req = swift.common.swob.Request.blank('/v1/a/c/o', method='POST')
1417+
primary_codes = [202] * self.replicas()
1418+
with mocked_http_conn(*primary_codes):
1419+
resp = req.get_response(self.app)
1420+
self.assertEqual(202, resp.status_int,
1421+
'replicas = %s' % self.replicas())
1422+
1423+
def test_POST_sufficient_primaries_succeed_others_404(self):
1424+
req = swift.common.swob.Request.blank('/v1/a/c/o', method='POST')
1425+
# NB: for POST to EC object quorum_size is sufficient for success
1426+
# rather than policy.quorum
1427+
primary_success = quorum_size(self.replicas())
1428+
primary_failure = self.replicas() - primary_success
1429+
primary_codes = [202] * primary_success + [404] * primary_failure
1430+
with mocked_http_conn(*primary_codes):
1431+
resp = req.get_response(self.app)
1432+
self.assertEqual(202, resp.status_int,
1433+
'replicas = %s' % self.replicas())
1434+
1435+
def test_POST_sufficient_primaries_succeed_others_fail(self):
1436+
req = swift.common.swob.Request.blank('/v1/a/c/o', method='POST')
1437+
# NB: for POST to EC object quorum_size is sufficient for success
1438+
# rather than policy.quorum
1439+
primary_success = quorum_size(self.replicas())
1440+
primary_failure = self.replicas() - primary_success
1441+
primary_codes = [202] * primary_success + [Timeout()] * primary_failure
1442+
handoff_codes = [404] * primary_failure
1443+
with mocked_http_conn(*(primary_codes + handoff_codes)):
1444+
resp = req.get_response(self.app)
1445+
self.assertEqual(202, resp.status_int,
1446+
'replicas = %s' % self.replicas())
1447+
1448+
def test_POST_insufficient_primaries_succeed_others_404(self):
1449+
req = swift.common.swob.Request.blank('/v1/a/c/o', method='POST')
1450+
primary_success = quorum_size(self.replicas()) - 1
1451+
primary_failure = self.replicas() - primary_success
1452+
primary_codes = [404] * primary_failure + [202] * primary_success
1453+
with mocked_http_conn(*primary_codes):
1454+
resp = req.get_response(self.app)
1455+
# TODO: should this be a 503?
1456+
self.assertEqual(404, resp.status_int,
1457+
'replicas = %s' % self.replicas())
1458+
1459+
def test_POST_insufficient_primaries_others_fail_handoffs_404(self):
1460+
req = swift.common.swob.Request.blank('/v1/a/c/o', method='POST')
1461+
primary_success = quorum_size(self.replicas()) - 1
1462+
primary_failure = self.replicas() - primary_success
1463+
primary_codes = [Timeout()] * primary_failure + [202] * primary_success
1464+
handoff_codes = [404] * primary_failure
1465+
with mocked_http_conn(*(primary_codes + handoff_codes)):
1466+
resp = req.get_response(self.app)
1467+
# TODO: this should really be a 503
1468+
self.assertEqual(404, resp.status_int,
1469+
'replicas = %s' % self.replicas())
1470+
1471+
def test_POST_insufficient_primaries_others_fail_handoffs_fail(self):
1472+
req = swift.common.swob.Request.blank('/v1/a/c/o', method='POST')
1473+
primary_success = quorum_size(self.replicas()) - 1
1474+
primary_failure = self.replicas() - primary_success
1475+
primary_codes = [Timeout()] * primary_failure + [202] * primary_success
1476+
handoff_codes = [507] * self.replicas()
1477+
with mocked_http_conn(*(primary_codes + handoff_codes)):
1478+
resp = req.get_response(self.app)
1479+
self.assertEqual(503, resp.status_int,
1480+
'replicas = %s' % self.replicas())
1481+
1482+
def test_POST_all_primaries_fail_insufficient_handoff_succeeds(self):
1483+
req = swift.common.swob.Request.blank('/v1/a/c/o', method='POST')
1484+
handoff_success = quorum_size(self.replicas()) - 1
1485+
handoff_not_found = self.replicas() - handoff_success
1486+
primary_codes = [Timeout()] * self.replicas()
1487+
handoff_codes = [202] * handoff_success + [404] * handoff_not_found
1488+
with mocked_http_conn(*(primary_codes + handoff_codes)):
1489+
resp = req.get_response(self.app)
1490+
# TODO: this should really be a 503
1491+
self.assertEqual(404, resp.status_int,
1492+
'replicas = %s' % self.replicas())
1493+
1494+
def test_POST_all_primaries_fail_sufficient_handoff_succeeds(self):
1495+
req = swift.common.swob.Request.blank('/v1/a/c/o', method='POST')
1496+
handoff_success = quorum_size(self.replicas())
1497+
handoff_not_found = self.replicas() - handoff_success
1498+
primary_codes = [Timeout()] * self.replicas()
1499+
handoff_codes = [202] * handoff_success + [404] * handoff_not_found
1500+
with mocked_http_conn(*(primary_codes + handoff_codes)):
1501+
resp = req.get_response(self.app)
1502+
self.assertEqual(202, resp.status_int,
1503+
'replicas = %s' % self.replicas())
1504+
13031505
# end of CommonObjectControllerMixin
13041506

13051507

0 commit comments

Comments
 (0)