mirror of
https://github.com/ceph/ceph
synced 2024-12-28 06:23:08 +00:00
a6eee01fa0
This buys us, also, the ability to select remotes by rule, i.e. "any osd or mon gets calamari-agent installed" Signed-off-by: Dan Mick <dan.mick@inktank.com>
346 lines
11 KiB
Python
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)
|