diff --git a/swift/common/ring/builder.py b/swift/common/ring/builder.py index 0881bad216..6d53f6ad79 100644 --- a/swift/common/ring/builder.py +++ b/swift/common/ring/builder.py @@ -1369,6 +1369,16 @@ class RingBuilder(object): def _sort_key_for(dev): return (dev['parts_wanted'], random.randint(0, 0xFFFF), dev['id']) + def _validate_replicas_at_tier(self, replicas_by_tier): + tiers = ['cluster', 'regions', 'zones', 'servers', 'devices'] + for i, tier_name in enumerate(tiers): + replicas_at_tier = sum(replicas_by_tier[t] for t in + replicas_by_tier if len(t) == i) + if abs(self.replicas - replicas_at_tier) > 1e-10: + raise exceptions.RingValidationError( + '%s != %s at tier %s' % ( + replicas_at_tier, self.replicas, tier_name)) + def _build_max_replicas_by_tier(self, bound=math.ceil): """ Returns a defaultdict of (tier: replica_count) for all tiers in the @@ -1483,14 +1493,7 @@ class RingBuilder(object): # belts & suspenders/paranoia - at every level, the sum of # weighted_replicas should be very close to the total number of # replicas for the ring - tiers = ['cluster', 'regions', 'zones', 'servers', 'devices'] - for i, tier_name in enumerate(tiers): - replicas_at_tier = sum(weighted_replicas_by_tier[t] for t in - weighted_replicas_by_tier if len(t) == i) - if abs(self.replicas - replicas_at_tier) > 1e-10: - raise exceptions.RingValidationError( - '%s != %s at tier %s' % ( - replicas_at_tier, self.replicas, tier_name)) + self._validate_replicas_at_tier(weighted_replicas_by_tier) return weighted_replicas_by_tier @@ -1593,14 +1596,7 @@ class RingBuilder(object): # belts & suspenders/paranoia - at every level, the sum of # wanted_replicas should be very close to the total number of # replicas for the ring - tiers = ['cluster', 'regions', 'zones', 'servers', 'devices'] - for i, tier_name in enumerate(tiers): - replicas_at_tier = sum(wanted_replicas[t] for t in - wanted_replicas if len(t) == i) - if abs(self.replicas - replicas_at_tier) > 1e-10: - raise exceptions.RingValidationError( - '%s != %s at tier %s' % ( - replicas_at_tier, self.replicas, tier_name)) + self._validate_replicas_at_tier(wanted_replicas) return wanted_replicas @@ -1629,14 +1625,7 @@ class RingBuilder(object): # belts & suspenders/paranoia - at every level, the sum of # target_replicas should be very close to the total number # of replicas for the ring - tiers = ['cluster', 'regions', 'zones', 'servers', 'devices'] - for i, tier_name in enumerate(tiers): - replicas_at_tier = sum(target_replicas[t] for t in - target_replicas if len(t) == i) - if abs(self.replicas - replicas_at_tier) > 1e-10: - raise exceptions.RingValidationError( - '%s != %s at tier %s' % ( - replicas_at_tier, self.replicas, tier_name)) + self._validate_replicas_at_tier(target_replicas) return target_replicas diff --git a/test/unit/common/ring/test_builder.py b/test/unit/common/ring/test_builder.py index 757aa2330a..0b9565b52d 100644 --- a/test/unit/common/ring/test_builder.py +++ b/test/unit/common/ring/test_builder.py @@ -446,6 +446,110 @@ class TestRingBuilder(unittest.TestCase): # maybe not *perfect*, but should be close self.assertLessEqual(balance, 1) + def test_validate_replicate_by_tier(self): + rb = ring.RingBuilder(5, 3, 0) + # replicas 3.0 and three devices with two weight 10 and one weight 5 + rb.add_dev({'id': 0, 'region': 0, 'zone': 0, 'weight': 10, + 'ip': '127.0.0.1', 'port': 6000, 'device': 'object1'}) + rb.add_dev({'id': 1, 'region': 1, 'zone': 1, 'weight': 10, + 'ip': '127.0.0.1', 'port': 6000, 'device': 'object2'}) + rb.add_dev({'id': 2, 'region': 2, 'zone': 2, 'weight': 5, + 'ip': '127.0.0.1', 'port': 6000, 'device': 'object3'}) + rb.rebalance() + + # validate weighted_replicas + weighted_replicas = \ + defaultdict(float, + {(0, 0): 1.0, (1,): 1.0, + (2,): 0.9999999999999999, + (0, 0, '127.0.0.1', 0): 1.0, + (1, 1, '127.0.0.1', 1): 1.0, + (0, 0, '127.0.0.1'): 1.0, + (1, 1, '127.0.0.1'): 1.0, (): 3.0, + (2, 2, '127.0.0.1'): 0.9999999999999999, + (2, 2): 0.9999999999999999, + (2, 2, '127.0.0.1', 2): 0.9999999999999999, + (1, 1): 1.0, (0,): 1.0 + }) + try: + rb._validate_replicas_at_tier(weighted_replicas) + except Exception as e: + self.fail('weighted_replicas is invalid for %s' % e) + + # validate wanted_replicas + wanted_replicas = \ + defaultdict(float, + {(0, 0): 1.0, (1,): 1.0, (2,): 1.0, + (0, 0, '127.0.0.1', 0): 1.0, + (1, 1, '127.0.0.1', 1): 1.0, + (0, 0, '127.0.0.1'): 1.0, + (1, 1, '127.0.0.1'): 1.0, (): 3.0, + (2, 2, '127.0.0.1'): 1.0, + (2, 2): 1.0, (2, 2, '127.0.0.1', 2): 1.0, + (1, 1): 1.0, (0,): 1.0 + }) + try: + rb._validate_replicas_at_tier(wanted_replicas) + except Exception as e: + self.fail('wanted_replicas is invalid for %s' % e) + + # validate target_replicas + target_replicas = \ + defaultdict(float, + {(0, 0): 1.0, (1,): 1.0, + (2,): 0.9999999999999999, + (0, 0, '127.0.0.1', 0): 1.0, + (1, 1, '127.0.0.1', 1): 1.0, + (0, 0, '127.0.0.1'): 1.0, + (1, 1, '127.0.0.1'): 1.0, + (2, 2, '127.0.0.1'): 0.9999999999999999, + (2, 2): 0.9999999999999999, (): 3.0, + (2, 2, '127.0.0.1', 2): 0.9999999999999999, + (1, 1): 1.0, (0,): 1.0 + }) + try: + rb._validate_replicas_at_tier(target_replicas) + except Exception as e: + self.fail('target_replicas is invalid for %s' % e) + + # set overload to 10% + rb.set_overload(0.1) + rb.rebalance() + # validate target_replicas + target_replicas = \ + defaultdict(float, + {(0, 0): 1.0, (1,): 1.0, (2,): 1.0, + (0, 0, '127.0.0.1', 0): 1.0, + (1, 1, '127.0.0.1', 1): 1.0, + (0, 0, '127.0.0.1'): 1.0, + (1, 1, '127.0.0.1'): 1.0, + (2, 2, '127.0.0.1'): 1.0, (2, 2): 1.0, (): 3.0, + (2, 2, '127.0.0.1', 2): 1.0, + (1, 1): 1.0, (0,): 1.0 + }) + try: + rb._validate_replicas_at_tier(target_replicas) + except Exception as e: + self.fail('target_replicas is invalid after overload for %s' + % e) + + # invalidate case + pseudo_replicas = \ + defaultdict(float, + {(0, 0): 1.1, (1,): 1.0, (2,): 1.0, + (0, 0, '127.0.0.1', 0): 1.0, + (1, 1, '127.0.0.1', 1): 1.0, + (0, 0, '127.0.0.1'): 1.0, + (1, 1, '127.0.0.1'): 1.0, + (2, 2, '127.0.0.1'): 1.0, + (2, 2): 1.0, (): 3.0, + (2, 2, '127.0.0.1', 2): 1.0, + (1, 1): 1.0, (0,): 1.0 + }) + with self.assertRaises(exceptions.RingValidationError) as ctx: + rb._validate_replicas_at_tier(pseudo_replicas) + self.assertEqual('3.1 != 3 at tier zones', str(ctx.exception)) + def test_multitier_partial(self): # Multitier test, nothing full rb = ring.RingBuilder(8, 3, 1)