| OLD | NEW |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import json | 5 import json |
| 6 import os | 6 import os |
| 7 import re | 7 import re |
| 8 import threading | 8 import threading |
| 9 import time | 9 import time |
| 10 import subprocess | 10 import subprocess |
| 11 import sys | 11 import sys |
| 12 | 12 |
| 13 from gcloud import storage | |
| 14 from gcloud.exceptions import NotFound | |
| 15 from oauth2client.client import GoogleCredentials | |
| 16 | |
| 17 # NOTE: The parent directory needs to be first in sys.path to avoid conflicts | 13 # NOTE: The parent directory needs to be first in sys.path to avoid conflicts |
| 18 # with catapult modules that have colliding names, as catapult inserts itself | 14 # with catapult modules that have colliding names, as catapult inserts itself |
| 19 # into the path as the second element. This is an ugly and fragile hack. | 15 # into the path as the second element. This is an ugly and fragile hack. |
| 20 sys.path.insert(0, | 16 sys.path.insert(0, |
| 21 os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)) | 17 os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)) |
| 22 import controller | 18 import controller |
| 19 from google_api_util import GoogleAPIUtil |
| 23 import loading_trace | 20 import loading_trace |
| 21 from loading_trace_database import LoadingTraceDatabase |
| 24 import options | 22 import options |
| 25 from loading_trace_database import LoadingTraceDatabase | |
| 26 | 23 |
| 27 | 24 |
| 28 class ServerApp(object): | 25 class ServerApp(object): |
| 29 """Simple web server application, collecting traces and writing them in | 26 """Simple web server application, collecting traces and writing them in |
| 30 Google Cloud Storage. | 27 Google Cloud Storage. |
| 31 """ | 28 """ |
| 32 | 29 |
| 33 def __init__(self, configuration_file): | 30 def __init__(self, configuration_file): |
| 34 """|configuration_file| is a path to a file containing JSON as described in | 31 """|configuration_file| is a path to a file containing JSON as described in |
| 35 README.md. | 32 README.md. |
| 36 """ | 33 """ |
| 37 self._tasks = [] # List of remaining tasks, only modified by _thread. | 34 self._tasks = [] # List of remaining tasks, only modified by _thread. |
| 38 self._failed_tasks = [] # Failed tasks, only modified by _thread. | 35 self._failed_tasks = [] # Failed tasks, only modified by _thread. |
| 39 self._thread = None | 36 self._thread = None |
| 40 self._tasks_lock = threading.Lock() # Protects _tasks and _failed_tasks. | 37 self._tasks_lock = threading.Lock() # Protects _tasks and _failed_tasks. |
| 41 self._initial_task_count = -1 | 38 self._initial_task_count = -1 |
| 42 self._start_time = None | 39 self._start_time = None |
| 43 print 'Initializing credentials' | |
| 44 self._credentials = GoogleCredentials.get_application_default() | |
| 45 print 'Reading configuration' | 40 print 'Reading configuration' |
| 46 with open(configuration_file) as config_json: | 41 with open(configuration_file) as config_json: |
| 47 config = json.load(config_json) | 42 config = json.load(config_json) |
| 48 self._project_name = config['project_name'] | |
| 49 | 43 |
| 50 # Separate the cloud storage path into the bucket and the base path under | 44 # Separate the cloud storage path into the bucket and the base path under |
| 51 # the bucket. | 45 # the bucket. |
| 52 storage_path_components = config['cloud_storage_path'].split('/') | 46 storage_path_components = config['cloud_storage_path'].split('/') |
| 53 self._bucket_name = storage_path_components[0] | 47 self._bucket_name = storage_path_components[0] |
| 54 self._base_path_in_bucket = '' | 48 self._base_path_in_bucket = '' |
| 55 if len(storage_path_components) > 1: | 49 if len(storage_path_components) > 1: |
| 56 self._base_path_in_bucket = '/'.join(storage_path_components[1:]) | 50 self._base_path_in_bucket = '/'.join(storage_path_components[1:]) |
| 57 if not self._base_path_in_bucket.endswith('/'): | 51 if not self._base_path_in_bucket.endswith('/'): |
| 58 self._base_path_in_bucket += '/' | 52 self._base_path_in_bucket += '/' |
| 59 | 53 |
| 60 self._src_path = config['src_path'] | 54 self._src_path = config['src_path'] |
| 55 self._google_api_util = GoogleAPIUtil( |
| 56 project_name=config['project_name'], bucket_name=self._bucket_name) |
| 61 | 57 |
| 62 # Initialize the global options that will be used during trace generation. | 58 # Initialize the global options that will be used during trace generation. |
| 63 options.OPTIONS.ParseArgs([]) | 59 options.OPTIONS.ParseArgs([]) |
| 64 options.OPTIONS.local_binary = config['chrome_path'] | 60 options.OPTIONS.local_binary = config['chrome_path'] |
| 65 | 61 |
| 66 def _IsProcessingTasks(self): | 62 def _IsProcessingTasks(self): |
| 67 """Returns True if the application is currently processing tasks.""" | 63 """Returns True if the application is currently processing tasks.""" |
| 68 return self._thread is not None and self._thread.is_alive() | 64 return self._thread is not None and self._thread.is_alive() |
| 69 | 65 |
| 70 def _GetStorageClient(self): | |
| 71 return storage.Client(project = self._project_name, | |
| 72 credentials = self._credentials) | |
| 73 | |
| 74 def _GetStorageBucket(self, storage_client): | |
| 75 return storage_client.get_bucket(self._bucket_name) | |
| 76 | |
| 77 def _UploadFile(self, filename_src, filename_dest): | |
| 78 """Uploads a file to Google Cloud Storage | |
| 79 | |
| 80 Args: | |
| 81 filename_src: name of the local file | |
| 82 filename_dest: name of the file in Google Cloud Storage | |
| 83 | |
| 84 Returns: | |
| 85 The URL of the file in Google Cloud Storage. | |
| 86 """ | |
| 87 client = self._GetStorageClient() | |
| 88 bucket = self._GetStorageBucket(client) | |
| 89 blob = bucket.blob(filename_dest) | |
| 90 with open(filename_src) as file_src: | |
| 91 blob.upload_from_file(file_src) | |
| 92 return blob.public_url | |
| 93 | |
| 94 def _UploadString(self, data_string, filename_dest): | |
| 95 """Uploads a string to Google Cloud Storage | |
| 96 | |
| 97 Args: | |
| 98 data_string: the contents of the file to be uploaded | |
| 99 filename_dest: name of the file in Google Cloud Storage | |
| 100 | |
| 101 Returns: | |
| 102 The URL of the file in Google Cloud Storage. | |
| 103 """ | |
| 104 client = self._GetStorageClient() | |
| 105 bucket = self._GetStorageBucket(client) | |
| 106 blob = bucket.blob(filename_dest) | |
| 107 blob.upload_from_string(data_string) | |
| 108 return blob.public_url | |
| 109 | |
| 110 def _GenerateTrace(self, url, emulate_device, emulate_network, filename, | 66 def _GenerateTrace(self, url, emulate_device, emulate_network, filename, |
| 111 log_filename): | 67 log_filename): |
| 112 """ Generates a trace on _thread. | 68 """ Generates a trace on _thread. |
| 113 | 69 |
| 114 Args: | 70 Args: |
| 115 url: URL as a string. | 71 url: URL as a string. |
| 116 emulate_device: Name of the device to emulate. Empty for no emulation. | 72 emulate_device: Name of the device to emulate. Empty for no emulation. |
| 117 emulate_network: Type of network emulation. Empty for no emulation. | 73 emulate_network: Type of network emulation. Empty for no emulation. |
| 118 filename: Name of the file where the trace is saved. | 74 filename: Name of the file where the trace is saved. |
| 119 log_filename: Name of the file where standard output and errors are logged | 75 log_filename: Name of the file where standard output and errors are logged |
| (...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 203 url = self._tasks[-1] | 159 url = self._tasks[-1] |
| 204 local_filename = pattern.sub('_', url) | 160 local_filename = pattern.sub('_', url) |
| 205 for repeat in range(repeat_count): | 161 for repeat in range(repeat_count): |
| 206 print 'Generating trace for URL: %s' % url | 162 print 'Generating trace for URL: %s' % url |
| 207 remote_filename = local_filename + '/' + str(repeat) | 163 remote_filename = local_filename + '/' + str(repeat) |
| 208 trace_metadata = self._GenerateTrace( | 164 trace_metadata = self._GenerateTrace( |
| 209 url, emulate_device, emulate_network, local_filename, log_filename) | 165 url, emulate_device, emulate_network, local_filename, log_filename) |
| 210 if trace_metadata['succeeded']: | 166 if trace_metadata['succeeded']: |
| 211 print 'Uploading: %s' % remote_filename | 167 print 'Uploading: %s' % remote_filename |
| 212 remote_trace_location = traces_dir + remote_filename | 168 remote_trace_location = traces_dir + remote_filename |
| 213 self._UploadFile(local_filename, remote_trace_location) | 169 self._google_api_util.UploadFile(local_filename, |
| 170 remote_trace_location) |
| 214 full_cloud_storage_path = ('gs://' + self._bucket_name + '/' + | 171 full_cloud_storage_path = ('gs://' + self._bucket_name + '/' + |
| 215 remote_trace_location) | 172 remote_trace_location) |
| 216 trace_database.AddTrace(full_cloud_storage_path, trace_metadata) | 173 trace_database.AddTrace(full_cloud_storage_path, trace_metadata) |
| 217 else: | 174 else: |
| 218 print 'Trace generation failed for URL: %s' % url | 175 print 'Trace generation failed for URL: %s' % url |
| 219 self._tasks_lock.acquire() | 176 self._tasks_lock.acquire() |
| 220 self._failed_tasks.append({ "url": url, "repeat": repeat}) | 177 self._failed_tasks.append({ "url": url, "repeat": repeat}) |
| 221 self._tasks_lock.release() | 178 self._tasks_lock.release() |
| 222 if os.path.isfile(local_filename): | 179 if os.path.isfile(local_filename): |
| 223 self._UploadFile(local_filename, failures_dir + remote_filename) | 180 self._google_api_util.UploadFile(local_filename, |
| 181 failures_dir + remote_filename) |
| 224 print 'Uploading log' | 182 print 'Uploading log' |
| 225 self._UploadFile(log_filename, logs_dir + remote_filename) | 183 self._google_api_util.UploadFile(log_filename, |
| 184 logs_dir + remote_filename) |
| 226 # Pop once task is finished, for accurate status tracking. | 185 # Pop once task is finished, for accurate status tracking. |
| 227 self._tasks_lock.acquire() | 186 self._tasks_lock.acquire() |
| 228 url = self._tasks.pop() | 187 url = self._tasks.pop() |
| 229 self._tasks_lock.release() | 188 self._tasks_lock.release() |
| 230 | 189 |
| 231 self._UploadString(json.dumps(trace_database.ToJsonDict(), indent=2), | 190 self._google_api_util.UploadString( |
| 232 traces_dir + 'trace_database.json') | 191 json.dumps(trace_database.ToJsonDict(), indent=2), |
| 192 traces_dir + 'trace_database.json') |
| 233 | 193 |
| 234 if len(self._failed_tasks) > 0: | 194 if len(self._failed_tasks) > 0: |
| 235 print 'Uploading failing URLs' | 195 print 'Uploading failing URLs' |
| 236 self._UploadString(json.dumps(self._failed_tasks, indent=2), | 196 self._google_api_util.UploadString( |
| 237 failures_dir + 'failures.json') | 197 json.dumps(self._failed_tasks, indent=2), |
| 198 failures_dir + 'failures.json') |
| 238 | 199 |
| 239 def _SetTaskList(self, http_body): | 200 def _SetTaskList(self, http_body): |
| 240 """Sets the list of tasks and starts processing them | 201 """Sets the list of tasks and starts processing them |
| 241 | 202 |
| 242 Args: | 203 Args: |
| 243 http_body: JSON dictionary. See README.md for a description of the format. | 204 http_body: JSON dictionary. See README.md for a description of the format. |
| 244 | 205 |
| 245 Returns: | 206 Returns: |
| 246 A string to be sent back to the client, describing the success status of | 207 A string to be sent back to the client, describing the success status of |
| 247 the request. | 208 the request. |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 310 response_headers = [ | 271 response_headers = [ |
| 311 ('Content-type','text/plain'), | 272 ('Content-type','text/plain'), |
| 312 ('Content-Length', str(len(data))) | 273 ('Content-Length', str(len(data))) |
| 313 ] | 274 ] |
| 314 start_response('200 OK', response_headers) | 275 start_response('200 OK', response_headers) |
| 315 return iter([data]) | 276 return iter([data]) |
| 316 | 277 |
| 317 | 278 |
| 318 def StartApp(configuration_file): | 279 def StartApp(configuration_file): |
| 319 return ServerApp(configuration_file) | 280 return ServerApp(configuration_file) |
| OLD | NEW |