1
0
mirror of https://github.com/github/octodns.git synced 2024-05-11 05:55:00 +00:00

Merge branch 'octodns:master' into sham/issue-710

This commit is contained in:
Sham
2021-06-10 00:05:33 -07:00
10 changed files with 738 additions and 264 deletions

View File

@@ -269,7 +269,7 @@ def _get_monitor(record):
port=record.healthcheck_port,
path=record.healthcheck_path,
)
host = record.healthcheck_host
host = record.healthcheck_host()
if host:
monitor.custom_headers = [MonitorConfigCustomHeadersItem(
name='Host', value=host
@@ -281,12 +281,29 @@ def _profile_is_match(have, desired):
if have is None or desired is None:
return False
log = logging.getLogger('azuredns._profile_is_match').debug
def false(have, desired, name=None):
prefix = 'profile={}'.format(name) if name else ''
attr = have.__class__.__name__
log('%s have.%s = %s', prefix, attr, have)
log('%s desired.%s = %s', prefix, attr, desired)
return False
# compare basic attributes
if have.name != desired.name or \
have.traffic_routing_method != desired.traffic_routing_method or \
have.dns_config.ttl != desired.dns_config.ttl or \
len(have.endpoints) != len(desired.endpoints):
return False
return false(have, desired)
# compare dns config
dns_have = have.dns_config
dns_desired = desired.dns_config
if dns_have.ttl != dns_desired.ttl or \
dns_have.relative_name is None or \
dns_desired.relative_name is None or \
dns_have.relative_name != dns_desired.relative_name:
return false(dns_have, dns_desired, have.name)
# compare monitoring configuration
monitor_have = have.monitor_config
@@ -295,7 +312,7 @@ def _profile_is_match(have, desired):
monitor_have.port != monitor_desired.port or \
monitor_have.path != monitor_desired.path or \
monitor_have.custom_headers != monitor_desired.custom_headers:
return False
return false(monitor_have, monitor_desired, have.name)
# compare endpoints
method = have.traffic_routing_method
@@ -313,26 +330,26 @@ def _profile_is_match(have, desired):
for have_endpoint, desired_endpoint in endpoints:
if have_endpoint.name != desired_endpoint.name or \
have_endpoint.type != desired_endpoint.type:
return False
return false(have_endpoint, desired_endpoint, have.name)
target_type = have_endpoint.type.split('/')[-1]
if target_type == 'externalEndpoints':
# compare value, weight, priority
if have_endpoint.target != desired_endpoint.target:
return False
return false(have_endpoint, desired_endpoint, have.name)
if method == 'Weighted' and \
have_endpoint.weight != desired_endpoint.weight:
return False
return false(have_endpoint, desired_endpoint, have.name)
elif target_type == 'nestedEndpoints':
# compare targets
if have_endpoint.target_resource_id != \
desired_endpoint.target_resource_id:
return False
return false(have_endpoint, desired_endpoint, have.name)
# compare geos
if method == 'Geographic':
have_geos = sorted(have_endpoint.geo_mapping)
desired_geos = sorted(desired_endpoint.geo_mapping)
if have_geos != desired_geos:
return False
return false(have_endpoint, desired_endpoint, have.name)
else:
# unexpected, give up
return False
@@ -629,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
@@ -645,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 ' + \
@@ -673,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:
@@ -694,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)
@@ -792,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)
@@ -811,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(
@@ -852,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,
@@ -863,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