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