No need to assume integers
[nodemanager-topo.git] / topo.py
1 # $Id$
2 # $URL$
3
4 """ 
5 VINI/Trellis NodeManager plugin.
6 Create virtual links from the topo_rspec slice attribute. 
7 """
8
9 import logger
10 import subprocess
11 import sioc
12 import re
13 import vserver
14 import os
15 from time import strftime
16
17 dryrun = 0
18 vinidir = "/usr/share/vini/"
19 setup_link_cmd = vinidir + "setup-egre-link"
20 teardown_link_cmd = vinidir + "teardown-egre-link"
21 setup_nat_cmd = vinidir + "setup-nat"
22 teardown_nat_cmd = vinidir + "teardown-nat"
23 ifaces = {}
24 old_ifaces = {}
25
26 def run(cmd):
27     if dryrun:
28         logger.log(cmd)
29         return -1
30     else:
31         return subprocess.call(cmd, shell=True);
32
33
34 """
35 Subnet used for virtual interfaces by setup-egre-link script
36 """
37 def iias_network():
38     return "192.168.0.0 255.255.0.0"
39
40
41 """
42 Check for existence of interface d<key>x<nodeid>
43 """
44 def virtual_link(key, nodeid):
45     name = "d%sx%s" % (key, nodeid)
46     if name in ifaces:
47         return True
48     else:
49         return False
50
51 """
52 Create a "virtual link" for slice between here and nodeid.
53 The key is used to create the EGRE tunnel.
54 """
55 def setup_virtual_link(slice, key, rate, myid, nodeid, ipaddr, virtip, vnet):
56     logger.log("%s: Set up virtual link to node %s" % (slice, nodeid))
57     run(setup_link_cmd + " %s %s %s %s %s %s %s" % (slice, nodeid, ipaddr, 
58                                                  key, rate, virtip, vnet))
59     return
60
61
62 """
63 Tear down the "virtual link" for slice between here and nodeid.
64 """
65 def teardown_virtual_link(key, nodeid):
66     logger.log("topo: Tear down virtual link %sx%s" % (key, nodeid))
67     run(teardown_link_cmd + " %s %s" % (nodeid, key))
68     return
69
70
71 """
72 Called for all active virtual link interfaces, so they won't be cleaned up.
73 """
74 def refresh_virtual_link(nodeid, key):
75     name = "d%sx%s" % (key, nodeid)
76     if name in old_ifaces:
77         del old_ifaces[name]
78     return
79
80
81 """
82 IP address of the NAT interface created inside the slice by the
83 setup-nat script.
84 """
85 def nat_inner_ip(key):
86     return "10.0.%s.2" % key
87
88
89 """
90 Check for existence of interface natx<key>
91 """
92 def nat_exists(key):
93     name = "natx%s" % key
94     if name in ifaces:
95         return True
96     else:
97         return False
98
99
100 """
101 Create a NAT interface inside the sliver.  
102 """
103 def setup_nat(slice, myid, key):
104     logger.log("%s: Set up NAT" % slice)
105     run(setup_nat_cmd + " %s %s %s" % (slice, myid, key))
106     return
107
108
109 """
110 Tear down the NAT interface identified by key
111 """
112 def teardown_nat(key):
113     logger.log("topo: Tear down NAT %s" % key)
114     run(teardown_nat_cmd + " %s" % key)
115     return
116
117
118 """
119 Called for all active NAT interfaces, so they won't be cleaned up.
120 """
121 def refresh_nat(key):
122     name = "natx%s" % (key)
123     if name in old_ifaces:
124         del old_ifaces[name]
125     return
126
127
128 """
129 Clean up old virtual links (e.g., to nodes that have been deleted 
130 from the slice).
131 """
132 def clean_up_old_virtual_links():
133     pattern1 = "d(.*)x(.*)"
134     pattern2 = "natx(.*)"
135     for iface in old_ifaces:
136         m = re.match(pattern1, iface)
137         if m:
138             key = m.group(1)
139             node = m.group(2)
140             teardown_virtual_link(key, node)
141
142         m = re.match(pattern2, iface)
143         if m:
144             key = m.group(1)
145             teardown_nat(key)
146     return
147
148
149 """
150 Not the safest thing to do, probably should use pickle() or something.
151 """
152 def convert_topospec_to_list(rspec):
153     return eval(rspec)
154
155
156 """
157 Update virtual links for the slice
158 """
159 def update_links(slice, myid, topospec, key, netns):
160     topolist = convert_topospec_to_list(topospec)
161     for (nodeid, ipaddr, rate, myvirtip, remvirtip, virtnet) in topolist:
162         if not virtual_link(key, nodeid):
163             if netns:
164                 setup_virtual_link(slice, key, rate, myid, nodeid, 
165                                    ipaddr, myvirtip, virtnet)
166         else:
167             logger.log("%s: virtual link to node %s exists" % (slice, nodeid))
168             refresh_virtual_link(nodeid, key)
169
170
171 """
172 Update NAT interface for the slice
173 """
174 def update_nat(slice, myid, key, netns):
175     if not nat_exists(key):
176         if netns:
177             setup_nat(slice, myid, key)
178     else:
179         logger.log("%s: NAT exists" % slice)
180         refresh_nat(key)
181
182
183 """
184 Write /etc/vservers/<slicename>/spaces/net.  If the vserver is running and the spaces/net file is modified, we need to restart it.
185 """
186 def write_conf_and_restart(slicename, value):
187     SLICEDIR="/etc/vservers/%s/" % slicename
188     SPACESDIR="%s/spaces/" % SLICEDIR
189     FILENAME="%s/net" % SPACESDIR
190     if os.path.exists(SLICEDIR):
191         if not os.path.exists(SPACESDIR):
192             try:
193                 os.mkdir(SPACESDIR)
194             except os.error:
195                 logger.log("topo: could not create %s\n" % SPACESDIR)
196                 return
197             
198         if os.path.exists(FILENAME) != value:
199             sliver = vserver.VServer(slicename)
200             restart = sliver.is_running()
201             
202             if (restart):
203                 sliver.stop()
204                 
205             if value:
206                 STATUS="ON"
207                 f = open(FILENAME, "w")
208                 f.close()
209             else:
210                 STATUS="OFF"
211                 os.remove(FILENAME)
212                 
213             logger.log("%s: network namespace %s\n" % (slicename, STATUS))
214
215             if (restart):
216                 logger.log("topo: restarting sliver %s\n" % slicename)
217                 sliver.start()
218
219
220 """
221 Generate information for each interface in the sliver, in order to configure
222 Quagga.
223 """
224 def get_ifaces(hostname, myid, topospec, key):
225     ifaces = {}
226     topolist = convert_topospec_to_list(topospec)
227     for (nodeid, ipaddr, rate, myvirtip, remvirtip, virtnet) in topolist:
228         name = "a%sx%s" % (key, nodeid)
229         ifaces[name] = {}
230         ifaces[name]['remote-ip'] = remvirtip
231         ifaces[name]['local-ip'] = myvirtip
232         ifaces[name]['network'] = virtnet
233         ifaces[name]['short-name'] = hostname.replace('.vini-veritas.net', '')
234     return ifaces
235
236
237 def write_header(f, myname, password):
238     f.write ("""! Configuration for %s
239 ! Generated at %s
240
241 hostname %s
242 password %s
243
244 """ % (myname, strftime("%Y-%m-%d %H:%M:%S"), myname, password))
245     return
246
247
248 """
249 IP address of NAT gateway to outside world
250 """
251 def nat_gw(key):
252     return "10.0.%s.1" %  key
253
254 """
255 IP address of the NAT interface inside the slice
256 """
257 def nat_inner(key):
258     return "10.0.%s.2" % key
259
260
261 """
262 Write zebra.conf file for Quagga
263 """
264 def write_zebra(filename, myname, ifaces, myid, key):
265     f = open(filename, 'w')
266     password = "zebra"
267     write_header(f, myname, password)
268
269     f.write ("enable password %s\n" % password)
270
271     for name in ifaces:
272         f.write ("""!     
273 interface %s
274 link-detect
275 """ %  name)
276
277     f.write ("""!
278 access-list vty permit 127.0.0.1/32
279 !
280 line vty
281 !
282 """)
283     f.close()
284     return
285
286
287 """
288 Write ospfd.conf file for Quagga.  
289 """
290 def write_ospf(filename, myname, ifaces):
291     f = open(filename, 'w')
292     password = "zebra"
293     write_header(f, myname, password)
294     name = None
295
296     for name in ifaces:
297         f.write ("""!
298      interface %s
299      ip ospf cost 10
300      ip ospf hello-interval 5
301      ip ospf dead-interval 10
302      ip ospf network non-broadcast
303 """ % name)
304
305     if name:
306         f.write ("""!
307      router ospf
308      ospf router-id %s
309 """ % ifaces[name]['local-ip'])
310
311     for name in ifaces:
312         f.write ("     neighbor %s\n" % ifaces[name]['remote-ip'])
313
314     for name in ifaces:
315         net = ifaces[name]['network']
316         f.write ("     network %s area 0\n" % net)
317
318     f.write("""     redistribute kernel
319 !
320 access-list vty permit 127.0.0.1/32
321 !
322 line vty
323 """)
324     return
325
326
327 """
328 Write config files directly into the slice's file system.
329 """
330 def update_quagga_configs(slicename, hostname, myid, topo, key, netns):
331     ifaces = get_ifaces(hostname, myid, topo, key)
332
333     quagga_dir = "/vservers/%s/etc/quagga/" % slicename
334     if not os.path.exists(quagga_dir):
335         try:
336             # Quagga not installed.  Install it here?  Chkconfig, sym links.
337             os.mkdir(quagga_dir)
338         except os.error:
339             logger.log("topo: could not create %s\n" % quagga_dir)
340             return
341
342     write_zebra(quagga_dir + "zebra.conf.generated", hostname, ifaces, 
343                 myid, key)
344     write_ospf(quagga_dir + "ospfd.conf.generated", hostname, ifaces)
345
346     # Start up Quagga if we installed it earlier and netns = 1.
347
348     return
349
350
351 """
352 Write /etc/hosts in the sliver
353 """
354 def update_hosts(slicename, hosts):
355     hosts_file = "/vservers/%s/etc/hosts" % slicename
356     f = open(hosts_file, 'w')
357     f.write(hosts)
358     f.close()
359     return
360
361 """
362 Write /etc/vini/egre-keys.txt, used by vsys topo scripts
363 """
364 def write_egre_keys(slicekeys):
365     vini_dir = "/etc/vini" 
366     if not os.path.exists(vini_dir):
367         try:
368             os.mkdir(vini_dir)
369         except os.error:
370             logger.log("topo: could not create %s\n" % vini_dir)
371             return
372     keys_file = "%s/egre-keys.txt" % vini_dir
373     f = open(keys_file, 'w')
374     for slice in slicekeys:
375         f.write("%s %s\n" % (slice, slicekeys[slice]))
376     f.close()
377     return
378
379
380 """
381 Executed on NM startup
382 """
383 def start(options, config):
384     run ("echo 1 > /proc/sys/net/ipv4/ip_forward")
385     pass
386
387
388 """
389 Update the virtual links for a sliver if it has a 'netns' attribute,
390 an 'egre_key' attribute, and a 'topo_rspec' attribute.
391
392 Creating the virtual link depends on the contents of 
393 /etc/vservers/<slice>/spaces/net.  Update this first.
394 """
395 def GetSlivers(data):
396     global ifaces, old_ifaces
397     ifaces = old_ifaces = sioc.gifconf()
398
399     slicekeys = {}
400     for sliver in data['slivers']:
401         attrs = {}
402         for tag in sliver['attributes']:
403             attrs[tag['tagname']] = tag['value']
404             if tag['tagname'] == 'egre_key':
405                 slicekeys[sliver['name']] = tag['value']
406                 
407
408         if 'netns' in attrs:
409             netns = int(attrs['netns'])
410         else:
411             netns = 0
412         write_conf_and_restart(sliver['name'], netns)
413
414         if vserver.VServer(sliver['name']).is_running():
415             if 'egre_key' in attrs:
416                 logger.log("topo: Update slice %s" % sliver['name'])
417                 update_nat(sliver['name'], data['node_id'], attrs['egre_key'],
418                            netns)
419                 if 'topo_rspec' in attrs:
420                     update_links(sliver['name'], data['node_id'], 
421                                 attrs['topo_rspec'], attrs['egre_key'], netns)
422                     update_quagga_configs(sliver['name'], data['hostname'],
423                                 data['node_id'], attrs['topo_rspec'], 
424                                 attrs['egre_key'], netns)
425             if 'hosts' in attrs:
426                 update_hosts(sliver['name'], attrs['hosts'])
427         else:
428             logger.log("topo: sliver %s not running yet. Deferring." % \
429                            sliver['name'])
430
431     clean_up_old_virtual_links()
432     write_egre_keys(slicekeys)
433     return
434
435