blob: dfe5c24ec96c9478beb2181a0c456fb4fe1bd1a9 [file] [log] [blame]
# coding: utf-8
# Copyright 2013 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
"""This file is meant to be overriden by the server's specific copy.
You can upload a new version via /restricted/upload/bot_config.
There's 3 types of functions in this file:
- get_*() to return properties to describe this bot.
- on_*() as hooks based on events happening on the bot.
- setup_*() to setup global state on the host.
This file shouldn't import from other scripts in this directory except
os_utilities which is guaranteed to be usable as an API. It's fine to import
from stdlib.
There can be two copies of this file. The base file and a bot specific version:
- For get_*() functions, the bot specific version is called first,
and if missing, the general version is called.
- For on_*() functions, the hook in the general bot_config.py is called first,
then the hook in the bot specific bot_config.py is called.
- For setup_*(), the bot specific bot_config.py is never used.
This file contains unicode to confirm UTF-8 encoded file is well supported.
Here's a pile of poo: 💩
"""
import os
import sys
from api import os_utilities
from api import platforms
# pylint: disable=unused-argument
def get_dimensions(bot):
# pylint: disable=line-too-long
"""Returns dict with the bot's dimensions.
The dimensions are what are used to select the bot that can run each task.
The bot id will be automatically selected based on the hostname with
os_utilities.get_dimensions(). If you want something more special, specify it
in your bot_config.py and override the item 'id'.
The dimensions returned here will be joined with server defined dimensions
(extracted from bots.cfg config file based on the bot id). Server defined
dimensions override the ones provided by the bot. See bot.Bot.dimensions for
more information.
See
https://chromium.googlesource.com/infra/luci/luci-py.git/+/master/appengine/swarming/doc/Magic-Values.md
Arguments:
- bot: bot.Bot instance or None. See ../api/bot.py.
"""
return os_utilities.get_dimensions()
def get_settings(bot):
"""Returns settings for this bot.
This function should be fast and mostly (preferably) constant.
"""
# Here is the default values. Keep in sync with the default values in
# ../bot_code/bot_main.py.
return {
# Free partition (disk) space to keep and to self-quarantine on.
#
# The exact minimum free space can be calculated with:
# max(disk_size * 'min_percent', min('size', disk_size * 'max_percent'))
# where 'min_percent' and 'max_percent' are relative to the total
# partition size.
# Setting any value to 0 disables the check for that value.
# Setting all values to 0 disables the minimum free disk space check.
# In practice, with the default values:
# - For disks <27GB this will be "disk_size * max_percent"
# - For disks 27GB-80GB this will be 4GB
# - For disks >80GB this will be "disk_size * min_percent"
#
# When trimming the cache, 'wiggle' is added to the value selected above.
#
'free_partition': {
# Settings specifically for the OS root partition: / on Linux
# distributions and OSX, generally (but not necessarily)
# C:\ on Windows).
# If the bot runs on the root partition, these values are ignored.
'root': {
# Minimum free space in bytes to use, if lower than 'max_percent'.
'size': 1 * 1024 * 1024 * 1024,
# Maximum free space in percent to ensure to keep free, if lower
# than 'size'.
'max_percent': 10.,
# Minimum of of free space percentage, even if higher than 'size'.
'min_percent': 6.,
},
# Settings specifically for the partition in which the bot runs on.
# These values are expected to be higher than 'root' values.
'bot': {
'size': 4 * 1024 * 1024 * 1024,
'max_percent': 15.,
'min_percent': 7.,
# Number of bytes to add to the minimum value selected above when
# calculating the isolated cache trimming. This is to ensure that
# system level processes writing logs and such do not cause to
# criss the self-quarantine line while the bot is idle.
'wiggle': 250 * 1024 * 1024,
},
},
# Local caches settings.
'caches': {
# Local isolated cache settings, used for isolated tasks. The cache
# actual size is bounded by the lesser of all 3:
# - The cache total size in bytes
# - The number of items in the cache
# - The cache is further trimmed until 'free_partition' value is
# respected.
'isolated': {
# Maximum local isolated cache size in bytes.
'size': 50 * 1024 * 1024 * 1024,
# Maximum number of items in the local isolated cache.
'items': 50 * 1024,
},
},
}
def get_state(bot):
# pylint: disable=line-too-long
"""Returns dict with a state of the bot reported to the server with each poll.
It is only for dynamic state that changes while bot is running for information
for the sysadmins.
The server can not use this state for immediate scheduling purposes (use
'dimensions' for that), but it can use it for maintenance and bookkeeping
tasks.
See
https://chromium.googlesource.com/infra/luci/luci-py.git/+/master/appengine/swarming/doc/Magic-Values.md
Arguments:
- bot: bot.Bot instance or None. See ../api/bot.py.
"""
return os_utilities.get_state()
def get_authentication_headers(bot):
"""Returns authentication headers and their expiration time.
The returned headers will be passed with each HTTP request to the Swarming
server (and only Swarming server). The bot will use the returned headers until
they are close to expiration (usually 6 min, see AUTH_HEADERS_EXPIRATION_SEC
in remote_client.py), and then it'll attempt to refresh them by calling
get_authentication_headers again.
Can be used to implement per-bot authentication. If no headers are returned,
the server will use only IP allowlist for bot authentication.
On GCE will use OAuth token of the default GCE service account. It should have
"User info" API scope enabled (this can be set when starting an instance). The
server should be configured (via bots.cfg) to trust this account (see
'require_service_account' in bots.proto).
May be called by different threads, but never concurrently.
Arguments:
- bot: bot.Bot instance. See ../api/bot.py.
Returns:
Tuple (dict with headers or None, unix timestamp of when they expire).
"""
if platforms.is_gce():
# By default, VMs do not have "User info" API enabled, as commented above.
# When this is the case, the oauth token is unusable. So do not use the
# oauth token in this case and fall back to IP based allowlist.
if ('https://www.googleapis.com/auth/userinfo.email' in platforms.gce
.oauth2_available_scopes('default')):
tok, exp = platforms.gce.oauth2_access_token_with_expiration('default')
return {'Authorization': 'Bearer %s' % tok}, exp
return (None, None)
### Hooks
def on_bot_shutdown(bot):
"""Hook function called when the bot shuts down, usually rebooting.
It's a good time to do other kinds of cleanup.
Arguments:
- bot: bot.Bot instance. See ../api/bot.py.
"""
def on_bot_startup(bot):
"""Hook function called when the bot starts, before handshake with the server.
Here the bot may initialize and examine its environment, pick initial state
and dimensions to send to the server during the handshake.
Arguments:
- bot: bot.Bot instance. See ../api/bot.py.
"""
def on_handshake(bot):
"""Hook function called when the bot starts, after handshake with the server.
Here the bot already knows server enforced dimensions (defined in server side
bots.cfg file).
This is called right before starting to poll for tasks. It's a good time to
do some final initialization or cleanup that may depend on server provided
configuration.
Arguments:
- bot: bot.Bot instance. See ../api/bot.py.
"""
def on_before_poll(bot):
"""Hook function called before polling the server for an action.
This function is guaranteed to be called before fetching the dimensions and
state of the bot.
Arguments:
- bot: bot.Bot instance. See ../api/bot.py.
"""
def on_after_poll(bot, cmd):
"""Hook function called immediately after polling the server for an action.
Arguments:
- bot: bot.Bot instance. See ../api/bot.py.
- cmd: The action that the server asked the bot to perform (e.g. "sleep",
"terminate", "run").
"""
def on_before_task(bot, bot_file, runner_cmd, runner_env):
"""Hook function called before running a task.
It shouldn't do much, since it can't cancel the task so it shouldn't do
anything too fancy.
Arguments:
- bot: bot.Bot instance. See ../api/bot.py.
- bot_file: Path to file to write information about the state of the bot.
This file can be used to pass certain info about the bot
to tasks, such as which connected android devices to run on. See
https://chromium.googlesource.com/infra/luci/luci-py.git/+/master/appengine/swarming/doc/Magic-Values.md#run_isolated
- runner_cmd: Command to be executed to launch task runner. This variable can
be mutated to override the task runner, modify its arguments
and/or add a wrapper script around it. USE WITH CAUTION.
- runner_env: Environment in which test runner is launched. Can be mutated.
"""
def on_after_task(bot, failure, internal_failure, task_dimensions, summary):
"""Hook function called after running a task.
It is an excellent place to do post-task cleanup of temporary files.
Arguments:
- bot: bot.Bot instance. See ../api/bot.py.
- failure: bool, True if the task failed.
- internal_failure: bool, True if an internal failure happened.
- task_dimensions: dict, Dimensions requested as part of the task.
- summary: dict, Summary of the task execution.
"""
# Example code:
#if failure:
# bot.host_reboot('Task failure')
#elif internal_failure:
# bot.host_reboot('Internal failure')
def on_bot_idle(bot, since_last_action):
"""Hook function called once when the bot has been idle; when it has no
command to execute.
This is an excellent place to put device in 'cool down' mode or any
"pre-warming" kind of stuff that could take several seconds to do, that would
not be appropriate to do in on_after_task(). It could be worth waiting for
`since_last_action` to be several seconds before doing a more lengthy
operation.
This function is called repeatedly until an action is taken (a task, updating,
etc).
This is a good place to do "auto reboot" for hardware based bots that are
rebooted periodically.
Arguments:
- bot: bot.Bot instance. See ../api/bot.py.
- since_last_action: time in second since last action; e.g. amount of time the
bot has been idle.
"""
# Don't try this if running inside docker.
#if (sys.platform != 'linux' or
# not platforms.linux.get_inside_docker()):
# uptime = os_utilities.get_uptime()
# if uptime > 12*60*60 * (1. + bot.get_pseudo_rand(0.2)):
# bot.host_reboot('Periodic reboot after %ds' % uptime)
### Setup
def setup_bot(bot):
"""Does one time initialization for this bot.
Returns True if it's fine to start the bot right away. Otherwise, the calling
script should exit.
TODO(maruel): Have the user call bot.host_reboot() or bot.bot_restart()
instead of returning a value.
This is an excellent place to drop a README file in the bot directory, to give
more information about the purpose of this bot.
Example: making this script starts automatically on user login via
os_utilities.set_auto_startup_win() or os_utilities.set_auto_startup_osx().
"""
with open(os.path.join(bot.base_dir, 'README'), 'w') as f:
f.write("""This directory contains a Swarming bot.
Swarming source code is hosted at
https://chromium.googlesource.com/infra/luci/luci-py.git.
The bot was generated from the server %s. To get the bot's attributes, run:
python swarming_bot.zip attributes
""" % bot.server)
return True