diff --git a/src/hostinfo.py b/src/hostinfo.py index c18ecbe16feffd7c637d25e71724d44ba99a27dd..4d3b3412744fb231a96a3ff875ff2cbaf3010924 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 e1f69efd718669b31589269b8c82453d18e50568..9654a47ffdd76619924f13af852d1a854bcac805 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 840f9229d3c3b18696ffaff801f25c744e853279..7ea126229621cc76878068900b1b0825a56e0ff0 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 cd7f8ca9434dbc52208f2667fe66cf06ce264b35..4936e1156c63c4e991133268ab179e6db9d9c296 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 24532f10ad5d66013ef62239032982a0ba518378..3e4c177046094f1d6a2e05fb8b6fa1e522c4a344 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