sharder: report perfectly overlapping shard ranges

Previously, shard ranges that overlapped perfectly (i.e. their bounds
were equal) were erroneously excluded from overlapping shard reports.
With this patch they will be reported in logs and cause shard audit
failures.

Also, the format of the audit log line is simplified to avoid
unnecessary duplication of information.

Change-Id: Ie15c9f40a132374c89337f0009fb5cf5a8e62c51
Closes-Bug: #1928459
Related-Bug: #1913332
This commit is contained in:
Alistair Coles 2021-05-14 13:59:37 +01:00
parent 9edcfad22f
commit 1b183f221d
2 changed files with 20 additions and 14 deletions

View File

@ -108,12 +108,13 @@ def find_overlapping_ranges(shard_ranges):
each other. each other.
""" """
result = set() result = set()
for shard_range in shard_ranges: for i, shard_range in enumerate(shard_ranges):
overlapping = [sr for sr in shard_ranges overlapping = [
if shard_range != sr and shard_range.overlaps(sr)] sr for sr in shard_ranges[i + 1:]
if shard_range.name != sr.name and shard_range.overlaps(sr)]
if overlapping: if overlapping:
overlapping.append(shard_range) overlapping.append(shard_range)
overlapping.sort() overlapping.sort(key=ShardRange.sort_key)
result.add(tuple(overlapping)) result.add(tuple(overlapping))
return result return result
@ -994,12 +995,14 @@ class ContainerSharder(ContainerReplicator):
continue continue
shard_ranges = broker.get_shard_ranges(states=state) shard_ranges = broker.get_shard_ranges(states=state)
overlaps = find_overlapping_ranges(shard_ranges) overlaps = find_overlapping_ranges(shard_ranges)
for overlapping_ranges in overlaps: if overlaps:
all_overlaps = ', '.join(
[' '.join(['%s-%s' % (sr.lower, sr.upper)
for sr in overlapping_ranges])
for overlapping_ranges in sorted(list(overlaps))])
warnings.append( warnings.append(
'overlapping ranges in state %s: %s' % 'overlapping ranges in state %r: %s' %
(ShardRange.STATES[state], (ShardRange.STATES[state], all_overlaps))
' '.join(['%s-%s' % (sr.lower, sr.upper)
for sr in overlapping_ranges])))
if warnings: if warnings:
self.logger.warning( self.logger.warning(

View File

@ -117,10 +117,10 @@ class BaseTestSharder(unittest.TestCase):
if not isinstance(state, (tuple, list)): if not isinstance(state, (tuple, list)):
state = [state] * len(bounds) state = [state] * len(bounds)
state_iter = iter(state) state_iter = iter(state)
return [ShardRange('.shards_a/c_%s' % upper, timestamp, return [ShardRange('.shards_a/c_%s_%s' % (upper, index), timestamp,
lower, upper, state=next(state_iter), lower, upper, state=next(state_iter),
object_count=object_count, **kwargs) object_count=object_count, **kwargs)
for lower, upper in bounds] for index, (lower, upper) in enumerate(bounds)]
def ts_encoded(self): def ts_encoded(self):
# make a unique timestamp string with multiple timestamps encoded; # make a unique timestamp string with multiple timestamps encoded;
@ -4859,11 +4859,14 @@ class TestSharder(BaseTestSharder):
self.assertIn( self.assertIn(
'Audit failed for root %s' % broker.db_file, line) 'Audit failed for root %s' % broker.db_file, line)
self.assertIn( self.assertIn(
'overlapping ranges in state %s: k-t s-z' % state_text, 'overlapping ranges in state %r: k-t s-y, y-z y-z'
line) % state_text, line)
# check for no duplicates in reversed order
self.assertNotIn('s-z k-t', line)
expected_stats = {'attempted': 1, 'success': 0, 'failure': 1} expected_stats = {'attempted': 1, 'success': 0, 'failure': 1}
shard_bounds = (('a', 'j'), ('k', 't'), ('s', 'z')) shard_bounds = (('a', 'j'), ('k', 't'), ('s', 'y'),
('y', 'z'), ('y', 'z'))
for state, state_text in ShardRange.STATES.items(): for state, state_text in ShardRange.STATES.items():
if state in (ShardRange.SHRINKING, if state in (ShardRange.SHRINKING,
ShardRange.SHARDED, ShardRange.SHARDED,