]> git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/keystone.py
import 15.2.4
[ceph.git] / ceph / qa / tasks / keystone.py
1 """
2 Deploy and configure Keystone for Teuthology
3 """
4 import argparse
5 import contextlib
6 import logging
7
8 # still need this for python3.6
9 from collections import OrderedDict
10 from itertools import chain
11
12 from teuthology import misc as teuthology
13 from teuthology import contextutil
14 from teuthology.orchestra import run
15 from teuthology.packaging import install_package
16 from teuthology.packaging import remove_package
17 from teuthology.exceptions import ConfigError
18
19 log = logging.getLogger(__name__)
20
21
22 def get_keystone_dir(ctx):
23 return '{tdir}/keystone'.format(tdir=teuthology.get_testdir(ctx))
24
25 def run_in_keystone_dir(ctx, client, args, **kwargs):
26 return ctx.cluster.only(client).run(
27 args=[ 'cd', get_keystone_dir(ctx), run.Raw('&&'), ] + args,
28 **kwargs
29 )
30
31 def get_toxvenv_dir(ctx):
32 return ctx.tox.venv_path
33
34 def toxvenv_sh(ctx, remote, args, **kwargs):
35 activate = get_toxvenv_dir(ctx) + '/bin/activate'
36 return remote.sh(['source', activate, run.Raw('&&')] + args, **kwargs)
37
38 def run_in_keystone_venv(ctx, client, args):
39 run_in_keystone_dir(ctx, client,
40 [ 'source',
41 '.tox/venv/bin/activate',
42 run.Raw('&&')
43 ] + args)
44
45 def get_keystone_venved_cmd(ctx, cmd, args):
46 kbindir = get_keystone_dir(ctx) + '/.tox/venv/bin/'
47 return [ kbindir + 'python', kbindir + cmd ] + args
48
49 @contextlib.contextmanager
50 def download(ctx, config):
51 """
52 Download the Keystone from github.
53 Remove downloaded file upon exit.
54
55 The context passed in should be identical to the context
56 passed in to the main task.
57 """
58 assert isinstance(config, dict)
59 log.info('Downloading keystone...')
60 keystonedir = get_keystone_dir(ctx)
61
62 for (client, cconf) in config.items():
63 ctx.cluster.only(client).run(
64 args=[
65 'git', 'clone',
66 '-b', cconf.get('force-branch', 'master'),
67 'https://github.com/openstack/keystone.git',
68 keystonedir,
69 ],
70 )
71
72 sha1 = cconf.get('sha1')
73 if sha1 is not None:
74 run_in_keystone_dir(ctx, client, [
75 'git', 'reset', '--hard', sha1,
76 ],
77 )
78
79 # hax for http://tracker.ceph.com/issues/23659
80 run_in_keystone_dir(ctx, client, [
81 'sed', '-i',
82 's/pysaml2<4.0.3,>=2.4.0/pysaml2>=4.5.0/',
83 'requirements.txt'
84 ],
85 )
86 try:
87 yield
88 finally:
89 log.info('Removing keystone...')
90 for client in config:
91 ctx.cluster.only(client).run(
92 args=[ 'rm', '-rf', keystonedir ],
93 )
94
95 @contextlib.contextmanager
96 def install_packages(ctx, config):
97 """
98 Download the packaged dependencies of Keystone.
99 Remove install packages upon exit.
100
101 The context passed in should be identical to the context
102 passed in to the main task.
103 """
104 assert isinstance(config, dict)
105 log.info('Installing packages for Keystone...')
106
107 packages = {}
108 for (client, _) in config.items():
109 (remote,) = ctx.cluster.only(client).remotes.keys()
110 # use bindep to read which dependencies we need from keystone/bindep.txt
111 toxvenv_sh(ctx, remote, ['pip', 'install', 'bindep'])
112 packages[client] = toxvenv_sh(ctx, remote,
113 ['bindep', '--brief', '--file', '{}/bindep.txt'.format(get_keystone_dir(ctx))],
114 check_status=False).splitlines() # returns 1 on success?
115 for dep in packages[client]:
116 install_package(dep, remote)
117 try:
118 yield
119 finally:
120 log.info('Removing packaged dependencies of Keystone...')
121
122 for (client, _) in config.items():
123 (remote,) = ctx.cluster.only(client).remotes.keys()
124 for dep in packages[client]:
125 remove_package(dep, remote)
126
127 @contextlib.contextmanager
128 def setup_venv(ctx, config):
129 """
130 Setup the virtualenv for Keystone using tox.
131 """
132 assert isinstance(config, dict)
133 log.info('Setting up virtualenv for keystone...')
134 for (client, _) in config.items():
135 run_in_keystone_dir(ctx, client,
136 [ 'source',
137 '{tvdir}/bin/activate'.format(tvdir=get_toxvenv_dir(ctx)),
138 run.Raw('&&'),
139 'tox', '-e', 'venv', '--notest'
140 ])
141
142 run_in_keystone_venv(ctx, client,
143 [ 'pip', 'install', 'python-openstackclient'])
144 try:
145 yield
146 finally:
147 pass
148
149 @contextlib.contextmanager
150 def configure_instance(ctx, config):
151 assert isinstance(config, dict)
152 log.info('Configuring keystone...')
153
154 keyrepo_dir = '{kdir}/etc/fernet-keys'.format(kdir=get_keystone_dir(ctx))
155 for (client, _) in config.items():
156 # prepare the config file
157 run_in_keystone_dir(ctx, client,
158 [
159 'source',
160 f'{get_toxvenv_dir(ctx)}/bin/activate',
161 run.Raw('&&'),
162 'tox', '-e', 'genconfig'
163 ])
164 run_in_keystone_dir(ctx, client,
165 [
166 'cp', '-f',
167 'etc/keystone.conf.sample',
168 'etc/keystone.conf'
169 ])
170 run_in_keystone_dir(ctx, client,
171 [
172 'sed',
173 '-e', 's^#key_repository =.*^key_repository = {kr}^'.format(kr = keyrepo_dir),
174 '-i', 'etc/keystone.conf'
175 ])
176 # log to a file that gets archived
177 log_file = '{p}/archive/keystone.{c}.log'.format(p=teuthology.get_testdir(ctx), c=client)
178 run_in_keystone_dir(ctx, client,
179 [
180 'sed',
181 '-e', 's^#log_file =.*^log_file = {}^'.format(log_file),
182 '-i', 'etc/keystone.conf'
183 ])
184 # copy the config to archive
185 run_in_keystone_dir(ctx, client, [
186 'cp', 'etc/keystone.conf',
187 '{}/archive/keystone.{}.conf'.format(teuthology.get_testdir(ctx), client)
188 ])
189
190 # prepare key repository for Fetnet token authenticator
191 run_in_keystone_dir(ctx, client, [ 'mkdir', '-p', keyrepo_dir ])
192 run_in_keystone_venv(ctx, client, [ 'keystone-manage', 'fernet_setup' ])
193
194 # sync database
195 run_in_keystone_venv(ctx, client, [ 'keystone-manage', 'db_sync' ])
196 yield
197
198 @contextlib.contextmanager
199 def run_keystone(ctx, config):
200 assert isinstance(config, dict)
201 log.info('Configuring keystone...')
202
203 for (client, _) in config.items():
204 (remote,) = ctx.cluster.only(client).remotes.keys()
205 cluster_name, _, client_id = teuthology.split_role(client)
206
207 # start the public endpoint
208 client_public_with_id = 'keystone.public' + '.' + client_id
209
210 public_host, public_port = ctx.keystone.public_endpoints[client]
211 run_cmd = get_keystone_venved_cmd(ctx, 'keystone-wsgi-public',
212 [ '--host', public_host, '--port', str(public_port),
213 # Let's put the Keystone in background, wait for EOF
214 # and after receiving it, send SIGTERM to the daemon.
215 # This crazy hack is because Keystone, in contrast to
216 # our other daemons, doesn't quit on stdin.close().
217 # Teuthology relies on this behaviour.
218 run.Raw('& { read; kill %1; }')
219 ]
220 )
221 ctx.daemons.add_daemon(
222 remote, 'keystone', client_public_with_id,
223 cluster=cluster_name,
224 args=run_cmd,
225 logger=log.getChild(client),
226 stdin=run.PIPE,
227 cwd=get_keystone_dir(ctx),
228 wait=False,
229 check_status=False,
230 )
231
232 # start the admin endpoint
233 client_admin_with_id = 'keystone.admin' + '.' + client_id
234
235 admin_host, admin_port = ctx.keystone.admin_endpoints[client]
236 run_cmd = get_keystone_venved_cmd(ctx, 'keystone-wsgi-admin',
237 [ '--host', admin_host, '--port', str(admin_port),
238 run.Raw('& { read; kill %1; }')
239 ]
240 )
241 ctx.daemons.add_daemon(
242 remote, 'keystone', client_admin_with_id,
243 cluster=cluster_name,
244 args=run_cmd,
245 logger=log.getChild(client),
246 stdin=run.PIPE,
247 cwd=get_keystone_dir(ctx),
248 wait=False,
249 check_status=False,
250 )
251
252 # sleep driven synchronization
253 run_in_keystone_venv(ctx, client, [ 'sleep', '15' ])
254 try:
255 yield
256 finally:
257 log.info('Stopping Keystone admin instance')
258 ctx.daemons.get_daemon('keystone', client_admin_with_id,
259 cluster_name).stop()
260
261 log.info('Stopping Keystone public instance')
262 ctx.daemons.get_daemon('keystone', client_public_with_id,
263 cluster_name).stop()
264
265
266 def dict_to_args(specials, items):
267 """
268 Transform
269 [(key1, val1), (special, val_special), (key3, val3) ]
270 into:
271 [ '--key1', 'val1', '--key3', 'val3', 'val_special' ]
272 """
273 args = []
274 special_vals = OrderedDict((k, '') for k in specials.split(','))
275 for (k, v) in items:
276 if k in special_vals:
277 special_vals[k] = v
278 else:
279 args.append('--{k}'.format(k=k))
280 args.append(v)
281 args.extend(arg for arg in special_vals.values() if arg)
282 return args
283
284 def run_section_cmds(ctx, cclient, section_cmd, specials,
285 section_config_list):
286 admin_host, admin_port = ctx.keystone.admin_endpoints[cclient]
287
288 auth_section = [
289 ( 'os-username', 'admin' ),
290 ( 'os-password', 'ADMIN' ),
291 ( 'os-user-domain-id', 'default' ),
292 ( 'os-project-name', 'admin' ),
293 ( 'os-project-domain-id', 'default' ),
294 ( 'os-identity-api-version', '3' ),
295 ( 'os-auth-url', 'http://{host}:{port}/v3'.format(host=admin_host,
296 port=admin_port) ),
297 ]
298
299 for section_item in section_config_list:
300 run_in_keystone_venv(ctx, cclient,
301 [ 'openstack' ] + section_cmd.split() +
302 dict_to_args(specials, auth_section + list(section_item.items())) +
303 [ '--debug' ])
304
305 def create_endpoint(ctx, cclient, service, url, adminurl=None):
306 endpoint_sections = [
307 {'service': service, 'interface': 'public', 'url': url},
308 ]
309 if adminurl:
310 endpoint_sections.append(
311 {'service': service, 'interface': 'admin', 'url': adminurl}
312 )
313 run_section_cmds(ctx, cclient, 'endpoint create',
314 'service,interface,url',
315 endpoint_sections)
316
317 @contextlib.contextmanager
318 def fill_keystone(ctx, config):
319 assert isinstance(config, dict)
320
321 for (cclient, cconfig) in config.items():
322 public_host, public_port = ctx.keystone.public_endpoints[cclient]
323 url = 'http://{host}:{port}/v3'.format(host=public_host,
324 port=public_port)
325 admin_host, admin_port = ctx.keystone.admin_endpoints[cclient]
326 admin_url = 'http://{host}:{port}/v3'.format(host=admin_host,
327 port=admin_port)
328 opts = {'password': 'ADMIN',
329 'username': 'admin',
330 'project-name': 'admin',
331 'role-name': 'admin',
332 'service-name': 'keystone',
333 'region-id': 'RegionOne',
334 'admin-url': admin_url,
335 'public-url': url}
336 bootstrap_args = chain.from_iterable(('--bootstrap-{}'.format(k), v)
337 for k, v in opts.items())
338 run_in_keystone_venv(ctx, cclient,
339 ['keystone-manage', 'bootstrap'] +
340 list(bootstrap_args))
341
342 # configure tenants/projects
343 run_section_cmds(ctx, cclient, 'domain create', 'name',
344 cconfig.get('domains', []))
345 run_section_cmds(ctx, cclient, 'project create', 'name',
346 cconfig.get('projects', []))
347 run_section_cmds(ctx, cclient, 'user create', 'name',
348 cconfig.get('users', []))
349 run_section_cmds(ctx, cclient, 'role create', 'name',
350 cconfig.get('roles', []))
351 run_section_cmds(ctx, cclient, 'role add', 'name',
352 cconfig.get('role-mappings', []))
353 run_section_cmds(ctx, cclient, 'service create', 'type',
354 cconfig.get('services', []))
355
356 # for the deferred endpoint creation; currently it's used in rgw.py
357 ctx.keystone.create_endpoint = create_endpoint
358
359 # sleep driven synchronization -- just in case
360 run_in_keystone_venv(ctx, cclient, [ 'sleep', '3' ])
361 try:
362 yield
363 finally:
364 pass
365
366 def assign_ports(ctx, config, initial_port):
367 """
368 Assign port numbers starting from @initial_port
369 """
370 port = initial_port
371 role_endpoints = {}
372 for remote, roles_for_host in ctx.cluster.remotes.items():
373 for role in roles_for_host:
374 if role in config:
375 role_endpoints[role] = (remote.name.split('@')[1], port)
376 port += 1
377
378 return role_endpoints
379
380 @contextlib.contextmanager
381 def task(ctx, config):
382 """
383 Deploy and configure Keystone
384
385 Example of configuration:
386
387 - install:
388 - ceph:
389 - tox: [ client.0 ]
390 - keystone:
391 client.0:
392 force-branch: master
393 domains:
394 - name: default
395 description: Default Domain
396 projects:
397 - name: admin
398 description: Admin Tenant
399 users:
400 - name: admin
401 password: ADMIN
402 project: admin
403 roles: [ name: admin, name: Member ]
404 role-mappings:
405 - name: admin
406 user: admin
407 project: admin
408 services:
409 - name: keystone
410 type: identity
411 description: Keystone Identity Service
412 - name: swift
413 type: object-store
414 description: Swift Service
415 """
416 assert config is None or isinstance(config, list) \
417 or isinstance(config, dict), \
418 "task keystone only supports a list or dictionary for configuration"
419
420 if not hasattr(ctx, 'tox'):
421 raise ConfigError('keystone must run after the tox task')
422
423 all_clients = ['client.{id}'.format(id=id_)
424 for id_ in teuthology.all_roles_of_type(ctx.cluster, 'client')]
425 if config is None:
426 config = all_clients
427 if isinstance(config, list):
428 config = dict.fromkeys(config)
429
430 log.debug('Keystone config is %s', config)
431
432 ctx.keystone = argparse.Namespace()
433 ctx.keystone.public_endpoints = assign_ports(ctx, config, 5000)
434 ctx.keystone.admin_endpoints = assign_ports(ctx, config, 35357)
435
436 with contextutil.nested(
437 lambda: download(ctx=ctx, config=config),
438 lambda: install_packages(ctx=ctx, config=config),
439 lambda: setup_venv(ctx=ctx, config=config),
440 lambda: configure_instance(ctx=ctx, config=config),
441 lambda: run_keystone(ctx=ctx, config=config),
442 lambda: fill_keystone(ctx=ctx, config=config),
443 ):
444 yield