From 0a2d962b3b9c396f45b6ef3e1d435bb63b83a15d Mon Sep 17 00:00:00 2001 From: Daniel Hokka Zakrisson Date: Wed, 3 Dec 2008 17:19:02 +0000 Subject: [PATCH] Add trunk version. --- modprobe.py | 130 ++++++++++++++++++++++++ plnet.py | 279 +++++++++++++++++++++++++++++++++++++++++++++++++++ pyplnet.spec | 54 ++++++++++ setup.py | 57 +++++++++++ sioc.c | 160 +++++++++++++++++++++++++++++ 5 files changed, 680 insertions(+) create mode 100644 modprobe.py create mode 100644 plnet.py create mode 100644 pyplnet.spec create mode 100644 setup.py create mode 100644 sioc.c diff --git a/modprobe.py b/modprobe.py new file mode 100644 index 0000000..d7fa2e3 --- /dev/null +++ b/modprobe.py @@ -0,0 +1,130 @@ +# +# $Id$ +# + +"""Modprobe is a utility to read/modify/write /etc/modprobe.conf""" + +import os + +class Modprobe(): + def __init__(self,filename="/etc/modprobe.conf"): + self.conffile = {} + self.origconffile = {} + for keyword in ("alias","options","install","remove","blacklist","MODULES"): + self.conffile[keyword]={} + self.filename = filename + + def input(self,filename=None): + if filename==None: filename=self.filename + fb = file(filename,"r") + for line in fb.readlines(): + parts = line.split() + command = parts[0].lower() + + table = self.conffile.get(command,None) + if table == None: + print "WARNING: command %s not recognize. Ignoring!" % command + continue + + if command == "alias": + wildcard=parts[1] + modulename=parts[2] + self.aliasset(wildcard,modulename) + options='' + if len(parts)>3: + options=" ".join(parts[3:]) + self.optionsset(modulename,options) + self.conffile['MODULES']={} + self.conffile['MODULES'][modulename]=options + else: + modulename=parts[1] + rest=" ".join(parts[2:]) + self._set(command,modulename,rest) + if command == "options": + self.conffile['MODULES'][modulename]=rest + + self.origconffile = self.conffile.copy() + + def _get(self,command,key): + return self.conffile[command].get(key,None) + + def _set(self,command,key,value): + self.conffile[command][key]=value + + def aliasget(self,key): + return self._get('alias',key) + + def optionsget(self,key): + return self._get('options',key) + + def aliasset(self,key,value): + self._set("alias",key,value) + + def optionsset(self,key,value): + self._set("options",key,value) + + def _comparefiles(self,a,b): + try: + if not os.path.exists(a): return False + fb = open(a) + buf_a = fb.read() + fb.close() + + if not os.path.exists(b): return False + fb = open(b) + buf_b = fb.read() + fb.close() + + return buf_a == buf_b + except IOError, e: + return False + + def output(self,filename="/etc/modprobe.conf",program="NodeManager"): + tmpnam = os.tmpnam() + fb = file(tmpnam,"w") + fb.write("# Written out by %s\n" % program) + + for command in ("alias","options","install","remove","blacklist"): + table = self.conffile[command] + keys = table.keys() + keys.sort() + for k in keys: + v = table[k] + fb.write("%s %s %s\n" % (command,k,v)) + + fb.close() + if not self._comparefiles(tmpnam,filename): + os.rename(tmpnam,filename) + os.chmod(filename,0644) + return True + else: + return False + + def probe(self,name): + o = os.popen("/sbin/modprobe %s" % name) + o.close() + + def checkmodules(self): + syspath="/sys/module" + modules = os.listdir(syspath) + for module in modules: + path="%/%s/parameters"%(syspath,module) + if os.path.exists(path): + ps=os.listdir(path) + parameters={} + for p in ps: + fb = file("%s/%s"%(path,p),"r") + parameters[p]=fb.readline() + fb.close() + +if __name__ == '__main__': + import sys + if len(sys.argv)>1: + m = Modprobe(sys.argv[1]) + else: + m = Modprobe() + + m.input() + m.aliasset("bond0","bonding") + m.optionsset("bond0","miimon=100") + m.output("/tmp/x") diff --git a/plnet.py b/plnet.py new file mode 100644 index 0000000..4b0dc76 --- /dev/null +++ b/plnet.py @@ -0,0 +1,279 @@ +# $Id$ + +import os +import socket +import time + +import sioc +import modprobe + +def InitInterfaces(logger, plc, data, root="", files_only=False): + sysconfig = "%s/etc/sysconfig/network-scripts" % root + + # query running network interfaces + devs = sioc.gifconf() + ips = dict(zip(devs.values(), devs.keys())) + macs = {} + for dev in devs: + macs[sioc.gifhwaddr(dev).lower()] = dev + + # assume data['networks'] contains this node's NodeNetworks + interfaces = {} + interface = 1 + hostname = data.get('hostname',socket.gethostname()) + networks = data['networks'] + failedToGetSettings = False + for network in networks: + logger.verbose('net:InitInterfaces interface %d: %s'%(interface,network)) + logger.verbose('net:InitInterfaces macs = %s' % macs) + logger.verbose('net:InitInterfaces ips = %s' % ips) + # Get interface name preferably from MAC address, falling back + # on IP address. + hwaddr=network['mac'] + if hwaddr <> None: hwaddr=hwaddr.lower() + if hwaddr in macs: + orig_ifname = macs[hwaddr] + elif network['ip'] in ips: + orig_ifname = ips[network['ip']] + else: + orig_ifname = None + + if orig_ifname: + logger.verbose('net:InitInterfaces orig_ifname = %s' % orig_ifname) + + inter = {} + inter['ONBOOT']='yes' + inter['USERCTL']='no' + if network['mac']: + inter['HWADDR'] = network['mac'] + + if network['method'] == "static": + inter['BOOTPROTO'] = "static" + inter['IPADDR'] = network['ip'] + inter['NETMASK'] = network['netmask'] + + elif network['method'] == "dhcp": + inter['BOOTPROTO'] = "dhcp" + if network['hostname']: + inter['DHCP_HOSTNAME'] = network['hostname'] + else: + inter['DHCP_HOSTNAME'] = hostname + if not network['is_primary']: + inter['DHCLIENTARGS'] = "-R subnet-mask" + + if len(network['interface_tag_ids']) > 0: + try: + settings = plc.GetInterfaceTags({'interface_tag_id': + network['interface_tag_ids']}) + except: + logger.log("net:InitInterfaces FATAL: failed call GetInterfaceTags({'interface_tag_id':{%s})"% \ + network['interface_tag_ids']) + failedToGetSettings = True + continue # on to the next network + + for setting in settings: + # to explicitly set interface name + settingname = setting['name'].upper() + if settingname in ('IFNAME','ALIAS','CFGOPTIONS','DRIVER'): + inter[settingname]=setting['value'] + else: + logger.log("net:InitInterfaces WARNING: ignored setting named %s"%setting['name']) + + # support aliases to interfaces either by name or HWADDR + if 'ALIAS' in inter: + if 'HWADDR' in inter: + hwaddr = inter['HWADDR'].lower() + del inter['HWADDR'] + if hwaddr in macs: + hwifname = macs[hwaddr] + if ('IFNAME' in inter) and inter['IFNAME'] <> hwifname: + logger.log("net:InitInterfaces WARNING: alias ifname (%s) and hwaddr ifname (%s) do not match"%\ + (inter['IFNAME'],hwifname)) + inter['IFNAME'] = hwifname + else: + logger.log('net:InitInterfaces WARNING: mac addr %s for alias not found' %(hwaddr,alias)) + + if 'IFNAME' in inter: + # stupid RH /etc/sysconfig/network-scripts/ifup-aliases:new_interface() + # checks if the "$DEVNUM" only consists of '^[0-9A-Za-z_]*$'. Need to make + # our aliases compliant. + parts = inter['ALIAS'].split('_') + isValid=True + for part in parts: + isValid=isValid and part.isalnum() + + if isValid: + interfaces["%s:%s" % (inter['IFNAME'],inter['ALIAS'])] = inter + else: + logger.log("net:InitInterfaces WARNING: interface alias (%s) not a valid string for RH ifup-aliases"% inter['ALIAS']) + else: + logger.log("net:InitInterfaces WARNING: interface alias (%s) not matched to an interface"% inter['ALIAS']) + interface -= 1 + else: + if ('IFNAME' not in inter) and not orig_ifname: + ifname="eth%d" % (interface-1) + # should check if $ifname is an eth already defines + if os.path.exists("%s/ifcfg-%s"%(sysconfig,ifname)): + logger.log("net:InitInterfaces WARNING: possibly blowing away %s configuration"%ifname) + else: + if ('IFNAME' not in inter) and orig_ifname: + ifname = orig_ifname + else: + ifname = inter['IFNAME'] + interface -= 1 + interfaces[ifname] = inter + + m = modprobe.Modprobe() + m.input("%s/etc/modprobe.conf" % root) + for (dev, inter) in interfaces.iteritems(): + # get the driver string "moduleName option1=a option2=b" + driver=inter.get('DRIVER','') + if driver <> '': + driver=driver.split() + kernelmodule=driver[0] + m.aliasset(dev,kernelmodule) + options=" ".join(driver[1:]) + if options <> '': + m.optionsset(dev,options) + m.output("%s/etc/modprobe.conf" % root) + + # clean up after any ifcfg-$dev script that's no longer listed as + # part of the NodeNetworks associated with this node + + # list all network-scripts + files = os.listdir(sysconfig) + + # filter out the ifcfg-* files + ifcfgs=[] + for f in files: + if f.find("ifcfg-") == 0: + ifcfgs.append(f) + + # remove loopback (lo) from ifcfgs list + lo = "ifcfg-lo" + if lo in ifcfgs: ifcfgs.remove(lo) + + # remove known devices from icfgs list + for (dev, inter) in interfaces.iteritems(): + ifcfg = 'ifcfg-'+dev + if ifcfg in ifcfgs: ifcfgs.remove(ifcfg) + + # delete the remaining ifcfgs from + deletedSomething = False + + if not failedToGetSettings: + for ifcfg in ifcfgs: + dev = ifcfg[len('ifcfg-'):] + path = "%s/ifcfg-%s" % (sysconfig,dev) + logger.verbose("net:InitInterfaces removing %s %s"%(dev,path)) + os.system("/sbin/ifdown %s" % dev) + deletedSomething=True + os.unlink(path) + + # wait a bit for the one or more ifdowns to have taken effect + if deletedSomething: + time.sleep(2) + + # Process ifcg-$dev changes / additions + newdevs = [] + for (dev, inter) in interfaces.iteritems(): + tmpnam = os.tmpnam() + f = file(tmpnam, "w") + f.write("# Autogenerated by NodeManager/net.py.... do not edit!\n") + if 'DRIVER' in inter: + f.write("# using %s driver for device %s\n" % (inter['DRIVER'],dev)) + f.write('DEVICE="%s"\n' % dev) + + # print the configuration values + for (key, val) in inter.iteritems(): + if key not in ('IFNAME','ALIAS','CFGOPTIONS','DRIVER'): + f.write('%s="%s"\n' % (key, val)) + + # print the configuration specific option values (if any) + if 'CFGOPTIONS' in inter: + cfgoptions = inter['CFGOPTIONS'] + f.write('#CFGOPTIONS are %s\n' % cfgoptions) + for cfgoption in cfgoptions.split(): + key,val = cfgoption.split('=') + key=key.strip() + key=key.upper() + val=val.strip() + f.write('%s="%s"\n' % (key,val)) + f.close() + + # compare whether two files are the same + def comparefiles(a,b): + try: + logger.verbose("net:InitInterfaces comparing %s with %s" % (a,b)) + if not os.path.exists(a): return False + fb = open(a) + buf_a = fb.read() + fb.close() + + if not os.path.exists(b): return False + fb = open(b) + buf_b = fb.read() + fb.close() + + return buf_a == buf_b + except IOError, e: + return False + + path = "%s/ifcfg-%s" % (sysconfig,dev) + if not os.path.exists(path): + logger.verbose('net:InitInterfaces adding configuration for %s' % dev) + # add ifcfg-$dev configuration file + os.rename(tmpnam,path) + os.chmod(path,0644) + newdevs.append(dev) + + elif not comparefiles(tmpnam,path): + logger.verbose('net:InitInterfaces Configuration change for %s' % dev) + logger.verbose('net:InitInterfaces ifdown %s' % dev) + # invoke ifdown for the old configuration + os.system("/sbin/ifdown %s" % dev) + # wait a few secs for ifdown to complete + time.sleep(2) + + logger.log('replacing configuration for %s' % dev) + # replace ifcfg-$dev configuration file + os.rename(tmpnam,path) + os.chmod(path,0644) + newdevs.append(dev) + else: + # tmpnam & path are identical + os.unlink(tmpnam) + + for dev in newdevs: + cfgvariables = {} + fb = file("%s/ifcfg-%s"%(sysconfig,dev),"r") + for line in fb.readlines(): + parts = line.split() + if parts[0][0]=="#":continue + if parts[0].find('='): + name,value = parts[0].split('=') + # clean up name & value + name = name.strip() + value = value.strip() + value = value.strip("'") + value = value.strip('"') + cfgvariables[name]=value + fb.close() + + def getvar(name): + if name in cfgvariables: + value=cfgvariables[name] + value = value.lower() + return value + return '' + + # skip over device configs with ONBOOT=no + if getvar("ONBOOT") == 'no': continue + + # don't bring up slave devices, the network scripts will + # handle those correctly + if getvar("SLAVE") == 'yes': continue + + logger.verbose('net:InitInterfaces bringing up %s' % dev) + os.system("/sbin/ifup %s" % dev) + diff --git a/pyplnet.spec b/pyplnet.spec new file mode 100644 index 0000000..bf443ee --- /dev/null +++ b/pyplnet.spec @@ -0,0 +1,54 @@ +# +# $Id$ +# +%define url $URL$ + +%define name pyplnet +%define version 4.3 +%define taglevel 0 + +%define release %{taglevel}%{?pldistro:.%{pldistro}}%{?date:.%{date}} + +%{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")} + +Summary: PlanetLab Network Configuration library +Name: %{name} +Version: %{version} +Release: %{release} +License: PlanetLab +Group: System Environment/Daemons +Source0: %{name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root + +Vendor: PlanetLab +Packager: PlanetLab Central +Distribution: PlanetLab %{plrelease} +URL: %(echo %{url} | cut -d ' ' -f 2) + +Requires: python >= 2.4 +BuildRequires: python, python-devel + +%description +pyplnet is used to write the network configuration files based on the +configuration data recorded at PLC. + +%prep +%setup -q + +%build +python setup.py build + +%install +rm -rf $RPM_BUILD_ROOT +python setup.py install --skip-build --root "$RPM_BUILD_ROOT" + +%clean +rm -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root,-) +%{python_sitearch}/* + +%changelog +* Tue Dec 2 2008 Daniel Hokka Zakrisson - pyplnet-4.3-1 +- initial release diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..4a9d1f3 --- /dev/null +++ b/setup.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# +# Setup script for pyplnet +# +# Daniel Hokka Zakrisson +# Copyright (C) 2008 The Trustees of Princeton University +# +# $Id$ +# + +import os +from distutils.core import setup, Extension +from distutils.cmd import Command +from distutils.command.sdist import sdist + +extra_dist = ['pyplnet.spec'] + +class my_sdist(sdist): + def add_defaults(self): + sdist.add_defaults(self) + if self.distribution.has_data_files(): + for data in self.distribution.data_files: + self.filelist.extend(data[1]) + self.filelist.extend(extra_dist) + +class bdist_rpmspec(Command): + user_options = [("rpmdef=", None, "define variables")] + def initialize_options(self): + self.rpmdef = None + def finalize_options(self): + pass + def run(self): + saved_dist_files = self.distribution.dist_files[:] + sdist = self.reinitialize_command('sdist') + sdist.formats = ['gztar'] + self.run_command('sdist') + self.distribution.dist_files = saved_dist_files + command = ["rpmbuild", "-tb"] + if self.rpmdef is not None: + command.extend(["--define", self.rpmdef]) + command.append(sdist.get_archive_files()[0]) + print "running '%s'" % "' '".join(command) + if not self.dry_run: + os.spawnvp(os.P_WAIT, "rpmbuild", command) + +setup( + name='pyplnet', + version='4.3', + ext_modules=[ + Extension('sioc', ['sioc.c']), + ], + py_modules=[ + 'plnet', + 'modprobe', + ], + cmdclass={'sdist': my_sdist, 'bdist_rpmspec': bdist_rpmspec}, + ) diff --git a/sioc.c b/sioc.c new file mode 100644 index 0000000..0ddeb31 --- /dev/null +++ b/sioc.c @@ -0,0 +1,160 @@ +/* + * Extension to gather information about network interfaces + * + * Mark Huang + * Copyright (C) 2006 The Trustees of Princeton University + * + * $Id$ + */ + +#include + +/* struct ifreq */ +#include + +/* socket() */ +#include +#include + +/* ioctl() */ +#include + +/* inet_ntoa() */ +#include +#include + +/* ARPHRD_ETHER */ +#include + +/* ETH_ALEN */ +#include + +static PyObject * +gifconf(PyObject *self, PyObject *args) +{ + struct ifconf ifc; + int len; + int s; + PyObject *addrs; + void *buf; + struct ifreq *ifr; + struct sockaddr_in *sin; + + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) + return PyErr_SetFromErrno(PyExc_OSError); + + len = sizeof(struct ifreq); + ifc.ifc_len = 0; + ifc.ifc_req = NULL; + + do { + len *= 2; + buf = realloc(ifc.ifc_req, len); + if (!buf) + break; + ifc.ifc_len = len; + ifc.ifc_req = buf; + if (ioctl(s, SIOCGIFCONF, &ifc) < 0) + break; + } while (ifc.ifc_len >= len); + + close(s); + + addrs = Py_BuildValue("{}"); + + for (ifr = ifc.ifc_req, len = ifc.ifc_len; len > 0; ifr++, len -= sizeof(struct ifreq)) { + sin = (struct sockaddr_in *) &ifr->ifr_addr; + PyDict_SetItem(addrs, + Py_BuildValue("s", ifr->ifr_name), + Py_BuildValue("s", inet_ntoa(sin->sin_addr))); + } + + if (ifc.ifc_req) + free(ifc.ifc_req); + + return addrs; +} + +static PyObject * +gifaddr(PyObject *self, PyObject *args) +{ + const char *name; + struct ifreq ifr; + int s; + struct sockaddr_in *sin; + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, name, IFNAMSIZ); + + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) + return PyErr_SetFromErrno(PyExc_OSError); + + if (ioctl(s, SIOCGIFADDR, &ifr) < 0) { + close(s); + return PyErr_SetFromErrno(PyExc_OSError); + } + + close(s); + + sin = (struct sockaddr_in *) &ifr.ifr_addr; + return Py_BuildValue("s", inet_ntoa(sin->sin_addr)); +} + +static PyObject * +gifhwaddr(PyObject *self, PyObject *args) +{ + const char *name; + struct ifreq ifr; + int s; + char mac[sizeof(ifr.ifr_hwaddr.sa_data) * 3], *c; + int len, i; + + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, name, IFNAMSIZ); + + if ((s = socket(PF_INET, SOCK_DGRAM, 0)) < 0) + return PyErr_SetFromErrno(PyExc_OSError); + + if (ioctl(s, SIOCGIFHWADDR, &ifr) < 0) { + close(s); + return PyErr_SetFromErrno(PyExc_OSError); + } + + close(s); + + switch (ifr.ifr_hwaddr.sa_family) { + case ARPHRD_ETHER: + len = ETH_ALEN; + break; + default: + len = sizeof(ifr.ifr_hwaddr.sa_data); + break; + } + + for (i = 0, c = mac; i < len; i++) { + if (i) + c += sprintf(c, ":"); + c += sprintf(c, "%02X", (unsigned char)(ifr.ifr_hwaddr.sa_data[i] & 0xFF)); + } + + return Py_BuildValue("s", mac); +} + +static PyMethodDef methods[] = { + { "gifconf", gifconf, METH_VARARGS, "Get all interface addresses" }, + { "gifaddr", gifaddr, METH_VARARGS, "Get interface address" }, + { "gifhwaddr", gifhwaddr, METH_VARARGS, "Get interface hardware address" }, + { NULL, NULL, 0, NULL } +}; + +PyMODINIT_FUNC +initsioc(void) +{ + Py_InitModule("sioc", methods); +} -- 2.43.0