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