blob: 4aae5fb0549adf89aba4bc8b1085e96d90623f3a [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A small maintenance tool to list slaves."""
import json
import optparse
import os
import re
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__),
os.pardir)))
from common import chromium_utils
def ProcessShortName(master):
"""Substitutes shortcuts."""
master = re.sub(r'\bt\b', 'tryserver', master)
master = re.sub(r'\bc\b', 'chromium', master)
master = re.sub(r'\bcr\b', 'chrome', master)
master = re.sub(r'\bco\b', 'chromiumos', master)
master = re.sub(r'\bcros\b', 'chromeos', master)
return master
def LoadMaster(slaves, path):
"""Add list of slaves for a given master to 'slaves'.
Args:
slaves(list): this list is extended with dicts describing each slave from
the specified master. *This is the output of this function*.
path(str): path to a master directory
(e.g. '<leading dirs>/build/masters/master.chromium.infra/')
"""
cur_slaves = chromium_utils.GetSlavesFromMasterPath(path)
cur_master = os.path.basename(path)
for cur_slave in cur_slaves:
cur_slave['mastername'] = cur_master
slaves.extend(cur_slaves)
def Main(argv):
usage = """%prog [options]
Note: t is replaced with 'tryserver', 'c' with chromium' and
co with 'chromiumos', 'cr' with chrome, 'cros' with 'chromeos'."""
masters_path = chromium_utils.ListMastersWithSlaves()
masters = [os.path.basename(f) for f in masters_path]
# Strip off 'master.'
masters = [re.match(r'(master\.|)(.*)', m).group(2) for m in masters]
parser = optparse.OptionParser(usage=usage)
group = optparse.OptionGroup(parser, 'Slaves to process')
group.add_option('-x', '--master', default=[], action='append',
help='Master to use to load the slaves list.')
group.add_option('-k', '--kind', action='append', default=[],
help='Only slaves with a substring present in a builder')
group.add_option('-b', '--builder', action='append', default=[],
help='Only slaves attached to a specific builder')
group.add_option('--os', action='append', default=[],
help='Only slaves using a specific OS')
group.add_option('--os-version', action='append', default=[],
help='Only slaves using a specific OS version')
group.add_option('-s', '--slave', action='append', default=[])
group.add_option('--cq',
help='Only slaves used by specific CQ (commit queue) config')
parser.add_option_group(group)
group = optparse.OptionGroup(parser, 'Output format')
group.add_option('-n', '--name', default='host',
dest='fmt', action='store_const', const='host',
help='Output slave hostname')
group.add_option('-r', '--raw',
dest='fmt', action='store_const', const='raw',
help='Output all slave info')
group.add_option('-t', '--assignment',
dest='fmt', action='store_const', const='assignment',
help='Output slave tasks too')
group.add_option('', '--botmap',
dest='fmt', action='store_const', const='botmap',
help='Output botmap style')
group.add_option('-w', '--waterfall',
dest='fmt', action='store_const', const='waterfall',
help='Output slave master and tasks')
group.add_option('', '--json',
dest='fmt', action='store_const', const='json',
help='Output slave list as JSON')
parser.add_option_group(group)
options, args = parser.parse_args(argv)
if args:
parser.error('Unknown argument(s): %s\n' % args)
slaves = []
if not options.master:
# Populates by default with every master with a twistd.pid, thus has
# been started.
for m_p in masters_path:
if os.path.isfile(os.path.join(m_p, 'twistd.pid')):
LoadMaster(slaves, m_p)
else:
for master in options.master:
if master == 'allcros':
for m in (m for m in masters if (m.startswith('chromeos') or
m.startswith('chromiumos'))):
LoadMaster(slaves, masters_path[masters.index(m)])
elif master == 'all':
for m_p in masters_path:
LoadMaster(slaves, m_p)
slaves.sort(key=lambda x: (x['mastername'], x.get('hostname')))
else:
if not master in masters:
master = ProcessShortName(master)
if not master in masters:
parser.error('Unknown master \'%s\'.\nChoices are: %s' % (
master, ', '.join(masters)))
LoadMaster(slaves, masters_path[masters.index(master)])
if options.kind:
def kind_interested_in_any(builders):
if isinstance(builders, basestring):
return any(builders.find(k) >= 0 for k in options.kind)
return any(any(x.find(k) >= 0 for k in options.kind) for x in builders)
slaves = [s for s in slaves if kind_interested_in_any(s.get('builder'))]
if options.builder:
builders = set(options.builder)
def builder_interested_in_any(x):
return builders.intersection(set(x))
slaves = [s for s in slaves
if builder_interested_in_any(s.get('builder', []))]
if options.os:
selected = set(options.os)
slaves = [s for s in slaves if s.get('os', 'unknown') in selected]
if options.os_version:
selected = set(options.os_version)
slaves = [s for s in slaves if s.get('version', 'unknown') in selected]
if options.slave:
selected = set(options.slave)
slaves = [s for s in slaves if s.get('hostname') in selected]
if options.cq:
with open(options.cq) as f:
cq_config = json.load(f)
# Handle CQ configs still remaining in the CQ repo (as opposed to project
# repos, see http://crbug.com/443613 .
legacy_config = cq_config.get(
'verifiers_no_patch', {}).get('try_job_verifier')
if not legacy_config:
legacy_config = cq_config.get(
'verifiers', {}).get('try_job_verifier')
if legacy_config:
cq_config = {'trybots': legacy_config}
def is_used_by_cq(slave):
def get_cq_builders(key):
return cq_config.get('trybots', {}).get(key, {}).get(
slave['mastername'].replace('master.', ''), {}).keys()
cq_builders = set(
get_cq_builders('launched') + get_cq_builders('triggered'))
return bool(cq_builders.intersection(set(slave.get('builder', []))))
slaves = [s for s in slaves if is_used_by_cq(s)]
if options.fmt == 'json':
normalized = []
for s in slaves:
host = s.get('hostname')
if host:
builder = s.get('builder') or []
if isinstance(builder, basestring):
builder = [builder]
assert isinstance(builder, list)
s['builder'] = sorted(builder)
normalized.append(s)
json.dump(
sorted(normalized, key=lambda s: (s.get('mastername'), s['hostname'])),
sys.stdout, sort_keys=True, indent=2, separators=(',', ': '))
sys.stdout.write('\n')
return
for s in slaves:
if options.fmt == 'raw':
print s
elif options.fmt == 'assignment':
print s.get('hostname', 'unknown'), ':', s.get('builder', 'unknown')
elif options.fmt == 'waterfall':
print s.get('hostname', 'unknown'), ':', s.get('master', 'unknown'), \
':', s.get('builder', 'unknown')
elif options.fmt == 'master':
print s.get('hostname', 'unknown'), ':', s.get('mastername', 'unknown'), \
':', s.get('builder', 'unknown')
elif options.fmt == 'botmap':
host = s.get('hostname')
if host:
master = s.get('mastername') or '?'
slaveos = s.get('os') or '?'
pathsep = '\\' if s.get('os') == 'win' else '/'
if 'subdir' in s:
d = (pathsep + 'b' + pathsep + 'build' + pathsep + 'nested' +
pathsep + s['subdir'])
else:
d = pathsep + 'b'
builders = s.get('builder') or '?'
if not isinstance(builders, list):
builders = [builders]
for b in sorted(builders):
print '%-30s %-20s %-35s %-35s %-10s' % (host, d, master, b, slaveos)
else:
print s.get('hostname', 'unknown')
if __name__ == '__main__':
sys.exit(Main(None))