def find_dns_owners(self, dns_owner_id, only_type=True): """Return information about entries using this dns_owner. If only_type=True, returns a list of owner_type. Otherwise returns a list of (owner_type, owner_id) tuples""" ret = [] arecord = ARecord.ARecord(self._db) for row in arecord.list_ext(dns_owner_id=dns_owner_id): ret.append((dns.A_RECORD, row['a_record_id'])) aaaarecord = AAAARecord.AAAARecord(self._db) for row in aaaarecord.list_ext(dns_owner_id=dns_owner_id): ret.append((dns.AAAA_RECORD, row['aaaa_record_id'])) hi = HostInfo.HostInfo(self._db) try: hi.find_by_dns_owner_id(dns_owner_id) ret.append((dns.HOST_INFO, hi.entity_id)) except Errors.NotFoundError: pass dns_owner = DnsOwner.DnsOwner(self._db) for row in dns_owner.list_srv_records(owner_id=dns_owner_id): ret.append((dns.SRV_OWNER, row['service_owner_id'])) for row in dns_owner.list_general_dns_records( dns_owner_id=dns_owner_id): ret.append((dns.GENERAL_DNS_RECORD, row['dns_owner_id'])) cn = CNameRecord.CNameRecord(self._db) for row in cn.list_ext(cname_owner=dns_owner_id): ret.append((dns.CNAME_OWNER, row['cname_id'])) if only_type: return [x[0] for x in ret] return ret
def _get_zone_data(self, zone): # ARecord has key=a_record_id # HostInfo, SrvRecord has key=dns_owner_id # CnameRecords key=target_owner_id # entity2txt, entity2note has_key=entity_id for row in ARecord.ARecord(db).list_ext(zone=zone): id = int(row['dns_owner_id']) if self.a_records_by_dns_owner.has_key(id): self.a_records_by_dns_owner[id] += [row] else: self.a_records_by_dns_owner[id] = [row] # Following dict is populated to support HostFile self.a_records[int(row['a_record_id'])] = row logger.debug("... arecords") for row in AAAARecord.AAAARecord(db).list_ext(zone=zone): id = int(row['dns_owner_id']) if self.aaaa_records_by_dns_owner.has_key(id): self.aaaa_records_by_dns_owner[id] += [row] else: self.aaaa_records_by_dns_owner[id] = [row] # Following dict is populated to support HostFile self.aaaa_records[int(row['aaaa_record_id'])] = row logger.debug("... aaaarecords") for row in HostInfo.HostInfo(db).list(zone=zone): # Unique constraint on dns_owner_id self.hosts[int(row['dns_owner_id'])] = row logger.debug("... hosts") for row in CNameRecord.CNameRecord(db).list_ext(zone=zone): # TBD: skal vi ha unique constraint på dns_owner? self.cnames.setdefault(int(row['target_owner_id']), []).append(row) logger.debug("... cnames") # From mix-in classes for row in DnsOwner.MXSet(db).list_mx_sets(): self.mx_sets.setdefault(int(row['mx_set_id']), []).append(row) logger.debug("... mx_sets") for row in DnsOwner.DnsOwner(db).list(zone=zone): self.owner_id2mx_set[int(row['dns_owner_id'])] = int( row['mx_set_id'] or 0) logger.debug("... mx_set owners") for row in DnsOwner.DnsOwner(db).list_general_dns_records( field_type=co.field_type_txt, zone=zone): self.dnsowner2txt_record[int(row['dns_owner_id'])] = row logger.debug("... txt reocrds") for row in DnsOwner.DnsOwner(db).list_srv_records(zone=zone): # We want them listed in the same place # TODO: while doing that, we want it below the first target_owner_id self.srv_records.setdefault(int(row['service_owner_id']), []).append(row) logger.debug("... srv records")
def remove_arecord(self, a_record_id, try_dns_remove=False): """Remove an a-record identified by a_record_id. Will also update override_revmap and remove the entry in ip_number if it is no longer referred to by other tables. :param int a_record_id: The ARecords id. :param bool try_dns_remove: Remove the DNSOwner too. """ ip_type = dns.IP_NUMBER record_type = dns.A_RECORD arecord = ARecord.ARecord(self._db) try: arecord.find(a_record_id) ip_number_id = arecord.ip_number_id ipnumber = IPNumber.IPNumber(self._db) except Errors.NotFoundError: arecord = AAAARecord.AAAARecord(self._db) arecord.find(a_record_id) ip_type = dns.IPv6_NUMBER record_type = dns.AAAA_RECORD ip_number_id = arecord.ipv6_number_id ipnumber = IPv6Number.IPv6Number(self._db) ipnumber.find(ip_number_id) dns_owner_id = arecord.dns_owner_id arecord._delete() refs = self._find.find_referers(ip_number_id=ipnumber.entity_id, ip_type=ip_type) if dns.REV_IP_NUMBER in refs: self._update_override(ipnumber.entity_id, dns_owner_id) refs = self._find.find_referers(ip_number_id=ipnumber.entity_id, ip_type=ip_type) if not (dns.REV_IP_NUMBER in refs or record_type in refs): # IP no longer used ipnumber.delete() # Assert that any cname/srv targets still point to atleast one # a-record. Assert that host_info has atleast one associated # a_record. # TODO: This check should be somewhere that makes it is easier # to always enforce this constraint. refs = set(self._find.find_referers(dns_owner_id=dns_owner_id, ip_type=dns.IP_NUMBER)) refs.update(self._find.find_referers(dns_owner_id=dns_owner_id, ip_type=dns.IPv6_NUMBER)) if not any(r_type in refs for r_type in (dns.A_RECORD, dns.AAAA_RECORD)): if dns.SRV_TARGET in refs or dns.CNAME_TARGET in refs: raise DNSError("Host is used as target for CNAME or SRV") if try_dns_remove: self.remove_dns_owner(dns_owner_id)
def __init__(self, *args, **kwargs): super(TSDUtils, self).__init__(*args, **kwargs) self.ou = Factory.get('OU')(self.db) self.et = EntityTrait.EntityTrait(self.db) self.dnsowner = DnsOwner.DnsOwner(self.db) self.subnet = Subnet.Subnet(self.db) self.subnet6 = IPv6Subnet.IPv6Subnet(self.db) self.ar = ARecord.ARecord(self.db) self.aaaar = AAAARecord.AAAARecord(self.db)
def __init__(self, *args, **kwargs): """Instantiate dns specific functionality.""" super(HostSync, self).__init__(*args, **kwargs) self.host = HostInfo.HostInfo(self.db) self.subnet = Subnet.Subnet(self.db) self.subnet6 = IPv6Subnet.IPv6Subnet(self.db) self.ar = ARecord.ARecord(self.db) self.aaaar = AAAARecord.AAAARecord(self.db)
def __init__(self, db, default_zone): self._db = db self._ip_number = IPNumber.IPNumber(db) self._ipv6_number = IPv6Number.IPv6Number(db) self._arecord = ARecord.ARecord(db) self._aaaarecord = AAAARecord.AAAARecord(db) self._dns_owner = DnsOwner.DnsOwner(db) self._mx_set = DnsOwner.MXSet(db) self._host = HostInfo.HostInfo(db) self._cname = CNameRecord.CNameRecord(db) self._dns_parser = DnsParser(db, default_zone)
def set_ttl(self, owner_id, ttl): """Set TTL entries for this dns_owner""" # TODO: Currently we do this by updating the TTL in all # tables. It has been decided to move ttl-information into # dns_owner. However, we will not do this until after we have # gone into production to avoid a huge diff when comparing # autogenerated zone files to the original ones. dns_owner = DnsOwner.DnsOwner(self.db) dns_owner.find(owner_id) arecord = ARecord.ARecord(self.db) for row in arecord.list_ext(dns_owner_id=owner_id): arecord.clear() arecord.find(row['a_record_id']) arecord.ttl = ttl arecord.write_db() aaaarecord = AAAARecord.AAAARecord(self.db) for row in aaaarecord.list_ext(dns_owner_id=owner_id): aaaarecord.clear() aaaarecord.find(row['aaaa_record_id']) aaaarecord.ttl = ttl aaaarecord.write_db() host = HostInfo.HostInfo(self.db) try: host.find_by_dns_owner_id(owner_id) except Errors.NotFoundError: pass else: host.ttl = ttl host.write_db() for row in dns_owner.list_general_dns_records(dns_owner_id=owner_id): dns_owner.update_general_dns_record(owner_id, row['field_type'], ttl, row['data']) mx_set = DnsOwner.MXSet(self.db) for row in mx_set.list_mx_sets(target_id=owner_id): mx_set.clear() mx_set.find(row['mx_set_id']) mx_set.update_mx_set_member(ttl, row['pri'], row['target_id']) cname = CNameRecord.CNameRecord(self.db) for row in cname.list_ext(cname_owner=owner_id): cname.clear() cname.find(row['cname_id']) cname.ttl = ttl cname.write_db() for row in dns_owner.list_srv_records(owner_id=owner_id): dns_owner.update_srv_record_ttl(owner_id, ttl)
def find_a_record(self, host_name, ip=None): owner_id = self.find_target_by_parsing(host_name, dns.DNS_OWNER) # Check for IPv6 / IPv4 if ip and ip.count(':') > 1: ar = AAAARecord.AAAARecord(self._db) ip_type = dns.IPv6_NUMBER rt = 'AAAA-record' elif host_name.count(':') > 1: # No IP specified. # See if host_name is an IPv6 addr and select an IPv6-type if it is ar = AAAARecord.AAAARecord(self._db) ip_type = dns.IPv6_NUMBER rt = 'AAAA-record' else: ar = ARecord.ARecord(self._db) ip_type = dns.IP_NUMBER rt = 'A-record' if ip: a_ip = ip ip = self.find_target_by_parsing( ip, ip_type) try: ar.find_by_owner_and_ip(ip, owner_id) except Errors.NotFoundError: raise CerebrumError( "No %s with name=%s and ip=%s" % (rt, host_name, a_ip)) else: try: ar.find_by_dns_owner_id(owner_id) except Errors.NotFoundError: raise CerebrumError("No %s with name=%s" % (rt, host_name)) except Errors.TooManyRowsError: raise CerebrumError("Multiple %s with name=%s" % (rt, host_name)) return ar.entity_id
def _populate_dnsowner(self, hostname): """Create or update a DnsOwner connected to the given project. The DnsOwner is given a trait, to affiliate it with this project-OU. This should rather be put in the DNS module, but due to its complexity, its weird layout, and my lack of IQ points to understand it, I started just using its API instead. :param str hostname: The given *FQDN* for the host. :rtype: DnsOwner object :return: The DnsOwner object that is created or updated. """ dns_owner = DnsOwner.DnsOwner(self._db) dnsfind = Utils.Find(self._db, cereconf.DNS_DEFAULT_ZONE) ipv6number = IPv6Number.IPv6Number(self._db) aaaarecord = AAAARecord.AAAARecord(self._db) ipnumber = IPNumber.IPNumber(self._db) arecord = ARecord.ARecord(self._db) try: dns_owner.find_by_name(hostname) except Errors.NotFoundError: # TODO: create owner here? dns_owner.populate(self.const.DnsZone(cereconf.DNS_DEFAULT_ZONE), hostname) dns_owner.write_db() # Affiliate with project: dns_owner.populate_trait(self.const.trait_project_host, target_id=self.entity_id) dns_owner.write_db() for (subnets, ipnum, record, ipstr) in ( (self.ipv6_subnets, ipv6number, aaaarecord, "IPv6"), (self.ipv4_subnets, ipnumber, arecord, "IPv4")): # TODO: check if dnsowner already has an ip address. try: ip = dnsfind.find_free_ip(subnets.next(), no_of_addrs=1)[0] except StopIteration: raise Errors.NotFoundError("No %s-subnet for project %s" % (ipstr, self.get_project_id())) ipnum.populate(ip) ipnum.write_db() record.populate(dns_owner.entity_id, ipnum.entity_id) record.write_db() return dns_owner
def _get_reverse_data(self): for row in IPv6Number.IPv6Number(db).list(start=self.start, stop=self.stop): self.ip_numbers[int(row['ipv6_number_id'])] = row for row in AAAARecord.AAAARecord(db).list_ext(start=self.start, stop=self.stop): self.a_records.setdefault(int(row['ipv6_number_id']), []).append(row) for row in IPv6Number.IPv6Number(db).list_override(start=self.start, stop=self.stop): self.override_ip.setdefault(int(row['ipv6_number_id']), []).append(row) logger.debug( "_get_reverse_ipv6_data -> %i, %i, %i" % (len(self.ip_numbers), len(self.a_records), len(self.override_ip)))
def __init__(self, operator_id): """Constructor. Since we are using access control, we need the authenticated entity's ID as a parameter. """ self.db = Factory.get('Database')() self.db.cl_init(change_program='resource_service') self.co = Factory.get('Constants')(self.db) self.finder = Utils.Find(self.db, self.default_zone) self.subnet = Subnet.Subnet(self.db) self.aaaa = AAAARecord.AAAARecord(self.db) self.ip = IPv6Number.IPv6Number(self.db) # TODO: could we save work by only using a single, shared object of # the auth class? It is supposed to be thread safe. #self.ba = BofhdAuth(self.db) self.operator_id = operator_id
def full_remove_dns_owner(self, dns_owner_id): # fjerner alle entries der dns_owner vil være til venstre i # sonefila. self.remove_host_info(dns_owner_id) arecord = ARecord.ARecord(self._db) for row in arecord.list_ext(dns_owner_id=dns_owner_id): self.remove_arecord(row['a_record_id']) aaaarecord = AAAARecord.AAAARecord(self._db) for row in aaaarecord.list_ext(dns_owner_id=dns_owner_id): self.remove_arecord(row['aaaa_record_id']) self.remove_cname(dns_owner_id) dns_owner = DnsOwner.DnsOwner(self._db) for row in dns_owner.list_general_dns_records( dns_owner_id=dns_owner_id): dns_owner.delete_general_dns_record(dns_owner_id, row['field_type']) self.remove_dns_owner(dns_owner_id)
def __init__(self, db, logger, default_zone): self.logger = logger self.db = db self.const = Factory.get('Constants')(self.db) # TBD: This pre-allocating may interfere with multi-threaded bofhd self._arecord = ARecord.ARecord(self.db) self._aaaarecord = AAAARecord.AAAARecord(self.db) self._host = HostInfo.HostInfo(self.db) self._dns_owner = DnsOwner.DnsOwner(self.db) self._ip_number = IPNumber.IPNumber(self.db) self._ipv6_number = IPv6Number.IPv6Number(self.db) self._cname = CNameRecord.CNameRecord(self.db) self._validator = IntegrityHelper.Validator(self.db, default_zone) self._update_helper = IntegrityHelper.Updater(self.db) self._mx_set = DnsOwner.MXSet(self.db) self.default_zone = default_zone self._find = Utils.Find(self.db, default_zone) self._parser = Utils.DnsParser(self.db, default_zone)
def _update_override(self, ip_number_id, dns_owner_id): """Handles the updating of the override_reversemap when an ARecord is removed.""" # Select correct IP-variant try: ipnumber = IPNumber.IPNumber(self._db) ipnumber.clear() ipnumber.find(ip_number_id) ar = ARecord.ARecord(self._db) except Errors.NotFoundError: ipnumber = IPv6Number.IPv6Number(self._db) ar = AAAARecord.AAAARecord(self._db) owners = [] for row in ipnumber.list_override(ip_number_id=ip_number_id): if dns_owner_id == row['dns_owner_id']: # Always remove the reverse which corresponds to the # ARecord which is being removed. ipnumber.delete_reverse_override(ip_number_id, dns_owner_id) elif row['dns_owner_id'] == None: # We know that this IP has been associated with an # ARecord. If PTR generation has been surpressed by # setting owner to NULL, we want to remove the reverse # to avoid surprises when the IP is reused. ipnumber.delete_reverse_override(ip_number_id, None) else: owners.append(row['dns_owner_id']) if len(owners) != 1: return # The single entry left is redundant if there is only one # ARecord referring to the IP. rows = ar.list_ext(ip_number_id=ip_number_id) if len(rows) == 1 and rows[0]['dns_owner_id'] == owners[0]: ipnumber.delete_reverse_override(ip_number_id, owners[0])
def get_ttl(self, owner_id): """Retrieve TTL ('Time to Live') setting for the records associated with gievn DNS-owner. """ # Caveat: if TTL is set for one of the host's A*-records, it is # set for the host in general. If no A*-record exists, we don't # acknowledge any other TTL than "default" dns_owner = DnsOwner.DnsOwner(self.db) dns_owner.find(owner_id) # This adaption to A- and AAAA-records is very ugly, but it honours # "The Old Way" of getting the TTL for a host. ar = ARecord.ARecord(self.db) ar.clear() for r in ar.list_ext(dns_owner_id=owner_id): ar.find(r['a_record_id']) return ar.ttl ar = AAAARecord.AAAARecord(self.db) ar.clear() for r in ar.list_ext(dns_owner_id=owner_id): ar.find(r['aaaa_record_id']) return ar.ttl return None
def process_dns(self): """Sync all DNS data with the gateway. In order, this function will: 1. Look up Cerebrum subnets and VLANs 2. Look up, compare and update VLANs in gateway 3. Look up, compare and update subnets in gateway 4. Look up Cerebrum hosts and IPs 5. Look up, compare and update hosts in gateway 6. Look up, compare and update IPs in gateway """ logger.debug("Processing DNS") # Map subnets to projects: sub2ouid = dict( (row['entity_id'], row['target_id']) for row in self.ent.list_traits(code=self.co.trait_project_subnet) if row['target_id'] in self.ouid2pid) sub2ouid.update( dict((row['entity_id'], row['target_id']) for row in self.ent.list_traits( code=self.co.trait_project_subnet6))) logger.debug("Mapped %d subnets to OUs", len(sub2ouid)) sub2pid = dict((k, self.ouid2pid[v]) for k, v in sub2ouid.iteritems() if v in self.ouid2pid) logger.debug("Mapped %d subnets to projects", len(sub2pid)) # Process subnets and VLANs: subnets, vlans = self._get_subnets_and_vlans(sub2pid) self._process_vlans(self.gw.list_vlans(), vlans) self._process_subnets(self.gw.list_subnets(), subnets, sub2ouid) # Mapping hosts to projects by what subnet they're on: hostid2pid = dict( (r['entity_id'], self.ouid2pid.get(r['target_id'])) for r in self.ent.list_traits(code=self.co.trait_project_host) if r['target_id'] in self.ouid2pid) host2project = dict() host2ips = dict() def _collect(record, ip_attr): if record['dns_owner_id'] not in hostid2pid: # Host is not connected to a project, and is therefore ignored. logger.debug2("Host not connected to project: %s", record['name']) return hostname = record['name'].rstrip('.') host2project[hostname] = hostid2pid[record['dns_owner_id']] host2ips.setdefault(hostname, set()).add(record[ip_attr]) for row in AAAARecord.AAAARecord(self.db).list_ext(): _collect(row, 'aaaa_ip') for row in ARecord.ARecord(self.db).list_ext(): _collect(row, 'a_ip') logger.debug2("Mapped %d hosts to projects", len(host2project)) logger.debug2("Mapped %d hosts with at least one IP address", len(host2ips)) # Process hosts and ips: self._process_hosts(self.gw.list_hosts(), host2project) self._process_ips(self.gw.list_ips(), host2project, host2ips)