Please note that the CVS and issue trackers have moved to GitHub. These Trac pages are no longer kept up-to-date.

root/seattle/trunk/deploymentscripts/deploy_server_final.py@5637

Revision 2520, 13.6 KB (checked in by konp, 10 years ago)

Committed the deployment scripts, just missing complete readme. Moved old files into /old before deletion.

Line 
1"""
2<Program Name>
3  deploy_server_final.py
4
5<Started>
6  July 2009
7
8<Author>
9  n2k8000@u.washington.edu
10  Konstantin Pik
11
12<Purpose>
13  This is a fully automated webserver that handles only certain web requests.  The server assumes files exist
14  in the locations that they're supposed to exist, and the files are placed there by make_summary.py which in turn
15  draws its files from deploy_main and related scripts.
16
17
18<Usage>
19  python deploy_server_final.py
20 
21  The deploy_server_monitor.py file should start and stop the server for you, so you shouldn't have to
22  manage the server by yourself.
23"""
24
25import os
26import time
27
28from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
29
30
31class RequestHandler(BaseHTTPRequestHandler):
32  """
33  <Purpose>
34    This class is the custom request handler that we'll have our server use.
35
36  <Arguments>
37    BaseHTTPRequestHandler:
38      The requesthandler object
39  """
40 
41 
42  def do_GET(self):
43    """
44    <Purpose>
45      Class method to handle GET requests.
46
47    <Arguments>
48      self:
49        this is the request object.
50
51    <Exceptions>
52      any type of error while sending back a reply.
53
54    <Side Effects>
55      None.
56
57    <Returns>
58      None.
59    """
60    try:
61     
62      # parse the requested page and see if it's valid
63      parse_status, explanation_str = self.parse_header(self.path)
64     
65      # parse_status:
66      # -1: error
67      # 0:  /log/* request
68      # 1:  /detailed/node/timestamp request
69      print str(self.parse_header(self.path))
70     
71      explanation_str = str(explanation_str)
72     
73      # error
74      if parse_status == -1:
75        # invalid header, close the connection and die but notify user
76        self.send_response(200)
77        self.send_header('Content-type',        'text/html')
78        self.end_headers()
79        self.wfile.write('Invalid request ('+explanation_str+')')
80        print '-1'
81        return
82     
83      # 1:  /detailed/node/timestamp request
84      elif parse_status == 1:
85        print '1'
86        # just need to respond with the file that's contained in explanation_str
87        # and once we verify that it exists, we're golden
88       
89        # path to the "detailed" file
90        file_path = explanation_str
91       
92        if os.path.isfile(file_path):
93          try:
94            # TODO: make HTML here to nav around previous node things
95            detailed_file_handle = open(file_path, 'r')
96            self.send_response(200)
97            self.send_header('Content-type',    'text/plain')
98            self.end_headers()           
99            self.wfile.write(detailed_file_handle.read())
100            detailed_file_handle.close()
101            return
102          except Exception, e:
103            print 'Error while sending detailed log file'
104            print e
105            return
106        else:
107          self.send_response(200)
108          self.send_header('Content-type',      'text/html')
109          self.end_headers()
110          self.wfile.write('Invalid file request')
111          return
112         
113      # 0:  /log/* request
114      elif parse_status == 0:
115        print '0'
116        # request was successfull, we just want the filename from index
117        log_index = explanation_str
118       
119        success_status, log_filename = self.get_filename_from_index(log_index)
120       
121        if success_status == -1:
122          # some kind of error of which the description is stored in log_filename
123          #sockobj.send('The server encountered an error opening the file, please'+\
124          #  ' try your request again')
125          self.send_response(200)
126          self.send_header('Content-type',      'text/html')
127          self.end_headers()           
128          self.wfile.write('The server encountered an error opening the file, please'+\
129            ' try your request again')
130          return
131       
132        # the file exists!
133        # just dump the file at this point, and then...
134       
135        # send the HTML file
136        self.send_response(200)
137        self.send_header('Content-type',        'text/html')
138        self.end_headers()
139        self.send_html_file(log_filename, log_index)
140        return
141
142      # invalid type
143      else:
144        self.send_response(200)
145        self.send_header('Content-type',        'text/html')
146        self.end_headers()
147        self.wfile.write('Invalid request type 2')
148        return
149       
150    except IOError:
151      self.send_error(404,'File Not Found: %s' % self.path)
152   
153    return
154
155   
156   
157  def do_POST(self):
158    # POST requests are not handled by our server.  just let the user know that.
159    try:
160      self.send_response(200)
161      self.send_header('Content-type',  'text/html')
162      self.end_headers()
163      self.wfile.write('Invalid request: POST is not supported')
164     
165    except :
166      pass
167
168     
169
170  def make_link_to(self, index, caption):
171    """
172    <Purpose>
173      This function makes a link to the index with a caption
174
175    <Arguments>
176      self:
177        this object
178      index: (expected int, but can be str)
179        the index to link to (relative to this one, 0 is most recent)
180      caption:
181        the caption for that index
182       
183
184    <Exceptions>
185      None.
186
187    <Side Effects>
188      None.
189
190    <Returns>
191      HTML to be inserted and created for page by page navigation
192    """
193   
194    # index is an int
195    return '<a href="/log/'+str(index)+'"> '+caption+' '+str(index)+'</a>'
196
197   
198   
199  def get_next_index(self, current_index_string):
200    """
201    <Purpose>
202      Gets the 'next' index to grab.
203
204    <Arguments>
205      self:
206        this requesthandler object
207      current_index_string:
208        the string representation of a number: current index
209
210    <Exceptions>
211      None.
212
213    <Side Effects>
214      None.
215
216    <Returns>
217      Integer.  The next index in the series.
218    """
219    # current index is a string, so cast to int
220    current_index = int(current_index_string)
221
222    return current_index+1
223
224
225   
226  def get_previous_index(self, current_index_string):
227    """
228    <Purpose>
229      Gets the 'previous' index to grab.
230
231    <Arguments>
232      self:
233        this requesthandler object
234      current_index_string:
235        the string representation of a number: current index
236
237    <Exceptions>
238      None.
239
240    <Side Effects>
241      None.
242
243    <Returns>
244      Integer.  The previous index in the series.
245    """
246    # current index is a string, so cast to int
247    current_index = int(current_index_string)
248
249    return current_index-1
250
251   
252   
253  def print_navigation(self, current_index):
254    """
255    <Purpose>
256      Prints the navigation on the current page.
257
258    <Arguments>
259      self:
260        this requesthandler object
261      current_index:
262        The current index that this person is on.
263
264    <Exceptions>
265      None.
266
267    <Side Effects>
268      None.
269
270    <Returns>
271      String. HTML representation of the navigation.
272    """
273    # current_index: current index
274   
275    # the html string we're going to build up
276    html = ""
277   
278    html+='<table width="100%"><tr>'
279   
280    # returns some HTML that are the navigation links at the bottom of the page
281    previous_index = self.get_previous_index(current_index)
282   
283    if previous_index != -1:
284      # not empty, so make a link
285      html += '<td align="center">'
286      previous_link = self.make_link_to(previous_index, 'Previous')
287      html += previous_link+'</td>'
288     
289    next_index = self.get_next_index(current_index)
290   
291    if next_index != -1:
292      html += '<td align="center">'
293      next_link = self.make_link_to(next_index, 'Next')
294      html += next_link+'</td>'
295
296    html += '</table>'
297    return html
298
299   
300
301  def read_whole_file(self, file_handle):
302    """
303    <Purpose>
304      Reads in a whole file given a file handle
305
306    <Arguments>
307      self:
308        this requesthandler object
309      file_handle
310        the file handle of the file to read
311
312    <Exceptions>
313      None.
314
315    <Side Effects>
316      None.
317
318    <Returns>
319      String. The file contents as string.
320    """
321    # reads in a whole file given a file handle
322   
323    temp_str = ""
324    for each_line in file_handle.xreadlines():
325      temp_str += each_line
326    return temp_str
327 
328 
329 
330  def send_html_file(self, html_fn, log_index):
331    """
332    <Purpose>
333      Grabs the html_fn (which is the file autogenerated by make_summary.py) and
334      sends it using the built-in methods in self.
335
336    <Arguments>
337      self:
338        this requesthandler object
339      html_fn:
340        the file name of the html file to send
341      log_index:
342        log index of the html_fn so that we can print the navigation at the bottom of the page
343
344    <Exceptions>
345      File related exceptions.
346      Exception sending response.     
347
348    <Side Effects>
349      None.
350
351    <Returns>
352     
353    """
354    try:
355      html_handle = open(html_fn, 'r')
356     
357      # read in the html_fil
358      file_data = self.read_whole_file(html_handle)     
359      html_handle.close()
360      # send the file, except add the nav links at the bottom
361      self.wfile.write(file_data.replace('</html>', self.print_navigation(log_index)+'</html>'))
362     
363    except Exception, e:
364      self.wfile.write('Server-side error while reading file ('+str(e)+')')
365     
366    return
367     
368     
369     
370  def parse_header(self, header):
371    """
372    <Purpose>
373      This function parser the 'header' which is a simple string of what we want to get
374
375    <Arguments>
376      self:
377        this object
378      header:
379        the request string (not the full http header)
380
381    <Exceptions>
382      None.
383
384    <Side Effects>
385      None.
386
387    <Returns>
388      returns tuples (SuccessStatus, Explanation)
389    """
390    #
391   
392    # this is what the line'll look like:
393    # e.g.: /logs/1
394    # e.g.: /detailed/host/timestamp
395
396    # get index of first slash
397    first_slash = header.index('/')
398   
399   
400    # splice the string now and remove any spaces
401    requested_folder = header.strip('/')
402   
403    # check if it's just a slash
404    if not requested_folder:
405      # return a 0 meaning we want the latest log file
406      return (0, 0)
407    else:
408      # check that it's a valid request
409      detailed_request = requested_folder.split('/')
410      # detailed_request should be of form /log/* where * is a number
411      # two types of requests:
412      # type 1: /log/* where * is a number
413      # type 2: /detailed/node_name/timestamp
414      #       node_name: node name
415      #       timetamp is the timestamp of the run
416     
417               
418      if len(detailed_request) == 2:
419        # type 1 request
420        # first entry is '' since there's a leading '/'
421        if detailed_request[0] == 'log':
422          # now get a valid number for a folder request
423          try:
424            log_number = int(detailed_request[1])
425          except Exception, e:
426            print "Error obtaining log (request: "+requested_folder+")"
427            return (-1, str(e))
428          else:
429            return (0, log_number)
430        else:
431          return (-1, 'Invalid request (len 2)')
432      elif len(detailed_request) == 3:
433        # type 2 request
434        if detailed_request[0] == 'detailed':
435          nodename = detailed_request[1]
436          timestamp = detailed_request[2]
437          # verify that timestamp is a valid #
438          try:
439            timestamp_int = int(timestamp)
440          except ValueError, ve:
441            print 'Invalid timestamp requested, '+timestamp
442            print ve
443            return (-1, 'Invalid timestamp')
444          else:
445            # return the filepath as our response
446            return (1, './detailed_logs/'+nodename+'/'+timestamp)
447           
448           
449        else:
450          return (-1, 'Invalid request (len 3)')
451       
452      else:
453        # invalid!
454        return (-1, 'Invalid detailed log request ('+str(detailed_request)+')')
455       
456
457       
458  def get_filename_from_index(self, log_index, depth = 5):
459    """
460    <Purpose>
461      Gets the nth (log_index) filename from the master.log file
462
463      master files keeps track of the fn's that we have, one on each line
464      newest ones are at the end.
465    <Arguments>
466      self:
467        this object
468      log_index:
469        the requested log_index:
470      depth:
471        not to be set manually. will retry this many times to get a filename.
472
473    <Exceptions>
474      File related errors (read/write).
475
476    <Side Effects>
477      None.
478
479    <Returns>
480      Tuple.
481      On error:
482        (-1, Error description string)
483      On success:
484        (1, filename)
485    """
486
487    try:
488     
489      if os.path.isfile('./master.log'):
490        #global_lock.acquire()
491        master_files_handle = open('./master.log', 'r')
492        all_lines = master_files_handle.readlines()
493        #global_lock.release()
494      else:
495        return_value = (-1, 'No logs to report from yet')
496        return return_value
497      # if 0, then that's the "most recent"
498     
499
500      # default value
501      return_value = (-1, 'No Valid Page')
502     
503      if str(log_index) == '0':
504        return_value = (1, all_lines[-1].strip('\n'))
505      else:
506        return_value = (1, all_lines[-(int(log_index) % len(all_lines)) - 1].strip(' \n'))
507      master_files_handle.close()   
508     
509    except Exception, e:
510      print 'Error in get_filename_from_index'
511      print e
512      return (-1, e)
513    else:
514      # means it's blank..
515      # the depth variable is so that we don't infinitely hang
516      if not return_value[1] and depth > 0:
517        new_index_int = (int(log_index)-1)
518        print 'Redirecting to '+str(new_index_int)+' with depth '+str(depth)
519        return_value = self.get_filename_from_index(str(new_index_int), depth - 1)
520      return return_value
521
522
523
524def main():
525  # boot up our server on port 4444 and set our custom class
526  # as the request handler for it.
527  try:
528    server = HTTPServer(('', 4444), RequestHandler)
529    print 'started httpserver...'
530    server.serve_forever()
531  except Exception, e:
532    print e
533 
534 
535 
536
537if __name__ == '__main__':
538  main()
Note: See TracBrowser for help on using the browser.