ceph/qa/tasks/calamari_nosetests.py
2016-12-14 11:29:55 -06:00

290 lines
9.3 KiB
Python

import contextlib
import logging
import os
import textwrap
import yaml
from cStringIO import StringIO
from teuthology import contextutil
from teuthology import misc
from teuthology import packaging
from teuthology.orchestra import run
log = logging.getLogger(__name__)
# extra stuff we need to do our job here
EXTRA_PKGS = [
'git',
]
# stuff that would be in a devmode install, but should be
# installed in the system for running nosetests against
# a production install.
EXTRA_NOSETEST_PKGS = [
'python-psutil',
'python-mock',
]
def find_client0(cluster):
''' Find remote that has client.0 role, or None '''
for rem, roles in cluster.remotes.iteritems():
if 'client.0' in roles:
return rem
return None
def pip(remote, package, venv=None, uninstall=False, force=False):
''' {un}install a package with pip, possibly in a virtualenv '''
if venv:
pip = os.path.join(venv, 'bin', 'pip')
args = ['sudo', pip]
else:
args = ['sudo', 'pip']
if uninstall:
args.extend(['uninstall', '-y'])
else:
args.append('install')
if force:
args.append('-I')
args.append(package)
remote.run(args=args)
@contextlib.contextmanager
def install_epel(remote):
''' install a disabled-by-default epel repo config file '''
remove = False
try:
if remote.os.package_type == 'deb':
yield
else:
remove = True
distromajor = remote.os.version.split('.')[0]
repofiledata = textwrap.dedent('''
[epel]
name=epel{version}
metalink=http://mirrors.fedoraproject.org/metalink?repo=epel-{version}&arch=$basearch
enabled=0
gpgcheck=0
''').format(version=distromajor)
misc.create_file(remote, '/etc/yum.repos.d/epel.repo',
data=repofiledata, sudo=True)
remote.run(args='sudo yum clean all')
yield
finally:
if remove:
misc.delete_file(remote, '/etc/yum.repos.d/epel.repo', sudo=True)
def enable_epel(remote, enable=True):
''' enable/disable the epel repo '''
args = 'sudo sed -i'.split()
if enable:
args.extend(['s/enabled=0/enabled=1/'])
else:
args.extend(['s/enabled=1/enabled=0/'])
args.extend(['/etc/yum.repos.d/epel.repo'])
remote.run(args=args)
remote.run(args='sudo yum clean all')
@contextlib.contextmanager
def install_extra_pkgs(client):
''' Install EXTRA_PKGS '''
try:
for pkg in EXTRA_PKGS:
packaging.install_package(pkg, client)
yield
finally:
for pkg in EXTRA_PKGS:
packaging.remove_package(pkg, client)
@contextlib.contextmanager
def clone_calamari(config, client):
''' clone calamari source into current directory on remote '''
branch = config.get('calamari_branch', 'master')
url = config.get('calamari_giturl', 'git://github.com/ceph/calamari')
try:
out = StringIO()
# ensure branch is present (clone -b will succeed even if
# the branch doesn't exist, falling back to master)
client.run(
args='git ls-remote %s %s' % (url, branch),
stdout=out,
label='check for calamari branch %s existence' % branch
)
if len(out.getvalue()) == 0:
raise RuntimeError("Calamari branch %s doesn't exist" % branch)
client.run(args='git clone -b %s %s' % (branch, url))
yield
finally:
# sudo python setup.py develop may have left some root files around
client.run(args='sudo rm -rf calamari')
@contextlib.contextmanager
def write_info_yaml(cluster, client):
''' write info.yaml to client for nosetests '''
try:
info = {
'cluster': {
rem.name: {'roles': roles}
for rem, roles in cluster.remotes.iteritems()
}
}
misc.create_file(client, 'calamari/info.yaml',
data=yaml.safe_dump(info, default_flow_style=False))
yield
finally:
misc.delete_file(client, 'calamari/info.yaml')
@contextlib.contextmanager
def write_test_conf(client):
''' write calamari/tests/test.conf to client for nosetests '''
try:
testconf = textwrap.dedent('''
[testing]
calamari_control = external
ceph_control = external
bootstrap = False
api_username = admin
api_password = admin
embedded_timeout_factor = 1
external_timeout_factor = 3
external_cluster_path = info.yaml
''')
misc.create_file(client, 'calamari/tests/test.conf', data=testconf)
yield
finally:
misc.delete_file(client, 'calamari/tests/test.conf')
@contextlib.contextmanager
def prepare_nosetest_env(client):
try:
# extra dependencies that would be in the devmode venv
if client.os.package_type == 'rpm':
enable_epel(client, enable=True)
for package in EXTRA_NOSETEST_PKGS:
packaging.install_package(package, client)
if client.os.package_type == 'rpm':
enable_epel(client, enable=False)
# install nose itself into the calamari venv, force it in case it's
# already installed in the system, so we can invoke it by path without
# fear that it's not present
pip(client, 'nose', venv='/opt/calamari/venv', force=True)
# install a later version of requests into the venv as well
# (for precise)
pip(client, 'requests', venv='/opt/calamari/venv', force=True)
# link (setup.py develop) calamari/rest-api into the production venv
# because production does not include calamari_rest.management, needed
# for test_rest_api.py's ApiIntrospection
args = 'cd calamari/rest-api'.split() + [run.Raw(';')] + \
'sudo /opt/calamari/venv/bin/python setup.py develop'.split()
client.run(args=args)
# because, at least in Python 2.6/Centos, site.py uses
# 'os.path.exists()' to process .pth file entries, and exists() uses
# access(2) to check for existence, all the paths leading up to
# $HOME/calamari/rest-api need to be searchable by all users of
# the package, which will include the WSGI/Django app, running
# as the Apache user. So make them all world-read-and-execute.
args = 'sudo chmod a+x'.split() + \
['.', './calamari', './calamari/rest-api']
client.run(args=args)
# make one dummy request just to get the WSGI app to do
# all its log creation here, before the chmod below (I'm
# looking at you, graphite -- /var/log/calamari/info.log and
# /var/log/calamari/exception.log)
client.run(args='wget -q -O /dev/null http://localhost')
# /var/log/calamari/* is root-or-apache write-only
client.run(args='sudo chmod a+w /var/log/calamari/*')
yield
finally:
args = 'cd calamari/rest-api'.split() + [run.Raw(';')] + \
'sudo /opt/calamari/venv/bin/python setup.py develop -u'.split()
client.run(args=args)
for pkg in ('nose', 'requests'):
pip(client, pkg, venv='/opt/calamari/venv', uninstall=True)
for package in EXTRA_NOSETEST_PKGS:
packaging.remove_package(package, client)
@contextlib.contextmanager
def run_nosetests(client):
''' Actually run the tests '''
args = [
'cd',
'calamari',
run.Raw(';'),
'CALAMARI_CONFIG=/etc/calamari/calamari.conf',
'/opt/calamari/venv/bin/nosetests',
'-v',
'tests/',
]
client.run(args=args)
yield
@contextlib.contextmanager
def task(ctx, config):
"""
Run Calamari tests against an instance set up by 'calamari_server'.
-- clone the Calamari source into $HOME (see options)
-- write calamari/info.yaml describing the cluster
-- write calamari/tests/test.conf containing
'external' for calamari_control and ceph_control
'bootstrap = False' to disable test bootstrapping (installing minions)
no api_url necessary (inferred from client.0)
'external_cluster_path = info.yaml'
-- modify the production Calamari install to allow test runs:
install nose in the venv
install EXTRA_NOSETEST_PKGS
link in, with setup.py develop, calamari_rest (for ApiIntrospection)
-- set CALAMARI_CONFIG to point to /etc/calamari/calamari.conf
-- nosetests -v tests/
Options are:
calamari_giturl: url from which to git clone calamari
(default: git://github.com/ceph/calamari)
calamari_branch: git branch of calamari to check out
(default: master)
Note: the tests must find a clean cluster, so don't forget to
set the crush default type appropriately, or install min_size OSD hosts
"""
client0 = find_client0(ctx.cluster)
if client0 is None:
raise RuntimeError("must have client.0 role")
with contextutil.nested(
lambda: install_epel(client0),
lambda: install_extra_pkgs(client0),
lambda: clone_calamari(config, client0),
lambda: write_info_yaml(ctx.cluster, client0),
lambda: write_test_conf(client0),
lambda: prepare_nosetest_env(client0),
lambda: run_nosetests(client0),
):
yield