/usr/bin/env python3
[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 urllib.request, urllib.error, urllib.parse
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 = [node for node in all_nodes if node['boot_state'] == "boot" and node['ip']]
89
90     # Hostnames
91     all_hosts = [node['hostname'] for node in nodes_in_boot]
92     f.write( "if( $which_node_list == 'all_hosts' )\n" )
93     f.write( "{\n" )
94     f.write( "?>\n" )
95     f.write( "\n".join(all_hosts) + "\n" )
96     f.write( "<?php\n" )
97     f.write( "}\n" )
98
99     # IPs
100     all_ips = [node['ip'] for node in nodes_in_boot]
101     f.write( "elseif( $which_node_list == 'all_ips' )\n" )
102     f.write( "{\n" )
103     f.write( "?>\n" )
104     f.write( "\n".join(all_ips) + "\n" )
105     f.write( "<?php\n" )
106     f.write( "}\n" )
107
108     # /etc/hosts entries
109     etc_hosts = [node['ip'] + "\t" + node['hostname'] for node in nodes_in_boot]
110     f.write( "elseif( $which_node_list == 'etc_hosts' )\n" )
111     f.write( "{\n" )
112     f.write( "?>\n" )
113     # Create a localhost entry for convenience
114     f.write( "127.0.0.1\tlocalhost.localdomain localhost\n" )
115     f.write( "\n".join(etc_hosts) + "\n" )
116     f.write( "<?php\n" )
117     f.write( "}\n" )
118
119     for group in ['Alpha', 'Beta']:
120         if group not in node_group_nodes:
121             node_group_nodes[group] = []
122
123         # Group nodes with primary IP addresses in boot state
124         group_nodes_in_boot = [node for node in node_group_nodes[group] if node['boot_state'] == "boot" and node['ip']]
125
126         # Group hostnames
127         group_hosts = [node['hostname'] for node in group_nodes_in_boot]
128         f.write( "elseif( $which_node_list == '%s_hosts' )\n" % group.lower() )
129         f.write( "{\n" )
130         f.write( "?>\n" )
131         f.write( "\n".join(group_hosts) + "\n" )
132         f.write( "<?php\n" )
133         f.write( "}\n" )
134
135         # Group IPs
136         group_ips = [node['ip'] for node in group_nodes_in_boot]
137         f.write( "elseif( $which_node_list == '%s_ips' )\n" % group.lower() )
138         f.write( "{\n" )
139         f.write( "?>\n" )
140         f.write( "\n".join(group_ips) + "\n" )
141         f.write( "<?php\n" )
142         f.write( "}\n" )
143
144     # All production nodes (nodes not in Alpha or Beta)
145     production_nodes_in_boot = [node for node in nodes_in_boot if node not in node_group_nodes['Alpha'] and \
146                                                    node not in node_group_nodes['Beta']]
147
148     production_hosts = [node['hostname'] for node in production_nodes_in_boot]                           
149     f.write( "elseif( $which_node_list == 'production_hosts' )\n" )
150     f.write( "{\n" )
151     f.write( "?>\n" )
152     f.write( "\n".join(production_hosts) + "\n" )
153     f.write( "<?php\n" )
154     f.write( "}\n" )
155
156     production_ips = [node['ip'] for node in production_nodes_in_boot]                           
157     f.write( "elseif( $which_node_list == 'production_ips' )\n" )
158     f.write( "{\n" )
159     f.write( "?>\n" )
160     f.write( "\n".join(production_ips) + "\n" )
161     f.write( "<?php\n" )
162     f.write( "}\n" )
163     f.write( "?>" )
164
165
166 def GetPlanetFlowStats(f):
167     if hasattr(config, 'PLANETFLOW_BASE'):
168         url = "http://" + config.PLANETFLOW_BASE
169     else:
170         return
171
172     # Slices to calculate detailed statistics for
173     slices = [
174         'cmu_esm',
175         'cornell_beehive',
176         'cornell_cobweb',
177         'cornell_codons',
178         'michigan_tmesh',
179         'nyu_d',
180         'princeton_codeen',
181         'princeton_coblitz',
182         'princeton_comon',
183         'rice_epost',
184         'ucb_bamboo',
185         'ucb_i3',
186         'ucsd_sword',
187         'upenn_dharma',
188         'idsl_psepr',
189         'ucb_ganglia',
190         'cmu_irislog',
191         'tennessee_hliu'
192         ]
193
194     # Seconds to wait
195     socket.setdefaulttimeout(3600)
196
197     url = url + '/slice.php?csv=1&start_time=2+days+ago'
198     if slices:
199         url = url + '&slices[]=' + '&slices[]='.join(slices)
200     stats = urllib.request.urlopen(url)
201     fields = ['slice', 'flows', 'packets', 'bytes', 'src_ips',
202               'dst_ips', 'top_dst_ip', 'top_dst_ip_bytes']
203     rows = csv.DictReader(stats, fields)
204     f.write("<?php\n")
205     f.write("$planetflow = array(\n")
206     for row in rows:
207         if 'slice' in row:
208             f.write("'%s' => array(\n" % row['slice'])
209             for field in fields:
210                 if field in row and \
211                    row[field] is not None and \
212                    row[field] != "":
213                     if type(row[field]) == type(0):
214                         f.write("\t'%s' => %d,\n" % (field, int(row[field])))
215                     else:
216                         f.write("\t'%s' => '%s',\n" % (field, row[field]))
217             f.write("),\n")
218     f.write(");\n")
219     f.write("?>")
220
221
222
223 def GenDistMap():
224     # update the node distribution map
225     datadir = '/var/www/html/plot-latlong'
226
227     # plot-latlong looks for .mapinfo and .mapimages in $HOME
228     os.environ['HOME'] = datadir
229
230     if hasattr(config, 'PLC_WWW_MAPIMAGE'):
231         image = config.PLC_WWW_MAPIMAGE
232     else:
233         image = "World50"
234
235     (child_stdin,
236      child_stdout) = \
237      os.popen2('perl ' + datadir + os.sep + 'plot-latlong -m "%s" -s 3' % image)
238
239     for site in all_sites:
240         if site['latitude'] and site['longitude']:
241             child_stdin.write("%f %f\n" % \
242                               (site['latitude'], site['longitude']))
243     child_stdin.close()
244
245     map = file(GENERATED_OUTPUT_PATH + os.sep + image + '.png', 'w')
246     map.write(child_stdout.read())
247     child_stdout.close()
248     map.close()
249
250
251 # which files to generate, and the functions in
252 # this script to call to get the content for
253 STATIC_FILE_LIST= (
254     ('_gen_counts.php',GetCountsFileContent),
255     ('_gen_node_lists.php',GetNodeListsContent),
256     ('_gen_known_hosts.php',GetHostKeys),
257     ('_gen_planetflow.php',GetPlanetFlowStats),
258     (None,GenDistMap)
259     )
260
261
262 if __name__ == '__main__':
263
264     # see if we are already running by checking the existance
265     # of a PID file, and if it exists, attempting a test kill
266     # to see if the process really does exist. If both of these
267     # tests pass, exit.
268         
269     if os.access(SCRIPT_PID_FILE, os.R_OK):
270         pid= string.strip(file(SCRIPT_PID_FILE).readline())
271         if pid != "":
272             if os.system("/bin/kill -0 %s > /dev/null 2>&1" % pid) == 0:
273                 sys.exit(0)
274             
275     # write out our process id
276     pidfile= file( SCRIPT_PID_FILE, 'w' )
277     pidfile.write( "%d\n" % os.getpid() )
278     pidfile.close()
279     pidfile= None
280
281     # Get all nodes and sites
282     begin()
283     GetNodes(None, ['node_id', 'hostname', 'boot_state', 'ssh_rsa_key', 'interface_ids'])
284     GetInterfaces(None, ['interface_id', 'ip', 'is_primary'])
285     GetSites(None, ['site_id', 'latitude', 'longitude'])
286     GetNodeGroups(None, ['nodegroup_id', 'tagname', 'node_ids'])
287     (all_nodes, all_nodenetworks, all_sites, all_groups) = commit()
288
289     all_nodenetworks = dict([(nodenetwork['interface_id'], nodenetwork) \
290                              for nodenetwork in all_nodenetworks])
291
292     # Set primary IP, if any
293     for node in all_nodes:
294         node['ip'] = None
295         for interface_id in node['interface_ids']:
296             try:
297                 nodenetwork = all_nodenetworks[interface_id]
298                 if nodenetwork['is_primary']:
299                     node['ip'] = nodenetwork['ip']
300                 break
301             except IndexError as KeyError:
302                 continue
303
304     # Get list of nodes in each node group
305     for group in all_groups:
306         nodes_in_group = [node for node in all_nodes if node['node_id'] in group['node_ids']]
307         node_group_nodes[group['tagname']] = nodes_in_group
308
309     # generate the static content files
310     for (file_name,func) in STATIC_FILE_LIST:
311         if file_name is not None:
312             try:
313                 output_file_path= "%s/%s" % (GENERATED_OUTPUT_PATH,file_name)
314                 tmp_output_file_path= output_file_path + '.tmp'
315                 tmp_output_file= codecs.open( tmp_output_file_path, encoding = 'utf-8', mode = "w" )
316             except IOError as err:
317                 print(( "Unable to open file %s for writing." % output_file_path ))
318                 continue
319
320             try:
321                 func(tmp_output_file)
322                 tmp_output_file.flush()
323                 shutil.copyfile( tmp_output_file_path, output_file_path )
324             except Exception as e:
325                 print("Unable to get content for file: %s" % file_name, e)
326                 import traceback
327                 traceback.print_exc()
328
329             tmp_output_file.close()
330             tmp_output_file= None
331             os.unlink( tmp_output_file_path )
332         else:
333             func()
334
335     # remove the PID file
336     os.unlink( SCRIPT_PID_FILE )