blob: a90b0ce641a35f4ddddd377c303e55a8d311d8fb [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
92from tempest import config
Matthew Treinish36c2e282015-08-25 00:30:15 -040093from tempest import exceptions as exc
sslypushenko0de7d052015-04-16 18:49:55 +030094from tempest.services.identity.v2.json import identity_client
David Kranz0aa4a7b2015-06-08 13:25:41 -040095from tempest.services.network.json import network_client
John Warren94d8faf2015-09-15 12:22:24 -040096from tempest.services.network.json import networks_client
John Warren3961acd2015-10-02 14:38:53 -040097from tempest.services.network.json import subnets_client
sslypushenko0de7d052015-04-16 18:49:55 +030098import tempest_lib.auth
99from tempest_lib.common.utils import data_utils
100import tempest_lib.exceptions
101
102LOG = None
103CONF = config.CONF
104
105
106def setup_logging():
107 global LOG
108 logging.setup(CONF, __name__)
109 LOG = logging.getLogger(__name__)
110
111
David Kranz0aa4a7b2015-06-08 13:25:41 -0400112def get_admin_clients(opts):
sslypushenko0de7d052015-04-16 18:49:55 +0300113 _creds = tempest_lib.auth.KeystoneV2Credentials(
114 username=opts.os_username,
115 password=opts.os_password,
116 tenant_name=opts.os_tenant_name)
117 auth_params = {
118 'disable_ssl_certificate_validation':
119 CONF.identity.disable_ssl_certificate_validation,
120 'ca_certs': CONF.identity.ca_certificates_file,
121 'trace_requests': CONF.debug.trace_requests
122 }
123 _auth = tempest_lib.auth.KeystoneV2AuthProvider(
124 _creds, CONF.identity.uri, **auth_params)
125 params = {
126 'disable_ssl_certificate_validation':
127 CONF.identity.disable_ssl_certificate_validation,
128 'ca_certs': CONF.identity.ca_certificates_file,
129 'trace_requests': CONF.debug.trace_requests,
130 'build_interval': CONF.compute.build_interval,
131 'build_timeout': CONF.compute.build_timeout
132 }
Ken'ichi Ohmichia6287072015-07-02 02:43:15 +0000133 identity_admin = identity_client.IdentityClient(
sslypushenko0de7d052015-04-16 18:49:55 +0300134 _auth,
135 CONF.identity.catalog_type,
136 CONF.identity.region,
137 endpoint_type='adminURL',
138 **params
139 )
David Kranz0aa4a7b2015-06-08 13:25:41 -0400140 network_admin = None
John Warren94d8faf2015-09-15 12:22:24 -0400141 networks_admin = None
John Warren3961acd2015-10-02 14:38:53 -0400142 subnets_admin = None
John Warren94d8faf2015-09-15 12:22:24 -0400143 neutron_iso_networks = False
David Kranz0aa4a7b2015-06-08 13:25:41 -0400144 if (CONF.service_available.neutron and
145 CONF.auth.create_isolated_networks):
John Warren94d8faf2015-09-15 12:22:24 -0400146 neutron_iso_networks = True
Ken'ichi Ohmichia6287072015-07-02 02:43:15 +0000147 network_admin = network_client.NetworkClient(
David Kranz0aa4a7b2015-06-08 13:25:41 -0400148 _auth,
149 CONF.network.catalog_type,
150 CONF.network.region or CONF.identity.region,
151 endpoint_type='adminURL',
152 **params)
John Warren94d8faf2015-09-15 12:22:24 -0400153 networks_admin = networks_client.NetworksClient(
154 _auth,
155 CONF.network.catalog_type,
156 CONF.network.region or CONF.identity.region,
157 endpoint_type='adminURL',
158 **params)
John Warren3961acd2015-10-02 14:38:53 -0400159 subnets_admin = subnets_client.SubnetsClient(
160 _auth,
161 CONF.network.catalog_type,
162 CONF.network.region or CONF.identity.region,
163 endpoint_type='adminURL',
164 **params)
165 return (identity_admin, neutron_iso_networks, network_admin,
166 networks_admin, subnets_admin)
sslypushenko0de7d052015-04-16 18:49:55 +0300167
168
169def create_resources(opts, resources):
John Warren94d8faf2015-09-15 12:22:24 -0400170 (identity_admin, neutron_iso_networks,
John Warren3961acd2015-10-02 14:38:53 -0400171 network_admin, networks_admin, subnets_admin) = get_admin_clients(opts)
John Warrene61a94f2015-09-28 13:51:51 -0400172 roles = identity_admin.list_roles()['roles']
sslypushenko0de7d052015-04-16 18:49:55 +0300173 for u in resources['users']:
174 u['role_ids'] = []
175 for r in u.get('roles', ()):
176 try:
177 role = filter(lambda r_: r_['name'] == r, roles)[0]
sslypushenko0de7d052015-04-16 18:49:55 +0300178 except IndexError:
Matthew Treinish36c2e282015-08-25 00:30:15 -0400179 msg = "Role: %s doesn't exist" % r
180 raise exc.InvalidConfiguration(msg)
181 u['role_ids'] += [role['id']]
John Warrenacf00512015-08-06 16:13:58 -0400182 existing = [x['name'] for x in identity_admin.list_tenants()['tenants']]
sslypushenko0de7d052015-04-16 18:49:55 +0300183 for tenant in resources['tenants']:
184 if tenant not in existing:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400185 identity_admin.create_tenant(tenant)
sslypushenko0de7d052015-04-16 18:49:55 +0300186 else:
187 LOG.warn("Tenant '%s' already exists in this environment" % tenant)
188 LOG.info('Tenants created')
189 for u in resources['users']:
190 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400191 tenant = identity_admin.get_tenant_by_name(u['tenant'])
sslypushenko0de7d052015-04-16 18:49:55 +0300192 except tempest_lib.exceptions.NotFound:
193 LOG.error("Tenant: %s - not found" % u['tenant'])
194 continue
195 while True:
196 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400197 identity_admin.get_user_by_username(tenant['id'], u['name'])
sslypushenko0de7d052015-04-16 18:49:55 +0300198 except tempest_lib.exceptions.NotFound:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400199 identity_admin.create_user(
sslypushenko0de7d052015-04-16 18:49:55 +0300200 u['name'], u['pass'], tenant['id'],
201 "%s@%s" % (u['name'], tenant['id']),
202 enabled=True)
203 break
204 else:
205 LOG.warn("User '%s' already exists in this environment. "
206 "New name generated" % u['name'])
207 u['name'] = random_user_name(opts.tag, u['prefix'])
208
209 LOG.info('Users created')
John Warren94d8faf2015-09-15 12:22:24 -0400210 if neutron_iso_networks:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400211 for u in resources['users']:
212 tenant = identity_admin.get_tenant_by_name(u['tenant'])
John Warren94d8faf2015-09-15 12:22:24 -0400213 network_name, router_name = create_network_resources(
John Warren3961acd2015-10-02 14:38:53 -0400214 network_admin, networks_admin, subnets_admin, tenant['id'],
215 u['name'])
David Kranz0aa4a7b2015-06-08 13:25:41 -0400216 u['network'] = network_name
David Paterson15be99e2015-04-08 21:58:19 -0400217 u['router'] = router_name
David Kranz0aa4a7b2015-06-08 13:25:41 -0400218 LOG.info('Networks created')
sslypushenko0de7d052015-04-16 18:49:55 +0300219 for u in resources['users']:
220 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400221 tenant = identity_admin.get_tenant_by_name(u['tenant'])
sslypushenko0de7d052015-04-16 18:49:55 +0300222 except tempest_lib.exceptions.NotFound:
223 LOG.error("Tenant: %s - not found" % u['tenant'])
224 continue
225 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400226 user = identity_admin.get_user_by_username(tenant['id'],
227 u['name'])
sslypushenko0de7d052015-04-16 18:49:55 +0300228 except tempest_lib.exceptions.NotFound:
229 LOG.error("User: %s - not found" % u['user'])
230 continue
231 for r in u['role_ids']:
232 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400233 identity_admin.assign_user_role(tenant['id'], user['id'], r)
sslypushenko0de7d052015-04-16 18:49:55 +0300234 except tempest_lib.exceptions.Conflict:
235 # don't care if it's already assigned
236 pass
237 LOG.info('Roles assigned')
238 LOG.info('Resources deployed successfully!')
239
240
John Warren94d8faf2015-09-15 12:22:24 -0400241def create_network_resources(network_admin_client, networks_admin_client,
John Warren3961acd2015-10-02 14:38:53 -0400242 subnets_admin_client, tenant_id, name):
David Kranz0aa4a7b2015-06-08 13:25:41 -0400243
244 def _create_network(name):
John Warren94d8faf2015-09-15 12:22:24 -0400245 resp_body = networks_admin_client.create_network(
David Kranz0aa4a7b2015-06-08 13:25:41 -0400246 name=name, tenant_id=tenant_id)
247 return resp_body['network']
248
249 def _create_subnet(subnet_name, network_id):
250 base_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
251 mask_bits = CONF.network.tenant_network_mask_bits
252 for subnet_cidr in base_cidr.subnet(mask_bits):
253 try:
John Warren3961acd2015-10-02 14:38:53 -0400254 resp_body = subnets_admin_client.\
David Kranz0aa4a7b2015-06-08 13:25:41 -0400255 create_subnet(
256 network_id=network_id, cidr=str(subnet_cidr),
257 name=subnet_name,
258 tenant_id=tenant_id,
259 enable_dhcp=True,
260 ip_version=4)
261 break
262 except tempest_lib.exceptions.BadRequest as e:
263 if 'overlaps with another subnet' not in str(e):
264 raise
265 else:
266 message = 'Available CIDR for subnet creation could not be found'
267 raise Exception(message)
268 return resp_body['subnet']
269
270 def _create_router(router_name):
271 external_net_id = dict(
272 network_id=CONF.network.public_network_id)
273 resp_body = network_admin_client.create_router(
274 router_name,
275 external_gateway_info=external_net_id,
276 tenant_id=tenant_id)
277 return resp_body['router']
278
279 def _add_router_interface(router_id, subnet_id):
280 network_admin_client.add_router_interface_with_subnet_id(
281 router_id, subnet_id)
282
283 network_name = name + "-network"
284 network = _create_network(network_name)
285 subnet_name = name + "-subnet"
286 subnet = _create_subnet(subnet_name, network['id'])
287 router_name = name + "-router"
288 router = _create_router(router_name)
289 _add_router_interface(router['id'], subnet['id'])
David Paterson15be99e2015-04-08 21:58:19 -0400290 return network_name, router_name
David Kranz0aa4a7b2015-06-08 13:25:41 -0400291
292
sslypushenko0de7d052015-04-16 18:49:55 +0300293def random_user_name(tag, prefix):
294 if tag:
295 return data_utils.rand_name('-'.join((tag, prefix)))
296 else:
297 return data_utils.rand_name(prefix)
298
299
300def generate_resources(opts):
301 spec = [{'number': 1,
302 'prefix': 'primary',
303 'roles': (CONF.auth.tempest_roles +
304 [CONF.object_storage.operator_role])},
305 {'number': 1,
306 'prefix': 'alt',
307 'roles': (CONF.auth.tempest_roles +
Matthew Treinish36c2e282015-08-25 00:30:15 -0400308 [CONF.object_storage.operator_role])}]
309 if CONF.service_available.swift:
310 spec.append({'number': 1,
Matthew Treinish7b05b342015-09-09 17:14:02 -0400311 'prefix': 'swift_operator',
Matthew Treinish36c2e282015-08-25 00:30:15 -0400312 'roles': (CONF.auth.tempest_roles +
Matthew Treinish7b05b342015-09-09 17:14:02 -0400313 [CONF.object_storage.operator_role])})
314 spec.append({'number': 1,
315 'prefix': 'swift_reseller_admin',
316 'roles': (CONF.auth.tempest_roles +
317 [CONF.object_storage.reseller_admin_role])})
Matthew Treinish36c2e282015-08-25 00:30:15 -0400318 if CONF.service_available.heat:
319 spec.append({'number': 1,
320 'prefix': 'stack_owner',
321 'roles': (CONF.auth.tempest_roles +
322 [CONF.orchestration.stack_owner_role])})
sslypushenko0de7d052015-04-16 18:49:55 +0300323 if opts.admin:
324 spec.append({
325 'number': 1,
326 'prefix': 'admin',
327 'roles': (CONF.auth.tempest_roles +
328 [CONF.identity.admin_role])
329 })
330 resources = {'tenants': [],
331 'users': []}
332 for count in range(opts.concurrency):
333 for user_group in spec:
334 users = [random_user_name(opts.tag, user_group['prefix'])
335 for _ in range(user_group['number'])]
336 for user in users:
337 tenant = '-'.join((user, 'tenant'))
338 resources['tenants'].append(tenant)
339 resources['users'].append({
340 'tenant': tenant,
341 'name': user,
342 'pass': data_utils.rand_name(),
343 'prefix': user_group['prefix'],
344 'roles': user_group['roles']
345 })
346 return resources
347
348
349def dump_accounts(opts, resources):
350 accounts = []
351 for user in resources['users']:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400352 account = {
sslypushenko0de7d052015-04-16 18:49:55 +0300353 'username': user['name'],
354 'tenant_name': user['tenant'],
355 'password': user['pass'],
356 'roles': user['roles']
David Kranz0aa4a7b2015-06-08 13:25:41 -0400357 }
David Paterson15be99e2015-04-08 21:58:19 -0400358 if 'network' or 'router' in user:
359 account['resources'] = {}
David Kranz0aa4a7b2015-06-08 13:25:41 -0400360 if 'network' in user:
David Paterson15be99e2015-04-08 21:58:19 -0400361 account['resources']['network'] = user['network']
362 if 'router' in user:
363 account['resources']['router'] = user['router']
David Kranz0aa4a7b2015-06-08 13:25:41 -0400364 accounts.append(account)
sslypushenko0de7d052015-04-16 18:49:55 +0300365 if os.path.exists(opts.accounts):
366 os.rename(opts.accounts, '.'.join((opts.accounts, 'bak')))
367 with open(opts.accounts, 'w') as f:
368 yaml.dump(accounts, f, default_flow_style=False)
369 LOG.info('%s generated successfully!' % opts.accounts)
370
371
372def get_options():
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300373 usage_string = ('tempest-account-generator [-h] <ARG> ...\n\n'
sslypushenko0de7d052015-04-16 18:49:55 +0300374 'To see help on specific argument, do:\n'
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300375 'tempest-account-generator <ARG> -h')
sslypushenko0de7d052015-04-16 18:49:55 +0300376 parser = argparse.ArgumentParser(
377 description='Create accounts.yaml file for concurrent test runs. '
378 'One primary user, one alt user, '
379 'one swift admin, one stack owner '
380 'and one admin (optionally) will be created '
381 'for each concurrent thread.',
382 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
383 usage=usage_string
384 )
385
386 parser.add_argument('-c', '--config-file',
387 metavar='/etc/tempest.conf',
388 help='path to tempest config file')
389 parser.add_argument('--os-username',
390 metavar='<auth-user-name>',
391 default=os.environ.get('OS_USERNAME'),
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300392 help='User should have permissions '
sslypushenko0de7d052015-04-16 18:49:55 +0300393 'to create new user accounts and '
394 'tenants. Defaults to env[OS_USERNAME].')
395 parser.add_argument('--os-password',
396 metavar='<auth-password>',
397 default=os.environ.get('OS_PASSWORD'),
398 help='Defaults to env[OS_PASSWORD].')
399 parser.add_argument('--os-tenant-name',
400 metavar='<auth-tenant-name>',
401 default=os.environ.get('OS_TENANT_NAME'),
402 help='Defaults to env[OS_TENANT_NAME].')
403 parser.add_argument('--tag',
404 default='',
405 required=False,
406 dest='tag',
407 help='Resources tag')
408 parser.add_argument('-r', '--concurrency',
409 default=1,
410 type=int,
411 required=True,
412 dest='concurrency',
413 help='Concurrency count')
414 parser.add_argument('--with-admin',
415 action='store_true',
416 dest='admin',
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300417 help='Creates admin for each concurrent group')
sslypushenko0de7d052015-04-16 18:49:55 +0300418 parser.add_argument('accounts',
419 metavar='accounts_file.yaml',
420 help='Output accounts yaml file')
421
422 opts = parser.parse_args()
423 if opts.config_file:
424 config.CONF.set_config_path(opts.config_file)
425 return opts
426
427
428def main(opts=None):
429 if not opts:
430 opts = get_options()
431 setup_logging()
432 resources = generate_resources(opts)
433 create_resources(opts, resources)
434 dump_accounts(opts, resources)
435
436if __name__ == "__main__":
437 main()