blob: 5a2713b9d025cc31e970aa40c05c220113885b66 [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:
zhangguoqing6c096642016-01-04 06:17:21 +0000204 LOG.warning("Tenant '%s' already exists in this environment"
205 % tenant)
sslypushenko0de7d052015-04-16 18:49:55 +0300206 LOG.info('Tenants created')
207 for u in resources['users']:
208 try:
Daniel Melladob04da902015-11-20 17:43:12 +0100209 tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
sslypushenko0de7d052015-04-16 18:49:55 +0300210 except tempest_lib.exceptions.NotFound:
211 LOG.error("Tenant: %s - not found" % u['tenant'])
212 continue
213 while True:
214 try:
Daniel Melladob04da902015-11-20 17:43:12 +0100215 identity.get_user_by_username(tenants_admin,
Ken'ichi Ohmichid9fed312015-11-09 13:05:32 +0000216 tenant['id'], u['name'])
sslypushenko0de7d052015-04-16 18:49:55 +0300217 except tempest_lib.exceptions.NotFound:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400218 identity_admin.create_user(
sslypushenko0de7d052015-04-16 18:49:55 +0300219 u['name'], u['pass'], tenant['id'],
220 "%s@%s" % (u['name'], tenant['id']),
221 enabled=True)
222 break
223 else:
zhangguoqing6c096642016-01-04 06:17:21 +0000224 LOG.warning("User '%s' already exists in this environment. "
225 "New name generated" % u['name'])
sslypushenko0de7d052015-04-16 18:49:55 +0300226 u['name'] = random_user_name(opts.tag, u['prefix'])
227
228 LOG.info('Users created')
John Warren94d8faf2015-09-15 12:22:24 -0400229 if neutron_iso_networks:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400230 for u in resources['users']:
Daniel Melladob04da902015-11-20 17:43:12 +0100231 tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
John Warren94d8faf2015-09-15 12:22:24 -0400232 network_name, router_name = create_network_resources(
John Warren3961acd2015-10-02 14:38:53 -0400233 network_admin, networks_admin, subnets_admin, tenant['id'],
234 u['name'])
David Kranz0aa4a7b2015-06-08 13:25:41 -0400235 u['network'] = network_name
David Paterson15be99e2015-04-08 21:58:19 -0400236 u['router'] = router_name
David Kranz0aa4a7b2015-06-08 13:25:41 -0400237 LOG.info('Networks created')
sslypushenko0de7d052015-04-16 18:49:55 +0300238 for u in resources['users']:
239 try:
Daniel Melladob04da902015-11-20 17:43:12 +0100240 tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
sslypushenko0de7d052015-04-16 18:49:55 +0300241 except tempest_lib.exceptions.NotFound:
242 LOG.error("Tenant: %s - not found" % u['tenant'])
243 continue
244 try:
Daniel Melladob04da902015-11-20 17:43:12 +0100245 user = identity.get_user_by_username(tenants_admin,
Ken'ichi Ohmichid9fed312015-11-09 13:05:32 +0000246 tenant['id'], u['name'])
sslypushenko0de7d052015-04-16 18:49:55 +0300247 except tempest_lib.exceptions.NotFound:
248 LOG.error("User: %s - not found" % u['user'])
249 continue
250 for r in u['role_ids']:
251 try:
Daniel Mellado6b16b922015-12-07 12:43:08 +0000252 roles_admin.assign_user_role(tenant['id'], user['id'], r)
sslypushenko0de7d052015-04-16 18:49:55 +0300253 except tempest_lib.exceptions.Conflict:
254 # don't care if it's already assigned
255 pass
256 LOG.info('Roles assigned')
257 LOG.info('Resources deployed successfully!')
258
259
John Warren94d8faf2015-09-15 12:22:24 -0400260def create_network_resources(network_admin_client, networks_admin_client,
John Warren3961acd2015-10-02 14:38:53 -0400261 subnets_admin_client, tenant_id, name):
David Kranz0aa4a7b2015-06-08 13:25:41 -0400262
263 def _create_network(name):
John Warren94d8faf2015-09-15 12:22:24 -0400264 resp_body = networks_admin_client.create_network(
David Kranz0aa4a7b2015-06-08 13:25:41 -0400265 name=name, tenant_id=tenant_id)
266 return resp_body['network']
267
268 def _create_subnet(subnet_name, network_id):
269 base_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
270 mask_bits = CONF.network.tenant_network_mask_bits
271 for subnet_cidr in base_cidr.subnet(mask_bits):
272 try:
John Warren3961acd2015-10-02 14:38:53 -0400273 resp_body = subnets_admin_client.\
David Kranz0aa4a7b2015-06-08 13:25:41 -0400274 create_subnet(
275 network_id=network_id, cidr=str(subnet_cidr),
276 name=subnet_name,
277 tenant_id=tenant_id,
278 enable_dhcp=True,
279 ip_version=4)
280 break
281 except tempest_lib.exceptions.BadRequest as e:
282 if 'overlaps with another subnet' not in str(e):
283 raise
284 else:
285 message = 'Available CIDR for subnet creation could not be found'
286 raise Exception(message)
287 return resp_body['subnet']
288
289 def _create_router(router_name):
290 external_net_id = dict(
291 network_id=CONF.network.public_network_id)
292 resp_body = network_admin_client.create_router(
293 router_name,
294 external_gateway_info=external_net_id,
295 tenant_id=tenant_id)
296 return resp_body['router']
297
298 def _add_router_interface(router_id, subnet_id):
299 network_admin_client.add_router_interface_with_subnet_id(
300 router_id, subnet_id)
301
302 network_name = name + "-network"
303 network = _create_network(network_name)
304 subnet_name = name + "-subnet"
305 subnet = _create_subnet(subnet_name, network['id'])
306 router_name = name + "-router"
307 router = _create_router(router_name)
308 _add_router_interface(router['id'], subnet['id'])
David Paterson15be99e2015-04-08 21:58:19 -0400309 return network_name, router_name
David Kranz0aa4a7b2015-06-08 13:25:41 -0400310
311
sslypushenko0de7d052015-04-16 18:49:55 +0300312def random_user_name(tag, prefix):
313 if tag:
314 return data_utils.rand_name('-'.join((tag, prefix)))
315 else:
316 return data_utils.rand_name(prefix)
317
318
319def generate_resources(opts):
320 spec = [{'number': 1,
321 'prefix': 'primary',
322 'roles': (CONF.auth.tempest_roles +
323 [CONF.object_storage.operator_role])},
324 {'number': 1,
325 'prefix': 'alt',
326 'roles': (CONF.auth.tempest_roles +
Matthew Treinish36c2e282015-08-25 00:30:15 -0400327 [CONF.object_storage.operator_role])}]
328 if CONF.service_available.swift:
329 spec.append({'number': 1,
Matthew Treinish7b05b342015-09-09 17:14:02 -0400330 'prefix': 'swift_operator',
Matthew Treinish36c2e282015-08-25 00:30:15 -0400331 'roles': (CONF.auth.tempest_roles +
Matthew Treinish7b05b342015-09-09 17:14:02 -0400332 [CONF.object_storage.operator_role])})
333 spec.append({'number': 1,
334 'prefix': 'swift_reseller_admin',
335 'roles': (CONF.auth.tempest_roles +
336 [CONF.object_storage.reseller_admin_role])})
Matthew Treinish36c2e282015-08-25 00:30:15 -0400337 if CONF.service_available.heat:
338 spec.append({'number': 1,
339 'prefix': 'stack_owner',
340 'roles': (CONF.auth.tempest_roles +
341 [CONF.orchestration.stack_owner_role])})
sslypushenko0de7d052015-04-16 18:49:55 +0300342 if opts.admin:
343 spec.append({
344 'number': 1,
345 'prefix': 'admin',
346 'roles': (CONF.auth.tempest_roles +
347 [CONF.identity.admin_role])
348 })
349 resources = {'tenants': [],
350 'users': []}
351 for count in range(opts.concurrency):
352 for user_group in spec:
353 users = [random_user_name(opts.tag, user_group['prefix'])
354 for _ in range(user_group['number'])]
355 for user in users:
356 tenant = '-'.join((user, 'tenant'))
357 resources['tenants'].append(tenant)
358 resources['users'].append({
359 'tenant': tenant,
360 'name': user,
Marc Koderer808e8ec2015-12-16 15:38:46 +0100361 'pass': data_utils.rand_password(),
sslypushenko0de7d052015-04-16 18:49:55 +0300362 'prefix': user_group['prefix'],
363 'roles': user_group['roles']
364 })
365 return resources
366
367
368def dump_accounts(opts, resources):
369 accounts = []
370 for user in resources['users']:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400371 account = {
sslypushenko0de7d052015-04-16 18:49:55 +0300372 'username': user['name'],
373 'tenant_name': user['tenant'],
374 'password': user['pass'],
375 'roles': user['roles']
David Kranz0aa4a7b2015-06-08 13:25:41 -0400376 }
David Paterson15be99e2015-04-08 21:58:19 -0400377 if 'network' or 'router' in user:
378 account['resources'] = {}
David Kranz0aa4a7b2015-06-08 13:25:41 -0400379 if 'network' in user:
David Paterson15be99e2015-04-08 21:58:19 -0400380 account['resources']['network'] = user['network']
381 if 'router' in user:
382 account['resources']['router'] = user['router']
David Kranz0aa4a7b2015-06-08 13:25:41 -0400383 accounts.append(account)
sslypushenko0de7d052015-04-16 18:49:55 +0300384 if os.path.exists(opts.accounts):
385 os.rename(opts.accounts, '.'.join((opts.accounts, 'bak')))
386 with open(opts.accounts, 'w') as f:
387 yaml.dump(accounts, f, default_flow_style=False)
388 LOG.info('%s generated successfully!' % opts.accounts)
389
390
391def get_options():
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300392 usage_string = ('tempest-account-generator [-h] <ARG> ...\n\n'
sslypushenko0de7d052015-04-16 18:49:55 +0300393 'To see help on specific argument, do:\n'
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300394 'tempest-account-generator <ARG> -h')
sslypushenko0de7d052015-04-16 18:49:55 +0300395 parser = argparse.ArgumentParser(
396 description='Create accounts.yaml file for concurrent test runs. '
397 'One primary user, one alt user, '
398 'one swift admin, one stack owner '
399 'and one admin (optionally) will be created '
400 'for each concurrent thread.',
401 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
402 usage=usage_string
403 )
404
405 parser.add_argument('-c', '--config-file',
406 metavar='/etc/tempest.conf',
407 help='path to tempest config file')
408 parser.add_argument('--os-username',
409 metavar='<auth-user-name>',
410 default=os.environ.get('OS_USERNAME'),
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300411 help='User should have permissions '
sslypushenko0de7d052015-04-16 18:49:55 +0300412 'to create new user accounts and '
413 'tenants. Defaults to env[OS_USERNAME].')
414 parser.add_argument('--os-password',
415 metavar='<auth-password>',
416 default=os.environ.get('OS_PASSWORD'),
417 help='Defaults to env[OS_PASSWORD].')
418 parser.add_argument('--os-tenant-name',
419 metavar='<auth-tenant-name>',
420 default=os.environ.get('OS_TENANT_NAME'),
421 help='Defaults to env[OS_TENANT_NAME].')
422 parser.add_argument('--tag',
423 default='',
424 required=False,
425 dest='tag',
426 help='Resources tag')
427 parser.add_argument('-r', '--concurrency',
428 default=1,
429 type=int,
430 required=True,
431 dest='concurrency',
432 help='Concurrency count')
433 parser.add_argument('--with-admin',
434 action='store_true',
435 dest='admin',
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300436 help='Creates admin for each concurrent group')
sslypushenko0de7d052015-04-16 18:49:55 +0300437 parser.add_argument('accounts',
438 metavar='accounts_file.yaml',
439 help='Output accounts yaml file')
440
441 opts = parser.parse_args()
442 if opts.config_file:
443 config.CONF.set_config_path(opts.config_file)
444 return opts
445
446
447def main(opts=None):
448 if not opts:
449 opts = get_options()
450 setup_logging()
451 resources = generate_resources(opts)
452 create_resources(opts, resources)
453 dump_accounts(opts, resources)
454
455if __name__ == "__main__":
456 main()