From c0a03c32b56a69f1183988d8e3e03a135fa7c437 Mon Sep 17 00:00:00 2001
From: Anders Blomdell <anders.blomdell@control.lth.se>
Date: Wed, 13 Nov 2013 12:33:59 +0100
Subject: [PATCH] Version 2013-11-13 12:33

M  src/hostinfo.py
M  src/hostinfo/dhcpd.py
M  src/hostinfo/named.py
M  src/hostinfo/parser.py
M  src/hostinfo/role.py
---
 src/hostinfo.py        | 146 ++++++++++++++++++++++-------------------
 src/hostinfo/dhcpd.py  |   7 ++
 src/hostinfo/named.py  |   5 +-
 src/hostinfo/parser.py |   6 +-
 src/hostinfo/role.py   |  31 +++++----
 5 files changed, 111 insertions(+), 84 deletions(-)

diff --git a/src/hostinfo.py b/src/hostinfo.py
index 772f031..122710e 100755
--- a/src/hostinfo.py
+++ b/src/hostinfo.py
@@ -12,6 +12,7 @@ import hostinfo.pxelinux
 import hostinfo.samba
 import hostinfo.yp
 import hostinfo.role
+import hostinfo.util
 import argparse
 import os
 import sys
@@ -78,6 +79,7 @@ attr_weight = {
     ('interface', 'ip') : 1,
     ('interface', 'ether') : 2,
     ('subnet', 'network') : 1,
+    ('subnet', 'prefix') : 1,
     ('subnet', 'domain') : 2,
     ('subnet', 'broadcast') : 3,
     ('subnet', 'netmask') : 4,
@@ -85,79 +87,81 @@ attr_weight = {
     ('subnet', 'name_servers') : 6,
 }
 
-#def cmp(a,b):
-#    if a < b:
-#        return -1
-#    elif a == b:
-#        return 0
-#    else:
-#        return 1
-
 class AttributeDict(dict): 
     __getattr__ = dict.__getitem__
 
+def network_order(a, b):
+    a_net = hostinfo.util.network(a)
+    b_net = hostinfo.util.network(b)
+    if not a_net or not b_net:
+        return cmp(a,b)
+    if a_net.version == b_net.version:
+        return cmp(a_net, b_net)
+    return cmp(a_net.version, b_net.version)
+
 def host_order(a,b):
-    def at_top(n):
-        # Place hosts without ethernet or ip address at top
-        for i1 in n._interface_:
-            if i1.ethernet[0]:
-                return False
-            for i2 in i1._ip_:
-                if i2.address[0]:
-                    return False
-                pass
-            pass
-        return True
-    def at_bottom(n):
-        # Place hosts with ethernet but without ip address at bottom
-        result = False
-        for i1 in n._interface_:
-            if i1.ethernet[0]:
-                result = True
-            for i2 in i1._ip_:
-                if i2.address[0]:
-                    return False
-                pass
-            pass
-        return result
-    if (at_top(a) == at_top(b) and at_bottom(a) == at_bottom(b)):
-        return cmp(a.name[0], b.name[0])
-    elif at_top(a):
-        return -1
-    elif at_top(b):
-        return 1
-    elif at_bottom(a):
-        return 1
-    elif at_bottom(b):
-        return -1
-    else:
-        raise Exception('Should never happen')
+    def fqn(host):
+        n = host.name[0]
+        return n and n.endswith('.')
+    def select(f, c):
+        return [ f(e) for e in c if f(e) ]
+    def ether(host):
+        return select(lambda i: i.ethernet[0], host._interface_)
+    def ip(host):
+        ipv4 = select(hostinfo.util.address, host._interface_._ip_)
+        ipv6 = select(hostinfo.util.address, host._interface_._ipv6_)
+        return ipv4 + ipv6
+    def no_addr(host):
+        return not ether(host) and not ip(host)
+    def all_addr(host):
+        return ether(host) and ip(host)
+
+    # Fully qualified names
+    if fqn(a) or fqn(b): 
+        if fqn(a) and fqn(b): return cmp(a.name[0], b.name[0])
+        if fqn(a): return -1
+        if fqn(b): return 1
+        pass
+    
+    #  Hosts without ether an ip address (reusable names)
+    if no_addr(a) or no_addr(b):
+        if no_addr(a) and no_addr(b): return cmp(a.name[0], b.name[0])
+        if no_addr(a): return -1
+        if no_addr(b): return 1
+        pass
+
+    # Hosts with ip and ether (active machines)
+    if all_addr(a) or all_addr(b):
+        if all_addr(a) and all_addr(b): return cmp(a.name[0], b.name[0])
+        if all_addr(a): return -1
+        if all_addr(b): return 1
+        pass
+    # This should be machines with ether but no ip (decommissioned)
+    if ip(a): 
+        raise hostinfo.util.HostinfoException('Unexpected ip address %s' %
+                                              a.name[0], where=a)
+    if ip(b): 
+        raise hostinfo.util.HostinfoException('Unexpected ip address %s' %
+                                              b.name[0], where=b)
+    return cmp(a.name[0], b.name[0])
 
 tag_weight = {
-    ('hostinfo', 'soa')        : (1,
-                                  None),
-    ('hostinfo', 'subnet')      : (2,
-                                   lambda a,b: cmp(a.network[0],b.network[0])),
-    ('hostinfo', 'netgroup')    : (3,
+    ('hostinfo', 'soa')         : (1, None),
+    ('hostinfo', 'subnet')      : (2, network_order),
+#                                   lambda a,b: cmp(a.network[0],b.network[0])),
+    ('hostinfo', 'nameserver')  : (3, None),
+    ('hostinfo', 'netgroup')    : (4,
                                    lambda a,b: cmp(a.name[0],b.name[0])),
-    ('hostinfo', 'host')        : (4,
-                                   host_order),
-    ('host', 'automount')       : (1,
-                                   lambda a,b: cmp(a.host[0],b.host[0])),
-    ('host', 'mio')             : (2,
-                                   None),
-    ('interface', 'kickstart')  : (1,
-                                   None),
-    ('interface', 'ip')         : (2,
-                                   None),
-    ('interface', 'alias')      : (3,
-                                   None),
-    ('interface', 'dhcpserver') : (4,
-                                   None),
-    ('interface', 'nameserver') : (5,
-                                   None),
-    ('automount', 'entry')      : (1,
-                                   lambda a,b: cmp(a.key[0],b.key[0])),
+    ('hostinfo', 'host')        : (5, host_order),
+    ('host', 'role')            : (1, None),
+    ('host', 'automount')       : (2, lambda a,b: cmp(a.host[0],b.host[0])),
+    ('host', 'mio')             : (3, None),
+    ('interface', 'kickstart')  : (1, None),
+    ('interface', 'ip')         : (2, None),
+    ('interface', 'alias')      : (3, None),
+    ('interface', 'dhcpserver') : (4, None),
+    ('interface', 'nameserver') : (5, None),
+    ('automount', 'entry')      : (1, lambda a,b: cmp(a.key[0],b.key[0])),
 }
 
 def attr_sort(node, a, b):
@@ -261,6 +265,10 @@ if __name__ == '__main__':
                            default=[],
                            action="append",  metavar="ROLE", 
                            help="check if machine has ROLE")
+    optParser.add_argument("--role-holder",
+                           default=[],
+                           action="store",  metavar="ROLE", 
+                           help="check which machine currently holding ROLE")
     optParser.add_argument("--samba",
                            action="store",  metavar="FILE",
                            help="generate samba share FILE")
@@ -331,12 +339,18 @@ if __name__ == '__main__':
         result = "<?xml version='1.0' encoding='utf-8'?>\n\n"
         result += tree._xml(attr_sort=attr_sort, tag_sort=tag_sort)
         print result.encode("utf-8")
+        sys.exit(0)
 
     if options.role:
         roles = hostinfo.role.generate(tree, host, options.role)
         # print "Roles:",",".join(roles)
         sys.exit(len(roles) != len(options.role))
         
+    if options.role_holder:
+        who = hostinfo.role.holder(tree, options.role_holder)
+        print who
+        pass
+        
     if options.yp:
         for (f, c) in hostinfo.yp.generate(tree, options.yp_auto_domain):
             file["%s/%s" % (options.yp, f)] = c
diff --git a/src/hostinfo/dhcpd.py b/src/hostinfo/dhcpd.py
index 529e192..766c513 100755
--- a/src/hostinfo/dhcpd.py
+++ b/src/hostinfo/dhcpd.py
@@ -90,6 +90,10 @@ class "pxeclient" {
   } else {
     filename "pxelinux.0";
   }
+}
+class "non-pxeclient" {
+  match if substring (option vendor-class-identifier, 0, 9) != "PXEClient";
+  ignore booting;
 }"""
 
 PXE_DENY="""
