blob: 02c6e7ffb3bd06519ba968c9e6b96ffdc9b1eda5 [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
sslypushenko0de7d052015-04-16 18:49:55 +030097import tempest_lib.auth
98from tempest_lib.common.utils import data_utils
99import tempest_lib.exceptions
100
101LOG = None
102CONF = config.CONF
103
104
105def setup_logging():
106 global LOG
107 logging.setup(CONF, __name__)
108 LOG = logging.getLogger(__name__)
109
110
David Kranz0aa4a7b2015-06-08 13:25:41 -0400111def get_admin_clients(opts):
sslypushenko0de7d052015-04-16 18:49:55 +0300112 _creds = tempest_lib.auth.KeystoneV2Credentials(
113 username=opts.os_username,
114 password=opts.os_password,
115 tenant_name=opts.os_tenant_name)
116 auth_params = {
117 'disable_ssl_certificate_validation':
118 CONF.identity.disable_ssl_certificate_validation,
119 'ca_certs': CONF.identity.ca_certificates_file,
120 'trace_requests': CONF.debug.trace_requests
121 }
122 _auth = tempest_lib.auth.KeystoneV2AuthProvider(
123 _creds, CONF.identity.uri, **auth_params)
124 params = {
125 'disable_ssl_certificate_validation':
126 CONF.identity.disable_ssl_certificate_validation,
127 'ca_certs': CONF.identity.ca_certificates_file,
128 'trace_requests': CONF.debug.trace_requests,
129 'build_interval': CONF.compute.build_interval,
130 'build_timeout': CONF.compute.build_timeout
131 }
Ken'ichi Ohmichia6287072015-07-02 02:43:15 +0000132 identity_admin = identity_client.IdentityClient(
sslypushenko0de7d052015-04-16 18:49:55 +0300133 _auth,
134 CONF.identity.catalog_type,
135 CONF.identity.region,
136 endpoint_type='adminURL',
137 **params
138 )
David Kranz0aa4a7b2015-06-08 13:25:41 -0400139 network_admin = None
John Warren94d8faf2015-09-15 12:22:24 -0400140 networks_admin = None
141 neutron_iso_networks = False
David Kranz0aa4a7b2015-06-08 13:25:41 -0400142 if (CONF.service_available.neutron and
143 CONF.auth.create_isolated_networks):
John Warren94d8faf2015-09-15 12:22:24 -0400144 neutron_iso_networks = True
Ken'ichi Ohmichia6287072015-07-02 02:43:15 +0000145 network_admin = network_client.NetworkClient(
David Kranz0aa4a7b2015-06-08 13:25:41 -0400146 _auth,
147 CONF.network.catalog_type,
148 CONF.network.region or CONF.identity.region,
149 endpoint_type='adminURL',
150 **params)
John Warren94d8faf2015-09-15 12:22:24 -0400151 networks_admin = networks_client.NetworksClient(
152 _auth,
153 CONF.network.catalog_type,
154 CONF.network.region or CONF.identity.region,
155 endpoint_type='adminURL',
156 **params)
157 return identity_admin, neutron_iso_networks, network_admin, networks_admin
sslypushenko0de7d052015-04-16 18:49:55 +0300158
159
160def create_resources(opts, resources):
John Warren94d8faf2015-09-15 12:22:24 -0400161 (identity_admin, neutron_iso_networks,
162 network_admin, networks_admin) = get_admin_clients(opts)
John Warrene61a94f2015-09-28 13:51:51 -0400163 roles = identity_admin.list_roles()['roles']
sslypushenko0de7d052015-04-16 18:49:55 +0300164 for u in resources['users']:
165 u['role_ids'] = []
166 for r in u.get('roles', ()):
167 try:
168 role = filter(lambda r_: r_['name'] == r, roles)[0]
sslypushenko0de7d052015-04-16 18:49:55 +0300169 except IndexError:
Matthew Treinish36c2e282015-08-25 00:30:15 -0400170 msg = "Role: %s doesn't exist" % r
171 raise exc.InvalidConfiguration(msg)
172 u['role_ids'] += [role['id']]
John Warrenacf00512015-08-06 16:13:58 -0400173 existing = [x['name'] for x in identity_admin.list_tenants()['tenants']]
sslypushenko0de7d052015-04-16 18:49:55 +0300174 for tenant in resources['tenants']:
175 if tenant not in existing:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400176 identity_admin.create_tenant(tenant)
sslypushenko0de7d052015-04-16 18:49:55 +0300177 else:
178 LOG.warn("Tenant '%s' already exists in this environment" % tenant)
179 LOG.info('Tenants created')
180 for u in resources['users']:
181 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400182 tenant = identity_admin.get_tenant_by_name(u['tenant'])
sslypushenko0de7d052015-04-16 18:49:55 +0300183 except tempest_lib.exceptions.NotFound:
184 LOG.error("Tenant: %s - not found" % u['tenant'])
185 continue
186 while True:
187 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400188 identity_admin.get_user_by_username(tenant['id'], u['name'])
sslypushenko0de7d052015-04-16 18:49:55 +0300189 except tempest_lib.exceptions.NotFound:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400190 identity_admin.create_user(
sslypushenko0de7d052015-04-16 18:49:55 +0300191 u['name'], u['pass'], tenant['id'],
192 "%s@%s" % (u['name'], tenant['id']),
193 enabled=True)
194 break
195 else:
196 LOG.warn("User '%s' already exists in this environment. "
197 "New name generated" % u['name'])
198 u['name'] = random_user_name(opts.tag, u['prefix'])
199
200 LOG.info('Users created')
John Warren94d8faf2015-09-15 12:22:24 -0400201 if neutron_iso_networks:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400202 for u in resources['users']:
203 tenant = identity_admin.get_tenant_by_name(u['tenant'])
John Warren94d8faf2015-09-15 12:22:24 -0400204 network_name, router_name = create_network_resources(
205 network_admin, networks_admin, tenant['id'], u['name'])
David Kranz0aa4a7b2015-06-08 13:25:41 -0400206 u['network'] = network_name
David Paterson15be99e2015-04-08 21:58:19 -0400207 u['router'] = router_name
David Kranz0aa4a7b2015-06-08 13:25:41 -0400208 LOG.info('Networks created')
sslypushenko0de7d052015-04-16 18:49:55 +0300209 for u in resources['users']:
210 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400211 tenant = identity_admin.get_tenant_by_name(u['tenant'])
sslypushenko0de7d052015-04-16 18:49:55 +0300212 except tempest_lib.exceptions.NotFound:
213 LOG.error("Tenant: %s - not found" % u['tenant'])
214 continue
215 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400216 user = identity_admin.get_user_by_username(tenant['id'],
217 u['name'])
sslypushenko0de7d052015-04-16 18:49:55 +0300218 except tempest_lib.exceptions.NotFound:
219 LOG.error("User: %s - not found" % u['user'])
220 continue
221 for r in u['role_ids']:
222 try:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400223 identity_admin.assign_user_role(tenant['id'], user['id'], r)
sslypushenko0de7d052015-04-16 18:49:55 +0300224 except tempest_lib.exceptions.Conflict:
225 # don't care if it's already assigned
226 pass
227 LOG.info('Roles assigned')
228 LOG.info('Resources deployed successfully!')
229
230
John Warren94d8faf2015-09-15 12:22:24 -0400231def create_network_resources(network_admin_client, networks_admin_client,
232 tenant_id, name):
David Kranz0aa4a7b2015-06-08 13:25:41 -0400233
234 def _create_network(name):
John Warren94d8faf2015-09-15 12:22:24 -0400235 resp_body = networks_admin_client.create_network(
David Kranz0aa4a7b2015-06-08 13:25:41 -0400236 name=name, tenant_id=tenant_id)
237 return resp_body['network']
238
239 def _create_subnet(subnet_name, network_id):
240 base_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
241 mask_bits = CONF.network.tenant_network_mask_bits
242 for subnet_cidr in base_cidr.subnet(mask_bits):
243 try:
244 resp_body = network_admin_client.\
245 create_subnet(
246 network_id=network_id, cidr=str(subnet_cidr),
247 name=subnet_name,
248 tenant_id=tenant_id,
249 enable_dhcp=True,
250 ip_version=4)
251 break
252 except tempest_lib.exceptions.BadRequest as e:
253 if 'overlaps with another subnet' not in str(e):
254 raise
255 else:
256 message = 'Available CIDR for subnet creation could not be found'
257 raise Exception(message)
258 return resp_body['subnet']
259
260 def _create_router(router_name):
261 external_net_id = dict(
262 network_id=CONF.network.public_network_id)
263 resp_body = network_admin_client.create_router(
264 router_name,
265 external_gateway_info=external_net_id,
266 tenant_id=tenant_id)
267 return resp_body['router']
268
269 def _add_router_interface(router_id, subnet_id):
270 network_admin_client.add_router_interface_with_subnet_id(
271 router_id, subnet_id)
272
273 network_name = name + "-network"
274 network = _create_network(network_name)
275 subnet_name = name + "-subnet"
276 subnet = _create_subnet(subnet_name, network['id'])
277 router_name = name + "-router"
278 router = _create_router(router_name)
279 _add_router_interface(router['id'], subnet['id'])
David Paterson15be99e2015-04-08 21:58:19 -0400280 return network_name, router_name
David Kranz0aa4a7b2015-06-08 13:25:41 -0400281
282
sslypushenko0de7d052015-04-16 18:49:55 +0300283def random_user_name(tag, prefix):
284 if tag:
285 return data_utils.rand_name('-'.join((tag, prefix)))
286 else:
287 return data_utils.rand_name(prefix)
288
289
290def generate_resources(opts):
291 spec = [{'number': 1,
292 'prefix': 'primary',
293 'roles': (CONF.auth.tempest_roles +
294 [CONF.object_storage.operator_role])},
295 {'number': 1,
296 'prefix': 'alt',
297 'roles': (CONF.auth.tempest_roles +
Matthew Treinish36c2e282015-08-25 00:30:15 -0400298 [CONF.object_storage.operator_role])}]
299 if CONF.service_available.swift:
300 spec.append({'number': 1,
Matthew Treinish7b05b342015-09-09 17:14:02 -0400301 'prefix': 'swift_operator',
Matthew Treinish36c2e282015-08-25 00:30:15 -0400302 'roles': (CONF.auth.tempest_roles +
Matthew Treinish7b05b342015-09-09 17:14:02 -0400303 [CONF.object_storage.operator_role])})
304 spec.append({'number': 1,
305 'prefix': 'swift_reseller_admin',
306 'roles': (CONF.auth.tempest_roles +
307 [CONF.object_storage.reseller_admin_role])})
Matthew Treinish36c2e282015-08-25 00:30:15 -0400308 if CONF.service_available.heat:
309 spec.append({'number': 1,
310 'prefix': 'stack_owner',
311 'roles': (CONF.auth.tempest_roles +
312 [CONF.orchestration.stack_owner_role])})
sslypushenko0de7d052015-04-16 18:49:55 +0300313 if opts.admin:
314 spec.append({
315 'number': 1,
316 'prefix': 'admin',
317 'roles': (CONF.auth.tempest_roles +
318 [CONF.identity.admin_role])
319 })
320 resources = {'tenants': [],
321 'users': []}
322 for count in range(opts.concurrency):
323 for user_group in spec:
324 users = [random_user_name(opts.tag, user_group['prefix'])
325 for _ in range(user_group['number'])]
326 for user in users:
327 tenant = '-'.join((user, 'tenant'))
328 resources['tenants'].append(tenant)
329 resources['users'].append({
330 'tenant': tenant,
331 'name': user,
332 'pass': data_utils.rand_name(),
333 'prefix': user_group['prefix'],
334 'roles': user_group['roles']
335 })
336 return resources
337
338
339def dump_accounts(opts, resources):
340 accounts = []
341 for user in resources['users']:
David Kranz0aa4a7b2015-06-08 13:25:41 -0400342 account = {
sslypushenko0de7d052015-04-16 18:49:55 +0300343 'username': user['name'],
344 'tenant_name': user['tenant'],
345 'password': user['pass'],
346 'roles': user['roles']
David Kranz0aa4a7b2015-06-08 13:25:41 -0400347 }
David Paterson15be99e2015-04-08 21:58:19 -0400348 if 'network' or 'router' in user:
349 account['resources'] = {}
David Kranz0aa4a7b2015-06-08 13:25:41 -0400350 if 'network' in user:
David Paterson15be99e2015-04-08 21:58:19 -0400351 account['resources']['network'] = user['network']
352 if 'router' in user:
353 account['resources']['router'] = user['router']
David Kranz0aa4a7b2015-06-08 13:25:41 -0400354 accounts.append(account)
sslypushenko0de7d052015-04-16 18:49:55 +0300355 if os.path.exists(opts.accounts):
356 os.rename(opts.accounts, '.'.join((opts.accounts, 'bak')))
357 with open(opts.accounts, 'w') as f:
358 yaml.dump(accounts, f, default_flow_style=False)
359 LOG.info('%s generated successfully!' % opts.accounts)
360
361
362def get_options():
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300363 usage_string = ('tempest-account-generator [-h] <ARG> ...\n\n'
sslypushenko0de7d052015-04-16 18:49:55 +0300364 'To see help on specific argument, do:\n'
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300365 'tempest-account-generator <ARG> -h')
sslypushenko0de7d052015-04-16 18:49:55 +0300366 parser = argparse.ArgumentParser(
367 description='Create accounts.yaml file for concurrent test runs. '
368 'One primary user, one alt user, '
369 'one swift admin, one stack owner '
370 'and one admin (optionally) will be created '
371 'for each concurrent thread.',
372 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
373 usage=usage_string
374 )
375
376 parser.add_argument('-c', '--config-file',
377 metavar='/etc/tempest.conf',
378 help='path to tempest config file')
379 parser.add_argument('--os-username',
380 metavar='<auth-user-name>',
381 default=os.environ.get('OS_USERNAME'),
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300382 help='User should have permissions '
sslypushenko0de7d052015-04-16 18:49:55 +0300383 'to create new user accounts and '
384 'tenants. Defaults to env[OS_USERNAME].')
385 parser.add_argument('--os-password',
386 metavar='<auth-password>',
387 default=os.environ.get('OS_PASSWORD'),
388 help='Defaults to env[OS_PASSWORD].')
389 parser.add_argument('--os-tenant-name',
390 metavar='<auth-tenant-name>',
391 default=os.environ.get('OS_TENANT_NAME'),
392 help='Defaults to env[OS_TENANT_NAME].')
393 parser.add_argument('--tag',
394 default='',
395 required=False,
396 dest='tag',
397 help='Resources tag')
398 parser.add_argument('-r', '--concurrency',
399 default=1,
400 type=int,
401 required=True,
402 dest='concurrency',
403 help='Concurrency count')
404 parser.add_argument('--with-admin',
405 action='store_true',
406 dest='admin',
Jane Zadorozhna00fc3dc2015-05-27 18:01:56 +0300407 help='Creates admin for each concurrent group')
sslypushenko0de7d052015-04-16 18:49:55 +0300408 parser.add_argument('accounts',
409 metavar='accounts_file.yaml',
410 help='Output accounts yaml file')
411
412 opts = parser.parse_args()
413 if opts.config_file:
414 config.CONF.set_config_path(opts.config_file)
415 return opts
416
417
418def main(opts=None):
419 if not opts:
420 opts = get_options()
421 setup_logging()
422 resources = generate_resources(opts)
423 create_resources(opts, resources)
424 dump_accounts(opts, resources)
425
426if __name__ == "__main__":
427 main()