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