|
|
|
@ -4,6 +4,7 @@ |
|
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function, \ |
|
|
|
unicode_literals |
|
|
|
from logging import debug |
|
|
|
|
|
|
|
from octodns.record import Create, Update, Delete, Record |
|
|
|
from octodns.provider.azuredns import _AzureRecord, AzureProvider, \ |
|
|
|
@ -883,12 +884,13 @@ class TestAzureDnsProvider(TestCase): |
|
|
|
|
|
|
|
# test simple records produce no extra changes |
|
|
|
desired = Zone(name=existing.name, sub_zones=[]) |
|
|
|
desired.add_record(Record.new(desired, 'simple', data={ |
|
|
|
simple = Record.new(desired, 'simple', data={ |
|
|
|
'type': record._type, |
|
|
|
'ttl': record.ttl, |
|
|
|
'value': record.value, |
|
|
|
})) |
|
|
|
extra = provider._extra_changes(desired, desired, []) |
|
|
|
}) |
|
|
|
desired.add_record(simple) |
|
|
|
extra = provider._extra_changes(desired, desired, [Create(simple)]) |
|
|
|
self.assertEqual(len(extra), 0) |
|
|
|
|
|
|
|
# test an unchanged dynamic record produces no extra changes |
|
|
|
@ -952,28 +954,28 @@ class TestAzureDnsProvider(TestCase): |
|
|
|
self.assertIsInstance(extra, Update) |
|
|
|
self.assertEqual(extra.new, update_dynamic) |
|
|
|
|
|
|
|
# test non-CNAME dynamic record throws exception |
|
|
|
a_dynamic = Record.new(desired, record.name + '3', data={ |
|
|
|
'type': 'A', |
|
|
|
# test dynamic record of unsupported type throws exception |
|
|
|
unsupported_dynamic = Record.new(desired, record.name + '3', data={ |
|
|
|
'type': 'DNAME', |
|
|
|
'ttl': record.ttl, |
|
|
|
'values': ['1.1.1.1'], |
|
|
|
'value': 'default.unit.tests.', |
|
|
|
'dynamic': { |
|
|
|
'pools': { |
|
|
|
'one': {'values': [{'value': '2.2.2.2'}]}, |
|
|
|
'one': {'values': [{'value': 'one.unit.tests.'}]}, |
|
|
|
}, |
|
|
|
'rules': [ |
|
|
|
{'pool': 'one'}, |
|
|
|
], |
|
|
|
}, |
|
|
|
}) |
|
|
|
desired.add_record(a_dynamic) |
|
|
|
changes.append(Create(a_dynamic)) |
|
|
|
desired.add_record(unsupported_dynamic) |
|
|
|
changes = [Create(unsupported_dynamic)] |
|
|
|
with self.assertRaises(AzureException) as ctx: |
|
|
|
provider._extra_changes(existing, desired, changes) |
|
|
|
self.assertTrue(text_type(ctx).endswith( |
|
|
|
'must be of type CNAME' |
|
|
|
)) |
|
|
|
desired._remove_record(a_dynamic) |
|
|
|
desired._remove_record(unsupported_dynamic) |
|
|
|
|
|
|
|
# test colliding ATM names throws exception |
|
|
|
record1 = Record.new(desired, 'sub.www', data={ |
|
|
|
@ -997,6 +999,100 @@ class TestAzureDnsProvider(TestCase): |
|
|
|
'Collision in Traffic Manager' |
|
|
|
)) |
|
|
|
|
|
|
|
def test_extra_changes_invalid_dynamic_a(self): |
|
|
|
provider = self._get_provider() |
|
|
|
|
|
|
|
desired = Zone(name=zone.name, sub_zones=[]) |
|
|
|
|
|
|
|
# too many test case combinations, here's a method to generate them |
|
|
|
def record_data(all_values=True, rr=True, fallback=True, geo=True): |
|
|
|
data = { |
|
|
|
'type': 'A', |
|
|
|
'ttl': 60, |
|
|
|
'dynamic': { |
|
|
|
'pools': { |
|
|
|
'one': { |
|
|
|
'values': [ |
|
|
|
{'value': '11.11.11.11'}, |
|
|
|
{'value': '12.12.12.12'}, |
|
|
|
], |
|
|
|
'fallback': 'two', |
|
|
|
}, |
|
|
|
'two': { |
|
|
|
'values': [ |
|
|
|
{'value': '2.2.2.2'}, |
|
|
|
], |
|
|
|
}, |
|
|
|
}, |
|
|
|
'rules': [ |
|
|
|
{'geos': ['EU'], 'pool': 'two'}, |
|
|
|
{'pool': 'one'}, |
|
|
|
], |
|
|
|
} |
|
|
|
} |
|
|
|
dynamic = data['dynamic'] |
|
|
|
if not rr: |
|
|
|
dynamic['pools']['one']['values'].pop() |
|
|
|
if not fallback: |
|
|
|
dynamic['pools']['one'].pop('fallback') |
|
|
|
if not geo: |
|
|
|
rule = dynamic['rules'].pop(0) |
|
|
|
if not fallback: |
|
|
|
dynamic['pools'].pop(rule['pool']) |
|
|
|
# put all pool values in default |
|
|
|
data['values'] = [ |
|
|
|
v['value'] |
|
|
|
for p in dynamic['pools'].values() |
|
|
|
for v in p['values'] |
|
|
|
] |
|
|
|
if not all_values: |
|
|
|
rm = list(dynamic['pools'].values())[0]['values'][0]['value'] |
|
|
|
data['values'].remove(rm) |
|
|
|
return data |
|
|
|
|
|
|
|
# test all combinations |
|
|
|
values = [True, False] |
|
|
|
combos = [ |
|
|
|
[arg1, arg2, arg3, arg4] |
|
|
|
for arg1 in values |
|
|
|
for arg2 in values |
|
|
|
for arg3 in values |
|
|
|
for arg4 in values |
|
|
|
] |
|
|
|
for all_values, rr, fallback, geo in combos: |
|
|
|
args = [all_values, rr, fallback, geo] |
|
|
|
|
|
|
|
if not any(args): |
|
|
|
# all False, invalid use-case |
|
|
|
continue |
|
|
|
|
|
|
|
debug('[all_values, rr, fallback, geo] = %s', args) |
|
|
|
data = record_data(*args) |
|
|
|
record = Record.new(desired, 'foo', data) |
|
|
|
|
|
|
|
features = args[1:] |
|
|
|
if all_values and features.count(True) <= 1: |
|
|
|
# assert does not raise exception |
|
|
|
provider._extra_changes(zone, desired, [Create(record)]) |
|
|
|
continue |
|
|
|
|
|
|
|
with self.assertRaises(AzureException) as ctx: |
|
|
|
msg = text_type(ctx) |
|
|
|
provider._extra_changes(zone, desired, [Create(record)]) |
|
|
|
if not all_values: |
|
|
|
self.assertTrue('included in top-level \'values\'' in msg) |
|
|
|
else: |
|
|
|
self.assertTrue('at most one of' in msg) |
|
|
|
|
|
|
|
# multiple profiles should also raise |
|
|
|
record = Record.new(desired, 'foo', |
|
|
|
record_data(True, True, True, True)) |
|
|
|
desired.add_record(record) |
|
|
|
with self.assertRaises(AzureException) as ctx: |
|
|
|
# bypass above check by setting changes to empty |
|
|
|
provider._extra_changes(zone, desired, []) |
|
|
|
self.assertTrue('more than 1 Traffic Managers' in text_type(ctx)) |
|
|
|
|
|
|
|
def test_generate_tm_profile(self): |
|
|
|
provider, zone, record = self._get_dynamic_package() |
|
|
|
profile_gen = provider._generate_tm_profile |
|
|
|
@ -1573,6 +1669,300 @@ class TestAzureDnsProvider(TestCase): |
|
|
|
record2 = provider._populate_record(zone, azrecord) |
|
|
|
self.assertEqual(record2.dynamic._data(), record.dynamic._data()) |
|
|
|
|
|
|
|
def test_dynamic_a_geo(self): |
|
|
|
provider = self._get_provider() |
|
|
|
external = 'Microsoft.Network/trafficManagerProfiles/externalEndpoints' |
|
|
|
|
|
|
|
record = Record.new(zone, 'foo', data={ |
|
|
|
'type': 'A', |
|
|
|
'ttl': 60, |
|
|
|
'values': ['1.1.1.1', '2.2.2.2', '3.3.3.3'], |
|
|
|
'dynamic': { |
|
|
|
'pools': { |
|
|
|
'one': { |
|
|
|
'values': [ |
|
|
|
{'value': '1.1.1.1'}, |
|
|
|
], |
|
|
|
}, |
|
|
|
'two': { |
|
|
|
'values': [ |
|
|
|
{'value': '2.2.2.2'}, |
|
|
|
], |
|
|
|
}, |
|
|
|
'three': { |
|
|
|
'values': [ |
|
|
|
{'value': '3.3.3.3'}, |
|
|
|
], |
|
|
|
}, |
|
|
|
}, |
|
|
|
'rules': [ |
|
|
|
{'geos': ['AS'], 'pool': 'one'}, |
|
|
|
{'geos': ['AF'], 'pool': 'two'}, |
|
|
|
{'pool': 'three'}, |
|
|
|
], |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
# test that extra_changes doesn't complain |
|
|
|
changes = [Create(record)] |
|
|
|
provider._extra_changes(zone, zone, changes) |
|
|
|
|
|
|
|
profiles = provider._generate_traffic_managers(record) |
|
|
|
|
|
|
|
self.assertEqual(len(profiles), 1) |
|
|
|
self.assertTrue(_profile_is_match(profiles[0], Profile( |
|
|
|
name='foo--unit--tests-A', |
|
|
|
traffic_routing_method='Geographic', |
|
|
|
dns_config=DnsConfig( |
|
|
|
relative_name='foo--unit--tests-a', ttl=record.ttl), |
|
|
|
monitor_config=_get_monitor(record), |
|
|
|
endpoints=[ |
|
|
|
Endpoint( |
|
|
|
name='one--default--', |
|
|
|
type=external, |
|
|
|
target='1.1.1.1', |
|
|
|
geo_mapping=['GEO-AS'], |
|
|
|
), |
|
|
|
Endpoint( |
|
|
|
name='two--default--', |
|
|
|
type=external, |
|
|
|
target='2.2.2.2', |
|
|
|
geo_mapping=['GEO-AF'], |
|
|
|
), |
|
|
|
Endpoint( |
|
|
|
name='three--default--', |
|
|
|
type=external, |
|
|
|
target='3.3.3.3', |
|
|
|
geo_mapping=['WORLD'], |
|
|
|
), |
|
|
|
], |
|
|
|
))) |
|
|
|
|
|
|
|
# test that the record and ATM profile gets created |
|
|
|
tm_sync = provider._tm_client.profiles.create_or_update |
|
|
|
create = provider._dns_client.record_sets.create_or_update |
|
|
|
provider._apply_Create(changes[0]) |
|
|
|
# A dynamic record can only have 1 profile |
|
|
|
tm_sync.assert_called_once() |
|
|
|
create.assert_called_once() |
|
|
|
|
|
|
|
# test broken alias |
|
|
|
azrecord = RecordSet( |
|
|
|
ttl=60, target_resource=SubResource(id=None)) |
|
|
|
azrecord.name = record.name or '@' |
|
|
|
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type) |
|
|
|
record2 = provider._populate_record(zone, azrecord) |
|
|
|
self.assertEqual(record2.values, ['255.255.255.255']) |
|
|
|
|
|
|
|
# test that same record gets populated back from traffic managers |
|
|
|
tm_list = provider._tm_client.profiles.list_by_resource_group |
|
|
|
tm_list.return_value = profiles |
|
|
|
azrecord = RecordSet( |
|
|
|
ttl=60, |
|
|
|
target_resource=SubResource(id=profiles[-1].id), |
|
|
|
) |
|
|
|
azrecord.name = record.name or '@' |
|
|
|
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type) |
|
|
|
record2 = provider._populate_record(zone, azrecord) |
|
|
|
self.assertEqual(record2.dynamic._data(), record.dynamic._data()) |
|
|
|
|
|
|
|
def test_dynamic_a_fallback(self): |
|
|
|
provider = self._get_provider() |
|
|
|
external = 'Microsoft.Network/trafficManagerProfiles/externalEndpoints' |
|
|
|
|
|
|
|
record = Record.new(zone, 'foo', data={ |
|
|
|
'type': 'A', |
|
|
|
'ttl': 60, |
|
|
|
'values': ['8.8.8.8'], |
|
|
|
'dynamic': { |
|
|
|
'pools': { |
|
|
|
'one': { |
|
|
|
'values': [ |
|
|
|
{'value': '1.1.1.1'}, |
|
|
|
], |
|
|
|
'fallback': 'two', |
|
|
|
}, |
|
|
|
'two': { |
|
|
|
'values': [ |
|
|
|
{'value': '2.2.2.2'}, |
|
|
|
], |
|
|
|
}, |
|
|
|
}, |
|
|
|
'rules': [ |
|
|
|
{'pool': 'one'}, |
|
|
|
], |
|
|
|
} |
|
|
|
}) |
|
|
|
profiles = provider._generate_traffic_managers(record) |
|
|
|
|
|
|
|
self.assertEqual(len(profiles), 1) |
|
|
|
self.assertTrue(_profile_is_match(profiles[0], Profile( |
|
|
|
name='foo--unit--tests-A', |
|
|
|
traffic_routing_method='Priority', |
|
|
|
dns_config=DnsConfig( |
|
|
|
relative_name='foo--unit--tests-a', ttl=record.ttl), |
|
|
|
monitor_config=_get_monitor(record), |
|
|
|
endpoints=[ |
|
|
|
Endpoint( |
|
|
|
name='one', |
|
|
|
type=external, |
|
|
|
target='1.1.1.1', |
|
|
|
priority=1, |
|
|
|
), |
|
|
|
Endpoint( |
|
|
|
name='two', |
|
|
|
type=external, |
|
|
|
target='2.2.2.2', |
|
|
|
priority=2, |
|
|
|
), |
|
|
|
Endpoint( |
|
|
|
name='--default--', |
|
|
|
type=external, |
|
|
|
target='8.8.8.8', |
|
|
|
priority=3, |
|
|
|
), |
|
|
|
], |
|
|
|
))) |
|
|
|
|
|
|
|
# test that same record gets populated back from traffic managers |
|
|
|
tm_list = provider._tm_client.profiles.list_by_resource_group |
|
|
|
tm_list.return_value = profiles |
|
|
|
azrecord = RecordSet( |
|
|
|
ttl=60, |
|
|
|
target_resource=SubResource(id=profiles[-1].id), |
|
|
|
) |
|
|
|
azrecord.name = record.name or '@' |
|
|
|
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type) |
|
|
|
record2 = provider._populate_record(zone, azrecord) |
|
|
|
self.assertEqual(record2.dynamic._data(), record.dynamic._data()) |
|
|
|
|
|
|
|
def test_dynamic_a_weighted_rr(self): |
|
|
|
provider = self._get_provider() |
|
|
|
external = 'Microsoft.Network/trafficManagerProfiles/externalEndpoints' |
|
|
|
|
|
|
|
record = Record.new(zone, 'foo', data={ |
|
|
|
'type': 'A', |
|
|
|
'ttl': 60, |
|
|
|
'values': ['1.1.1.1', '8.8.8.8'], |
|
|
|
'dynamic': { |
|
|
|
'pools': { |
|
|
|
'one': { |
|
|
|
'values': [ |
|
|
|
{'value': '1.1.1.1', 'weight': 11}, |
|
|
|
{'value': '8.8.8.8', 'weight': 8}, |
|
|
|
], |
|
|
|
}, |
|
|
|
}, |
|
|
|
'rules': [ |
|
|
|
{'pool': 'one'}, |
|
|
|
], |
|
|
|
} |
|
|
|
}) |
|
|
|
profiles = provider._generate_traffic_managers(record) |
|
|
|
|
|
|
|
self.assertEqual(len(profiles), 1) |
|
|
|
self.assertTrue(_profile_is_match(profiles[0], Profile( |
|
|
|
name='foo--unit--tests-A', |
|
|
|
traffic_routing_method='Weighted', |
|
|
|
dns_config=DnsConfig( |
|
|
|
relative_name='foo--unit--tests-a', ttl=record.ttl), |
|
|
|
monitor_config=_get_monitor(record), |
|
|
|
endpoints=[ |
|
|
|
Endpoint( |
|
|
|
name='one--1.1.1.1--default--', |
|
|
|
type=external, |
|
|
|
target='1.1.1.1', |
|
|
|
weight=11, |
|
|
|
), |
|
|
|
Endpoint( |
|
|
|
name='one--8.8.8.8--default--', |
|
|
|
type=external, |
|
|
|
target='8.8.8.8', |
|
|
|
weight=8, |
|
|
|
), |
|
|
|
], |
|
|
|
))) |
|
|
|
|
|
|
|
# test that same record gets populated back from traffic managers |
|
|
|
tm_list = provider._tm_client.profiles.list_by_resource_group |
|
|
|
tm_list.return_value = profiles |
|
|
|
azrecord = RecordSet( |
|
|
|
ttl=60, |
|
|
|
target_resource=SubResource(id=profiles[-1].id), |
|
|
|
) |
|
|
|
azrecord.name = record.name or '@' |
|
|
|
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type) |
|
|
|
record2 = provider._populate_record(zone, azrecord) |
|
|
|
self.assertEqual(record2.dynamic._data(), record.dynamic._data()) |
|
|
|
|
|
|
|
def test_dynamic_aaaa(self): |
|
|
|
provider = self._get_provider() |
|
|
|
external = 'Microsoft.Network/trafficManagerProfiles/externalEndpoints' |
|
|
|
|
|
|
|
record = Record.new(zone, 'foo', data={ |
|
|
|
'type': 'AAAA', |
|
|
|
'ttl': 60, |
|
|
|
'values': ['1::1'], |
|
|
|
'dynamic': { |
|
|
|
'pools': { |
|
|
|
'one': { |
|
|
|
'values': [ |
|
|
|
{'value': '1::1'}, |
|
|
|
], |
|
|
|
}, |
|
|
|
}, |
|
|
|
'rules': [ |
|
|
|
{'pool': 'one'}, |
|
|
|
], |
|
|
|
} |
|
|
|
}) |
|
|
|
profiles = provider._generate_traffic_managers(record) |
|
|
|
|
|
|
|
self.assertEqual(len(profiles), 1) |
|
|
|
self.assertTrue(_profile_is_match(profiles[0], Profile( |
|
|
|
name='foo--unit--tests-AAAA', |
|
|
|
traffic_routing_method='Geographic', |
|
|
|
dns_config=DnsConfig( |
|
|
|
relative_name='foo--unit--tests-aaaa', ttl=record.ttl), |
|
|
|
monitor_config=_get_monitor(record), |
|
|
|
endpoints=[ |
|
|
|
Endpoint( |
|
|
|
name='one--default--', |
|
|
|
type=external, |
|
|
|
target='1::1', |
|
|
|
geo_mapping=['WORLD'], |
|
|
|
), |
|
|
|
], |
|
|
|
))) |
|
|
|
|
|
|
|
# test that the record and ATM profile gets created |
|
|
|
tm_sync = provider._tm_client.profiles.create_or_update |
|
|
|
create = provider._dns_client.record_sets.create_or_update |
|
|
|
provider._apply_Create(Create(record)) |
|
|
|
# A dynamic record can only have 1 profile |
|
|
|
tm_sync.assert_called_once() |
|
|
|
create.assert_called_once() |
|
|
|
|
|
|
|
# test broken alias |
|
|
|
azrecord = RecordSet( |
|
|
|
ttl=60, target_resource=SubResource(id=None)) |
|
|
|
azrecord.name = record.name or '@' |
|
|
|
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type) |
|
|
|
record2 = provider._populate_record(zone, azrecord) |
|
|
|
self.assertEqual(record2.values, ['::1']) |
|
|
|
|
|
|
|
# test that same record gets populated back from traffic managers |
|
|
|
tm_list = provider._tm_client.profiles.list_by_resource_group |
|
|
|
tm_list.return_value = profiles |
|
|
|
azrecord = RecordSet( |
|
|
|
ttl=60, |
|
|
|
target_resource=SubResource(id=profiles[-1].id), |
|
|
|
) |
|
|
|
azrecord.name = record.name or '@' |
|
|
|
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type) |
|
|
|
record2 = provider._populate_record(zone, azrecord) |
|
|
|
self.assertEqual(record2.dynamic._data(), record.dynamic._data()) |
|
|
|
|
|
|
|
def test_sync_traffic_managers(self): |
|
|
|
provider, zone, record = self._get_dynamic_package() |
|
|
|
provider._populate_traffic_managers() |
|
|
|
|