source: genesis/nodes/gformat.py@ 8578

Last change on this file since 8578 was 8575, checked in by rick, 14 years ago

Some more user friendly format

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 14.7 KB
Line 
1#!/usr/bin/env python
2#
3# vim:ts=2:et:sw=2:ai
4# Wireless Leiden configuration generator, based on yaml files'
5# Rick van der Zwet <info@rickvanderzwet.nl>
6import cgi
7import cgitb
8import copy
9import glob
10import os
11import socket
12import string
13import subprocess
14import sys
15import time
16try:
17 import yaml
18except ImportError, e:
19 print e
20 print "[ERROR] Please install the python-yaml or devel/py-yaml package"
21 exit(1)
22
23
24NODE_DIR = os.path.dirname(os.path.realpath(__file__))
25__version__ = '$Id: gformat.py 8575 2010-10-19 19:18:36Z rick $'
26
27
28files = [
29 'authorized_keys',
30 'dnsmasq.conf',
31 'rc.conf.local',
32 'resolv.conf',
33 'wleiden.yaml'
34 ]
35
36# Global variables uses
37OK = 10
38DOWN = 20
39UNKNOWN = 90
40
41
42def get_proxylist():
43 """Get all available proxies proxyX sorting based on X number"""
44 os.chdir(NODE_DIR)
45 proxylist = sorted(glob.glob("proxy*"),
46 key=lambda name: int(''.join([c for c in name if c in string.digits])),
47 cmp=lambda x,y: x - y)
48 return proxylist
49
50
51
52def valid_addr(addr):
53 """ Show which address is valid in which are not """
54 return str(addr).startswith('172.')
55
56
57def get_nodelist():
58 """ Get all available nodes - sorted """
59 os.chdir(NODE_DIR)
60 nodelist = sorted(glob.glob("CNode*"))
61 return nodelist
62
63def get_hostlist():
64 """ Combined hosts and proxy list"""
65 return get_nodelist() + get_proxylist()
66
67
68def generate_title(nodelist):
69 """ Main overview page """
70 items = {'root' : "." }
71 output = """
72<html>
73 <head>
74 <title>Wireless leiden Configurator - GFormat</title>
75 <style type="text/css">
76 th {background-color: #999999}
77 tr:nth-child(odd) {background-color: #cccccc}
78 tr:nth-child(even) {background-color: #ffffff}
79 th, td {padding: 0.1em 1em}
80 </style>
81 </head>
82 <body>
83 <center>
84 <form type="GET" action="%(root)s">
85 <input type="hidden" name="action" value="update">
86 <input type="submit" value="Update Configuration Database (SVN)">
87 </form>
88 <table>
89 <caption><h3>Wireless Leiden Configurator</h3></caption>
90 """ % items
91
92 for node in nodelist:
93 items['node'] = node
94 output += '<tr><td><a href="%(root)s/%(node)s">%(node)s</a></td>' % items
95 for config in files:
96 items['config'] = config
97 output += '<td><a href="%(root)s/%(node)s/%(config)s">%(config)s</a></td>' % items
98 output += "</tr>"
99 output += """
100 </table>
101 <hr />
102 <em>%s</em>
103 </center>
104 </body>
105</html>
106 """ % __version__
107
108 return output
109
110
111
112def generate_node(node):
113 """ Print overview of all files available for node """
114 return "\n".join(files)
115
116
117
118def generate_header(ctag="#"):
119 return """\
120%(ctag)s
121%(ctag)s DO NOT EDIT - Automatically generated by 'gformat'
122%(ctag)s Generated at %(date)s by %(host)s
123%(ctag)s
124""" % { 'ctag' : ctag, 'date' : time.ctime(), 'host' : socket.gethostname() }
125
126
127
128def parseaddr(s):
129 """ Process IPv4 CIDR notation addr to a (binary) number """
130 f = s.split('.')
131 return (long(f[0]) << 24L) + \
132 (long(f[1]) << 16L) + \
133 (long(f[2]) << 8L) + \
134 long(f[3])
135
136
137
138def showaddr(a):
139 """ Display IPv4 addr in (dotted) CIDR notation """
140 return "%d.%d.%d.%d" % ((a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff)
141
142
143
144def netmask2subnet(netmask):
145 """ Given a 'netmask' return corresponding CIDR """
146 return showaddr(0xffffffff & (0xffffffff << (32 - int(netmask))))
147
148
149
150def generate_dnsmasq_conf(datadump):
151 """ Generate configuration file '/usr/local/etc/dnsmasq.conf' """
152 output = generate_header()
153 output += """\
154# DHCP server options
155dhcp-authoritative
156dhcp-fqdn
157domain=dhcp.%(nodename_lower)s.%(domain)s
158domain-needed
159expand-hosts
160
161# Low memory footprint
162cache-size=10000
163 \n""" % datadump
164
165 for iface_key in datadump['iface_keys']:
166 if not datadump[iface_key].has_key('comment'):
167 datadump[iface_key]['comment'] = None
168 output += "## %(interface)s - %(desc)s - %(comment)s\n" % datadump[iface_key]
169
170 try:
171 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
172 (ip, netmask) = datadump[iface_key]['ip'].split('/')
173 datadump[iface_key]['subnet'] = netmask2subnet(netmask)
174 except (AttributeError, ValueError):
175 output += "# not autoritive\n\n"
176 continue
177
178 dhcp_part = ".".join(ip.split('.')[0:3])
179 datadump[iface_key]['dhcp_start'] = dhcp_part + "." + dhcp_start
180 datadump[iface_key]['dhcp_stop'] = dhcp_part + "." + dhcp_stop
181 output += "dhcp-range=%(interface)s,%(dhcp_start)s,%(dhcp_stop)s,%(subnet)s,24h\n\n" % datadump[iface_key]
182
183 return output
184
185
186
187def generate_rc_conf_local(datadump):
188 """ Generate configuration file '/etc/rc.conf.local' """
189 output = generate_header("#");
190 output += """\
191hostname='%(nodetype)s%(nodename)s.%(domain)s'
192location='%(location)s'
193""" % datadump
194
195 # TProxy configuration
196 output += "\n"
197 try:
198 if datadump['tproxy']:
199 output += """\
200tproxy_enable='YES'
201tproxy_range='%(tproxy)s'
202""" % datadump
203 except KeyError:
204 output += "tproxy_enable='NO'\n"
205
206 output += '\n'
207 # lo0 configuration:
208 # - 172.32.255.1/32 is the proxy.wleiden.net deflector
209 # - masterip is special as it needs to be assigned to at
210 # least one interface, so if not used assign to lo0
211 addrs_list = { 'lo0' : ["127.0.0.1/8", "172.31.255.1/32"] }
212 iface_map = {'lo0' : 'lo0'}
213
214 masterip_used = False
215 for iface_key in datadump['iface_keys']:
216 if datadump[iface_key]['ip'].startswith(datadump['masterip']):
217 masterip_used = True
218 break
219 if not masterip_used:
220 addrs_list['lo0'].append(datadump['masterip'] + "/32")
221
222 wlan_count = 0
223 for iface_key in datadump['iface_keys']:
224 ifacedump = datadump[iface_key]
225 interface = ifacedump['interface']
226 # By default no special interface mapping
227 iface_map[interface] = interface
228
229 # Add interface IP to list
230 if addrs_list.has_key(interface):
231 addrs_list[interface].append(ifacedump['ip'])
232 else:
233 addrs_list[interface] = [ifacedump['ip']]
234
235 # Alias only needs IP assignment for now, this might change if we
236 # are going to use virtual accesspoints
237 if "alias" in iface_key:
238 continue
239
240 # XXX: Might want to deduct type directly from interface name
241 if ifacedump['type'] in ['11a', '11b', '11g', 'wireless']:
242 # Create wlanX interface
243 ifacedump['wlanif'] ="wlan%i" % wlan_count
244 iface_map[interface] = ifacedump['wlanif']
245 wlan_count += 1
246
247 # Default to station (client) mode
248 ifacedump['wlanmode'] = "sta"
249 if ifacedump['mode'] in ['master', 'master-wds']:
250 ifacedump['wlanmode'] = "ap"
251 # Default to 802.11b mode
252 ifacedump['mode'] = '11b'
253 if ifacedump['type'] in ['11a', '11b' '11g']:
254 ifacedump['mode'] = ifacedump['type']
255
256 if not ifacedump.has_key('channel'):
257 if ifacedump['type'] == '11a':
258 ifacedump['channel'] = 36
259 else:
260 ifacedump['channel'] = 1
261
262 # Allow special hacks at the back like wds and stuff
263 if not ifacedump.has_key('extra'):
264 ifacedump['extra'] = 'regdomain ETSI country NL'
265
266 output += "wlans_%(interface)s='%(wlanif)s'\n" % ifacedump
267 output += ("create_args_%(wlanif)s='wlanmode %(wlanmode)s mode " +\
268 "%(mode)s ssid %(ssid)s %(extra)s channel %(channel)s'\n") % ifacedump
269
270 elif ifacedump['type'] in ['ethernet', 'eth']:
271 # No special config needed besides IP
272 pass
273 else:
274 assert False, "Unknown type " + ifacedump['type']
275
276 # Print IP address which needs to be assigned over here
277 output += "\n"
278 for iface,addrs in sorted(addrs_list.iteritems()):
279 output += "ipv4_addrs_%s='%s'\n" % (iface_map[iface], " ".join(addrs))
280
281 return output
282
283
284
285def get_yaml(item):
286 """ Get configuration yaml for 'item'"""
287 gfile = NODE_DIR + '/%s/wleiden.yaml' % item
288
289 f = open(gfile, 'r')
290 datadump = yaml.load(f)
291 f.close()
292
293 return datadump
294
295
296
297def get_all_configs():
298 """ Get dict with key 'host' with all configs present """
299 configs = dict()
300 for host in get_hostlist():
301 datadump = get_yaml(host)
302 configs[host] = datadump
303 return configs
304
305
306def get_interface_keys(config):
307 """ Quick hack to get all interface keys, later stage convert this to a iterator """
308 return [elem for elem in config.keys() if (elem.startswith('iface_') and not "lo0" in elem)]
309
310
311def get_used_ips(configs):
312 """ Return array of all IPs used in config files"""
313 ip_list = []
314 for config in configs:
315 ip_list.append(config['masterip'])
316 for iface_key in get_interface_keys(config):
317 l = config[iface_key]['ip']
318 addr, mask = l.split('/')
319 # Special case do not process
320 if valid_addr(addr):
321 ip_list.append(addr)
322 else:
323 print "## IP '%s' in '%s' not valid" % (addr, config['nodename'])
324 return sorted(ip_list)
325
326
327
328def write_yaml(item, datadump):
329 """ Write configuration yaml for 'item'"""
330 gfile = NODE_DIR + '/%s/wleiden.yaml' % item
331
332 f = open(gfile, 'w')
333 f.write(format_wleiden_yaml(datadump))
334 f.close()
335
336
337
338def generate_resolv_conf(datadump):
339 """ Generate configuration file '/etc/resolv.conf' """
340 output = generate_header("#");
341 output += """\
342search wleiden.net
343# Try local (cache) first
344nameserver 127.0.0.1
345
346# Proxies are recursive nameservers
347# needs to be in resolv.conf for dnsmasq as well
348""" % datadump
349
350 for proxy in get_proxylist():
351 proxy_ip = get_yaml(proxy)['masterip']
352 output += "nameserver %-15s # %s\n" % (proxy_ip, proxy)
353 return output
354
355
356
357def format_yaml_value(value):
358 """ Get yaml value in right syntax for outputting """
359 if isinstance(value,str):
360 output = "'%s'" % value
361 else:
362 output = value
363 return output
364
365
366
367def format_wleiden_yaml(datadump):
368 """ Special formatting to ensure it is editable"""
369 output = "# Genesis config yaml style\n"
370 output += "# vim:ts=2:et:sw=2:ai\n"
371 output += "#\n"
372 iface_keys = [elem for elem in datadump.keys() if elem.startswith('iface_')]
373 for key in sorted(set(datadump.keys()) - set(iface_keys)):
374 output += "%-10s: %s\n" % (key, format_yaml_value(datadump[key]))
375
376 output += "\n\n"
377
378 key_order = [ 'comment', 'interface', 'ip', 'desc', 'sdesc', 'mode', 'type',
379 'extra_type', 'channel', 'ssid', 'dhcp' ]
380
381 for iface_key in sorted(iface_keys):
382 output += "%s:\n" % iface_key
383 for key in key_order + list(sorted(set(datadump[iface_key].keys()) - set(key_order))):
384 if datadump[iface_key].has_key(key):
385 output += " %-11s: %s\n" % (key, format_yaml_value(datadump[iface_key][key]))
386 output += "\n\n"
387
388 return output
389
390
391
392def generate_wleiden_yaml(datadump):
393 """ Generate (petty) version of wleiden.yaml"""
394 output = generate_header("#")
395 output += format_wleiden_yaml(datadump)
396 return output
397
398
399
400def generate_config(node, config, datadump=None):
401 """ Print configuration file 'config' of 'node' """
402 output = ""
403 try:
404 # Load config file
405 if datadump == None:
406 datadump = get_yaml(node)
407
408 # Preformat certain needed variables for formatting and push those into special object
409 datadump_extra = copy.deepcopy(datadump)
410 if not datadump_extra.has_key('domain'):
411 datadump_extra['domain'] = 'wleiden.net'
412 datadump_extra['nodename_lower'] = datadump_extra['nodename'].lower()
413 datadump_extra['iface_keys'] = sorted([elem for elem in datadump.keys() if elem.startswith('iface_')])
414
415 if config == 'wleiden.yaml':
416 output += generate_wleiden_yaml(datadump)
417 elif config == 'authorized_keys':
418 f = open("global_keys", 'r')
419 output += f.read()
420 f.close()
421 elif config == 'dnsmasq.conf':
422 output += generate_dnsmasq_conf(datadump_extra)
423 elif config == 'rc.conf.local':
424 output += generate_rc_conf_local(datadump_extra)
425 elif config == 'resolv.conf':
426 output += generate_resolv_conf(datadump_extra)
427 else:
428 assert False, "Config not found!"
429 except IOError, e:
430 output += "[ERROR] Config file not found"
431 return output
432
433
434
435def process_cgi_request():
436 """ When calling from CGI """
437 # Update repository if requested
438 form = cgi.FieldStorage()
439 if form.getvalue("action") == "update":
440 print "Refresh: 5; url=."
441 print "Content-type:text/plain\r\n\r\n",
442 print "[INFO] Updating subverion, please wait..."
443 print subprocess.Popen(['svn', 'up', NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0],
444 print "[INFO] All done, redirecting in 5 seconds"
445 sys.exit(0)
446
447
448 uri = os.environ['PATH_INFO'].strip('/').split('/')
449 output = ""
450 if not uri[0]:
451 output += "Content-type:text/html\r\n\r\n"
452 output += generate_title(get_hostlist())
453 elif len(uri) == 1:
454 output += "Content-type:text/plain\r\n\r\n"
455 output += generate_node(uri[0])
456 elif len(uri) == 2:
457 output += "Content-type:text/plain\r\n\r\n"
458 output += generate_config(uri[0], uri[1])
459 else:
460 assert False, "Invalid option"
461 print output
462
463
464def usage():
465 print """Usage: %s <standalone [port] |test [test arguments]|static>
466Examples:
467\tstandalone = Run configurator webserver [default port=8000]
468\tstatic = Generate all config files and store on disk
469\t with format ./static/%%NODE%%/%%FILE%%
470\ttest CNodeRick dnsmasq.conf = Receive output of CGI script
471\t for arguments CNodeRick/dnsmasq.conf
472"""
473 exit(0)
474
475
476
477def main():
478 """Hard working sub"""
479 # Allow easy hacking using the CLI
480 if not os.environ.has_key('PATH_INFO'):
481 if len(sys.argv) < 2:
482 usage()
483
484 if sys.argv[1] == "standalone":
485 import SocketServer
486 import CGIHTTPServer
487 try:
488 PORT = int(sys.argv[2])
489 except (IndexError,ValueError):
490 PORT = 8000
491
492 class MyCGIHTTPRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
493 """ Serve this CGI from the root of the webserver """
494 def is_cgi(self):
495 if "favicon" in self.path:
496 return False
497
498 self.cgi_info = (__file__, self.path)
499 self.path = ''
500 return True
501 handler = MyCGIHTTPRequestHandler
502 httpd = SocketServer.TCPServer(("", PORT), handler)
503 httpd.server_name = 'localhost'
504 httpd.server_port = PORT
505
506 print "serving at port", PORT
507 httpd.serve_forever()
508 elif sys.argv[1] == "test":
509 os.environ['PATH_INFO'] = "/".join(sys.argv[2:])
510 os.environ['SCRIPT_NAME'] = __file__
511 process_cgi_request()
512 elif sys.argv[1] == "static":
513 items = dict()
514 for node in get_hostlist():
515 items['node'] = node
516 items['wdir'] = "./static/%(node)s" % items
517 if not os.path.isdir(items['wdir']):
518 os.makedirs(items['wdir'])
519 datadump = get_yaml(node)
520 for config in files:
521 items['config'] = config
522 print "## Generating %(node)s %(config)s" % items
523 f = open("%(wdir)s/%(config)s" % items, "w")
524 f.write(generate_config(node, config, datadump))
525 f.close()
526 else:
527 usage()
528 else:
529 cgitb.enable()
530 process_cgi_request()
531
532
533if __name__ == "__main__":
534 main()
Note: See TracBrowser for help on using the repository browser.