ceph/teuthology/suite.py
Zack Cerza 193b3112a3 Move teuthology-results' arg parsing to scripts/
Signed-off-by: Zack Cerza <zack.cerza@inktank.com>
2013-10-10 19:09:34 -05:00

316 lines
9.7 KiB
Python

# this file is responsible for submitting tests into the queue
# by generating combinations of facets found in
# https://github.com/ceph/ceph-qa-suite.git
import copy
import errno
import itertools
import logging
import os
import re
import subprocess
import sys
import yaml
from teuthology import lock as lock
log = logging.getLogger(__name__)
def main(args):
loglevel = logging.INFO
if args.verbose:
loglevel = logging.DEBUG
logging.basicConfig(
level=loglevel,
)
base_arg = [
os.path.join(os.path.dirname(sys.argv[0]), 'teuthology-schedule'),
'--name', args.name,
'--num', str(args.num),
'--worker', args.worker,
]
if args.verbose:
base_arg.append('-v')
if args.owner:
base_arg.extend(['--owner', args.owner])
collections = [
(os.path.join(args.base, collection), collection)
for collection in args.collections
]
num_jobs = 0
for collection, collection_name in sorted(collections):
log.debug('Collection %s in %s' % (collection_name, collection))
configs = [(combine_path(collection_name, item[0]), item[1])
for item in build_matrix(collection)]
log.info('Collection %s in %s generated %d jobs' %
(collection_name, collection, len(configs)))
num_jobs += len(configs)
arch = get_arch(args.config)
machine_type = get_machine_type(args.config)
for description, config in configs:
raw_yaml = '\n'.join([file(a, 'r').read() for a in config])
parsed_yaml = yaml.load(raw_yaml)
os_type = parsed_yaml.get('os_type')
exclude_arch = parsed_yaml.get('exclude_arch')
exclude_os_type = parsed_yaml.get('exclude_os_type')
if exclude_arch:
if exclude_arch == arch:
log.info('Skipping due to excluded_arch: %s facets %s',
exclude_arch, description)
continue
if exclude_os_type:
if exclude_os_type == os_type:
log.info('Skipping due to excluded_os_type: %s facets %s',
exclude_os_type, description)
continue
# We should not run multiple tests (changing distros) unless the
# machine is a VPS.
# Re-imaging baremetal is not yet supported.
if machine_type != 'vps':
if os_type and os_type != 'ubuntu':
log.info(
'Skipping due to non-ubuntu on baremetal facets %s',
description)
continue
log.info(
'Scheduling %s', description
)
arg = copy.deepcopy(base_arg)
arg.extend([
'--description', description,
'--',
])
arg.extend(args.config)
arg.extend(config)
if args.dry_run:
log.info('dry-run: %s' % ' '.join(arg))
else:
subprocess.check_call(
args=arg,
)
if num_jobs:
arg = copy.deepcopy(base_arg)
arg.append('--last-in-suite')
if args.email:
arg.extend(['--email', args.email])
if args.timeout:
arg.extend(['--timeout', args.timeout])
if args.dry_run:
log.info('dry-run: %s' % ' '.join(arg))
else:
subprocess.check_call(
args=arg,
)
def combine_path(left, right):
"""
os.path.join(a, b) doesn't like it when b is None
"""
if right:
return os.path.join(left, right)
return left
def build_matrix(path):
"""
Return a list of items describe by path
The input is just a path. The output is an array of (description,
[file list]) tuples.
For a normal file we generate a new item for the result list.
For a directory, we (recursively) generate a new item for each
file/dir.
For a directory with a magic '+' file, we generate a single item
that concatenates all files/subdirs.
For a directory with a magic '%' file, we generate a result set
for each tiem in the directory, and then do a product to generate
a result list with all combinations.
The final description (after recursion) for each item will look
like a relative path. If there was a % product, that path
component will appear as a file with braces listing the selection
of chosen subitems.
"""
if os.path.isfile(path):
if path.endswith('.yaml'):
return [(None, [path])]
if os.path.isdir(path):
files = sorted(os.listdir(path))
if '+' in files:
# concatenate items
files.remove('+')
out = []
for fn in files:
out.extend(build_matrix(os.path.join(path, fn)))
return [(
'+',
[a[1] for a in out]
)]
elif '%' in files:
# convolve items
files.remove('%')
sublists = []
for fn in files:
raw = build_matrix(os.path.join(path, fn))
sublists.append([(combine_path(fn, item[0]), item[1])
for item in raw])
out = []
if sublists:
for sublist in itertools.product(*sublists):
name = '{' + ' '.join([item[0] for item in sublist]) + '}'
val = []
for item in sublist:
val.extend(item[1])
out.append((name, val))
return out
else:
# list items
out = []
for fn in files:
raw = build_matrix(os.path.join(path, fn))
out.extend([(combine_path(fn, item[0]), item[1])
for item in raw])
return out
return []
def ls(archive_dir, verbose):
for j in get_jobs(archive_dir):
job_dir = os.path.join(archive_dir, j)
summary = {}
try:
with file(os.path.join(job_dir, 'summary.yaml')) as f:
g = yaml.safe_load_all(f)
for new in g:
summary.update(new)
except IOError as e:
if e.errno == errno.ENOENT:
print '%s ' % j,
# pid
try:
pidfile = os.path.join(job_dir, 'pid')
found = False
if os.path.isfile(pidfile):
pid = open(pidfile, 'r').read()
if os.path.isdir("/proc/%s" % pid):
cmdline = open('/proc/%s/cmdline' % pid,
'r').read()
if cmdline.find(archive_dir) >= 0:
print '(pid %s)' % pid,
found = True
if not found:
print '(no process or summary.yaml)',
# tail
tail = os.popen(
'tail -1 %s/%s/teuthology.log' % (archive_dir, j)
).read().rstrip()
print tail,
except IOError as e:
continue
print ''
continue
else:
raise
print "{job} {success} {owner} {desc} {duration}s".format(
job=j,
owner=summary.get('owner', '-'),
desc=summary.get('description', '-'),
success='pass' if summary.get('success', False) else 'FAIL',
duration=int(summary.get('duration', 0)),
)
if verbose and 'failure_reason' in summary:
print ' {reason}'.format(reason=summary['failure_reason'])
def get_jobs(archive_dir):
dir_contents = os.listdir(archive_dir)
def is_job_dir(parent, subdir):
if (os.path.isdir(os.path.join(parent, subdir)) and re.match('\d+$',
subdir)):
return True
return False
jobs = [job for job in dir_contents if is_job_dir(archive_dir, job)]
return sorted(jobs)
def get_arch(config):
for yamlfile in config:
y = yaml.safe_load(file(yamlfile))
machine_type = y.get('machine_type')
if machine_type:
fakectx = []
locks = lock.list_locks(fakectx)
for machine in locks:
if machine['type'] == machine_type:
arch = machine['arch']
return arch
return None
def get_os_type(configs):
for config in configs:
yamlfile = config[2]
y = yaml.safe_load(file(yamlfile))
if not y:
y = {}
os_type = y.get('os_type')
if os_type:
return os_type
return None
def get_exclude_arch(configs):
for config in configs:
yamlfile = config[2]
y = yaml.safe_load(file(yamlfile))
if not y:
y = {}
exclude_arch = y.get('exclude_arch')
if exclude_arch:
return exclude_arch
return None
def get_exclude_os_type(configs):
for config in configs:
yamlfile = config[2]
y = yaml.safe_load(file(yamlfile))
if not y:
y = {}
exclude_os_type = y.get('exclude_os_type')
if exclude_os_type:
return exclude_os_type
return None
def get_machine_type(config):
for yamlfile in config:
y = yaml.safe_load(file(yamlfile))
if not y:
y = {}
machine_type = y.get('machine_type')
if machine_type:
return machine_type
return None