OLD | NEW |
1 # Copyright 2013 The LUCI Authors. All rights reserved. | 1 # Copyright 2013 The LUCI Authors. All rights reserved. |
2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
4 | 4 |
5 """Swarming bot main process. | 5 """Swarming bot main process. |
6 | 6 |
7 This is the program that communicates with the Swarming server, ensures the code | 7 This is the program that communicates with the Swarming server, ensures the code |
8 is always up to date and executes a child process to run tasks and upload | 8 is always up to date and executes a child process to run tasks and upload |
9 results back. | 9 results back. |
10 | 10 |
11 It manages self-update and rebooting the host in case of problems. | 11 It manages self-update and rebooting the host in case of problems. |
12 | 12 |
13 Set the environment variable SWARMING_LOAD_TEST=1 to disable the use of | 13 Set the environment variable SWARMING_LOAD_TEST=1 to disable the use of |
14 server-provided bot_config.py. This permits safe load testing. | 14 server-provided bot_config.py. This permits safe load testing. |
15 """ | 15 """ |
16 | 16 |
17 import contextlib | 17 import contextlib |
18 import json | 18 import json |
19 import logging | 19 import logging |
20 import optparse | 20 import optparse |
21 import os | 21 import os |
22 import shutil | 22 import shutil |
23 import sys | 23 import sys |
24 import tempfile | 24 import tempfile |
25 import threading | 25 import threading |
26 import time | 26 import time |
27 import traceback | 27 import traceback |
28 import zipfile | 28 import zipfile |
29 | 29 |
| 30 import bot_auth |
30 import common | 31 import common |
| 32 import file_refresher |
| 33 import remote_client |
31 import singleton | 34 import singleton |
32 from api import bot | 35 from api import bot |
33 from api import os_utilities | 36 from api import os_utilities |
34 from utils import file_path | 37 from utils import file_path |
35 from utils import net | 38 from utils import net |
36 from utils import on_error | 39 from utils import on_error |
37 from utils import subprocess42 | 40 from utils import subprocess42 |
38 from utils import zip_package | 41 from utils import zip_package |
39 | 42 |
40 | 43 |
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
170 should_continue = bot_config.setup_bot(botobj) | 173 should_continue = bot_config.setup_bot(botobj) |
171 except Exception as e: | 174 except Exception as e: |
172 msg = '%s\n%s' % (e, traceback.format_exc()[-2048:]) | 175 msg = '%s\n%s' % (e, traceback.format_exc()[-2048:]) |
173 botobj.post_error('bot_config.setup_bot() threw: %s' % msg) | 176 botobj.post_error('bot_config.setup_bot() threw: %s' % msg) |
174 return | 177 return |
175 | 178 |
176 if not should_continue and not skip_reboot: | 179 if not should_continue and not skip_reboot: |
177 botobj.restart('Starting new swarming bot: %s' % THIS_FILE) | 180 botobj.restart('Starting new swarming bot: %s' % THIS_FILE) |
178 | 181 |
179 | 182 |
| 183 def get_authentication_headers(botobj): |
| 184 """Calls bot_config.get_authentication_headers() if it is defined. |
| 185 |
| 186 Doesn't catch exceptions. |
| 187 """ |
| 188 if _in_load_test_mode(): |
| 189 return (None, None) |
| 190 logging.info('get_authentication_headers()') |
| 191 from config import bot_config |
| 192 func = getattr(bot_config, 'get_authentication_headers', None) |
| 193 return func(botobj) if func else (None, None) |
| 194 |
| 195 |
180 ### end of bot_config handler part. | 196 ### end of bot_config handler part. |
181 | 197 |
182 | 198 |
183 def get_min_free_space(): | 199 def get_min_free_space(): |
184 """Returns free disk space needed. | 200 """Returns free disk space needed. |
185 | 201 |
186 Add a "250 MiB slack space" for logs, temporary files and whatever other leak. | 202 Add a "250 MiB slack space" for logs, temporary files and whatever other leak. |
187 """ | 203 """ |
188 return int((os_utilities.get_min_free_space(THIS_FILE) + 250.) * 1024 * 1024) | 204 return int((os_utilities.get_min_free_space(THIS_FILE) + 250.) * 1024 * 1024) |
189 | 205 |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
228 exception handler for incoming commands from the Swarming server. If for | 244 exception handler for incoming commands from the Swarming server. If for |
229 any reason the local test runner script can not be run successfully, | 245 any reason the local test runner script can not be run successfully, |
230 this function is invoked. | 246 this function is invoked. |
231 """ | 247 """ |
232 logging.error('Error: %s', error) | 248 logging.error('Error: %s', error) |
233 data = { | 249 data = { |
234 'id': botobj.id, | 250 'id': botobj.id, |
235 'message': error, | 251 'message': error, |
236 'task_id': task_id, | 252 'task_id': task_id, |
237 } | 253 } |
238 return net.url_read_json( | 254 return botobj.remote.url_read_json( |
239 botobj.server + '/swarming/api/v1/bot/task_error/%s' % task_id, data=data) | 255 '/swarming/api/v1/bot/task_error/%s' % task_id, data=data) |
240 | 256 |
241 | 257 |
242 def on_shutdown_hook(b): | 258 def on_shutdown_hook(b): |
243 """Called when the bot is restarting.""" | 259 """Called when the bot is restarting.""" |
244 call_hook(b, 'on_bot_shutdown') | 260 call_hook(b, 'on_bot_shutdown') |
245 # Aggressively set itself up so we ensure the auto-reboot configuration is | 261 # Aggressively set itself up so we ensure the auto-reboot configuration is |
246 # fine before restarting the host. This is important as some tasks delete the | 262 # fine before restarting the host. This is important as some tasks delete the |
247 # autorestart script (!) | 263 # autorestart script (!) |
248 setup_bot(True) | 264 setup_bot(True) |
249 | 265 |
250 | 266 |
251 def get_bot(): | 267 def get_bot(): |
252 """Returns a valid Bot instance. | 268 """Returns a valid Bot instance. |
253 | 269 |
254 Should only be called once in the process lifetime. | 270 Should only be called once in the process lifetime. |
255 """ | 271 """ |
256 # This variable is used to bootstrap the initial bot.Bot object, which then is | 272 # This variable is used to bootstrap the initial bot.Bot object, which then is |
257 # used to get the dimensions and state. | 273 # used to get the dimensions and state. |
258 attributes = { | 274 attributes = { |
259 'dimensions': {u'id': ['none']}, | 275 'dimensions': {u'id': ['none']}, |
260 'state': {}, | 276 'state': {}, |
261 'version': generate_version(), | 277 'version': generate_version(), |
262 } | 278 } |
263 config = get_config() | 279 config = get_config() |
264 assert not config['server'].endswith('/'), config | 280 assert not config['server'].endswith('/'), config |
265 | 281 |
266 # Create a temporary object to call the hooks. | 282 # Use temporary Bot object to call get_attributes. Attributes are needed to |
| 283 # construct the "real" bot.Bot. |
| 284 attributes = get_attributes( |
| 285 bot.Bot( |
| 286 remote_client.RemoteClient(config['server'], None), |
| 287 attributes, |
| 288 config['server'], |
| 289 config['server_version'], |
| 290 os.path.dirname(THIS_FILE), |
| 291 on_shutdown_hook)) |
| 292 |
| 293 # Make remote client callback use the returned bot object. We assume here |
| 294 # RemoteClient doesn't call its callback in the constructor (since 'botobj' is |
| 295 # undefined during the construction). |
267 botobj = bot.Bot( | 296 botobj = bot.Bot( |
| 297 remote_client.RemoteClient( |
| 298 config['server'], |
| 299 lambda: get_authentication_headers(botobj)), |
268 attributes, | 300 attributes, |
269 config['server'], | 301 config['server'], |
270 config['server_version'], | 302 config['server_version'], |
271 os.path.dirname(THIS_FILE), | 303 os.path.dirname(THIS_FILE), |
272 on_shutdown_hook) | 304 on_shutdown_hook) |
273 return bot.Bot( | 305 return botobj |
274 get_attributes(botobj), | |
275 config['server'], | |
276 config['server_version'], | |
277 os.path.dirname(THIS_FILE), | |
278 on_shutdown_hook) | |
279 | 306 |
280 | 307 |
281 def clean_isolated_cache(botobj): | 308 def clean_isolated_cache(botobj): |
282 """Asks run_isolated to clean its cache. | 309 """Asks run_isolated to clean its cache. |
283 | 310 |
284 This may take a while but it ensures that in the case of a run_isolated run | 311 This may take a while but it ensures that in the case of a run_isolated run |
285 failed and it temporarily used more space than min_free_disk, it can cleans up | 312 failed and it temporarily used more space than min_free_disk, it can cleans up |
286 the mess properly. | 313 the mess properly. |
287 | 314 |
288 It will remove unexpected files, remove corrupted files, trim the cache size | 315 It will remove unexpected files, remove corrupted files, trim the cache size |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
335 # First thing is to get an arbitrary url. This also ensures the network is | 362 # First thing is to get an arbitrary url. This also ensures the network is |
336 # up and running, which is necessary before trying to get the FQDN below. | 363 # up and running, which is necessary before trying to get the FQDN below. |
337 resp = net.url_read(config['server'] + '/swarming/api/v1/bot/server_ping') | 364 resp = net.url_read(config['server'] + '/swarming/api/v1/bot/server_ping') |
338 if resp is None: | 365 if resp is None: |
339 logging.error('No response from server_ping') | 366 logging.error('No response from server_ping') |
340 except Exception as e: | 367 except Exception as e: |
341 # url_read() already traps pretty much every exceptions. This except | 368 # url_read() already traps pretty much every exceptions. This except |
342 # clause is kept there "just in case". | 369 # clause is kept there "just in case". |
343 logging.exception('server_ping threw') | 370 logging.exception('server_ping threw') |
344 | 371 |
| 372 # Next we make sure the bot can make authenticated calls by grabbing |
| 373 # the auth headers, retrying on errors a bunch of times. We don't give up |
| 374 # if it fails though (maybe the bot will "fix itself" later). |
| 375 botobj = get_bot() |
| 376 try: |
| 377 botobj.remote.initialize(quit_bit) |
| 378 except remote_client.InitializationError as exc: |
| 379 botobj.post_error('failed to grab auth headers: %s' % exc.last_error) |
| 380 logging.error('Can\'t grab auth headers, continuing anyway...') |
| 381 |
345 if quit_bit.is_set(): | 382 if quit_bit.is_set(): |
346 logging.info('Early quit 1') | 383 logging.info('Early quit 1') |
347 return 0 | 384 return 0 |
348 | 385 |
349 # If this fails, there's hardly anything that can be done, the bot can't | 386 # If this fails, there's hardly anything that can be done, the bot can't |
350 # even get to the point to be able to self-update. | 387 # even get to the point to be able to self-update. |
351 botobj = get_bot() | 388 resp = botobj.remote.url_read_json( |
352 resp = net.url_read_json( | 389 '/swarming/api/v1/bot/handshake', data=botobj._attributes) |
353 botobj.server + '/swarming/api/v1/bot/handshake', | |
354 data=botobj._attributes) | |
355 if not resp: | 390 if not resp: |
356 logging.error('Failed to contact for handshake') | 391 logging.error('Failed to contact for handshake') |
357 else: | 392 else: |
358 logging.info('Connected to %s', resp.get('server_version')) | 393 logging.info('Connected to %s', resp.get('server_version')) |
359 if resp.get('bot_version') != botobj._attributes['version']: | 394 if resp.get('bot_version') != botobj._attributes['version']: |
360 logging.warning( | 395 logging.warning( |
361 'Found out we\'ll need to update: server said %s; we\'re %s', | 396 'Found out we\'ll need to update: server said %s; we\'re %s', |
362 resp.get('bot_version'), botobj._attributes['version']) | 397 resp.get('bot_version'), botobj._attributes['version']) |
363 | 398 |
364 if arg_error: | 399 if arg_error: |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
411 return 0 | 446 return 0 |
412 | 447 |
413 | 448 |
414 def poll_server(botobj, quit_bit): | 449 def poll_server(botobj, quit_bit): |
415 """Polls the server to run one loop. | 450 """Polls the server to run one loop. |
416 | 451 |
417 Returns True if executed some action, False if server asked the bot to sleep. | 452 Returns True if executed some action, False if server asked the bot to sleep. |
418 """ | 453 """ |
419 # Access to a protected member _XXX of a client class - pylint: disable=W0212 | 454 # Access to a protected member _XXX of a client class - pylint: disable=W0212 |
420 start = time.time() | 455 start = time.time() |
421 resp = net.url_read_json( | 456 resp = botobj.remote.url_read_json( |
422 botobj.server + '/swarming/api/v1/bot/poll', data=botobj._attributes) | 457 '/swarming/api/v1/bot/poll', data=botobj._attributes) |
423 if not resp: | 458 if not resp: |
424 return False | 459 return False |
425 logging.debug('Server response:\n%s', resp) | 460 logging.debug('Server response:\n%s', resp) |
426 | 461 |
427 cmd = resp['cmd'] | 462 cmd = resp['cmd'] |
428 if cmd == 'sleep': | 463 if cmd == 'sleep': |
429 quit_bit.wait(resp['duration']) | 464 quit_bit.wait(resp['duration']) |
430 return False | 465 return False |
431 | 466 |
432 if cmd == 'terminate': | 467 if cmd == 'terminate': |
433 quit_bit.set() | 468 quit_bit.set() |
434 # This is similar to post_update() in task_runner.py. | 469 # This is similar to post_update() in task_runner.py. |
435 params = { | 470 params = { |
436 'cost_usd': 0, | 471 'cost_usd': 0, |
437 'duration': 0, | 472 'duration': 0, |
438 'exit_code': 0, | 473 'exit_code': 0, |
439 'hard_timeout': False, | 474 'hard_timeout': False, |
440 'id': botobj.id, | 475 'id': botobj.id, |
441 'io_timeout': False, | 476 'io_timeout': False, |
442 'output': '', | 477 'output': '', |
443 'output_chunk_start': 0, | 478 'output_chunk_start': 0, |
444 'task_id': resp['task_id'], | 479 'task_id': resp['task_id'], |
445 } | 480 } |
446 net.url_read_json( | 481 botobj.remote.url_read_json( |
447 botobj.server + '/swarming/api/v1/bot/task_update/%s' % resp['task_id'], | 482 '/swarming/api/v1/bot/task_update/%s' % resp['task_id'], |
448 data=params) | 483 data=params) |
449 return False | 484 return False |
450 | 485 |
451 if cmd == 'run': | 486 if cmd == 'run': |
452 if run_manifest(botobj, resp['manifest'], start): | 487 if run_manifest(botobj, resp['manifest'], start): |
453 # Completed a task successfully so update swarming_bot.zip if necessary. | 488 # Completed a task successfully so update swarming_bot.zip if necessary. |
454 update_lkgbc(botobj) | 489 update_lkgbc(botobj) |
455 # Clean up cache after a task | 490 # Clean up cache after a task |
456 clean_isolated_cache(botobj) | 491 clean_isolated_cache(botobj) |
457 # TODO(maruel): Handle the case where quit_bit.is_set() happens here. This | 492 # TODO(maruel): Handle the case where quit_bit.is_set() happens here. This |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
496 if not manifest['command']: | 531 if not manifest['command']: |
497 hard_timeout += manifest['io_timeout'] or 600 | 532 hard_timeout += manifest['io_timeout'] or 600 |
498 | 533 |
499 url = manifest.get('host', botobj.server) | 534 url = manifest.get('host', botobj.server) |
500 task_dimensions = manifest['dimensions'] | 535 task_dimensions = manifest['dimensions'] |
501 task_result = {} | 536 task_result = {} |
502 | 537 |
503 failure = False | 538 failure = False |
504 internal_failure = False | 539 internal_failure = False |
505 msg = None | 540 msg = None |
| 541 auth_params_dumper = None |
506 work_dir = os.path.join(botobj.base_dir, 'work') | 542 work_dir = os.path.join(botobj.base_dir, 'work') |
507 try: | 543 try: |
508 try: | 544 try: |
509 if os.path.isdir(work_dir): | 545 if os.path.isdir(work_dir): |
510 file_path.rmtree(work_dir) | 546 file_path.rmtree(work_dir) |
511 except OSError: | 547 except OSError: |
512 # If a previous task created an undeleteable file/directory inside 'work', | 548 # If a previous task created an undeleteable file/directory inside 'work', |
513 # make sure that following tasks are not affected. This is done by working | 549 # make sure that following tasks are not affected. This is done by working |
514 # around the undeleteable directory by creating a temporary directory | 550 # around the undeleteable directory by creating a temporary directory |
515 # instead. This is not normal behavior. The bot will report a failure on | 551 # instead. This is not normal behavior. The bot will report a failure on |
516 # start. | 552 # start. |
517 work_dir = tempfile.mkdtemp(dir=botobj.base_dir, prefix='work') | 553 work_dir = tempfile.mkdtemp(dir=botobj.base_dir, prefix='work') |
518 else: | 554 else: |
519 os.makedirs(work_dir) | 555 os.makedirs(work_dir) |
520 | 556 |
521 env = os.environ.copy() | 557 env = os.environ.copy() |
522 # Windows in particular does not tolerate unicode strings in environment | 558 # Windows in particular does not tolerate unicode strings in environment |
523 # variables. | 559 # variables. |
524 env['SWARMING_TASK_ID'] = task_id.encode('ascii') | 560 env['SWARMING_TASK_ID'] = task_id.encode('ascii') |
525 | 561 |
526 task_in_file = os.path.join(work_dir, 'task_runner_in.json') | 562 task_in_file = os.path.join(work_dir, 'task_runner_in.json') |
527 with open(task_in_file, 'wb') as f: | 563 with open(task_in_file, 'wb') as f: |
528 f.write(json.dumps(manifest)) | 564 f.write(json.dumps(manifest)) |
529 call_hook(botobj, 'on_before_task') | 565 call_hook(botobj, 'on_before_task') |
530 task_result_file = os.path.join(work_dir, 'task_runner_out.json') | 566 task_result_file = os.path.join(work_dir, 'task_runner_out.json') |
531 if os.path.exists(task_result_file): | 567 if os.path.exists(task_result_file): |
532 os.remove(task_result_file) | 568 os.remove(task_result_file) |
| 569 |
| 570 # Start a thread that periodically puts authentication headers and other |
| 571 # authentication related information to a file on disk. task_runner and its |
| 572 # subprocesses read it from there before making authenticated HTTP calls. |
| 573 auth_params_file = os.path.join(work_dir, 'bot_auth_params.json') |
| 574 if botobj.remote.uses_auth: |
| 575 env['SWARMING_AUTH_PARAMS'] = str(auth_params_file) |
| 576 auth_params_dumper = file_refresher.FileRefresherThread( |
| 577 auth_params_file, lambda: bot_auth.prepare_auth_params_json(botobj)) |
| 578 auth_params_dumper.start() |
| 579 else: |
| 580 env.pop('SWARMING_AUTH_PARAMS', None) |
| 581 if os.path.exists(auth_params_file): |
| 582 os.remove(auth_params_file) |
| 583 |
533 command = [ | 584 command = [ |
534 sys.executable, THIS_FILE, 'task_runner', | 585 sys.executable, THIS_FILE, 'task_runner', |
535 '--swarming-server', url, | 586 '--swarming-server', url, |
536 '--in-file', task_in_file, | 587 '--in-file', task_in_file, |
537 '--out-file', task_result_file, | 588 '--out-file', task_result_file, |
538 '--cost-usd-hour', str(botobj.state.get('cost_usd_hour') or 0.), | 589 '--cost-usd-hour', str(botobj.state.get('cost_usd_hour') or 0.), |
539 # Include the time taken to poll the task in the cost. | 590 # Include the time taken to poll the task in the cost. |
540 '--start', str(start), | 591 '--start', str(start), |
541 '--min-free-space', str(get_min_free_space()), | 592 '--min-free-space', str(get_min_free_space()), |
542 ] | 593 ] |
543 logging.debug('Running command: %s', command) | 594 logging.debug('Running command: %s', command) |
| 595 |
544 # Put the output file into the current working directory, which should be | 596 # Put the output file into the current working directory, which should be |
545 # the one containing swarming_bot.zip. | 597 # the one containing swarming_bot.zip. |
546 log_path = os.path.join(botobj.base_dir, 'logs', 'task_runner_stdout.log') | 598 log_path = os.path.join(botobj.base_dir, 'logs', 'task_runner_stdout.log') |
547 os_utilities.roll_log(log_path) | 599 os_utilities.roll_log(log_path) |
548 os_utilities.trim_rolled_log(log_path) | 600 os_utilities.trim_rolled_log(log_path) |
549 with open(log_path, 'a+b') as f: | 601 with open(log_path, 'a+b') as f: |
550 proc = subprocess42.Popen( | 602 proc = subprocess42.Popen( |
551 command, | 603 command, |
552 detached=True, | 604 detached=True, |
553 cwd=botobj.base_dir, | 605 cwd=botobj.base_dir, |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
594 failure = bool(task_result.get('exit_code')) if task_result else False | 646 failure = bool(task_result.get('exit_code')) if task_result else False |
595 return not internal_failure and not failure | 647 return not internal_failure and not failure |
596 except Exception as e: | 648 except Exception as e: |
597 # Failures include IOError when writing if the disk is full, OSError if | 649 # Failures include IOError when writing if the disk is full, OSError if |
598 # swarming_bot.zip doesn't exist anymore, etc. | 650 # swarming_bot.zip doesn't exist anymore, etc. |
599 logging.exception('run_manifest failed') | 651 logging.exception('run_manifest failed') |
600 msg = 'Internal exception occured: %s\n%s' % ( | 652 msg = 'Internal exception occured: %s\n%s' % ( |
601 e, traceback.format_exc()[-2048:]) | 653 e, traceback.format_exc()[-2048:]) |
602 internal_failure = True | 654 internal_failure = True |
603 finally: | 655 finally: |
| 656 if auth_params_dumper: |
| 657 auth_params_dumper.stop() |
604 if internal_failure: | 658 if internal_failure: |
605 post_error_task(botobj, msg, task_id) | 659 post_error_task(botobj, msg, task_id) |
606 call_hook( | 660 call_hook( |
607 botobj, 'on_after_task', failure, internal_failure, task_dimensions, | 661 botobj, 'on_after_task', failure, internal_failure, task_dimensions, |
608 task_result) | 662 task_result) |
609 if os.path.isdir(work_dir): | 663 if os.path.isdir(work_dir): |
610 try: | 664 try: |
611 file_path.rmtree(work_dir) | 665 file_path.rmtree(work_dir) |
612 except Exception as e: | 666 except Exception as e: |
613 botobj.post_error( | 667 botobj.post_error( |
(...skipping 10 matching lines...) Expand all Loading... |
624 | 678 |
625 Does not return. | 679 Does not return. |
626 """ | 680 """ |
627 # Alternate between .1.zip and .2.zip. | 681 # Alternate between .1.zip and .2.zip. |
628 new_zip = 'swarming_bot.1.zip' | 682 new_zip = 'swarming_bot.1.zip' |
629 if os.path.basename(THIS_FILE) == new_zip: | 683 if os.path.basename(THIS_FILE) == new_zip: |
630 new_zip = 'swarming_bot.2.zip' | 684 new_zip = 'swarming_bot.2.zip' |
631 new_zip = os.path.join(os.path.dirname(THIS_FILE), new_zip) | 685 new_zip = os.path.join(os.path.dirname(THIS_FILE), new_zip) |
632 | 686 |
633 # Download as a new file. | 687 # Download as a new file. |
634 url = botobj.server + '/swarming/api/v1/bot/bot_code/%s' % version | 688 url_path = '/swarming/api/v1/bot/bot_code/%s' % version |
635 if not net.url_retrieve(new_zip, url): | 689 if not botobj.remote.url_retrieve(new_zip, url_path): |
636 # It can happen when a server is rapidly updated multiple times in a row. | 690 # It can happen when a server is rapidly updated multiple times in a row. |
637 botobj.post_error( | 691 botobj.post_error( |
638 'Unable to download %s from %s; first tried version %s' % | 692 'Unable to download %s from %s; first tried version %s' % |
639 (new_zip, url, version)) | 693 (new_zip, botobj.server + url_path, version)) |
640 # Poll again, this may work next time. To prevent busy-loop, sleep a little. | 694 # Poll again, this may work next time. To prevent busy-loop, sleep a little. |
641 time.sleep(2) | 695 time.sleep(2) |
642 return | 696 return |
643 | 697 |
644 s = os.stat(new_zip) | 698 s = os.stat(new_zip) |
645 logging.info('Restarting to %s; %d bytes.', new_zip, s.st_size) | 699 logging.info('Restarting to %s; %d bytes.', new_zip, s.st_size) |
646 sys.stdout.flush() | 700 sys.stdout.flush() |
647 sys.stderr.flush() | 701 sys.stderr.flush() |
648 | 702 |
649 proc = subprocess42.Popen( | 703 proc = subprocess42.Popen( |
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
744 os.path.dirname(THIS_FILE), 'logs', 'bot_std%s.log' % t) | 798 os.path.dirname(THIS_FILE), 'logs', 'bot_std%s.log' % t) |
745 os_utilities.roll_log(log_path) | 799 os_utilities.roll_log(log_path) |
746 os_utilities.trim_rolled_log(log_path) | 800 os_utilities.trim_rolled_log(log_path) |
747 | 801 |
748 error = None | 802 error = None |
749 if len(args) != 0: | 803 if len(args) != 0: |
750 error = 'Unexpected arguments: %s' % args | 804 error = 'Unexpected arguments: %s' % args |
751 try: | 805 try: |
752 return run_bot(error) | 806 return run_bot(error) |
753 finally: | 807 finally: |
754 call_hook(bot.Bot(None, None, None, os.path.dirname(THIS_FILE), None), | 808 call_hook(bot.Bot(None, None, None, None, os.path.dirname(THIS_FILE), None), |
755 'on_bot_shutdown') | 809 'on_bot_shutdown') |
756 logging.info('main() returning') | 810 logging.info('main() returning') |
OLD | NEW |