]> git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/ragweed.py
import quincy beta 17.1.0
[ceph.git] / ceph / qa / tasks / ragweed.py
1 """
2 Run a set of s3 tests on rgw.
3 """
4 from io import BytesIO
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
18 log = logging.getLogger(__name__)
19
20
21 def get_ragweed_branches(config, client_conf):
22 """
23 figure out the ragweed branch according to the per-client settings
24
25 use force-branch is specified, and fall back to the ones deduced using ceph
26 branch under testing
27 """
28 force_branch = client_conf.get('force-branch', None)
29 if force_branch:
30 return [force_branch]
31 else:
32 S3_BRANCHES = ['master', 'nautilus', 'mimic',
33 'luminous', 'kraken', 'jewel']
34 ceph_branch = config.get('branch')
35 suite_branch = config.get('suite_branch', ceph_branch)
36 if suite_branch in S3_BRANCHES:
37 branch = client_conf.get('branch', 'ceph-' + suite_branch)
38 else:
39 branch = client_conf.get('branch', suite_branch)
40 default_branch = client_conf.get('default-branch', None)
41 if default_branch:
42 return [branch, default_branch]
43 else:
44 return [branch]
45
46
47 @contextlib.contextmanager
48 def download(ctx, config):
49 """
50 Download the s3 tests from the git builder.
51 Remove downloaded s3 file upon exit.
52
53 The context passed in should be identical to the context
54 passed in to the main task.
55 """
56 assert isinstance(config, dict)
57 log.info('Downloading ragweed...')
58 testdir = teuthology.get_testdir(ctx)
59 for (client, cconf) in config.items():
60 ragweed_repo = ctx.config.get('ragweed_repo',
61 teuth_config.ceph_git_base_url + 'ragweed.git')
62 for branch in get_ragweed_branches(ctx.config, cconf):
63 log.info("Using branch '%s' for ragweed", branch)
64 try:
65 ctx.cluster.only(client).sh(
66 script=f'git clone -b {branch} {ragweed_repo} {testdir}/ragweed')
67 break
68 except Exception as e:
69 exc = e
70 else:
71 raise exc
72
73 sha1 = cconf.get('sha1')
74 if sha1 is not None:
75 ctx.cluster.only(client).run(
76 args=[
77 'cd', '{tdir}/ragweed'.format(tdir=testdir),
78 run.Raw('&&'),
79 'git', 'reset', '--hard', sha1,
80 ],
81 )
82 try:
83 yield
84 finally:
85 log.info('Removing ragweed...')
86 testdir = teuthology.get_testdir(ctx)
87 for client in config:
88 ctx.cluster.only(client).run(
89 args=[
90 'rm',
91 '-rf',
92 '{tdir}/ragweed'.format(tdir=testdir),
93 ],
94 )
95
96
97 def _config_user(ragweed_conf, section, user):
98 """
99 Configure users for this section by stashing away keys, ids, and
100 email addresses.
101 """
102 ragweed_conf[section].setdefault('user_id', user)
103 ragweed_conf[section].setdefault('email', '{user}+test@test.test'.format(user=user))
104 ragweed_conf[section].setdefault('display_name', 'Mr. {user}'.format(user=user))
105 ragweed_conf[section].setdefault('access_key', ''.join(random.choice(string.ascii_uppercase) for i in range(20)))
106 ragweed_conf[section].setdefault('secret_key', base64.b64encode(os.urandom(40)).decode('ascii'))
107
108
109 @contextlib.contextmanager
110 def create_users(ctx, config, run_stages):
111 """
112 Create a main and an alternate s3 user.
113 """
114 assert isinstance(config, dict)
115
116 for client, properties in config['config'].items():
117 run_stages[client] = properties.get('stages', 'prepare,check').split(',')
118
119 log.info('Creating rgw users...')
120 testdir = teuthology.get_testdir(ctx)
121 users = {'user regular': 'ragweed', 'user system': 'sysuser'}
122 for client in config['clients']:
123 if not 'prepare' in run_stages[client]:
124 # should have been prepared in a previous run
125 continue
126
127 ragweed_conf = config['ragweed_conf'][client]
128 ragweed_conf.setdefault('fixtures', {})
129 ragweed_conf['rgw'].setdefault('bucket_prefix', 'test-' + client)
130 for section, user in users.items():
131 _config_user(ragweed_conf, section, '{user}.{client}'.format(user=user, client=client))
132 log.debug('Creating user {user} on {host}'.format(user=ragweed_conf[section]['user_id'], host=client))
133 if user == 'sysuser':
134 sys_str = 'true'
135 else:
136 sys_str = 'false'
137 ctx.cluster.only(client).run(
138 args=[
139 'adjust-ulimits',
140 'ceph-coverage',
141 '{tdir}/archive/coverage'.format(tdir=testdir),
142 'radosgw-admin',
143 '-n', client,
144 'user', 'create',
145 '--uid', ragweed_conf[section]['user_id'],
146 '--display-name', ragweed_conf[section]['display_name'],
147 '--access-key', ragweed_conf[section]['access_key'],
148 '--secret', ragweed_conf[section]['secret_key'],
149 '--email', ragweed_conf[section]['email'],
150 '--system', sys_str,
151 ],
152 )
153 try:
154 yield
155 finally:
156 for client in config['clients']:
157 if not 'check' in run_stages[client]:
158 # only remove user if went through the check stage
159 continue
160 for user in users.values():
161 uid = '{user}.{client}'.format(user=user, client=client)
162 ctx.cluster.only(client).run(
163 args=[
164 'adjust-ulimits',
165 'ceph-coverage',
166 '{tdir}/archive/coverage'.format(tdir=testdir),
167 'radosgw-admin',
168 '-n', client,
169 'user', 'rm',
170 '--uid', uid,
171 '--purge-data',
172 ],
173 )
174
175
176 @contextlib.contextmanager
177 def configure(ctx, config, run_stages):
178 """
179 Configure the ragweed. This includes the running of the
180 bootstrap code and the updating of local conf files.
181 """
182 assert isinstance(config, dict)
183 log.info('Configuring ragweed...')
184 testdir = teuthology.get_testdir(ctx)
185 for client, properties in config['clients'].items():
186 (remote,) = ctx.cluster.only(client).remotes.keys()
187 remote.run(
188 args=[
189 'cd',
190 '{tdir}/ragweed'.format(tdir=testdir),
191 run.Raw('&&'),
192 './bootstrap',
193 ],
194 )
195
196 preparing = 'prepare' in run_stages[client]
197 if not preparing:
198 # should have been prepared in a previous run
199 continue
200
201 ragweed_conf = config['ragweed_conf'][client]
202 if properties is not None and 'slow_backend' in properties:
203 ragweed_conf['fixtures']['slow backend'] = properties['slow_backend']
204
205 conf_fp = BytesIO()
206 ragweed_conf.write(conf_fp)
207 remote.write_file(
208 path='{tdir}/archive/ragweed.{client}.conf'.format(tdir=testdir, client=client),
209 data=conf_fp.getvalue(),
210 )
211
212 log.info('Configuring boto...')
213 boto_src = os.path.join(os.path.dirname(__file__), 'boto.cfg.template')
214 for client, properties in config['clients'].items():
215 with open(boto_src, 'r') as f:
216 (remote,) = ctx.cluster.only(client).remotes.keys()
217 conf = f.read().format(
218 idle_timeout=config.get('idle_timeout', 30)
219 )
220 remote.write_file('{tdir}/boto.cfg'.format(tdir=testdir), conf)
221
222 try:
223 yield
224
225 finally:
226 log.info('Cleaning up boto...')
227 for client, properties in config['clients'].items():
228 (remote,) = ctx.cluster.only(client).remotes.keys()
229 remote.run(
230 args=[
231 'rm',
232 '{tdir}/boto.cfg'.format(tdir=testdir),
233 ],
234 )
235
236 @contextlib.contextmanager
237 def run_tests(ctx, config, run_stages):
238 """
239 Run the ragweed after everything is set up.
240
241 :param ctx: Context passed to task
242 :param config: specific configuration information
243 """
244 assert isinstance(config, dict)
245 testdir = teuthology.get_testdir(ctx)
246 attrs = ["!fails_on_rgw"]
247 for client, client_config in config.items():
248 stages = ','.join(run_stages[client])
249 args = [
250 'RAGWEED_CONF={tdir}/archive/ragweed.{client}.conf'.format(tdir=testdir, client=client),
251 'RAGWEED_STAGES={stages}'.format(stages=stages),
252 'BOTO_CONFIG={tdir}/boto.cfg'.format(tdir=testdir),
253 '{tdir}/ragweed/virtualenv/bin/python'.format(tdir=testdir),
254 '-m', 'nose',
255 '-w',
256 '{tdir}/ragweed'.format(tdir=testdir),
257 '-v',
258 '-a', ','.join(attrs),
259 ]
260 if client_config is not None and 'extra_args' in client_config:
261 args.extend(client_config['extra_args'])
262
263 ctx.cluster.only(client).run(
264 args=args,
265 label="ragweed tests against rgw"
266 )
267 yield
268
269 @contextlib.contextmanager
270 def task(ctx, config):
271 """
272 Run the ragweed suite against rgw.
273
274 To run all tests on all clients::
275
276 tasks:
277 - ceph:
278 - rgw:
279 - ragweed:
280
281 To restrict testing to particular clients::
282
283 tasks:
284 - ceph:
285 - rgw: [client.0]
286 - ragweed: [client.0]
287
288 To run against a server on client.1 and increase the boto timeout to 10m::
289
290 tasks:
291 - ceph:
292 - rgw: [client.1]
293 - ragweed:
294 client.0:
295 rgw_server: client.1
296 idle_timeout: 600
297 stages: prepare,check
298
299 To pass extra arguments to nose (e.g. to run a certain test)::
300
301 tasks:
302 - ceph:
303 - rgw: [client.0]
304 - ragweed:
305 client.0:
306 extra_args: ['test_s3:test_object_acl_grand_public_read']
307 client.1:
308 extra_args: ['--exclude', 'test_100_continue']
309 """
310 assert hasattr(ctx, 'rgw'), 'ragweed must run after the rgw task'
311 assert config is None or isinstance(config, list) \
312 or isinstance(config, dict), \
313 "task ragweed only supports a list or dictionary for configuration"
314 all_clients = ['client.{id}'.format(id=id_)
315 for id_ in teuthology.all_roles_of_type(ctx.cluster, 'client')]
316 if config is None:
317 config = all_clients
318 if isinstance(config, list):
319 config = dict.fromkeys(config)
320 clients = config.keys()
321
322 overrides = ctx.config.get('overrides', {})
323 # merge each client section, not the top level.
324 for client in config.keys():
325 if not config[client]:
326 config[client] = {}
327 teuthology.deep_merge(config[client], overrides.get('ragweed', {}))
328
329 log.debug('ragweed config is %s', config)
330
331 ragweed_conf = {}
332 for client in clients:
333 # use rgw_server endpoint if given, or default to same client
334 target = config[client].get('rgw_server', client)
335
336 endpoint = ctx.rgw.role_endpoints.get(target)
337 assert endpoint, 'ragweed: no rgw endpoint for {}'.format(target)
338
339 ragweed_conf[client] = ConfigObj(
340 indent_type='',
341 infile={
342 'rgw':
343 {
344 'host' : endpoint.dns_name,
345 'port' : endpoint.port,
346 'is_secure' : endpoint.cert is not None,
347 },
348 'fixtures' : {},
349 'user system' : {},
350 'user regular' : {},
351 'rados':
352 {
353 'ceph_conf' : '/etc/ceph/ceph.conf',
354 },
355 }
356 )
357
358 run_stages = {}
359
360 with contextutil.nested(
361 lambda: download(ctx=ctx, config=config),
362 lambda: create_users(ctx=ctx, config=dict(
363 clients=clients,
364 ragweed_conf=ragweed_conf,
365 config=config,
366 ),
367 run_stages=run_stages),
368 lambda: configure(ctx=ctx, config=dict(
369 clients=config,
370 ragweed_conf=ragweed_conf,
371 ),
372 run_stages=run_stages),
373 lambda: run_tests(ctx=ctx, config=config, run_stages=run_stages),
374 ):
375 pass
376 yield