remove ugly hack
[myplc.git] / support-scripts / gen-static-content.py
1 #!/usr/bin/env /usr/bin/plcsh
2 #
3 # Generates static versions of expensive web pages
4 #
5 # Mark Huang <mlhuang@cs.princeton.edu>
6 # Copyright (C) 2005 The Trustees of Princeton University
7 #
8
9 import os, sys, shutil
10 import time
11 import string
12 import codecs
13 import socket
14 import urllib2
15 import csv
16
17 SCRIPT_PID_FILE= "/var/run/gen-static-content.pid"
18
19 # where to store the generated files
20 GENERATED_OUTPUT_PATH= '/var/www/html/generated'
21
22 # this php block, if put at the top of the files,
23 # will enable them to be downloaded without the php
24 # engine parsing them
25 DISABLE_PHP_BLOCK= \
26 """<?php
27 if( isset($_GET['disablephp']) )
28   {
29     readfile(__FILE__);
30     exit();
31   }
32 ?>
33 """
34
35 # Globals
36 all_nodes = []
37 all_sites = []
38 node_group_nodes = {}
39
40 # return a php page that has node and site counts in it
41 def GetCountsFileContent(f):
42     f.write( DISABLE_PHP_BLOCK )
43     f.write( "<?php\n" )
44
45     node_count = len(all_nodes)
46     f.write( "$node_count= %s;\n" % node_count )
47     
48     site_count= len(all_sites)
49     f.write( "$site_count= %s;\n" % site_count )
50
51     f.write( "?>" )
52
53
54 # generate a plain text file in ~/.ssh/known_hosts format
55 def GetHostKeys(f):
56     time_generated= time.strftime("%a, %d %b %Y %H:%M:%S")
57
58     f.write( DISABLE_PHP_BLOCK )
59     
60     f.write( "<?php\n" )
61     f.write( "$node_list_generated_time= '%s';\n" % time_generated )
62     f.write( "header('Content-type: text/plain');\n" )
63     f.write( "?>\n" )
64
65     nodes = all_nodes
66
67     for node in all_nodes:
68         hostname = node['hostname']
69         ssh_rsa_key = node['ssh_rsa_key']
70         ip = node['ip']
71         if ssh_rsa_key:
72             if hostname:
73                 f.write( "%s %s\n" % (hostname, ssh_rsa_key) )
74             if ip:
75                 f.write( "%s %s\n" % (ip, ssh_rsa_key) )
76
77
78 # return php content that includes all the node lists
79 def GetNodeListsContent(f):
80     time_generated= time.strftime("%a, %d %b %Y %H:%M:%S")
81
82     f.write( DISABLE_PHP_BLOCK )
83     
84     f.write( "<?php\n" )
85     f.write( "$node_list_generated_time= '%s';\n" % time_generated )
86
87     # Nodes with primary IP addresses in boot state
88     nodes_in_boot = filter(lambda node: node['boot_state'] == "boot" and node['ip'],
89                            all_nodes)
90
91     # Hostnames
92     all_hosts = [node['hostname'] for node in nodes_in_boot]
93     f.write( "if( $which_node_list == 'all_hosts' )\n" )
94     f.write( "{\n" )
95     f.write( "?>\n" )
96     f.write( "\n".join(all_hosts) + "\n" )
97     f.write( "<?php\n" )
98     f.write( "}\n" )
99
100     # IPs
101     all_ips = [node['ip'] for node in nodes_in_boot]
102     f.write( "elseif( $which_node_list == 'all_ips' )\n" )
103     f.write( "{\n" )
104     f.write( "?>\n" )
105     f.write( "\n".join(all_ips) + "\n" )
106     f.write( "<?php\n" )
107     f.write( "}\n" )
108
109     # /etc/hosts entries
110     etc_hosts = [node['ip'] + "\t" + node['hostname'] for node in nodes_in_boot]
111     f.write( "elseif( $which_node_list == 'etc_hosts' )\n" )
112     f.write( "{\n" )
113     f.write( "?>\n" )
114     # Create a localhost entry for convenience
115     f.write( "127.0.0.1\tlocalhost.localdomain localhost\n" )
116     f.write( "\n".join(etc_hosts) + "\n" )
117     f.write( "<?php\n" )
118     f.write( "}\n" )
119
120     for group in ['Alpha', 'Beta']:
121         if not node_group_nodes.has_key(group):
122             node_group_nodes[group] = []
123
124         # Group nodes with primary IP addresses in boot state
125         group_nodes_in_boot = filter(lambda node: node['boot_state'] == "boot" and node['ip'],
126                                      node_group_nodes[group])
127
128         # Group hostnames
129         group_hosts = [node['hostname'] for node in group_nodes_in_boot]
130         f.write( "elseif( $which_node_list == '%s_hosts' )\n" % group.lower() )
131         f.write( "{\n" )
132         f.write( "?>\n" )
133         f.write( "\n".join(group_hosts) + "\n" )
134         f.write( "<?php\n" )
135         f.write( "}\n" )
136
137         # Group IPs
138         group_ips = [node['ip'] for node in group_nodes_in_boot]
139         f.write( "elseif( $which_node_list == '%s_ips' )\n" % group.lower() )
140         f.write( "{\n" )
141         f.write( "?>\n" )
142         f.write( "\n".join(group_ips) + "\n" )
143         f.write( "<?php\n" )
144         f.write( "}\n" )
145
146     # All production nodes (nodes not in Alpha or Beta)
147     production_nodes_in_boot = filter(lambda node: node not in node_group_nodes['Alpha'] and \
148                                                    node not in node_group_nodes['Beta'],
149                                       nodes_in_boot)
150
151     production_hosts = [node['hostname'] for node in production_nodes_in_boot]                           
152     f.write( "elseif( $which_node_list == 'production_hosts' )\n" )
153     f.write( "{\n" )
154     f.write( "?>\n" )
155     f.write( "\n".join(production_hosts) + "\n" )
156     f.write( "<?php\n" )
157     f.write( "}\n" )
158
159     production_ips = [node['ip'] for node in production_nodes_in_boot]                           
160     f.write( "elseif( $which_node_list == 'production_ips' )\n" )
161     f.write( "{\n" )
162     f.write( "?>\n" )
163     f.write( "\n".join(production_ips) + "\n" )
164     f.write( "<?php\n" )
165     f.write( "}\n" )
166     f.write( "?>" )
167
168
169 def GetPlanetFlowStats(f):
170     if hasattr(config, 'PLANETFLOW_BASE'):
171         url = "http://" + config.PLANETFLOW_BASE
172     else:
173         return
174
175     # Slices to calculate detailed statistics for
176     slices = [
177         'cmu_esm',
178         'cornell_beehive',
179         'cornell_cobweb',
180         'cornell_codons',
181         'michigan_tmesh',
182         'nyu_d',
183         'princeton_codeen',
184         'princeton_coblitz',
185         'princeton_comon',
186         'rice_epost',
187         'ucb_bamboo',
188         'ucb_i3',
189         'ucsd_sword',
190         'upenn_dharma',
191         'idsl_psepr',
192         'ucb_ganglia',
193         'cmu_irislog',
194         'tennessee_hliu'
195         ]
196
197     # Seconds to wait
198     socket.setdefaulttimeout(3600)
199
200     url = url + '/slice.php?csv=1&start_time=2+days+ago'
201     if slices:
202         url = url + '&slices[]=' + '&slices[]='.join(slices)
203     stats = urllib2.urlopen(url)
204     fields = ['slice', 'flows', 'packets', 'bytes', 'src_ips',
205               'dst_ips', 'top_dst_ip', 'top_dst_ip_bytes']
206     rows = csv.DictReader(stats, fields)
207     f.write("<?php\n")
208     f.write("$planetflow = array(\n")
209     for row in rows:
210         if row.has_key('slice'):
211             f.write("'%s' => array(\n" % row['slice'])
212             for field in fields:
213                 if row.has_key(field) and \
214                    row[field] is not None and \
215                    row[field] != "":
216                     if type(row[field]) == type(0):
217                         f.write("\t'%s' => %d,\n" % (field, int(row[field])))
218                     else:
219                         f.write("\t'%s' => '%s',\n" % (field, row[field]))
220             f.write("),\n")
221     f.write(");\n")
222     f.write("?>")
223
224
225
226 def GenDistMap():
227     # update the node distribution map
228     datadir = '/var/www/html/plot-latlong'
229
230     # plot-latlong looks for .mapinfo and .mapimages in $HOME
231     os.environ['HOME'] = datadir
232
233     if hasattr(config, 'PLC_WWW_MAPIMAGE'):
234         image = config.PLC_WWW_MAPIMAGE
235     else:
236         image = "World50"
237
238     (child_stdin,
239      child_stdout) = \
240      os.popen2('perl ' + datadir + os.sep + 'plot-latlong -m "%s" -s 3' % image)
241
242     for site in all_sites:
243         if site['latitude'] and site['longitude']:
244             child_stdin.write("%f %f\n" % \
245                               (site['latitude'], site['longitude']))
246     child_stdin.close()
247
248     map = file(GENERATED_OUTPUT_PATH + os.sep + image + '.png', 'w')
249     map.write(child_stdout.read())
250     child_stdout.close()
251     map.close()
252
253
254 # which files to generate, and the functions in
255 # this script to call to get the content for
256 STATIC_FILE_LIST= (
257     ('_gen_counts.php',GetCountsFileContent),
258     ('_gen_node_lists.php',GetNodeListsContent),
259     ('_gen_known_hosts.php',GetHostKeys),
260     ('_gen_planetflow.php',GetPlanetFlowStats),
261     (None,GenDistMap)
262     )
263
264
265 if __name__ == '__main__':
266
267     # see if we are already running by checking the existance
268     # of a PID file, and if it exists, attempting a test kill
269     # to see if the process really does exist. If both of these
270     # tests pass, exit.
271         
272     if os.access(SCRIPT_PID_FILE, os.R_OK):
273         pid= string.strip(file(SCRIPT_PID_FILE).readline())
274         if pid <> "":
275             if os.system("/bin/kill -0 %s > /dev/null 2>&1" % pid) == 0:
276                 sys.exit(0)
277             
278     # write out our process id
279     pidfile= file( SCRIPT_PID_FILE, 'w' )
280     pidfile.write( "%d\n" % os.getpid() )
281     pidfile.close()
282     pidfile= None
283
284     # Get all nodes and sites
285     begin()
286     GetNodes(None, ['node_id', 'hostname', 'boot_state', 'ssh_rsa_key', 'interface_ids'])
287     GetInterfaces(None, ['interface_id', 'ip', 'is_primary'])
288     GetSites(None, ['site_id', 'latitude', 'longitude'])
289     GetNodeGroups(None, ['nodegroup_id', 'tagname', 'node_ids'])
290     (all_nodes, all_nodenetworks, all_sites, all_groups) = commit()
291
292     all_nodenetworks = dict([(nodenetwork['interface_id'], nodenetwork) \
293                              for nodenetwork in all_nodenetworks])
294
295     # Set primary IP, if any
296     for node in all_nodes:
297         node['ip'] = None
298         for interface_id in node['interface_ids']:
299             try:
300                 nodenetwork = all_nodenetworks[interface_id]
301                 if nodenetwork['is_primary']:
302                     node['ip'] = nodenetwork['ip']
303                 break
304             except IndexError, KeyError:
305                 continue
306
307     # Get list of nodes in each node group
308     for group in all_groups:
309         nodes_in_group = filter(lambda node: node['node_id'] in group['node_ids'], all_nodes)
310         node_group_nodes[group['tagname']] = nodes_in_group
311
312     # generate the static content files
313     for (file_name,func) in STATIC_FILE_LIST:
314         if file_name is not None:
315             try:
316                 output_file_path= "%s/%s" % (GENERATED_OUTPUT_PATH,file_name)
317                 tmp_output_file_path= output_file_path + '.tmp'
318                 tmp_output_file= codecs.open( tmp_output_file_path, encoding = 'utf-8', mode = "w" )
319             except IOError, err:
320                 print( "Unable to open file %s for writing." % output_file_path )
321                 continue
322
323             try:
324                 func(tmp_output_file)
325                 tmp_output_file.flush()
326                 shutil.copyfile( tmp_output_file_path, output_file_path )
327             except Exception, e:
328                 print "Unable to get content for file: %s" % file_name, e
329                 import traceback
330                 traceback.print_exc()
331
332             tmp_output_file.close()
333             tmp_output_file= None
334             os.unlink( tmp_output_file_path )
335         else:
336             func()
337
338     # remove the PID file
339     os.unlink( SCRIPT_PID_FILE )