Public feed
#724812
diff --git a/koan/app.py b/koan/app.py
index e2bc6ee..0d80665 100755
--- a/koan/app.py
+++ b/koan/app.py
@@ -33,6 +33,7 @@ import xmlrpclib
import string
import re
import glob
+import socket
# the version of cobbler needed to interact with this version of koan
# this is an decimal value (major + 0.1 * minor + 0.01 * maint)
@@ -617,7 +618,10 @@ class Koan:
def after_download(self, profile_data):
for x in DISPLAY_PARAMS:
if profile_data.has_key(x):
- print "%20s : %s" % (x, profile_data[x])
+ value = profile_data[x]
+ if x == 'kernel_options':
+ value = self.calc_kernel_args(profile_data)
+ print "%20s : %s" % (x, value)
return self.net_install(after_download)
#---------------------------------------------------
@@ -657,11 +661,17 @@ class Koan:
k_args = self.safe_load(profile_data,'kernel_options')
k_args = k_args + " ks=file:ks.cfg"
+ kickstart = self.safe_load(profile_data,'kickstart')
+
self.build_initrd(
self.safe_load(profile_data,'initrd_local'),
- self.safe_load(profile_data,'kickstart'),
+ kickstart,
profile_data
)
+
+ if k_args.find('koan_autonet') != -1:
+ k_args = k_args.replace('koan_autonet', self.get_netconfig(kickstart))
+
k_args = k_args.replace("lang ","lang= ")
cmd = [ "/sbin/grubby",
@@ -785,6 +795,156 @@ class Koan:
shutil.copyfile("/var/spool/koan/initrd_final", initrd)
#---------------------------------------------------
+ def get_netconfig_from_running_system(self):
+ """
+ Get the network configuration from the running system (as opposed
+ to config files, which are distribution-specific). This isn't going to
+ be 100% correct on multi-homed machines, but should work in 98% of the
+ sane cases in the wild.
+ """
+ ret = dict()
+
+ # get the hostname from the system
+ ret['hostname'] = socket.gethostname()
+ try:
+ ret['ip'] = socket.gethostbyname( ret['hostname'] )
+ except:
+ # ignore the error, but still print the trace
+ traceback.print_exc()
+
+ if ret.has_key('ip'):
+ iface = self.get_local_interface( ip=ret['ip'] )
+ ret.update(**iface)
+ else:
+ iface = self.get_local_interface()
+ ret.update(**iface)
+
+ # if cobbler knows about this system, prefer cobbler's data
+ if self.system is not None and (ret['mac'] or ret['ip'] or ret['hostname']):
+ try:
+ systems = self.get_systems_xmlrpc()
+
+ for system in systems:
+ if ret.has_key('mac') and system['mac_address'].upper() == ret['mac'].upper():
+ ret.update(system)
+ break
+ if ret.has_key('ip') and system['ip_address'] == ret['ip']:
+ ret.update(system)
+ break
+ if ret.has_key('hostname') and system['hostname'] == ret['hostname']:
+ ret.update(system)
+ break
+ except:
+ # ignore the error, but still print the trace
+ traceback.print_exc()
+
+ # get the first nameserver configured on the local system
+ fd = open("/etc/resolv.conf", "r")
+ for line in fd.readlines():
+ if line.startswith('nameserver'):
+ ret['nameserver'] = line.split()[1]
+ break
+ fd.close()
+
+ return ret
+
+ def get_netconfig_from_kickstart(self,kickstart):
+ """
+ Scan the kickstart configuration for a network --bootproto static line.
+ """
+ ret = dict()
+ lines = []
+
+ if os.path.exists("/var/spool/koan/ks.cfg"):
+ fd = open("/var/spool/koan/ks.cfg", "r")
+ lines = fd.readlines()
+ fd.close()
+ else:
+ ksdata = self.get_kickstart_data(kickstart,None)
+ lines = ksdata.splitlines()
+
+ for line in lines:
+ if line.startswith('network '):
+ p = opt_parse.OptionParser()
+ p.add_option("--bootproto", dest="bootproto")
+ p.add_option("--ip", dest="ip")
+ p.add_option("--netmask", dest="netmask")
+ p.add_option("--gateway", dest="gateway")
+ p.add_option("--hostname", dest="hostname")
+ p.add_option("--nameserver", dest="nameserver")
+ # ignored, but required for clean parsing
+ p.add_option("--device", dest="device")
+ p.add_option("--onboot", dest="onboot")
+ p.add_option("--nodns", dest="nodns")
+ (options,args) = p.parse_args( line.split()[1:] )
+
+ if options.bootproto and options.bootproto == 'static':
+ ret['ip'] = options.ip
+ ret['netmask'] = options.netmask
+ ret['gateway'] = options.gateway
+ ret['hostname'] = options.hostname
+ ret['nameserver'] = options.nameserver
+
+ break
+ return ret
+
+ def get_netconfig(self,kickstart):
+ """
+ If this is a --replace-self run, get the running network configuration and
+ put it on the kernel command line so DHCP isn't required.
+
+ If it's a new kickstart, check the kickstart for a static network
+ configuration and push that into the kernel command line.
+ """
+ kargs = ""
+ hostname = None
+ ip = None
+ netmask = None
+ nameserver = None
+ gateway = None
+
+ # Always try to get the network configuration kickstart. This will
+ # override anything set in the 'if self.is_replace' block above.
+ ksconf = self.get_netconfig_from_kickstart(kickstart)
+ if ksconf.has_key('ip') and ksconf['ip'] is not None:
+ ip = ksconf['ip']
+ if ksconf.has_key('netmask') and ksconf['netmask'] is not None:
+ netmask = ksconf['netmask']
+ if ksconf.has_key('hostname') and ksconf['hostname'] is not None:
+ hostname = ksconf['hostname']
+ if ksconf.has_key('nameserver') and ksconf['nameserver'] is not None:
+ nameserver = ksconf['nameserver']
+ if ksconf.has_key('gateway') and ksconf['gateway'] is not None:
+ gateway = ksconf['gateway']
+
+ if self.is_replace:
+ conf = self.get_netconfig_from_running_system()
+ if conf is not None:
+ if ip is None and conf['ip'] is not None:
+ ip = conf['ip']
+ if netmask is None and conf['netmask'] is not None:
+ netmask = conf['netmask']
+ if hostname is None and conf['hostname'] is not None:
+ hostname = conf['hostname']
+ if nameserver is None and conf['nameserver'] is not None:
+ nameserver = conf['nameserver']
+ if gateway is None and conf['gateway'] is not None:
+ gateway = conf['gateway']
+
+ if ip is not None:
+ kargs = kargs + " ip=%s" % ip
+ if netmask is not None:
+ kargs = kargs + " netmask=%s" % netmask
+ if hostname is not None:
+ kargs = kargs + " hostname=%s" % hostname
+ if nameserver is not None:
+ kargs = kargs + " nameserver=%s" % nameserver
+ if gateway is not None:
+ kargs = kargs + " gateway=%s" % gateway
+
+ return kargs
+
+ #---------------------------------------------------
def connect_fail(self):
raise InfoException, "Could not communicate with %s:%s" % (self.server, self.port)
@@ -827,6 +987,104 @@ class Koan:
#---------------------------------------------------
+ def get_local_interface(self,ip=None,mac=None,iface=None,want_all=None):
+ """
+ Fetches interfaces, macs, ips, netmask (in cidr notation), and optionally
+ default route using iproute2.
+
+ The default is to return a best-guess of the primary interface on the
+ system. If a system is hooked up with a default route, it will be chosen.
+ Otherwise, eth0 is preferred. After that, the "most configured" interface
+ will be returned. If no appropriate-looking interfaces are found, None will
+ be returned.
+
+ Options:
+ ip: specify an ip to search for, return only that interface
+ mac: sepcify a mac to search for, return only that interface
+ iface: return only the specified interface
+ want_all: (boolean) return all information in a dict
+
+ """
+ interfaces = dict()
+ iface_re = re.compile('\d+: ([a-z]+[0-9]+): <.*> mtu \d+', re.IGNORECASE)
+
+ mac_re = re.compile('.*link/ether ([a-z0-9:]{17}) ', re.IGNORECASE)
+ if mac is not None:
+ mac_re = re.compile('.*link/ether (%s) ' % mac, re.IGNORECASE)
+
+ ip_re = re.compile('.*inet ([\.0-9]+)/(\d+) ')
+ if ip is not None:
+ ip_re = re.compile('.*inet (%s)/(\d+) ' % ip)
+
+ sys_iface = None
+ fd = os.popen("/sbin/ip address show")
+ for line in fd.readlines():
+ mac_match = mac_re.match(line)
+ iface_match = iface_re.match(line)
+ ip_match = ip_re.match(line)
+
+ if iface_match:
+ sys_iface = iface_match.group(1)
+ interfaces[sys_iface] = { 'iface': sys_iface }
+ if mac_match and sys_iface:
+ interfaces[sys_iface]['mac'] = mac_match.group(1)
+ if ip_match and sys_iface:
+ interfaces[sys_iface]['ip'] = ip_match.group(1)
+ interfaces[sys_iface]['netmask_int'] = ip_match.group(2)
+ interfaces[sys_iface]['netmask'] = self.netmask_i2dq( ip_match.group(2) )
+
+ fd.close()
+
+ # get the default route
+ fd = os.popen("/sbin/ip route show")
+ for line in fd.readlines():
+ if line.startswith('default via '):
+ parts = line.split()
+ interfaces[parts[4]]['gateway'] = parts[2]
+ break
+ fd.close()
+
+ if want_all is not None:
+ return interfaces
+ elif mac:
+ for i in interfaces.values():
+ if i[mac] == mac: return i
+ elif ip:
+ for i in interfaces.values():
+ if i[ip] == ip: return i
+ elif iface:
+ # this can and should throw an error if the interface doesn't exist
+ return interfaces[iface]
+ else:
+ ret = None
+
+ # but even more likely, we want the device the default route is on
+ for i in interfaces.values():
+ if i.has_key('gateway'): ret = i
+
+ if ret is None:
+ for i in interfaces.values():
+ if i.has_key('ip') and i.has_key('mac'): ret = i
+
+ # eth0 is the most likely cnadidate
+ if ret is None and interfaces.has_key('eth0'):
+ ret = interfaces['eth0']
+
+ return ret
+
+ def netmask_i2dq(self,nm):
+ """
+ Convert a netmask from integer to dotted-quad notation.
+ """
+ intval = 0xffffffff << (32 - int(nm))
+ ret = "%u.%u.%u.%u" % ((intval >> 24) & 0x000000ff,
+ ((intval & 0x00ff0000) >> 16),
+ ((intval & 0x0000ff00) >> 8),
+ (intval & 0x000000ff))
+ return ret
+
+ #---------------------------------------------------
+
def is_ip(self,strdata):
"""
Is strdata an IP?
@@ -901,6 +1159,9 @@ class Koan:
def calc_kernel_args(self, pd):
kickstart = self.safe_load(pd,'kickstart')
options = self.safe_load(pd,'kernel_options')
+
+ options = options.replace('koan_autonet', self.get_netconfig(kickstart))
+
kextra = ""
if kickstart != "":
kextra = kextra + "ks=" + kickstart
@@ -910,6 +1171,7 @@ class Koan:
kextra = kextra + options
# parser issues? lang needs a trailing = and somehow doesn't have it.
kextra = kextra.replace("lang ","lang= ")
+
return kextra
#---------------------------------------------------