From b6dd7535a4fcd0fb0959558f842bdde0ec8ab1c4 Mon Sep 17 00:00:00 2001
From: Anders Blomdell <anders.blomdell@control.lth.se>
Date: Tue, 22 Oct 2013 15:39:07 +0200
Subject: [PATCH] Version 2013-10-22 15:39
M src/hostinfo.py
M src/hostinfo/dhcpd.py
M src/hostinfo/dhcpd_ipv6.py
M src/hostinfo/ifconfig.py
M src/hostinfo/util.py
---
src/hostinfo.py | 13 +-
src/hostinfo/dhcpd.py | 489 ++++++++++++++++++++-----------------
src/hostinfo/dhcpd_ipv6.py | 218 +++++++++++------
src/hostinfo/ifconfig.py | 161 ++++++------
src/hostinfo/util.py | 58 +++++
5 files changed, 547 insertions(+), 392 deletions(-)
diff --git a/src/hostinfo.py b/src/hostinfo.py
index c18ecbe..4d3b341 100755
--- a/src/hostinfo.py
+++ b/src/hostinfo.py
@@ -233,9 +233,9 @@ if __name__ == '__main__':
optParser.add_argument("--ifconfig",
action="store", metavar="DIR",
help="Generate DIR/ifcfg-eth*")
- optParser.add_argument("--kickstart",
- action="store", metavar="DIR",
- help="kickstart files should be fetched from DIR")
+ optParser.add_argument("--kickstart",
+ action="store", metavar="PREFIX",
+ help="kickstart file PREFIX")
optParser.add_argument("--macosx_auto",
action="store", metavar="DIR",
help="Generate MacOSX autmount maps")
@@ -288,13 +288,8 @@ if __name__ == '__main__':
file["%s/%s" % (options.automount, f)] = c
if options.dhcpd:
- if options.kickstart:
- kickstart = os.path.abspath(options.kickstart)
- else:
- kickstart = None
- pass
file["%s/dhcpd.conf" % options.dhcpd] = hostinfo.dhcpd.generate(
- tree, host, kickstart, next_server=options.next_server)
+ tree, options)
pass
if options.dhcpd6:
diff --git a/src/hostinfo/dhcpd.py b/src/hostinfo/dhcpd.py
index e1f69ef..9654a47 100755
--- a/src/hostinfo/dhcpd.py
+++ b/src/hostinfo/dhcpd.py
@@ -3,19 +3,120 @@ import os
from hostinfo.util import ntoa, aton
import hostinfo.util as util
-CLASS_PXECLIENT = """
- class "pxeclient" {
- match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
- next-server %(next_server)s;
+"""
+ddns-update-style none;
+authoritative;
+
+option space PXE;
+option PXE.mtftp-ip code 1 = ip-address;
+option PXE.mtftp-cport code 2 = unsigned integer 16;
+option PXE.mtftp-sport code 3 = unsigned integer 16;
+option PXE.mtftp-tmout code 4 = unsigned integer 8;
+option PXE.mtftp-delay code 5 = unsigned integer 8;
+option arch code 93 = unsigned integer 16; # RFC4578
+
+shared-network "MAC(02:64:01:00:00:02)" {
+
+ subnet 192.168.73.0 netmask 255.255.255.0 {
+ option subnet-mask 255.255.255.0;
+ option broadcast-address 192.168.73.255;
+ option routers 192.168.73.1;
+ option domain-name "i.control.lth.se";
+ option domain-name-servers 192.168.73.1;
+ default-lease-time 14400; # 4 hours
+ max-lease-time 86400; # 1 day
+ get-lease-hostnames true;
+ use-host-decl-names on;
+
+ class "pxeclient" {
+ match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
+ next-server dist-01.i.control.lth.se;
if option arch = 00:06 {
- filename "pxelinux/bootia32.efi";
+ filename "bootia32.efi";
} else if option arch = 00:07 {
- filename "pxelinux/bootx64.efi";
+ filename "bootx64.efi";
} else {
filename "pxelinux.0";
}
}
+
+ class "anaconda" {
+ match if substring (option vendor-class-identifier, 0, 14) = "anaconda-Linux";
+ match hardware;
+ next-server dist-01.i.control.lth.se;
+ }
+
+ pool {
+ allow members of "pxeclient";
+ #allow members of "anaconda";
+ range 192.168.73.100 192.168.73.119;
+ }
+
+ pool {
+ allow members of "anaconda";
+ range 192.168.73.200 192.168.73.219;
+ }
+
+ host test.control.lth.se {
+ hardware ethernet 02:13:10:10:18:51;
+ option host-name "test";
+ #filename "/bootserver/kickstart/Fedora-19-qemu-dist";
+ }
+
+ subclass "anaconda" 1:02:13:10:10:18:51 {
+ # filename "/bootserver/kickstart/Fedora-19-qemu-dist";
+ }
+ }
+
+}
+"""
+
+PXE_ALLOW="""
+option space PXE;
+option PXE.mtftp-ip code 1 = ip-address;
+option PXE.mtftp-cport code 2 = unsigned integer 16;
+option PXE.mtftp-sport code 3 = unsigned integer 16;
+option PXE.mtftp-tmout code 4 = unsigned integer 8;
+option PXE.mtftp-delay code 5 = unsigned integer 8;
+option arch code 93 = unsigned integer 16; # RFC4578
+
+class "pxeclient_allow" {
+ match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
+ if option arch = 00:06 {
+ filename "bootia32.efi";
+ } else if option arch = 00:07 {
+ filename "bootx64.efi";
+ } else {
+ filename "pxelinux.0";
+ }
+}
+"""
+
+PXE_DENY="""
+class "pxeclient_deny" {
+ match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
+ ignore booting;
+}
+"""
+
+CLASS_PXECLIENT = """
+class "pxeclient" {
+ match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
+ next-server %(next_server)s;
+ if option arch = 00:06 {
+ filename "bootia32.efi";
+ } else if option arch = 00:07 {
+ filename "bootx64.efi";
+ } else {
+ filename "pxelinux.0";
+ }
}
+}
+
+class "noboot" {
+ match if substring (option vendor-class-identifier, 0, 9) != "PXEClient";
+ ignore booting;
+}
"""
def MacOS_NETBOOT(dhcphost):
if not os.path.exists('/local/macos'):
@@ -61,248 +162,196 @@ class "AppleNBI-ppc" {
""" % (dhcphost, dhcphost)
-def generate(tree, dhcphost, kickstart, next_server=None):
- #
- # A. Get interfaces to serve
- #
- interface = []
- for ds in tree._host_._interface_._dhcpserver_:
- if dhcphost in ds.name[0::1]:
- # Host is dhcpserver for this interface
- dhcpip = ds._parent._ip_[0].address[0]
- interface.append(ds._parent)
- pass
- pass
- if not interface:
- raise Exception("%s is not a dhcpserver" % dhcphost)
+def generate(tree, options):
+ result = util.StringArray()
+ dhcp = set(filter(lambda d: options.host in d.name[0::1],
+ tree._host_._interface_._ip_._dhcpserver_))
+ assert dhcp, '%s is not a dhcp server' % options.host
- #
- # B. Emit header
- #
- result = "ddns-update-style none;\n"
- result += "authoritative;\n"
- result += MacOS_NETBOOT(dhcpip)
+ result.append_lines("""
+ |ddns-update-style none;
+ |authoritative;
+ |get-lease-hostnames true;
+ |use-host-decl-names on;
+ """)
+ if get_pxeboot(dhcp) == 'only':
+ result += PXE_ALLOW
+ pass
+ else:
+ result += PXE_DENY
+ pass
+ #result += MacOS_NETBOOT(dhcpip)
- #
- # C. Emit subnet declarations for each interface
- #
+ interface = set(map(lambda d: d._parent._parent, dhcp))
for i in interface:
- address = []
- vlan = []
- for ip in i._ip_:
- if ip.alias[0] and ip.address[0]:
- # Alias addresses
- address.append(ip.address[0])
- pass
- elif ip.vlan[0] and ip.address[0]:
- vlan.append(ip.address[0])
- pass
- elif ip.address[0]:
- address.append(ip.address[0])
- pass
- pass
- result += 'shared-network "MAC(%s)" {\n\n' % i.ethernet[0]
- result += emit_network(tree, dhcphost, address, kickstart,
- interface=i, next_server=next_server)
- result += "}\n"
- for v in vlan:
- result += emit_network(tree, dhcphost, [ v ], kickstart,
- next_server=next_server)
- pass
+ result += emit_interface(tree, options, i)
pass
- return result
-
-def emit_network(tree, dhcphost, addr, kickstart,
- interface=None, next_server=None):
- result = ""
- subnet = {}
- for s in filter(util.network, tree._subnet_):
- n = util.network(s)
- for a in addr:
- if util.address(a) in n:
- subnet[n] = s
- pass
- pass
- pass
-
- for n in sorted(subnet):
- for l in emit_subnet(tree, subnet[n], dhcphost, kickstart,
- interface=interface,
- next_server=next_server).split("\n"):
- result += " %s\n" % l
- pass
+ served_networks = map(lambda d: util.network(get_subnet(tree, d._parent)),
+ dhcp)
+ result += emit_hosts(tree, options, served_networks)
+ return str(result)
+
+def emit_interface(tree, options, interface):
+ result = util.StringArray()
+ subnet = dict(map(lambda d: (get_subnet(tree, d._parent), d),
+ interface._ip_._dhcpserver_))
+ result += 'shared-network "MAC(%s)" {' % interface.ethernet[0]
+ for sn,dhcp in map(lambda n: (n, subnet[n]), sorted(subnet)):
+ result += emit_network(tree, options, sn, dhcp).indent()
pass
+ result += "}"
return result
-def emit_subnet(tree, subnet, dhcphost, kickstart,
- interface=None, next_server=None):
- result = ""
+def emit_network(tree, options, subnet, dhcp):
+ result = util.StringArray()
net = util.network(subnet)
- if next_server == None:
- next_server = dhcphost
- pass
- static = {}
- dynamic = set()
- never = []
- for ip in tree._host_._interface_._ip_:
- # Find all hosts that belong to this network
- a = util.address(ip)
- if a:
- a = util.address(ip)
- if a in net:
- if ip.dynamic[0] == dhcphost:
- # Dynamic address served by this host
- dynamic.add(a)
- pass
- else:
- static[ip.name[0:]] = ip
- pass
- pass
+ pxeboot = get_pxeboot(dhcp)
+ result.append_lines("""
+ |subnet %(network)s netmask %(netmask)s {
+ | option subnet-mask %(netmask)s;
+ | option broadcast-address %(broadcast)s;
+ | server-name "%(host)s";
+ | default-lease-time 14400; # 4 hours
+ | max-lease-time 86400; # 1 day
+ """ % dict(network=net.network,
+ netmask=net.netmask,
+ broadcast=net.broadcast,
+ host=options.host))
+ if dhcp.first[0] and dhcp.last[0]:
+ first = util.address(dhcp.first[0])
+ last = util.address(dhcp.last[0])
+ assert first in net, '%s not part of %s' % (first, net)
+ assert last in net, '%s not part of %s' % (last, net)
+ if pxeboot == 'no':
+ result += " range %s %s;" % (first, last)
pass
- if ip.never[0]:
- a = util.address(ip.never[0])
- if a in net:
- never.append(ip.ethernet[0:])
- pass
+ elif pxeboot == 'only':
+ result.append_lines("""
+ | pool {
+ | allow members of "pxeclient";
+ | range %(first)s %(last)s;
+ | }""" % dict(first=first, last=last))
pass
pass
- result += "subnet %s netmask %s {\n" % (net.network, net.netmask)
- result += " option subnet-mask %s;\n" % net.netmask
- result += " option broadcast-address %s;\n" % net.broadcast
+ result += emit_subnet_info(subnet).indent()
+ result += '}'
+ return result
+
+def emit_subnet_info(subnet):
+ result = util.StringArray()
if subnet.gateway[0]:
- result += " option routers %s;\n" % subnet.gateway[0]
+ result += "option routers %s;" % subnet.gateway[0]
pass
if subnet.domain[0]:
- result += " option domain-name \"%s\";\n" % subnet.domain[0]
+ result += "option domain-name \"%s\";" % subnet.domain[0]
pass
if subnet.name_servers[0]:
- result += " option domain-name-servers %s;\n" % subnet.name_servers[0]
+ result += "option domain-name-servers %s;" % subnet.name_servers[0]
pass
if subnet.ntp_servers[0]:
- result += " option ntp-servers %s;\n" % (subnet.ntp_servers[0])
+ result += "option ntp-servers %s;" % (subnet.ntp_servers[0])
pass
- #
- # Emit dynamic hosts
- #
- result += " default-lease-time 14400; # 4 hours\n"
- result += " max-lease-time 86400; # 1 day\n"
- for s in interface_subnets_matching(interface, net):
- if s.first[0] and s.last[0]:
- result += " range %s %s;\n" % (util.address(s.first[0]),
- util.address(s.last[0]))
+ result += ''
+ return result
+
+def emit_hosts(tree, options, networks):
+ result = util.StringArray()
+ def match(a):
+ return filter(lambda n: a in n, networks)
+ static = {}
+ never = {}
+ for ip in tree._host_._interface_._ip_:
+ # Find all hosts that associated with this network
+ ethernet = ip.ethernet[0:]
+ if not ethernet:
+ continue
+ if ethernet.lower() != ethernet:
+ raise util.HostinfoException('%s not lower-case' % ethernet)
+ if ip.never[0]:
+ if match(util.address(ip.never[0])):
+ if not ethernet in never:
+ never[ethernet] = []
+ pass
+ never[ethernet].append(ip)
+ pass
+ continue
+ if match(util.address(ip)):
+ if not ethernet in static:
+ static[ethernet] = []
+ pass
+ static[ethernet].append(ip)
pass
pass
- # FIXME: Old style dynamic...
- min = 0
- max = 0
- for d in sorted(dynamic):
- if d == max + 1:
- max = d
+ def by_name(ether_ip_dict):
+ result = {}
+ for e in ether_ip_dict:
+ name = set(map(lambda n: n.name[0:], ether_ip_dict[e]))
+ if len(name) != 1:
+ raise util.HostinfoException('Multiple names %s' % name,
+ where=ether_ip_dict[e])
+ name = name.pop()
+ result[name] = (e, ether_ip_dict[e])
pass
- else:
- if min:
- result += " range %s %s;\n" % (min, max)
- pass
- min = d
- max = d
+ for name in sorted(result):
+ ether, ip = result[name]
+ yield name, ether, ip
pass
pass
- if min:
- result += " range %s %s;\n" % (min, max)
- pass
- result += " get-lease-hostnames true;\n"
- result += " use-host-decl-names on;\n"
-
- #
- # Emit static hosts
- #
- result += "\n group {\n"
- result += " default-lease-time 315360000; # 10 year\n"
- result += " max-lease-time 315360000; # 10 years\n\n"
- sk = static.keys()
- sk.sort()
- for i in [static[x] for x in sk]:
- ether = i._parent.ethernet[0]
- ip = i.address[0]
- if ether:
- assert ether.lower() == ether, "%s is not lower-case" % ether
- result += " host %s.%s {\n"% (i.name[0:], subnet.domain[0])
- result += " hardware ethernet %s;\n" % ether
- result += " fixed-address %s;\n" % ip
- result += " option host-name \"%s\";\n" % i.name[0:]
- if i._parent._kickstart_:
- if not kickstart:
- raise Exception("--kickstart needed for %s" % i.name[0:])
- kf = i._parent._kickstart_[0].file[0]
- result += " server-name \"%s\";\n" % dhcphost
- result += " next-server %s;\n" % next_server
- result += " if substring(option vendor-class-identifier, "
- result += "0, 20) = \n"
- result += " \"PXEClient:Arch:00000\" {\n"
- result += " filename \"pxelinux.0\";\n"
- result += " } else {\n"
- result += " filename \"%s/%s\";\n" % (kickstart, kf)
- result += " }\n"
- pass
- for d in i._dhcp_:
- result += " %s\n" % d.value[0]
- pass
- result += " }\n"
+ if static:
+ result.append_lines("""
+ |group { # Static hosts
+ | default-lease-time 315360000; # 10 year
+ | max-lease-time 315360000; # 10 years
+ |""")
+ for name, ether, ip in by_name(static):
+ if ether in never:
+ never.pop(ether)
+ result.append_lines("""
+ | host %(id)s {
+ | hardware ethernet %(ethernet)s;
+ | fixed-address %(address)s;
+ | option host-name "%(name)s";
+ | }""" % dict(id='%s_%s' % (name, ether.replace(':','')),
+ name=name,
+ ethernet=ether,
+ address=', '.join(map(str,
+ map(util.address, ip)))))
pass
+ result += "}"
pass
- for e in never:
- result += " host never_%s {\n" % e.replace(':', '')
- result += " hardware ethernet %s;\n" % e
- result += " ignore booting;\n"
- result += " }\n"
- pass
-
-# result += CLASS_PXECLIENT % { 'next_server': next_server }
- for s in interface_subnets_matching(interface, net):
- if s.kickstart[0] != 'any':
- continue
- else:
- continue
- for i in filter(lambda i: i.ethernet[0] and i._kickstart_,
- tree._host_._interface_):
- e = i.ethernet[0]
- assert e.lower() == e, "%s not lower-case" % e
- result += " host %s {\n"% (i.name[0:])
- result += " hardware ethernet %s;\n" % e
- result += " option host-name \"%s\";\n" % i.name[0:]
- if not kickstart:
- raise Exception("--kickstart needed for %s" % i.name[0:])
- kf = i._kickstart_[0].file[0]
- result += " server-name \"%s\";\n" % dhcphost
- result += " next-server %s;\n" % next_server
- result += " if substring(option vendor-class-identifier, "
- result += "0, 20) = \n"
- result += " \"PXEClient:Arch:00000\" {\n"
- result += " filename \"pxelinux.0\";\n"
- result += " } else {\n"
- result += " filename \"%s/%s\";\n" % (kickstart, kf)
- result += " }\n"
- result += " }\n"
+ if never:
+ result += 'group { # Disallowed hosts'
+ for name, ether, ip in by_name(never):
+ result.append_lines("""
+ | host %(name)s {
+ | hardware ethernet %(ethernet)s;
+ | ignore booting;
+ | }""" % dict(name='%s_never_%s' % (name, ether.replace(':','')),
+ ethernet=ether))
pass
- result += " if substring(option vendor-class-identifier, "
- result += "0, 20) = \n"
- result += " \"PXEClient:Arch:00000\" {\n"
- result += " filename \"pxelinux.0\";\n"
- result += " } else {\n"
- result += " filename \"%s/default\";\n" % (kickstart)
- result += " }\n"
- break
-
- result += " }\n"
- result += "}\n"
-
+ result += "}"
+ pass
return result
-def interface_subnets_matching(interface, net):
- if not interface:
- return
- for s in filter(lambda s: util.network(s) == net, interface._subnet_):
- yield s
+def get_subnet(tree, ip):
+ a = util.address(ip)
+ def match(subnet):
+ s = util.network(subnet)
+ return s and a in s
+ subnet = filter(match, tree._subnet_)
+ assert len(subnet) == 1, '%s matches multiple networks %s' % (a, subnet)
+ return subnet[0]
+
+def get_pxeboot(dhcp):
+ try:
+ p = set(map(lambda d: d.pxeboot[0] or 'no', dhcp))
+ assert len(p) == 1, "Mixed pxeboot not allowed (%s)" % ','.join(p)
+ result = p.pop()
pass
- pass
+ except TypeError:
+ result = dhcp.pxeboot[0] or 'no'
+ pass
+ allowed = [ 'no', 'only' ]
+ assert result in allowed,"pxeboot ('%s') shold be in %s" % (result, allowed)
+ return result
diff --git a/src/hostinfo/dhcpd_ipv6.py b/src/hostinfo/dhcpd_ipv6.py
index 840f922..7ea1262 100755
--- a/src/hostinfo/dhcpd_ipv6.py
+++ b/src/hostinfo/dhcpd_ipv6.py
@@ -2,88 +2,9 @@ import sys
import os
import re
import ipaddr
+import hostinfo.util as util
-class AttributeDict(dict):
- __getattr__ = dict.__getitem__
- __setattr__ = dict.__setitem__
-
-class StringArray(list):
-
- def append_lines(self, lines, sep='|'):
- for l in map(lambda s: s.strip(), lines.split('\n')):
- if not l:
- continue
- if not l.startswith(sep):
- raise Exception('%s do not start with "%s"' % (l, pattern))
- self.append(l[len(sep):])
- pass
-
- def indent(self):
- for l in self:
- yield ' %s' % l
- pass
- pass
-
- pass
-
-def generate(tree, options):
- result = StringArray()
- interface = dict([ (h.name[0], [ i for i in h._interface_ ])
- for h in tree._host_ if h._interface_ ])
- host = [ h for h in tree._host_ if h.name[0] == options.host ][0]
- with_dhcp = list([ i for i in host._interface_ if i._dhcpserver_ ])
- if not with_dhcp:
- raise Exception("%s is not a dhcpserver" % options.host)
- with_ipv6 = [ i for i in with_dhcp if i._ipv6_ ]
- if not with_ipv6:
- return ""
- result.append_lines("""
- |ddns-update-style none;
- |authoritative;
- |allow leasequery;
- """)
- for i in with_ipv6:
- result.append('shared-network "MAC(%s)" {' % i.ethernet[0])
- result.extend(emit_interface_ipv6(tree, i, options).indent())
- result.append('}')
- return '\n'.join(result) + '\n'
-
-def emit_interface_ipv6(tree, interface, options):
- result = StringArray()
- subnet = set()
- for ip in [ ip for ip in interface._ipv6_ if ip.address[0] ]:
- a = ipaddr.IPAddress(ip.address[0])
- subnet.update([ (s.prefix[0], s) for s in tree._subnet_
- if (s.prefix[0] and a in ipaddr.IPNetwork(s.prefix[0]))
- ])
- pass
- for k,s in sorted(subnet):
- result = StringArray()
- dynamic = [ map(ipaddr.IPAddress, (r.first[0], r.last[0]))
- for r in interface._dhcpserver_._subnet_
- if r.first[0] and r.last[0] and r.prefix[0] == s.prefix[0] ]
- result.append('subnet6 %s {' % ipaddr.IPNetwork(s.prefix[0]) )
- result.extend(emit_subnet_ipv6(tree, s, dynamic, options).indent())
- result.append('}' )
- pass
- return result
-
-def emit_subnet_ipv6(tree, subnet, dynamic, options):
- result = StringArray()
- for first,last in dynamic:
- result.append('range6 %s %s;' % (first, last))
- pass
- for name,ip in sorted([ (ip.name[0:], ip)
- for ip in tree._host_._interface_._ipv6_
- if ip.address[0]]):
- result.append('host %s.%s {' % (name, subnet.domain[0]))
- result.append(' fixed-address6 %s;' % ip.address[0])
- result.append(' hardware ethernet %s;' % ip.ethernet[0:].upper())
- result.append('}')
- return result
-
-def ignore():
- """
+"""
default-lease-time 2592000;
preferred-lifetime 604800;
@@ -107,3 +28,138 @@ subnet6 2001:ed8:77b5::/64 {
range6 2001:ed8:77b5::1 2001:ed8:77b5::ffff:ffff;
}
"""
+
+def generate(tree, options):
+ result = util.StringArray()
+ dhcp = set(filter(lambda d: options.host in d.name[0::1],
+ tree._host_._interface_._ipv6_._dhcpserver_))
+ assert dhcp, '%s is not a dhcp6 server' % options.host
+
+ result.append_lines("""
+ |ddns-update-style none;
+ |authoritative;
+ |get-lease-hostnames true;
+ |use-host-decl-names on;
+ |allow leasequery;
+ """)
+ interface = set(map(lambda d: d._parent._parent, dhcp))
+ for i in interface:
+ result += emit_interface(tree, options, i)
+ pass
+ return str(result)
+
+def emit_interface(tree, options, interface):
+ result = util.StringArray()
+ subnet = dict(map(lambda d: (get_subnet(tree, d._parent), d),
+ interface._ipv6_._dhcpserver_))
+ result += 'shared-network "MAC(%s)" {' % interface.ethernet[0]
+ for sn,dhcp in map(lambda n: (n, subnet[n]), sorted(subnet)):
+ result += emit_network(tree, options, sn, dhcp).indent()
+ pass
+ result += "}"
+ return result
+
+def emit_network(tree, options, subnet, dhcp):
+ result = util.StringArray()
+ net = util.network(subnet)
+ result.append_lines("""
+ |subnet6 %(prefix)s {
+ | server-name "%(host)s";
+ | default-lease-time 14400; # 4 hours
+ | max-lease-time 86400; # 1 day
+ """ % dict(prefix=str(net),
+ netmask=net.netmask,
+ broadcast=net.broadcast,
+ host=options.host))
+ if dhcp.first[0] and dhcp.last[0]:
+ first = util.address(dhcp.first[0])
+ last = util.address(dhcp.last[0])
+ assert first in net, '%s not part of %s' % (first, net)
+ assert last in net, '%s not part of %s' % (last, net)
+ result += " range6 %s %s;" % (first, last)
+ pass
+ result += emit_subnet_info(subnet).indent()
+ result += emit_hosts(tree, options, net).indent()
+ result += '}'
+ return result
+
+def emit_subnet_info(subnet):
+ result = util.StringArray()
+ if subnet.gateway[0]:
+ result += "option routers %s;" % subnet.gateway[0]
+ pass
+ if subnet.domain[0]:
+ result += "option domain-name \"%s\";" % subnet.domain[0]
+ pass
+ if subnet.name_servers[0]:
+ result += "option dhcp6.domain-name-servers %s;" % (
+ subnet.name_servers[0])
+ pass
+ if subnet.ntp_servers[0]:
+ result += "option ntp-servers %s;" % (subnet.ntp_servers[0])
+ pass
+ result += ''
+ return result
+
+def emit_hosts(tree, options, net):
+ result = util.StringArray()
+ static = {}
+ never = {}
+ for ip in tree._host_._interface_._ipv6_:
+ # Find all hosts that associated with this network
+ ethernet = ip.ethernet[0:]
+ if not ethernet:
+ continue
+ assert ethernet.lower() == ethernet, '%s not lower-case' % ethernet
+ if ip.never[0]:
+ a = util.address(ip.never[0])
+ if a in net:
+ never[ip.name[0:]] =ip
+ pass
+ pass
+ else:
+ a = util.address(ip)
+ if a in net:
+ static[ip.name[0:]] = ip
+ pass
+ pass
+ pass
+ if static:
+ result.append_lines("""
+ |group { # Static hosts
+ | default-lease-time 315360000; # 10 year
+ | max-lease-time 315360000; # 10 years
+ |""")
+ for h in sorted(static):
+ result.append_lines("""
+ | host %(name)s {
+ | hardware ethernet %(ethernet)s;
+ | fixed-address6 %(address)s;
+ | option host-name "%(name)s";
+ | }""" % dict(name=h,
+ ethernet=static[h].ethernet[0:],
+ address=util.address(static[h])))
+ result += "}"
+ pass
+ if never:
+ result.append_lines("""
+ |group { # Disallowed hosts
+ | ignore booting;
+ |""")
+ for h in sorted(never):
+ result.append(
+ ' host never_%(name)s { hardware ethernet %(ether)s; }' %
+ dict(name=h, ether=never[h].ethernet[0:]))
+ result += "}"
+
+ return result
+
+def get_subnet(tree, ip):
+ a = util.address(ip)
+ def match(subnet):
+ s = util.network(subnet)
+ return s and a in s
+ subnet = filter(match, tree._subnet_)
+ assert len(subnet) == 1, '%s matches multiple networks %s' % (a, subnet)
+ return subnet[0]
+
diff --git a/src/hostinfo/ifconfig.py b/src/hostinfo/ifconfig.py
index cd7f8ca..4936e11 100755
--- a/src/hostinfo/ifconfig.py
+++ b/src/hostinfo/ifconfig.py
@@ -1,4 +1,3 @@
-from hostinfo.util import aton
import hostinfo.util as util
import subprocess
import re
@@ -28,81 +27,60 @@ def is_static(interface):
pass
return True
-def generate_ifcfgv4(tree, interface):
+def generate_ifcfgv4(tree, interface, search, nameservers):
config = []
- static_config = []
- search = []
- nameservers = []
- index = ''
- for ip in interface._ip_:
- if not (ip.address[0] and (is_static(interface) or
- (not ip.vlan[0] and
- ip.alias[0]))):
- continue
- sub = subnet(tree, ip.address[0])
- if ip.search[0]:
- search.extend(ip.search[0].split())
- pass
- static_config.append('IPADDR%s=%s' % (index, ip.address[0]))
- netmask = ip.netmask[0] or sub and sub.netmask[0] or '255.255.255.255'
- gateway = ip.gateway[0] or sub and sub.gateway[0]
- network = ip.network[0] or sub and sub.network[0]
- broadcast = ip.broadcast[0] or sub and sub.broadcast[0]
- name_servers = ip.name_servers[0] or sub and sub.name_servers[0]
- if netmask:
- static_config.append('NETMASK%s=%s' % (index, netmask))
- pass
- if gateway:
- static_config.append('GATEWAY%s=%s' % (index, gateway))
- pass
- if network:
- static_config.append('NETWORK%s=%s' % (index,network))
- pass
- if broadcast:
- static_config.append('BROADCAST%s=%s' % (index, broadcast))
- pass
-
- if sub:
- for n in re.split('[, ]+', sub.name_servers[0]):
- # Domain nameservers
- if not n in nameservers:
- nameservers.append(n)
- pass
- pass
- for n in interface._nameserver_:
- if n.domain[0] == sub.domain[0]:
- if not '127.0.0.1' in nameservers:
- # Insert own address first in nameserver list
- nameservers.insert(0, '127.0.0.1')
+ if not is_static(interface):
+ config.append('BOOTPROTO=dhcp')
+ pass
+ else:
+ config.append('BOOTPROTO=none')
+ index = ''
+ for ip in filter(util.address, interface._ip_):
+ a = util.address(ip)
+ netmask = None
+ network = None
+ broadcast = None
+ gateway = None
+ for s in filter(util.network, tree._subnet_ ):
+ n = util.network(s)
+ if a in n:
+ netmask = n.netmask
+ network = n.network
+ broadcast = n.broadcast
+ gateway = s.gateway[0]
+ if s.name_servers[0]:
+ nameservers.extend(re.split('[, ]+', s.name_servers[0]))
+ pass
+ if s.domain[0]:
+ search.update(s.domain[0].split())
pass
pass
pass
+ if ip.netmask[0]: netmask = ip.netmask[0]
+ if ip.network[0]: network = ip.network[0]
+ if ip.broadcast[0]: broadcast = ip.broadcast[0]
+ if ip.gateway[0]: gateway = ip.gateway[0]
+ if ip.search[0]: search.update(ip.search[0].split())
+ config.append('IPADDR%s=%s' % (index, a))
+ if netmask:
+ config.append('NETMASK%s=%s' % (index, netmask))
+ pass
+ if gateway:
+ config.append('GATEWAY%s=%s' % (index, gateway))
+ pass
+ if network:
+ config.append('NETWORK%s=%s' % (index, network))
+ pass
+ if broadcast:
+ config.append('BROADCAST%s=%s' % (index, broadcast))
+ pass
+ index = int('0%s' % index)+1
pass
- index = int('0%s' % index)+1
- pass
- if interface.defroute[0]:
- config.append('DEFROUTE=%s' % interface.defroute[0])
- pass
- index = 0
- for n in nameservers:
- index += 1
- static_config.append('DNS%d=%s' % (index, n))
- pass
- if search:
- static_config.append('SEARCH="%s"' % (' '.join(search)))
- pass
- if not is_static(interface):
- config.insert(0, 'BOOTPROTO=dhcp')
- config.extend(map(lambda s: '# %s' % s, static_config))
- pre= '# '
- pass
- else:
- config.insert(0, 'BOOTPROTO=none')
- config.extend(static_config)
pass
+
return config
-def generate_ifcfgv6(tree, interface):
+def generate_ifcfgv6(tree, interface, search, nameservers):
config = []
if not is_static(interface):
config.append('IPV6INIT=yes')
@@ -116,9 +94,13 @@ def generate_ifcfgv6(tree, interface):
address = []
for ipv6 in filter(util.address, interface._ipv6_):
a = util.address(ipv6)
- for s in map(util.network, filter(util.network, tree._subnet_ )):
- if a in s:
- address.append('%s/%d' % (a, s.prefixlen))
+ for s in filter(util.network, tree._subnet_ ):
+ n = util.network(s)
+ if a in n:
+ address.append('%s/%d' % (a, n.prefixlen))
+ if s.domain[0]:
+ search.update(s.domain[0].split())
+ pass
pass
pass
pass
@@ -136,10 +118,34 @@ def generate_ifcfgv6(tree, interface):
pass
return config
+def indexed_assign(label, values, start_index=''):
+ index = start_index
+ for v in values:
+ yield '%s%s=%s' % (label, index, v)
+ index = int('0%s' % index)+1
+
def generate_ifcfg(tree, interface):
config = []
- config.extend(generate_ifcfgv4(tree, interface))
- config.extend(generate_ifcfgv6(tree, interface))
+ search = set()
+ nameservers = []
+ config.extend(generate_ifcfgv4(tree, interface,
+ search=search, nameservers=nameservers))
+ config.extend(generate_ifcfgv6(tree, interface,
+ search=search, nameservers=nameservers))
+ if interface._nameserver_:
+ # Nameservers should ask themselves first
+ nameservers.insert(0, '127.0.0.1')
+ pass
+ # Remove duplicate nameservers
+ nameservers = reduce(lambda r,v: v in r and r or r + [v],
+ nameservers, [])
+ config.extend(indexed_assign('DNS', nameservers, 1))
+ if search:
+ config.append('SEARCH="%s"' % (' '.join(search)))
+ pass
+ if interface.defroute[0]:
+ config.append('DEFROUTE=%s' % interface.defroute[0])
+ pass
return '\n'.join(config) + '\n'
@@ -192,15 +198,6 @@ def generate(tree, host):
return result
-def subnet(tree, ip):
- for s in tree._subnet_:
- if not s.netmask[0] or not s.network[0]:
- continue
- if aton(s.network[0]) == aton(ip) & aton(s.netmask[0]):
- return s
- pass
- return None
-
def device(ethernet):
""" Map ethernet to device name"""
if not ethernet:
diff --git a/src/hostinfo/util.py b/src/hostinfo/util.py
index 24532f1..3e4c177 100755
--- a/src/hostinfo/util.py
+++ b/src/hostinfo/util.py
@@ -1,6 +1,7 @@
import ipaddr
import itertools
import types
+import hostinfo.parser
def network(s):
if s.network[0] and s.netmask[0]:
@@ -77,3 +78,60 @@ def by_mac(a, b):
elif int(aa[i],16) > int(ba[i],16):
return 1
return 0
+
+class StringArray(list, object):
+
+ def append_lines(self, lines, sep='|'):
+ for l in map(lambda s: s.strip(), lines.split('\n')):
+ if not l:
+ continue
+ if not l.startswith(sep):
+ raise Exception('%s do not start with "%s"' % (l, sep))
+ self.append(l[len(sep):])
+ pass
+
+ def __iadd__(self, other):
+ try:
+ self.extend(other.split('\n'))
+ pass
+ except AttributeError:
+ self.extend(other)
+ pass
+ return self
+
+ def indent(self, indent=' '):
+ for l in self:
+ yield '%s%s' % (indent, l)
+ pass
+ pass
+
+ def __str__(self):
+ return '\n'.join(self) + '\n'
+
+ pass
+
+class HostinfoException(Exception):
+
+ def __init__(self, *args, **kwargs):
+ self.reason = list(args)
+ def get(key, args):
+ return key in args and args[key] or None
+
+ where = get('where', kwargs)
+ if where:
+ if isinstance(where, hostinfo.parser.Node):
+ where = [ where ]
+ pass
+ for node in where:
+ root = node
+ while root._parent:
+ root = root._parent
+ pass
+ self.reason.append('(%s:%d)' % (root._url, node._line))
+ pass
+ pass
+ pass
+
+ def __str__(self):
+ return ' '.join(self.reason)
+ pass
--
GitLab