Generate Quagga config files for each sliver
[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 Check for existence of interface d<key>x<nodeid>
36 """
37 def virtual_link(key, nodeid):
38     name = "d%sx%s" % (key, nodeid)
39     if name in ifaces:
40         return True
41     else:
42         return False
43
44 def get_virt_ip(myid, nodeid):
45     if myid < nodeid:
46         virtip = "10.%d.%d.2" % (myid, nodeid)
47     else:
48         virtip = "10.%d.%d.3" % (nodeid, myid)
49     return virtip
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):
56     logger.log("%s: Set up virtual link to node %d" % (slice, nodeid))
57     virtip = get_virt_ip(myid, nodeid)
58     run(setup_link_cmd + " %s %s %s %s %s %s" % (slice, nodeid, ipaddr, 
59                                                  key, rate, virtip))
60     return
61
62
63 """
64 Tear down the "virtual link" for slice between here and nodeid.
65 """
66 def teardown_virtual_link(key, nodeid):
67     logger.log("topo: Tear down virtual link %sx%s" % (key, nodeid))
68     run(teardown_link_cmd + " %s %s" % (nodeid, key))
69     return
70
71
72 """
73 Called for all active virtual link interfaces, so they won't be cleaned up.
74 """
75 def refresh_virtual_link(nodeid, key):
76     name = "d%sx%s" % (key, nodeid)
77     if name in old_ifaces:
78         del old_ifaces[name]
79     return
80
81
82 """
83 Check for existence of interface natx<key>
84 """
85 def nat_exists(key):
86     name = "natx%s" % key
87     if name in ifaces:
88         return True
89     else:
90         return False
91
92
93 """
94 Create a NAT interface inside the sliver.  
95 """
96 def setup_nat(slice, myid, key):
97     logger.log("%s: Set up NAT" % slice)
98     run(setup_nat_cmd + " %s %s %s" % (slice, myid, key))
99     return
100
101
102 """
103 Tear down the NAT interface identified by key
104 """
105 def teardown_nat(key):
106     logger.log("topo: Tear down NAT %s" % key)
107     run(teardown_nat_cmd + " %s" % key)
108     return
109
110
111 """
112 Called for all active NAT interfaces, so they won't be cleaned up.
113 """
114 def refresh_nat(key):
115     name = "natx%s" % (key)
116     if name in old_ifaces:
117         del old_ifaces[name]
118     return
119
120
121 """
122 Clean up old virtual links (e.g., to nodes that have been deleted 
123 from the slice).
124 """
125 def clean_up_old_virtual_links():
126     pattern1 = "d(.*)x(.*)"
127     pattern2 = "natx(.*)"
128     for iface in old_ifaces:
129         m = re.match(pattern1, iface)
130         if m:
131             key = int(m.group(1))
132             node = int(m.group(2))
133             teardown_virtual_link(key, node)
134
135         m = re.match(pattern2, iface)
136         if m:
137             key = int(m.group(1))
138             teardown_nat(key)
139     return
140
141
142 """
143 Not the safest thing to do, probably should use pickle() or something.
144 """
145 def convert_topospec_to_list(rspec):
146     return eval(rspec)
147
148
149 """
150 Update virtual links for the slice
151 """
152 def update_links(slice, myid, topospec, key, netns):
153     topolist = convert_topospec_to_list(topospec)
154     for (nodeid,ipaddr,rate) in topolist:
155         if not virtual_link(key, nodeid):
156             if netns:
157                 setup_virtual_link(slice, key, rate, myid, nodeid, ipaddr)
158         else:
159             logger.log("%s: virtual link to node %s exists" % (slice, nodeid))
160             refresh_virtual_link(nodeid, key)
161
162     if not nat_exists(key):
163         setup_nat(slice, myid, key)
164     else:
165         logger.log("%s: NAT exists" % slice)
166         refresh_nat(key)
167
168
169 """
170 Write /etc/vservers/<slicename>/spaces/net
171 """
172 def writeConf(slicename, value):
173     SLICEDIR="/etc/vservers/%s/" % slicename
174     SPACESDIR="%s/spaces/" % SLICEDIR
175     if os.path.exists(SLICEDIR):
176         if not os.path.exists(SPACESDIR):
177             try:
178                 os.mkdir(SPACESDIR)
179             except os.error:
180                 logger.log("topo: could not create %s\n" % SPACESDIR)
181                 return
182         f = open("%s/net" % SPACESDIR, "w")
183         f.write("%s\n" % value)
184         f.close()
185         STATUS="OFF"
186         if value:
187             STATUS="ON"
188         logger.log("%s: network namespace %s\n" % (slicename, STATUS))
189
190
191 """
192 Generate information for each interface in the sliver, in order to configure
193 Quagga.
194 """
195 def get_ifaces(hostname, myid, topospec, key):
196     ifaces = {}
197     topolist = convert_topospec_to_list(topospec)
198     for (nodeid, ipaddr, rate) in topolist:
199         name = "a%sx%s" % (key, nodeid)
200         ifaces[name] = {}
201         ifaces[name]['remote-ip'] = get_virt_ip(nodeid, myid)
202         ifaces[name]['local-ip'] = get_virt_ip(myid, nodeid)
203         ifaces[name]['short-name'] = hostname.replace('.vini-veritas.net', '')
204     return ifaces
205
206
207 def write_header(f, myname, password):
208     f.write ("""! Configuration for %s
209 ! Generated at %s
210
211 hostname %s
212 password %s
213
214 """ % (myname, strftime("%Y-%m-%d %H:%M:%S"), myname, password))
215     return
216
217
218 """
219 Write zebra.conf file for Quagga
220 """
221 def write_zebra(filename, myname, ifaces):
222     f = open(filename, 'w')
223     password = "zebra"
224     write_header(f, myname, password)
225
226     f.write ("""enable password %s
227 !
228 """ % password)
229
230     for name in ifaces:
231         f.write ("""!     
232      interface %s
233      link-detect
234 """ %  name)
235
236     f.write ("""!
237 !
238  access-list vty permit 127.0.0.1/32
239 !
240  line vty
241 !
242 """)
243     f.close()
244     return
245
246
247 """
248 Write ospfd.conf file for Quagga.  
249 """
250 def write_ospf(filename, myname, ifaces):
251     f = open(filename, 'w')
252     password = "zebra"
253     write_header(f, myname, password)
254
255     for name in ifaces:
256         f.write ("""!
257      interface %s
258      ip ospf cost 10
259      ip ospf hello-interval 5
260      ip ospf dead-interval 10
261      ip ospf network non-broadcast
262 """ % name)
263
264     f.write ("""!
265      router ospf
266      ospf router-id %s
267 """ % ifaces[name]['local-ip'])
268
269     for name in ifaces:
270         f.write ("     neighbor %s\n" % ifaces[name]['remote-ip'])
271
272     for name in ifaces:
273         net = ifaces[name]['local-ip'].rstrip('23')+'0'
274         f.write ("     network %s/24 area 0\n" % net)
275
276     f.write("""!
277 access-list vty permit 127.0.0.1/32
278 !
279 line vty
280 """)
281     return
282
283
284 """
285 Write config files directly into the slice's file system.
286 """
287 def update_quagga_configs(slicename, hostname, myid, topo, key, netns):
288     ifaces = get_ifaces(hostname, myid, topo, key)
289
290     quagga_dir = "/vservers/%s/etc/quagga/" % slicename
291     if not os.path.exists(quagga_dir):
292         try:
293             # Quagga not installed.  Install it here?  Chkconfig, sym links.
294             os.mkdir(quagga_dir)
295         except os.error:
296             logger.log("topo: could not create %s\n" % quagga_dir)
297             return
298
299     write_zebra(quagga_dir + "zebra.conf.generated", hostname, ifaces)
300     write_ospf(quagga_dir + "ospfd.conf.generated", hostname, ifaces)
301
302     # Start up Quagga if we installed it earlier and netns = 1.
303
304     return
305
306
307 """
308 Write /etc/hosts in the sliver
309 """
310 def update_hosts(slicename, hosts):
311     hosts_file = "/vservers/%s/etc/hosts" % slicename
312     f = open(hosts_file, 'w')
313     f.write(hosts)
314     f.close()
315     return
316
317
318 def start(options, config):
319     pass
320
321
322 """
323 Update the virtual links for a sliver if it has a 'netns' attribute,
324 an 'egre_key' attribute, and a 'topo_rspec' attribute.
325
326 Creating the virtual link depends on the contents of 
327 /etc/vservers/<slice>/spaces/net.  Update this first.
328 """
329 def GetSlivers(data):
330     global ifaces, old_ifaces
331     ifaces = old_ifaces = sioc.gifconf()
332
333     for sliver in data['slivers']:
334         attrs = {}
335         for attribute in sliver['attributes']:
336             attrs[attribute['name']] = attribute['value']
337         if 'netns' in attrs:
338             netns = int(attrs['netns'])
339             writeConf(sliver['name'], netns)
340         else:
341             netns = 0
342
343         if vserver.VServer(sliver['name']).is_running():
344             if 'egre_key' in attrs and 'topo_rspec' in attrs:
345                 logger.log("topo: Update topology for slice %s" % \
346                                sliver['name'])
347                 update_links(sliver['name'], data['node_id'], 
348                              attrs['topo_rspec'], attrs['egre_key'], netns)
349                 update_quagga_configs(sliver['name'], data['hostname'],
350                                data['node_id'], attrs['topo_rspec'], 
351                                attrs['egre_key'], netns)
352             if 'hosts' in attrs:
353                 update_hosts(sliver['name'], attrs['hosts'])
354         else:
355             logger.log("topo: sliver %s not running yet. Deferring." % \
356                            sliver['name'])
357
358     clean_up_old_virtual_links()
359     return
360
361