# https://github.com/ceph/ceph-qa-suite.git
import copy
+from datetime import datetime
import itertools
import logging
import os
import requests
+import pwd
import subprocess
+import smtplib
import sys
import yaml
+from email.mime.text import MIMEText
+from tempfile import NamedTemporaryFile
import teuthology
from teuthology import lock as lock
+from teuthology.config import config
log = logging.getLogger(__name__)
def main(args):
verbose = args['--verbose']
- limit = int(args['--limit'])
+ if verbose:
+ teuthology.log.setLevel(logging.DEBUG)
dry_run = args['--dry-run']
- name = args['--name']
+
+ base_yaml_paths = args['<config_yaml>']
+ base = os.path.expanduser(args['--base'])
+ if not os.path.exists(base):
+ schedule_fail("Base directory not found: {dir}".format(dir=base))
+ suite = args['--suite']
+ nice_suite = suite.replace('/', ':')
+ ceph_branch = args['--ceph']
+ kernel_branch = args['--kernel']
+ kernel_flavor = args['--flavor']
+ teuthology_branch = args['--teuthology-branch']
+ machine_type = args['--machine-type']
+ distro = args['--distro']
+
+ limit = int(args['--limit'])
priority = int(args['--priority'])
num = int(args['--num'])
- machine_type = args['--machine-type']
owner = args['--owner']
- base = args['--base']
- suite = args['--suite']
email = args['--email']
+ if email:
+ config.email_specified = True
+ config.results_email = email
timeout = args['--timeout']
- base_yaml_paths = args['<config_yaml>']
-
- if verbose:
- teuthology.log.setLevel(logging.DEBUG)
- prepare_and_schedule(owner=owner,
- name=name,
- suite=suite,
- machine_type=machine_type,
- base=base,
- base_yaml_paths=base_yaml_paths,
- email=email,
- priority=priority,
- limit=limit,
- num=num,
- timeout=timeout,
- dry_run=dry_run,
- verbose=verbose,
- )
+ name = make_name(nice_suite, ceph_branch, kernel_branch, kernel_flavor,
+ machine_type)
+ config_string = create_initial_config(nice_suite, ceph_branch,
+ teuthology_branch, kernel_branch,
+ kernel_flavor, distro, machine_type)
+
+ with NamedTemporaryFile(prefix='schedule_suite_') as base_yaml:
+ base_yaml.write(config_string)
+ base_yaml_paths.insert(0, base_yaml.name)
+ prepare_and_schedule(owner=owner,
+ name=name,
+ suite=suite,
+ machine_type=machine_type,
+ base=base,
+ base_yaml_paths=base_yaml_paths,
+ email=email,
+ priority=priority,
+ limit=limit,
+ num=num,
+ timeout=timeout,
+ dry_run=dry_run,
+ verbose=verbose,
+ )
+
+
+def make_name(suite, ceph_branch, kernel_branch, kernel_flavor, machine_type,
+ user=None, timestamp=None):
+ if not user:
+ user = pwd.getpwuid(os.getuid()).pw_name
+ # We assume timestamp is a datetime.datetime object
+ if not timestamp:
+ timestamp = datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
+
+ worker = get_worker(machine_type)
+ return '-'.join(
+ [user, str(timestamp), suite, ceph_branch,
+ kernel_branch, kernel_flavor, worker]
+ )
+
+
+def create_initial_config(nice_suite, ceph_branch, teuthology_branch,
+ kernel_branch, kernel_flavor, distro, machine_type):
+ # Put together a stanza specifying the kernel hash
+ if kernel_branch == 'distro':
+ kernel_hash = 'distro'
+ else:
+ kernel_hash = get_hash('kernel', kernel_branch, kernel_flavor,
+ machine_type)
+ if not kernel_hash:
+ schedule_fail(message="Kernel branch '{branch} not found".format(
+ branch=kernel_branch))
+ if kernel_hash:
+ log.info("kernel sha1: {hash}".format(hash=kernel_hash))
+ kernel_dict = dict(kernel=dict(kdb=True, sha1=kernel_hash))
+
+ # Get the ceph hash
+ ceph_hash = get_hash('ceph', ceph_branch, kernel_flavor, machine_type)
+ if not ceph_hash:
+ schedule_fail("Ceph branch '{branch}' not found".format(
+ branch=ceph_branch))
+ log.info("ceph sha1: {hash}".format(hash=ceph_hash))
+
+ # Get the ceph package version
+ ceph_version = package_version_for_hash(ceph_hash, kernel_flavor,
+ machine_type)
+ if not ceph_version:
+ schedule_fail("Packages for ceph version '{ver}' not found".format(
+ ver=ceph_version))
+ log.info("ceph version: {ver}".format(ver=ceph_version))
+
+ # Decide what branch of s3-tests to use
+ if get_branch_info('s3-tests', ceph_branch):
+ s3_branch = ceph_branch
+ else:
+ log.info("branch {0} not in s3-tests.git; will use master for"
+ "s3-tests".format(ceph_branch))
+ s3_branch = 'master'
+ log.info("s3-tests branch: %s", s3_branch)
+
+ if not teuthology_branch:
+ # Decide what branch of teuthology to use
+ if get_branch_info('teuthology', ceph_branch):
+ teuthology_branch = ceph_branch
+ else:
+ log.info("branch {0} not in teuthology.git; will use master for"
+ "teuthology".format(ceph_branch))
+ teuthology_branch = 'master'
+ log.info("teuthology branch: %s", teuthology_branch)
+
+ config_input = dict(
+ nice_suite=nice_suite,
+ ceph_branch=ceph_branch,
+ ceph_hash=ceph_hash,
+ teuthology_branch=teuthology_branch,
+ machine_type=machine_type,
+ kernel_stanza=yaml.dump(kernel_dict, default_flow_style=False).strip(),
+ distro=distro,
+ s3_branch=s3_branch,
+ )
+ return config_template.format(**config_input)
def prepare_and_schedule(owner, name, suite, machine_type, base,
dry_run, verbose):
arch = get_arch(machine_type)
- if ',' in machine_type:
- worker = 'multi'
- else:
- worker = machine_type
-
base_args = [
os.path.join(os.path.dirname(sys.argv[0]), 'teuthology-schedule'),
'--name', name,
'--num', str(num),
- '--worker', worker,
+ '--worker', get_worker(machine_type),
]
if priority:
base_args.extend(['--priority', str(priority)])
if email and num_jobs:
arg = copy.deepcopy(base_args)
arg.append('--last-in-suite')
- arg.extend(['--email', email])
+ if email:
+ arg.extend(['--email', email])
if timeout:
arg.extend(['--timeout', timeout])
if dry_run:
)
-def get_gitbuilder_url(distro, pkg_type, arch, kernel_flavor):
+def schedule_fail(message, name=None):
+ email = config.results_email
+ if not email:
+ return
+ subject = "Failed to schedule {name}".format(name=name)
+ msg = MIMEText(message)
+ msg['Subject'] = subject
+ msg['From'] = config.results_sending_email
+ msg['To'] = email
+ smtp = smtplib.SMTP('localhost')
+ smtp.sendmail(msg['From'], [msg['To']], msg.as_string())
+ smtp.quit()
+ raise ScheduleFailError(message, name)
+
+
+class ScheduleFailError(RuntimeError):
+ def __init__(self, message, name=None):
+ self.message = message
+ self.name = name
+
+ def __str__(self):
+ return "Job scheduling {name} failed: '{msg}'".format(
+ name=self.name,
+ msg=self.message,
+ ).replace(' ', ' ')
+
+
+def get_worker(machine_type):
+ if ',' in machine_type:
+ return 'multi'
+ else:
+ return machine_type
+
+
+def get_hash(project='ceph', branch='master', flavor='basic',
+ distro='ubuntu', machine_type='plana'):
+ # Alternate method for ceph
+ #resp = requests.get(
+ # 'https://api.github.com/repos/ceph/ceph/git/refs/heads/master')
+ #hash = .json()['object']['sha']
+ (arch, release, pkg_type) = get_distro_defaults(distro, machine_type)
+ base_url = get_gitbuilder_url(project, release, pkg_type, arch, flavor)
+ url = os.path.join(base_url, 'ref', branch, 'sha1')
+ resp = requests.get(url)
+ if not resp.ok:
+ return None
+ return str(resp.text.strip())
+
+
+def get_distro_defaults(distro, machine_type):
+ """
+ Given a distro (e.g. 'ubuntu') and machine type, return:
+ (arch, release, pkg_type)
+
+ This is mainly used to default to:
+ ('x86_64', 'precise', 'deb') when passed 'ubuntu' and 'plana'
+ And ('armv7l', 'saucy', 'deb') when passed 'ubuntu' and 'saya'
+ And ('x86_64', 'centos6', 'rpm') when passed anything non-ubuntu
+ """
+ if distro == 'ubuntu':
+ if machine_type == 'saya':
+ arch = 'armv7l'
+ release = 'saucy'
+ pkg_type = 'deb'
+ else:
+ arch = 'x86_64'
+ release = 'precise'
+ pkg_type = 'deb'
+ else:
+ arch = 'x86_64'
+ release = 'centos6'
+ pkg_type = 'rpm'
+ return (
+ arch,
+ release,
+ pkg_type,
+ )
+
+
+def get_gitbuilder_url(project, distro, pkg_type, arch, kernel_flavor):
"""
Return a base URL like:
http://gitbuilder.ceph.com/ceph-deb-squeeze-x86_64-basic/
+ :param project: 'ceph' or 'kernel'
:param distro: A distro-ish string like 'trusty' or 'fedora20'
:param pkg_type: Probably 'rpm' or 'deb'
:param arch: A string like 'x86_64'
:param kernel_flavor: A string like 'basic'
"""
- templ = 'http://gitbuilder.ceph.com/ceph-{pkg}-{distro}-{arch}-{flav}/'
- return templ.format(pkg=pkg_type, distro=distro, arch=arch,
+ templ = 'http://gitbuilder.ceph.com/{proj}-{pkg}-{distro}-{arch}-{flav}/'
+ return templ.format(proj=project, pkg=pkg_type, distro=distro, arch=arch,
flav=kernel_flavor)
-def get_hash(branch='master'):
- # Alternate method:
- #resp = requests.get(
- # 'https://api.github.com/repos/ceph/ceph/git/refs/heads/master')
- #hash = .json()['object']['sha']
- base_url = get_gitbuilder_url('precise', 'deb', 'x86_64', 'basic')
- url = os.path.join(base_url, 'ref', branch, 'sha1')
- resp = requests.get(url)
- resp.raise_for_status()
- return resp.text.strip()
-
-
-def package_version_for_hash(hash, distro, pkg_type, arch='x86_64',
- kernel_flavor='basic'):
- base_url = get_gitbuilder_url(distro, pkg_type, arch, kernel_flavor)
+def package_version_for_hash(hash, kernel_flavor='basic',
+ distro='ubuntu', machine_type='plana'):
+ (arch, release, pkg_type) = get_distro_defaults(distro, machine_type)
+ base_url = get_gitbuilder_url('ceph', release, pkg_type, arch,
+ kernel_flavor)
url = os.path.join(base_url, 'sha1', hash, 'version')
resp = requests.get(url)
if not resp.ok:
return resp.text.strip()
+def get_branch_info(project, branch, project_owner='ceph'):
+ url_templ = 'https://api.github.com/repos/{project_owner}/{project}/git/refs/heads/{branch}' # noqa
+ url = url_templ.format(project_owner=project_owner, project=project,
+ branch=branch)
+ resp = requests.get(url)
+ if resp.ok:
+ return resp.json()
+
+
def schedule_suite(name,
path,
base_yamls,
arch = machine['arch']
return arch
return None
+
+# yaml template for the config that becomes the base for each generated job
+# config
+config_template = """
+teuthology_branch: {teuthology_branch}
+{kernel_stanza}
+nuke-on-error: true
+machine_type: {machine_type}
+os_type: {distro}
+branch: {ceph_branch}
+suite: {nice_suite}
+tasks:
+- chef:
+- clock.check:
+overrides:
+ workunit:
+ sha1: {ceph_hash}
+ s3tests:
+ branch: {s3_branch}
+ install:
+ ceph:
+ sha1: {ceph_hash}
+ ceph:
+ sha1: {ceph_hash}
+ conf:
+ mon:
+ debug ms: 1
+ debug mon: 20
+ debug paxos: 20
+ osd:
+ debug ms: 1
+ debug osd: 20
+ debug filestore: 20
+ debug journal: 20
+ log-whitelist:
+ - slow request
+ ceph-deploy:
+ branch:
+ dev: {ceph_branch}
+ conf:
+ mon:
+ osd default pool size: 2
+ debug mon: 1
+ debug paxos: 20
+ debug ms: 20
+ client:
+ log file: /var/log/ceph/ceph-$name.$pid.log
+ admin_socket:
+ branch: {ceph_branch}
+""".strip()