blob: 03601461e7bd800d1a3015bde8cfaf37890cead9 [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
28-----------------
29**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
52-----------------
53**-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
93from tempest import exceptions
94from tempest.services.identity.v2.json import identity_client
David Kranz0aa4a7b2015-06-08 13:25:41 -040095from tempest.services.network.json import network_client
sslypushenko0de7d052015-04-16 18:49:55 +030096import tempest_lib.auth
97from tempest_lib.common.utils import data_utils
98import tempest_lib.exceptions
99
100LOG = None
101CONF = config.CONF
102
103
104def setup_logging():
105 global LOG
106 logging.setup(CONF, __name__)
107 LOG = logging.getLogger(__name__)
108
109
David Kranz0aa4a7b2015-06-08 13:25:41 -0400110def get_admin_clients(opts):
sslypushenko0de7d052015-04-16 18:49:55 +0300111 _creds = tempest_lib.auth.KeystoneV2Credentials(
112 username=opts.os_username,
113 password=opts.os_password,
114 tenant_name=opts.os_tenant_name)
115 auth_params = {
116 'disable_ssl_certificate_validation':
117 CONF.identity.disable_ssl_certificate_validation,
118 'ca_certs': CONF.identity.ca_certificates_file,
119 'trace_requests': CONF.debug.trace_requests
120 }
121 _auth = tempest_lib.auth.KeystoneV2AuthProvider(
122 _creds, CONF.identity.uri, **auth_params)
123 params = {
124 'disable_ssl_certificate_validation':
125 CONF.identity.disable_ssl_certificate_validation,
126 'ca_certs': CONF.identity.ca_certificates_file,
127 'trace_requests': CONF.debug.trace_requests,
128 'build_interval': CONF.compute.build_interval,
129 'build_timeout': CONF.compute.build_timeout
130 }
Ken'ichi Ohmichia6287072015-07-02 02:43:15 +0000131 identity_admin = identity_client.IdentityClient(
sslypushenko0de7d052015-04-16 18:49:55 +0300132 _auth,
133 CONF.identity.catalog_type,
134 CONF.identity.region,
135 endpoint_type='adminURL',
136 **params
137 )
David Kranz0aa4a7b2015-06-08 13:25:41 -0400138 network_admin = None
139 if (CONF.service_available.neutron and
140 CONF.auth.create_isolated_networks):
Ken'ichi Ohmichia6287072015-07-02 02:43:15 +0000141 network_admin = network_client.NetworkClient(
David Kranz0aa4a7b2015-06-08 13:25:41 -0400142 _auth,
143 CONF.network.catalog_type,
144 CONF.network.region or CONF.identity.region,
145 endpoint_type='adminURL',
146 **params)
147 return identity_admin, network_admin
sslypushenko0de7d052015-04-16 18:49:55 +0300148
149
150def create_resources(opts, resources):
David Kranz0aa4a7b2015-06-08 13:25:41 -0400151 identity_admin, network_admin = get_admin_clients(opts)
152 roles = identity_admin.list_roles()
sslypushenko0de7d052015-04-16 18:49:55 +0300153 for u in resources['users']:
154 u['role_ids'] = []
155 for r in u.get('roles', ()):
156 try:
157 role = filter(lambda r_: r_['name'] == r, roles)[0]
158 u['role_ids'] += [role['id']]
159 except IndexError:
160 raise exceptions.TempestException(
161 "Role: %s - doesn't exist" % r
162 )
David Kranz0aa4a7b2015-06-08 13:25:41 -0400163 existing = [x['name'] for x in identity_admin.list_tenants()]
sslypushenko0de7d052015-04-16 18:49:55 +0300164 for tenant in resources['tenants']:
165 if tenant not in existing:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400166 identity_admin.create_tenant(tenant)
sslypushenko0de7d052015-04-16 18:49:55 +0300167 else:
168 LOG.warn("Tenant '%s' already exists in this environment" % tenant)
169 LOG.info('Tenants created')
170 for u in resources['users']:
171 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400172 tenant = identity_admin.get_tenant_by_name(u['tenant'])
sslypushenko0de7d052015-04-16 18:49:55 +0300173 except tempest_lib.exceptions.NotFound:
174 LOG.error("Tenant: %s - not found" % u['tenant'])
175 continue
176 while True:
177 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400178 identity_admin.get_user_by_username(tenant['id'], u['name'])
sslypushenko0de7d052015-04-16 18:49:55 +0300179 except tempest_lib.exceptions.NotFound:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400180 identity_admin.create_user(
sslypushenko0de7d052015-04-16 18:49:55 +0300181 u['name'], u['pass'], tenant['id'],
182 "%s@%s" % (u['name'], tenant['id']),
183 enabled=True)
184 break
185 else:
186 LOG.warn("User '%s' already exists in this environment. "
187 "New name generated" % u['name'])
188 u['name'] = random_user_name(opts.tag, u['prefix'])
189
190 LOG.info('Users created')
David Kranz0aa4a7b2015-06-08 13:25:41 -0400191 if network_admin:
192 for u in resources['users']:
193 tenant = identity_admin.get_tenant_by_name(u['tenant'])
David Paterson15be99e2015-04-08 21:58:19 -0400194 network_name, router_name = create_network_resources(network_admin,
195 tenant['id'],
196 u['name'])
David Kranz0aa4a7b2015-06-08 13:25:41 -0400197 u['network'] = network_name
David Paterson15be99e2015-04-08 21:58:19 -0400198 u['router'] = router_name
David Kranz0aa4a7b2015-06-08 13:25:41 -0400199 LOG.info('Networks created')
sslypushenko0de7d052015-04-16 18:49:55 +0300200 for u in resources['users']:
201 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400202 tenant = identity_admin.get_tenant_by_name(u['tenant'])
sslypushenko0de7d052015-04-16 18:49:55 +0300203 except tempest_lib.exceptions.NotFound:
204 LOG.error("Tenant: %s - not found" % u['tenant'])
205 continue
206 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400207 user = identity_admin.get_user_by_username(tenant['id'],
208 u['name'])
sslypushenko0de7d052015-04-16 18:49:55 +0300209 except tempest_lib.exceptions.NotFound:
210 LOG.error("User: %s - not found" % u['user'])
211 continue
212 for r in u['role_ids']:
213 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400214 identity_admin.assign_user_role(tenant['id'], user['id'], r)
sslypushenko0de7d052015-04-16 18:49:55 +0300215 except tempest_lib.exceptions.Conflict:
216 # don't care if it's already assigned
217 pass
218 LOG.info('Roles assigned')
219 LOG.info('Resources deployed successfully!')
220
221
David Kranz0aa4a7b2015-06-08 13:25:41 -0400222def create_network_resources(network_admin_client, tenant_id, name):
223
224 def _create_network(name):
225 resp_body = network_admin_client.create_network(
226 name=name, tenant_id=tenant_id)
227 return resp_body['network']
228
229 def _create_subnet(subnet_name, network_id):
230 base_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
231 mask_bits = CONF.network.tenant_network_mask_bits
232 for subnet_cidr in base_cidr.subnet(mask_bits):
233 try:
234 resp_body = network_admin_client.\
235 create_subnet(
236 network_id=network_id, cidr=str(subnet_cidr),
237 name=subnet_name,
238 tenant_id=tenant_id,
239 enable_dhcp=True,
240 ip_version=4)
241 break
242 except tempest_lib.exceptions.BadRequest as e:
243 if 'overlaps with another subnet' not in str(e):
244 raise
245 else:
246 message = 'Available CIDR for subnet creation could not be found'
247 raise Exception(message)
248 return resp_body['subnet']
249
250 def _create_router(router_name):
251 external_net_id = dict(
252 network_id=CONF.network.public_network_id)
253 resp_body = network_admin_client.create_router(
254 router_name,
255 external_gateway_info=external_net_id,
256 tenant_id=tenant_id)
257 return resp_body['router']
258
259 def _add_router_interface(router_id, subnet_id):
260 network_admin_client.add_router_interface_with_subnet_id(
261 router_id, subnet_id)
262
263 network_name = name + "-network"
264 network = _create_network(network_name)
265 subnet_name = name + "-subnet"
266 subnet = _create_subnet(subnet_name, network['id'])
267 router_name = name + "-router"
268 router = _create_router(router_name)
269 _add_router_interface(router['id'], subnet['id'])
David Paterson15be99e2015-04-08 21:58:19 -0400270 return network_name, router_name
David Kranz0aa4a7b2015-06-08 13:25:41 -0400271
272
sslypushenko0de7d052015-04-16 18:49:55 +0300273def random_user_name(tag, prefix):
274 if tag:
275 return data_utils.rand_name('-'.join((tag, prefix)))
276 else:
277 return data_utils.rand_name(prefix)
278
279
280def generate_resources(opts):
281 spec = [{'number': 1,
282 'prefix': 'primary',
283 'roles': (CONF.auth.tempest_roles +
284 [CONF.object_storage.operator_role])},
285 {'number': 1,
286 'prefix': 'alt',
287 'roles': (CONF.auth.tempest_roles +
288 [CONF.object_storage.operator_role])},
289 {'number': 1,
290 'prefix': 'swift_admin',
291 'roles': (CONF.auth.tempest_roles +
292 [CONF.object_storage.operator_role,
293 CONF.object_storage.reseller_admin_role])},
294 {'number': 1,
295 'prefix': 'stack_owner',
296 'roles': (CONF.auth.tempest_roles +
297 [CONF.orchestration.stack_owner_role])},
298 ]
299 if opts.admin:
300 spec.append({
301 'number': 1,
302 'prefix': 'admin',
303 'roles': (CONF.auth.tempest_roles +
304 [CONF.identity.admin_role])
305 })
306 resources = {'tenants': [],
307 'users': []}
308 for count in range(opts.concurrency):
309 for user_group in spec:
310 users = [random_user_name(opts.tag, user_group['prefix'])
311 for _ in range(user_group['number'])]
312 for user in users:
313 tenant = '-'.join((user, 'tenant'))
314 resources['tenants'].append(tenant)
315 resources['users'].append({
316 'tenant': tenant,
317 'name': user,
318 'pass': data_utils.rand_name(),
319 'prefix': user_group['prefix'],
320 'roles': user_group['roles']
321 })
322 return resources
323
324
325def dump_accounts(opts, resources):
326 accounts = []
327 for user in resources['users']:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400328 account = {
sslypushenko0de7d052015-04-16 18:49:55 +0300329 'username': user['name'],
330 'tenant_name': user['tenant'],
331 'password': user['pass'],
332 'roles': user['roles']
David Kranz0aa4a7b2015-06-08 13:25:41 -0400333 }
David Paterson15be99e2015-04-08 21:58:19 -0400334 if 'network' or 'router' in user:
335 account['resources'] = {}
David Kranz0aa4a7b2015-06-08 13:25:41 -0400336 if 'network' in user:
David Paterson15be99e2015-04-08 21:58:19 -0400337 account['resources']['network'] = user['network']
338 if 'router' in user:
339 account['resources']['router'] = user['router']
David Kranz0aa4a7b2015-06-08 13:25:41 -0400340 accounts.append(account)
sslypushenko0de7d052015-04-16 18:49:55 +0300341 if os.path.exists(opts.accounts):
342 os.rename(opts.accounts, '.'.join((opts.accounts, 'bak')))
343 with open(opts.accounts, 'w') as f:
344 yaml.dump(accounts, f, default_flow_style=False)
345 LOG.info('%s generated successfully!' % opts.accounts)
346
347
348def get_options():
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300349 usage_string = ('tempest-account-generator [-h] <ARG> ...\n\n'
sslypushenko0de7d052015-04-16 18:49:55 +0300350 'To see help on specific argument, do:\n'
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300351 'tempest-account-generator <ARG> -h')
sslypushenko0de7d052015-04-16 18:49:55 +0300352 parser = argparse.ArgumentParser(
353 description='Create accounts.yaml file for concurrent test runs. '
354 'One primary user, one alt user, '
355 'one swift admin, one stack owner '
356 'and one admin (optionally) will be created '
357 'for each concurrent thread.',
358 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
359 usage=usage_string
360 )
361
362 parser.add_argument('-c', '--config-file',
363 metavar='/etc/tempest.conf',
364 help='path to tempest config file')
365 parser.add_argument('--os-username',
366 metavar='<auth-user-name>',
367 default=os.environ.get('OS_USERNAME'),
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300368 help='User should have permissions '
sslypushenko0de7d052015-04-16 18:49:55 +0300369 'to create new user accounts and '
370 'tenants. Defaults to env[OS_USERNAME].')
371 parser.add_argument('--os-password',
372 metavar='<auth-password>',
373 default=os.environ.get('OS_PASSWORD'),
374 help='Defaults to env[OS_PASSWORD].')
375 parser.add_argument('--os-tenant-name',
376 metavar='<auth-tenant-name>',
377 default=os.environ.get('OS_TENANT_NAME'),
378 help='Defaults to env[OS_TENANT_NAME].')
379 parser.add_argument('--tag',
380 default='',
381 required=False,
382 dest='tag',
383 help='Resources tag')
384 parser.add_argument('-r', '--concurrency',
385 default=1,
386 type=int,
387 required=True,
388 dest='concurrency',
389 help='Concurrency count')
390 parser.add_argument('--with-admin',
391 action='store_true',
392 dest='admin',
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300393 help='Creates admin for each concurrent group')
sslypushenko0de7d052015-04-16 18:49:55 +0300394 parser.add_argument('accounts',
395 metavar='accounts_file.yaml',
396 help='Output accounts yaml file')
397
398 opts = parser.parse_args()
399 if opts.config_file:
400 config.CONF.set_config_path(opts.config_file)
401 return opts
402
403
404def main(opts=None):
405 if not opts:
406 opts = get_options()
407 setup_logging()
408 resources = generate_resources(opts)
409 create_resources(opts, resources)
410 dump_accounts(opts, resources)
411
412if __name__ == "__main__":
413 main()