@@ -154,6 +158,9 @@ def generate(tree, options):
       |ddns-update-style none;
       |get-lease-hostnames true;
       |use-host-decl-names on;
+      |# Windows proxy server/WPAD URL (Stops DHCPINFORM)
+      |option url-252 code 252 = text;
+      |option url-252 "\\n\\000";
     """)
     if get_pxeboot(dhcp) == 'only':
         if not options.next_server:
diff --git a/src/hostinfo/named.py b/src/hostinfo/named.py
index a17a33f..2a90372 100755
--- a/src/hostinfo/named.py
+++ b/src/hostinfo/named.py
@@ -25,10 +25,7 @@ def generate(tree, options):
     conf = util.StringArray()
     
     for h in tree._nameserver_:
-        for l in re.sub('([{;]\s)', '\g<1>\n', h.conf[0]).split('\n'):
-            if l[0] == ' ':
-                l = l[1:]
-                pass
+        for l in h._char:
             conf.append(l)
             pass
         pass
diff --git a/src/hostinfo/parser.py b/src/hostinfo/parser.py
index 4bd8330..3d6dc24 100755
--- a/src/hostinfo/parser.py
+++ b/src/hostinfo/parser.py
@@ -382,7 +382,11 @@ class Node:
             result += "%s/>\n" % line
         else:
             result += "%s>\n" % line
-            result += xml_escape("".join(self._char))
+            #result += xml_escape('\n'.join(self._char))
+            if self._char:
+                result += '\n'.join(self._char)
+                result += '\n'
+                pass
             child = self._children
             child.sort(TagSortWrapper(self, tag_sort))
             for c in child:
diff --git a/src/hostinfo/role.py b/src/hostinfo/role.py
index b70a512..ebcb336 100755
--- a/src/hostinfo/role.py
+++ b/src/hostinfo/role.py
@@ -1,19 +1,24 @@
+import hostinfo.util as util
+
 def generate(tree, host, roles):
-    result = []
-    by_role = {}
-    for r in tree._host_._role_:
-        role = r.name[0]
-        if host == r.name[1] and role in roles:
-            result.append(role)
-            pass
-        if role in by_role:
-            raise Exception("Duplicate roles '%s' on %s, %s" %
-                            (role, host, by_role[role]) )
-        by_role[role] = r.name[1]
-        pass
+    by_role = get_all(tree)
     for r in roles:
         if not r in by_role:
             raise Exception("Role '%s' not found in %s" %
                             (r, by_role.keys()))
-            
+    return filter(lambda (k,v): k in roles and v == host, 
+                  by_role.iteritems())
+
+def holder(tree, role):
+    return get_all(tree)[role]
+
+def get_all(tree):
+    result = {}
+    for r in tree._host_._role_:
+        name = r.name[0]
+        if name in result:
+            raise util.HostinfoException('Duplicate role holders %s' % name,
+                                         where=[result[name], r])
+        result[name] = r.name[1]
+        pass
     return result
-- 
GitLab