mirror of
https://github.com/github/octodns.git
synced 2024-05-11 05:55:00 +00:00
minimize Azure Traffic Manager hops
This commit is contained in:
@ -646,14 +646,23 @@ class AzureProvider(BaseProvider):
|
||||
pools = defaultdict(lambda: {'fallback': None, 'values': []})
|
||||
rules = []
|
||||
|
||||
# top level geo profile
|
||||
geo_profile = self._get_tm_profile_by_id(azrecord.target_resource.id)
|
||||
for geo_ep in geo_profile.endpoints:
|
||||
# top level profile
|
||||
root_profile = self._get_tm_profile_by_id(azrecord.target_resource.id)
|
||||
if root_profile.traffic_routing_method != 'Geographic':
|
||||
# This record does not use geo fencing, so we skip the Geographic
|
||||
# profile hop; let's pretend to be a geo-profile's only endpoint
|
||||
geo_ep = Endpoint(target_resource_id=root_profile.id)
|
||||
geo_ep.target_resource = root_profile
|
||||
endpoints = [geo_ep]
|
||||
else:
|
||||
endpoints = root_profile.endpoints
|
||||
|
||||
for geo_ep in endpoints:
|
||||
rule = {}
|
||||
|
||||
# resolve list of regions
|
||||
geo_map = list(geo_ep.geo_mapping)
|
||||
if geo_map != ['WORLD']:
|
||||
geo_map = list(geo_ep.geo_mapping or [])
|
||||
if geo_map and geo_map != ['WORLD']:
|
||||
if 'GEO-ME' in geo_map:
|
||||
# Azure treats Middle East as a separate group, but
|
||||
# its part of Asia in octoDNS, so we need to remove GEO-ME
|
||||
@ -662,7 +671,7 @@ class AzureProvider(BaseProvider):
|
||||
# profile was generated by octoDNS
|
||||
if 'GEO-AS' not in geo_map:
|
||||
msg = 'Profile={} for record {}: '.format(
|
||||
geo_profile.name, azrecord.fqdn)
|
||||
root_profile.name, azrecord.fqdn)
|
||||
msg += 'Middle East (GEO-ME) is not supported by ' + \
|
||||
'octoDNS. It needs to be either paired ' + \
|
||||
'with Asia (GEO-AS) or expanded into ' + \
|
||||
@ -690,18 +699,35 @@ class AzureProvider(BaseProvider):
|
||||
# country
|
||||
geos.append(GeoCodes.country_to_code(code))
|
||||
|
||||
# second level priority profile
|
||||
# build fallback chain from second level priority profile
|
||||
if geo_ep.target_resource_id:
|
||||
target = geo_ep.target_resource
|
||||
if target.traffic_routing_method == 'Priority':
|
||||
rule_endpoints = target.endpoints
|
||||
rule_endpoints.sort(key=lambda e: e.priority)
|
||||
else:
|
||||
# Weighted
|
||||
geo_ep.name = target.endpoints[0].name.split('--', 1)[0]
|
||||
rule_endpoints = [geo_ep]
|
||||
else:
|
||||
# this geo directly points to the default, so we skip the
|
||||
# Priority profile hop and directly use an external endpoint;
|
||||
# let's pretend to be a Priority profile's only endpoint
|
||||
rule_endpoints = [geo_ep]
|
||||
|
||||
pool = None
|
||||
rule_endpoints = geo_ep.target_resource.endpoints
|
||||
rule_endpoints = sorted(rule_endpoints, key=lambda e: e.priority)
|
||||
for rule_ep in rule_endpoints:
|
||||
pool_name = rule_ep.name
|
||||
|
||||
# last/default pool
|
||||
if pool_name == '--default--':
|
||||
if pool_name.endswith('--default--'):
|
||||
default.add(rule_ep.target)
|
||||
# this should be the last one, so let's break here
|
||||
break
|
||||
if pool_name == '--default--':
|
||||
# this should be the last one, so let's break here
|
||||
break
|
||||
# last pool is a single value pool and its value is same
|
||||
# as record's default value
|
||||
pool_name = pool_name[:-len('--default--')]
|
||||
|
||||
# set first priority endpoint as the rule's primary pool
|
||||
if 'pool' not in rule:
|
||||
@ -711,32 +737,32 @@ class AzureProvider(BaseProvider):
|
||||
# set current pool as fallback of the previous pool
|
||||
pool['fallback'] = pool_name
|
||||
|
||||
if pool_name in pools:
|
||||
# we've already populated the pool
|
||||
continue
|
||||
|
||||
# populate the pool from Weighted profile
|
||||
# these should be leaf node entries with no further nesting
|
||||
pool = pools[pool_name]
|
||||
endpoints = []
|
||||
# these should be leaf node entries with no further nesting
|
||||
|
||||
if rule_ep.target_resource_id:
|
||||
# third (and last) level weighted RR profile
|
||||
endpoints = rule_ep.target_resource.endpoints
|
||||
else:
|
||||
# single-value pool
|
||||
# single-value pool, so we skip the Weighted profile hop
|
||||
# and directly use an external endpoint; let's pretend to
|
||||
# be a Weighted profile's only endpoint
|
||||
endpoints = [rule_ep]
|
||||
|
||||
for pool_ep in endpoints:
|
||||
val = pool_ep.target
|
||||
value_dict = {
|
||||
pool['values'].append({
|
||||
'value': _check_endswith_dot(val),
|
||||
'weight': pool_ep.weight or 1,
|
||||
}
|
||||
if value_dict not in pool['values']:
|
||||
pool['values'].append(value_dict)
|
||||
|
||||
if 'pool' not in rule or not default:
|
||||
# this will happen if the priority profile does not have
|
||||
# enough endpoints
|
||||
msg = 'Expected at least 2 endpoints in {}, got {}'.format(
|
||||
geo_ep.target_resource.name, len(rule_endpoints)
|
||||
)
|
||||
raise AzureException(msg)
|
||||
})
|
||||
if pool_ep.name.endswith('--default--'):
|
||||
default.add(val)
|
||||
|
||||
rules.append(rule)
|
||||
|
||||
@ -809,7 +835,7 @@ class AzureProvider(BaseProvider):
|
||||
elif ep.target:
|
||||
ep.type = endpoint_type_prefix + 'externalEndpoints'
|
||||
else:
|
||||
msg = ('Invalid endpoint {} in profile {}, needs to have' +
|
||||
msg = ('Invalid endpoint {} in profile {}, needs to have ' +
|
||||
'either target or target_resource_id').format(
|
||||
ep.name, name)
|
||||
raise AzureException(msg)
|
||||
@ -828,39 +854,83 @@ class AzureProvider(BaseProvider):
|
||||
location='global',
|
||||
)
|
||||
|
||||
def _update_tm_name(self, profile, new_name):
|
||||
profile.name = new_name
|
||||
profile.id = self._profile_name_to_id(new_name)
|
||||
profile.dns_config.relative_name = new_name
|
||||
|
||||
return profile
|
||||
|
||||
def _generate_traffic_managers(self, record):
|
||||
traffic_managers = []
|
||||
pools = record.dynamic.pools
|
||||
|
||||
default = record.value[:-1]
|
||||
tm_suffix = _traffic_manager_suffix(record)
|
||||
profile = self._generate_tm_profile
|
||||
|
||||
geo_endpoints = []
|
||||
pool_profiles = {}
|
||||
|
||||
for rule in record.dynamic.rules:
|
||||
# Prepare the list of Traffic manager geos
|
||||
rule_geos = rule.data.get('geos', [])
|
||||
geos = []
|
||||
for geo in rule_geos:
|
||||
if '-' in geo:
|
||||
# country/state
|
||||
geos.append(geo.split('-', 1)[-1])
|
||||
else:
|
||||
# continent
|
||||
if geo == 'AS':
|
||||
# Middle East is part of Asia in octoDNS, but
|
||||
# Azure treats it as a separate "group", so let's
|
||||
# add it in the list of geo mappings. We will drop
|
||||
# it when we later parse the list of regions.
|
||||
geos.append('GEO-ME')
|
||||
elif geo == 'OC':
|
||||
# Azure uses Australia/Pacific (AP) instead of
|
||||
# Oceania
|
||||
geo = 'AP'
|
||||
|
||||
geos.append('GEO-{}'.format(geo))
|
||||
if not geos:
|
||||
geos.append('WORLD')
|
||||
|
||||
pool_name = rule.data['pool']
|
||||
rule_endpoints = []
|
||||
priority = 1
|
||||
default_seen = False
|
||||
|
||||
while pool_name:
|
||||
# iterate until we reach end of fallback chain
|
||||
default_seen = False
|
||||
pool = pools[pool_name].data
|
||||
profile_name = 'pool-{}--{}'.format(pool_name, tm_suffix)
|
||||
if len(pool['values']) > 1:
|
||||
# create Weighted profile for multi-value pool
|
||||
endpoints = []
|
||||
for val in pool['values']:
|
||||
target = val['value']
|
||||
# strip trailing dot from CNAME value
|
||||
target = target[:-1]
|
||||
endpoints.append(Endpoint(
|
||||
name=target,
|
||||
target=target,
|
||||
weight=val.get('weight', 1),
|
||||
))
|
||||
pool_profile = profile(profile_name, 'Weighted', endpoints,
|
||||
record)
|
||||
traffic_managers.append(pool_profile)
|
||||
pool_profile = pool_profiles.get(pool_name)
|
||||
if pool_profile is None:
|
||||
endpoints = []
|
||||
for val in pool['values']:
|
||||
target = val['value']
|
||||
# strip trailing dot from CNAME value
|
||||
target = target[:-1]
|
||||
ep_name = '{}--{}'.format(pool_name, target)
|
||||
if target == default:
|
||||
# mark default
|
||||
ep_name += '--default--'
|
||||
default_seen = True
|
||||
endpoints.append(Endpoint(
|
||||
name=ep_name,
|
||||
target=target,
|
||||
weight=val.get('weight', 1),
|
||||
))
|
||||
profile_name = 'pool-{}--{}'.format(
|
||||
pool_name, tm_suffix)
|
||||
pool_profile = profile(profile_name, 'Weighted',
|
||||
endpoints, record)
|
||||
traffic_managers.append(pool_profile)
|
||||
pool_profiles[pool_name] = pool_profile
|
||||
|
||||
# append pool to endpoint list of fallback rule profile
|
||||
rule_endpoints.append(Endpoint(
|
||||
@ -869,8 +939,15 @@ class AzureProvider(BaseProvider):
|
||||
priority=priority,
|
||||
))
|
||||
else:
|
||||
# add single-value pool as an external endpoint
|
||||
# Skip Weighted profile hop for single-value pool
|
||||
# append its value as an external endpoint to fallback
|
||||
# rule profile
|
||||
target = pool['values'][0]['value'][:-1]
|
||||
ep_name = pool_name
|
||||
if target == default:
|
||||
# mark default
|
||||
ep_name += '--default--'
|
||||
default_seen = True
|
||||
rule_endpoints.append(Endpoint(
|
||||
name=pool_name,
|
||||
target=target,
|
||||
@ -880,51 +957,61 @@ class AzureProvider(BaseProvider):
|
||||
priority += 1
|
||||
pool_name = pool.get('fallback')
|
||||
|
||||
# append default profile to the end
|
||||
rule_endpoints.append(Endpoint(
|
||||
name='--default--',
|
||||
target=record.value[:-1],
|
||||
priority=priority,
|
||||
))
|
||||
# create rule profile with fallback chain
|
||||
rule_profile_name = 'rule-{}--{}'.format(rule.data['pool'],
|
||||
tm_suffix)
|
||||
rule_profile = profile(rule_profile_name, 'Priority',
|
||||
rule_endpoints, record)
|
||||
traffic_managers.append(rule_profile)
|
||||
# append default endpoint unless it is already included in
|
||||
# last pool of rule profile
|
||||
if not default_seen:
|
||||
rule_endpoints.append(Endpoint(
|
||||
name='--default--',
|
||||
target=default,
|
||||
priority=priority,
|
||||
))
|
||||
|
||||
# append rule profile to top-level geo profile
|
||||
rule_geos = rule.data.get('geos', [])
|
||||
geos = []
|
||||
if len(rule_geos) > 0:
|
||||
for geo in rule_geos:
|
||||
if '-' in geo:
|
||||
# country or state
|
||||
geos.append(geo.split('-', 1)[-1])
|
||||
else:
|
||||
# continent
|
||||
if geo == 'AS':
|
||||
# Middle East is part of Asia in octoDNS, but
|
||||
# Azure treats it as a separate "group", so let's
|
||||
# add it in the list of geo mappings. We will drop
|
||||
# it when we later parse the list of regions.
|
||||
geos.append('GEO-ME')
|
||||
elif geo == 'OC':
|
||||
# Azure uses Australia/Pacific (AP) instead of
|
||||
# Oceania
|
||||
geo = 'AP'
|
||||
if len(rule_endpoints) > 1:
|
||||
# create rule profile with fallback chain
|
||||
rule_profile_name = 'rule-{}--{}'.format(
|
||||
rule.data['pool'], tm_suffix)
|
||||
rule_profile = profile(rule_profile_name, 'Priority',
|
||||
rule_endpoints, record)
|
||||
traffic_managers.append(rule_profile)
|
||||
|
||||
geos.append('GEO-{}'.format(geo))
|
||||
# append rule profile to top-level geo profile
|
||||
geo_endpoints.append(Endpoint(
|
||||
name='rule-{}'.format(rule.data['pool']),
|
||||
target_resource_id=rule_profile.id,
|
||||
geo_mapping=geos,
|
||||
))
|
||||
else:
|
||||
geos.append('WORLD')
|
||||
geo_endpoints.append(Endpoint(
|
||||
name='rule-{}'.format(rule.data['pool']),
|
||||
target_resource_id=rule_profile.id,
|
||||
geo_mapping=geos,
|
||||
))
|
||||
# Priority profile has only one endpoint; skip the hop and
|
||||
# append its only endpoint to the top-level profile
|
||||
rule_ep = rule_endpoints[0]
|
||||
if rule_ep.target_resource_id:
|
||||
# point directly to the Weighted pool profile
|
||||
geo_endpoints.append(Endpoint(
|
||||
name='rule-{}'.format(rule.data['pool']),
|
||||
target_resource_id=rule_ep.target_resource_id,
|
||||
geo_mapping=geos,
|
||||
))
|
||||
else:
|
||||
# just add the value of single-value pool
|
||||
geo_endpoints.append(Endpoint(
|
||||
name=rule_ep.name + '--default--',
|
||||
target=rule_ep.target,
|
||||
geo_mapping=geos,
|
||||
))
|
||||
|
||||
geo_profile = profile(tm_suffix, 'Geographic', geo_endpoints, record)
|
||||
traffic_managers.append(geo_profile)
|
||||
if len(geo_endpoints) == 1 and \
|
||||
geo_endpoints[0].geo_mapping == ['WORLD'] and \
|
||||
geo_endpoints[0].target_resource_id:
|
||||
# Single WORLD rule does not require a Geographic profile, use
|
||||
# the target profile as the root profile
|
||||
target_profile_id = geo_endpoints[0].target_resource_id
|
||||
profile_map = dict((tm.id, tm) for tm in traffic_managers)
|
||||
target_profile = profile_map[target_profile_id]
|
||||
self._update_tm_name(target_profile, tm_suffix)
|
||||
else:
|
||||
geo_profile = profile(tm_suffix, 'Geographic', geo_endpoints,
|
||||
record)
|
||||
traffic_managers.append(geo_profile)
|
||||
|
||||
return traffic_managers
|
||||
|
||||
|
@ -614,13 +614,13 @@ class TestAzureDnsProvider(TestCase):
|
||||
monitor_config=monitor,
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
name='two1.unit.tests',
|
||||
name='two--two1.unit.tests',
|
||||
type=external,
|
||||
target='two1.unit.tests',
|
||||
weight=3,
|
||||
),
|
||||
Endpoint(
|
||||
name='two2.unit.tests',
|
||||
name='two--two2.unit.tests',
|
||||
type=external,
|
||||
target='two2.unit.tests',
|
||||
weight=4,
|
||||
@ -847,121 +847,6 @@ class TestAzureDnsProvider(TestCase):
|
||||
self.assertEquals(len(zone.records), 17)
|
||||
self.assertTrue(exists)
|
||||
|
||||
def test_populate_dynamic(self):
|
||||
# Middle east without Asia raises exception
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
tm_suffix = _traffic_manager_suffix(record)
|
||||
tm_id = provider._profile_name_to_id
|
||||
tm_list = provider._tm_client.profiles.list_by_resource_group
|
||||
rule_name = 'rule-one--{}'.format(tm_suffix)
|
||||
nested = 'Microsoft.Network/trafficManagerProfiles/nestedEndpoints'
|
||||
tm_list.return_value = [
|
||||
Profile(
|
||||
id=tm_id(tm_suffix),
|
||||
name=tm_suffix,
|
||||
traffic_routing_method='Geographic',
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
geo_mapping=['GEO-ME'],
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
azrecord = RecordSet(
|
||||
ttl=60,
|
||||
target_resource=SubResource(id=tm_id(tm_suffix)),
|
||||
)
|
||||
azrecord.name = record.name or '@'
|
||||
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
|
||||
with self.assertRaises(AzureException) as ctx:
|
||||
provider._populate_record(zone, azrecord)
|
||||
self.assertTrue(text_type(ctx).startswith(
|
||||
'Middle East (GEO-ME) is not supported'
|
||||
))
|
||||
|
||||
# empty priority profile raises exception
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
tm_list = provider._tm_client.profiles.list_by_resource_group
|
||||
rule_name = 'rule-one--{}'.format(tm_suffix)
|
||||
nested = 'Microsoft.Network/trafficManagerProfiles/nestedEndpoints'
|
||||
tm_list.return_value = [
|
||||
Profile(
|
||||
id=tm_id(rule_name),
|
||||
name=rule_name,
|
||||
traffic_routing_method='Priority',
|
||||
endpoints=[],
|
||||
),
|
||||
Profile(
|
||||
id=tm_id(tm_suffix),
|
||||
name=tm_suffix,
|
||||
traffic_routing_method='Geographic',
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
geo_mapping=['WORLD'],
|
||||
name='rule-one',
|
||||
type=nested,
|
||||
target_resource_id=tm_id(rule_name),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
with self.assertRaises(AzureException) as ctx:
|
||||
provider._populate_record(zone, azrecord)
|
||||
self.assertTrue(text_type(ctx).startswith(
|
||||
'Expected at least 2 endpoints'
|
||||
))
|
||||
|
||||
# valid set of profiles produce expected dynamic record
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
root_profile_id = provider._profile_name_to_id(
|
||||
_traffic_manager_suffix(record)
|
||||
)
|
||||
azrecord = RecordSet(
|
||||
ttl=60,
|
||||
target_resource=SubResource(id=root_profile_id),
|
||||
)
|
||||
azrecord.name = record.name or '@'
|
||||
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
|
||||
|
||||
record = provider._populate_record(zone, azrecord)
|
||||
self.assertEqual(record.name, 'foo')
|
||||
self.assertEqual(record.ttl, 60)
|
||||
self.assertEqual(record.value, 'default.unit.tests.')
|
||||
self.assertEqual(record.dynamic._data(), {
|
||||
'pools': {
|
||||
'one': {
|
||||
'values': [
|
||||
{'value': 'one.unit.tests.', 'weight': 1},
|
||||
],
|
||||
'fallback': 'two',
|
||||
},
|
||||
'two': {
|
||||
'values': [
|
||||
{'value': 'two1.unit.tests.', 'weight': 3},
|
||||
{'value': 'two2.unit.tests.', 'weight': 4},
|
||||
],
|
||||
'fallback': 'three',
|
||||
},
|
||||
'three': {
|
||||
'values': [
|
||||
{'value': 'three.unit.tests.', 'weight': 1},
|
||||
],
|
||||
'fallback': None,
|
||||
},
|
||||
},
|
||||
'rules': [
|
||||
{'geos': ['AF', 'EU-DE', 'NA-US-CA', 'OC'], 'pool': 'one'},
|
||||
{'pool': 'two'},
|
||||
],
|
||||
})
|
||||
|
||||
# valid profiles with Middle East test case
|
||||
geo_profile = provider._get_tm_for_dynamic_record(record)
|
||||
geo_profile.endpoints[0].geo_mapping.extend(['GEO-ME', 'GEO-AS'])
|
||||
record = provider._populate_record(zone, azrecord)
|
||||
self.assertIn('AS', record.dynamic.rules[0].data['geos'])
|
||||
self.assertNotIn('ME', record.dynamic.rules[0].data['geos'])
|
||||
|
||||
def test_populate_zone(self):
|
||||
provider = self._get_provider()
|
||||
|
||||
@ -1114,6 +999,7 @@ class TestAzureDnsProvider(TestCase):
|
||||
'/providers/Microsoft.Network/trafficManagerProfiles/' + name
|
||||
self.assertEqual(profile.id, expected_id)
|
||||
self.assertEqual(profile.name, name)
|
||||
self.assertEqual(profile.name, profile.dns_config.relative_name)
|
||||
self.assertEqual(profile.traffic_routing_method, routing)
|
||||
self.assertEqual(profile.dns_config.ttl, record.ttl)
|
||||
self.assertEqual(len(profile.endpoints), len(endpoints))
|
||||
@ -1127,33 +1013,451 @@ class TestAzureDnsProvider(TestCase):
|
||||
'Microsoft.Network/trafficManagerProfiles/nestedEndpoints'
|
||||
)
|
||||
|
||||
def test_generate_traffic_managers(self):
|
||||
def test_dynamic_record(self):
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
profiles = provider._generate_traffic_managers(record)
|
||||
deduped = []
|
||||
seen = set()
|
||||
for profile in profiles:
|
||||
if profile.name not in seen:
|
||||
deduped.append(profile)
|
||||
seen.add(profile.name)
|
||||
|
||||
# check that every profile is a match with what we expect
|
||||
expected_profiles = self._get_tm_profiles(provider)
|
||||
self.assertEqual(len(expected_profiles), len(deduped))
|
||||
for have, expected in zip(deduped, expected_profiles):
|
||||
self.assertEqual(len(expected_profiles), len(profiles))
|
||||
for have, expected in zip(profiles, expected_profiles):
|
||||
self.assertTrue(_profile_is_match(have, expected))
|
||||
|
||||
# check that dynamic record is populated back from 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_generate_traffic_managers_middle_east(self):
|
||||
# check Asia/Middle East test case
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
record.dynamic._data()['rules'][0]['geos'].append('AS')
|
||||
profiles = provider._generate_traffic_managers(record)
|
||||
geo_profile_name = _traffic_manager_suffix(record)
|
||||
geo_profile = next(
|
||||
profile
|
||||
for profile in profiles
|
||||
if profile.name == geo_profile_name
|
||||
self.assertIn('GEO-ME', profiles[-1].endpoints[0].geo_mapping)
|
||||
self.assertIn('GEO-AS', profiles[-1].endpoints[0].geo_mapping)
|
||||
|
||||
def test_populate_dynamic_middle_east(self):
|
||||
# Middle east without Asia raises exception
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
tm_suffix = _traffic_manager_suffix(record)
|
||||
tm_id = provider._profile_name_to_id
|
||||
tm_list = provider._tm_client.profiles.list_by_resource_group
|
||||
tm_list.return_value = [
|
||||
Profile(
|
||||
id=tm_id(tm_suffix),
|
||||
name=tm_suffix,
|
||||
traffic_routing_method='Geographic',
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
geo_mapping=['GEO-ME'],
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
azrecord = RecordSet(
|
||||
ttl=60,
|
||||
target_resource=SubResource(id=tm_id(tm_suffix)),
|
||||
)
|
||||
self.assertIn('GEO-ME', geo_profile.endpoints[0].geo_mapping)
|
||||
self.assertIn('GEO-AS', geo_profile.endpoints[0].geo_mapping)
|
||||
azrecord.name = record.name or '@'
|
||||
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
|
||||
with self.assertRaises(AzureException) as ctx:
|
||||
provider._populate_record(zone, azrecord)
|
||||
self.assertTrue(text_type(ctx).startswith(
|
||||
'Middle East (GEO-ME) is not supported'
|
||||
))
|
||||
|
||||
# valid profiles with Middle East test case
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
geo_profile = provider._get_tm_for_dynamic_record(record)
|
||||
geo_profile.endpoints[0].geo_mapping.extend(['GEO-ME', 'GEO-AS'])
|
||||
record = provider._populate_record(zone, azrecord)
|
||||
self.assertIn('AS', record.dynamic.rules[0].data['geos'])
|
||||
self.assertNotIn('ME', record.dynamic.rules[0].data['geos'])
|
||||
|
||||
def test_dynamic_no_geo(self):
|
||||
# test that traffic managers are generated as expected
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
external = 'Microsoft.Network/trafficManagerProfiles/externalEndpoints'
|
||||
|
||||
record = Record.new(zone, 'foo', data={
|
||||
'type': 'CNAME',
|
||||
'ttl': 60,
|
||||
'value': 'default.unit.tests.',
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'one': {
|
||||
'values': [
|
||||
{'value': 'one.unit.tests.'},
|
||||
],
|
||||
},
|
||||
},
|
||||
'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',
|
||||
traffic_routing_method='Priority',
|
||||
dns_config=DnsConfig(
|
||||
relative_name='foo-unit-tests', ttl=60),
|
||||
monitor_config=_get_monitor(record),
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
name='one',
|
||||
type=external,
|
||||
target='one.unit.tests',
|
||||
priority=1,
|
||||
),
|
||||
Endpoint(
|
||||
name='--default--',
|
||||
type=external,
|
||||
target='default.unit.tests',
|
||||
priority=2,
|
||||
),
|
||||
],
|
||||
)))
|
||||
|
||||
# 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[0].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_fallback_is_default(self):
|
||||
# test that traffic managers are generated as expected
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
external = 'Microsoft.Network/trafficManagerProfiles/externalEndpoints'
|
||||
|
||||
record = Record.new(zone, 'foo', data={
|
||||
'type': 'CNAME',
|
||||
'ttl': 60,
|
||||
'value': 'default.unit.tests.',
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'def': {
|
||||
'values': [
|
||||
{'value': 'default.unit.tests.'},
|
||||
],
|
||||
},
|
||||
},
|
||||
'rules': [
|
||||
{'geos': ['AF'], 'pool': 'def'},
|
||||
],
|
||||
}
|
||||
})
|
||||
profiles = provider._generate_traffic_managers(record)
|
||||
|
||||
self.assertEqual(len(profiles), 1)
|
||||
self.assertTrue(_profile_is_match(profiles[0], Profile(
|
||||
name='foo-unit-tests',
|
||||
traffic_routing_method='Geographic',
|
||||
dns_config=DnsConfig(
|
||||
relative_name='foo-unit-tests', ttl=60),
|
||||
monitor_config=_get_monitor(record),
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
name='def--default--',
|
||||
type=external,
|
||||
target='default.unit.tests',
|
||||
geo_mapping=['GEO-AF'],
|
||||
),
|
||||
],
|
||||
)))
|
||||
|
||||
# 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[0].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_pool_contains_default(self):
|
||||
# test that traffic managers are generated as expected
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
tm_id = provider._profile_name_to_id
|
||||
external = 'Microsoft.Network/trafficManagerProfiles/externalEndpoints'
|
||||
nested = 'Microsoft.Network/trafficManagerProfiles/nestedEndpoints'
|
||||
|
||||
record = Record.new(zone, 'foo', data={
|
||||
'type': 'CNAME',
|
||||
'ttl': 60,
|
||||
'value': 'default.unit.tests.',
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'rr': {
|
||||
'values': [
|
||||
{'value': 'one.unit.tests.'},
|
||||
{'value': 'two.unit.tests.'},
|
||||
{'value': 'default.unit.tests.'},
|
||||
{'value': 'final.unit.tests.'},
|
||||
],
|
||||
},
|
||||
},
|
||||
'rules': [
|
||||
{'geos': ['AF'], 'pool': 'rr'},
|
||||
],
|
||||
}
|
||||
})
|
||||
profiles = provider._generate_traffic_managers(record)
|
||||
|
||||
self.assertEqual(len(profiles), 2)
|
||||
self.assertTrue(_profile_is_match(profiles[0], Profile(
|
||||
name='pool-rr--foo-unit-tests',
|
||||
traffic_routing_method='Weighted',
|
||||
dns_config=DnsConfig(
|
||||
relative_name='pool-rr--foo-unit-tests', ttl=60),
|
||||
monitor_config=_get_monitor(record),
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
name='rr--one.unit.tests',
|
||||
type=external,
|
||||
target='one.unit.tests',
|
||||
weight=1,
|
||||
),
|
||||
Endpoint(
|
||||
name='rr--two.unit.tests',
|
||||
type=external,
|
||||
target='two.unit.tests',
|
||||
weight=1,
|
||||
),
|
||||
Endpoint(
|
||||
name='rr--default.unit.tests--default--',
|
||||
type=external,
|
||||
target='default.unit.tests',
|
||||
weight=1,
|
||||
),
|
||||
Endpoint(
|
||||
name='rr--final.unit.tests',
|
||||
type=external,
|
||||
target='final.unit.tests',
|
||||
weight=1,
|
||||
),
|
||||
],
|
||||
)))
|
||||
self.assertTrue(_profile_is_match(profiles[1], Profile(
|
||||
name='foo-unit-tests',
|
||||
traffic_routing_method='Geographic',
|
||||
dns_config=DnsConfig(
|
||||
relative_name='foo-unit-tests', ttl=60),
|
||||
monitor_config=_get_monitor(record),
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
name='rule-rr',
|
||||
type=nested,
|
||||
target_resource_id=tm_id('pool-rr--foo-unit-tests'),
|
||||
geo_mapping=['GEO-AF'],
|
||||
),
|
||||
],
|
||||
)))
|
||||
|
||||
# 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_pool_contains_default_no_geo(self):
|
||||
# test that traffic managers are generated as expected
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
external = 'Microsoft.Network/trafficManagerProfiles/externalEndpoints'
|
||||
|
||||
record = Record.new(zone, 'foo', data={
|
||||
'type': 'CNAME',
|
||||
'ttl': 60,
|
||||
'value': 'default.unit.tests.',
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'rr': {
|
||||
'values': [
|
||||
{'value': 'one.unit.tests.'},
|
||||
{'value': 'two.unit.tests.'},
|
||||
{'value': 'default.unit.tests.'},
|
||||
{'value': 'final.unit.tests.'},
|
||||
],
|
||||
},
|
||||
},
|
||||
'rules': [
|
||||
{'pool': 'rr'},
|
||||
],
|
||||
}
|
||||
})
|
||||
profiles = provider._generate_traffic_managers(record)
|
||||
|
||||
self.assertEqual(len(profiles), 1)
|
||||
self.assertTrue(_profile_is_match(profiles[0], Profile(
|
||||
name='foo-unit-tests',
|
||||
traffic_routing_method='Weighted',
|
||||
dns_config=DnsConfig(
|
||||
relative_name='foo-unit-tests', ttl=60),
|
||||
monitor_config=_get_monitor(record),
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
name='rr--one.unit.tests',
|
||||
type=external,
|
||||
target='one.unit.tests',
|
||||
weight=1,
|
||||
),
|
||||
Endpoint(
|
||||
name='rr--two.unit.tests',
|
||||
type=external,
|
||||
target='two.unit.tests',
|
||||
weight=1,
|
||||
),
|
||||
Endpoint(
|
||||
name='rr--default.unit.tests--default--',
|
||||
type=external,
|
||||
target='default.unit.tests',
|
||||
weight=1,
|
||||
),
|
||||
Endpoint(
|
||||
name='rr--final.unit.tests',
|
||||
type=external,
|
||||
target='final.unit.tests',
|
||||
weight=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[0].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_last_pool_contains_default_no_geo(self):
|
||||
# test that traffic managers are generated as expected
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
tm_id = provider._profile_name_to_id
|
||||
external = 'Microsoft.Network/trafficManagerProfiles/externalEndpoints'
|
||||
nested = 'Microsoft.Network/trafficManagerProfiles/nestedEndpoints'
|
||||
|
||||
record = Record.new(zone, 'foo', data={
|
||||
'type': 'CNAME',
|
||||
'ttl': 60,
|
||||
'value': 'default.unit.tests.',
|
||||
'dynamic': {
|
||||
'pools': {
|
||||
'cloud': {
|
||||
'values': [
|
||||
{'value': 'cloud.unit.tests.'},
|
||||
],
|
||||
'fallback': 'rr',
|
||||
},
|
||||
'rr': {
|
||||
'values': [
|
||||
{'value': 'one.unit.tests.'},
|
||||
{'value': 'two.unit.tests.'},
|
||||
{'value': 'default.unit.tests.'},
|
||||
{'value': 'final.unit.tests.'},
|
||||
],
|
||||
},
|
||||
},
|
||||
'rules': [
|
||||
{'pool': 'cloud'},
|
||||
],
|
||||
}
|
||||
})
|
||||
profiles = provider._generate_traffic_managers(record)
|
||||
|
||||
self.assertEqual(len(profiles), 2)
|
||||
self.assertTrue(_profile_is_match(profiles[0], Profile(
|
||||
name='pool-rr--foo-unit-tests',
|
||||
traffic_routing_method='Weighted',
|
||||
dns_config=DnsConfig(
|
||||
relative_name='pool-rr--foo-unit-tests', ttl=60),
|
||||
monitor_config=_get_monitor(record),
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
name='rr--one.unit.tests',
|
||||
type=external,
|
||||
target='one.unit.tests',
|
||||
weight=1,
|
||||
),
|
||||
Endpoint(
|
||||
name='rr--two.unit.tests',
|
||||
type=external,
|
||||
target='two.unit.tests',
|
||||
weight=1,
|
||||
),
|
||||
Endpoint(
|
||||
name='rr--default.unit.tests--default--',
|
||||
type=external,
|
||||
target='default.unit.tests',
|
||||
weight=1,
|
||||
),
|
||||
Endpoint(
|
||||
name='rr--final.unit.tests',
|
||||
type=external,
|
||||
target='final.unit.tests',
|
||||
weight=1,
|
||||
),
|
||||
],
|
||||
)))
|
||||
self.assertTrue(_profile_is_match(profiles[1], Profile(
|
||||
name='foo-unit-tests',
|
||||
traffic_routing_method='Priority',
|
||||
dns_config=DnsConfig(
|
||||
relative_name='foo-unit-tests', ttl=60),
|
||||
monitor_config=_get_monitor(record),
|
||||
endpoints=[
|
||||
Endpoint(
|
||||
name='cloud',
|
||||
type=external,
|
||||
target='cloud.unit.tests',
|
||||
priority=1,
|
||||
),
|
||||
Endpoint(
|
||||
name='rr',
|
||||
type=nested,
|
||||
target_resource_id=tm_id('pool-rr--foo-unit-tests'),
|
||||
priority=2,
|
||||
),
|
||||
],
|
||||
)))
|
||||
|
||||
# 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()
|
||||
@ -1194,6 +1498,21 @@ class TestAzureDnsProvider(TestCase):
|
||||
)
|
||||
self.assertEqual(new_profile.endpoints[0].weight, 14)
|
||||
|
||||
@patch(
|
||||
'octodns.provider.azuredns.AzureProvider._generate_traffic_managers')
|
||||
def test_sync_traffic_managers_duplicate(self, mock_gen_tms):
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
tm_sync = provider._tm_client.profiles.create_or_update
|
||||
|
||||
# change and duplicate profiles
|
||||
profile = self._get_tm_profiles(provider)[0]
|
||||
profile.name = 'changing_this_to_trigger_sync'
|
||||
mock_gen_tms.return_value = [profile, profile]
|
||||
provider._sync_traffic_managers(record)
|
||||
|
||||
# it should only be called once for duplicate profiles
|
||||
tm_sync.assert_called_once()
|
||||
|
||||
def test_find_traffic_managers(self):
|
||||
provider, zone, record = self._get_dynamic_package()
|
||||
|
||||
|
Reference in New Issue
Block a user