ceph/teuthology/task/calamari.py
Dan Mick e26bb35649 Add calamari setup/test tasks
Signed-off-by: Warren Usui <warren.usui@inktank.com>
Signed-off-by: Dan Mick <dan.mick@inktank.com>
2014-02-11 13:34:31 -08:00

461 lines
15 KiB
Python

"""
calamari - set up various machines with roles for participating
in Calamari management app testing. Requires secret info for
accessing authenticated package repos to install Calamari, supplied
in an override: clause for calamari.reposetup below. Contains
five tasks:
- calamari.reposetup: set up the calamari package repos (all targets)
- calamari.agent: install stats collection daemons (all cluster targets)
- calamari.restapi: cluster-management api access (one monitor target)
- calamari.server: main webapp/gui front end (management target)
- calamari.test: run automated test against calamari.server target (local)
calamari.test runs on the local machine, as it accesses the Calamari
server across https using requests.py, which must be present. It uses
several external modules in calamari_test/.
"""
from cStringIO import StringIO
import contextlib
import logging
import os
import subprocess
import teuthology.misc as teuthology
import textwrap
import time
from ..orchestra import run
log = logging.getLogger(__name__)
def _edit_diamond_config(remote, serverhost):
""" Edit remote's diamond config to send stats to serverhost """
ret = remote.run(args=['sudo',
'sed',
'-i',
's/calamari/{host}/'.format(host=serverhost),
'/etc/diamond/diamond.conf'],
stdout=StringIO())
if not ret:
return False
return remote.run(args=['sudo', 'service', 'diamond', 'restart'])
def _disable_default_nginx(remote):
"""
Fix up nginx values
"""
script = textwrap.dedent('''
if [ -f /etc/nginx/conf.d/default.conf ]; then
mv /etc/nginx/conf.d/default.conf \
/etc/nginx/conf.d/default.disabled
fi
if [ -f /etc/nginx/sites-enabled/default ] ; then
rm /etc/nginx/sites-enabled/default
fi
service nginx restart
service {service} restart
''')
service = _http_service_name(remote)
script = script.format(service=service)
teuthology.sudo_write_file(remote, '/tmp/disable.nginx', script)
return remote.run(args=['sudo', 'bash', '/tmp/disable.nginx'],
stdout=StringIO())
def _setup_calamari_cluster(remote, restapi_remote):
"""
Add restapi db entry to the server.
"""
restapi_hostname = str(restapi_remote).split('@')[1]
sqlcmd = 'insert into ceph_cluster (name, api_base_url) ' \
'values ("{host}", "http://{host}:5000/api/v0.1/");'. \
format(host=restapi_hostname)
teuthology.write_file(remote, '/tmp/create.cluster.sql', sqlcmd)
return remote.run(args=['cat',
'/tmp/create.cluster.sql',
run.Raw('|'),
'sudo',
'sqlite3',
'/opt/calamari/webapp/calamari/db.sqlite3'],
stdout=StringIO())
RELEASE_MAP = {
'Ubuntu precise': dict(flavor='deb', release='ubuntu', version='precise'),
'Debian wheezy': dict(flavor='deb', release='debian', version='wheezy'),
'CentOS 6.4': dict(flavor='rpm', release='centos', version='6.4'),
'RedHatEnterpriseServer 6.4': dict(flavor='rpm', release='rhel',
version='6.4'),
}
def _get_relmap(rem):
relmap = getattr(rem, 'relmap', None)
if relmap is not None:
return relmap
lsb_release_out = StringIO()
rem.run(args=['lsb_release', '-ics'], stdout=lsb_release_out)
release = lsb_release_out.getvalue().replace('\n', ' ').rstrip()
if release in RELEASE_MAP:
rem.relmap = RELEASE_MAP[release]
return rem.relmap
else:
lsb_release_out = StringIO()
rem.run(args=['lsb_release', '-irs'], stdout=lsb_release_out)
release = lsb_release_out.getvalue().replace('\n', ' ').rstrip()
if release in RELEASE_MAP:
rem.relmap = RELEASE_MAP[release]
return rem.relmap
raise RuntimeError('Can\'t get release info for {}'.format(rem))
def _sqlite_package_name(rem):
name = 'sqlite3' if _get_relmap(rem)['flavor'] == 'deb' else None
return name
def _http_service_name(rem):
name = 'httpd' if _get_relmap(rem)['flavor'] == 'rpm' else 'apache2'
return name
def _install_repo(remote, pkgdir, username, password):
# installing repo is assumed to be idempotent
relmap = _get_relmap(remote)
log.info('Installing repo on %s', remote)
if relmap['flavor'] == 'deb':
contents = 'deb https://{username}:{password}@download.inktank.com/' \
'{pkgdir}/deb {codename} main'
contents = contents.format(username=username, password=password,
pkgdir=pkgdir, codename=relmap['version'],)
teuthology.sudo_write_file(remote,
'/etc/apt/sources.list.d/inktank.list',
contents)
remote.run(args=['sudo',
'apt-get',
'install',
'apt-transport-https',
'-y'])
result = remote.run(args=['sudo', 'apt-get', 'update', '-y'],
stdout=StringIO())
return True
elif relmap['flavor'] == 'rpm':
baseurl = 'https://{username}:{password}@download.inktank.com/' \
'{pkgdir}/rpm/{release}{version}'
contents = textwrap.dedent('''
[inktank]
name=Inktank Storage, Inc.
baseurl={baseurl}
gpgcheck=1
enabled=1
'''.format(baseurl=baseurl))
contents = contents.format(username=username,
password=password,
pkgdir=pkgdir,
release=relmap['release'],
version=relmap['version'])
teuthology.sudo_write_file(remote,
'/etc/yum.repos.d/inktank.repo',
contents)
return remote.run(args=['sudo', 'yum', 'makecache'])
else:
return False
def _remove_repo(remote):
log.info('Removing repo on %s', remote)
flavor = _get_relmap(remote)['flavor']
if flavor == 'deb':
teuthology.delete_file(remote, '/etc/apt/sources.list.d/inktank.list',
sudo=True, force=True)
result = remote.run(args=['sudo', 'apt-get', 'update', '-y'],
stdout=StringIO())
return True
elif flavor == 'rpm':
teuthology.delete_file(remote, '/etc/yum.repos.d/inktank.repo',
sudo=True, force=True)
return remote.run(args=['sudo', 'yum', 'makecache'])
else:
return False
def _install_repokey(remote):
# installing keys is assumed to be idempotent
log.info('Installing repo key on %s', remote)
flavor = _get_relmap(remote)['flavor']
if flavor == 'deb':
return remote.run(args=['wget',
'-q',
'-O-',
'http://download.inktank.com/keys/release.asc',
run.Raw('|'),
'sudo',
'apt-key',
'add',
'-'])
elif flavor == 'rpm':
args = ['sudo', 'rpm', '--import',
'http://download.inktank.com/keys/release.asc']
return remote.run(args=args)
else:
return False
def _install_package(package, remote):
"""
package: name
remote: Remote() to install on
release: deb only, 'precise' or 'wheezy'
pkgdir: may or may not include a branch name, so, say, either
packages or packages-staging/master
"""
log.info('Installing package %s on %s', package, remote)
flavor = _get_relmap(remote)['flavor']
if flavor == 'deb':
pkgcmd = ['DEBIAN_FRONTEND=noninteractive',
'sudo',
'-E',
'apt-get',
'-y',
'install',
'{package}'.format(package=package)]
elif flavor == 'rpm':
pkgcmd = ['sudo',
'yum',
'-y',
'install',
'{package}'.format(package=package)]
else:
log.error('_install_package: bad flavor ' + flavor + '\n')
return False
return remote.run(args=pkgcmd)
def _remove_package(package, remote):
"""
remove package from remote
"""
flavor = _get_relmap(remote)['flavor']
if flavor == 'deb':
pkgcmd = ['DEBIAN_FRONTEND=noninteractive',
'sudo',
'-E',
'apt-get',
'-y',
'purge',
'{package}'.format(package=package)]
elif flavor == 'rpm':
pkgcmd = ['sudo',
'yum',
'-y',
'erase',
'{package}'.format(package=package)]
else:
log.error('_remove_package: bad flavor ' + flavor + '\n')
return False
return remote.run(args=pkgcmd)
"""
Tasks
"""
@contextlib.contextmanager
def agent(ctx, config):
"""
task agent
calamari.agent: install stats collection (for each cluster host)
For example::
tasks:
- calamari.agent:
roles:
- mon.0
- osd.0
- osd.1
server: server.0
"""
log.info('calamari.agent starting')
overrides = ctx.config.get('overrides', {})
teuthology.deep_merge(config, overrides.get('calamari.agent', {}))
remotes = teuthology.roles_to_remotes(ctx.cluster, config)
try:
for rem in remotes:
log.info('Installing calamari-agent on %s', rem)
_install_package('calamari-agent', rem)
server_role = config.get('server')
if not server_role:
raise RuntimeError('must supply \'server\' config key')
server_remote = ctx.cluster.only(server_role).remotes.keys()[0]
# why isn't shortname available by default?
serverhost = server_remote.name.split('@')[1]
log.info('configuring Diamond for {}'.format(serverhost))
if not _edit_diamond_config(rem, serverhost):
raise RuntimeError(
'Diamond config edit failed on {0}'.format(rem)
)
yield
finally:
for rem in remotes:
_remove_package('calamari-agent', rem)
@contextlib.contextmanager
def reposetup(ctx, config):
"""
task reposetup
Sets up calamari repository on all remotes; cleans up when done
calamari.reposetup:
pkgdir:
username:
password:
Supply the above in an override file if you need to manage the
secret repo credentials separately from the test definition (likely).
pkgdir encodes package directory (possibly more than one path component)
as in https://<username>:<password>@SERVER/<pkgdir>/{deb,rpm}{..}
"""
overrides = ctx.config.get('overrides', {})
# XXX deep_merge returns the result, which matters if either is None
# make sure that doesn't happen
if config is None:
config = {'dummy': 'dummy'}
teuthology.deep_merge(config, overrides.get('calamari.reposetup', {}))
try:
pkgdir = config['pkgdir']
username = config['username']
password = config['password']
except KeyError:
raise RuntimeError('requires pkgdir, username, and password')
remotes = ctx.cluster.remotes.keys()
try:
for rem in remotes:
log.info(rem)
_install_repokey(rem)
_install_repo(rem, pkgdir, username, password)
yield
finally:
for rem in remotes:
_remove_repo(rem)
@contextlib.contextmanager
def restapi(ctx, config):
"""
task restapi
Calamari Rest API
For example::
tasks:
- calamari.restapi:
roles: mon.0
"""
overrides = ctx.config.get('overrides', {})
teuthology.deep_merge(config, overrides.get('calamari.restapi', {}))
remotes = teuthology.roles_to_remotes(ctx.cluster, config)
try:
for rem in remotes:
log.info(rem)
_install_package('calamari-restapi', rem)
yield
finally:
for rem in remotes:
_remove_package('calamari-restapi', rem)
@contextlib.contextmanager
def server(ctx, config):
"""
task server:
Calamari server setup. "roles" is a list of roles that should run
the webapp, and "restapi_server" is a list of roles that will
be running the calamari-restapi package. Both lists probably should
have only one entry (only the first is used).
For example::
roles: [[server.0], [mon.0], [osd.0, osd.1]]
tasks:
- calamari.server:
roles: [server.0]
restapi_server: [mon.0]
"""
overrides = ctx.config.get('overrides', {})
teuthology.deep_merge(config, overrides.get('calamari.server', {}))
remote = teuthology.roles_to_remotes(ctx.cluster, config)[0]
restapi_remote = teuthology.roles_to_remotes(ctx.cluster, config,
attrname='restapi_server')[0]
if not restapi_remote:
raise RuntimeError('Must supply restapi_server')
try:
# sqlite3 command is required; on some platforms it's already
# there and not removable (required for, say yum)
sqlite_package = _sqlite_package_name(remote)
if sqlite_package and not _install_package(sqlite_package, remote):
raise RuntimeError('{} install failed'.format(sqlite_package))
if not _install_package('calamari-server', remote) or \
not _install_package('calamari-clients', remote) or \
not _disable_default_nginx(remote) or \
not _setup_calamari_cluster(remote, restapi_remote):
raise RuntimeError('Server installation failure')
log.info('client/server setup complete')
yield
finally:
_remove_package('calamari-server', remote)
_remove_package('calamari-clients', remote)
if sqlite_package:
_remove_package(sqlite_package, remote)
def test(ctx, config):
"""
task test
Run the calamari smoketest
Tests to run are in calamari_testdir.
delay: wait this long before starting
tasks:
- calamari.test:
delay: 30
server: server.0
"""
delay = config.get('delay', 0)
if delay:
log.info("delaying %d sec", delay)
time.sleep(delay)
testhost = ctx.cluster.only(config['server']).remotes.keys()[0].name
testhost = testhost.split('@')[1]
mypath = os.path.dirname(__file__)
cmd_list = [os.path.join(mypath, 'calamari_testdir',
'test_server_1_0.py')]
os.environ['CALAMARI_BASE_URI'] = 'http://{0}/api/v1/'.format(testhost)
log.info("testing %s", testhost)
return subprocess.call(cmd_list)