1import BaseHTTPServer, cgi, threading, urllib, fcntl, logging 2import common 3from autotest_lib.scheduler import drone_manager, scheduler_config 4 5_PORT = 13467 6 7_HEADER = """ 8<html> 9<head><title>Scheduler status</title></head> 10<body> 11Actions:<br> 12<a href="?reparse_config=1">Reparse global config values</a><br> 13<br> 14""" 15 16_FOOTER = """ 17</body> 18</html> 19""" 20 21class StatusServerRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 22 def _send_headers(self): 23 self.send_response(200, 'OK') 24 self.send_header('Content-Type', 'text/html') 25 self.end_headers() 26 27 28 def _parse_arguments(self): 29 path_parts = self.path.split('?', 1) 30 if len(path_parts) == 1: 31 return {} 32 33 encoded_args = path_parts[1] 34 return cgi.parse_qs(encoded_args) 35 36 37 def _write_line(self, line=''): 38 self.wfile.write(line + '<br>\n') 39 40 41 def _write_field(self, field, value): 42 self._write_line('%s=%s' % (field, value)) 43 44 45 def _write_all_fields(self): 46 self._write_line('Config values:') 47 for field, datatype in scheduler_config.SchedulerConfig.FIELDS: 48 self._write_field(field, getattr(scheduler_config.config, field)) 49 self._write_line() 50 51 52 def _write_drone(self, drone): 53 if drone.allowed_users: 54 allowed_users = ', '.join(drone.allowed_users) 55 else: 56 allowed_users = 'all' 57 line = ('%s: %s/%s processes, users: %s' 58 % (drone.hostname, drone.active_processes, drone.max_processes, 59 allowed_users)) 60 if not drone.enabled: 61 line += ' (disabled)' 62 self._write_line(line) 63 64 65 def _write_drone_list(self): 66 self._write_line('Drones:') 67 for drone in self.server._drone_manager.get_drones(): 68 self._write_drone(drone) 69 self._write_line() 70 71 72 def _execute_actions(self, arguments): 73 if 'reparse_config' in arguments: 74 scheduler_config.config.read_config() 75 self.server._drone_manager.refresh_drone_configs() 76 self._write_line('Reparsed config!') 77 elif 'restart_scheduler' in arguments: 78 self.server._shutdown_scheduler = True 79 self._write_line('Posted the shutdown request') 80 self._write_line() 81 82 83 def do_GET(self): 84 self._send_headers() 85 self.wfile.write(_HEADER) 86 87 arguments = self._parse_arguments() 88 self._execute_actions(arguments) 89 self._write_all_fields() 90 self._write_drone_list() 91 92 self.wfile.write(_FOOTER) 93 94 95class StatusServer(BaseHTTPServer.HTTPServer): 96 def __init__(self): 97 address = ('', _PORT) 98 # HTTPServer is an old-style class :( 99 BaseHTTPServer.HTTPServer.__init__(self, address, 100 StatusServerRequestHandler) 101 self._shutting_down = False 102 self._drone_manager = drone_manager.instance() 103 self._shutdown_scheduler = False 104 105 # ensure the listening socket is not inherited by child processes 106 old_flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD) 107 fcntl.fcntl(self.fileno(), fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC) 108 109 110 def shutdown(self): 111 if self._shutting_down: 112 return 113 logging.info('Shutting down server...') 114 self._shutting_down = True 115 # make one last request to awaken the server thread and make it exit 116 urllib.urlopen('http://localhost:%s' % _PORT) 117 118 119 def _serve_until_shutdown(self): 120 logging.info('Status server running on %s', self.server_address) 121 while not self._shutting_down: 122 self.handle_request() 123 124 125 def start(self): 126 self._thread = threading.Thread(target=self._serve_until_shutdown, 127 name='status_server') 128 self._thread.start() 129