]>
git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/keystone.py
2 Deploy and configure Keystone for Teuthology
8 # still need this for python3.6
9 from collections
import OrderedDict
10 from itertools
import chain
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
19 log
= logging
.getLogger(__name__
)
22 def get_keystone_dir(ctx
):
23 return '{tdir}/keystone'.format(tdir
=teuthology
.get_testdir(ctx
))
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
,
31 def get_toxvenv_dir(ctx
):
32 return ctx
.tox
.venv_path
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
)
38 def run_in_keystone_venv(ctx
, client
, args
):
39 run_in_keystone_dir(ctx
, client
,
41 '.tox/venv/bin/activate',
45 def get_keystone_venved_cmd(ctx
, cmd
, args
, env
=[]):
46 kbindir
= get_keystone_dir(ctx
) + '/.tox/venv/bin/'
47 return env
+ [ kbindir
+ 'python', kbindir
+ cmd
] + args
49 @contextlib.contextmanager
50 def download(ctx
, config
):
52 Download the Keystone from github.
53 Remove downloaded file upon exit.
55 The context passed in should be identical to the context
56 passed in to the main task.
58 assert isinstance(config
, dict)
59 log
.info('Downloading keystone...')
60 keystonedir
= get_keystone_dir(ctx
)
62 for (client
, cconf
) in config
.items():
63 ctx
.cluster
.only(client
).run(
66 '-b', cconf
.get('force-branch', 'master'),
67 'https://github.com/openstack/keystone.git',
72 sha1
= cconf
.get('sha1')
74 run_in_keystone_dir(ctx
, client
, [
75 'git', 'reset', '--hard', sha1
,
79 # hax for http://tracker.ceph.com/issues/23659
80 run_in_keystone_dir(ctx
, client
, [
82 's/pysaml2<4.0.3,>=2.4.0/pysaml2>=4.5.0/',
89 log
.info('Removing keystone...')
91 ctx
.cluster
.only(client
).run(
92 args
=[ 'rm', '-rf', keystonedir
],
95 patch_bindep_template
= """\
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"
107 print("Fixed line" if fixed else "No fix necessary", file=sys.stderr)
111 @contextlib.contextmanager
112 def install_packages(ctx
, config
):
114 Download the packaged dependencies of Keystone.
115 Remove install packages upon exit.
117 The context passed in should be identical to the context
118 passed in to the main task.
120 assert isinstance(config
, dict)
121 log
.info('Installing packages for Keystone...')
123 patch_bindep
= patch_bindep_template \
124 .replace("{keystone_dir}", get_keystone_dir(ctx
))
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
)
139 log
.info('Removing packaged dependencies of Keystone...')
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
)
146 def 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
)
151 @contextlib.contextmanager
152 def setup_database(ctx
, config
):
154 Setup database for Keystone.
156 assert isinstance(config
, dict)
157 log
.info('Setting up database for keystone...')
159 for (client
, cconf
) in config
.items():
160 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
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'])
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;")
177 @contextlib.contextmanager
178 def setup_venv(ctx
, config
):
180 Setup the virtualenv for Keystone using tox.
182 assert isinstance(config
, dict)
183 log
.info('Setting up virtualenv for keystone...')
184 for (client
, _
) in config
.items():
185 run_in_keystone_dir(ctx
, client
,
186 ['sed', '-i', 's/usedevelop.*/usedevelop=false/g', 'tox.ini'])
188 run_in_keystone_dir(ctx
, client
,
190 '{tvdir}/bin/activate'.format(tvdir
=get_toxvenv_dir(ctx
)),
192 'tox', '-e', 'venv', '--notest'
195 run_in_keystone_venv(ctx
, client
,
197 'python-openstackclient==5.2.1',
205 @contextlib.contextmanager
206 def configure_instance(ctx
, config
):
207 assert isinstance(config
, dict)
208 log
.info('Configuring keystone...')
210 kdir
= get_keystone_dir(ctx
)
211 keyrepo_dir
= '{kdir}/etc/fernet-keys'.format(kdir
=kdir
)
212 for (client
, _
) in config
.items():
213 # prepare the config file
214 run_in_keystone_dir(ctx
, client
,
217 f
'{get_toxvenv_dir(ctx)}/bin/activate',
219 'tox', '-e', 'genconfig'
221 run_in_keystone_dir(ctx
, client
,
224 'etc/keystone.conf.sample',
227 run_in_keystone_dir(ctx
, client
,
230 '-e', 's^#key_repository =.*^key_repository = {kr}^'.format(kr
= keyrepo_dir
),
231 '-i', 'etc/keystone.conf'
233 run_in_keystone_dir(ctx
, client
,
236 '-e', 's^#connection =.*^connection = mysql+pymysql://keystone:SECRET@localhost/keystone^',
237 '-i', 'etc/keystone.conf'
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
,
244 '-e', 's^#log_file =.*^log_file = {}^'.format(log_file
),
245 '-i', 'etc/keystone.conf'
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
)
253 conf_file
= '{kdir}/etc/keystone.conf'.format(kdir
=get_keystone_dir(ctx
))
255 # prepare key repository for Fetnet token authenticator
256 run_in_keystone_dir(ctx
, client
, [ 'mkdir', '-p', keyrepo_dir
])
257 run_in_keystone_venv(ctx
, client
, [ 'keystone-manage', '--config-file', conf_file
, 'fernet_setup' ])
260 run_in_keystone_venv(ctx
, client
, [ 'keystone-manage', '--config-file', conf_file
, 'db_sync' ])
263 @contextlib.contextmanager
264 def run_keystone(ctx
, config
):
265 assert isinstance(config
, dict)
266 log
.info('Configuring keystone...')
268 conf_file
= '{kdir}/etc/keystone.conf'.format(kdir
=get_keystone_dir(ctx
))
270 for (client
, _
) in config
.items():
271 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
272 cluster_name
, _
, client_id
= teuthology
.split_role(client
)
274 # start the public endpoint
275 client_public_with_id
= 'keystone.public' + '.' + client_id
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; }')
288 run
.Raw('OS_KEYSTONE_CONFIG_FILES={}'.format(conf_file
)),
291 ctx
.daemons
.add_daemon(
292 remote
, 'keystone', client_public_with_id
,
293 cluster
=cluster_name
,
295 logger
=log
.getChild(client
),
301 # sleep driven synchronization
302 run_in_keystone_venv(ctx
, client
, [ 'sleep', '15' ])
306 log
.info('Stopping Keystone public instance')
307 ctx
.daemons
.get_daemon('keystone', client_public_with_id
,
311 def dict_to_args(specials
, items
):
314 [(key1, val1), (special, val_special), (key3, val3) ]
316 [ '--key1', 'val1', '--key3', 'val3', 'val_special' ]
319 special_vals
= OrderedDict((k
, '') for k
in specials
.split(','))
321 if k
in special_vals
:
324 args
.append('--{k}'.format(k
=k
))
326 args
.extend(arg
for arg
in special_vals
.values() if arg
)
329 def run_section_cmds(ctx
, cclient
, section_cmd
, specials
,
330 section_config_list
):
331 public_host
, public_port
= ctx
.keystone
.public_endpoints
[cclient
]
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' ),
340 ( 'os-auth-url', 'http://{host}:{port}/v3'.format(host
=public_host
,
344 for section_item
in section_config_list
:
345 run_in_keystone_venv(ctx
, cclient
,
346 [ 'openstack' ] + section_cmd
.split() +
347 dict_to_args(specials
, auth_section
+ list(section_item
.items())) +
350 def create_endpoint(ctx
, cclient
, service
, url
, adminurl
=None):
351 endpoint_sections
= [
352 {'service': service
, 'interface': 'public', 'url': url
},
355 endpoint_sections
.append(
356 {'service': service
, 'interface': 'admin', 'url': adminurl
}
358 run_section_cmds(ctx
, cclient
, 'endpoint create',
359 'service,interface,url',
362 @contextlib.contextmanager
363 def fill_keystone(ctx
, config
):
364 assert isinstance(config
, dict)
366 for (cclient
, cconfig
) in config
.items():
367 public_host
, public_port
= ctx
.keystone
.public_endpoints
[cclient
]
368 url
= 'http://{host}:{port}/v3'.format(host
=public_host
,
370 opts
= {'password': 'ADMIN',
371 'region-id': 'RegionOne',
375 bootstrap_args
= chain
.from_iterable(('--bootstrap-{}'.format(k
), v
)
376 for k
, v
in opts
.items())
377 conf_file
= '{kdir}/etc/keystone.conf'.format(kdir
=get_keystone_dir(ctx
))
378 run_in_keystone_venv(ctx
, cclient
,
379 ['keystone-manage', '--config-file', conf_file
, 'bootstrap'] +
380 list(bootstrap_args
))
382 # configure tenants/projects
383 run_section_cmds(ctx
, cclient
, 'domain create --or-show', 'name',
384 cconfig
.get('domains', []))
385 run_section_cmds(ctx
, cclient
, 'project create --or-show', 'name',
386 cconfig
.get('projects', []))
387 run_section_cmds(ctx
, cclient
, 'user create --or-show', 'name',
388 cconfig
.get('users', []))
389 run_section_cmds(ctx
, cclient
, 'role create --or-show', 'name',
390 cconfig
.get('roles', []))
391 run_section_cmds(ctx
, cclient
, 'role add', 'name',
392 cconfig
.get('role-mappings', []))
393 run_section_cmds(ctx
, cclient
, 'service create', 'type',
394 cconfig
.get('services', []))
396 # for the deferred endpoint creation; currently it's used in rgw.py
397 ctx
.keystone
.create_endpoint
= create_endpoint
399 # sleep driven synchronization -- just in case
400 run_in_keystone_venv(ctx
, cclient
, [ 'sleep', '3' ])
406 def assign_ports(ctx
, config
, initial_port
):
408 Assign port numbers starting from @initial_port
412 for remote
, roles_for_host
in ctx
.cluster
.remotes
.items():
413 for role
in roles_for_host
:
415 role_endpoints
[role
] = (remote
.name
.split('@')[1], port
)
418 return role_endpoints
420 @contextlib.contextmanager
421 def task(ctx
, config
):
423 Deploy and configure Keystone
425 Example of configuration:
435 description: Custom domain
438 description: Custom project
443 roles: [ name: custom ]
451 description: Swift Service
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"
457 if not hasattr(ctx
, 'tox'):
458 raise ConfigError('keystone must run after the tox task')
460 all_clients
= ['client.{id}'.format(id=id_
)
461 for id_
in teuthology
.all_roles_of_type(ctx
.cluster
, 'client')]
464 if isinstance(config
, list):
465 config
= dict.fromkeys(config
)
467 log
.debug('Keystone config is %s', config
)
469 ctx
.keystone
= argparse
.Namespace()
470 ctx
.keystone
.public_endpoints
= assign_ports(ctx
, config
, 5000)
472 with contextutil
.nested(
473 lambda: download(ctx
=ctx
, config
=config
),
474 lambda: install_packages(ctx
=ctx
, config
=config
),
475 lambda: setup_database(ctx
=ctx
, config
=config
),
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
),