import contextlib import logging import os import textwrap import yaml 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: cmd = 'git clone -b {branch} {giturl}'.format( branch=branch, giturl=url ) client.run(args=cmd) 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