Changes for 2.6.32 kernel
[nodemanager-topo.git] / topo.py
diff --git a/topo.py b/topo.py
index 88327fd..2f7b6dd 100755 (executable)
--- a/topo.py
+++ b/topo.py
@@ -10,11 +10,19 @@ import logger
 import subprocess
 import sioc
 import re
 import subprocess
 import sioc
 import re
+import vserver
+import os
+from time import strftime
+import socket
 
 
-dryrun=0
-setup_link_cmd="/usr/share/vini/setup-egre-link"
-teardown_link_cmd="/usr/share/vini/teardown-egre-link"
+dryrun = 0
+vinidir = "/usr/share/vini/"
+setup_link_cmd = vinidir + "setup-egre-link"
+teardown_link_cmd = vinidir + "teardown-egre-link"
+setup_nat_cmd = vinidir + "setup-nat"
+teardown_nat_cmd = vinidir + "teardown-nat"
 ifaces = {}
 ifaces = {}
+old_ifaces = {}
 
 def run(cmd):
     if dryrun:
 
 def run(cmd):
     if dryrun:
@@ -23,9 +31,43 @@ def run(cmd):
     else:
         return subprocess.call(cmd, shell=True);
 
     else:
         return subprocess.call(cmd, shell=True);
 
+"""
+From old pyplnet, former semantics needed for VINI
+"""
+def gifconf():
+    try:
+        interfaces = os.listdir("/sys/class/net")
+    except:
+        interfaces = []
+    s = None
+    ret = {}
+    try:
+        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
+        for interface in interfaces:
+            try:
+                ifreq = fcntl.ioctl(s.fileno(), SIOCGIFADDR,
+                                    struct.pack("16sH14x", interface, socket.AF_INET))
+                (family, ip) = struct.unpack(SIOCGIFADDR_struct, ifreq)
+                if family == socket.AF_INET:
+                    ret[interface] = _format_ip(ip)
+                else:
+                    raise Exception
+            except:
+                ret[interface] = "0.0.0.0"
+    finally:
+        if s is not None:
+            s.close()
+    return ret
+
+"""
+Subnet used for virtual interfaces by setup-egre-link script
+"""
+def iias_network():
+    return "192.168.0.0 255.255.0.0"
+
 
 """
 
 """
-Check for existence of interface a<key>x<nodeid>
+Check for existence of interface d<key>x<nodeid>
 """
 def virtual_link(key, nodeid):
     name = "d%sx%s" % (key, nodeid)
 """
 def virtual_link(key, nodeid):
     name = "d%sx%s" % (key, nodeid)
@@ -34,28 +76,80 @@ def virtual_link(key, nodeid):
     else:
         return False
 
     else:
         return False
 
-
 """
 Create a "virtual link" for slice between here and nodeid.
 The key is used to create the EGRE tunnel.
 """
 """
 Create a "virtual link" for slice between here and nodeid.
 The key is used to create the EGRE tunnel.
 """
-def setup_virtual_link(slice, key, rate, myid, nodeid, ipaddr):
-    if myid < nodeid:
-        virtip = "10.%d.%d.2" % (myid, nodeid)
-    else:
-        virtip = "10.%d.%d.3" % (nodeid, myid)
-        
-    run(setup_link_cmd + " %s %s %s %s %s %s" % (slice, nodeid, ipaddr, 
-                                                 key, rate, virtip))
+def setup_virtual_link(slice, key, rate, myid, nodeid, ipaddr, virtip, vnet):
+    logger.log("%s: Set up virtual link to node %s" % (slice, nodeid))
+    run(setup_link_cmd + " %s %s %s %s %s %s %s" % (slice, nodeid, ipaddr, 
+                                                 key, rate, virtip, vnet))
     return
 
 
 """
 Tear down the "virtual link" for slice between here and nodeid.
 """
     return
 
 
 """
 Tear down the "virtual link" for slice between here and nodeid.
 """
