Changes for 2.6.32 kernel
[nodemanager-topo.git] / create-topo-attributes.py
index 8f26ff8..78d965e 100755 (executable)
 # $URL$
 
 """
-Scan the VINI Central database and create topology "rspec" attributes for
+Scan the VINI Central database and create topology "rspec" tags for
 slices that have an EGRE key.  This script to be run from a cron job.
 """
 
 import string
 import socket
+from topology import links, bwlimits
 
-"""
-Map sites to adjacent sites in topology.  Generated manually :-(
-A site is adjacent to itself.
-"""
-adjacencies = {
-    1: [1], 2: [2,12], 3: [3], 4: [4,5,6,7,9,10], 5: [4,5,6,8], 
-    6: [4,5,6,10], 7: [4,7,8], 8: [5,7,8], 9: [4,9,10], 10: [4,6,9,10], 
-    11: [11,13,15,16,17], 12: [2,12,13], 13: [11,12,13,15], 14: [14], 
-    15: [11,13,15,19], 16: [11,16], 17: [11,17,19,22], 18: [18], 
-    19: [15,17,19,20], 20: [19,20,21,22], 21: [20,21,22], 22: [17,20,21,22]
-    }
+class Node:
+    def __init__(self, node):
+        self.id = node['node_id']
+        self.hostname = node['hostname']
+        self.shortname = self.hostname.replace('.vini-veritas.net', '')
+        self.site_id = node['site_id']
+        self.ipaddr = socket.gethostbyname(self.hostname)
 
-"""
-Test whether two sites are adjacent to each other in the adjacency graph.
-"""
-def is_adjacent(s1, s2):
-    set1 = set(adjacencies[s1])
-    set2 = set(adjacencies[s2])
-
-    if s1 in set2 and s2 in set1:
-        return True
-    elif not s1 in set2 and not s2 in set1:
-        return False
-    else:
-        raise Exception("Adjacency mismatch, sites %d and %d." % (s1, s2))
-
-
-"""
-Check the adjacency graph for discrepancies.
-"""
-def check_adjacencies():
-    for site in adjacencies:
-        for adj in adjacencies[site]:
-            try:
-                test = is_adjacent(site, adj)
-            except Exception, e:
-                print "Error: ", e, " Fix adjacencies!"
-    return
-
-
-def get_site(nodeid):
-    if nodes[nodeid]:
-        return nodes[nodeid]['site_id']
-    raise Exception("Nodeid %s not found." % nodeid)
+    def get_link_id(self, remote):
+        if self.id < remote.id:
+            link = (self.id<<7) + remote.id
+        else:
+            link = (remote.id<<7) + self.id
+        return link
+        
+    def get_iface_id(self, remote):
+        if self.id < remote.id:
+            iface = 1
+        else:
+            iface = 2
+        return iface
+    
+    def get_virt_ip(self, remote):
+        link = self.get_link_id(remote)
+        iface = self.get_iface_id(remote)
+        first = link >> 6
+        second = ((link & 0x3f)<<2) + iface
+        return "192.168.%d.%d" % (first, second)
 
+    def get_virt_net(self, remote):
+        link = self.get_link_id(remote)
+        first = link >> 6
+        second = (link & 0x3f)<<2
+        return "192.168.%d.%d/30" % (first, second)
+        
+    def get_site(self, sites):
+        return sites[self.site_id]
+            
+    
+    # What nodes in the set of node_ids are adjacent to this one?
+    def adjacent_nodes(self, sites, nodes, node_ids):
+        mysite = self.get_site(sites)
+        adj_ids = mysite.adj_node_ids.intersection(node_ids)
+        adj_nodes = []
+        for id in adj_ids:
+            adj_nodes.append(nodes[id])
+        return adj_nodes
+    
+    def init_rspecs(self):
+        self.rspecs = []
+        
+    def add_rspec(self, remote, bw):
+        my_ip = self.get_virt_ip(remote)
+        remote_ip = remote.get_virt_ip(self)
+        net = self.get_virt_net(remote)
+        rspec = remote.id, remote.ipaddr, bw, my_ip, remote_ip, net
+        self.rspecs.append(rspec)
 
-def get_ipaddr(nodeid):
-    if nodes[nodeid]:
-        return socket.gethostbyname(nodes[nodeid]['hostname'])
-    raise Exception("Nodeid %s not found." % nodeid)
+        
+class Site:
+    def __init__(self, site):
+        self.id = site['site_id']
+        self.node_ids = site['node_ids']
+        self.adj_site_ids = set()
+        self.adj_node_ids = set()
 
