@ -519,37 +519,71 @@ class _Route53GeoRecord(_Route53Record):
self . values )
_mod_keyer_action_order = {
' DELETE ' : 0 , # Delete things first
' CREATE ' : 1 , # Then Create things
' UPSERT ' : 2 , # Upsert things last
}
def _mod_keyer ( mod ) :
rrset = mod [ ' ResourceRecordSet ' ]
action_order = _mod_keyer_action_order [ mod [ ' Action ' ] ]
# We're sorting by 3 "columns", the action, the rrset type, and finally the
# name/id of the rrset. This ensures that Route53 won't see a RRSet that
# targets another that hasn't been seen yet. I.e. targets must come before
# things that target them. We sort on types of things rather than
# explicitly looking for targeting relationships since that's sufficent and
# easier to grok/do.
# Route53 requires that changes are ordered such that a target of an
# AliasTarget is created or upserted prior to the record that targets it.
# This is complicated by "UPSERT" appearing to be implemented as "DELETE"
# before all changes, followed by a "CREATE", internally in the AWS API.
# Because of this, we order changes as follows:
# - Delete any records that we wish to delete that are GEOS
# (because they are never targetted by anything)
# - Delete any records that we wish to delete that are SECONDARY
# (because they are no longer targetted by GEOS)
# - Delete any records that we wish to delete that are PRIMARY
# (because they are no longer targetted by SECONDARY)
# - Delete any records that we wish to delete that are VALUES
# (because they are no longer targetted by PRIMARY)
# - CREATE/UPSERT any records that are VALUES
# (because they don't depend on other records)
# - CREATE/UPSERT any records that are PRIMARY
# (because they always point to VALUES which now exist)
# - CREATE/UPSERT any records that are SECONDARY
# (because they now have PRIMARY records to target)
# - CREATE/UPSERT any records that are GEOS
# (because they now have all their PRIMARY pools to target)
# - :tada:
#
# In theory we could also do this based on actual target reference
# checking, but that's more complex. Since our rules have a known
# dependency order, we just rely on that.
# Get the unique ID from the name/id to get a consistent ordering.
if rrset . get ( ' GeoLocation ' , False ) :
unique_id = rrset [ ' SetIdentifier ' ]
else :
unique_id = rrset [ ' Name ' ]
# Prioritise within the action_priority, ensuring targets come first.
if rrset . get ( ' GeoLocation ' , False ) :
return ( action_order , 3 , rrset [ ' SetIdentifier ' ] )
# Geos reference pools, so they come last.
record_priority = 3
elif rrset . get ( ' AliasTarget ' , False ) :
# We use an alias
if rrset . get ( ' Failover ' , False ) == ' SECONDARY ' :
# We're a secondary we'll ref primaries
return ( action_order , 2 , rrset [ ' Name ' ] )
# We're a secondary, which reference the primary (failover, P1).
record_priority = 2
else :
# We're a primary we'll ref values
return ( action_order , 1 , rrset [ ' Name ' ] )
# We're just a plain value, these come first
return ( action_order , 0 , rrset [ ' Name ' ] )
# We're a primary, we reference values (P0).
record_priority = 1
else :
# We're just a plain value, has no dependencies so first.
record_priority = 0
if mod [ ' Action ' ] == ' DELETE ' :
# Delete things first, so we can never trounce our own additions
action_priority = 0
# Delete in the reverse order of priority, e.g. start with the deepest
# reference and work back to the values, rather than starting at the
# values (still ref'd).
record_priority = - record_priority
else :
# For CREATE and UPSERT, Route53 seems to treat them the same, so
# interleave these, keeping the reference order described above.
action_priority = 1
return ( action_priority , record_priority , unique_id )
def _parse_pool_name ( n ) :
@ -1250,15 +1284,27 @@ class Route53Provider(BaseProvider):
' %s ' , record . fqdn , record . _type )
fqdn = record . fqdn
_type = record . _type
# loop through all the r53 rrsets
for rrset in self . _load_records ( zone_id ) :
name = rrset [ ' Name ' ]
# Break off the first piece of the name, it'll let us figure out if
# this is an rrset we're interested in.
maybe_meta , rest = name . split ( ' . ' , 1 )
if not maybe_meta . startswith ( ' _octodns- ' ) or \
not maybe_meta . endswith ( ' -value ' ) or \
' -default- ' in name :
# We're only interested in non-default dynamic value records,
# as that's where healthchecks live
continue
if rest != fqdn or _type != rrset [ ' Type ' ] :
# rrset isn't for the current record
continue
if record . _type == rrset [ ' Type ' ] and name . endswith ( fqdn ) and \
name . startswith ( ' _octodns- ' ) and ' -value. ' in name and \
' -default- ' not in name and \
self . _extra_changes_update_needed ( record , rrset ) :
if self . _extra_changes_update_needed ( record , rrset ) :
# no good, doesn't have the right health check, needs an update
self . log . info ( ' _extra_changes_dynamic_needs_update: '
' health-check caused update of %s : %s ' ,