blob: 69464f2ef763be4ca6ed4179e7f9b304f0010c50 [file] [log] [blame]
#! /usr/bin/python
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Manage swarming bots.
* Launch bots, e.g. 200 bots:
$ swarming_bots.py launch --working_dir WORKING_DIR --bot_count 200
* Kill working bots:
$ swarming_bots.py kill
* The hierachy of working dir is like
WORKING_DIR
|-- bot_0
| |-- 092b5bd4562f579711823f61e311de37247c853a-cacert.pem
| |-- bot_config.log
| |-- swarming_bot.log
| |-- swarming_bot.zip
|-- bot_1
|-- 092b5bd4562f579711823f61e311de37247c853a-cacert.pem
|-- bot_config.log
|-- swarming_bot.log
|-- swarming_bot.zip
Note bot_config.py:get_dimensions() will rely on the the bot number
in the path to generate bot id.
* TODO (fdeng):
** Better management of each individual bot given a bot id.
*** Kill a bot given a bot id.
*** Launch a bot given a bot id.
*** Restart a bot given a bot id.
** Kill a bot gracefully.
"""
import argparse
import logging
import os
import shutil
import subprocess
import sys
import time
import urllib
import common
from autotest_lib.client.common_lib import global_config
DEFAULT_SWARMING_PROXY = global_config.global_config.get_config_value(
'CROS', "swarming_proxy", default=None)
BOT_FILENAME = 'swarming_bot.zip'
class BotManagementError(Exception):
"""Raised for any bot management related error."""
def _parse_args(args):
"""Parse args.
@param args: Argument list passed from main.
@return: A tuple with the parsed args, as returned by parser.parse_args.
"""
if not args:
print 'Too few arguments, try --help'
sys.exit(1)
parser = argparse.ArgumentParser(
description='Launch swarming bots on a autotest server')
subparsers = parser.add_subparsers()
# launch parser
launch_parser = subparsers.add_parser(
'launch', help='Launch swarming bots.')
launch_parser.required = False
launch_parser.set_defaults(which='launch')
launch_parser.add_argument(
'--bot_count', type=int, dest='bot_count',required=True,
help='Number of bots to launch ')
launch_parser.add_argument(
'--working_dir', type=str, dest='working_dir', required=True,
help='A working directory where bots will store files '
'generated at runtime')
launch_parser.add_argument(
'--swarming_proxy', type=str, dest='swarming_proxy',
default=DEFAULT_SWARMING_PROXY,
help='The URL of the swarming instance to talk to, '
'Default to the one specified in global config')
# kill parser
kill_parser = subparsers.add_parser(
'kill', help='Kill current running swarming bots.')
kill_parser.required = False
kill_parser.set_defaults(which='kill')
return parser.parse_args(args)
def launch_bot(bot_dir, swarming_proxy):
"""Launch a swarming bot.
@param bot_dir: The working directory for the bot.
The directory is used to store bot code,
log file, and any file generated by the bot
at run time.
@param swarming_proxy: URL to the swarming instance.
"""
logging.debug('Bootstrap bot in %s', bot_dir)
if os.path.exists(bot_dir):
shutil.rmtree(bot_dir)
os.makedirs(bot_dir)
dest = os.path.join(bot_dir, BOT_FILENAME)
logging.debug('Getting bot code from: %s/bot_code', swarming_proxy)
urllib.urlretrieve('%s/bot_code' % swarming_proxy, dest)
cmd = [sys.executable, BOT_FILENAME]
logging.debug('Calling command: %s', cmd)
process = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=bot_dir)
logging.debug('Created bot (pid: %d)', process.pid)
def launch(bot_count, working_dir, swarming_proxy):
"""Launch |bot_count| number of swarming bots.
Each bot will have a seperate working dir,
the name is like 'WORKING_DIR/bot_3'.
bot_config.py will depend on the bot number generated here
to generate bot id.
@param bot_count: the number of bots to launch.
@param working_dir: The root working directory for all bots.
@param swarming_proxy: URL to the swarming instance.
"""
wdir = os.path.abspath(os.path.expanduser(working_dir))
for i in range(0, bot_count):
bot_dir = os.path.join(wdir, 'bot_%s' % i)
launch_bot(bot_dir, swarming_proxy)
# If we let the process exit immediately, the last process won't
# be launched sometimes. So sleep for 3 secs.
logging.debug('Wait 3 seconds for process creation to complete.')
time.sleep(3)
def killall():
"""Kill all the running bots."""
# TODO(fdeng): We should record PID and use python's
# os module to manage the processes.
cmd = ('ps ax|grep \'swarming_bot.*zip\'|grep -v grep|'
'awk {\'print $1\'}|xargs -i kill {}')
logging.debug('Killing all bots, cmd: %s', cmd)
process = subprocess.Popen(cmd, shell=True)
process.wait()
if process.returncode != 0:
raise BotManagementError(
'Kill bots failed, cmd return code %d: %s',
process.returncode, cmd)
def main(args):
"""Main.
@args: A list of system arguments.
"""
logging.basicConfig(level=logging.DEBUG)
args = _parse_args(args)
if args.which == 'launch':
if not args.swarming_proxy:
logging.error(
'No swarming proxy instance specified. '
'Specify swarming_proxy in [CROS] in shadow_config, '
'or use --swarming_proxy')
return 1
if not args.swarming_proxy.startswith('https://'):
swarming_proxy = 'https://' + args.swarming_proxy
else:
swarming_proxy = args.swarming_proxy
logging.info('Connecting to %s', swarming_proxy)
launch(args.bot_count, args.working_dir, args.swarming_proxy)
elif args.which == 'kill':
killall()
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))