blob: 64c9b005c473d27c974d428cbf3c31897a7d576f [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 Melladob04da902015-11-20 17:43:12 +010096from tempest.services.identity.v2.json import tenants_client
David Kranz0aa4a7b2015-06-08 13:25:41 -040097from tempest.services.network.json import network_client
John Warren94d8faf2015-09-15 12:22:24 -040098from tempest.services.network.json import networks_client
John Warren3961acd2015-10-02 14:38:53 -040099from tempest.services.network.json import subnets_client
sslypushenko0de7d052015-04-16 18:49:55 +0300100import tempest_lib.auth
101from tempest_lib.common.utils import data_utils
102import tempest_lib.exceptions
103
104LOG = None
105CONF = config.CONF
106
107
108def setup_logging():
109 global LOG
110 logging.setup(CONF, __name__)
111 LOG = logging.getLogger(__name__)
112
113
David Kranz0aa4a7b2015-06-08 13:25:41 -0400114def get_admin_clients(opts):
sslypushenko0de7d052015-04-16 18:49:55 +0300115 _creds = tempest_lib.auth.KeystoneV2Credentials(
116 username=opts.os_username,
117 password=opts.os_password,
118 tenant_name=opts.os_tenant_name)
119 auth_params = {
120 'disable_ssl_certificate_validation':
121 CONF.identity.disable_ssl_certificate_validation,
122 'ca_certs': CONF.identity.ca_certificates_file,
123 'trace_requests': CONF.debug.trace_requests
124 }
125 _auth = tempest_lib.auth.KeystoneV2AuthProvider(
126 _creds, CONF.identity.uri, **auth_params)
127 params = {
128 'disable_ssl_certificate_validation':
129 CONF.identity.disable_ssl_certificate_validation,
130 'ca_certs': CONF.identity.ca_certificates_file,
131 'trace_requests': CONF.debug.trace_requests,
132 'build_interval': CONF.compute.build_interval,
133 'build_timeout': CONF.compute.build_timeout
134 }
Ken'ichi Ohmichia6287072015-07-02 02:43:15 +0000135 identity_admin = identity_client.IdentityClient(
sslypushenko0de7d052015-04-16 18:49:55 +0300136 _auth,
137 CONF.identity.catalog_type,
138 CONF.identity.region,
139 endpoint_type='adminURL',
140 **params
141 )
Daniel Melladob04da902015-11-20 17:43:12 +0100142 tenants_admin = tenants_client.TenantsClient(
143 _auth,
144 CONF.identity.catalog_type,
145 CONF.identity.region,
146 endpoint_type='adminURL',
147 **params
148 )
David Kranz0aa4a7b2015-06-08 13:25:41 -0400149 network_admin = None
John Warren94d8faf2015-09-15 12:22:24 -0400150 networks_admin = None
John Warren3961acd2015-10-02 14:38:53 -0400151 subnets_admin = None
John Warren94d8faf2015-09-15 12:22:24 -0400152 neutron_iso_networks = False
David Kranz0aa4a7b2015-06-08 13:25:41 -0400153 if (CONF.service_available.neutron and
154 CONF.auth.create_isolated_networks):
John Warren94d8faf2015-09-15 12:22:24 -0400155 neutron_iso_networks = True
Ken'ichi Ohmichia6287072015-07-02 02:43:15 +0000156 network_admin = network_client.NetworkClient(
David Kranz0aa4a7b2015-06-08 13:25:41 -0400157 _auth,
158 CONF.network.catalog_type,
159 CONF.network.region or CONF.identity.region,
160 endpoint_type='adminURL',
161 **params)
John Warren94d8faf2015-09-15 12:22:24 -0400162 networks_admin = networks_client.NetworksClient(
163 _auth,
164 CONF.network.catalog_type,
165 CONF.network.region or CONF.identity.region,
166 endpoint_type='adminURL',
167 **params)
John Warren3961acd2015-10-02 14:38:53 -0400168 subnets_admin = subnets_client.SubnetsClient(
169 _auth,
170 CONF.network.catalog_type,
171 CONF.network.region or CONF.identity.region,
172 endpoint_type='adminURL',
173 **params)
Daniel Melladob04da902015-11-20 17:43:12 +0100174 return (identity_admin, tenants_admin, neutron_iso_networks, network_admin,
John Warren3961acd2015-10-02 14:38:53 -0400175 networks_admin, subnets_admin)
sslypushenko0de7d052015-04-16 18:49:55 +0300176
177
178def create_resources(opts, resources):
Daniel Melladob04da902015-11-20 17:43:12 +0100179 (identity_admin, tenants_admin, neutron_iso_networks,
John Warren3961acd2015-10-02 14:38:53 -0400180 network_admin, networks_admin, subnets_admin) = get_admin_clients(opts)
John Warrene61a94f2015-09-28 13:51:51 -0400181 roles = identity_admin.list_roles()['roles']
sslypushenko0de7d052015-04-16 18:49:55 +0300182 for u in resources['users']:
183 u['role_ids'] = []
184 for r in u.get('roles', ()):
185 try:
186 role = filter(lambda r_: r_['name'] == r, roles)[0]
sslypushenko0de7d052015-04-16 18:49:55 +0300187 except IndexError:
Matthew Treinish36c2e282015-08-25 00:30:15 -0400188 msg = "Role: %s doesn't exist" % r
189 raise exc.InvalidConfiguration(msg)
190 u['role_ids'] += [role['id']]
Daniel Melladob04da902015-11-20 17:43:12 +0100191 existing = [x['name'] for x in tenants_admin.list_tenants()['tenants']]
sslypushenko0de7d052015-04-16 18:49:55 +0300192 for tenant in resources['tenants']:
193 if tenant not in existing:
Daniel Melladob04da902015-11-20 17:43:12 +0100194 tenants_admin.create_tenant(tenant)
sslypushenko0de7d052015-04-16 18:49:55 +0300195 else:
196 LOG.warn("Tenant '%s' already exists in this environment" % tenant)
197 LOG.info('Tenants created')
198 for u in resources['users']:
199 try:
Daniel Melladob04da902015-11-20 17:43:12 +0100200 tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
sslypushenko0de7d052015-04-16 18:49:55 +0300201 except tempest_lib.exceptions.NotFound:
202 LOG.error("Tenant: %s - not found" % u['tenant'])
203 continue
204 while True:
205 try:
Daniel Melladob04da902015-11-20 17:43:12 +0100206 identity.get_user_by_username(tenants_admin,
Ken'ichi Ohmichid9fed312015-11-09 13:05:32 +0000207 tenant['id'], u['name'])
sslypushenko0de7d052015-04-16 18:49:55 +0300208 except tempest_lib.exceptions.NotFound:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400209 identity_admin.create_user(
sslypushenko0de7d052015-04-16 18:49:55 +0300210 u['name'], u['pass'], tenant['id'],
211 "%s@%s" % (u['name'], tenant['id']),
212 enabled=True)
213 break
214 else:
215 LOG.warn("User '%s' already exists in this environment. "
216 "New name generated" % u['name'])
217 u['name'] = random_user_name(opts.tag, u['prefix'])
218
219 LOG.info('Users created')
John Warren94d8faf2015-09-15 12:22:24 -0400220 if neutron_iso_networks:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400221 for u in resources['users']:
Daniel Melladob04da902015-11-20 17:43:12 +0100222 tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
John Warren94d8faf2015-09-15 12:22:24 -0400223 network_name, router_name = create_network_resources(
John Warren3961acd2015-10-02 14:38:53 -0400224 network_admin, networks_admin, subnets_admin, tenant['id'],
225 u['name'])
David Kranz0aa4a7b2015-06-08 13:25:41 -0400226 u['network'] = network_name
David Paterson15be99e2015-04-08 21:58:19 -0400227 u['router'] = router_name
David Kranz0aa4a7b2015-06-08 13:25:41 -0400228 LOG.info('Networks created')
sslypushenko0de7d052015-04-16 18:49:55 +0300229 for u in resources['users']:
230 try:
Daniel Melladob04da902015-11-20 17:43:12 +0100231 tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
sslypushenko0de7d052015-04-16 18:49:55 +0300232 except tempest_lib.exceptions.NotFound:
233 LOG.error("Tenant: %s - not found" % u['tenant'])
234 continue
235 try:
Daniel Melladob04da902015-11-20 17:43:12 +0100236 user = identity.get_user_by_username(tenants_admin,
Ken'ichi Ohmichid9fed312015-11-09 13:05:32 +0000237 tenant['id'], u['name'])
sslypushenko0de7d052015-04-16 18:49:55 +0300238 except tempest_lib.exceptions.NotFound:
239 LOG.error("User: %s - not found" % u['user'])
240 continue
241 for r in u['role_ids']:
242 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400243 identity_admin.assign_user_role(tenant['id'], user['id'], r)
sslypushenko0de7d052015-04-16 18:49:55 +0300244 except tempest_lib.exceptions.Conflict:
245 # don't care if it's already assigned
246 pass
247 LOG.info('Roles assigned')
248 LOG.info('Resources deployed successfully!')
249
250
John Warren94d8faf2015-09-15 12:22:24 -0400251def create_network_resources(network_admin_client, networks_admin_client,
John Warren3961acd2015-10-02 14:38:53 -0400252 subnets_admin_client, tenant_id, name):
David Kranz0aa4a7b2015-06-08 13:25:41 -0400253
254 def _create_network(name):
John Warren94d8faf2015-09-15 12:22:24 -0400255 resp_body = networks_admin_client.create_network(
David Kranz0aa4a7b2015-06-08 13:25:41 -0400256 name=name, tenant_id=tenant_id)
257 return resp_body['network']
258
259 def _create_subnet(subnet_name, network_id):
260 base_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
261 mask_bits = CONF.network.tenant_network_mask_bits
262 for subnet_cidr in base_cidr.subnet(mask_bits):
263 try:
John Warren3961acd2015-10-02 14:38:53 -0400264 resp_body = subnets_admin_client.\
David Kranz0aa4a7b2015-06-08 13:25:41 -0400265 create_subnet(
266 network_id=network_id, cidr=str(subnet_cidr),
267 name=subnet_name,
268 tenant_id=tenant_id,
269 enable_dhcp=True,
270 ip_version=4)
271 break
272 except tempest_lib.exceptions.BadRequest as e:
273 if 'overlaps with another subnet' not in str(e):
274 raise
275 else:
276 message = 'Available CIDR for subnet creation could not be found'
277 raise Exception(message)
278 return resp_body['subnet']
279
280 def _create_router(router_name):
281 external_net_id = dict(
282 network_id=CONF.network.public_network_id)
283 resp_body = network_admin_client.create_router(
284 router_name,
285 external_gateway_info=external_net_id,
286 tenant_id=tenant_id)
287 return resp_body['router']
288
289 def _add_router_interface(router_id, subnet_id):
290 network_admin_client.add_router_interface_with_subnet_id(
291 router_id, subnet_id)
292
293 network_name = name + "-network"
294 network = _create_network(network_name)
295 subnet_name = name + "-subnet"
296 subnet = _create_subnet(subnet_name, network['id'])
297 router_name = name + "-router"
298 router = _create_router(router_name)
299 _add_router_interface(router['id'], subnet['id'])
David Paterson15be99e2015-04-08 21:58:19 -0400300 return network_name, router_name
David Kranz0aa4a7b2015-06-08 13:25:41 -0400301
302
sslypushenko0de7d052015-04-16 18:49:55 +0300303def random_user_name(tag, prefix):
304 if tag:
305 return data_utils.rand_name('-'.join((tag, prefix)))
306 else:
307 return data_utils.rand_name(prefix)
308
309
310def generate_resources(opts):
311 spec = [{'number': 1,
312 'prefix': 'primary',
313 'roles': (CONF.auth.tempest_roles +
314 [CONF.object_storage.operator_role])},
315 {'number': 1,
316 'prefix': 'alt',
317 'roles': (CONF.auth.tempest_roles +
Matthew Treinish36c2e282015-08-25 00:30:15 -0400318 [CONF.object_storage.operator_role])}]
319 if CONF.service_available.swift:
320 spec.append({'number': 1,
Matthew Treinish7b05b342015-09-09 17:14:02 -0400321 'prefix': 'swift_operator',
Matthew Treinish36c2e282015-08-25 00:30:15 -0400322 'roles': (CONF.auth.tempest_roles +
Matthew Treinish7b05b342015-09-09 17:14:02 -0400323 [CONF.object_storage.operator_role])})
324 spec.append({'number': 1,
325 'prefix': 'swift_reseller_admin',
326 'roles': (CONF.auth.tempest_roles +
327 [CONF.object_storage.reseller_admin_role])})
Matthew Treinish36c2e282015-08-25 00:30:15 -0400328 if CONF.service_available.heat:
329 spec.append({'number': 1,
330 'prefix': 'stack_owner',
331 'roles': (CONF.auth.tempest_roles +
332 [CONF.orchestration.stack_owner_role])})
sslypushenko0de7d052015-04-16 18:49:55 +0300333 if opts.admin:
334 spec.append({
335 'number': 1,
336 'prefix': 'admin',
337 'roles': (CONF.auth.tempest_roles +
338 [CONF.identity.admin_role])
339 })
340 resources = {'tenants': [],
341 'users': []}
342 for count in range(opts.concurrency):
343 for user_group in spec:
344 users = [random_user_name(opts.tag, user_group['prefix'])
345 for _ in range(user_group['number'])]
346 for user in users:
347 tenant = '-'.join((user, 'tenant'))
348 resources['tenants'].append(tenant)
349 resources['users'].append({
350 'tenant': tenant,
351 'name': user,
352 'pass': data_utils.rand_name(),
353 'prefix': user_group['prefix'],
354 'roles': user_group['roles']
355 })
356 return resources
357
358
359def dump_accounts(opts, resources):
360 accounts = []
361 for user in resources['users']:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400362 account = {
sslypushenko0de7d052015-04-16 18:49:55 +0300363 'username': user['name'],
364 'tenant_name': user['tenant'],
365 'password': user['pass'],
366 'roles': user['roles']
David Kranz0aa4a7b2015-06-08 13:25:41 -0400367 }
David Paterson15be99e2015-04-08 21:58:19 -0400368 if 'network' or 'router' in user:
369 account['resources'] = {}
David Kranz0aa4a7b2015-06-08 13:25:41 -0400370 if 'network' in user:
David Paterson15be99e2015-04-08 21:58:19 -0400371 account['resources']['network'] = user['network']
372 if 'router' in user:
373 account['resources']['router'] = user['router']
David Kranz0aa4a7b2015-06-08 13:25:41 -0400374 accounts.append(account)
sslypushenko0de7d052015-04-16 18:49:55 +0300375 if os.path.exists(opts.accounts):
376 os.rename(opts.accounts, '.'.join((opts.accounts, 'bak')))
377 with open(opts.accounts, 'w') as f:
378 yaml.dump(accounts, f, default_flow_style=False)
379 LOG.info('%s generated successfully!' % opts.accounts)
380
381
382def get_options():
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300383 usage_string = ('tempest-account-generator [-h] <ARG> ...\n\n'
sslypushenko0de7d052015-04-16 18:49:55 +0300384 'To see help on specific argument, do:\n'
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300385 'tempest-account-generator <ARG> -h')
sslypushenko0de7d052015-04-16 18:49:55 +0300386 parser = argparse.ArgumentParser(
387 description='Create accounts.yaml file for concurrent test runs. '
388 'One primary user, one alt user, '
389 'one swift admin, one stack owner '
390 'and one admin (optionally) will be created '
391 'for each concurrent thread.',
392 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
393 usage=usage_string
394 )
395
396 parser.add_argument('-c', '--config-file',
397 metavar='/etc/tempest.conf',
398 help='path to tempest config file')
399 parser.add_argument('--os-username',
400 metavar='<auth-user-name>',
401 default=os.environ.get('OS_USERNAME'),
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300402 help='User should have permissions '
sslypushenko0de7d052015-04-16 18:49:55 +0300403 'to create new user accounts and '
404 'tenants. Defaults to env[OS_USERNAME].')
405 parser.add_argument('--os-password',
406 metavar='<auth-password>',
407 default=os.environ.get('OS_PASSWORD'),
408 help='Defaults to env[OS_PASSWORD].')
409 parser.add_argument('--os-tenant-name',
410 metavar='<auth-tenant-name>',
411 default=os.environ.get('OS_TENANT_NAME'),
412 help='Defaults to env[OS_TENANT_NAME].')
413 parser.add_argument('--tag',
414 default='',
415 required=False,
416 dest='tag',
417 help='Resources tag')
418 parser.add_argument('-r', '--concurrency',
419 default=1,
420 type=int,
421 required=True,
422 dest='concurrency',
423 help='Concurrency count')
424 parser.add_argument('--with-admin',
425 action='store_true',
426 dest='admin',
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300427 help='Creates admin for each concurrent group')
sslypushenko0de7d052015-04-16 18:49:55 +0300428 parser.add_argument('accounts',
429 metavar='accounts_file.yaml',
430 help='Output accounts yaml file')
431
432 opts = parser.parse_args()
433 if opts.config_file:
434 config.CONF.set_config_path(opts.config_file)
435 return opts
436
437
438def main(opts=None):
439 if not opts:
440 opts = get_options()
441 setup_logging()
442 resources = generate_resources(opts)
443 create_resources(opts, resources)
444 dump_accounts(opts, resources)
445
446if __name__ == "__main__":
447 main()