+    def get_sitenodes(self, nodes):
+        n = []
+        for i in self.node_ids:
+            n.append(nodes[i])
+        return n
+    
+    def add_adjacency(self, site):
+        self.adj_site_ids.add(site.id)
+        for n in site.node_ids:
+            self.adj_node_ids.add(n)
+        
+    
+class Slice:
+    def __init__(self, slice):
+        self.id = slice['slice_id']
+        self.name = slice['name']
+        self.node_ids = set(slice['node_ids'])
+        self.slice_tag_ids = slice['slice_tag_ids']
+    
+    def get_tag(self, tagname, slicetags, node = None):
+        for i in self.slice_tag_ids:
+            tag = slicetags[i]
+            if tag.tagname == tagname:
+                if (not node) or (node.id == tag.node_id):
+                    return tag
+        else:
+            return None
+        
+    def get_nodes(self, nodes):
+        n = []
+        for id in self.node_ids:
+            n.append(nodes[id])
+        return n
+             
+    
+    # Add a new slice tag   
+    def add_tag(self, tagname, value, slicetags, node = None):
+        record = {'slice_tag_id':None, 'slice_id':self.id, 'tagname':tagname, 'value':value}
+        if node:
+            record['node_id'] = node.id
+        else:
+            record['node_id'] = None
+        tag = Slicetag(record)
+        slicetags[tag.id] = tag
+        self.slice_tag_ids.append(tag.id)
+        tag.changed = True       
+        tag.updated = True
+        return tag
+    
+    # Update a slice tag if it exists, else add it             
+    def update_tag(self, tagname, value, slicetags, node = None):
+        tag = self.get_tag(tagname, slicetags, node)
+        if tag and tag.value == value:
+            value = "no change"
+        elif tag:
+            tag.value = value
+            tag.changed = True
+        else:
+            tag = self.add_tag(tagname, value, slicetags, node)
+        tag.updated = True
+            
+    def assign_egre_key(self, slicetags):
+        if not self.get_tag('egre_key', slicetags):
+            try:
+                key = free_egre_key(slicetags)
+                self.update_tag('egre_key', key, slicetags)
+            except:
+                # Should handle this case...
+                pass
+        return
+            
+    def turn_on_netns(self, slicetags):
+        tag = self.get_tag('netns', slicetags)
+        if (not tag) or (tag.value != '1'):
+            self.update_tag('netns', '1', slicetags)
+        return
+   
+    def turn_off_netns(self, slicetags):
+        tag = self.get_tag('netns', slicetags)
+        if tag and (tag.value != '0'):
+            tag.delete()
+        return
+    
+    def add_cap_net_admin(self, slicetags):
+        tag = self.get_tag('capabilities', slicetags)
+        if tag:
+            caps = tag.value.split(',')
+            for cap in caps:
+                if cap == "CAP_NET_ADMIN":
+                    return
+            else:
+                newcaps = "CAP_NET_ADMIN," + tag.value
+                self.update_tag('capabilities', newcaps, slicetags)
+        else:
+            self.add_tag('capabilities', 'CAP_NET_ADMIN', slicetags)
+        return
+    
+    def remove_cap_net_admin(self, slicetags):
+        tag = self.get_tag('capabilities', slicetags)
+        if tag:
+            caps = tag.value.split(',')
+            newcaps = []
+            for cap in caps:
+                if cap != "CAP_NET_ADMIN":
+                    newcaps.append(cap)
+            if newcaps:
+                value = ','.join(newcaps)
+                self.update_tag('capabilities', value, slicetags)
+            else:
+                tag.delete()
+        return
 
