blob: b90ee0414538df4db81514b9813117aab127e47c [file] [log] [blame]
sslypushenko0de7d052015-04-16 18:49:55 +03001#!/usr/bin/env python
2
3# Copyright 2015 Mirantis, Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +030017"""
18Utility for creating **accounts.yaml** file for concurrent test runs.
19Creates one primary user, one alt user, one swift admin, one stack owner
20and one admin (optionally) for each concurrent thread. The utility creates
21user for each tenant. The **accounts.yaml** file will be valid and contain
22credentials for created users, so each user will be in separate tenant and
23have the username, tenant_name, password and roles.
24
25**Usage:** ``tempest-account-generator [-h] [OPTIONS] accounts_file.yaml``.
26
27Positional Arguments
Matthew Treinishf45ba2e2015-08-24 15:05:01 -040028--------------------
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +030029**accounts_file.yaml** (Required) Provide an output accounts yaml file. Utility
30creates a .yaml file in the directory where the command is ran. The appropriate
31name for the file is *accounts.yaml* and it should be placed in *tempest/etc*
32directory.
33
34Authentication
35--------------
36
37Account generator creates users and tenants so it needs the admin credentials
38of your cloud to operate properly. The corresponding info can be given either
39through CLI options or environment variables.
40
41You're probably familiar with these, but just to remind::
42
43 +----------+------------------+----------------------+
44 | Param | CLI | Environment Variable |
45 +----------+------------------+----------------------+
46 | Username | --os-username | OS_USERNAME |
47 | Password | --os-password | OS_PASSWORD |
48 | Tenant | --os-tenant-name | OS_TENANT_NAME |
49 +----------+------------------+----------------------+
50
51Optional Arguments
Matthew Treinishf45ba2e2015-08-24 15:05:01 -040052------------------
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +030053**-h**, **--help** (Optional) Shows help message with the description of
54utility and its arguments, and exits.
55
56**c /etc/tempest.conf**, **--config-file /etc/tempest.conf** (Optional) Path to
57tempest config file.
58
59**--os-username <auth-user-name>** (Optional) Name used for authentication with
60the OpenStack Identity service. Defaults to env[OS_USERNAME]. Note: User should
61have permissions to create new user accounts and tenants.
62
63**--os-password <auth-password>** (Optional) Password used for authentication
64with the OpenStack Identity service. Defaults to env[OS_PASSWORD].
65
66**--os-tenant-name <auth-tenant-name>** (Optional) Tenant to request
67authorization on. Defaults to env[OS_TENANT_NAME].
68
69**--tag TAG** (Optional) Resources tag. Each created resource (user, project)
70will have the prefix with the given TAG in its name. Using tag is recommended
71for the further using, cleaning resources.
72
73**-r CONCURRENCY**, **--concurrency CONCURRENCY** (Required) Concurrency count
74(default: 1). The number of accounts required can be estimated as
75CONCURRENCY x 2. Each user provided in *accounts.yaml* file will be in
76a different tenant. This is required to provide isolation between test for
77running in parallel.
78
79**--with-admin** (Optional) Creates admin for each concurrent group
80(default: False).
81
82To see help on specific argument, please do: ``tempest-account-generator
83[OPTIONS] <accounts_file.yaml> -h``.
84"""
sslypushenko0de7d052015-04-16 18:49:55 +030085import argparse
David Kranz0aa4a7b2015-06-08 13:25:41 -040086import netaddr
sslypushenko0de7d052015-04-16 18:49:55 +030087import os
88
89from oslo_log import log as logging
90import yaml
91
Ken'ichi Ohmichi6ea3f982015-11-09 12:41:13 +000092from tempest.common import identity
sslypushenko0de7d052015-04-16 18:49:55 +030093from tempest import config
Matthew Treinish36c2e282015-08-25 00:30:15 -040094from tempest import exceptions as exc
sslypushenko0de7d052015-04-16 18:49:55 +030095from tempest.services.identity.v2.json import identity_client
Daniel Mellado6b16b922015-12-07 12:43:08 +000096from tempest.services.identity.v2.json import roles_client
Daniel Melladob04da902015-11-20 17:43:12 +010097from tempest.services.identity.v2.json import tenants_client
David Kranz0aa4a7b2015-06-08 13:25:41 -040098from tempest.services.network.json import network_client
John Warren94d8faf2015-09-15 12:22:24 -040099from tempest.services.network.json import networks_client
John Warren3961acd2015-10-02 14:38:53 -0400100from tempest.services.network.json import subnets_client
sslypushenko0de7d052015-04-16 18:49:55 +0300101import tempest_lib.auth
102from tempest_lib.common.utils import data_utils
103import tempest_lib.exceptions
104
105LOG = None
106CONF = config.CONF
107
108
109def setup_logging():
110 global LOG
111 logging.setup(CONF, __name__)
112 LOG = logging.getLogger(__name__)
113
114
David Kranz0aa4a7b2015-06-08 13:25:41 -0400115def get_admin_clients(opts):
sslypushenko0de7d052015-04-16 18:49:55 +0300116 _creds = tempest_lib.auth.KeystoneV2Credentials(
117 username=opts.os_username,
118 password=opts.os_password,
119 tenant_name=opts.os_tenant_name)
120 auth_params = {
121 'disable_ssl_certificate_validation':
122 CONF.identity.disable_ssl_certificate_validation,
123 'ca_certs': CONF.identity.ca_certificates_file,
124 'trace_requests': CONF.debug.trace_requests
125 }
126 _auth = tempest_lib.auth.KeystoneV2AuthProvider(
127 _creds, CONF.identity.uri, **auth_params)
128 params = {
129 'disable_ssl_certificate_validation':
130 CONF.identity.disable_ssl_certificate_validation,
131 'ca_certs': CONF.identity.ca_certificates_file,
132 'trace_requests': CONF.debug.trace_requests,
133 'build_interval': CONF.compute.build_interval,
134 'build_timeout': CONF.compute.build_timeout
135 }
Ken'ichi Ohmichia6287072015-07-02 02:43:15 +0000136 identity_admin = identity_client.IdentityClient(
sslypushenko0de7d052015-04-16 18:49:55 +0300137 _auth,
138 CONF.identity.catalog_type,
139 CONF.identity.region,
140 endpoint_type='adminURL',
141 **params
142 )
Daniel Melladob04da902015-11-20 17:43:12 +0100143 tenants_admin = tenants_client.TenantsClient(
144 _auth,
145 CONF.identity.catalog_type,
146 CONF.identity.region,
147 endpoint_type='adminURL',
148 **params
149 )
Daniel Mellado6b16b922015-12-07 12:43:08 +0000150 roles_admin = roles_client.RolesClient(
151 _auth,
152 CONF.identity.catalog_type,
153 CONF.identity.region,
154 endpoint_type='adminURL',
155 **params
156 )
David Kranz0aa4a7b2015-06-08 13:25:41 -0400157 network_admin = None
John Warren94d8faf2015-09-15 12:22:24 -0400158 networks_admin = None
John Warren3961acd2015-10-02 14:38:53 -0400159 subnets_admin = None
John Warren94d8faf2015-09-15 12:22:24 -0400160 neutron_iso_networks = False
David Kranz0aa4a7b2015-06-08 13:25:41 -0400161 if (CONF.service_available.neutron and
162 CONF.auth.create_isolated_networks):
John Warren94d8faf2015-09-15 12:22:24 -0400163 neutron_iso_networks = True
Ken'ichi Ohmichia6287072015-07-02 02:43:15 +0000164 network_admin = network_client.NetworkClient(
David Kranz0aa4a7b2015-06-08 13:25:41 -0400165 _auth,
166 CONF.network.catalog_type,
167 CONF.network.region or CONF.identity.region,
168 endpoint_type='adminURL',
169 **params)
John Warren94d8faf2015-09-15 12:22:24 -0400170 networks_admin = networks_client.NetworksClient(
171 _auth,
172 CONF.network.catalog_type,
173 CONF.network.region or CONF.identity.region,
174 endpoint_type='adminURL',
175 **params)
John Warren3961acd2015-10-02 14:38:53 -0400176 subnets_admin = subnets_client.SubnetsClient(
177 _auth,
178 CONF.network.catalog_type,
179 CONF.network.region or CONF.identity.region,
180 endpoint_type='adminURL',
181 **params)
Daniel Mellado6b16b922015-12-07 12:43:08 +0000182 return (identity_admin, tenants_admin, roles_admin, neutron_iso_networks,
183 network_admin, networks_admin, subnets_admin)
sslypushenko0de7d052015-04-16 18:49:55 +0300184
185
186def create_resources(opts, resources):
Daniel Mellado6b16b922015-12-07 12:43:08 +0000187 (identity_admin, tenants_admin, roles_admin, neutron_iso_networks,
John Warren3961acd2015-10-02 14:38:53 -0400188 network_admin, networks_admin, subnets_admin) = get_admin_clients(opts)
Daniel Mellado6b16b922015-12-07 12:43:08 +0000189 roles = roles_admin.list_roles()['roles']
sslypushenko0de7d052015-04-16 18:49:55 +0300190 for u in resources['users']:
191 u['role_ids'] = []
192 for r in u.get('roles', ()):
193 try:
194 role = filter(lambda r_: r_['name'] == r, roles)[0]
sslypushenko0de7d052015-04-16 18:49:55 +0300195 except IndexError:
Matthew Treinish36c2e282015-08-25 00:30:15 -0400196 msg = "Role: %s doesn't exist" % r
197 raise exc.InvalidConfiguration(msg)
198 u['role_ids'] += [role['id']]
Daniel Melladob04da902015-11-20 17:43:12 +0100199 existing = [x['name'] for x in tenants_admin.list_tenants()['tenants']]
sslypushenko0de7d052015-04-16 18:49:55 +0300200 for tenant in resources['tenants']:
201 if tenant not in existing:
Daniel Melladob04da902015-11-20 17:43:12 +0100202 tenants_admin.create_tenant(tenant)
sslypushenko0de7d052015-04-16 18:49:55 +0300203 else:
204 LOG.warn("Tenant '%s' already exists in this environment" % tenant)
205 LOG.info('Tenants created')
206 for u in resources['users']:
207 try:
Daniel Melladob04da902015-11-20 17:43:12 +0100208 tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
sslypushenko0de7d052015-04-16 18:49:55 +0300209 except tempest_lib.exceptions.NotFound:
210 LOG.error("Tenant: %s - not found" % u['tenant'])
211 continue
212 while True:
213 try:
Daniel Melladob04da902015-11-20 17:43:12 +0100214 identity.get_user_by_username(tenants_admin,
Ken'ichi Ohmichid9fed312015-11-09 13:05:32 +0000215 tenant['id'], u['name'])
sslypushenko0de7d052015-04-16 18:49:55 +0300216 except tempest_lib.exceptions.NotFound:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400217 identity_admin.create_user(
sslypushenko0de7d052015-04-16 18:49:55 +0300218 u['name'], u['pass'], tenant['id'],
219 "%s@%s" % (u['name'], tenant['id']),
220 enabled=True)
221 break
222 else:
223 LOG.warn("User '%s' already exists in this environment. "
224 "New name generated" % u['name'])
225 u['name'] = random_user_name(opts.tag, u['prefix'])
226
227 LOG.info('Users created')
John Warren94d8faf2015-09-15 12:22:24 -0400228 if neutron_iso_networks:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400229 for u in resources['users']:
Daniel Melladob04da902015-11-20 17:43:12 +0100230 tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
John Warren94d8faf2015-09-15 12:22:24 -0400231 network_name, router_name = create_network_resources(
John Warren3961acd2015-10-02 14:38:53 -0400232 network_admin, networks_admin, subnets_admin, tenant['id'],
233 u['name'])
David Kranz0aa4a7b2015-06-08 13:25:41 -0400234 u['network'] = network_name
David Paterson15be99e2015-04-08 21:58:19 -0400235 u['router'] = router_name
David Kranz0aa4a7b2015-06-08 13:25:41 -0400236 LOG.info('Networks created')
sslypushenko0de7d052015-04-16 18:49:55 +0300237 for u in resources['users']:
238 try:
Daniel Melladob04da902015-11-20 17:43:12 +0100239 tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
sslypushenko0de7d052015-04-16 18:49:55 +0300240 except tempest_lib.exceptions.NotFound:
241 LOG.error("Tenant: %s - not found" % u['tenant'])
242 continue
243 try:
Daniel Melladob04da902015-11-20 17:43:12 +0100244 user = identity.get_user_by_username(tenants_admin,
Ken'ichi Ohmichid9fed312015-11-09 13:05:32 +0000245 tenant['id'], u['name'])
sslypushenko0de7d052015-04-16 18:49:55 +0300246 except tempest_lib.exceptions.NotFound:
247 LOG.error("User: %s - not found" % u['user'])
248 continue
249 for r in u['role_ids']:
250 try:
Daniel Mellado6b16b922015-12-07 12:43:08 +0000251 roles_admin.assign_user_role(tenant['id'], user['id'], r)
sslypushenko0de7d052015-04-16 18:49:55 +0300252 except tempest_lib.exceptions.Conflict:
253 # don't care if it's already assigned
254 pass
255 LOG.info('Roles assigned')
256 LOG.info('Resources deployed successfully!')
257
258
John Warren94d8faf2015-09-15 12:22:24 -0400259def create_network_resources(network_admin_client, networks_admin_client,
John Warren3961acd2015-10-02 14:38:53 -0400260 subnets_admin_client, tenant_id, name):
David Kranz0aa4a7b2015-06-08 13:25:41 -0400261
262 def _create_network(name):
John Warren94d8faf2015-09-15 12:22:24 -0400263 resp_body = networks_admin_client.create_network(
David Kranz0aa4a7b2015-06-08 13:25:41 -0400264 name=name, tenant_id=tenant_id)
265 return resp_body['network']
266
267 def _create_subnet(subnet_name, network_id):
268 base_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
269 mask_bits = CONF.network.tenant_network_mask_bits
270 for subnet_cidr in base_cidr.subnet(mask_bits):
271 try:
John Warren3961acd2015-10-02 14:38:53 -0400272 resp_body = subnets_admin_client.\
David Kranz0aa4a7b2015-06-08 13:25:41 -0400273 create_subnet(
274 network_id=network_id, cidr=str(subnet_cidr),
275 name=subnet_name,
276 tenant_id=tenant_id,
277 enable_dhcp=True,
278 ip_version=4)
279 break
280 except tempest_lib.exceptions.BadRequest as e:
281 if 'overlaps with another subnet' not in str(e):
282 raise
283 else:
284 message = 'Available CIDR for subnet creation could not be found'
285 raise Exception(message)
286 return resp_body['subnet']
287
288 def _create_router(router_name):
289 external_net_id = dict(
290 network_id=CONF.network.public_network_id)
291 resp_body = network_admin_client.create_router(
292 router_name,
293 external_gateway_info=external_net_id,
294 tenant_id=tenant_id)
295 return resp_body['router']
296
297 def _add_router_interface(router_id, subnet_id):
298 network_admin_client.add_router_interface_with_subnet_id(
299 router_id, subnet_id)
300
301 network_name = name + "-network"
302 network = _create_network(network_name)
303 subnet_name = name + "-subnet"
304 subnet = _create_subnet(subnet_name, network['id'])
305 router_name = name + "-router"
306 router = _create_router(router_name)
307 _add_router_interface(router['id'], subnet['id'])
David Paterson15be99e2015-04-08 21:58:19 -0400308 return network_name, router_name
David Kranz0aa4a7b2015-06-08 13:25:41 -0400309
310
sslypushenko0de7d052015-04-16 18:49:55 +0300311def random_user_name(tag, prefix):
312 if tag:
313 return data_utils.rand_name('-'.join((tag, prefix)))
314 else:
315 return data_utils.rand_name(prefix)
316
317
318def generate_resources(opts):
319 spec = [{'number': 1,
320 'prefix': 'primary',
321 'roles': (CONF.auth.tempest_roles +
322 [CONF.object_storage.operator_role])},
323 {'number': 1,
324 'prefix': 'alt',
325 'roles': (CONF.auth.tempest_roles +
Matthew Treinish36c2e282015-08-25 00:30:15 -0400326 [CONF.object_storage.operator_role])}]
327 if CONF.service_available.swift:
328 spec.append({'number': 1,
Matthew Treinish7b05b342015-09-09 17:14:02 -0400329 'prefix': 'swift_operator',
Matthew Treinish36c2e282015-08-25 00:30:15 -0400330 'roles': (CONF.auth.tempest_roles +
Matthew Treinish7b05b342015-09-09 17:14:02 -0400331 [CONF.object_storage.operator_role])})
332 spec.append({'number': 1,
333 'prefix': 'swift_reseller_admin',
334 'roles': (CONF.auth.tempest_roles +
335 [CONF.object_storage.reseller_admin_role])})
Matthew Treinish36c2e282015-08-25 00:30:15 -0400336 if CONF.service_available.heat:
337 spec.append({'number': 1,
338 'prefix': 'stack_owner',
339 'roles': (CONF.auth.tempest_roles +
340 [CONF.orchestration.stack_owner_role])})
sslypushenko0de7d052015-04-16 18:49:55 +0300341 if opts.admin:
342 spec.append({
343 'number': 1,
344 'prefix': 'admin',
345 'roles': (CONF.auth.tempest_roles +
346 [CONF.identity.admin_role])
347 })
348 resources = {'tenants': [],
349 'users': []}
350 for count in range(opts.concurrency):
351 for user_group in spec:
352 users = [random_user_name(opts.tag, user_group['prefix'])
353 for _ in range(user_group['number'])]
354 for user in users:
355 tenant = '-'.join((user, 'tenant'))
356 resources['tenants'].append(tenant)
357 resources['users'].append({
358 'tenant': tenant,
359 'name': user,
Marc Koderer808e8ec2015-12-16 15:38:46 +0100360 'pass': data_utils.rand_password(),
sslypushenko0de7d052015-04-16 18:49:55 +0300361 'prefix': user_group['prefix'],
362 'roles': user_group['roles']
363 })
364 return resources
365
366
367def dump_accounts(opts, resources):
368 accounts = []
369 for user in resources['users']:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400370 account = {
sslypushenko0de7d052015-04-16 18:49:55 +0300371 'username': user['name'],
372 'tenant_name': user['tenant'],
373 'password': user['pass'],
374 'roles': user['roles']
David Kranz0aa4a7b2015-06-08 13:25:41 -0400375 }
David Paterson15be99e2015-04-08 21:58:19 -0400376 if 'network' or 'router' in user:
377 account['resources'] = {}
David Kranz0aa4a7b2015-06-08 13:25:41 -0400378 if 'network' in user:
David Paterson15be99e2015-04-08 21:58:19 -0400379 account['resources']['network'] = user['network']
380 if 'router' in user:
381 account['resources']['router'] = user['router']
David Kranz0aa4a7b2015-06-08 13:25:41 -0400382 accounts.append(account)
sslypushenko0de7d052015-04-16 18:49:55 +0300383 if os.path.exists(opts.accounts):
384 os.rename(opts.accounts, '.'.join((opts.accounts, 'bak')))
385 with open(opts.accounts, 'w') as f:
386 yaml.dump(accounts, f, default_flow_style=False)
387 LOG.info('%s generated successfully!' % opts.accounts)
388
389
390def get_options():
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300391 usage_string = ('tempest-account-generator [-h] <ARG> ...\n\n'
sslypushenko0de7d052015-04-16 18:49:55 +0300392 'To see help on specific argument, do:\n'
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300393 'tempest-account-generator <ARG> -h')
sslypushenko0de7d052015-04-16 18:49:55 +0300394 parser = argparse.ArgumentParser(
395 description='Create accounts.yaml file for concurrent test runs. '
396 'One primary user, one alt user, '
397 'one swift admin, one stack owner '
398 'and one admin (optionally) will be created '
399 'for each concurrent thread.',
400 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
401 usage=usage_string
402 )
403
404 parser.add_argument('-c', '--config-file',
405 metavar='/etc/tempest.conf',
406 help='path to tempest config file')
407 parser.add_argument('--os-username',
408 metavar='<auth-user-name>',
409 default=os.environ.get('OS_USERNAME'),
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300410 help='User should have permissions '
sslypushenko0de7d052015-04-16 18:49:55 +0300411 'to create new user accounts and '
412 'tenants. Defaults to env[OS_USERNAME].')
413 parser.add_argument('--os-password',
414 metavar='<auth-password>',
415 default=os.environ.get('OS_PASSWORD'),
416 help='Defaults to env[OS_PASSWORD].')
417 parser.add_argument('--os-tenant-name',
418 metavar='<auth-tenant-name>',
419 default=os.environ.get('OS_TENANT_NAME'),
420 help='Defaults to env[OS_TENANT_NAME].')
421 parser.add_argument('--tag',
422 default='',
423 required=False,
424 dest='tag',
425 help='Resources tag')
426 parser.add_argument('-r', '--concurrency',
427 default=1,
428 type=int,
429 required=True,
430 dest='concurrency',
431 help='Concurrency count')
432 parser.add_argument('--with-admin',
433 action='store_true',
434 dest='admin',
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300435 help='Creates admin for each concurrent group')
sslypushenko0de7d052015-04-16 18:49:55 +0300436 parser.add_argument('accounts',
437 metavar='accounts_file.yaml',
438 help='Output accounts yaml file')
439
440 opts = parser.parse_args()
441 if opts.config_file:
442 config.CONF.set_config_path(opts.config_file)
443 return opts
444
445
446def main(opts=None):
447 if not opts:
448 opts = get_options()
449 setup_logging()
450 resources = generate_resources(opts)
451 create_resources(opts, resources)
452 dump_accounts(opts, resources)
453
454if __name__ == "__main__":
455 main()