blob: 7b3bbf43eaf7f69897c0cba39c561500e6a28e1b [file] [log] [blame] [edit]
"""A helper script to setup and start bots on a host machine.
This script does following:
- Parses devicemap.yaml and finds out how many bots are needed for a host.
- Check if bot directories exist and create bot directories as needed.
- Create systemd configs and bot startup script.
- Run systemctl start swarming_bot--<bot number> for any bots not currently
running
NOTE: This script is being invoked from a puppet manifest and will not have
access to rest of the test code or python virtual environment. So keep in mind
to not use any non standard python libraries.
Example usage:
python bot_setup.py bot_setup.py \
--user gtvchrome --group gtvchrome --swarm-bots-home /home/bots_home \
--gsutil-dir /home/gsutil_home --gsutil-boto /home/.boto \
--fragglerock-key /home/fragglerock_service_key.json \
--eurtest-binaries /home/eurtest_binaries --venv /home/bots-home/swarm_env
"""
import argparse
import errno
import grp
import logging
import os
import pwd
import shutil
import socket
import string
import subprocess
import sys
import yaml
# This code runs on catatester host and doesn't have whole code tree, hence
# do relative import.
try:
from catatester.devicemap import devicemap_lib
except ImportError:
import devicemap_lib # pylint: disable=relative-import
_BOT_FILE_MODE = 0o774 # permit owner group execution
_SYSTEMD_FILE_MODE = 0o644 # permit owner group write
_ROOT_USER = 'root'
# TODO(adrexler): Remove _DEVICEMAP_DIR once the arg is being passed from
# puppet.
_DEVICEMAP_DIR = os.path.dirname(os.path.realpath(__file__))
_STARTUP_SCRIPT_TEMPLATE_PATH = os.path.join(
_DEVICEMAP_DIR, 'swarm_bot_startup.sh.template')
_STARTUP_SCRIPTS_PATH = 'startup_scripts'
_SWARM_BOT_PREFIX = 'swarming_bot--'
_SWARM_ZIP_RELATIVE_PATH_TO_BOT_HOME = '1/swarming_bot.zip'
_SYSTEMD_TEMPLATE_PATH = os.path.join(
_DEVICEMAP_DIR, 'swarm_bot.systemd.template')
_DEPOT_TOOLS_DIR = os.path.join(os.path.expanduser('~'), 'depot_tools')
class OSException(Exception):
"""Exception class to handle OS Exceptions."""
def _GetNumBotConfigs(hostname):
"""Gets the number of bot configs assigned to the given hostname.
Args:
hostname: (String) Catatester hostame.
Returns:
An integer, The number of bot configs for given hostname.
"""
logging.debug('Finding bots needed for host [%s]', hostname)
configs = devicemap_lib.Configs()
return configs.GetNumBotConfigs(hostname)
def CreateStartupScript(
user, group, swarm_bots_home, bot_num, bot_name, gsutil_dir,
gsutil_boto, fragglerock_key, eurtest_binaries, venv,
devicemap_dir, depot_tools_dir):
"""Create the bot startup script.
This method loads tokenized swarm_bot_startup.sh.template file, replaces the
tokens with given param values and writes it into bot folder.
Args:
user: (String) User name of the catatester host.
group: (String) Group name that is user is part of.
swarm_bots_home: (String) Root directory of all the bots.
bot_num: (Integer) The N-th bot to be created.
bot_name: (String) Bot service name.
gsutil_dir: (String) Path to gsutil installation in the host.
gsutil_boto: (String) Path to gsutil boto file in the host.
fragglerock_key: (String) Path to auth key file in the host.
eurtest_binaries: (String) Path to test binaries in the host.
venv: (String) Path to python virtual environment to be used.
devicemap_dir: (String) Path to devicemap directory on host.
depot_tools_dir: (String) Path to depot tools directory.
Returns:
The path of newly created startup script.
"""
bot_dir_path = os.path.join(swarm_bots_home, str(bot_num))
with open(_STARTUP_SCRIPT_TEMPLATE_PATH, 'r') as bot_startup:
bot_startup_template = bot_startup.read()
startup_script_content = string.Template(bot_startup_template).substitute(
bot_dir=bot_dir_path,
devicemap_dir=devicemap_dir,
eurtest_binaries=eurtest_binaries,
fragglerock_key=fragglerock_key,
gsutil_dir=gsutil_dir,
gsutil_boto=gsutil_boto,
venv=venv,
depot_tools_dir=depot_tools_dir
)
startup_script_path = os.path.join(swarm_bots_home, _STARTUP_SCRIPTS_PATH,
'%s_startup.sh' % bot_name)
_CreateFile(
startup_script_path, startup_script_content, user, group, _BOT_FILE_MODE)
return startup_script_path
def CreateSystemdServiceFile(
bot_service_name, startup_script_path, user, group):
"""Create the systemd service script for a bot.
This method loads tokenized swarm_bot.systemd.template file, replaces the
tokens with given param values and writes it into systemd service folder.
Args:
bot_service_name: (String) The systemd service name to be used.
startup_script_path: (String) Startup script path of the bot.
user: (String) User name of the catatester host.
group: (String) Group name that is user is part of.
"""
with open(_SYSTEMD_TEMPLATE_PATH, 'r') as bot_systemd:
bot_systemd_template = bot_systemd.read()
systemd_service_content = string.Template(bot_systemd_template).substitute(
bot_name=bot_service_name,
group=group,
user=user,
startup_script_path=startup_script_path)
systemd_service_path = '/etc/systemd/system/%s.service' % bot_service_name
_CreateFile(
systemd_service_path, systemd_service_content, _ROOT_USER, _ROOT_USER,
_SYSTEMD_FILE_MODE)
def SetupBot(
bot_num, user, group, swarm_bots_home, swarm_bot_prefix, swarm_zip_path,
gsutil_dir, gsutil_boto, fragglerock_key, eurtest_binaries, venv,
devicemap_dir, depot_tools_dir):
"""Setup the swarming bot.
This method :
1. Creates bot directory.
2. Copies the swarm binary to bot directory.
3. Creates start up scripts and systemd configs the bot.
4. Do a systemd config reload.
5. And finally start the bot service, if its not up.
Args:
bot_num: (Integer) The N-th bot to be created.
user: (String) User name of the catatester host.
group: (String) Group name that is user is part of.
swarm_bots_home: (String) Root directory of the bots.
swarm_bot_prefix: (String) Prefix to be used for bot service name.
swarm_zip_path: (String) Swarming binary path in the host.
gsutil_dir: (String) Path to gsutil installation in the host.
gsutil_boto: (String) Path to gsutil boto file in the host.
fragglerock_key: (String) Path to auth key file in the host.
eurtest_binaries: (String) Path to test binaries in the host.
venv: (String) Path to python virtual environment to be used.
devicemap_dir: (String) Path to the device map directory.
depot_tools_dir: (String) Path to depot tools directory.
"""
logging.debug('Working on bot #%s', bot_num)
# Create bot directory.
bot_dir_path = os.path.join(swarm_bots_home, str(bot_num))
if not os.path.exists(bot_dir_path):
logging.info('Creating directory: %s', bot_dir_path)
os.makedirs(bot_dir_path)
_UpdateFilePermissions(bot_dir_path, user, group, _BOT_FILE_MODE)
# Copy swarm binary
if os.path.dirname(swarm_zip_path) != bot_dir_path:
shutil.copy(swarm_zip_path, bot_dir_path)
_UpdateFilePermissions(swarm_zip_path, user, group, _BOT_FILE_MODE)
# Create startup scripts.
bot_name = '%s%s' % (swarm_bot_prefix, bot_num)
startup_script_path = CreateStartupScript(
user, group, swarm_bots_home, bot_num, bot_name, gsutil_dir, gsutil_boto,
fragglerock_key, eurtest_binaries, venv, devicemap_dir, depot_tools_dir)
CreateSystemdServiceFile(bot_name, startup_script_path, user, group)
# Reload the daemon, check bot status and start service if needed.
if subprocess.call(['systemctl', 'daemon-reload']):
sys.exit('Failed to reload systemctl daemon for bot # %s' % bot_num)
if subprocess.call(['systemctl', 'is-active', '--quiet', bot_name]):
logging.debug('Bot number [%s] not running. Starting now.', bot_num)
if subprocess.call(['systemctl', 'start', bot_name]):
sys.exit('Failed to start bot # %s' % bot_num)
def _UpdateFilePermissions(file_path, owner, group, mode):
"""Update file with specified mode and permissions."""
uid = pwd.getpwnam(owner).pw_uid
gid = grp.getgrnam(group).gr_gid
os.chown(file_path, uid, gid)
os.chmod(file_path, mode)
def _CreateFile(file_path, content, owner, group, mode):
"""Create file with specified content, mode and permissions."""
if not os.path.exists(os.path.dirname(file_path)):
try:
os.makedirs(os.path.dirname(file_path))
_UpdateFilePermissions(os.path.dirname(file_path), owner, group, mode)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise OSException('Unable to create dir %s due to %s.'
% (os.path.dirname(file_path), exc))
with open(file_path, 'w') as f:
f.write(content)
_UpdateFilePermissions(file_path, owner, group, mode)
def _ParseArgs():
"""Parses command-line input."""
parser = argparse.ArgumentParser()
parser.add_argument(
'--user', help='User name of the catatester host.', required=True)
parser.add_argument('--group', help='Group name to be used.', required=True)
parser.add_argument(
'--swarm_bots_home', help='Root directory of the bots.', required=True)
parser.add_argument(
'--gsutil_dir', help='Path to gsutil installation.', required=True)
parser.add_argument(
'--gsutil_boto', help='Path to gs util boto file.', required=True)
parser.add_argument(
'--fragglerock_key', help='Path to fragglerock auth key file.',
required=True)
parser.add_argument(
'--eurtest_binaries', help='Test binary path.', required=True)
parser.add_argument(
'--venv', help='Path to python to virtual environment.', required=True)
parser.add_argument(
'--devicemap_dir', help='Path to devicemap directory on the host.',
default=_DEVICEMAP_DIR)
parser.add_argument(
'--depot_tools_dir', help='Path to depot_tools in the host.',
default=_DEPOT_TOOLS_DIR)
args = parser.parse_args()
logging.debug('Executing with flags: %s', args)
return args
def main():
flags = _ParseArgs()
bot_count = _GetNumBotConfigs(socket.gethostname().split('.')[0])
swarm_bots_home = flags.swarm_bots_home
swarm_zip_path = os.path.join(
swarm_bots_home, _SWARM_ZIP_RELATIVE_PATH_TO_BOT_HOME)
if not os.path.exists(swarm_zip_path):
raise ValueError('Swarm binary does not exists at %s' % swarm_zip_path)
for bot_num in range(1, bot_count +1):
SetupBot(
bot_num=bot_num, user=flags.user, group=flags.group,
swarm_bots_home=swarm_bots_home, swarm_bot_prefix=_SWARM_BOT_PREFIX,
swarm_zip_path=swarm_zip_path, gsutil_dir=flags.gsutil_dir,
gsutil_boto=flags.gsutil_boto, fragglerock_key=flags.fragglerock_key,
eurtest_binaries=flags.eurtest_binaries, venv=flags.venv,
devicemap_dir=flags.devicemap_dir,
depot_tools_dir=flags.depot_tools_dir)
if __name__ == '__main__':
main()