| #!/usr/bin/python |
| |
| import cgi, os, socket, sys, urllib2 |
| import common |
| from multiprocessing import pool |
| from autotest_lib.frontend import setup_django_environment |
| |
| from autotest_lib.client.common_lib import global_config |
| from autotest_lib.client.bin import utils |
| from autotest_lib.frontend.afe.json_rpc import serviceHandler |
| from autotest_lib.server import system_utils |
| from autotest_lib.server import utils as server_utils |
| |
| |
| _PAGE = """\ |
| Status: 302 Found |
| Content-Type: text/plain |
| Location: %s\r\n\r |
| """ |
| |
| GOOGLE_STORAGE_PATTERN = 'pantheon.corp.google.com/storage/browser/' |
| |
| # Define function for retrieving logs |
| def _retrieve_logs_dummy(job_path): |
| pass |
| |
| site_retrieve_logs = utils.import_site_function(__file__, |
| "autotest_lib.tko.site_retrieve_logs", "site_retrieve_logs", |
| _retrieve_logs_dummy) |
| |
| site_find_repository_host = utils.import_site_function(__file__, |
| "autotest_lib.tko.site_retrieve_logs", "site_find_repository_host", |
| _retrieve_logs_dummy) |
| |
| form = cgi.FieldStorage(keep_blank_values=True) |
| # determine if this is a JSON-RPC request. we support both so that the new TKO |
| # client can use its RPC client code, but the old TKO can still use simple GET |
| # params. |
| _is_json_request = form.has_key('callback') |
| |
| # if this key exists, we check if requested log exists in local machine, |
| # and do not return Google Storage URL when the log doesn't exist. |
| _local_only = form.has_key('localonly') |
| |
| |
| def _get_requested_path(): |
| if _is_json_request: |
| request_data = form['request'].value |
| request = serviceHandler.ServiceHandler.translateRequest(request_data) |
| parameters = request['params'][0] |
| return parameters['path'] |
| |
| return form['job'].value |
| |
| |
| def _check_result(args): |
| host = args['host'] |
| job_path = args['job_path'] |
| shard = args['shard'] |
| if shard: |
| http_path = 'http://%s/tko/retrieve_logs.cgi?localonly&job=%s' % ( |
| host, job_path) |
| else: |
| http_path = 'http://%s%s' % (host, job_path) |
| |
| try: |
| utils.urlopen(http_path) |
| |
| # On Vms the shard name is set to the default gateway but the |
| # browser used to navigate frontends (that runs on the host of |
| # the vms) is immune to the same NAT routing the vms have, so we |
| # need to replace the gateway with 'localhost'. |
| if utils.DEFAULT_VM_GATEWAY in host: |
| normalized_host = host.replace(utils.DEFAULT_VM_GATEWAY, 'localhost') |
| else: |
| try: |
| normalized_host = utils.normalize_hostname(host) |
| except socket.herror: |
| # Ignore error: 'socket.herror: [Errno 1] Unknown host' |
| # This can happen when reverse name lookup is not stable. |
| normalized_host = host |
| return 'http', normalized_host, job_path |
| except urllib2.URLError: |
| return None |
| |
| |
| def _get_tpool_args(hosts, job_path, is_shard, host_set): |
| """Get a list of arguments to be passed to multiprocessing.pool.ThreadPool. |
| |
| @param hosts: a list of host names. |
| @param job_path: a requested job path. |
| @param is_shard: True if hosts are shards, False otherwise. |
| @param host_set: a Set to filter out duplicated hosts. |
| |
| @return: a list of dictionaries to be used as input of _check_result(). |
| """ |
| args = [] |
| for host in hosts: |
| host = host.strip() |
| if host and host != 'localhost' and host not in host_set: |
| host_set.add(host) |
| arg = {'host': host, 'job_path': job_path, 'shard': is_shard} |
| args.append(arg) |
| return args |
| |
| |
| def find_repository_host(job_path): |
| """Find the machine holding the given logs and return a URL to the logs""" |
| site_repo_info = site_find_repository_host(job_path) |
| if site_repo_info is not None: |
| return site_repo_info |
| |
| # This cgi script is run only in master (cautotest) and shards. |
| # Drones do not run this script when receiving '/results/...' request. |
| # Only master should check drones and shards for the requested log. |
| # Also restricted users do not have access to drones or shards, |
| # always point them to localhost or google storage. |
| if (not server_utils.is_shard() and |
| not server_utils.is_restricted_user(os.environ.get('REMOTE_USER'))): |
| drones = system_utils.get_drones() |
| shards = system_utils.get_shards() |
| |
| host_set = set() |
| tpool_args = _get_tpool_args(drones, job_path, False, host_set) |
| tpool_args += _get_tpool_args(shards, job_path, True, host_set) |
| |
| tpool = pool.ThreadPool() |
| for result_path in tpool.imap_unordered(_check_result, tpool_args): |
| if result_path: |
| return result_path |
| |
| # If the URL requested is a test result, it is now either on the local |
| # host or in Google Storage. |
| if job_path.startswith('/results/'): |
| # We only care about the path after '/results/'. |
| job_relative_path = job_path[9:] |
| if not _local_only and not os.path.exists( |
| os.path.join('/usr/local/autotest/results', |
| job_relative_path)): |
| gsuri = utils.get_offload_gsuri().split('gs://')[1] |
| return ['https', GOOGLE_STORAGE_PATTERN, gsuri + job_relative_path] |
| |
| |
| def get_full_url(info, log_path): |
| if info is not None: |
| protocol, host, path = info |
| prefix = '%s://%s' % (protocol, host) |
| else: |
| prefix = '' |
| path = log_path |
| |
| if _is_json_request: |
| return '%s/tko/jsonp_fetcher.cgi?%s' % (prefix, |
| os.environ['QUERY_STRING']) |
| else: |
| return prefix + path |
| |
| |
| log_path = _get_requested_path() |
| info = find_repository_host(log_path) |
| site_retrieve_logs(log_path) |
| print _PAGE % get_full_url(info, log_path) |