-def get_sitenodes(siteid):
-    if sites[siteid]:
-        return sites[siteid]['node_ids']
-    raise Exception("Siteid %s not found." % siteid)
+    # Update the vsys/setup-link and vsys/setup-nat slice tags.
+    def add_vsys_tags(self, slicetags):
+        link = nat = False
+        for i in self.slice_tag_ids:
+            tag = slicetags[i]
+            if tag.tagname == 'vsys':
+                if tag.value == 'setup-link':
+                    link = True
+                elif tag.value == 'setup-nat':
+                    nat = True
+        if not link:
+            self.add_tag('vsys', 'setup-link', slicetags)
+        if not nat:
+            self.add_tag('vsys', 'setup-nat', slicetags)
+        return
 
 
-"""
-Find the IP address assigned to a virtual interface in the topology
-(for creating /etc/hosts)
-"""
-def get_virt_ip(myid, nodeid):
-    if myid < nodeid:
-        virtip = "10.%d.%d.2" % (myid, nodeid)
-    else:
-        virtip = "10.%d.%d.3" % (nodeid, myid)
-    return virtip
+class Slicetag:
+    newid = -1 
+    def __init__(self, tag):
+        self.id = tag['slice_tag_id']
+        if not self.id:
+            # Make one up for the time being...
+            self.id = Slicetag.newid
+            Slicetag.newid -= 1
+        self.slice_id = tag['slice_id']
+        self.tagname = tag['tagname']
+        self.value = tag['value']
+        self.node_id = tag['node_id']
+        self.updated = False
+        self.changed = False
+        self.deleted = False
+    
+    # Mark a tag as deleted
+    def delete(self):
+        self.deleted = True
+        self.updated = True
+    
+    def write(self, slices, nodes, dryrun):
+        if not dryrun:
+            if self.changed:
+                if int(self.id) > 0:
+                    UpdateSliceTag(self.id, self.value)
+                else:
+                    AddSliceTag(self.slice_id, self.tagname, self.value, self.node_id)
+            elif self.deleted and int(self.id) > 0:
+                try:
+                    DeleteSliceTag(self.id)
+                except:
+                    print "[%s] %s: could not delete" % (self.id, self.tagname)
+        else:
+            try:
+                slice = slices[self.slice_id].name
+            except:
+                return
+            if self.node_id:
+                node = nodes[tag.node_id].hostname
+            if self.updated:
+                if self.deleted:
+                    self.value = "deleted"
+                elif not self.changed:
+                    self.value = "no change"
+                if int(self.id) < 0:
+                    self.id = "new"
+                if self.node_id:
+                    print "[%s] %s: %s (%s, %s)" % (self.id, self.tagname, self.value, slice, node)
+                else:
+                    print "[%s] %s: %s (%s)" % (self.id, self.tagname, self.value, slice)
 
 
 """
-Create a dictionary of site records keyed by site ID
+Create a dictionary of site objects keyed by site ID
 """
 def get_sites():
     tmp = []
     for site in GetSites():
-        t = site['site_id'], site
+        t = site['site_id'], Site(site)
         tmp.append(t)
     return dict(tmp)
 
 
 """
-Create a dictionary of node records keyed by node ID
+Create a dictionary of node objects keyed by node ID
 """
 def get_nodes():
     tmp = []
     for node in GetNodes():
-        t = node['node_id'], node
+        t = node['node_id'], Node(node)
         tmp.append(t)
     return dict(tmp)
 
+"""
+Create a dictionary of slice objects keyed by slice ID
+"""
+def get_slices():
+    tmp = []
+    for slice in GetSlices():
+        t = slice['slice_id'], Slice(slice)
+        tmp.append(t)
+    return dict(tmp)
+
+"""
+Create a dictionary of slicetag objects keyed by slice tag ID
+"""
+def get_slice_tags():
+    tmp = []
+    for tag in GetSliceTags():
+        t = tag['slice_tag_id'], Slicetag(tag)
+        tmp.append(t)
+    return dict(tmp)
     
-check_adjacencies()
+"""
+Find a free EGRE key
+"""
+def free_egre_key(slicetags):
+    used = set()
+    for i in slicetags:
+        tag = slicetags[i]
+        if tag.tagname == 'egre_key':
+            used.add(int(tag.value))
+                
+    for i in range(1, 256):
+        if i not in used:
+            key = i
+            break
+    else:
+        raise KeyError("No more EGRE keys available")
+        
+    return "%s" % key
+   
+# For debugging
+dryrun = 0
 
-""" Need global topology information """
 sites = get_sites()
 nodes = get_nodes()
+slices = get_slices()
+slicetags = get_slice_tags()
+
+# Add adjacencies
+for (a, b) in links:
+    sites[a].add_adjacency(sites[b])
+    sites[b].add_adjacency(sites[a])  
 
