Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / qa / tasks / calamari_nosetests.py
1 import contextlib
2 import logging
3 import os
4 import textwrap
5 import yaml
6
7 from cStringIO import StringIO
8 from teuthology import contextutil
9 from teuthology import misc
10 from teuthology import packaging
11 from teuthology.orchestra import run
12
13 log = logging.getLogger(__name__)
14
15 # extra stuff we need to do our job here
16 EXTRA_PKGS = [
17     'git',
18 ]
19
20 # stuff that would be in a devmode install, but should be
21 # installed in the system for running nosetests against
22 # a production install.
23 EXTRA_NOSETEST_PKGS = [
24     'python-psutil',
25     'python-mock',
26 ]
27
28
29 def find_client0(cluster):
30     ''' Find remote that has client.0 role, or None '''
31     for rem, roles in cluster.remotes.iteritems():
32         if 'client.0' in roles:
33             return rem
34     return None
35
36
37 def pip(remote, package, venv=None, uninstall=False, force=False):
38     ''' {un}install a package with pip, possibly in a virtualenv '''
39     if venv:
40         pip = os.path.join(venv, 'bin', 'pip')
41         args = ['sudo', pip]
42     else:
43         args = ['sudo', 'pip']
44
45     if uninstall:
46         args.extend(['uninstall', '-y'])
47     else:
48         args.append('install')
49         if force:
50             args.append('-I')
51
52     args.append(package)
53     remote.run(args=args)
54
55
56 @contextlib.contextmanager
57 def install_epel(remote):
58     ''' install a disabled-by-default epel repo config file '''
59     remove = False
60     try:
61         if remote.os.package_type == 'deb':
62             yield
63         else:
64             remove = True
65             distromajor = remote.os.version.split('.')[0]
66
67             repofiledata = textwrap.dedent('''
68                 [epel]
69                 name=epel{version}
70                 metalink=http://mirrors.fedoraproject.org/metalink?repo=epel-{version}&arch=$basearch
71                 enabled=0
72                 gpgcheck=0
73             ''').format(version=distromajor)
74
75             misc.create_file(remote, '/etc/yum.repos.d/epel.repo',
76                              data=repofiledata, sudo=True)
77             remote.run(args='sudo yum clean all')
78             yield
79
80     finally:
81         if remove:
82             misc.delete_file(remote, '/etc/yum.repos.d/epel.repo', sudo=True)
83
84
85 def enable_epel(remote, enable=True):
86     ''' enable/disable the epel repo '''
87     args = 'sudo sed -i'.split()
88     if enable:
89         args.extend(['s/enabled=0/enabled=1/'])
90     else:
91         args.extend(['s/enabled=1/enabled=0/'])
92     args.extend(['/etc/yum.repos.d/epel.repo'])
93
94     remote.run(args=args)
95     remote.run(args='sudo yum clean all')
96
97
98 @contextlib.contextmanager
99 def install_extra_pkgs(client):
100     ''' Install EXTRA_PKGS '''
101     try:
102         for pkg in EXTRA_PKGS:
103             packaging.install_package(pkg, client)
104         yield
105
106     finally:
107         for pkg in EXTRA_PKGS:
108             packaging.remove_package(pkg, client)
109
110
111 @contextlib.contextmanager
112 def clone_calamari(config, client):
113     ''' clone calamari source into current directory on remote '''
114     branch = config.get('calamari_branch', 'master')
115     url = config.get('calamari_giturl', 'git://github.com/ceph/calamari')
116     try:
117         out = StringIO()
118         # ensure branch is present (clone -b will succeed even if
119         # the branch doesn't exist, falling back to master)
120         client.run(
121             args='git ls-remote %s %s' % (url, branch),
122             stdout=out,
123             label='check for calamari branch %s existence' % branch
124         )
125         if len(out.getvalue()) == 0:
126             raise RuntimeError("Calamari branch %s doesn't exist" % branch)
127         client.run(args='git clone -b %s %s' % (branch, url))
128         yield
129     finally:
130         # sudo python setup.py develop may have left some root files around
131         client.run(args='sudo rm -rf calamari')
132
133
134 @contextlib.contextmanager
135 def write_info_yaml(cluster, client):
136     ''' write info.yaml to client for nosetests '''
137     try:
138         info = {
139             'cluster': {
140                 rem.name: {'roles': roles}
141                 for rem, roles in cluster.remotes.iteritems()
142             }
143         }
144         misc.create_file(client, 'calamari/info.yaml',
145                          data=yaml.safe_dump(info, default_flow_style=False))
146         yield
147     finally:
148         misc.delete_file(client, 'calamari/info.yaml')
149
150
151 @contextlib.contextmanager
152 def write_test_conf(client):
153     ''' write calamari/tests/test.conf to client for nosetests '''
154     try:
155         testconf = textwrap.dedent('''
156             [testing]
157
158             calamari_control = external
159             ceph_control = external
160             bootstrap = False
161             api_username = admin
162             api_password = admin
163             embedded_timeout_factor = 1
164             external_timeout_factor = 3
165             external_cluster_path = info.yaml
166         ''')
167         misc.create_file(client, 'calamari/tests/test.conf', data=testconf)
168         yield
169
170     finally:
171         misc.delete_file(client, 'calamari/tests/test.conf')
172
173
174 @contextlib.contextmanager
175 def prepare_nosetest_env(client):
176     try:
177         # extra dependencies that would be in the devmode venv
178         if client.os.package_type == 'rpm':
179             enable_epel(client, enable=True)
180         for package in EXTRA_NOSETEST_PKGS:
181             packaging.install_package(package, client)
182         if client.os.package_type == 'rpm':
183             enable_epel(client, enable=False)
184
185         # install nose itself into the calamari venv, force it in case it's
186         # already installed in the system, so we can invoke it by path without
187         # fear that it's not present
188         pip(client, 'nose', venv='/opt/calamari/venv', force=True)
189
190         # install a later version of requests into the venv as well
191         # (for precise)
192         pip(client, 'requests', venv='/opt/calamari/venv', force=True)
193
194         # link (setup.py develop) calamari/rest-api into the production venv
195         # because production does not include calamari_rest.management, needed
196         # for test_rest_api.py's ApiIntrospection
197         args = 'cd calamari/rest-api'.split() + [run.Raw(';')] + \
198                'sudo /opt/calamari/venv/bin/python setup.py develop'.split()
199         client.run(args=args)
200
201         # because, at least in Python 2.6/Centos, site.py uses
202         # 'os.path.exists()' to process .pth file entries, and exists() uses
203         # access(2) to check for existence, all the paths leading up to
204         # $HOME/calamari/rest-api need to be searchable by all users of
205         # the package, which will include the WSGI/Django app, running
206         # as the Apache user.  So make them all world-read-and-execute.
207         args = 'sudo chmod a+x'.split() + \
208             ['.', './calamari', './calamari/rest-api']
209         client.run(args=args)
210
211         # make one dummy request just to get the WSGI app to do
212         # all its log creation here, before the chmod below (I'm
213         # looking at you, graphite -- /var/log/calamari/info.log and
214         # /var/log/calamari/exception.log)
215         client.run(args='wget -q -O /dev/null http://localhost')
216
217         # /var/log/calamari/* is root-or-apache write-only
218         client.run(args='sudo chmod a+w /var/log/calamari/*')
219
220         yield
221
222     finally:
223         args = 'cd calamari/rest-api'.split() + [run.Raw(';')] + \
224                'sudo /opt/calamari/venv/bin/python setup.py develop -u'.split()
225         client.run(args=args)
226         for pkg in ('nose', 'requests'):
227             pip(client, pkg, venv='/opt/calamari/venv', uninstall=True)
228         for package in EXTRA_NOSETEST_PKGS:
229             packaging.remove_package(package, client)
230
231
232 @contextlib.contextmanager
233 def run_nosetests(client):
234     ''' Actually run the tests '''
235     args = [
236         'cd',
237         'calamari',
238         run.Raw(';'),
239         'CALAMARI_CONFIG=/etc/calamari/calamari.conf',
240         '/opt/calamari/venv/bin/nosetests',
241         '-v',
242         'tests/',
243     ]
244     client.run(args=args)
245     yield
246
247
248 @contextlib.contextmanager
249 def task(ctx, config):
250     """
251     Run Calamari tests against an instance set up by 'calamari_server'.
252
253     -- clone the Calamari source into $HOME (see options)
254     -- write calamari/info.yaml describing the cluster
255     -- write calamari/tests/test.conf containing
256         'external' for calamari_control and ceph_control
257         'bootstrap = False' to disable test bootstrapping (installing minions)
258         no api_url necessary (inferred from client.0)
259         'external_cluster_path = info.yaml'
260     -- modify the production Calamari install to allow test runs:
261         install nose in the venv
262         install EXTRA_NOSETEST_PKGS
263         link in, with setup.py develop, calamari_rest (for ApiIntrospection)
264     -- set CALAMARI_CONFIG to point to /etc/calamari/calamari.conf
265     -- nosetests -v tests/
266
267     Options are:
268         calamari_giturl: url from which to git clone calamari
269                          (default: git://github.com/ceph/calamari)
270         calamari_branch: git branch of calamari to check out
271                          (default: master)
272
273     Note: the tests must find a clean cluster, so don't forget to
274     set the crush default type appropriately, or install min_size OSD hosts
275     """
276     client0 = find_client0(ctx.cluster)
277     if client0 is None:
278         raise RuntimeError("must have client.0 role")
279
280     with contextutil.nested(
281         lambda: install_epel(client0),
282         lambda: install_extra_pkgs(client0),
283         lambda: clone_calamari(config, client0),
284         lambda: write_info_yaml(ctx.cluster, client0),
285         lambda: write_test_conf(client0),
286         lambda: prepare_nosetest_env(client0),
287         lambda: run_nosetests(client0),
288     ):
289         yield