Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / qa / tasks / s3tests.py
1 """
2 Run a set of s3 tests on rgw.
3 """
4 from cStringIO import StringIO
5 from configobj import ConfigObj
6 import base64
7 import contextlib
8 import logging
9 import os
10 import random
11 import string
12
13 from teuthology import misc as teuthology
14 from teuthology import contextutil
15 from teuthology.config import config as teuth_config
16 from teuthology.orchestra import run
17 from teuthology.orchestra.connection import split_user
18
19 log = logging.getLogger(__name__)
20
21 @contextlib.contextmanager
22 def download(ctx, config):
23     """
24     Download the s3 tests from the git builder.
25     Remove downloaded s3 file upon exit.
26
27     The context passed in should be identical to the context
28     passed in to the main task.
29     """
30     assert isinstance(config, dict)
31     log.info('Downloading s3-tests...')
32     testdir = teuthology.get_testdir(ctx)
33     s3_branches = [ 'giant', 'firefly', 'firefly-original', 'hammer' ]
34     for (client, cconf) in config.items():
35         branch = cconf.get('force-branch', None)
36         if not branch:
37             ceph_branch = ctx.config.get('branch')
38             suite_branch = ctx.config.get('suite_branch', ceph_branch)
39             if suite_branch in s3_branches:
40                 branch = cconf.get('branch', suite_branch)
41             else:
42                 branch = cconf.get('branch', 'ceph-' + suite_branch)
43         if not branch:
44             raise ValueError(
45                 "Could not determine what branch to use for s3tests!")
46         else:
47             log.info("Using branch '%s' for s3tests", branch)
48         sha1 = cconf.get('sha1')
49         git_remote = cconf.get('git_remote', None) or teuth_config.ceph_git_base_url
50         ctx.cluster.only(client).run(
51             args=[
52                 'git', 'clone',
53                 '-b', branch,
54                 git_remote + 's3-tests.git',
55                 '{tdir}/s3-tests'.format(tdir=testdir),
56                 ],
57             )
58         if sha1 is not None:
59             ctx.cluster.only(client).run(
60                 args=[
61                     'cd', '{tdir}/s3-tests'.format(tdir=testdir),
62                     run.Raw('&&'),
63                     'git', 'reset', '--hard', sha1,
64                     ],
65                 )
66     try:
67         yield
68     finally:
69         log.info('Removing s3-tests...')
70         testdir = teuthology.get_testdir(ctx)
71         for client in config:
72             ctx.cluster.only(client).run(
73                 args=[
74                     'rm',
75                     '-rf',
76                     '{tdir}/s3-tests'.format(tdir=testdir),
77                     ],
78                 )
79
80
81 def _config_user(s3tests_conf, section, user):
82     """
83     Configure users for this section by stashing away keys, ids, and
84     email addresses.
85     """
86     s3tests_conf[section].setdefault('user_id', user)
87     s3tests_conf[section].setdefault('email', '{user}+test@test.test'.format(user=user))
88     s3tests_conf[section].setdefault('display_name', 'Mr. {user}'.format(user=user))
89     s3tests_conf[section].setdefault('access_key', ''.join(random.choice(string.uppercase) for i in xrange(20)))
90     s3tests_conf[section].setdefault('secret_key', base64.b64encode(os.urandom(40)))
91
92
93 @contextlib.contextmanager
94 def create_users(ctx, config):
95     """
96     Create a main and an alternate s3 user.
97     """
98     assert isinstance(config, dict)
99     log.info('Creating rgw users...')
100     testdir = teuthology.get_testdir(ctx)
101     users = {'s3 main': 'foo', 's3 alt': 'bar', 's3 tenant': 'testx$tenanteduser'}
102     for client in config['clients']:
103         s3tests_conf = config['s3tests_conf'][client]
104         s3tests_conf.setdefault('fixtures', {})
105         s3tests_conf['fixtures'].setdefault('bucket prefix', 'test-' + client + '-{random}-')
106         for section, user in users.iteritems():
107             _config_user(s3tests_conf, section, '{user}.{client}'.format(user=user, client=client))
108             log.debug('Creating user {user} on {host}'.format(user=s3tests_conf[section]['user_id'], host=client))
109             cluster_name, daemon_type, client_id = teuthology.split_role(client)
110             client_with_id = daemon_type + '.' + client_id
111             ctx.cluster.only(client).run(
112                 args=[
113                     'adjust-ulimits',
114                     'ceph-coverage',
115                     '{tdir}/archive/coverage'.format(tdir=testdir),
116                     'radosgw-admin',
117                     '-n', client_with_id,
118                     'user', 'create',
119                     '--uid', s3tests_conf[section]['user_id'],
120                     '--display-name', s3tests_conf[section]['display_name'],
121                     '--access-key', s3tests_conf[section]['access_key'],
122                     '--secret', s3tests_conf[section]['secret_key'],
123                     '--email', s3tests_conf[section]['email'],
124                     '--cluster', cluster_name,
125                 ],
126             )
127     try:
128         yield
129     finally:
130         for client in config['clients']:
131             for user in users.itervalues():
132                 uid = '{user}.{client}'.format(user=user, client=client)
133                 cluster_name, daemon_type, client_id = teuthology.split_role(client)
134                 client_with_id = daemon_type + '.' + client_id
135                 ctx.cluster.only(client).run(
136                     args=[
137                         'adjust-ulimits',
138                         'ceph-coverage',
139                         '{tdir}/archive/coverage'.format(tdir=testdir),
140                         'radosgw-admin',
141                         '-n', client_with_id,
142                         'user', 'rm',
143                         '--uid', uid,
144                         '--purge-data',
145                         '--cluster', cluster_name,
146                         ],
147                     )
148
149
150 @contextlib.contextmanager
151 def configure(ctx, config):
152     """
153     Configure the s3-tests.  This includes the running of the
154     bootstrap code and the updating of local conf files.
155     """
156     assert isinstance(config, dict)
157     log.info('Configuring s3-tests...')
158     testdir = teuthology.get_testdir(ctx)
159     for client, properties in config['clients'].iteritems():
160         s3tests_conf = config['s3tests_conf'][client]
161         if properties is not None and 'rgw_server' in properties:
162             host = None
163             for target, roles in zip(ctx.config['targets'].iterkeys(), ctx.config['roles']):
164                 log.info('roles: ' + str(roles))
165                 log.info('target: ' + str(target))
166                 if properties['rgw_server'] in roles:
167                     _, host = split_user(target)
168             assert host is not None, "Invalid client specified as the rgw_server"
169             s3tests_conf['DEFAULT']['host'] = host
170         else:
171             s3tests_conf['DEFAULT']['host'] = 'localhost'
172
173         if properties is not None and 'slow_backend' in properties:
174             s3tests_conf['fixtures']['slow backend'] = properties['slow_backend']
175
176         (remote,) = ctx.cluster.only(client).remotes.keys()
177         remote.run(
178             args=[
179                 'cd',
180                 '{tdir}/s3-tests'.format(tdir=testdir),
181                 run.Raw('&&'),
182                 './bootstrap',
183                 ],
184             )
185         conf_fp = StringIO()
186         s3tests_conf.write(conf_fp)
187         teuthology.write_file(
188             remote=remote,
189             path='{tdir}/archive/s3-tests.{client}.conf'.format(tdir=testdir, client=client),
190             data=conf_fp.getvalue(),
191             )
192
193     log.info('Configuring boto...')
194     boto_src = os.path.join(os.path.dirname(__file__), 'boto.cfg.template')
195     for client, properties in config['clients'].iteritems():
196         with file(boto_src, 'rb') as f:
197             (remote,) = ctx.cluster.only(client).remotes.keys()
198             conf = f.read().format(
199                 idle_timeout=config.get('idle_timeout', 30)
200                 )
201             teuthology.write_file(
202                 remote=remote,
203                 path='{tdir}/boto.cfg'.format(tdir=testdir),
204                 data=conf,
205                 )
206
207     try:
208         yield
209
210     finally:
211         log.info('Cleaning up boto...')
212         for client, properties in config['clients'].iteritems():
213             (remote,) = ctx.cluster.only(client).remotes.keys()
214             remote.run(
215                 args=[
216                     'rm',
217                     '{tdir}/boto.cfg'.format(tdir=testdir),
218                     ],
219                 )
220
221 @contextlib.contextmanager
222 def run_tests(ctx, config):
223     """
224     Run the s3tests after everything is set up.
225
226     :param ctx: Context passed to task
227     :param config: specific configuration information
228     """
229     assert isinstance(config, dict)
230     testdir = teuthology.get_testdir(ctx)
231     attrs = ["!fails_on_rgw", "!lifecycle"]
232     for client, client_config in config.iteritems():
233         args = [
234             'S3TEST_CONF={tdir}/archive/s3-tests.{client}.conf'.format(tdir=testdir, client=client),
235             'BOTO_CONFIG={tdir}/boto.cfg'.format(tdir=testdir),
236             '{tdir}/s3-tests/virtualenv/bin/nosetests'.format(tdir=testdir),
237             '-w',
238             '{tdir}/s3-tests'.format(tdir=testdir),
239             '-v',
240             '-a', ','.join(attrs),
241             ]
242         if client_config is not None and 'extra_args' in client_config:
243             args.extend(client_config['extra_args'])
244
245         ctx.cluster.only(client).run(
246             args=args,
247             label="s3 tests against rgw"
248             )
249     yield
250
251 @contextlib.contextmanager
252 def scan_for_leaked_encryption_keys(ctx, config):
253     """
254     Scan radosgw logs for the encryption keys used by s3tests to
255     verify that we're not leaking secrets.
256
257     :param ctx: Context passed to task
258     :param config: specific configuration information
259     """
260     assert isinstance(config, dict)
261
262     try:
263         yield
264     finally:
265         # x-amz-server-side-encryption-customer-key
266         s3test_customer_key = 'pO3upElrwuEXSoFwCfnZPdSsmt/xWeFa0N9KgDijwVs='
267
268         log.debug('Scanning radosgw logs for leaked encryption keys...')
269         procs = list()
270         for client, client_config in config.iteritems():
271             if not client_config.get('scan_for_encryption_keys', True):
272                 continue
273             cluster_name, daemon_type, client_id = teuthology.split_role(client)
274             client_with_cluster = '.'.join((cluster_name, daemon_type, client_id))
275             (remote,) = ctx.cluster.only(client).remotes.keys()
276             proc = remote.run(
277                 args=[
278                     'grep',
279                     '--binary-files=text',
280                     s3test_customer_key,
281                     '/var/log/ceph/rgw.{client}.log'.format(client=client_with_cluster),
282                 ],
283                 wait=False,
284                 check_status=False,
285             )
286             procs.append(proc)
287
288         for proc in procs:
289             proc.wait()
290             if proc.returncode == 1: # 1 means no matches
291                 continue
292             log.error('radosgw log is leaking encryption keys!')
293             raise Exception('radosgw log is leaking encryption keys')
294
295 @contextlib.contextmanager
296 def task(ctx, config):
297     """
298     Run the s3-tests suite against rgw.
299
300     To run all tests on all clients::
301
302         tasks:
303         - ceph:
304         - rgw:
305         - s3tests:
306
307     To restrict testing to particular clients::
308
309         tasks:
310         - ceph:
311         - rgw: [client.0]
312         - s3tests: [client.0]
313
314     To run against a server on client.1 and increase the boto timeout to 10m::
315
316         tasks:
317         - ceph:
318         - rgw: [client.1]
319         - s3tests:
320             client.0:
321               rgw_server: client.1
322               idle_timeout: 600
323
324     To pass extra arguments to nose (e.g. to run a certain test)::
325
326         tasks:
327         - ceph:
328         - rgw: [client.0]
329         - s3tests:
330             client.0:
331               extra_args: ['test_s3:test_object_acl_grand_public_read']
332             client.1:
333               extra_args: ['--exclude', 'test_100_continue']
334     """
335     assert config is None or isinstance(config, list) \
336         or isinstance(config, dict), \
337         "task s3tests only supports a list or dictionary for configuration"
338     all_clients = ['client.{id}'.format(id=id_)
339                    for id_ in teuthology.all_roles_of_type(ctx.cluster, 'client')]
340     if config is None:
341         config = all_clients
342     if isinstance(config, list):
343         config = dict.fromkeys(config)
344     clients = config.keys()
345
346     overrides = ctx.config.get('overrides', {})
347     # merge each client section, not the top level.
348     for client in config.iterkeys():
349         if not config[client]:
350             config[client] = {}
351         teuthology.deep_merge(config[client], overrides.get('s3tests', {}))
352
353     log.debug('s3tests config is %s', config)
354
355     s3tests_conf = {}
356     for client in clients:
357         s3tests_conf[client] = ConfigObj(
358             indent_type='',
359             infile={
360                 'DEFAULT':
361                     {
362                     'port'      : 7280,
363                     'is_secure' : 'no',
364                     },
365                 'fixtures' : {},
366                 's3 main'  : {},
367                 's3 alt'   : {},
368                 's3 tenant': {},
369                 }
370             )
371
372     with contextutil.nested(
373         lambda: download(ctx=ctx, config=config),
374         lambda: create_users(ctx=ctx, config=dict(
375                 clients=clients,
376                 s3tests_conf=s3tests_conf,
377                 )),
378         lambda: configure(ctx=ctx, config=dict(
379                 clients=config,
380                 s3tests_conf=s3tests_conf,
381                 )),
382         lambda: run_tests(ctx=ctx, config=config),
383         lambda: scan_for_leaked_encryption_keys(ctx=ctx, config=config),
384         ):
385         pass
386     yield