Add 'manual' topology mode for manually specifying rspecs
[nodemanager-topo.git] / create-topo-attributes.py
1 # $Id$
2 # $URL$
3
4 """
5 Scan the VINI Central database and create topology "rspec" tags for
6 slices that have an EGRE key.  This script to be run from a cron job.
7 """
8
9 import string
10 import socket
11 from topology import links
12
13 class Node:
14     def __init__(self, node):
15         self.id = node['node_id']
16         self.hostname = node['hostname']
17         self.shortname = self.hostname.replace('.vini-veritas.net', '')
18         self.site_id = node['site_id']
19         self.ipaddr = socket.gethostbyname(self.hostname)
20
21     def get_link_id(self, remote):
22         if self.id < remote.id:
23             link = (self.id<<7) + remote.id
24         else:
25             link = (remote.id<<7) + self.id
26         return link
27         
28     def get_iface_id(self, remote):
29         if self.id < remote.id:
30             iface = 1
31         else:
32             iface = 2
33         return iface
34     
35     def get_virt_ip(self, remote):
36         link = self.get_link_id(remote)
37         iface = self.get_iface_id(remote)
38         first = link >> 6
39         second = ((link & 0x3f)<<2) + iface
40         return "192.168.%d.%d" % (first, second)
41
42     def get_virt_net(self, remote):
43         link = self.get_link_id(remote)
44         first = link >> 6
45         second = (link & 0x3f)<<2
46         return "192.168.%d.%d/30" % (first, second)
47         
48     def get_site(self, sites):
49         return sites[self.site_id]
50             
51     
52     # What nodes in the set of node_ids are adjacent to this one?
53     def adjacent_nodes(self, sites, nodes, node_ids):
54         mysite = self.get_site(sites)
55         adj_ids = mysite.adj_node_ids.intersection(node_ids)
56         adj_nodes = []
57         for id in adj_ids:
58             adj_nodes.append(nodes[id])
59         return adj_nodes
60     
61     def init_rspecs(self):
62         self.rspecs = []
63         
64     def add_rspec(self, remote):
65         my_ip = self.get_virt_ip(remote)
66         remote_ip = remote.get_virt_ip(self)
67         net = self.get_virt_net(remote)
68         rspec = remote.id, remote.ipaddr, "1Mbit", my_ip, remote_ip, net
69         self.rspecs.append(rspec)
70
71         
72 class Site:
73     def __init__(self, site):
74         self.id = site['site_id']
75         self.node_ids = site['node_ids']
76         self.adj_site_ids = set()
77         self.adj_node_ids = set()
78
79     def get_sitenodes(self, nodes):
80         n = []
81         for i in self.node_ids:
82             n.append(nodes[i])
83         return n
84     
85     def add_adjacency(self, site):
86         self.adj_site_ids.add(site.id)
87         for n in site.node_ids:
88             self.adj_node_ids.add(n)
89         
90     
91 class Slice:
92     def __init__(self, slice):
93         self.id = slice['slice_id']
94         self.name = slice['name']
95         self.node_ids = set(slice['node_ids'])
96         self.slice_tag_ids = slice['slice_tag_ids']
97     
98     def get_tag(self, tagname, slicetags, node = None):
99         for i in self.slice_tag_ids:
100             tag = slicetags[i]
101             if tag.tagname == tagname:
102                 if (not node) or (node.id == tag.node_id):
103                     return tag
104         else:
105             return None
106         
107     def get_nodes(self, nodes):
108         n = []
109         for id in self.node_ids:
110             n.append(nodes[id])
111         return n
112              
113     
114     # Add a new slice tag   
115     def add_tag(self, tagname, value, slicetags, node = None):
116         record = {'slice_tag_id':None, 'slice_id':self.id, 'tagname':tagname, 'value':value}
117         if node:
118             record['node_id'] = node.id
119         else:
120             record['node_id'] = None
121         tag = Slicetag(record)
122         slicetags[id] = tag
123         self.slice_tag_ids.append(id)
124         tag.changed = True       
125         tag.updated = True
126         return tag
127     
128     # Update a slice tag if it exists, else add it             
129     def update_tag(self, tagname, value, slicetags, node = None):
130         tag = self.get_tag(tagname, slicetags, node)
131         if tag and tag.value == value:
132             value = "no change"
133         elif tag:
134             tag.value = value
135             tag.changed = True
136         else:
137             tag = self.add_tag(tagname, value, slicetags, node)
138         tag.updated = True
139             
140     def assign_egre_key(self, slicetags):
141         if not self.get_tag('egre_key', slicetags):
142             try:
143                 key = free_egre_key(slicetags)
144                 self.update_tag('egre_key', key, slicetags)
145             except:
146                 # Should handle this case...
147                 pass
148         return
149             
150     def turn_on_netns(self, slicetags):
151         tag = self.get_tag('netns', slicetags)
152         if (not tag) or (tag.value != '1'):
153             self.update_tag('netns', '1', slicetags)
154         return
155    
156     def turn_off_netns(self, slicetags):
157         tag = self.get_tag('netns', slicetags)
158         if tag and (tag.value != '0'):
159             tag.delete()
160         return
161     
162     def add_cap_net_admin(self, slicetags):
163         tag = self.get_tag('capabilities', slicetags)
164         if tag:
165             caps = tag.value.split(',')
166             for cap in caps:
167                 if cap == "CAP_NET_ADMIN":
168                     return
169             else:
170                 newcaps = "CAP_NET_ADMIN," + caps
171                 self.update_tag('capabilities', newcaps, slicetags)
172         else:
173             self.add_tag('capabilities', 'CAP_NET_ADMIN', slicetags)
174         return
175     
176     def remove_cap_net_admin(self, slicetags):
177         tag = self.get_tag('capabilities', slicetags)
178         if tag:
179             caps = tag.value.split(',')
180             newcaps = []
181             for cap in caps:
182                 if cap != "CAP_NET_ADMIN":
183                     newcaps.append(cap)
184             if newcaps:
185                 value = ','.join(newcaps)
186                 self.update_tag('capabilities', value, slicetags)
187             else:
188                 tag.delete()
189         return
190
191     # Update the vsys/setup-link and vsys/setup-nat slice tags.
192     def add_vsys_tags(self, slicetags):
193         link = nat = False
194         for i in self.slice_tag_ids:
195             tag = slicetags[i]
196             if tag.tagname == 'vsys':
197                 if tag.value == 'setup-link':
198                     link = True
199                 elif tag.value == 'setup-nat':
200                     nat = True
201         if not link:
202             self.add_tag('vsys', 'setup-link', slicetags)
203         if not nat:
204             self.add_tag('vsys', 'setup-nat', slicetags)
205         return
206
207
208 class Slicetag:
209     newid = -1 
210     def __init__(self, tag):
211         self.id = tag['slice_tag_id']
212         if not self.id:
213             # Make one up for the time being...
214             self.id = Slicetag.newid
215             Slicetag.newid -= 1
216         self.slice_id = tag['slice_id']
217         self.tagname = tag['tagname']
218         self.value = tag['value']
219         self.node_id = tag['node_id']
220         self.updated = False
221         self.changed = False
222         self.deleted = False
223     
224     # Mark a tag as deleted
225     def delete(self):
226         self.deleted = True
227         self.updated = True
228     
229     def write(self, slices, nodes, dryrun):
230         if not dryrun:
231             if self.changed:
232                 if int(self.id) > 0:
233                     UpdateSliceTag(self.id, self.value)
234                 else:
235                     AddSliceTag(self.slice_id, self.tagname, self.value, self.node_id)
236             elif self.deleted and int(self.id) > 0:
237                 DeleteSliceTag(self.id)
238         else:
239             slice = slices[self.slice_id].name
240             if self.node_id:
241                 node = nodes[tag.node_id].hostname
242             if self.updated:
243                 if self.deleted:
244                     self.value = "deleted"
245                 elif not self.changed:
246                     self.value = "no change"
247                 if int(self.id) < 0:
248                     self.id = "new"
249                 if self.node_id:
250                     print "[%s] %s: %s (%s, %s)" % (self.id, self.tagname, self.value, slice, node)
251                 else:
252                     print "[%s] %s: %s (%s)" % (self.id, self.tagname, self.value, slice)
253
254
255 """
256 Create a dictionary of site objects keyed by site ID
257 """
258 def get_sites():
259     tmp = []
260     for site in GetSites():
261         t = site['site_id'], Site(site)
262         tmp.append(t)
263     return dict(tmp)
264
265
266 """
267 Create a dictionary of node objects keyed by node ID
268 """
269 def get_nodes():
270     tmp = []
271     for node in GetNodes():
272         t = node['node_id'], Node(node)
273         tmp.append(t)
274     return dict(tmp)
275
276 """
277 Create a dictionary of slice objects keyed by slice ID
278 """
279 def get_slices():
280     tmp = []
281     for slice in GetSlices():
282         t = slice['slice_id'], Slice(slice)
283         tmp.append(t)
284     return dict(tmp)
285
286 """
287 Create a dictionary of slicetag objects keyed by slice tag ID
288 """
289 def get_slice_tags():
290     tmp = []
291     for tag in GetSliceTags():
292         t = tag['slice_tag_id'], Slicetag(tag)
293         tmp.append(t)
294     return dict(tmp)
295     
296 """
297 Find a free EGRE key
298 """
299 def free_egre_key(slicetags):
300     for i in slicetags:
301         used = set()
302         tag = slicetags[i]
303         if tag.tagname == 'egre_key':
304             used.add(int(tag.value))
305                 
306     for i in range(1, 256):
307         if i not in used:
308             key = i
309             break
310     else:
311         raise KeyError("No more EGRE keys available")
312         
313     return "%s" % key
314    
315 # For debugging
316 dryrun = 0
317
318 sites = get_sites()
319 nodes = get_nodes()
320 slices = get_slices()
321 slicetags = get_slice_tags()
322
323 # Add adjacencies
324 for (a, b) in links:
325     sites[a].add_adjacency(sites[b])
326     sites[b].add_adjacency(sites[a])  
327
328 for i in slices:
329     slice = slices[i]
330     tag = slice.get_tag('vini_topo', slicetags)
331     if tag:
332         topo_type = tag.value
333     else:
334         topo_type = None
335     
336     """
337     Valid values of topo_type: 
338     'vsys':   Use vsys topology scripts to set up virtual links
339     'iias':   Automatically create a virtual topology mirroring the physical one
340     'manual': Don't modify the topo_rspec tags if present
341     None:     No virtual topology
342     """
343     if topo_type in ['vsys', 'iias', 'manual']:
344         slice.assign_egre_key(slicetags)
345         slice.turn_on_netns(slicetags)
346         slice.add_cap_net_admin(slicetags)
347     else:
348         # Let them keep EGRE key for now...
349         slice.turn_off_netns(slicetags)
350         slice.remove_cap_net_admin(slicetags)
351             
352     # Add vsys/setup-link and vsys/setup-nat
353     if topo_type == 'vsys' and slice.get_tag('egre_key', slicetags):
354         slice.add_vsys_tags(slicetags)
355         
356     if topo_type == 'iias' and slice.get_tag('egre_key', slicetags):
357         if dryrun:
358             print "Building virtual topology for %s" % slice.name
359             
360         hosts = "127.0.0.1\t\tlocalhost\n"
361         """
362         For each node in the slice, check whether the slice is running on any
363         adjacent nodes.  For each pair of adjacent nodes, add to nodes' rspecs.
364         """
365         for node in slice.get_nodes(nodes):
366             node.init_rspecs()
367             adj_nodes = node.adjacent_nodes(sites, nodes, slice.node_ids)
368             for adj in adj_nodes:
369                 node.add_rspec(adj)
370                 hosts += "%s\t\t%s\n" % (node.get_virt_ip(adj), node.shortname)
371             if node.rspecs:
372                 topo_str = "%s" % node.rspecs
373                 slice.update_tag('topo_rspec', topo_str, slicetags, node)
374                     
375         slice.update_tag('hosts', hosts, slicetags)
376     else:
377         if dryrun:
378             print "Slice %s not using IIAS" % slice.name
379
380     if topo_type == 'manual' and slice.get_tag('egre_key', slicetags):
381         topo_tag = slice.get_tag('topo_rspec', slicetags)
382         if topo_tag:
383             topo_tag.updated = True
384             
385 # Update the tag values in the database
386 for i in slicetags:
387     tag = slicetags[i]
388     if (tag.tagname == 'topo_rspec' or tag.tagname == 'hosts') and not tag.updated:
389         tag.delete()
390     tag.write(slices, nodes, dryrun)
391
392