def _new_dyn_traffic_director_service(name, ttl=DEFAULT_TD_TTL, records=None, attach_nodes=None) -> TrafficDirector: """Creates an opinionated Dyn Traffic Director service - Creates a CNAME recordset - Expects all records to be DSFCNAMERecord type - Creates a single failover chain pointing to the recordset - Creates a single pool pointing to the failover chain - Creates a single ruleset that always responds and points to the pool NOTE: The dyn module TrafficDirector constructor has side effects and calls the Dyn API multiple times. It is possible that Dyn resources are created even if the constructor failed """ if records is None: records = [] if attach_nodes is None: attach_nodes = [] try: record_set = DSFRecordSet('CNAME', label=name, automation='manual', records=records) failover_chain = DSFFailoverChain(label=name, record_sets=[record_set]) rpool = DSFResponsePool(label=name, rs_chains=[failover_chain]) ruleset = DSFRuleset(label=name, criteria_type='always', response_pools=[rpool]) # Constructor does the actual resource creation and checking for # creation completion. # It returns returns a resource ID which we don't need return TrafficDirector(name, ttl=ttl, rulesets=[ruleset], nodes=attach_nodes) except Exception as e: raise CreateTrafficDirectorError( f'Exception caught during creation of Traffic Director: {name} ' f'The exception was: {e}')
def _mod_rulesets(self, td, change): new = change.new # Response Pools pools = {} # Get existing pools. This should be simple, but it's not b/c the dyn # api is a POS. We need all response pools so we can GC and check to # make sure that what we're after doesn't already exist. # td.all_response_pools just returns thin objects that don't include # their rs_chains (and children down to actual records.) We could just # foreach over those turning them into full DSFResponsePool objects # with get_response_pool, but that'd be N round-trips. We can avoid # those round trips in cases where the pools are in use in rules where # they're already full objects. # First up populate all the full pools we have under rules, the _ # prevents a td.refresh we don't need :-( seriously? existing_rulesets = td._rulesets for ruleset in existing_rulesets: for pool in ruleset.response_pools: pools[pool.response_pool_id] = pool # Now we need to find any pools that aren't referenced by rules for pool in td.all_response_pools: rpid = pool.response_pool_id if rpid not in pools: # we want this one, but it's thin, inflate it pools[rpid] = get_response_pool(rpid, td) # now that we have full objects for the complete set of existing pools, # a list will be more useful pools = pools.values() # Rulesets # add the default label = 'default:{}'.format(uuid4().hex) ruleset = DSFRuleset(label, 'always', []) ruleset.create(td, index=0) pool = self._find_or_create_pool(td, pools, 'default', new._type, new.values) # There's no way in the client lib to create a ruleset with an existing # pool (ref'd by id) so we have to do this round-a-bout. active_pools = {'default': pool.response_pool_id} ruleset.add_response_pool(pool.response_pool_id) monitor_id = self._traffic_director_monitor(new.fqdn).dsf_monitor_id # Geos ordered least to most specific so that parents will always be # created before their children (and thus can be referenced geos = sorted(new.geo.items(), key=lambda d: d[0]) for _, geo in geos: if geo.subdivision_code: criteria = {'province': geo.subdivision_code.lower()} elif geo.country_code: criteria = {'country': geo.country_code} else: criteria = {'region': self.REGION_CODES[geo.continent_code]} label = '{}:{}'.format(geo.code, uuid4().hex) ruleset = DSFRuleset(label, 'geoip', [], {'geoip': criteria}) # Something you have to call create others the constructor does it ruleset.create(td, index=0) first = geo.values[0] pool = self._find_or_create_pool(td, pools, first, new._type, geo.values, monitor_id) active_pools[geo.code] = pool.response_pool_id ruleset.add_response_pool(pool.response_pool_id) # look for parent rulesets we can add in the chain for code in geo.parents: try: pool_id = active_pools[code] # looking at client lib code, index > exists appends ruleset.add_response_pool(pool_id, index=999) except KeyError: pass # and always add default as the last pool_id = active_pools['default'] ruleset.add_response_pool(pool_id, index=999) # we're done with active_pools as a lookup, convert it in to a set of # the ids in use active_pools = set(active_pools.values()) # Clean up unused response_pools for pool in pools: if pool.response_pool_id in active_pools: continue pool.delete() # Clean out the old rulesets for ruleset in existing_rulesets: ruleset.delete()