]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
Do everything that schedule_suite.sh does
authorZack Cerza <zack@cerza.org>
Thu, 19 Jun 2014 17:41:51 +0000 (13:41 -0400)
committerZack Cerza <zack@cerza.org>
Wed, 25 Jun 2014 18:54:23 +0000 (12:54 -0600)
Signed-off-by: Zack Cerza <zack.cerza@inktank.com>
scripts/suite.py
teuthology/suite.py

index 58baf03c2d203ace5babbcc759b5739d5087ad41..3d9ac4e55f6c48d2e57642d3a13d78c3ce7020b5 100644 (file)
@@ -4,8 +4,8 @@ import teuthology.suite
 
 doc = """
 usage: teuthology-suite [-h]
-       teuthology-suite --name <name> --suite <suite> [options]
-       teuthology-suite -n <name> -s <suite> [options] [<config_yaml>...]
+       teuthology-suite --suite <suite> [options]
+       teuthology-suite  -s <suite> [options] [<config_yaml>...]
 
 Run a suite of ceph integration tests. A suite is a directory containing
 facets. A facet is a directory containing config snippets. Running a suite
@@ -21,13 +21,27 @@ Miscellaneous arguments:
 
 Standard arguments:
   <config_yaml>               Optional extra job yaml to include
-  -n, --name <name>           Name for this suite
   --base <base>               Base directory for the suite
                               e.g. ~/src/ceph-qa-suite/suites
   -s <suite>, --suite <suite>
                               The suite to schedule
+  -c <ceph>, --ceph <ceph>    The ceph branch to run against
+                              [default: master]
+  -k <kernel>, --kernel <kernel>
+                              The kernel branch to run against
+                              [default: testing]
+  -f <flavor>, --flavor <flavor>
+                              The kernel flavor to run against: ('basic',
+                              'gcov', 'notcmalloc')
+                              [default: basic]
+  -t <branch>, --teuthology-branch <branch>
+                              The teuthology branch to run against
+                              [default: master]
   -m <type>, --machine-type <type>
                               Machine type [default: plana]
+  -d <distro>, --distro <distro>
+                              Distribution to run against
+                              [default: ubuntu]
 
 Scheduler arguments:
   --owner <owner>             Job owner
index acfb37a9ea2820503e9ed5c89ac81a1a8bb32a1f..f7e6dd2df27fbdd283ca203d51925922b15d2e3e 100644 (file)
 # 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,
@@ -56,16 +159,11 @@ 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)])
@@ -90,7 +188,8 @@ def prepare_and_schedule(owner, name, suite, machine_type, base,
     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:
@@ -101,36 +200,106 @@ def prepare_and_schedule(owner, name, suite, machine_type, base,
             )
 
 
-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:
@@ -138,6 +307,15 @@ def package_version_for_hash(hash, distro, pkg_type, arch='x86_64',
     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,
@@ -295,3 +473,53 @@ def get_arch(machine_type):
             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()