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