-def teardown_virtual_link(slice, key, nodeid):
-    logger.log("Tear down virtual link to node %d" % nodeid)
-    run(teardown_link_cmd + " %s %s %s" % (slice, nodeid, key))
+def teardown_virtual_link(key, nodeid):
+    logger.log("topo: Tear down virtual link %sx%s" % (key, nodeid))
+    run(teardown_link_cmd + " %s %s" % (nodeid, key))
+    return
+
+
+"""
+Called for all active virtual link interfaces, so they won't be cleaned up.
+"""
+def refresh_virtual_link(nodeid, key):
+    name = "d%sx%s" % (key, nodeid)
+    if name in old_ifaces:
+        del old_ifaces[name]
+    return
+
+
+"""
+IP address of the NAT interface created inside the slice by the
+setup-nat script.
+"""
+def nat_inner_ip(key):
+    return "10.0.%s.2" % key
+
+
+"""
+Check for existence of interface natx<key>
+"""
+def nat_exists(key):
+    name = "natx%s" % key
+    if name in ifaces:
+        return True
+    else:
+        return False
+
+
+"""
+Create a NAT interface inside the sliver.  
+"""
+def setup_nat(slice, myid, key):
+    logger.log("%s: Set up NAT" % slice)
+    run(setup_nat_cmd + " %s %s %s" % (slice, myid, key))
+    return
+
+
+"""
+Tear down the NAT interface identified by key
+"""
+def teardown_nat(key):
+    logger.log("topo: Tear down NAT %s" % key)
+    run(teardown_nat_cmd + " %s" % key)
+    return
+
+
+"""
+Called for all active NAT interfaces, so they won't be cleaned up.
+"""
+def refresh_nat(key):
+    name = "natx%s" % (key)
+    if name in old_ifaces:
+        del old_ifaces[name]
     return
 
 
     return
 
 
@@ -63,14 +157,21 @@ def teardown_virtual_link(slice, key, nodeid):
 Clean up old virtual links (e.g., to nodes that have been deleted 
 from the slice).
 """
 Clean up old virtual links (e.g., to nodes that have been deleted 
 from the slice).
 """
-def clean_up_old_virtual_links(slice, key, nodelist):
-    pattern = "d%sx(.*)" % key
-    for iface in ifaces:
-        m = re.match(pattern, iface)
+def clean_up_old_virtual_links():
+    pattern1 = "d(.*)x(.*)"
+    pattern2 = "natx(.*)"
+    for iface in old_ifaces:
+        m = re.match(pattern1, iface)
         if m:
         if m:
-            node = m.group(1)
-            if not node in nodelist:
-                teardown_virtual_link(slice, key, node)
+            key = m.group(1)
+            node = m.group(2)
+            teardown_virtual_link(key, node)
+
+        m = re.match(pattern2, iface)
+        if m:
+            key = m.group(1)
+            teardown_nat(key)
+    return
 
 
 """
 
 
 """
@@ -83,38 +184,276 @@ def convert_topospec_to_list(rspec):
 """
 Update virtual links for the slice
 """
 """
 Update virtual links for the slice
 """
-def update(slice, myid, topospec, key):
+def update_links(slice, myid, topospec, key, netns):
     topolist = convert_topospec_to_list(topospec)
     topolist = convert_topospec_to_list(topospec)
-    nodelist=[]
-    for (nodeid,ipaddr,rate) in topolist:
-        nodelist.append(nodeid)
+    for (nodeid, ipaddr, rate, myvirtip, remvirtip, virtnet) in topolist:
         if not virtual_link(key, nodeid):
         if not virtual_link(key, nodeid):
-            setup_virtual_link(slice, key, rate, myid, nodeid, ipaddr)
+            if netns:
+                setup_virtual_link(slice, key, rate, myid, nodeid, 
+                                   ipaddr, myvirtip, virtnet)
         else:
         else:
