tero@440: import collections tero@440: import ipaddr tero@440: import logging; log = logging.getLogger('pvl.hosts.host') tero@440: import pvl.dns.zone tero@440: tero@440: class HostError (Exception) : tero@440: def __init__(self, name, error): tero@440: self.name = name tero@440: self.error = error tero@440: tero@440: def __str__ (self): tero@440: return "{self.name}: {self.error}".format(self=self) tero@440: tero@440: def parse_ethernet (value) : tero@440: """ tero@440: Normalize ethernet str. tero@440: """ tero@440: tero@440: return ':'.join('%02x' % int(x, 16) for x in value.split(':')) tero@440: tero@440: class Host (object) : tero@440: """ tero@440: A host is a network node that can have multiple ethernet interfaces, and multiple IP addresses in different domains. tero@440: """ tero@440: tero@440: # the label used for alias4/6 hosts tero@440: ALIAS4_FMT = '{host}-ipv4' tero@440: ALIAS6_FMT = '{host}-ipv6' tero@440: tero@440: @classmethod tero@440: def build (cls, name, domain, tero@440: ip=None, ip6=None, owner=None, location=None, tero@440: alias=None, alias4=None, alias6=None, tero@440: forward=None, reverse=None, tero@440: down=None, tero@440: boot=None, tero@440: **extra) : tero@440: """ tero@440: Return a Host initialized from data attributes. tero@440: tero@440: This handles all string parsing to our data types. tero@440: """ tero@440: tero@440: if alias : tero@440: alias = alias.split() tero@440: else : tero@440: alias = () tero@440: tero@440: if alias4 : tero@440: alias4 = alias4.split() tero@440: else : tero@440: alias4 = () tero@440: tero@440: if alias6 : tero@440: alias6 = alias6.split() tero@440: else : tero@440: alias6 = () tero@440: tero@440: ethernet = { } tero@440: extensions = collections.defaultdict(dict) tero@440: tero@440: for field, value in extra.iteritems() : tero@440: if ':' in field : tero@440: extension, field = field.split(':', 1) tero@440: tero@440: extensions[extension][field] = value tero@440: tero@440: continue tero@440: tero@440: if '.' in field : tero@440: field, instance = field.split('.') tero@440: else : tero@440: instance = None tero@440: tero@440: if field == 'ethernet' : tero@440: if instance : tero@440: ethernet[instance] = parse_ethernet(value) tero@440: else : tero@440: for eth in value.split() : tero@440: ethernet[len(ethernet)] = parse_ethernet(eth) tero@440: else : tero@440: raise HostError(name, "Unknown host field: %s=%s" % (field, value)) tero@440: tero@440: tero@440: if forward is None : tero@440: # normal zone tero@440: pass tero@440: elif forward : tero@440: # alias to external zone tero@440: pass tero@440: else : tero@440: # omit tero@440: forward = False tero@440: tero@440: if reverse is None : tero@440: # normal zone tero@440: pass tero@440: elif reverse : tero@440: # alias to external zone tero@440: pass tero@440: else : tero@440: # omit tero@440: reverse = False tero@440: tero@440: if down : tero@440: down = True tero@440: else : tero@440: down = None tero@440: tero@440: if not location : tero@440: location = location_domain = None tero@440: elif '@' in location: tero@440: location, location_domain = location.split('@', 1) tero@440: else: tero@440: location_domain = None tero@440: tero@440: return cls(name, tero@440: domain = domain, tero@440: ip = ipaddr.IPv4Address(ip) if ip else None, tero@440: ip6 = ipaddr.IPv6Address(ip6) if ip6 else None, tero@440: ethernet = ethernet, tero@440: alias = alias, tero@440: alias4 = alias4, tero@440: alias6 = alias6, tero@440: owner = owner, tero@440: location = location, tero@440: location_domain = location_domain, tero@440: boot = boot, tero@440: forward = forward, tero@440: reverse = reverse, tero@440: down = down, tero@440: tero@440: **extensions tero@440: ) tero@440: tero@446: def __init__ (self, name, domain, tero@440: ip=None, ip6=None, tero@440: ethernet={ }, tero@440: alias=(), tero@440: owner=None, tero@440: location=None, tero@440: location_domain=None, tero@440: boot=None, tero@440: alias4=None, alias6=None, tero@440: forward=None, reverse=None, tero@440: down=None, tero@440: **extensions tero@440: ) : tero@440: """ tero@446: name - str tero@440: domain - str tero@440: ip - ipaddr.IPv4Address tero@440: ip6 - ipaddr.IPv6Address tero@440: ethernet - { index: ethernet } tero@440: alias - list tero@440: owner - str: LDAP uid tero@440: location - location name@ part tero@440: location_domain - location @domain part tero@440: alias4 - list (CNAME -> A) tero@440: alias6 - list (CNAME -> AAAA) tero@440: forward - generate forward records, or CNAME into given zone tero@440: reverse - generate reverse records, or CNAME into given zone tero@440: down - not online tero@440: """ tero@440: tero@446: self.name = name tero@440: self.domain = domain tero@440: self.ip = ip tero@440: self.ip6 = ip6 tero@440: self.ethernet = ethernet tero@440: self.alias = alias tero@440: self.alias4 = alias4 tero@440: self.alias6 = alias6 tero@440: self.owner = owner tero@440: self.location = location tero@440: self.location_domain = location_domain tero@440: self.boot = boot tero@440: self.forward = forward tero@440: self.reverse = reverse tero@440: self.down = down tero@440: tero@440: self.extensions = extensions tero@440: tero@440: def sort_key (self): tero@440: """ tero@440: Stable sort ordering tero@440: """ tero@440: tero@440: if self.ip : tero@441: return self.ip tero@440: else : tero@440: # sorts first tero@440: return ipaddr.IPAddress(0) tero@440: tero@440: def fqdn (self) : tero@446: if '.' in self.name: tero@446: return self.name + '.' tero@440: elif self.domain : tero@446: return pvl.dns.zone.fqdn(self.name, self.domain) tero@440: else : tero@440: raise ValueError("%s: have no fqdn/domain" % (self, )) tero@440: tero@440: def __str__ (self) : tero@446: return "{self.name}@{self.domain}".format(self=self)