]> git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/s3tests.py
ee16381d1b491b027bcd84d4bce200b4f2cd5569
[ceph.git] / 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"]
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