]> git.proxmox.com Git - ceph.git/blame - ceph/qa/tasks/keystone.py
update ceph source to reef 18.2.0
[ceph.git] / ceph / qa / tasks / keystone.py
CommitLineData
11fdf7f2
TL
1"""
2Deploy and configure Keystone for Teuthology
3"""
4import argparse
5import contextlib
6import logging
e306af50
TL
7
8# still need this for python3.6
9from collections import OrderedDict
10from itertools import chain
11fdf7f2
TL
11
12from teuthology import misc as teuthology
13from teuthology import contextutil
14from teuthology.orchestra import run
11fdf7f2
TL
15from teuthology.packaging import install_package
16from teuthology.packaging import remove_package
9f95a23c 17from teuthology.exceptions import ConfigError
11fdf7f2
TL
18
19log = logging.getLogger(__name__)
20
21
11fdf7f2
TL
22def get_keystone_dir(ctx):
23 return '{tdir}/keystone'.format(tdir=teuthology.get_testdir(ctx))
24
9f95a23c
TL
25def run_in_keystone_dir(ctx, client, args, **kwargs):
26 return ctx.cluster.only(client).run(
11fdf7f2 27 args=[ 'cd', get_keystone_dir(ctx), run.Raw('&&'), ] + args,
9f95a23c
TL
28 **kwargs
29 )
30
31def get_toxvenv_dir(ctx):
32 return ctx.tox.venv_path
33
e306af50
TL
34def toxvenv_sh(ctx, remote, args, **kwargs):
35 activate = get_toxvenv_dir(ctx) + '/bin/activate'
36 return remote.sh(['source', activate, run.Raw('&&')] + args, **kwargs)
11fdf7f2
TL
37
38def 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
05a536ef 45def get_keystone_venved_cmd(ctx, cmd, args, env=[]):
11fdf7f2 46 kbindir = get_keystone_dir(ctx) + '/.tox/venv/bin/'
05a536ef 47 return env + [ kbindir + 'python', kbindir + cmd ] + args
11fdf7f2 48
11fdf7f2
TL
49@contextlib.contextmanager
50def 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
f67539c2
TL
95patch_bindep_template = """\
96import fileinput
97import sys
98import os
99fixed=False
100os.chdir("{keystone_dir}")
101for line in fileinput.input("bindep.txt", inplace=True):
102 if line == "python34-devel [platform:centos]\\n":
103 line="python34-devel [platform:centos-7]\\npython36-devel [platform:centos-8]\\n"
104 fixed=True
105 print(line,end="")
106
107print("Fixed line" if fixed else "No fix necessary", file=sys.stderr)
108exit(0)
109"""
110
9f95a23c
TL
111@contextlib.contextmanager
112def install_packages(ctx, config):
113 """
114 Download the packaged dependencies of Keystone.
115 Remove install packages upon exit.
116
117 The context passed in should be identical to the context
118 passed in to the main task.
119 """
120 assert isinstance(config, dict)
121 log.info('Installing packages for Keystone...')
122
f67539c2
TL
123 patch_bindep = patch_bindep_template \
124 .replace("{keystone_dir}", get_keystone_dir(ctx))
9f95a23c
TL
125 packages = {}
126 for (client, _) in config.items():
127 (remote,) = ctx.cluster.only(client).remotes.keys()
f67539c2 128 toxvenv_sh(ctx, remote, ['python'], stdin=patch_bindep)
9f95a23c 129 # use bindep to read which dependencies we need from keystone/bindep.txt
e306af50
TL
130 toxvenv_sh(ctx, remote, ['pip', 'install', 'bindep'])
131 packages[client] = toxvenv_sh(ctx, remote,
9f95a23c 132 ['bindep', '--brief', '--file', '{}/bindep.txt'.format(get_keystone_dir(ctx))],
e306af50 133 check_status=False).splitlines() # returns 1 on success?
9f95a23c
TL
134 for dep in packages[client]:
135 install_package(dep, remote)
136 try:
137 yield
138 finally:
139 log.info('Removing packaged dependencies of Keystone...')
140
141 for (client, _) in config.items():
142 (remote,) = ctx.cluster.only(client).remotes.keys()
143 for dep in packages[client]:
144 remove_package(dep, remote)
145
05a536ef
TL
146def run_mysql_query(ctx, remote, query):
147 query_arg = '--execute="{}"'.format(query)
148 args = ['sudo', 'mysql', run.Raw(query_arg)]
149 remote.run(args=args)
150
151@contextlib.contextmanager
152def setup_database(ctx, config):
153 """
154 Setup database for Keystone.
155 """
156 assert isinstance(config, dict)
157 log.info('Setting up database for keystone...')
158
159 for (client, cconf) in config.items():
160 (remote,) = ctx.cluster.only(client).remotes.keys()
161
162 # MariaDB on RHEL/CentOS needs service started after package install
163 # while Ubuntu starts service by default.
164 if remote.os.name == 'rhel' or remote.os.name == 'centos':
165 remote.run(args=['sudo', 'systemctl', 'restart', 'mariadb'])
166
167 run_mysql_query(ctx, remote, "CREATE USER 'keystone'@'localhost' IDENTIFIED BY 'SECRET';")
168 run_mysql_query(ctx, remote, "CREATE DATABASE keystone;")
169 run_mysql_query(ctx, remote, "GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost';")
170 run_mysql_query(ctx, remote, "FLUSH PRIVILEGES;")
171
172 try:
173 yield
174 finally:
175 pass
176
11fdf7f2
TL
177@contextlib.contextmanager
178def setup_venv(ctx, config):
179 """
180 Setup the virtualenv for Keystone using tox.
181 """
182 assert isinstance(config, dict)
183 log.info('Setting up virtualenv for keystone...')
184 for (client, _) in config.items():
05a536ef
TL
185 run_in_keystone_dir(ctx, client,
186 ['sed', '-i', 's/usedevelop.*/usedevelop=false/g', 'tox.ini'])
187
11fdf7f2
TL
188 run_in_keystone_dir(ctx, client,
189 [ 'source',
9f95a23c 190 '{tvdir}/bin/activate'.format(tvdir=get_toxvenv_dir(ctx)),
11fdf7f2
TL
191 run.Raw('&&'),
192 'tox', '-e', 'venv', '--notest'
193 ])
194
195 run_in_keystone_venv(ctx, client,
f67539c2
TL
196 [ 'pip', 'install',
197 'python-openstackclient==5.2.1',
198 'osc-lib==2.0.0'
199 ])
11fdf7f2
TL
200 try:
201 yield
202 finally:
9f95a23c 203 pass
11fdf7f2
TL
204
205@contextlib.contextmanager
206def configure_instance(ctx, config):
207 assert isinstance(config, dict)
208 log.info('Configuring keystone...')
209
05a536ef
TL
210 kdir = get_keystone_dir(ctx)
211 keyrepo_dir = '{kdir}/etc/fernet-keys'.format(kdir=kdir)
11fdf7f2
TL
212 for (client, _) in config.items():
213 # prepare the config file
214 run_in_keystone_dir(ctx, client,
215 [
e306af50
TL
216 'source',
217 f'{get_toxvenv_dir(ctx)}/bin/activate',
218 run.Raw('&&'),
219 'tox', '-e', 'genconfig'
11fdf7f2
TL
220 ])
221 run_in_keystone_dir(ctx, client,
222 [
e306af50
TL
223 'cp', '-f',
224 'etc/keystone.conf.sample',
225 'etc/keystone.conf'
11fdf7f2
TL
226 ])
227 run_in_keystone_dir(ctx, client,
228 [
229 'sed',
230 '-e', 's^#key_repository =.*^key_repository = {kr}^'.format(kr = keyrepo_dir),
231 '-i', 'etc/keystone.conf'
232 ])
05a536ef
TL
233 run_in_keystone_dir(ctx, client,
234 [
235 'sed',
236 '-e', 's^#connection =.*^connection = mysql+pymysql://keystone:SECRET@localhost/keystone^',
237 '-i', 'etc/keystone.conf'
238 ])
9f95a23c
TL
239 # log to a file that gets archived
240 log_file = '{p}/archive/keystone.{c}.log'.format(p=teuthology.get_testdir(ctx), c=client)
241 run_in_keystone_dir(ctx, client,
242 [
243 'sed',
244 '-e', 's^#log_file =.*^log_file = {}^'.format(log_file),
245 '-i', 'etc/keystone.conf'
246 ])
247 # copy the config to archive
248 run_in_keystone_dir(ctx, client, [
249 'cp', 'etc/keystone.conf',
250 '{}/archive/keystone.{}.conf'.format(teuthology.get_testdir(ctx), client)
251 ])
11fdf7f2 252
05a536ef
TL
253 conf_file = '{kdir}/etc/keystone.conf'.format(kdir=get_keystone_dir(ctx))
254
11fdf7f2
TL
255 # prepare key repository for Fetnet token authenticator
256 run_in_keystone_dir(ctx, client, [ 'mkdir', '-p', keyrepo_dir ])
05a536ef 257 run_in_keystone_venv(ctx, client, [ 'keystone-manage', '--config-file', conf_file, 'fernet_setup' ])
11fdf7f2
TL
258
259 # sync database
05a536ef 260 run_in_keystone_venv(ctx, client, [ 'keystone-manage', '--config-file', conf_file, 'db_sync' ])
11fdf7f2
TL
261 yield
262
263@contextlib.contextmanager
264def run_keystone(ctx, config):
265 assert isinstance(config, dict)
266 log.info('Configuring keystone...')
267
05a536ef
TL
268 conf_file = '{kdir}/etc/keystone.conf'.format(kdir=get_keystone_dir(ctx))
269
11fdf7f2 270 for (client, _) in config.items():
9f95a23c 271 (remote,) = ctx.cluster.only(client).remotes.keys()
11fdf7f2
TL
272 cluster_name, _, client_id = teuthology.split_role(client)
273
274 # start the public endpoint
275 client_public_with_id = 'keystone.public' + '.' + client_id
11fdf7f2
TL
276
277 public_host, public_port = ctx.keystone.public_endpoints[client]
278 run_cmd = get_keystone_venved_cmd(ctx, 'keystone-wsgi-public',
279 [ '--host', public_host, '--port', str(public_port),
280 # Let's put the Keystone in background, wait for EOF
281 # and after receiving it, send SIGTERM to the daemon.
282 # This crazy hack is because Keystone, in contrast to
283 # our other daemons, doesn't quit on stdin.close().
284 # Teuthology relies on this behaviour.
285 run.Raw('& { read; kill %1; }')
05a536ef
TL
286 ],
287 [
288 run.Raw('OS_KEYSTONE_CONFIG_FILES={}'.format(conf_file)),
289 ],
11fdf7f2
TL
290 )
291 ctx.daemons.add_daemon(
292 remote, 'keystone', client_public_with_id,
293 cluster=cluster_name,
294 args=run_cmd,
295 logger=log.getChild(client),
296 stdin=run.PIPE,
11fdf7f2
TL
297 wait=False,
298 check_status=False,
299 )
300
301 # sleep driven synchronization
302 run_in_keystone_venv(ctx, client, [ 'sleep', '15' ])
303 try:
304 yield
305 finally:
11fdf7f2
TL
306 log.info('Stopping Keystone public instance')
307 ctx.daemons.get_daemon('keystone', client_public_with_id,
308 cluster_name).stop()
309
310
e306af50 311def dict_to_args(specials, items):
11fdf7f2
TL
312 """
313 Transform
314 [(key1, val1), (special, val_special), (key3, val3) ]
315 into:
316 [ '--key1', 'val1', '--key3', 'val3', 'val_special' ]
317 """
e306af50
TL
318 args = []
319 special_vals = OrderedDict((k, '') for k in specials.split(','))
11fdf7f2 320 for (k, v) in items:
e306af50
TL
321 if k in special_vals:
322 special_vals[k] = v
11fdf7f2
TL
323 else:
324 args.append('--{k}'.format(k=k))
325 args.append(v)
e306af50 326 args.extend(arg for arg in special_vals.values() if arg)
11fdf7f2
TL
327 return args
328
e306af50 329def run_section_cmds(ctx, cclient, section_cmd, specials,
11fdf7f2 330 section_config_list):
05a536ef 331 public_host, public_port = ctx.keystone.public_endpoints[cclient]
11fdf7f2
TL
332
333 auth_section = [
e306af50
TL
334 ( 'os-username', 'admin' ),
335 ( 'os-password', 'ADMIN' ),
336 ( 'os-user-domain-id', 'default' ),
337 ( 'os-project-name', 'admin' ),
338 ( 'os-project-domain-id', 'default' ),
339 ( 'os-identity-api-version', '3' ),
05a536ef
TL
340 ( 'os-auth-url', 'http://{host}:{port}/v3'.format(host=public_host,
341 port=public_port) ),
11fdf7f2
TL
342 ]
343
344 for section_item in section_config_list:
345 run_in_keystone_venv(ctx, cclient,
346 [ 'openstack' ] + section_cmd.split() +
e306af50 347 dict_to_args(specials, auth_section + list(section_item.items())) +
9f95a23c 348 [ '--debug' ])
11fdf7f2 349
9f95a23c 350def create_endpoint(ctx, cclient, service, url, adminurl=None):
e306af50
TL
351 endpoint_sections = [
352 {'service': service, 'interface': 'public', 'url': url},
353 ]
9f95a23c 354 if adminurl:
e306af50
TL
355 endpoint_sections.append(
356 {'service': service, 'interface': 'admin', 'url': adminurl}
357 )
358 run_section_cmds(ctx, cclient, 'endpoint create',
359 'service,interface,url',
360 endpoint_sections)
11fdf7f2
TL
361
362@contextlib.contextmanager
363def fill_keystone(ctx, config):
364 assert isinstance(config, dict)
365
366 for (cclient, cconfig) in config.items():
e306af50
TL
367 public_host, public_port = ctx.keystone.public_endpoints[cclient]
368 url = 'http://{host}:{port}/v3'.format(host=public_host,
369 port=public_port)
e306af50 370 opts = {'password': 'ADMIN',
e306af50 371 'region-id': 'RegionOne',
f67539c2 372 'internal-url': url,
05a536ef 373 'admin-url': url,
e306af50
TL
374 'public-url': url}
375 bootstrap_args = chain.from_iterable(('--bootstrap-{}'.format(k), v)
376 for k, v in opts.items())
05a536ef 377 conf_file = '{kdir}/etc/keystone.conf'.format(kdir=get_keystone_dir(ctx))
e306af50 378 run_in_keystone_venv(ctx, cclient,
05a536ef 379 ['keystone-manage', '--config-file', conf_file, 'bootstrap'] +
e306af50
TL
380 list(bootstrap_args))
381
11fdf7f2 382 # configure tenants/projects
05a536ef 383 run_section_cmds(ctx, cclient, 'domain create --or-show', 'name',
e306af50 384 cconfig.get('domains', []))
05a536ef 385 run_section_cmds(ctx, cclient, 'project create --or-show', 'name',
e306af50 386 cconfig.get('projects', []))
05a536ef 387 run_section_cmds(ctx, cclient, 'user create --or-show', 'name',
e306af50 388 cconfig.get('users', []))
05a536ef 389 run_section_cmds(ctx, cclient, 'role create --or-show', 'name',
e306af50 390 cconfig.get('roles', []))
11fdf7f2 391 run_section_cmds(ctx, cclient, 'role add', 'name',
e306af50
TL
392 cconfig.get('role-mappings', []))
393 run_section_cmds(ctx, cclient, 'service create', 'type',
394 cconfig.get('services', []))
11fdf7f2 395
11fdf7f2
TL
396 # for the deferred endpoint creation; currently it's used in rgw.py
397 ctx.keystone.create_endpoint = create_endpoint
398
399 # sleep driven synchronization -- just in case
400 run_in_keystone_venv(ctx, cclient, [ 'sleep', '3' ])
401 try:
402 yield
403 finally:
404 pass
405
406def assign_ports(ctx, config, initial_port):
407 """
408 Assign port numbers starting from @initial_port
409 """
410 port = initial_port
411 role_endpoints = {}
9f95a23c 412 for remote, roles_for_host in ctx.cluster.remotes.items():
11fdf7f2
TL
413 for role in roles_for_host:
414 if role in config:
415 role_endpoints[role] = (remote.name.split('@')[1], port)
416 port += 1
417
418 return role_endpoints
419
420@contextlib.contextmanager
421def task(ctx, config):
422 """
423 Deploy and configure Keystone
424
425 Example of configuration:
426
427 - install:
428 - ceph:
429 - tox: [ client.0 ]
430 - keystone:
431 client.0:
432 force-branch: master
e306af50 433 domains:
05a536ef
TL
434 - name: custom
435 description: Custom domain
e306af50 436 projects:
05a536ef
TL
437 - name: custom
438 description: Custom project
11fdf7f2 439 users:
05a536ef
TL
440 - name: custom
441 password: SECRET
442 project: custom
443 roles: [ name: custom ]
11fdf7f2 444 role-mappings:
05a536ef
TL
445 - name: custom
446 user: custom
447 project: custom
11fdf7f2 448 services:
11fdf7f2
TL
449 - name: swift
450 type: object-store
451 description: Swift Service
452 """
453 assert config is None or isinstance(config, list) \
454 or isinstance(config, dict), \
455 "task keystone only supports a list or dictionary for configuration"
456
9f95a23c 457 if not hasattr(ctx, 'tox'):
11fdf7f2
TL
458 raise ConfigError('keystone must run after the tox task')
459
460 all_clients = ['client.{id}'.format(id=id_)
461 for id_ in teuthology.all_roles_of_type(ctx.cluster, 'client')]
462 if config is None:
463 config = all_clients
464 if isinstance(config, list):
465 config = dict.fromkeys(config)
466
467 log.debug('Keystone config is %s', config)
468
469 ctx.keystone = argparse.Namespace()
470 ctx.keystone.public_endpoints = assign_ports(ctx, config, 5000)
11fdf7f2
TL
471
472 with contextutil.nested(
11fdf7f2 473 lambda: download(ctx=ctx, config=config),
9f95a23c 474 lambda: install_packages(ctx=ctx, config=config),
05a536ef 475 lambda: setup_database(ctx=ctx, config=config),
11fdf7f2
TL
476 lambda: setup_venv(ctx=ctx, config=config),
477 lambda: configure_instance(ctx=ctx, config=config),
478 lambda: run_keystone(ctx=ctx, config=config),
479 lambda: fill_keystone(ctx=ctx, config=config),
480 ):
481 yield