From a767a5cb252a63fa7143bcdd8a75601931e0dc2c Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Fri, 7 Dec 2018 14:29:05 -0800 Subject: [PATCH] Implement pool-level fallbacks and validations --- octodns/record.py | 19 +++++++ tests/test_octodns_record.py | 106 +++++++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/octodns/record.py b/octodns/record.py index dc3db15..4e434e3 100644 --- a/octodns/record.py +++ b/octodns/record.py @@ -509,6 +509,25 @@ class _DynamicMixin(object): reasons.append('missing value in pool "{}" ' 'value {}'.format(_id, value_num)) + fallback = pool.get('fallback', None) + if fallback is not None and fallback not in pools: + reasons.append('undefined fallback "{}" for pool "{}"' + .format(fallback, _id)) + + # Check for loops + fallback = pools[_id].get('fallback', None) + seen = [_id, fallback] + while fallback is not None: + # See if there's a next fallback + fallback = pools.get(fallback, {}).get('fallback', None) + if fallback in seen: + seen = ' -> '.join(seen) + reasons.append('loop in pool fallbacks: {}' + .format(seen)) + # exit the loop + break + seen.append(fallback) + try: rules = data['dynamic']['rules'] except KeyError: diff --git a/tests/test_octodns_record.py b/tests/test_octodns_record.py index 45853d7..8f64a30 100644 --- a/tests/test_octodns_record.py +++ b/tests/test_octodns_record.py @@ -2177,6 +2177,7 @@ class TestDynamicRecords(TestCase): }], }, 'two': { + 'fallback': 'one', 'values': [{ 'value': '4.4.4.4', }, { @@ -2184,6 +2185,7 @@ class TestDynamicRecords(TestCase): }] }, 'three': { + 'fallback': 'two', 'values': [{ 'weight': 1, 'value': '5.5.5.5', @@ -2446,6 +2448,110 @@ class TestDynamicRecords(TestCase): self.assertEquals(['invalid weight "foo" in pool "three" value 2'], ctx.exception.reasons) + # invalid fallback + a_data = { + 'dynamic': { + 'pools': { + 'one': { + 'values': [{ + 'value': '3.3.3.3', + }], + }, + 'two': { + 'fallback': 'invalid', + 'values': [{ + 'value': '4.4.4.4', + }, { + 'value': '5.5.5.5', + }] + }, + 'three': { + 'fallback': 'two', + 'values': [{ + 'weight': 1, + 'value': '6.6.6.6', + }, { + 'weight': 5, + 'value': '7.7.7.7', + }], + }, + }, + 'rules': [{ + 'geos': ['AF', 'EU'], + 'pool': 'three', + }, { + 'geos': ['NA-US-CA'], + 'pool': 'two', + }, { + 'pool': 'one', + }], + }, + 'ttl': 60, + 'type': 'A', + 'values': [ + '1.1.1.1', + '2.2.2.2', + ], + } + with self.assertRaises(ValidationError) as ctx: + Record.new(self.zone, 'bad', a_data) + self.assertEquals(['undefined fallback "invalid" for pool "two"'], + ctx.exception.reasons) + + # fallback loop + a_data = { + 'dynamic': { + 'pools': { + 'one': { + 'fallback': 'three', + 'values': [{ + 'value': '3.3.3.3', + }], + }, + 'two': { + 'fallback': 'one', + 'values': [{ + 'value': '4.4.4.4', + }, { + 'value': '5.5.5.5', + }] + }, + 'three': { + 'fallback': 'two', + 'values': [{ + 'weight': 1, + 'value': '6.6.6.6', + }, { + 'weight': 5, + 'value': '7.7.7.7', + }], + }, + }, + 'rules': [{ + 'geos': ['AF', 'EU'], + 'pool': 'three', + }, { + 'geos': ['NA-US-CA'], + 'pool': 'two', + }, { + 'pool': 'one', + }], + }, + 'ttl': 60, + 'type': 'A', + 'values': [ + '1.1.1.1', + '2.2.2.2', + ], + } + with self.assertRaises(ValidationError) as ctx: + Record.new(self.zone, 'bad', a_data) + self.assertEquals([ + 'loop in pool fallbacks: one -> three -> two', + 'loop in pool fallbacks: three -> two -> one', + 'loop in pool fallbacks: two -> one -> three' + ], ctx.exception.reasons) + # multiple pool problems a_data = { 'dynamic': {