]> git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/keystone.py
update source to Ceph Pacific 16.2.2
[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 patch_bindep_template = """\
96 import fileinput
97 import sys
98 import os
99 fixed=False
100 os.chdir("{keystone_dir}")
101 for 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
107 print("Fixed line" if fixed else "No fix necessary", file=sys.stderr)
108 exit(0)
109 """
110
111 @contextlib.contextmanager
112 def 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
123 patch_bindep = patch_bindep_template \
124 .replace("{keystone_dir}", get_keystone_dir(ctx))
125 packages = {}
126 for (client, _) in config.items():
127 (remote,) = ctx.cluster.only(client).remotes.keys()
128 toxvenv_sh(ctx, remote, ['python'], stdin=patch_bindep)
129 # use bindep to read which dependencies we need from keystone/bindep.txt
130 toxvenv_sh(ctx, remote, ['pip', 'install', 'bindep'])
131 packages[client] = toxvenv_sh(ctx, remote,
132 ['bindep', '--brief', '--file', '{}/bindep.txt'.format(get_keystone_dir(ctx))],
133 check_status=False).splitlines() # returns 1 on success?
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
146 @contextlib.contextmanager
147 def setup_venv(ctx, config):
148 """
149 Setup the virtualenv for Keystone using tox.
150 """
151 assert isinstance(config, dict)
152 log.info('Setting up virtualenv for keystone...')
153 for (client, _) in config.items():
154 run_in_keystone_dir(ctx, client,
155 [ 'source',
156 '{tvdir}/bin/activate'.format(tvdir=get_toxvenv_dir(ctx)),
157 run.Raw('&&'),
158 'tox', '-e', 'venv', '--notest'
159 ])
160
161 run_in_keystone_venv(ctx, client,
162 [ 'pip', 'install',
163 'python-openstackclient==5.2.1',
164 'osc-lib==2.0.0'
165 ])
166 try:
167 yield
168 finally:
169 pass
170
171 @contextlib.contextmanager
172 def configure_instance(ctx, config):
173 assert isinstance(config, dict)
174 log.info('Configuring keystone...')
175
176 keyrepo_dir = '{kdir}/etc/fernet-keys'.format(kdir=get_keystone_dir(ctx))
177 for (client, _) in config.items():
178 # prepare the config file
179 run_in_keystone_dir(ctx, client,
180 [
181 'source',
182 f'{get_toxvenv_dir(ctx)}/bin/activate',
183 run.Raw('&&'),
184 'tox', '-e', 'genconfig'
185 ])
186 run_in_keystone_dir(ctx, client,
187 [
188 'cp', '-f',
189 'etc/keystone.conf.sample',
190 'etc/keystone.conf'
191 ])
192 run_in_keystone_dir(ctx, client,
193 [
194 'sed',
195 '-e', 's^#key_repository =.*^key_repository = {kr}^'.format(kr = keyrepo_dir),
196 '-i', 'etc/keystone.conf'
197 ])
198 # log to a file that gets archived
199 log_file = '{p}/archive/keystone.{c}.log'.format(p=teuthology.get_testdir(ctx), c=client)
200 run_in_keystone_dir(ctx, client,
201 [
202 'sed',
203 '-e', 's^#log_file =.*^log_file = {}^'.format(log_file),
204 '-i', 'etc/keystone.conf'
205 ])
206 # copy the config to archive
207 run_in_keystone_dir(ctx, client, [
208 'cp', 'etc/keystone.conf',
209 '{}/archive/keystone.{}.conf'.format(teuthology.get_testdir(ctx), client)
210 ])
211
212 # prepare key repository for Fetnet token authenticator
213 run_in_keystone_dir(ctx, client, [ 'mkdir', '-p', keyrepo_dir ])
214 run_in_keystone_venv(ctx, client, [ 'keystone-manage', 'fernet_setup' ])
215
216 # sync database
217 run_in_keystone_venv(ctx, client, [ 'keystone-manage', 'db_sync' ])
218 yield
219
220 @contextlib.contextmanager
221 def run_keystone(ctx, config):
222 assert isinstance(config, dict)
223 log.info('Configuring keystone...')
224
225 for (client, _) in config.items():
226 (remote,) = ctx.cluster.only(client).remotes.keys()
227 cluster_name, _, client_id = teuthology.split_role(client)
228
229 # start the public endpoint
230 client_public_with_id = 'keystone.public' + '.' + client_id
231
232 public_host, public_port = ctx.keystone.public_endpoints[client]
233 run_cmd = get_keystone_venved_cmd(ctx, 'keystone-wsgi-public',
234 [ '--host', public_host, '--port', str(public_port),
235 # Let's put the Keystone in background, wait for EOF
236 # and after receiving it, send SIGTERM to the daemon.
237 # This crazy hack is because Keystone, in contrast to
238 # our other daemons, doesn't quit on stdin.close().
239 # Teuthology relies on this behaviour.
240 run.Raw('& { read; kill %1; }')
241 ]
242 )
243 ctx.daemons.add_daemon(
244 remote, 'keystone', client_public_with_id,
245 cluster=cluster_name,
246 args=run_cmd,
247 logger=log.getChild(client),
248 stdin=run.PIPE,
249 cwd=get_keystone_dir(ctx),
250 wait=False,
251 check_status=False,
252 )
253
254 # start the admin endpoint
255 client_admin_with_id = 'keystone.admin' + '.' + client_id
256
257 admin_host, admin_port = ctx.keystone.admin_endpoints[client]
258 run_cmd = get_keystone_venved_cmd(ctx, 'keystone-wsgi-admin',
259 [ '--host', admin_host, '--port', str(admin_port),
260 run.Raw('& { read; kill %1; }')
261 ]
262 )
263 ctx.daemons.add_daemon(
264 remote, 'keystone', client_admin_with_id,
265 cluster=cluster_name,
266 args=run_cmd,
267 logger=log.getChild(client),
268 stdin=run.PIPE,
269 cwd=get_keystone_dir(ctx),
270 wait=False,
271 check_status=False,
272 )
273
274 # sleep driven synchronization
275 run_in_keystone_venv(ctx, client, [ 'sleep', '15' ])
276 try:
277 yield
278 finally:
279 log.info('Stopping Keystone admin instance')
280 ctx.daemons.get_daemon('keystone', client_admin_with_id,
281 cluster_name).stop()
282
283 log.info('Stopping Keystone public instance')
284 ctx.daemons.get_daemon('keystone', client_public_with_id,
285 cluster_name).stop()
286
287
288 def dict_to_args(specials, items):
289 """
290 Transform
291 [(key1, val1), (special, val_special), (key3, val3) ]
292 into:
293 [ '--key1', 'val1', '--key3', 'val3', 'val_special' ]
294 """
295 args = []
296 special_vals = OrderedDict((k, '') for k in specials.split(','))
297 for (k, v) in items:
298 if k in special_vals:
299 special_vals[k] = v
300 else:
301 args.append('--{k}'.format(k=k))
302 args.append(v)
303 args.extend(arg for arg in special_vals.values() if arg)
304 return args
305
306 def run_section_cmds(ctx, cclient, section_cmd, specials,
307 section_config_list):
308 admin_host, admin_port = ctx.keystone.admin_endpoints[cclient]
309
310 auth_section = [
311 ( 'os-username', 'admin' ),
312 ( 'os-password', 'ADMIN' ),
313 ( 'os-user-domain-id', 'default' ),
314 ( 'os-project-name', 'admin' ),
315 ( 'os-project-domain-id', 'default' ),
316 ( 'os-identity-api-version', '3' ),
317 ( 'os-auth-url', 'http://{host}:{port}/v3'.format(host=admin_host,
318 port=admin_port) ),
319 ]
320
321 for section_item in section_config_list:
322 run_in_keystone_venv(ctx, cclient,
323 [ 'openstack' ] + section_cmd.split() +
324 dict_to_args(specials, auth_section + list(section_item.items())) +
325 [ '--debug' ])
326
327 def create_endpoint(ctx, cclient, service, url, adminurl=None):
328 endpoint_sections = [
329 {'service': service, 'interface': 'public', 'url': url},
330 ]
331 if adminurl:
332 endpoint_sections.append(
333 {'service': service, 'interface': 'admin', 'url': adminurl}
334 )
335 run_section_cmds(ctx, cclient, 'endpoint create',
336 'service,interface,url',
337 endpoint_sections)
338
339 @contextlib.contextmanager
340 def fill_keystone(ctx, config):
341 assert isinstance(config, dict)
342
343 for (cclient, cconfig) in config.items():
344 public_host, public_port = ctx.keystone.public_endpoints[cclient]
345 url = 'http://{host}:{port}/v3'.format(host=public_host,
346 port=public_port)
347 admin_host, admin_port = ctx.keystone.admin_endpoints[cclient]
348 admin_url = 'http://{host}:{port}/v3'.format(host=admin_host,
349 port=admin_port)
350 opts = {'password': 'ADMIN',
351 'region-id': 'RegionOne',
352 'internal-url': url,
353 'admin-url': admin_url,
354 'public-url': url}
355 bootstrap_args = chain.from_iterable(('--bootstrap-{}'.format(k), v)
356 for k, v in opts.items())
357 run_in_keystone_venv(ctx, cclient,
358 ['keystone-manage', 'bootstrap'] +
359 list(bootstrap_args))
360
361 # configure tenants/projects
362 run_section_cmds(ctx, cclient, 'domain create', 'name',
363 cconfig.get('domains', []))
364 run_section_cmds(ctx, cclient, 'project create', 'name',
365 cconfig.get('projects', []))
366 run_section_cmds(ctx, cclient, 'user create', 'name',
367 cconfig.get('users', []))
368 run_section_cmds(ctx, cclient, 'role create', 'name',
369 cconfig.get('roles', []))
370 run_section_cmds(ctx, cclient, 'role add', 'name',
371 cconfig.get('role-mappings', []))
372 run_section_cmds(ctx, cclient, 'service create', 'type',
373 cconfig.get('services', []))
374
375 # for the deferred endpoint creation; currently it's used in rgw.py
376 ctx.keystone.create_endpoint = create_endpoint
377
378 # sleep driven synchronization -- just in case
379 run_in_keystone_venv(ctx, cclient, [ 'sleep', '3' ])
380 try:
381 yield
382 finally:
383 pass
384
385 def assign_ports(ctx, config, initial_port):
386 """
387 Assign port numbers starting from @initial_port
388 """
389 port = initial_port
390 role_endpoints = {}
391 for remote, roles_for_host in ctx.cluster.remotes.items():
392 for role in roles_for_host:
393 if role in config:
394 role_endpoints[role] = (remote.name.split('@')[1], port)
395 port += 1
396
397 return role_endpoints
398
399 @contextlib.contextmanager
400 def task(ctx, config):
401 """
402 Deploy and configure Keystone
403
404 Example of configuration:
405
406 - install:
407 - ceph:
408 - tox: [ client.0 ]
409 - keystone:
410 client.0:
411 force-branch: master
412 domains:
413 - name: default
414 description: Default Domain
415 projects:
416 - name: admin
417 description: Admin Tenant
418 users:
419 - name: admin
420 password: ADMIN
421 project: admin
422 roles: [ name: admin, name: Member ]
423 role-mappings:
424 - name: admin
425 user: admin
426 project: admin
427 services:
428 - name: keystone
429 type: identity
430 description: Keystone Identity Service
431 - name: swift
432 type: object-store
433 description: Swift Service
434 """
435 assert config is None or isinstance(config, list) \
436 or isinstance(config, dict), \
437 "task keystone only supports a list or dictionary for configuration"
438
439 if not hasattr(ctx, 'tox'):
440 raise ConfigError('keystone must run after the tox task')
441
442 all_clients = ['client.{id}'.format(id=id_)
443 for id_ in teuthology.all_roles_of_type(ctx.cluster, 'client')]
444 if config is None:
445 config = all_clients
446 if isinstance(config, list):
447 config = dict.fromkeys(config)
448
449 log.debug('Keystone config is %s', config)
450
451 ctx.keystone = argparse.Namespace()
452 ctx.keystone.public_endpoints = assign_ports(ctx, config, 5000)
453 ctx.keystone.admin_endpoints = assign_ports(ctx, config, 35357)
454
455 with contextutil.nested(
456 lambda: download(ctx=ctx, config=config),
457 lambda: install_packages(ctx=ctx, config=config),
458 lambda: setup_venv(ctx=ctx, config=config),
459 lambda: configure_instance(ctx=ctx, config=config),
460 lambda: run_keystone(ctx=ctx, config=config),
461 lambda: fill_keystone(ctx=ctx, config=config),
462 ):
463 yield