-            logger.log("Virtual link to node %s exists" % nodeid)
+            logger.log("%s: virtual link to node %s exists" % (slice, nodeid))
+            refresh_virtual_link(nodeid, key)
+
+
+"""
+Update NAT interface for the slice
+"""
+def update_nat(slice, myid, key, netns):
+    if not nat_exists(key):
+        if netns:
+            setup_nat(slice, myid, key)
+    else:
+        logger.log("%s: NAT exists" % slice)
+        refresh_nat(key)
+
+
+"""
+Write /etc/vservers/<slicename>/spaces/net.  
+Restart the vserver if there are any changes.
+"""
+def write_spaces_net(slicename, value):
+    SLICEDIR="/etc/vservers/%s/" % slicename
+    SPACESDIR="%s/spaces/" % SLICEDIR
+    FILENAME="%s/net" % SPACESDIR
+    if os.path.exists(SLICEDIR):
+        if not os.path.exists(SPACESDIR):
+            try:
+                os.mkdir(SPACESDIR)
+            except os.error:
+                logger.log("topo: could not create %s\n" % SPACESDIR)
+                return
+            
+        if os.path.exists(FILENAME) != value:
+            sliver = vserver.VServer(slicename)
+            
+            sliver.stop()
+
+            if value:
+                STATUS="ON"
+                f = open(FILENAME, "w")
+                f.close()
+            else:
+                STATUS="OFF"
+                os.remove(FILENAME)
+
+            sliver.start()
+
+            logger.log("%s: network namespace %s\n" % (slicename, STATUS))
+
+
+"""
+Generate information for each interface in the sliver, in order to configure
+Quagga.
+"""
+def get_ifaces(hostname, myid, topospec, key):
+    ifaces = {}
+    topolist = convert_topospec_to_list(topospec)
+    for (nodeid, ipaddr, rate, myvirtip, remvirtip, virtnet) in topolist:
+        name = "a%sx%s" % (key, nodeid)
+        ifaces[name] = {}
+        ifaces[name]['remote-ip'] = remvirtip
+        ifaces[name]['local-ip'] = myvirtip
+        ifaces[name]['network'] = virtnet
+        ifaces[name]['short-name'] = hostname.replace('.vini-veritas.net', '')
+    return ifaces
+
+
+def write_header(f, myname, password):
+    f.write ("""! Configuration for %s
+! Generated at %s
+
+hostname %s
+password %s
+
+""" % (myname, strftime("%Y-%m-%d %H:%M:%S"), myname, password))
+    return
+
+
+"""
+IP address of NAT gateway to outside world
+"""
+def nat_gw(key):
+    return "10.0.%s.1" %  key
+
+"""
+IP address of the NAT interface inside the slice
+"""
+def nat_inner(key):
+    return "10.0.%s.2" % key
+
+
+"""
+Write zebra.conf file for Quagga
+"""
+def write_zebra(filename, myname, ifaces, myid, key):
+    f = open(filename, 'w')
+    password = "zebra"
+    write_header(f, myname, password)
+
+    f.write ("enable password %s\n" % password)
+
+    for name in ifaces:
+        f.write ("""!     
+interface %s
+link-detect
+""" %  name)
+
+    f.write ("""!
+access-list vty permit 127.0.0.1/32
+!
+line vty
+!
+""")
+    f.close()
+    return
+
+
+"""
+Write ospfd.conf file for Quagga.  
+"""
+def write_ospf(filename, myname, ifaces):
+    f = open(filename, 'w')
+    password = "zebra"
+    write_header(f, myname, password)
+    name = None
+
+    for name in ifaces:
+        f.write ("""!
+     interface %s
+     ip ospf cost 10
+     ip ospf hello-interval 5
+     ip ospf dead-interval 10
+     ip ospf network non-broadcast
+""" % name)
+
+    if name:
+        f.write ("""!
+     router ospf
+     ospf router-id %s
+""" % ifaces[name]['local-ip'])
 
 
-    clean_up_old_virtual_links(slice, key, nodelist)
+    for name in ifaces:
+        f.write ("     neighbor %s\n" % ifaces[name]['remote-ip'])
 
 
+    for name in ifaces:
+        net = ifaces[name]['network']
+        f.write ("     network %s area 0\n" % net)
 
 
-def start(options, config):
+    f.write("""     redistribute kernel
+!
+access-list vty permit 127.0.0.1/32
+!
+line vty
+""")
+    return
+
+
+"""
+Write config files directly into the slice's file system.
+"""
+def update_quagga_configs(slicename, hostname, myid, topo, key, netns):
+    ifaces = get_ifaces(hostname, myid, topo, key)
+
+    quagga_dir = "/vservers/%s/etc/quagga/" % slicename
+    if not os.path.exists(quagga_dir):
+        try:
+            os.mkdir(quagga_dir)
+        except os.error:
+            logger.log("topo: could not create %s\n" % quagga_dir)
+            return
+
+    write_zebra(quagga_dir + "zebra.conf.generated", hostname, ifaces, 
+                myid, key)
+    write_ospf(quagga_dir + "ospfd.conf.generated", hostname, ifaces)
+
+    return
+
+
+"""
+Write /etc/hosts in the sliver
+"""
+def update_hosts(slicename, hosts):
+    hosts_file = "/vservers/%s/etc/hosts" % slicename
+    f = open(hosts_file, 'w')
+    f.write(hosts)
+    f.close()
+    return
+
+"""
+Write /etc/vini/egre-keys.txt, used by vsys topo scripts
+"""
+def write_egre_keys(slicekeys):
+    vini_dir = "/etc/vini" 
+    if not os.path.exists(vini_dir):
+        try:
+            os.mkdir(vini_dir)
+        except os.error:
+            logger.log("topo: could not create %s\n" % vini_dir)
+            return
+    keys_file = "%s/egre-keys.txt" % vini_dir
+    f = open(keys_file, 'w')
+    for slice in slicekeys:
+        f.write("%s %s\n" % (slice, slicekeys[slice]))
+    f.close()
+    return
+
+
+"""
+Executed on NM startup
+"""
+def start():
+    # Should be taken care of by /etc/sysctl.conf, but it doesn't hurt...
+    run ("echo 1 > /proc/sys/net/ipv4/ip_forward")
     pass
 
 
 """
 Update the virtual links for a sliver if it has a 'netns' attribute,
 an 'egre_key' attribute, and a 'topo_rspec' attribute.
     pass
 
 
 """
 Update the virtual links for a sliver if it has a 'netns' attribute,
 an 'egre_key' attribute, and a 'topo_rspec' attribute.
