source: genesis/tools/gformat.py@ 9960

Last change on this file since 9960 was 9958, checked in by rick, 13 years ago

Make sure master is lower and slave is high.

Fix typo causing master ip to be re-used twice.

  • Property svn:executable set to *
  • Property svn:keywords set to Id
File size: 25.9 KB
RevLine 
[8242]1#!/usr/bin/env python
2#
3# vim:ts=2:et:sw=2:ai
4# Wireless Leiden configuration generator, based on yaml files'
[9957]5#
6# XXX: This should be rewritten to make use of the ipaddr.py library.
7#
[8242]8# Rick van der Zwet <info@rickvanderzwet.nl>
[9957]9#
[8622]10
11# Hack to make the script directory is also threated as a module search path.
12import sys
13import os
[9286]14import re
[8622]15sys.path.append(os.path.dirname(__file__))
16
[8242]17import cgi
[8267]18import cgitb
19import copy
[8242]20import glob
21import socket
22import string
23import subprocess
24import time
[8622]25import rdnap
[8584]26from pprint import pprint
[8575]27try:
28 import yaml
29except ImportError, e:
30 print e
31 print "[ERROR] Please install the python-yaml or devel/py-yaml package"
32 exit(1)
[8588]33
34try:
35 from yaml import CLoader as Loader
36 from yaml import CDumper as Dumper
37except ImportError:
38 from yaml import Loader, Dumper
39
[9697]40import logging
41logging.basicConfig(format='# %(levelname)s: %(message)s' )
42logger = logging.getLogger()
43logger.setLevel(logging.DEBUG)
[8242]44
[9283]45
[8948]46if os.environ.has_key('CONFIGROOT'):
47 NODE_DIR = os.environ['CONFIGROOT']
48else:
[9283]49 NODE_DIR = os.path.abspath(os.path.dirname(__file__)) + '/../nodes'
[8242]50__version__ = '$Id: gformat.py 9958 2012-02-14 10:57:30Z rick $'
51
[8267]52
[9283]53files = [
[8242]54 'authorized_keys',
55 'dnsmasq.conf',
56 'rc.conf.local',
57 'resolv.conf',
58 'wleiden.yaml'
59 ]
60
[8319]61# Global variables uses
[8323]62OK = 10
63DOWN = 20
64UNKNOWN = 90
[8257]65
66
[8267]67def get_proxylist():
68 """Get all available proxies proxyX sorting based on X number"""
69 os.chdir(NODE_DIR)
70 proxylist = sorted(glob.glob("proxy*"),
71 key=lambda name: int(''.join([c for c in name if c in string.digits])),
72 cmp=lambda x,y: x - y)
73 return proxylist
74
75
76
[8321]77def valid_addr(addr):
78 """ Show which address is valid in which are not """
79 return str(addr).startswith('172.')
80
81
[8267]82def get_nodelist():
83 """ Get all available nodes - sorted """
84 os.chdir(NODE_DIR)
85 nodelist = sorted(glob.glob("CNode*"))
86 return nodelist
87
[8296]88def get_hostlist():
89 """ Combined hosts and proxy list"""
90 return get_nodelist() + get_proxylist()
[8267]91
[8588]92def angle_between_points(lat1,lat2,long1,long2):
[9283]93 """
[8588]94 Return Angle in radians between two GPS coordinates
95 See: http://stackoverflow.com/questions/3809179/angle-between-2-gps-coordinates
96 """
97 dy = lat2 - lat1
98 dx = math.cos(math.pi/180*lat1)*(long2 - long1)
99 angle = math.atan2(dy,dx)
100 return angle
[8267]101
[8588]102def angle_to_cd(angle):
103 """ Return Dutch Cardinal Direction estimation in 'one digit' of radian angle """
104
105 # For easy conversion get positive degree
106 degrees = math.degrees(angle)
107 if degrees < 0:
108 360 - abs(degrees)
109
110 # Numbers can be confusing calculate from the 4 main directions
111 p = 22.5
112 if degrees < p:
113 return "n"
[9283]114 elif degrees < (90 - p):
[8588]115 return "no"
[9283]116 elif degrees < (90 + p):
[8588]117 return "o"
[9283]118 elif degrees < (180 - p):
[8588]119 return "zo"
[9283]120 elif degrees < (180 + p):
[8588]121 return "z"
[9283]122 elif degrees < (270 - p):
[8588]123 return "zw"
[9283]124 elif degrees < (270 + p):
[8588]125 return "w"
[9283]126 elif degrees < (360 - p):
[8588]127 return "nw"
128 else:
129 return "n"
130
131
[8267]132def generate_title(nodelist):
[8257]133 """ Main overview page """
[9283]134 items = {'root' : "." }
[8267]135 output = """
[8257]136<html>
137 <head>
138 <title>Wireless leiden Configurator - GFormat</title>
139 <style type="text/css">
140 th {background-color: #999999}
141 tr:nth-child(odd) {background-color: #cccccc}
142 tr:nth-child(even) {background-color: #ffffff}
143 th, td {padding: 0.1em 1em}
144 </style>
145 </head>
146 <body>
147 <center>
[8259]148 <form type="GET" action="%(root)s">
[8257]149 <input type="hidden" name="action" value="update">
150 <input type="submit" value="Update Configuration Database (SVN)">
151 </form>
152 <table>
153 <caption><h3>Wireless Leiden Configurator</h3></caption>
154 """ % items
[8242]155
[8296]156 for node in nodelist:
[8257]157 items['node'] = node
[8267]158 output += '<tr><td><a href="%(root)s/%(node)s">%(node)s</a></td>' % items
[8257]159 for config in files:
160 items['config'] = config
[8267]161 output += '<td><a href="%(root)s/%(node)s/%(config)s">%(config)s</a></td>' % items
162 output += "</tr>"
163 output += """
[8257]164 </table>
165 <hr />
166 <em>%s</em>
167 </center>
168 </body>
169</html>
170 """ % __version__
[8242]171
[8267]172 return output
[8257]173
174
[8267]175
176def generate_node(node):
[8257]177 """ Print overview of all files available for node """
[8267]178 return "\n".join(files)
[8242]179
[8257]180
181
[8242]182def generate_header(ctag="#"):
183 return """\
[9283]184%(ctag)s
[8242]185%(ctag)s DO NOT EDIT - Automatically generated by 'gformat'
186%(ctag)s Generated at %(date)s by %(host)s
[9283]187%(ctag)s
[8242]188""" % { 'ctag' : ctag, 'date' : time.ctime(), 'host' : socket.gethostname() }
189
[8257]190
191
[8242]192def parseaddr(s):
[8257]193 """ Process IPv4 CIDR notation addr to a (binary) number """
[8242]194 f = s.split('.')
195 return (long(f[0]) << 24L) + \
196 (long(f[1]) << 16L) + \
197 (long(f[2]) << 8L) + \
198 long(f[3])
199
[8257]200
201
[8242]202def showaddr(a):
[8257]203 """ Display IPv4 addr in (dotted) CIDR notation """
[8242]204 return "%d.%d.%d.%d" % ((a >> 24) & 0xff, (a >> 16) & 0xff, (a >> 8) & 0xff, a & 0xff)
205
[8257]206
[8584]207def is_member(ip, mask, canidate):
208 """ Return True if canidate is part of ip/mask block"""
209 ip_addr = gformat.parseaddr(ip)
210 ip_canidate = gformat.parseaddr(canidate)
211 mask = int(mask)
212 ip_addr = ip_addr & ~((1 << (32 - mask)) - 1)
213 ip_canidate = ip_canidate & ~((1 << (32 - mask)) - 1)
214 return ip_addr == ip_canidate
[8257]215
[8584]216
217
[9283]218
[8242]219def netmask2subnet(netmask):
[8257]220 """ Given a 'netmask' return corresponding CIDR """
[8242]221 return showaddr(0xffffffff & (0xffffffff << (32 - int(netmask))))
222
[8257]223
224
[8242]225def generate_dnsmasq_conf(datadump):
[8257]226 """ Generate configuration file '/usr/local/etc/dnsmasq.conf' """
[8242]227 output = generate_header()
228 output += """\
[9283]229# DHCP server options
[8242]230dhcp-authoritative
231dhcp-fqdn
232domain=dhcp.%(nodename_lower)s.%(domain)s
233domain-needed
234expand-hosts
235
236# Low memory footprint
237cache-size=10000
238 \n""" % datadump
239
240 for iface_key in datadump['iface_keys']:
[8262]241 if not datadump[iface_key].has_key('comment'):
242 datadump[iface_key]['comment'] = None
243 output += "## %(interface)s - %(desc)s - %(comment)s\n" % datadump[iface_key]
[8242]244
245 try:
[8257]246 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
[8242]247 (ip, netmask) = datadump[iface_key]['ip'].split('/')
248 datadump[iface_key]['subnet'] = netmask2subnet(netmask)
[8262]249 except (AttributeError, ValueError):
[8242]250 output += "# not autoritive\n\n"
251 continue
252
253 dhcp_part = ".".join(ip.split('.')[0:3])
254 datadump[iface_key]['dhcp_start'] = dhcp_part + "." + dhcp_start
255 datadump[iface_key]['dhcp_stop'] = dhcp_part + "." + dhcp_stop
256 output += "dhcp-range=%(interface)s,%(dhcp_start)s,%(dhcp_stop)s,%(subnet)s,24h\n\n" % datadump[iface_key]
[9283]257
[8242]258 return output
259
[8257]260
261
[8242]262def generate_rc_conf_local(datadump):
[8257]263 """ Generate configuration file '/etc/rc.conf.local' """
[8242]264 output = generate_header("#");
265 output += """\
266hostname='%(nodetype)s%(nodename)s.%(domain)s'
[9283]267location='%(location)s'
[8242]268""" % datadump
[9283]269
[8242]270 # TProxy configuration
271 output += "\n"
272 try:
273 if datadump['tproxy']:
274 output += """\
275tproxy_enable='YES'
276tproxy_range='%(tproxy)s'
277""" % datadump
278 except KeyError:
279 output += "tproxy_enable='NO'\n"
[9283]280
[8242]281 output += '\n'
282 # lo0 configuration:
283 # - 172.32.255.1/32 is the proxy.wleiden.net deflector
[9283]284 # - masterip is special as it needs to be assigned to at
[8242]285 # least one interface, so if not used assign to lo0
[9808]286 addrs_list = { 'lo0' : [("127.0.0.1/8", "LocalHost"), ("172.31.255.1/32","Proxy IP")] }
[9283]287 iface_map = {'lo0' : 'lo0'}
[8242]288
[8297]289 masterip_used = False
290 for iface_key in datadump['iface_keys']:
291 if datadump[iface_key]['ip'].startswith(datadump['masterip']):
292 masterip_used = True
293 break
[9283]294 if not masterip_used:
[8297]295 addrs_list['lo0'].append(datadump['masterip'] + "/32")
296
[8242]297 wlan_count = 0
298 for iface_key in datadump['iface_keys']:
299 ifacedump = datadump[iface_key]
300 interface = ifacedump['interface']
301 # By default no special interface mapping
302 iface_map[interface] = interface
303
304 # Add interface IP to list
[9808]305 item = (ifacedump['ip'], ifacedump['desc'])
[8242]306 if addrs_list.has_key(interface):
[9808]307 addrs_list[interface].append(item)
[8242]308 else:
[9808]309 addrs_list[interface] = [item]
[8242]310
311 # Alias only needs IP assignment for now, this might change if we
312 # are going to use virtual accesspoints
313 if "alias" in iface_key:
314 continue
315
316 # XXX: Might want to deduct type directly from interface name
317 if ifacedump['type'] in ['11a', '11b', '11g', 'wireless']:
318 # Create wlanX interface
[9283]319 ifacedump['wlanif'] ="wlan%i" % wlan_count
[8242]320 iface_map[interface] = ifacedump['wlanif']
321 wlan_count += 1
322
323 # Default to station (client) mode
324 ifacedump['wlanmode'] = "sta"
[8274]325 if ifacedump['mode'] in ['master', 'master-wds']:
[8242]326 ifacedump['wlanmode'] = "ap"
327 # Default to 802.11b mode
328 ifacedump['mode'] = '11b'
329 if ifacedump['type'] in ['11a', '11b' '11g']:
[9283]330 ifacedump['mode'] = ifacedump['type']
[8242]331
332 if not ifacedump.has_key('channel'):
333 if ifacedump['type'] == '11a':
334 ifacedump['channel'] = 36
335 else:
336 ifacedump['channel'] = 1
337
338 # Allow special hacks at the back like wds and stuff
339 if not ifacedump.has_key('extra'):
340 ifacedump['extra'] = 'regdomain ETSI country NL'
341
342 output += "wlans_%(interface)s='%(wlanif)s'\n" % ifacedump
343 output += ("create_args_%(wlanif)s='wlanmode %(wlanmode)s mode " +\
[8274]344 "%(mode)s ssid %(ssid)s %(extra)s channel %(channel)s'\n") % ifacedump
[9283]345
[8242]346 elif ifacedump['type'] in ['ethernet', 'eth']:
347 # No special config needed besides IP
348 pass
349 else:
350 assert False, "Unknown type " + ifacedump['type']
351
[9283]352 # Print IP address which needs to be assigned over here
[8242]353 output += "\n"
354 for iface,addrs in sorted(addrs_list.iteritems()):
[9808]355 for addr,comment in addrs:
356 output += "# %s || %s || %s\n" % (iface, addr, comment)
357 output += "ipv4_addrs_%s='%s'\n\n" % (iface_map[iface], " ".join([x[0] for x in addrs]))
[8242]358
359 return output
360
[8257]361
362
[8242]363def get_yaml(item):
[8257]364 """ Get configuration yaml for 'item'"""
[9284]365 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
[8242]366
367 f = open(gfile, 'r')
[8588]368 datadump = yaml.load(f,Loader=Loader)
[8242]369 f.close()
370
371 return datadump
372
[8588]373def store_yaml(datadump):
374 """ Store configuration yaml for 'item'"""
[9284]375 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
[8257]376
[8588]377 f = open(gfile, 'w')
378 f.write(generate_wleiden_yaml(datadump))
379 f.close()
[8257]380
[8588]381
382
[8317]383def get_all_configs():
384 """ Get dict with key 'host' with all configs present """
385 configs = dict()
386 for host in get_hostlist():
387 datadump = get_yaml(host)
388 configs[host] = datadump
389 return configs
390
391
[8319]392def get_interface_keys(config):
393 """ Quick hack to get all interface keys, later stage convert this to a iterator """
394 return [elem for elem in config.keys() if (elem.startswith('iface_') and not "lo0" in elem)]
[8317]395
[8319]396
[8317]397def get_used_ips(configs):
398 """ Return array of all IPs used in config files"""
399 ip_list = []
[8319]400 for config in configs:
[8317]401 ip_list.append(config['masterip'])
[8319]402 for iface_key in get_interface_keys(config):
[8317]403 l = config[iface_key]['ip']
404 addr, mask = l.split('/')
405 # Special case do not process
[8332]406 if valid_addr(addr):
407 ip_list.append(addr)
408 else:
[9728]409 logger.error("## IP '%s' in '%s' not valid" % (addr, config['nodename']))
[8317]410 return sorted(ip_list)
411
412
413
[8267]414def write_yaml(item, datadump):
415 """ Write configuration yaml for 'item'"""
[9284]416 gfile = os.path.join(NODE_DIR,item,'wleiden.yaml')
[8267]417
418 f = open(gfile, 'w')
419 f.write(format_wleiden_yaml(datadump))
420 f.close()
421
422
423
[8242]424def generate_resolv_conf(datadump):
[8257]425 """ Generate configuration file '/etc/resolv.conf' """
[8242]426 output = generate_header("#");
427 output += """\
428search wleiden.net
429# Try local (cache) first
430nameserver 127.0.0.1
431
[9283]432# Proxies are recursive nameservers
[8242]433# needs to be in resolv.conf for dnsmasq as well
434""" % datadump
[9283]435
[8267]436 for proxy in get_proxylist():
[8242]437 proxy_ip = get_yaml(proxy)['masterip']
438 output += "nameserver %-15s # %s\n" % (proxy_ip, proxy)
439 return output
440
441
[8257]442
[8267]443def format_yaml_value(value):
444 """ Get yaml value in right syntax for outputting """
445 if isinstance(value,str):
446 output = "'%s'" % value
447 else:
448 output = value
[9283]449 return output
[8267]450
451
452
453def format_wleiden_yaml(datadump):
[8242]454 """ Special formatting to ensure it is editable"""
[9283]455 output = "# Genesis config yaml style\n"
[8262]456 output += "# vim:ts=2:et:sw=2:ai\n"
[8242]457 output += "#\n"
458 iface_keys = [elem for elem in datadump.keys() if elem.startswith('iface_')]
459 for key in sorted(set(datadump.keys()) - set(iface_keys)):
[8267]460 output += "%-10s: %s\n" % (key, format_yaml_value(datadump[key]))
[9283]461
[8242]462 output += "\n\n"
[9283]463
[8272]464 key_order = [ 'comment', 'interface', 'ip', 'desc', 'sdesc', 'mode', 'type',
465 'extra_type', 'channel', 'ssid', 'dhcp' ]
466
[8242]467 for iface_key in sorted(iface_keys):
468 output += "%s:\n" % iface_key
[8272]469 for key in key_order + list(sorted(set(datadump[iface_key].keys()) - set(key_order))):
470 if datadump[iface_key].has_key(key):
[9283]471 output += " %-11s: %s\n" % (key, format_yaml_value(datadump[iface_key][key]))
[8242]472 output += "\n\n"
473
474 return output
475
476
[8257]477
[8267]478def generate_wleiden_yaml(datadump):
479 """ Generate (petty) version of wleiden.yaml"""
480 output = generate_header("#")
481 output += format_wleiden_yaml(datadump)
482 return output
483
484
[8588]485def generate_yaml(datadump):
486 return generate_config(datadump['nodename'], "wleiden.yaml", datadump)
[8267]487
[8588]488
[9283]489
[8298]490def generate_config(node, config, datadump=None):
[8257]491 """ Print configuration file 'config' of 'node' """
[8267]492 output = ""
[8242]493 try:
494 # Load config file
[8298]495 if datadump == None:
496 datadump = get_yaml(node)
[9283]497
[8267]498 # Preformat certain needed variables for formatting and push those into special object
499 datadump_extra = copy.deepcopy(datadump)
500 if not datadump_extra.has_key('domain'):
501 datadump_extra['domain'] = 'wleiden.net'
502 datadump_extra['nodename_lower'] = datadump_extra['nodename'].lower()
503 datadump_extra['iface_keys'] = sorted([elem for elem in datadump.keys() if elem.startswith('iface_')])
504
[8242]505 if config == 'wleiden.yaml':
[8267]506 output += generate_wleiden_yaml(datadump)
507 elif config == 'authorized_keys':
[8242]508 f = open("global_keys", 'r')
[8267]509 output += f.read()
[8242]510 f.close()
511 elif config == 'dnsmasq.conf':
[8267]512 output += generate_dnsmasq_conf(datadump_extra)
[8242]513 elif config == 'rc.conf.local':
[8267]514 output += generate_rc_conf_local(datadump_extra)
[8242]515 elif config == 'resolv.conf':
[8267]516 output += generate_resolv_conf(datadump_extra)
[8242]517 else:
[9283]518 assert False, "Config not found!"
[8242]519 except IOError, e:
[8267]520 output += "[ERROR] Config file not found"
521 return output
[8242]522
523
[8257]524
[8258]525def process_cgi_request():
526 """ When calling from CGI """
527 # Update repository if requested
528 form = cgi.FieldStorage()
529 if form.getvalue("action") == "update":
[8259]530 print "Refresh: 5; url=."
[8258]531 print "Content-type:text/plain\r\n\r\n",
532 print "[INFO] Updating subverion, please wait..."
533 print subprocess.Popen(['svn', 'up', NODE_DIR], stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0],
534 print "[INFO] All done, redirecting in 5 seconds"
535 sys.exit(0)
[9283]536
537
[8258]538 uri = os.environ['PATH_INFO'].strip('/').split('/')
[8267]539 output = ""
[8258]540 if not uri[0]:
[8267]541 output += "Content-type:text/html\r\n\r\n"
[8296]542 output += generate_title(get_hostlist())
[8258]543 elif len(uri) == 1:
[8267]544 output += "Content-type:text/plain\r\n\r\n"
545 output += generate_node(uri[0])
[8258]546 elif len(uri) == 2:
[8267]547 output += "Content-type:text/plain\r\n\r\n"
548 output += generate_config(uri[0], uri[1])
[8258]549 else:
550 assert False, "Invalid option"
[8267]551 print output
[8242]552
[8588]553def get_fqdn(datadump):
554 # Proxy naming convention is special
555 if datadump['nodetype'] == 'Proxy':
556 fqdn = datadump['nodename']
557 else:
558 # By default the full name is listed and also a shortname CNAME for easy use.
559 fqdn = datadump['nodetype'] + datadump['nodename']
560 return(fqdn)
[8259]561
[9283]562
563
[9284]564def make_dns(output_dir = 'dns'):
[8588]565 items = dict()
[8598]566
[8588]567 # hostname is key, IP is value
568 wleiden_zone = dict()
569 wleiden_cname = dict()
[8598]570
[8588]571 pool = dict()
572 for node in get_hostlist():
[9697]573 logger.info("Processing host %s", node)
[8588]574 datadump = get_yaml(node)
[9283]575
[8588]576 # Proxy naming convention is special
577 fqdn = get_fqdn(datadump)
578 if datadump['nodetype'] == 'CNode':
579 wleiden_cname[datadump['nodename']] = fqdn
580
581 wleiden_zone[fqdn] = datadump['masterip']
582
[8598]583 # Hacking to get proper DHCP IPs and hostnames
[8588]584 for iface_key in get_interface_keys(datadump):
[8598]585 iface_name = datadump[iface_key]['interface'].replace(':',"-alias-")
[8588]586 (ip, netmask) = datadump[iface_key]['ip'].split('/')
587 try:
588 (dhcp_start, dhcp_stop) = datadump[iface_key]['dhcp'].split('-')
589 datadump[iface_key]['subnet'] = netmask2subnet(netmask)
590 dhcp_part = ".".join(ip.split('.')[0:3])
591 if ip != datadump['masterip']:
592 wleiden_zone["dhcp-gateway-%s.%s" % (iface_name, fqdn)] = ip
593 for i in range(int(dhcp_start), int(dhcp_stop) + 1):
594 wleiden_zone["dhcp-%s-%s.%s" % (i, iface_name, fqdn)] = "%s.%s" % (dhcp_part, i)
595 except (AttributeError, ValueError):
596 # First push it into a pool, to indentify the counter-part later on
597 addr = parseaddr(ip)
598 netmask = int(netmask)
599 addr = addr & ~((1 << (32 - netmask)) - 1)
[9283]600 if pool.has_key(addr):
[8588]601 pool[addr] += [(iface_name, fqdn, ip)]
[9283]602 else:
[8588]603 pool[addr] = [(iface_name, fqdn, ip)]
604 continue
605
[9286]606
607 def pool_to_name(node, pool_members):
608 """Convert the joined name to a usable pool name"""
609
610 # Get rid of the own entry
611 pool_members = list(set(pool_members) - set([fqdn]))
612
613 target = oldname = ''
614 for node in sorted(pool_members):
615 (name, number) = re.match('^([A-Za-z]+)([0-9]*)$',node).group(1,2)
616 target += "-" + number if name == oldname else "-" + node if target else node
617 oldname = name
618
619 return target
620
621
[9957]622 # WL uses an /29 to configure an interface. IP's are ordered like this:
[9958]623 # MasterA (.1) -- DeviceA (.2) <<>> DeviceB (.3) --- SlaveB (.4)
[9957]624
625 sn = lambda x: re.sub(r'(?i)^cnode','',x)
626
[8598]627 # Automatic naming convention of interlinks namely 2 + remote.lower()
[8588]628 for (key,value) in pool.iteritems():
[9958]629 # Make sure they are sorted from low-ip to high-ip
630 value = sorted(value, key=lambda x: parseaddr(x[2]))
631
[8588]632 if len(value) == 1:
633 (iface_name, fqdn, ip) = value[0]
634 wleiden_zone["2unused-%s.%s" % (iface_name, fqdn)] = ip
[9957]635
636 # Device DNS names
637 if 'cnode' in fqdn.lower():
638 wleiden_zone["d-at-%s.%s" % (iface_name, fqdn)] = showaddr(parseaddr(ip) + 1)
639 wleiden_cname["d-at-%s.%s" % (iface_name,sn(fqdn))] = "d-at-%s.%s" % (iface_name, fqdn)
640
[8588]641 elif len(value) == 2:
642 (a_iface_name, a_fqdn, a_ip) = value[0]
643 (b_iface_name, b_fqdn, b_ip) = value[1]
644 wleiden_zone["2%s.%s" % (b_fqdn,a_fqdn)] = a_ip
645 wleiden_zone["2%s.%s" % (a_fqdn,b_fqdn)] = b_ip
[9957]646
647 # Device DNS names
648 if 'cnode' in a_fqdn.lower() and 'cnode' in b_fqdn.lower():
649 wleiden_zone["d-at-%s.%s" % (a_iface_name, a_fqdn)] = showaddr(parseaddr(a_ip) + 1)
[9958]650 wleiden_zone["d-at-%s.%s" % (b_iface_name, b_fqdn)] = showaddr(parseaddr(b_ip) - 1)
[9957]651 wleiden_cname["d-at-%s.%s" % (a_iface_name,sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
652 wleiden_cname["d-at-%s.%s" % (b_iface_name,sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
653 wleiden_cname["d2%s.%s" % (sn(b_fqdn),sn(a_fqdn))] = "d-at-%s.%s" % (a_iface_name, a_fqdn)
654 wleiden_cname["d2%s.%s" % (sn(a_fqdn),sn(b_fqdn))] = "d-at-%s.%s" % (b_iface_name, b_fqdn)
655
[8588]656 else:
657 pool_members = [k[1] for k in value]
658 for item in value:
[9283]659 (iface_name, fqdn, ip) = item
[9286]660 pool_name = "2pool-" + showaddr(key).replace('.','-') + "-" + pool_to_name(fqdn,pool_members)
[8588]661 wleiden_zone["%s.%s" % (pool_name, fqdn)] = ip
[8598]662
663 # Include static DNS entries
664 # XXX: Should they override the autogenerated results?
665 # XXX: Convert input to yaml more useable.
666 # Format:
667 ##; this is a comment
668 ## roomburgh=CNodeRoomburgh1
669 ## apkerk1.CNodeVosko=172.17.176.8 ;this as well
[9284]670 dns = yaml.load(open(os.path.join(NODE_DIR,'../dns/staticDNS.yaml'),'r'))
[9938]671
672 # Hack to allow special entries, for development
673 wleiden_raw = dns['raw']
674 del dns['raw']
675
[8622]676 for comment, block in dns.iteritems():
677 for k,v in block.iteritems():
[8598]678 if valid_addr(v):
679 wleiden_zone[k] = v
680 else:
681 wleiden_cname[k] = v
[9283]682
[8598]683 details = dict()
684 # 24 updates a day allowed
685 details['serial'] = time.strftime('%Y%m%d%H')
686
687 dns_header = '''
688$TTL 3h
689%(zone)s. SOA sunny.wleiden.net. beheer.lijst.wirelessleiden.nl. ( %(serial)s 1d 12h 1w 3h )
690 ; Serial, Refresh, Retry, Expire, Neg. cache TTL
691
692 NS sunny.wleiden.net.
693 \n'''
694
[9283]695
[8598]696 if not os.path.isdir('dns'):
697 os.makedirs('dns')
698 details['zone'] = 'wleiden.net'
[9284]699 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
[8598]700 f.write(dns_header % details)
701
[8588]702 for host,ip in wleiden_zone.iteritems():
[8598]703 if valid_addr(ip):
[9283]704 f.write("%s.wleiden.net. IN A %s \n" % (host.lower(), ip))
[8588]705 for source,dest in wleiden_cname.iteritems():
[8636]706 f.write("%s.wleiden.net. IN CNAME %s.wleiden.net.\n" % (source.lower(), dest.lower()))
[9938]707 for source, dest in wleiden_raw.iteritems():
708 f.write("%s.wleiden.net. %s\n" % (source, dest))
[8588]709 f.close()
[9283]710
[8598]711 # Create whole bunch of specific sub arpa zones. To keep it compliant
712 for s in range(16,32):
713 details['zone'] = '%i.172.in-addr.arpa' % s
[9284]714 f = open(os.path.join(output_dir,"db." + details['zone']), "w")
[8598]715 f.write(dns_header % details)
[8588]716
[8598]717 #XXX: Not effient, fix to proper data structure and do checks at other
718 # stages
719 for host,ip in wleiden_zone.iteritems():
720 if valid_addr(ip):
721 if int(ip.split('.')[1]) == s:
722 rev_ip = '.'.join(reversed(ip.split('.')))
[9283]723 f.write("%s.in-addr.arpa. IN PTR %s.wleiden.net.\n" % (rev_ip.lower(), host.lower()))
[8598]724 f.close()
[8588]725
[8598]726
[8259]727def usage():
[8598]728 print """Usage: %s <standalone [port] |test [test arguments]|static|dns>
[8259]729Examples:
[9284]730\tdns [outputdir] = Generate BIND compliant zone files in dns.
[8259]731\tstandalone = Run configurator webserver [default port=8000]
[9589]732\twind-export = Generate SQL import scripts for WIND database
733\tfull-export = Generate yaml export script for heatmap.
[8296]734\tstatic = Generate all config files and store on disk
735\t with format ./static/%%NODE%%/%%FILE%%
[9283]736\ttest CNodeRick dnsmasq.conf = Receive output of CGI script
[8259]737\t for arguments CNodeRick/dnsmasq.conf
738"""
739 exit(0)
740
741
742
[8267]743def main():
744 """Hard working sub"""
745 # Allow easy hacking using the CLI
746 if not os.environ.has_key('PATH_INFO'):
747 if len(sys.argv) < 2:
748 usage()
[9283]749
[8267]750 if sys.argv[1] == "standalone":
751 import SocketServer
752 import CGIHTTPServer
[8867]753 # CGI does not go backward, little hack to get ourself in the right working directory.
754 os.chdir(os.path.dirname(__file__) + '/..')
[8267]755 try:
756 PORT = int(sys.argv[2])
757 except (IndexError,ValueError):
758 PORT = 8000
[9283]759
[8267]760 class MyCGIHTTPRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler):
761 """ Serve this CGI from the root of the webserver """
762 def is_cgi(self):
763 if "favicon" in self.path:
764 return False
[9283]765
[8267]766 self.cgi_info = (__file__, self.path)
767 self.path = ''
768 return True
769 handler = MyCGIHTTPRequestHandler
[9807]770 SocketServer.TCPServer.allow_reuse_address = True
[8267]771 httpd = SocketServer.TCPServer(("", PORT), handler)
772 httpd.server_name = 'localhost'
773 httpd.server_port = PORT
[9283]774
[9728]775 logger.info("serving at port %s", PORT)
[8860]776 try:
777 httpd.serve_forever()
778 except KeyboardInterrupt:
779 httpd.shutdown()
[9728]780 logger.info("All done goodbye")
[8267]781 elif sys.argv[1] == "test":
782 os.environ['PATH_INFO'] = "/".join(sys.argv[2:])
783 os.environ['SCRIPT_NAME'] = __file__
784 process_cgi_request()
[8296]785 elif sys.argv[1] == "static":
786 items = dict()
787 for node in get_hostlist():
788 items['node'] = node
789 items['wdir'] = "./static/%(node)s" % items
790 if not os.path.isdir(items['wdir']):
791 os.makedirs(items['wdir'])
[8298]792 datadump = get_yaml(node)
[8296]793 for config in files:
794 items['config'] = config
[9728]795 logger.info("## Generating %(node)s %(config)s" % items)
[8296]796 f = open("%(wdir)s/%(config)s" % items, "w")
[8298]797 f.write(generate_config(node, config, datadump))
[8296]798 f.close()
[9514]799 elif sys.argv[1] == "wind-export":
800 items = dict()
801 for node in get_hostlist():
802 datadump = get_yaml(node)
803 sql = """INSERT IGNORE INTO nodes (name, name_ns, longitude, latitude)
804 VALUES ('%(nodename)s', '%(nodename)s', %(latitude)s, %(longitude)s);""" % datadump;
805 sql = """INSERT IGNORE INTO users_nodes (user_id, node_id, owner)
806 VALUES (
807 (SELECT id FROM users WHERE username = 'rvdzwet'),
808 (SELECT id FROM nodes WHERE name = '%(nodename)s'),
809 'Y');""" % datadump
810 #for config in files:
811 # items['config'] = config
812 # print "## Generating %(node)s %(config)s" % items
813 # f = open("%(wdir)s/%(config)s" % items, "w")
814 # f.write(generate_config(node, config, datadump))
815 # f.close()
816 for node in get_hostlist():
817 datadump = get_yaml(node)
818 for iface_key in sorted([elem for elem in datadump.keys() if elem.startswith('iface_')]):
819 ifacedump = datadump[iface_key]
820 if ifacedump.has_key('mode') and ifacedump['mode'] == 'ap-wds':
821 ifacedump['nodename'] = datadump['nodename']
822 if not ifacedump.has_key('channel') or not ifacedump['channel']:
823 ifacedump['channel'] = 0
824 sql = """INSERT INTO links (node_id, type, ssid, protocol, channel, status)
825 VALUES ((SELECT id FROM nodes WHERE name = '%(nodename)s'), 'ap',
826 '%(ssid)s', 'IEEE 802.11b', %(channel)s, 'active');""" % ifacedump
[9589]827 elif sys.argv[1] == "full-export":
828 hosts = {}
829 for node in get_hostlist():
830 datadump = get_yaml(node)
831 hosts[datadump['nodename']] = datadump
832 print yaml.dump(hosts)
833
[8584]834 elif sys.argv[1] == "dns":
[9285]835 make_dns(sys.argv[2] if len(sys.argv) > 2 else 'dns')
[9283]836 elif sys.argv[1] == "cleanup":
[8588]837 # First generate all datadumps
838 datadumps = dict()
839 for host in get_hostlist():
[9728]840 logger.info("# Processing: %s", host)
[8588]841 datadump = get_yaml(host)
842 datadumps[get_fqdn(datadump)] = datadump
[9283]843
[8622]844 datadump['latitude'], datadump['longitude'] = rdnap.rd2etrs(datadump['rdnap_x'], datadump['rdnap_y'])
[8588]845 write_yaml(host, datadump)
[9283]846 else:
847 usage()
848 else:
849 cgitb.enable()
850 process_cgi_request()
851
852
853if __name__ == "__main__":
854 main()
Note: See TracBrowser for help on using the repository browser.