Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(591)

Side by Side Diff: swarming.py

Issue 23657003: Move url_open with dependencies to utils.net module. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/swarm_client
Patch Set: Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2013 The Chromium Authors. All rights reserved. 2 # Copyright 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Client tool to trigger tasks or retrieve results from a Swarming server.""" 6 """Client tool to trigger tasks or retrieve results from a Swarming server."""
7 7
8 __version__ = '0.1' 8 __version__ = '0.1'
9 9
10 import binascii
10 import hashlib 11 import hashlib
11 import json 12 import json
12 import logging 13 import logging
13 import os 14 import os
14 import re 15 import re
15 import shutil 16 import shutil
16 import StringIO
17 import subprocess 17 import subprocess
18 import sys 18 import sys
19 import time 19 import time
20 import urllib 20 import urllib
21 import zipfile
22 21
23 from third_party import colorama 22 from third_party import colorama
24 from third_party.depot_tools import fix_encoding 23 from third_party.depot_tools import fix_encoding
25 from third_party.depot_tools import subcommand 24 from third_party.depot_tools import subcommand
25
26 from utils import net
27 from utils import threading_utils
26 from utils import tools 28 from utils import tools
27 from utils import threading_utils 29 from utils import zip_package
28 30
29 import run_isolated 31 import run_isolated
30 32
31 33
32 ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) 34 ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
33 TOOLS_PATH = os.path.join(ROOT_DIR, 'tools') 35 TOOLS_PATH = os.path.join(ROOT_DIR, 'tools')
34 36
35 37
36 # Default servers. 38 # Default servers.
37 # TODO(maruel): Chromium-specific. 39 # TODO(maruel): Chromium-specific.
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
76 shards - The number of swarm shards to request. 78 shards - The number of swarm shards to request.
77 test_filter - The gtest filter to apply when running the test. 79 test_filter - The gtest filter to apply when running the test.
78 slave_os - OS to run on. 80 slave_os - OS to run on.
79 working_dir - Relative working directory to start the script. 81 working_dir - Relative working directory to start the script.
80 isolate_server - isolate server url. 82 isolate_server - isolate server url.
81 verbose - if True, have the slave print more details. 83 verbose - if True, have the slave print more details.
82 profile - if True, have the slave print more timing data. 84 profile - if True, have the slave print more timing data.
83 priority - int between 0 and 1000, lower the higher priority 85 priority - int between 0 and 1000, lower the higher priority
84 """ 86 """
85 self.manifest_hash = manifest_hash 87 self.manifest_hash = manifest_hash
88 self.bundle = zip_package.ZipPackage(ROOT_DIR)
89
86 self._test_name = test_name 90 self._test_name = test_name
87 self._shards = shards 91 self._shards = shards
88 self._test_filter = test_filter 92 self._test_filter = test_filter
89 self._target_platform = PLATFORM_MAPPING[slave_os] 93 self._target_platform = PLATFORM_MAPPING[slave_os]
90 self._working_dir = working_dir 94 self._working_dir = working_dir
91 95
92 self.data_server_retrieval = isolate_server + '/content/retrieve/default/' 96 self.data_server_retrieval = isolate_server + '/content/retrieve/default/'
93 self._data_server_storage = isolate_server + '/content/store/default/' 97 self._data_server_storage = isolate_server + '/content/store/default/'
94 self._data_server_has = isolate_server + '/content/contains/default' 98 self._data_server_has = isolate_server + '/content/contains/default'
95 self._data_server_get_token = isolate_server + '/content/get_token' 99 self._data_server_get_token = isolate_server + '/content/get_token'
96 100
97 self.verbose = bool(verbose) 101 self.verbose = bool(verbose)
98 self.profile = bool(profile) 102 self.profile = bool(profile)
99 self.priority = priority 103 self.priority = priority
100 104
101 self._zip_file_hash = '' 105 self._zip_file_hash = ''
102 self._tasks = [] 106 self._tasks = []
103 self._files = {} 107 self._files = {}
104 self._token_cache = None 108 self._token_cache = None
105 109
106 def _token(self): 110 def _token(self):
107 if not self._token_cache: 111 if not self._token_cache:
108 result = run_isolated.url_open(self._data_server_get_token) 112 result = net.url_open(self._data_server_get_token)
109 if not result: 113 if not result:
110 # TODO(maruel): Implement authentication. 114 # TODO(maruel): Implement authentication.
111 raise Failure('Failed to get token, need authentication') 115 raise Failure('Failed to get token, need authentication')
112 # Quote it right away, so creating the urls is simpler. 116 # Quote it right away, so creating the urls is simpler.
113 self._token_cache = urllib.quote(result.read()) 117 self._token_cache = urllib.quote(result.read())
114 return self._token_cache 118 return self._token_cache
115 119
116 def add_task(self, task_name, actions, time_out=600): 120 def add_task(self, task_name, actions, time_out=600):
117 """Appends a new task to the swarm manifest file.""" 121 """Appends a new task to the swarm manifest file."""
118 # See swarming/src/common/test_request_message.py TestObject constructor for 122 # See swarming/src/common/test_request_message.py TestObject constructor for
119 # the valid flags. 123 # the valid flags.
120 self._tasks.append( 124 self._tasks.append(
121 { 125 {
122 'action': actions, 126 'action': actions,
123 'decorate_output': self.verbose, 127 'decorate_output': self.verbose,
124 'test_name': task_name, 128 'test_name': task_name,
125 'time_out': time_out, 129 'time_out': time_out,
126 }) 130 })
127 131
128 def add_file(self, source_path, rel_path):
129 self._files[source_path] = rel_path
130
131 def zip_and_upload(self): 132 def zip_and_upload(self):
132 """Zips up all the files necessary to run a shard and uploads to Swarming 133 """Zips up all the files necessary to run a shard and uploads to Swarming
133 master. 134 master.
134 """ 135 """
135 assert not self._zip_file_hash 136 assert not self._zip_file_hash
137
136 start_time = time.time() 138 start_time = time.time()
137 139 zip_contents = self.bundle.zip_into_buffer()
138 zip_memory_file = StringIO.StringIO() 140 self._zip_file_hash = hashlib.sha1(zip_contents).hexdigest()
139 zip_file = zipfile.ZipFile(zip_memory_file, 'w')
140
141 for source, relpath in self._files.iteritems():
142 zip_file.write(source, relpath)
143
144 zip_file.close()
145 print 'Zipping completed, time elapsed: %f' % (time.time() - start_time) 141 print 'Zipping completed, time elapsed: %f' % (time.time() - start_time)
146 142
147 zip_memory_file.flush() 143 response = net.url_open(
148 zip_contents = zip_memory_file.getvalue()
149 zip_memory_file.close()
150
151 self._zip_file_hash = hashlib.sha1(zip_contents).hexdigest()
152
153 response = run_isolated.url_open(
154 self._data_server_has + '?token=%s' % self._token(), 144 self._data_server_has + '?token=%s' % self._token(),
155 data=self._zip_file_hash, 145 data=binascii.unhexlify(self._zip_file_hash),
M-A Ruel 2013/08/28 15:01:48 You could use .digest() instead.
156 content_type='application/octet-stream') 146 content_type='application/octet-stream')
157 if response is None: 147 if response is None:
158 print >> sys.stderr, ( 148 print >> sys.stderr, (
159 'Unable to query server for zip file presence, aborting.') 149 'Unable to query server for zip file presence, aborting.')
160 return False 150 return False
161 151
162 if response.read(1) == chr(1): 152 if response.read(1) == chr(1):
163 print 'Zip file already on server, no need to reupload.' 153 print 'Zip file already on server, no need to reupload.'
164 return True 154 return True
165 155
166 print 'Zip file not on server, starting uploading.' 156 print 'Zip file not on server, starting uploading.'
167 157
168 url = '%s%s?priority=0&token=%s' % ( 158 url = '%s%s?priority=0&token=%s' % (
169 self._data_server_storage, self._zip_file_hash, self._token()) 159 self._data_server_storage, self._zip_file_hash, self._token())
170 response = run_isolated.url_open( 160 response = net.url_open(
171 url, data=zip_contents, content_type='application/octet-stream') 161 url, data=zip_contents, content_type='application/octet-stream')
172 if response is None: 162 if response is None:
173 print >> sys.stderr, 'Failed to upload the zip file: %s' % url 163 print >> sys.stderr, 'Failed to upload the zip file: %s' % url
174 return False 164 return False
175 165
176 return True 166 return True
177 167
178 def to_json(self): 168 def to_json(self):
179 """Exports the current configuration into a swarm-readable manifest file. 169 """Exports the current configuration into a swarm-readable manifest file.
180 170
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
216 def now(): 206 def now():
217 """Exists so it can be mocked easily.""" 207 """Exists so it can be mocked easily."""
218 return time.time() 208 return time.time()
219 209
220 210
221 def get_test_keys(swarm_base_url, test_name): 211 def get_test_keys(swarm_base_url, test_name):
222 """Returns the Swarm test key for each shards of test_name.""" 212 """Returns the Swarm test key for each shards of test_name."""
223 key_data = urllib.urlencode([('name', test_name)]) 213 key_data = urllib.urlencode([('name', test_name)])
224 url = '%s/get_matching_test_cases?%s' % (swarm_base_url, key_data) 214 url = '%s/get_matching_test_cases?%s' % (swarm_base_url, key_data)
225 215
226 for i in range(run_isolated.URL_OPEN_MAX_ATTEMPTS): 216 for i in range(net.URL_OPEN_MAX_ATTEMPTS):
227 response = run_isolated.url_open(url, retry_404=True) 217 response = net.url_open(url, retry_404=True)
228 if response is None: 218 if response is None:
229 raise Failure( 219 raise Failure(
230 'Error: Unable to find any tests with the name, %s, on swarm server' 220 'Error: Unable to find any tests with the name, %s, on swarm server'
231 % test_name) 221 % test_name)
232 222
233 result = response.read() 223 result = response.read()
234 # TODO(maruel): Compare exact string. 224 # TODO(maruel): Compare exact string.
235 if 'No matching' in result: 225 if 'No matching' in result:
236 logging.warning('Unable to find any tests with the name, %s, on swarm ' 226 logging.warning('Unable to find any tests with the name, %s, on swarm '
237 'server' % test_name) 227 'server' % test_name)
238 if i != run_isolated.URL_OPEN_MAX_ATTEMPTS: 228 if i != net.URL_OPEN_MAX_ATTEMPTS:
239 run_isolated.HttpService.sleep_before_retry(i, None) 229 net.HttpService.sleep_before_retry(i, None)
240 continue 230 continue
241 return json.loads(result) 231 return json.loads(result)
242 232
243 raise Failure( 233 raise Failure(
244 'Error: Unable to find any tests with the name, %s, on swarm server' 234 'Error: Unable to find any tests with the name, %s, on swarm server'
245 % test_name) 235 % test_name)
246 236
247 237
248 def retrieve_results(base_url, test_key, timeout, should_stop): 238 def retrieve_results(base_url, test_key, timeout, should_stop):
249 """Retrieves results for a single test_key.""" 239 """Retrieves results for a single test_key."""
250 assert isinstance(timeout, float) 240 assert isinstance(timeout, float)
251 params = [('r', test_key)] 241 params = [('r', test_key)]
252 result_url = '%s/get_result?%s' % (base_url, urllib.urlencode(params)) 242 result_url = '%s/get_result?%s' % (base_url, urllib.urlencode(params))
253 start = now() 243 start = now()
254 while True: 244 while True:
255 if timeout and (now() - start) >= timeout: 245 if timeout and (now() - start) >= timeout:
256 logging.error('retrieve_results(%s) timed out', base_url) 246 logging.error('retrieve_results(%s) timed out', base_url)
257 return {} 247 return {}
258 # Do retries ourselves. 248 # Do retries ourselves.
259 response = run_isolated.url_open( 249 response = net.url_open(result_url, retry_404=False, retry_50x=False)
260 result_url, retry_404=False, retry_50x=False)
261 if response is None: 250 if response is None:
262 # Aggressively poll for results. Do not use retry_404 so 251 # Aggressively poll for results. Do not use retry_404 so
263 # should_stop is polled more often. 252 # should_stop is polled more often.
264 remaining = min(5, timeout - (now() - start)) if timeout else 5 253 remaining = min(5, timeout - (now() - start)) if timeout else 5
265 if remaining > 0: 254 if remaining > 0:
266 run_isolated.HttpService.sleep_before_retry(1, remaining) 255 net.HttpService.sleep_before_retry(1, remaining)
267 else: 256 else:
268 try: 257 try:
269 data = json.load(response) or {} 258 data = json.load(response) or {}
270 except (ValueError, TypeError): 259 except (ValueError, TypeError):
271 logging.warning( 260 logging.warning(
272 'Received corrupted data for test_key %s. Retrying.', test_key) 261 'Received corrupted data for test_key %s. Retrying.', test_key)
273 else: 262 else:
274 if data['output']: 263 if data['output']:
275 return data 264 return data
276 if should_stop.get(): 265 if should_stop.get():
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
314 finally: 303 finally:
315 # Done, kill the remaining threads. 304 # Done, kill the remaining threads.
316 should_stop.set() 305 should_stop.set()
317 306
318 307
319 def chromium_setup(manifest): 308 def chromium_setup(manifest):
320 """Sets up the commands to run. 309 """Sets up the commands to run.
321 310
322 Highly chromium specific. 311 Highly chromium specific.
323 """ 312 """
313 # Add uncompressed zip here. It'll be compressed as part of the package sent
314 # to Swarming server.
315 run_test_name = 'run_isolated.zip'
316 manifest.bundle.add_buffer(run_test_name,
317 run_isolated.get_as_zip_package().zip_into_buffer(compress=False))
318
324 cleanup_script_name = 'swarm_cleanup.py' 319 cleanup_script_name = 'swarm_cleanup.py'
325 cleanup_script_path = os.path.join(TOOLS_PATH, cleanup_script_name) 320 manifest.bundle.add_file(os.path.join(TOOLS_PATH, cleanup_script_name),
326 run_test_name = 'run_isolated.py' 321 cleanup_script_name)
327 run_test_path = os.path.join(ROOT_DIR, run_test_name)
328 322
329 manifest.add_file(run_test_path, run_test_name)
330 manifest.add_file(cleanup_script_path, cleanup_script_name)
331 run_cmd = [ 323 run_cmd = [
332 'python', run_test_name, 324 'python', run_test_name,
333 '--hash', manifest.manifest_hash, 325 '--hash', manifest.manifest_hash,
334 '--remote', manifest.data_server_retrieval.rstrip('/') + '-gzip/', 326 '--remote', manifest.data_server_retrieval.rstrip('/') + '-gzip/',
335 ] 327 ]
336 if manifest.verbose or manifest.profile: 328 if manifest.verbose or manifest.profile:
337 # Have it print the profiling section. 329 # Have it print the profiling section.
338 run_cmd.append('--verbose') 330 run_cmd.append('--verbose')
339 manifest.add_task('Run Test', run_cmd) 331 manifest.add_task('Run Test', run_cmd)
340 332
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
397 print('Zipping up files...') 389 print('Zipping up files...')
398 if not manifest.zip_and_upload(): 390 if not manifest.zip_and_upload():
399 return 1 391 return 1
400 392
401 # Send test requests off to swarm. 393 # Send test requests off to swarm.
402 print('Sending test requests to swarm.') 394 print('Sending test requests to swarm.')
403 print('Server: %s' % swarming) 395 print('Server: %s' % swarming)
404 print('Job name: %s' % test_name) 396 print('Job name: %s' % test_name)
405 test_url = swarming + '/test' 397 test_url = swarming + '/test'
406 manifest_text = manifest.to_json() 398 manifest_text = manifest.to_json()
407 result = run_isolated.url_open(test_url, data={'request': manifest_text}) 399 result = net.url_open(test_url, data={'request': manifest_text})
408 if not result: 400 if not result:
409 print >> sys.stderr, 'Failed to send test for %s\n%s' % ( 401 print >> sys.stderr, 'Failed to send test for %s\n%s' % (
410 test_name, test_url) 402 test_name, test_url)
411 return 1 403 return 1
412 try: 404 try:
413 json.load(result) 405 json.load(result)
414 except (ValueError, TypeError) as e: 406 except (ValueError, TypeError) as e:
415 print >> sys.stderr, 'Failed to send test for %s' % test_name 407 print >> sys.stderr, 'Failed to send test for %s' % test_name
416 print >> sys.stderr, 'Manifest: %s' % manifest_text 408 print >> sys.stderr, 'Manifest: %s' % manifest_text
417 print >> sys.stderr, str(e) 409 print >> sys.stderr, str(e)
(...skipping 261 matching lines...) Expand 10 before | Expand all | Expand 10 after
679 sys.stderr.write(str(e)) 671 sys.stderr.write(str(e))
680 sys.stderr.write('\n') 672 sys.stderr.write('\n')
681 return 1 673 return 1
682 674
683 675
684 if __name__ == '__main__': 676 if __name__ == '__main__':
685 fix_encoding.fix_encoding() 677 fix_encoding.fix_encoding()
686 tools.disable_buffering() 678 tools.disable_buffering()
687 colorama.init() 679 colorama.init()
688 sys.exit(main(sys.argv[1:])) 680 sys.exit(main(sys.argv[1:]))
OLDNEW
« run_isolated.py ('K') | « run_isolated.py ('k') | tests/isolateserver_archive_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698