+
+Creating the virtual link depends on the contents of 
+/etc/vservers/<slice>/spaces/net.  Update this first.
 """
 """
-def GetSlivers(data):
-    global ifaces
-    ifaces = sioc.gifconf()
+def GetSlivers(data, config = None, plc = None):
+    global ifaces, old_ifaces
+    ifaces = old_ifaces = gifconf()
 
 
+    slicekeys = {}
     for sliver in data['slivers']:
         attrs = {}
     for sliver in data['slivers']:
         attrs = {}
-        for attribute in sliver['attributes']:
-            attrs[attribute['name']] = attribute['value']
-        if 'netns' in attrs and 'egre_key' in attrs and 'topo_rspec' in attrs:
-            if attrs['netns'] > 0:
-                logger.log("Update topology for slice %s" % sliver['name'])
-                update(sliver['name'], data['node_id'], 
-                       attrs['topo_rspec'], attrs['egre_key'])
+        for tag in sliver['attributes']:
+            attrs[tag['tagname']] = tag['value']
+            if tag['tagname'] == 'egre_key':
+                slicekeys[sliver['name']] = tag['value']
+                
+
+        if vserver.VServer(sliver['name']).is_running():
+            if 'netns' in attrs:
+                netns = int(attrs['netns'])
+            else:
+                netns = 0
+            write_spaces_net(sliver['name'], netns)
+
+        if vserver.VServer(sliver['name']).is_running():
+            if 'egre_key' in attrs:
+                logger.log("topo: Update slice %s" % sliver['name'])
+                update_nat(sliver['name'], data['node_id'], attrs['egre_key'],
+                           netns)
+                if 'topo_rspec' in attrs:
+                    update_links(sliver['name'], data['node_id'], 
+                                attrs['topo_rspec'], attrs['egre_key'], netns)
+                    update_quagga_configs(sliver['name'], data['hostname'],
+                                data['node_id'], attrs['topo_rspec'], 
+                                attrs['egre_key'], netns)
+            if 'hosts' in attrs:
+                update_hosts(sliver['name'], attrs['hosts'])
+        else:
+            logger.log("topo: sliver %s not running yet. Deferring." % \
+                           sliver['name'])
+
+    clean_up_old_virtual_links()
+    write_egre_keys(slicekeys)
+    return
+