195 lines
6.5 KiB
Python
Executable file
195 lines
6.5 KiB
Python
Executable file
#! /usr/bin/python
|
|
|
|
"""A simple heartbeat server.
|
|
|
|
Executes *readonly* heartbeats against the given database.
|
|
|
|
Usage:
|
|
1. heartbeat_server.py
|
|
--port 8080
|
|
|
|
Start to serve heartbeats on port 8080 using the database credentials
|
|
found in the shadow_config. One would perform heartbeats for board:lumpy
|
|
against this server with:
|
|
curl http://localhost:8080/lumpy.
|
|
Or just visiting the url through the browser.
|
|
|
|
Such a server is capable of handling the following urls:
|
|
/lumpy: Return formatted heartbeat packets with timing information for
|
|
each stage, to be viewed in the browser.
|
|
/lumpy?raw: Return raw json heartbeat packets for lumpy
|
|
/lumpy?raw&host_limit=1&job_limit=0: Return a 'raw' heartbeat with the
|
|
first host and not jobs.
|
|
|
|
2. heartbeat_server.py
|
|
--db_host <ip, eg: production db server>
|
|
--db_user <user, eg: chromeosqa-admin>
|
|
--db_password <password, eg: production db password>
|
|
|
|
The same as 1. but use the remote db server specified via
|
|
db_(host,user,password).
|
|
"""
|
|
|
|
|
|
import argparse
|
|
import sys
|
|
import time
|
|
import urlparse
|
|
from BaseHTTPServer import BaseHTTPRequestHandler
|
|
from BaseHTTPServer import HTTPServer
|
|
|
|
import common
|
|
from autotest_lib.client.common_lib.global_config import global_config as config
|
|
from autotest_lib.frontend import setup_django_environment
|
|
|
|
|
|
# Populated with command line database credentials.
|
|
DB_SETTINGS = {
|
|
'ENGINE': 'autotest_lib.frontend.db.backends.afe',
|
|
}
|
|
|
|
# Indent level used when formatting json for the browser.
|
|
JSON_FORMATTING_INDENT = 4
|
|
|
|
|
|
def time_call(func):
|
|
"""A simple timer wrapper.
|
|
|
|
@param func: The function to wrap.
|
|
"""
|
|
def wrapper(*args, **kwargs):
|
|
"""Wrapper returned by time_call decorator."""
|
|
start = time.time()
|
|
res = func(*args, **kwargs)
|
|
return time.time()-start, res
|
|
return wrapper
|
|
|
|
|
|
class BoardHandler(BaseHTTPRequestHandler):
|
|
"""Handles heartbeat urls."""
|
|
|
|
# Prefix for all board labels.
|
|
board_prefix = 'board:'
|
|
|
|
|
|
@staticmethod
|
|
@time_call
|
|
def _get_jobs(board, job_limit=None):
|
|
jobs = models.Job.objects.filter(
|
|
dependency_labels__name=board).exclude(
|
|
hostqueueentry__complete=True).exclude(
|
|
hostqueueentry__active=True)
|
|
return jobs[:job_limit] if job_limit is not None else jobs
|
|
|
|
|
|
@staticmethod
|
|
@time_call
|
|
def _get_hosts(board, host_limit=None):
|
|
hosts = models.Host.objects.filter(
|
|
labels__name__in=[board], leased=False)
|
|
return hosts[:host_limit] if host_limit is not None else hosts
|
|
|
|
|
|
@staticmethod
|
|
@time_call
|
|
def _create_packet(hosts, jobs):
|
|
return {
|
|
'hosts': [h.serialize() for h in hosts],
|
|
'jobs': [j.serialize() for j in jobs]
|
|
}
|
|
|
|
|
|
def do_GET(self):
|
|
"""GET handler.
|
|
|
|
Handles urls like: http://localhost:8080/lumpy?raw&host_limit=5
|
|
and writes the appropriate http response containing the heartbeat.
|
|
"""
|
|
parsed_path = urlparse.urlparse(self.path, allow_fragments=True)
|
|
board = '%s%s' % (self.board_prefix, parsed_path.path.rsplit('/')[-1])
|
|
|
|
raw = False
|
|
job_limit = None
|
|
host_limit = None
|
|
for query in parsed_path.query.split('&'):
|
|
split_query = query.split('=')
|
|
if split_query[0] == 'job_limit':
|
|
job_limit = int(split_query[1])
|
|
elif split_query[0] == 'host_limit':
|
|
host_limit = int(split_query[1])
|
|
elif split_query[0] == 'raw':
|
|
raw = True
|
|
|
|
host_time, hosts = self._get_hosts(board, host_limit)
|
|
job_time, jobs = self._get_jobs(board, job_limit)
|
|
|
|
serialize_time, heartbeat_packet = self._create_packet(hosts, jobs)
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
|
|
# Format browser requests, the heartbeat client will request using ?raw
|
|
# while the browser will perform a plain request like
|
|
# http://localhost:8080/lumpy. The latter needs to be human readable and
|
|
# include more details timing information.
|
|
json_encoder = django_encoder.DjangoJSONEncoder()
|
|
if not raw:
|
|
json_encoder.indent = JSON_FORMATTING_INDENT
|
|
self.wfile.write('Serialize: %s,\nJob query: %s\nHost query: %s\n'
|
|
'Hosts: %s\nJobs: %s\n' %
|
|
(serialize_time, job_time, host_time,
|
|
len(heartbeat_packet['hosts']),
|
|
len(heartbeat_packet['jobs'])))
|
|
self.wfile.write(json_encoder.encode(heartbeat_packet))
|
|
return
|
|
|
|
|
|
def _parse_args(args):
|
|
parser = argparse.ArgumentParser(
|
|
description='Start up a simple heartbeat server on localhost.')
|
|
parser.add_argument(
|
|
'--port', default=8080,
|
|
help='The port to start the heartbeat server.')
|
|
parser.add_argument(
|
|
'--db_host',
|
|
default=config.get_config_value('AUTOTEST_WEB', 'host'),
|
|
help='Db server ip address.')
|
|
parser.add_argument(
|
|
'--db_name',
|
|
default=config.get_config_value('AUTOTEST_WEB', 'database'),
|
|
help='Name of the db table.')
|
|
parser.add_argument(
|
|
'--db_user',
|
|
default=config.get_config_value('AUTOTEST_WEB', 'user'),
|
|
help='User for the db server.')
|
|
parser.add_argument(
|
|
'--db_password',
|
|
default=config.get_config_value('AUTOTEST_WEB', 'password'),
|
|
help='Password for the db server.')
|
|
parser.add_argument(
|
|
'--db_port',
|
|
default=config.get_config_value('AUTOTEST_WEB', 'port', default=''),
|
|
help='Port of the db server.')
|
|
|
|
return parser.parse_args(args)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
args = _parse_args(sys.argv[1:])
|
|
server = HTTPServer(('localhost', args.port), BoardHandler)
|
|
print ('Starting heartbeat server, query eg: http://localhost:%s/lumpy' %
|
|
args.port)
|
|
|
|
# We need these lazy imports to allow command line specification of
|
|
# database credentials.
|
|
from autotest_lib.frontend import settings
|
|
DB_SETTINGS['HOST'] = args.db_host
|
|
DB_SETTINGS['NAME'] = args.db_name
|
|
DB_SETTINGS['USER'] = args.db_user
|
|
DB_SETTINGS['PASSWORD'] = args.db_password
|
|
DB_SETTINGS['PORT'] = args.db_port
|
|
settings.DATABASES['default'] = DB_SETTINGS
|
|
from autotest_lib.frontend.afe import models
|
|
from django.core.serializers import json as django_encoder
|
|
|
|
server.serve_forever()
|
|
|