Add trunk version.
authorDaniel Hokka Zakrisson <dhokka@cs.princeton.edu>
Wed, 3 Dec 2008 17:19:02 +0000 (17:19 +0000)
committerDaniel Hokka Zakrisson <dhokka@cs.princeton.edu>
Wed, 3 Dec 2008 17:19:02 +0000 (17:19 +0000)
modprobe.py [new file with mode: 0644]
plnet.py [new file with mode: 0644]
pyplnet.spec [new file with mode: 0644]
setup.py [new file with mode: 0644]
sioc.c [new file with mode: 0644]

diff --git a/modprobe.py b/modprobe.py
new file mode 100644 (file)
index 0000000..d7fa2e3
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..bf443ee
--- /dev/null
@@ -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 <support@planet-lab.org>
+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 <daniel@hozac.com> - pyplnet-4.3-1
+- initial release
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..4a9d1f3
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,57 @@
+#!/usr/bin/python
+#
+# Setup script for pyplnet
+#
+# Daniel Hokka Zakrisson <daniel@hozac.com>
+# 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 (file)
index 0000000..0ddeb31
--- /dev/null
+++ b/sioc.c
@@ -0,0 +1,160 @@
+/*
+ * Extension to gather information about network interfaces
+ *
+ * Mark Huang <mlhuang@cs.princeton.edu>
+ * Copyright (C) 2006 The Trustees of Princeton University
+ *
+ * $Id$
+ */
+
+#include <Python.h>
+
+/* struct ifreq */
+#include <net/if.h>
+
+/* socket() */
+#include <sys/types.h>
+#include <sys/socket.h>
+
+/* ioctl() */
+#include <sys/ioctl.h>
+
+/* inet_ntoa() */
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+/* ARPHRD_ETHER */
+#include <net/if_arp.h>
+
+/* ETH_ALEN */
+#include <net/ethernet.h>
+
+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);
+}