2 Run a set of s3 tests on rgw.
4 from cStringIO import StringIO
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.orchestra.connection import split_user
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 s3_branches = [ 'giant', 'firefly', 'firefly-original', 'hammer' ]
34 for (client, cconf) in config.items():
35 branch = cconf.get('force-branch', None)
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)
42 branch = cconf.get('branch', 'ceph-' + suite_branch)
45 "Could not determine what branch to use for s3tests!")
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(
54 git_remote + 's3-tests.git',
55 '{tdir}/s3-tests'.format(tdir=testdir),
59 ctx.cluster.only(client).run(
61 'cd', '{tdir}/s3-tests'.format(tdir=testdir),
63 'git', 'reset', '--hard', sha1,
69 log.info('Removing s3-tests...')
70 testdir = teuthology.get_testdir(ctx)
72 ctx.cluster.only(client).run(
76 '{tdir}/s3-tests'.format(tdir=testdir),
81 def _config_user(s3tests_conf, section, user):
83 Configure users for this section by stashing away keys, ids, and
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)))
93 @contextlib.contextmanager
94 def create_users(ctx, config):
96 Create a main and an alternate s3 user.
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(
115 '{tdir}/archive/coverage'.format(tdir=testdir),
117 '-n', client_with_id,
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,
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(
139 '{tdir}/archive/coverage'.format(tdir=testdir),
141 '-n', client_with_id,
145 '--cluster', cluster_name,
150 @contextlib.contextmanager
151 def configure(ctx, config):
153 Configure the s3-tests. This includes the running of the
154 bootstrap code and the updating of local conf files.
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:
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
171 s3tests_conf['DEFAULT']['host'] = 'localhost'
173 if properties is not None and 'slow_backend' in properties:
174 s3tests_conf['fixtures']['slow backend'] = properties['slow_backend']
176 (remote,) = ctx.cluster.only(client).remotes.keys()
180 '{tdir}/s3-tests'.format(tdir=testdir),
186 s3tests_conf.write(conf_fp)
187 teuthology.write_file(
189 path='{tdir}/archive/s3-tests.{client}.conf'.format(tdir=testdir, client=client),
190 data=conf_fp.getvalue(),
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)
201 teuthology.write_file(
203 path='{tdir}/boto.cfg'.format(tdir=testdir),
211 log.info('Cleaning up boto...')
212 for client, properties in config['clients'].iteritems():
213 (remote,) = ctx.cluster.only(client).remotes.keys()
217 '{tdir}/boto.cfg'.format(tdir=testdir),
221 @contextlib.contextmanager
222 def run_tests(ctx, config):
224 Run the s3tests after everything is set up.
226 :param ctx: Context passed to task
227 :param config: specific configuration information
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():
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),
238 '{tdir}/s3-tests'.format(tdir=testdir),
240 '-a', ','.join(attrs),
242 if client_config is not None and 'extra_args' in client_config:
243 args.extend(client_config['extra_args'])
245 ctx.cluster.only(client).run(
247 label="s3 tests against rgw"
251 @contextlib.contextmanager
252 def scan_for_leaked_encryption_keys(ctx, config):
254 Scan radosgw logs for the encryption keys used by s3tests to
255 verify that we're not leaking secrets.
257 :param ctx: Context passed to task
258 :param config: specific configuration information
260 assert isinstance(config, dict)
265 # x-amz-server-side-encryption-customer-key
266 s3test_customer_key = 'pO3upElrwuEXSoFwCfnZPdSsmt/xWeFa0N9KgDijwVs='
268 log.debug('Scanning radosgw logs for leaked encryption keys...')
270 for client, client_config in config.iteritems():
271 if not client_config.get('scan_for_encryption_keys', True):
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()
279 '--binary-files=text',
281 '/var/log/ceph/rgw.{client}.log'.format(client=client_with_cluster),
290 if proc.returncode == 1: # 1 means no matches
292 log.error('radosgw log is leaking encryption keys!')
293 raise Exception('radosgw log is leaking encryption keys')
295 @contextlib.contextmanager
296 def task(ctx, config):
298 Run the s3-tests suite against rgw.
300 To run all tests on all clients::
307 To restrict testing to particular clients::
312 - s3tests: [client.0]
314 To run against a server on client.1 and increase the boto timeout to 10m::
324 To pass extra arguments to nose (e.g. to run a certain test)::
331 extra_args: ['test_s3:test_object_acl_grand_public_read']
333 extra_args: ['--exclude', 'test_100_continue']
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')]
342 if isinstance(config, list):
343 config = dict.fromkeys(config)
344 clients = config.keys()
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]:
351 teuthology.deep_merge(config[client], overrides.get('s3tests', {}))
353 log.debug('s3tests config is %s', config)
356 for client in clients:
357 s3tests_conf[client] = ConfigObj(
372 with contextutil.nested(
373 lambda: download(ctx=ctx, config=config),
374 lambda: create_users(ctx=ctx, config=dict(
376 s3tests_conf=s3tests_conf,
378 lambda: configure(ctx=ctx, config=dict(
380 s3tests_conf=s3tests_conf,
382 lambda: run_tests(ctx=ctx, config=config),
383 lambda: scan_for_leaked_encryption_keys(ctx=ctx, config=config),