-for slice in GetSlices():
-    """ Create dictionary of the slice's attributes """
-    attrs ={}
-    topo_attr = {}
-    for attribute in GetSliceAttributes(slice['slice_attribute_ids']):
-        attrs[attribute['name']] = attribute['slice_attribute_id']
-        if attribute['name'] == 'topo_rspec' and attribute['node_id']:
-            topo_attr[attribute['node_id']] = attribute['slice_attribute_id']
+for i in slices:
+    slice = slices[i]
+    tag = slice.get_tag('vini_topo', slicetags)
+    if tag:
+        topo_type = tag.value
+    else:
+        topo_type = None
+    
+    """
+    Valid values of topo_type: 
+    'vsys':   Use vsys topology scripts to set up virtual links
+    'iias':   Automatically create a virtual topology mirroring the physical one
+    'manual': Don't modify the topo_rspec tags if present
+    None:     No virtual topology
+    """
+    if topo_type in ['vsys', 'iias', 'manual']:
+        slice.assign_egre_key(slicetags)
+        slice.turn_on_netns(slicetags)
+        slice.add_cap_net_admin(slicetags)
+    else:
+        # Let them keep EGRE key for now...
+        slice.turn_off_netns(slicetags)
+        slice.remove_cap_net_admin(slicetags)
+            
+    # Add vsys/setup-link and vsys/setup-nat
+    if topo_type == 'vsys' and slice.get_tag('egre_key', slicetags):
+        slice.add_vsys_tags(slicetags)
+        
+    if topo_type == 'iias' and slice.get_tag('egre_key', slicetags):
+        if dryrun:
+            print "Building virtual topology for %s" % slice.name
             
-    if 'egre_key' in attrs:
-        #print "Virtual topology for %s:" % slice['name']
-        slicenodes = set(slice['node_ids'])
+        if slice.name in bwlimits:
+            bw = bwlimits[slice.name]
+        else:
+            bw = "1Mbit"
+
         hosts = "127.0.0.1\t\tlocalhost\n"
         """
-        For each node in the slice, check whether nodes at adjacent sites
-        are also in the slice's node set.  If so, add a virtual link to 
-        the rspec.  
+        For each node in the slice, check whether the slice is running on any
+        adjacent nodes.  For each pair of adjacent nodes, add to nodes' rspecs.
         """
-        for node in slicenodes:
-            topo = []
-            for adj in adjacencies[get_site(node)]:
-                for adj_node in get_sitenodes(adj):
-                    if node != adj_node and adj_node in slicenodes:
-                        link = adj_node, get_ipaddr(adj_node), "1Mbit"
-                        topo.append(link)
-                        shortname = nodes[node]['hostname'].replace('.vini-veritas.net', '')
-                        hosts += "%s\t\t%s\n" % (get_virt_ip(node, adj_node),
-                                                  shortname)
-            topo_str = "%s" % topo
-            #print node, topo_str
-            if node in topo_attr:
-                UpdateSliceAttribute(topo_attr[node], topo_str)
-                del topo_attr[node]
-            else:
-                id = slice['slice_id']
-                AddSliceAttribute(id, 'topo_rspec', topo_str, node)
+        for node in slice.get_nodes(nodes):
+            node.init_rspecs()
+            adj_nodes = node.adjacent_nodes(sites, nodes, slice.node_ids)
+            for adj in adj_nodes:
+                node.add_rspec(adj, bw)
+                hosts += "%s\t\t%s\n" % (node.get_virt_ip(adj), node.shortname)
+            if node.rspecs:
+                topo_str = "%s" % node.rspecs
+                slice.update_tag('topo_rspec', topo_str, slicetags, node)
+                    
+        slice.update_tag('hosts', hosts, slicetags)
+    else:
+        if dryrun:
+            print "Slice %s not using IIAS" % slice.name
 
-        #print hosts
-        if 'hosts' in attrs:
-            UpdateSliceAttribute(attrs['hosts'], hosts)
-        else:
-            id = slice['slice_id']
-            AddSliceAttribute(id, 'hosts', hosts)
-    #else:
-        #print "No EGRE key for %s" % slice['name']
-
-    """ Remove old topo_rspec entries """
-    for node in topo_attr:
-        DeleteSliceAttribute(topo_attr[node])
+    if topo_type == 'manual' and slice.get_tag('egre_key', slicetags):
+        for node in slice.get_nodes(nodes):
+            topo_tag = slice.get_tag('topo_rspec', slicetags, node)
+            if topo_tag:
+                topo_tag.updated = True
+            
+# Update the tag values in the database
+for i in slicetags:
+    tag = slicetags[i]
+    if (tag.tagname == 'topo_rspec' or tag.tagname == 'hosts') and not tag.updated:
+        tag.delete()
+    tag.write(slices, nodes, dryrun)