ceph/teuthology/task/calamari.py

346 lines
11 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/.
Sample configuration:
roles:
- [osd.0, osd.1, mon.0, calamari.restapi]
- [osd.2, osd.3, calamari.server]
tasks:
- install:
branch: dumpling
- ceph:
- calamari.reposetup:
- calamari.agent:
- calamari.restapi:
- calamari.server:
- calamari.test:
delay: 40
calamari.reposetup will happen on all osd/mon/calamari.* remotes
calamari.agent will run on all osd/mon
calamari.restapi must run on a remote with a monitor
calamari.server must be able to find calamari.restapi to talk to
calamari.test has an optional delay to allow the webapp to settle before
talking to it (we could make the test retry/timeout instead)
"""
from cStringIO import StringIO
import contextlib
import logging
import os
import subprocess
import teuthology.misc as teuthology
import teuthology.packaging as pkg
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 = pkg.get_service_name('httpd', 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())
def _remotes(ctx, selector):
return ctx.cluster.only(selector).remotes.keys()
"""
Tasks
"""
@contextlib.contextmanager
def agent(ctx, config):
"""
task agent
calamari.agent: install stats collection
(for each role of type 'mon.' or 'osd.')
For example::
roles:
- [osd.0, mon.a]
- [osd.1]
tasks:
- calamari.agent:
"""
log.info('calamari.agent starting')
overrides = ctx.config.get('overrides', {})
teuthology.deep_merge(config, overrides.get('calamari.agent', {}))
# agent gets installed on any remote with role mon or osd
def needs_agent(role):
for type in 'mon.', 'osd.':
if role.startswith(type):
return True
return False
remotes = _remotes(ctx, needs_agent)
if remotes is None:
raise RuntimeError('No role configured')
try:
for rem in remotes:
log.info('Installing calamari-agent on %s', rem)
pkg.install_package('calamari-agent', rem)
server_remote = _remotes(ctx,
lambda r: r.startswith('calamari.server'))
if not server_remote:
raise RuntimeError('No calamari.server role available')
server_remote = server_remote[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:
pkg.remove_package('calamari-agent', rem)
@contextlib.contextmanager
def reposetup(ctx, config):
"""
task reposetup
Sets up calamari repository on all 'osd', 'mon', and 'calamari.' 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')
# repo gets installed on any remote with role mon, osd, or calamari
def needs_repo(role):
for type in 'mon.', 'osd.', 'calamari.':
if role.startswith(type):
return True
return False
remotes = _remotes(ctx, needs_repo)
if remotes is None:
raise RuntimeError('No roles configured')
try:
for rem in remotes:
log.info(rem)
keypath = 'http://download.inktank.com/keys/release.asc'
pkg.install_repokey(rem, keypath)
pkg.install_repo(rem, 'download.inktank.com', pkgdir,
username, password)
yield
finally:
for rem in remotes:
pkg.remove_repo(rem)
@contextlib.contextmanager
def restapi(ctx, config):
"""
task restapi
Calamari Rest API
For example::
roles:
- [mon.a, osd.0, osd.1, calamari.restapi]
- [osd.2, osd.3]
tasks:
- calamari.restapi:
"""
overrides = ctx.config.get('overrides', {})
teuthology.deep_merge(config, overrides.get('calamari.restapi', {}))
remotes_and_roles = \
ctx.cluster.only(lambda r: r.startswith('calamari.restapi')).remotes
if remotes_and_roles is None:
raise RuntimeError('No role configured')
# check that the role selected also has at least one mon role
for rem, roles in remotes_and_roles.iteritems():
if not any([r for r in roles if r.startswith('mon.')]):
raise RuntimeError('no mon on remote with roles %s', roles)
try:
for rem in remotes_and_roles.iterkeys():
log.info(rem)
pkg.install_package('calamari-restapi', rem)
yield
finally:
for rem in remotes_and_roles.iterkeys():
pkg.remove_package('calamari-restapi', rem)
@contextlib.contextmanager
def server(ctx, config):
"""
task server:
Calamari server setup. Add role 'calamari.server' to the remote
that will run the webapp. 'calamari.restapi' role must be present
to serve as the cluster-api target for calamari-server. Only one
of calamari.server and calamari.restapi roles is supported currently.
For example::
roles:
- [calamari.server]
- [mon.0, calamari.restapi]
- [osd.0, osd.1]
tasks:
- calamari.restapi:
- calamari.server:
"""
overrides = ctx.config.get('overrides', {})
teuthology.deep_merge(config, overrides.get('calamari.server', {}))
remote = _remotes(ctx, lambda r: r.startswith('calamari.server'))
if not remote:
raise RuntimeError('No role configured')
restapi_remote = _remotes(ctx, lambda r: r.startswith('calamari.restapi'))
if not restapi_remote:
raise RuntimeError('Must supply calamari.restapi role')
remote = remote[0]
restapi_remote = restapi_remote[0]
try:
# sqlite3 command is required; on some platforms it's already
# there and not removable (required for, say yum)
sqlite_package = pkg.get_package_name('sqlite', remote)
if sqlite_package and not pkg.install_package(sqlite_package, remote):
raise RuntimeError('{} install failed'.format(sqlite_package))
if not pkg.install_package('calamari-server', remote) or \
not pkg.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:
pkg.remove_package('calamari-server', remote)
pkg.remove_package('calamari-clients', remote)
if sqlite_package:
pkg.remove_package(sqlite_package, remote)
def test(ctx, config):
"""
task test
Run the calamari smoketest on the teuthology host (no role required)
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 = _remotes(ctx, lambda r: r.startswith('calamari.server'))[0]
testhost = testhost.name.split('@')[1]
mypath = os.path.dirname(__file__)
cmd_list = [os.path.join(mypath, 'calamari', 'servertest_1_0.py')]
os.environ['CALAMARI_BASE_URI'] = 'http://{0}/api/v1/'.format(testhost)
log.info("testing %s", testhost)
return subprocess.call(cmd_list)