]>
git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/s3tests.py
2 Run a set of s3 tests on rgw.
5 from configobj
import ConfigObj
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
.exceptions
import ConfigError
19 log
= logging
.getLogger(__name__
)
21 @contextlib.contextmanager
22 def download(ctx
, config
):
24 Download the s3 tests from the git builder.
25 Remove downloaded s3 file upon exit.
27 The context passed in should be identical to the context
28 passed in to the main task.
30 assert isinstance(config
, dict)
31 log
.info('Downloading s3-tests...')
32 testdir
= teuthology
.get_testdir(ctx
)
33 for (client
, client_config
) in config
.items():
34 s3tests_branch
= client_config
.get('force-branch', None)
35 if not s3tests_branch
:
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.")
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
)
42 ctx
.cluster
.only(client
).run(
46 git_remote
+ 's3-tests.git',
47 '{tdir}/s3-tests'.format(tdir
=testdir
),
51 ctx
.cluster
.only(client
).run(
53 'cd', '{tdir}/s3-tests'.format(tdir
=testdir
),
55 'git', 'reset', '--hard', sha1
,
61 log
.info('Removing s3-tests...')
62 testdir
= teuthology
.get_testdir(ctx
)
64 ctx
.cluster
.only(client
).run(
68 '{tdir}/s3-tests'.format(tdir
=testdir
),
73 def _config_user(s3tests_conf
, section
, user
):
75 Configure users for this section by stashing away keys, ids, and
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',
82 ''.join(random
.choice(string
.ascii_uppercase
) for i
in range(20)))
83 s3tests_conf
[section
].setdefault('secret_key',
84 base64
.b64encode(os
.urandom(40)).decode())
85 s3tests_conf
[section
].setdefault('totp_serial',
86 ''.join(random
.choice(string
.digits
) for i
in range(10)))
87 s3tests_conf
[section
].setdefault('totp_seed',
88 base64
.b32encode(os
.urandom(40)).decode())
89 s3tests_conf
[section
].setdefault('totp_seconds', '5')
92 @contextlib.contextmanager
93 def create_users(ctx
, config
):
95 Create a main and an alternate s3 user.
97 assert isinstance(config
, dict)
98 log
.info('Creating rgw users...')
99 testdir
= teuthology
.get_testdir(ctx
)
102 users
= {'s3 main': 'foo', 's3 alt': 'bar', 's3 tenant': 'testx$tenanteduser', 'iam': 'foobar'}
103 for client
in config
['clients']:
104 s3tests_conf
= config
['s3tests_conf'][client
]
105 s3tests_conf
.setdefault('fixtures', {})
106 s3tests_conf
['fixtures'].setdefault('bucket prefix', 'test-' + client
+ '-{random}-')
107 for section
, user
in users
.items():
108 _config_user(s3tests_conf
, section
, '{user}.{client}'.format(user
=user
, client
=client
))
109 log
.debug('Creating user {user} on {host}'.format(user
=s3tests_conf
[section
]['user_id'], host
=client
))
110 cluster_name
, daemon_type
, client_id
= teuthology
.split_role(client
)
111 client_with_id
= daemon_type
+ '.' + client_id
113 ctx
.cluster
.only(client
).run(
117 '{tdir}/archive/coverage'.format(tdir
=testdir
),
119 '-n', client_with_id
,
121 '--uid', s3tests_conf
[section
]['user_id'],
122 '--display-name', s3tests_conf
[section
]['display_name'],
123 '--access-key', s3tests_conf
[section
]['access_key'],
124 '--secret', s3tests_conf
[section
]['secret_key'],
125 '--cluster', cluster_name
,
128 ctx
.cluster
.only(client
).run(
132 '{tdir}/archive/coverage'.format(tdir
=testdir
),
134 '-n', client_with_id
,
136 '--uid', s3tests_conf
[section
]['user_id'],
137 '--caps', 'user-policy=*',
138 '--cluster', cluster_name
,
141 ctx
.cluster
.only(client
).run(
145 '{tdir}/archive/coverage'.format(tdir
=testdir
),
147 '-n', client_with_id
,
149 '--uid', s3tests_conf
[section
]['user_id'],
151 '--cluster', cluster_name
,
154 ctx
.cluster
.only(client
).run(
158 '{tdir}/archive/coverage'.format(tdir
=testdir
),
160 '-n', client_with_id
,
162 '--uid', s3tests_conf
[section
]['user_id'],
163 '--caps', 'oidc-provider=*',
164 '--cluster', cluster_name
,
169 ctx
.cluster
.only(client
).run(
173 '{tdir}/archive/coverage'.format(tdir
=testdir
),
175 '-n', client_with_id
,
177 '--uid', s3tests_conf
[section
]['user_id'],
178 '--display-name', s3tests_conf
[section
]['display_name'],
179 '--access-key', s3tests_conf
[section
]['access_key'],
180 '--secret', s3tests_conf
[section
]['secret_key'],
181 '--email', s3tests_conf
[section
]['email'],
182 '--caps', 'user-policy=*',
183 '--cluster', cluster_name
,
186 ctx
.cluster
.only(client
).run(
190 '{tdir}/archive/coverage'.format(tdir
=testdir
),
192 '-n', client_with_id
,
194 '--uid', s3tests_conf
[section
]['user_id'],
195 '--totp-serial', s3tests_conf
[section
]['totp_serial'],
196 '--totp-seed', s3tests_conf
[section
]['totp_seed'],
197 '--totp-seconds', s3tests_conf
[section
]['totp_seconds'],
198 '--totp-window', '8',
199 '--totp-seed-type', 'base32',
200 '--cluster', cluster_name
,
205 users
= {'s3 main': 'foo', 's3 alt': 'bar', 's3 tenant': 'testx$tenanteduser'}
206 for client
in config
['clients']:
207 s3tests_conf
= config
['s3tests_conf'][client
]
208 s3tests_conf
.setdefault('fixtures', {})
209 s3tests_conf
['fixtures'].setdefault('bucket prefix', 'test-' + client
+ '-{random}-')
210 for section
, user
in users
.items():
211 _config_user(s3tests_conf
, section
, '{user}.{client}'.format(user
=user
, client
=client
))
212 log
.debug('Creating user {user} on {host}'.format(user
=s3tests_conf
[section
]['user_id'], host
=client
))
213 cluster_name
, daemon_type
, client_id
= teuthology
.split_role(client
)
214 client_with_id
= daemon_type
+ '.' + client_id
215 ctx
.cluster
.only(client
).run(
219 '{tdir}/archive/coverage'.format(tdir
=testdir
),
221 '-n', client_with_id
,
223 '--uid', s3tests_conf
[section
]['user_id'],
224 '--display-name', s3tests_conf
[section
]['display_name'],
225 '--access-key', s3tests_conf
[section
]['access_key'],
226 '--secret', s3tests_conf
[section
]['secret_key'],
227 '--email', s3tests_conf
[section
]['email'],
228 '--caps', 'user-policy=*',
229 '--cluster', cluster_name
,
232 ctx
.cluster
.only(client
).run(
236 '{tdir}/archive/coverage'.format(tdir
=testdir
),
238 '-n', client_with_id
,
240 '--uid', s3tests_conf
[section
]['user_id'],
241 '--totp-serial', s3tests_conf
[section
]['totp_serial'],
242 '--totp-seed', s3tests_conf
[section
]['totp_seed'],
243 '--totp-seconds', s3tests_conf
[section
]['totp_seconds'],
244 '--totp-window', '8',
245 '--totp-seed-type', 'base32',
246 '--cluster', cluster_name
,
250 if "TOKEN" in os
.environ
:
251 s3tests_conf
.setdefault('webidentity', {})
252 s3tests_conf
['webidentity'].setdefault('token',os
.environ
['TOKEN'])
253 s3tests_conf
['webidentity'].setdefault('aud',os
.environ
['AUD'])
254 s3tests_conf
['webidentity'].setdefault('sub',os
.environ
['SUB'])
255 s3tests_conf
['webidentity'].setdefault('azp',os
.environ
['AZP'])
256 s3tests_conf
['webidentity'].setdefault('user_token',os
.environ
['USER_TOKEN'])
257 s3tests_conf
['webidentity'].setdefault('thumbprint',os
.environ
['THUMBPRINT'])
258 s3tests_conf
['webidentity'].setdefault('KC_REALM',os
.environ
['KC_REALM'])
263 for client
in config
['clients']:
264 for user
in users
.values():
265 uid
= '{user}.{client}'.format(user
=user
, client
=client
)
266 cluster_name
, daemon_type
, client_id
= teuthology
.split_role(client
)
267 client_with_id
= daemon_type
+ '.' + client_id
268 ctx
.cluster
.only(client
).run(
272 '{tdir}/archive/coverage'.format(tdir
=testdir
),
274 '-n', client_with_id
,
278 '--cluster', cluster_name
,
283 @contextlib.contextmanager
284 def configure(ctx
, config
):
286 Configure the s3-tests. This includes the running of the
287 bootstrap code and the updating of local conf files.
289 assert isinstance(config
, dict)
290 log
.info('Configuring s3-tests...')
291 testdir
= teuthology
.get_testdir(ctx
)
292 for client
, properties
in config
['clients'].items():
293 properties
= properties
or {}
294 s3tests_conf
= config
['s3tests_conf'][client
]
295 s3tests_conf
['DEFAULT']['calling_format'] = properties
.get('calling-format', 'ordinary')
297 # use rgw_server if given, or default to local client
298 role
= properties
.get('rgw_server', client
)
300 endpoint
= ctx
.rgw
.role_endpoints
.get(role
)
301 assert endpoint
, 's3tests: no rgw endpoint for {}'.format(role
)
303 s3tests_conf
['DEFAULT']['host'] = endpoint
.dns_name
305 website_role
= properties
.get('rgw_website_server')
307 website_endpoint
= ctx
.rgw
.role_endpoints
.get(website_role
)
308 assert website_endpoint
, \
309 's3tests: no rgw endpoint for rgw_website_server {}'.format(website_role
)
310 assert website_endpoint
.website_dns_name
, \
311 's3tests: no dns-s3website-name for rgw_website_server {}'.format(website_role
)
312 s3tests_conf
['DEFAULT']['s3website_domain'] = website_endpoint
.website_dns_name
314 if hasattr(ctx
, 'barbican'):
315 properties
= properties
['barbican']
316 if properties
is not None and 'kms_key' in properties
:
317 if not (properties
['kms_key'] in ctx
.barbican
.keys
):
318 raise ConfigError('Key '+properties
['kms_key']+' not defined')
320 if not (properties
['kms_key2'] in ctx
.barbican
.keys
):
321 raise ConfigError('Key '+properties
['kms_key2']+' not defined')
323 key
= ctx
.barbican
.keys
[properties
['kms_key']]
324 s3tests_conf
['DEFAULT']['kms_keyid'] = key
['id']
326 key
= ctx
.barbican
.keys
[properties
['kms_key2']]
327 s3tests_conf
['DEFAULT']['kms_keyid2'] = key
['id']
329 elif hasattr(ctx
, 'vault'):
330 engine_or_flavor
= vars(ctx
.vault
).get('flavor',ctx
.vault
.engine
)
332 for name
in (x
['Path'] for x
in vars(ctx
.vault
).get('keys', {}).get(ctx
.rgw
.vault_role
)):
335 keys
.extend(['testkey-1','testkey-2'])
336 if engine_or_flavor
== "old":
337 keys
=[keys
[i
] + "/1" for i
in range(len(keys
))]
339 properties
= properties
.get('vault_%s' % engine_or_flavor
, {})
340 s3tests_conf
['DEFAULT']['kms_keyid'] = properties
.get('key_path', keys
[0])
341 s3tests_conf
['DEFAULT']['kms_keyid2'] = properties
.get('key_path2', keys
[1])
342 elif hasattr(ctx
.rgw
, 'pykmip_role'):
344 for name
in (x
['Name'] for x
in ctx
.pykmip
.keys
[ctx
.rgw
.pykmip_role
]):
345 p
=name
.partition('-')
346 keys
.append(p
[2] if p
[2] else p
[0])
347 keys
.extend(['testkey-1', 'testkey-2'])
348 s3tests_conf
['DEFAULT']['kms_keyid'] = properties
.get('kms_key', keys
[0])
349 s3tests_conf
['DEFAULT']['kms_keyid2'] = properties
.get('kms_key2', keys
[1])
351 # Fallback scenario where it's the local (ceph.conf) kms being tested
352 s3tests_conf
['DEFAULT']['kms_keyid'] = 'testkey-1'
353 s3tests_conf
['DEFAULT']['kms_keyid2'] = 'testkey-2'
355 slow_backend
= properties
.get('slow_backend')
357 s3tests_conf
['fixtures']['slow backend'] = slow_backend
359 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
363 '{tdir}/s3-tests'.format(tdir
=testdir
),
369 s3tests_conf
.write(conf_fp
)
371 path
='{tdir}/archive/s3-tests.{client}.conf'.format(tdir
=testdir
, client
=client
),
372 data
=conf_fp
.getvalue(),
375 log
.info('Configuring boto...')
376 boto_src
= os
.path
.join(os
.path
.dirname(__file__
), 'boto.cfg.template')
377 for client
, properties
in config
['clients'].items():
378 with
open(boto_src
) as f
:
379 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
380 conf
= f
.read().format(
381 idle_timeout
=config
.get('idle_timeout', 30)
383 remote
.write_file('{tdir}/boto.cfg'.format(tdir
=testdir
), conf
)
389 log
.info('Cleaning up boto...')
390 for client
, properties
in config
['clients'].items():
391 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
395 '{tdir}/boto.cfg'.format(tdir
=testdir
),
399 @contextlib.contextmanager
400 def run_tests(ctx
, config
):
402 Run the s3tests after everything is set up.
404 :param ctx: Context passed to task
405 :param config: specific configuration information
407 assert isinstance(config
, dict)
408 testdir
= teuthology
.get_testdir(ctx
)
409 for client
, client_config
in config
.items():
410 client_config
= client_config
or {}
411 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
413 'S3TEST_CONF={tdir}/archive/s3-tests.{client}.conf'.format(tdir
=testdir
, client
=client
),
414 'BOTO_CONFIG={tdir}/boto.cfg'.format(tdir
=testdir
)
416 # the 'requests' library comes with its own ca bundle to verify ssl
417 # certificates - override that to use the system's ca bundle, which
418 # is where the ssl task installed this certificate
419 if remote
.os
.package_type
== 'deb':
420 args
+= ['REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt']
422 args
+= ['REQUESTS_CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt']
423 # civetweb > 1.8 && beast parsers are strict on rfc2616
424 attrs
= ["!fails_on_rgw", "!lifecycle_expiration", "!fails_strict_rfc2616","!test_of_sts","!webidentity_test"]
425 if client_config
.get('calling-format') != 'ordinary':
426 attrs
+= ['!fails_with_subdomain']
428 if 'extra_attrs' in client_config
:
429 attrs
= client_config
.get('extra_attrs')
431 '{tdir}/s3-tests/virtualenv/bin/python'.format(tdir
=testdir
),
434 '{tdir}/s3-tests'.format(tdir
=testdir
),
436 '-a', ','.join(attrs
),
438 if 'extra_args' in client_config
:
439 args
.append(client_config
['extra_args'])
443 label
="s3 tests against rgw"
447 @contextlib.contextmanager
448 def scan_for_leaked_encryption_keys(ctx
, config
):
450 Scan radosgw logs for the encryption keys used by s3tests to
451 verify that we're not leaking secrets.
453 :param ctx: Context passed to task
454 :param config: specific configuration information
456 assert isinstance(config
, dict)
461 # x-amz-server-side-encryption-customer-key
462 s3test_customer_key
= 'pO3upElrwuEXSoFwCfnZPdSsmt/xWeFa0N9KgDijwVs='
464 log
.debug('Scanning radosgw logs for leaked encryption keys...')
466 for client
, client_config
in config
.items():
467 if not client_config
.get('scan_for_encryption_keys', True):
469 cluster_name
, daemon_type
, client_id
= teuthology
.split_role(client
)
470 client_with_cluster
= '.'.join((cluster_name
, daemon_type
, client_id
))
471 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
475 '--binary-files=text',
477 '/var/log/ceph/rgw.{client}.log'.format(client
=client_with_cluster
),
486 if proc
.returncode
== 1: # 1 means no matches
488 log
.error('radosgw log is leaking encryption keys!')
489 raise Exception('radosgw log is leaking encryption keys')
491 @contextlib.contextmanager
492 def task(ctx
, config
):
494 Run the s3-tests suite against rgw.
496 To run all tests on all clients::
503 To restrict testing to particular clients::
508 - s3tests: [client.0]
510 To run against a server on client.1 and increase the boto timeout to 10m::
520 To pass extra arguments to nose (e.g. to run a certain test)::
527 extra_args: ['test_s3:test_object_acl_grand_public_read']
529 extra_args: ['--exclude', 'test_100_continue']
531 To run any sts-tests don't forget to set a config variable named 'sts_tests' to 'True' as follows::
542 assert hasattr(ctx
, 'rgw'), 's3tests must run after the rgw task'
543 assert config
is None or isinstance(config
, list) \
544 or isinstance(config
, dict), \
545 "task s3tests only supports a list or dictionary for configuration"
546 all_clients
= ['client.{id}'.format(id=id_
)
547 for id_
in teuthology
.all_roles_of_type(ctx
.cluster
, 'client')]
550 if isinstance(config
, list):
551 config
= dict.fromkeys(config
)
552 clients
= config
.keys()
554 overrides
= ctx
.config
.get('overrides', {})
555 # merge each client section, not the top level.
556 for client
in config
.keys():
557 if not config
[client
]:
559 teuthology
.deep_merge(config
[client
], overrides
.get('s3tests', {}))
561 log
.debug('s3tests config is %s', config
)
565 for client
, client_config
in config
.items():
566 if 'sts_tests' in client_config
:
567 ctx
.sts_variable
= True
569 ctx
.sts_variable
= False
570 #This will be the structure of config file when you want to run webidentity_test (sts-test)
571 if ctx
.sts_variable
and "TOKEN" in os
.environ
:
572 for client
in clients
:
573 endpoint
= ctx
.rgw
.role_endpoints
.get(client
)
574 assert endpoint
, 's3tests: no rgw endpoint for {}'.format(client
)
576 s3tests_conf
[client
] = ConfigObj(
581 'port' : endpoint
.port
,
582 'is_secure' : endpoint
.cert
is not None,
583 'api_name' : 'default',
594 elif ctx
.sts_variable
:
595 #This will be the structure of config file when you want to run assume_role_test and get_session_token_test (sts-test)
596 for client
in clients
:
597 endpoint
= ctx
.rgw
.role_endpoints
.get(client
)
598 assert endpoint
, 's3tests: no rgw endpoint for {}'.format(client
)
600 s3tests_conf
[client
] = ConfigObj(
605 'port' : endpoint
.port
,
606 'is_secure' : endpoint
.cert
is not None,
607 'api_name' : 'default',
618 #This will be the structure of config file when you want to run normal s3-tests
619 for client
in clients
:
620 endpoint
= ctx
.rgw
.role_endpoints
.get(client
)
621 assert endpoint
, 's3tests: no rgw endpoint for {}'.format(client
)
623 s3tests_conf
[client
] = ConfigObj(
628 'port' : endpoint
.port
,
629 'is_secure' : endpoint
.cert
is not None,
630 'api_name' : 'default',
639 with contextutil
.nested(
640 lambda: download(ctx
=ctx
, config
=config
),
641 lambda: create_users(ctx
=ctx
, config
=dict(
643 s3tests_conf
=s3tests_conf
,
645 lambda: configure(ctx
=ctx
, config
=dict(
647 s3tests_conf
=s3tests_conf
,
649 lambda: run_tests(ctx
=ctx
, config
=config
),
650 lambda: scan_for_leaked_encryption_keys(ctx
=ctx
, config
=config
),