Account generator for identity v3
Extend account generator to work with identity v3 API and
credentials. To do so, replace a good chunk of the existing code
with the credential provisioning APIs of the dynamic credentials
provider. Add unit test coverage for the tool.
Depends-on: I9fcb3497387fb32f3e515cda44a1a105921a04c2
Closes-bug 1494293
Change-Id: I9970cb29ec4d448a381b4fe293fdfd91d8cfc17a
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 94a646a..249261b 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -40,13 +40,15 @@
You're probably familiar with these, but just to remind::
- +----------+------------------+----------------------+
- | Param | CLI | Environment Variable |
- +----------+------------------+----------------------+
- | Username | --os-username | OS_USERNAME |
- | Password | --os-password | OS_PASSWORD |
- | Tenant | --os-tenant-name | OS_TENANT_NAME |
- +----------+------------------+----------------------+
+ +----------+---------------------------+----------------------+
+ | Param | CLI | Environment Variable |
+ +----------+---------------------------+----------------------+
+ | Username | --os-username | OS_USERNAME |
+ | Password | --os-password | OS_PASSWORD |
+ | Project | --os-project-name | OS_PROJECT_NAME |
+ | Tenant | --os-tenant-name (depr.) | OS_TENANT_NAME |
+ | Domain | --os-domain-name | OS_DOMAIN_NAME |
+ +----------+---------------------------+----------------------+
Optional Arguments
------------------
@@ -63,8 +65,14 @@
**--os-password <auth-password>** (Optional) Password used for authentication
with the OpenStack Identity service. Defaults to env[OS_PASSWORD].
-**--os-tenant-name <auth-tenant-name>** (Optional) Tenant to request
-authorization on. Defaults to env[OS_TENANT_NAME].
+**--os-project-name <auth-project-name>** (Optional) Project to request
+authorization on. Defaults to env[OS_PROJECT_NAME].
+
+**--os-tenant-name <auth-tenant-name>** (Optional, deprecated) Tenant to
+request authorization on. Defaults to env[OS_TENANT_NAME].
+
+**--os-domain-name <auth-domain-name>** (Optional) Domain the user and project
+belong to. Defaults to env[OS_DOMAIN_NAME].
**--tag TAG** (Optional) Resources tag. Each created resource (user, project)
will have the prefix with the given TAG in its name. Using tag is recommended
@@ -79,11 +87,13 @@
**--with-admin** (Optional) Creates admin for each concurrent group
(default: False).
+**-i VERSION**, **--identity-version VERSION** (Optional) Provisions accounts
+using the specified version of the identity API. (default: '3').
+
To see help on specific argument, please do: ``tempest-account-generator
[OPTIONS] <accounts_file.yaml> -h``.
"""
import argparse
-import netaddr
import os
import traceback
@@ -91,19 +101,10 @@
from oslo_log import log as logging
import yaml
-from tempest.common import identity
+from tempest.common import credentials_factory
+from tempest.common import dynamic_creds
from tempest import config
-from tempest import exceptions as exc
-import tempest.lib.auth
-from tempest.lib.common.utils import data_utils
-import tempest.lib.exceptions
-from tempest.lib.services.network import networks_client
-from tempest.lib.services.network import routers_client
-from tempest.lib.services.network import subnets_client
-from tempest.services.identity.v2.json import identity_client
-from tempest.services.identity.v2.json import roles_client
-from tempest.services.identity.v2.json import tenants_client
-from tempest.services.identity.v2.json import users_client
+
LOG = None
CONF = config.CONF
@@ -120,290 +121,83 @@
LOG = logging.getLogger(__name__)
-def get_admin_clients(opts):
- _creds = tempest.lib.auth.KeystoneV2Credentials(
- username=opts.os_username,
- password=opts.os_password,
- tenant_name=opts.os_tenant_name)
- auth_params = {
- 'disable_ssl_certificate_validation':
- CONF.identity.disable_ssl_certificate_validation,
- 'ca_certs': CONF.identity.ca_certificates_file,
- 'trace_requests': CONF.debug.trace_requests
- }
- _auth = tempest.lib.auth.KeystoneV2AuthProvider(
- _creds, CONF.identity.uri, **auth_params)
- params = {
- 'disable_ssl_certificate_validation':
- CONF.identity.disable_ssl_certificate_validation,
- 'ca_certs': CONF.identity.ca_certificates_file,
- 'trace_requests': CONF.debug.trace_requests,
- 'build_interval': CONF.compute.build_interval,
- 'build_timeout': CONF.compute.build_timeout
- }
- identity_admin = identity_client.IdentityClient(
- _auth,
- CONF.identity.catalog_type,
- CONF.identity.region,
- endpoint_type='adminURL',
- **params
- )
- tenants_admin = tenants_client.TenantsClient(
- _auth,
- CONF.identity.catalog_type,
- CONF.identity.region,
- endpoint_type='adminURL',
- **params
- )
- roles_admin = roles_client.RolesClient(
- _auth,
- CONF.identity.catalog_type,
- CONF.identity.region,
- endpoint_type='adminURL',
- **params
- )
- users_admin = users_client.UsersClient(
- _auth,
- CONF.identity.catalog_type,
- CONF.identity.region,
- endpoint_type='adminURL',
- **params
- )
- networks_admin = None
- routers_admin = None
- subnets_admin = None
- neutron_iso_networks = False
- if (CONF.service_available.neutron and
- CONF.auth.create_isolated_networks):
- neutron_iso_networks = True
- networks_admin = networks_client.NetworksClient(
- _auth,
- CONF.network.catalog_type,
- CONF.network.region or CONF.identity.region,
- endpoint_type='adminURL',
- **params)
- routers_admin = routers_client.RoutersClient(
- _auth,
- CONF.network.catalog_type,
- CONF.network.region or CONF.identity.region,
- endpoint_type='adminURL',
- **params)
- subnets_admin = subnets_client.SubnetsClient(
- _auth,
- CONF.network.catalog_type,
- CONF.network.region or CONF.identity.region,
- endpoint_type='adminURL',
- **params)
- return (identity_admin, tenants_admin, roles_admin, users_admin,
- neutron_iso_networks, networks_admin, routers_admin,
- subnets_admin)
+def get_credential_provider(opts):
+ identity_version = "".join(['v', str(opts.identity_version)])
+ # NOTE(andreaf) For now tempest.conf controls whether resources will
+ # actually be created. Once we remove the dependency from tempest.conf
+ # we will need extra CLI option(s) to control this.
+ network_resources = {'router': True,
+ 'network': True,
+ 'subnet': True,
+ 'dhcp': True}
+ admin_creds_dict = {'username': opts.os_username,
+ 'password': opts.os_password}
+ _project_name = opts.os_project_name or opts.os_tenant_name
+ if opts.identity_version == 3:
+ admin_creds_dict['project_name'] = _project_name
+ admin_creds_dict['domain_name'] = opts.os_domain_name or 'Default'
+ elif opts.identity_version == 2:
+ admin_creds_dict['tenant_name'] = _project_name
+ admin_creds = credentials_factory.get_credentials(
+ fill_in=False, identity_version=identity_version, **admin_creds_dict)
+ return dynamic_creds.DynamicCredentialProvider(
+ identity_version=identity_version,
+ name=opts.tag,
+ network_resources=network_resources,
+ admin_creds=admin_creds,
+ **credentials_factory.get_dynamic_provider_params())
-def create_resources(opts, resources):
- (identity_admin, tenants_admin, roles_admin, users_admin,
- neutron_iso_networks, networks_admin, routers_admin,
- subnets_admin) = get_admin_clients(opts)
- roles = roles_admin.list_roles()['roles']
- for u in resources['users']:
- u['role_ids'] = []
- for r in u.get('roles', ()):
- try:
- role = filter(lambda r_: r_['name'] == r, roles)[0]
- except IndexError:
- msg = "Role: %s doesn't exist" % r
- raise exc.InvalidConfiguration(msg)
- u['role_ids'] += [role['id']]
- existing = [x['name'] for x in tenants_admin.list_tenants()['tenants']]
- for tenant in resources['tenants']:
- if tenant not in existing:
- tenants_admin.create_tenant(tenant)
- else:
- LOG.warning("Tenant '%s' already exists in this environment"
- % tenant)
- LOG.info('Tenants created')
- for u in resources['users']:
- try:
- tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
- except tempest.lib.exceptions.NotFound:
- LOG.error("Tenant: %s - not found" % u['tenant'])
- continue
- while True:
- try:
- identity.get_user_by_username(tenants_admin,
- tenant['id'], u['name'])
- except tempest.lib.exceptions.NotFound:
- users_admin.create_user(
- u['name'], u['pass'], tenant['id'],
- "%s@%s" % (u['name'], tenant['id']),
- enabled=True)
- break
- else:
- LOG.warning("User '%s' already exists in this environment. "
- "New name generated" % u['name'])
- u['name'] = random_user_name(opts.tag, u['prefix'])
-
- LOG.info('Users created')
- if neutron_iso_networks:
- for u in resources['users']:
- tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
- network_name, router_name = create_network_resources(
- networks_admin, routers_admin, subnets_admin,
- tenant['id'], u['name'])
- u['network'] = network_name
- u['router'] = router_name
- LOG.info('Networks created')
- for u in resources['users']:
- try:
- tenant = identity.get_tenant_by_name(tenants_admin, u['tenant'])
- except tempest.lib.exceptions.NotFound:
- LOG.error("Tenant: %s - not found" % u['tenant'])
- continue
- try:
- user = identity.get_user_by_username(tenants_admin,
- tenant['id'], u['name'])
- except tempest.lib.exceptions.NotFound:
- LOG.error("User: %s - not found" % u['name'])
- continue
- for r in u['role_ids']:
- try:
- roles_admin.assign_user_role(tenant['id'], user['id'], r)
- except tempest.lib.exceptions.Conflict:
- # don't care if it's already assigned
- pass
- LOG.info('Roles assigned')
- LOG.info('Resources deployed successfully!')
-
-
-def create_network_resources(networks_admin_client,
- routers_admin_client, subnets_admin_client,
- tenant_id, name):
-
- def _create_network(name):
- resp_body = networks_admin_client.create_network(
- name=name, tenant_id=tenant_id)
- return resp_body['network']
-
- def _create_subnet(subnet_name, network_id):
- base_cidr = netaddr.IPNetwork(CONF.network.project_network_cidr)
- mask_bits = CONF.network.project_network_mask_bits
- for subnet_cidr in base_cidr.subnet(mask_bits):
- try:
- resp_body = subnets_admin_client.\
- create_subnet(
- network_id=network_id, cidr=str(subnet_cidr),
- name=subnet_name,
- tenant_id=tenant_id,
- enable_dhcp=True,
- ip_version=4)
- break
- except tempest.lib.exceptions.BadRequest as e:
- if 'overlaps with another subnet' not in str(e):
- raise
- else:
- message = 'Available CIDR for subnet creation could not be found'
- raise Exception(message)
- return resp_body['subnet']
-
- def _create_router(router_name):
- external_net_id = dict(
- network_id=CONF.network.public_network_id)
- resp_body = routers_admin_client.create_router(
- name=router_name,
- external_gateway_info=external_net_id,
- tenant_id=tenant_id)
- return resp_body['router']
-
- def _add_router_interface(router_id, subnet_id):
- routers_admin_client.add_router_interface(router_id,
- subnet_id=subnet_id)
-
- network_name = name + "-network"
- network = _create_network(network_name)
- subnet_name = name + "-subnet"
- subnet = _create_subnet(subnet_name, network['id'])
- router_name = name + "-router"
- router = _create_router(router_name)
- _add_router_interface(router['id'], subnet['id'])
- return network_name, router_name
-
-
-def random_user_name(tag, prefix):
- if tag:
- return data_utils.rand_name('-'.join((tag, prefix)))
- else:
- return data_utils.rand_name(prefix)
-
-
-def generate_resources(opts):
- spec = [{'number': 1,
- 'prefix': 'primary',
- 'roles': (CONF.auth.tempest_roles +
- [CONF.object_storage.operator_role])},
- {'number': 1,
- 'prefix': 'alt',
- 'roles': (CONF.auth.tempest_roles +
- [CONF.object_storage.operator_role])}]
+def generate_resources(cred_provider, admin):
+ # Create the list of resources to be provisioned for each process
+ # NOTE(andreaf) get_credentials expects a string for types or a list for
+ # roles. Adding all required inputs to the spec list.
+ spec = ['primary', 'alt']
if CONF.service_available.swift:
- spec.append({'number': 1,
- 'prefix': 'swift_operator',
- 'roles': (CONF.auth.tempest_roles +
- [CONF.object_storage.operator_role])})
- spec.append({'number': 1,
- 'prefix': 'swift_reseller_admin',
- 'roles': (CONF.auth.tempest_roles +
- [CONF.object_storage.reseller_admin_role])})
+ spec.append([CONF.object_storage.operator_role])
+ spec.append([CONF.object_storage.reseller_admin_role])
if CONF.service_available.heat:
- spec.append({'number': 1,
- 'prefix': 'stack_owner',
- 'roles': (CONF.auth.tempest_roles +
- [CONF.orchestration.stack_owner_role])})
- if opts.admin:
- spec.append({
- 'number': 1,
- 'prefix': 'admin',
- 'roles': (CONF.auth.tempest_roles +
- [CONF.identity.admin_role])
- })
- resources = {'tenants': [],
- 'users': []}
- for count in range(opts.concurrency):
- for user_group in spec:
- users = [random_user_name(opts.tag, user_group['prefix'])
- for _ in range(user_group['number'])]
- for user in users:
- tenant = '-'.join((user, 'tenant'))
- resources['tenants'].append(tenant)
- resources['users'].append({
- 'tenant': tenant,
- 'name': user,
- 'pass': data_utils.rand_password(),
- 'prefix': user_group['prefix'],
- 'roles': user_group['roles']
- })
+ spec.append([CONF.orchestration.stack_owner_role])
+ if admin:
+ spec.append('admin')
+ resources = []
+ for cred_type in spec:
+ resources.append((cred_type, cred_provider.get_credentials(
+ credential_type=cred_type)))
return resources
-def dump_accounts(opts, resources):
+def dump_accounts(resources, identity_version, account_file):
accounts = []
- for user in resources['users']:
+ for resource in resources:
+ cred_type, test_resource = resource
account = {
- 'username': user['name'],
- 'tenant_name': user['tenant'],
- 'password': user['pass'],
- 'roles': user['roles']
+ 'username': test_resource.username,
+ 'password': test_resource.password
}
- if 'network' in user or 'router' in user:
+ if identity_version == 3:
+ account['project_name'] = test_resource.project_name
+ account['domain_name'] = test_resource.domain_name
+ else:
+ account['project_name'] = test_resource.tenant_name
+
+ # If the spec includes 'admin' credentials are defined via type,
+ # else they are defined via list of roles.
+ if cred_type == 'admin':
+ account['types'] = [cred_type]
+ elif cred_type not in ['primary', 'alt']:
+ account['roles'] = cred_type
+
+ if test_resource.network:
account['resources'] = {}
- if 'network' in user:
- account['resources']['network'] = user['network']
- if 'router' in user:
- account['resources']['router'] = user['router']
+ if test_resource.network:
+ account['resources']['network'] = test_resource.network['name']
accounts.append(account)
- if os.path.exists(opts.accounts):
- os.rename(opts.accounts, '.'.join((opts.accounts, 'bak')))
- with open(opts.accounts, 'w') as f:
- yaml.dump(accounts, f, default_flow_style=False)
- LOG.info('%s generated successfully!' % opts.accounts)
+ if os.path.exists(account_file):
+ os.rename(account_file, '.'.join((account_file, 'bak')))
+ with open(account_file, 'w') as f:
+ yaml.safe_dump(accounts, f, default_flow_style=False)
+ LOG.info('%s generated successfully!' % account_file)
def _parser_add_args(parser):
@@ -420,10 +214,18 @@
metavar='<auth-password>',
default=os.environ.get('OS_PASSWORD'),
help='Defaults to env[OS_PASSWORD].')
+ parser.add_argument('--os-project-name',
+ metavar='<auth-project-name>',
+ default=os.environ.get('OS_PROJECT_NAME'),
+ help='Defaults to env[OS_PROJECT_NAME].')
parser.add_argument('--os-tenant-name',
metavar='<auth-tenant-name>',
default=os.environ.get('OS_TENANT_NAME'),
help='Defaults to env[OS_TENANT_NAME].')
+ parser.add_argument('--os-domain-name',
+ metavar='<auth-domain-name>',
+ default=os.environ.get('OS_DOMAIN_NAME'),
+ help='Defaults to env[OS_DOMAIN_NAME].')
parser.add_argument('--tag',
default='',
required=False,
@@ -439,6 +241,13 @@
action='store_true',
dest='admin',
help='Creates admin for each concurrent group')
+ parser.add_argument('-i', '--identity-version',
+ default=3,
+ choices=[2, 3],
+ type=int,
+ required=False,
+ dest='identity_version',
+ help='Version of the Identity API to use')
parser.add_argument('accounts',
metavar='accounts_file.yaml',
help='Output accounts yaml file')
@@ -487,9 +296,16 @@
opts = get_options()
if opts.config_file:
config.CONF.set_config_path(opts.config_file)
- resources = generate_resources(opts)
- create_resources(opts, resources)
- dump_accounts(opts, resources)
+ if opts.os_tenant_name:
+ LOG.warning("'os-tenant-name' and 'OS_TENANT_NAME' are both "
+ "deprecated, please use 'os-project-name' or "
+ "'OS_PROJECT_NAME' instead")
+ resources = []
+ for count in range(opts.concurrency):
+ # Use N different cred_providers to obtain different sets of creds
+ cred_provider = get_credential_provider(opts)
+ resources.extend(generate_resources(cred_provider, opts.admin))
+ dump_accounts(resources, opts.identity_version, opts.accounts)
if __name__ == "__main__":
main()
diff --git a/tempest/common/cred_client.py b/tempest/common/cred_client.py
index b23bc6f..aac036b 100644
--- a/tempest/common/cred_client.py
+++ b/tempest/common/cred_client.py
@@ -155,6 +155,9 @@
def get_credentials(self, user, project, password):
# User, project and domain already include both ID and name here,
# so there's no need to use the fill_in mode.
+ # NOTE(andreaf) We need to set all fields in the returned credentials.
+ # Scope is then used to pick only those relevant for the type of
+ # token needed by each service client.
return auth.get_credentials(
auth_url=None,
fill_in=False,
@@ -163,7 +166,9 @@
project_name=project['name'], project_id=project['id'],
password=password,
project_domain_id=self.creds_domain['id'],
- project_domain_name=self.creds_domain['name'])
+ project_domain_name=self.creds_domain['name'],
+ domain_id=self.creds_domain['id'],
+ domain_name=self.creds_domain['name'])
def _assign_user_role(self, project, user, role):
self.roles_client.assign_user_role_on_project(project['id'],
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index 286e01f..4873fcf 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -39,19 +39,19 @@
# Subset of the parameters of credential providers that depend on configuration
-def _get_common_provider_params():
+def get_common_provider_params():
return {
'credentials_domain': CONF.auth.default_credentials_domain_name,
'admin_role': CONF.identity.admin_role
}
-def _get_dynamic_provider_params():
- return _get_common_provider_params()
+def get_dynamic_provider_params():
+ return get_common_provider_params()
-def _get_preprov_provider_params():
- _common_params = _get_common_provider_params()
+def get_preprov_provider_params():
+ _common_params = get_common_provider_params()
reseller_admin_role = CONF.object_storage.reseller_admin_role
return dict(_common_params, **dict([
('accounts_lock_dir', lockutils.get_lock_path(CONF)),
@@ -80,13 +80,13 @@
network_resources=network_resources,
identity_version=identity_version,
admin_creds=admin_creds,
- **_get_dynamic_provider_params())
+ **get_dynamic_provider_params())
else:
if CONF.auth.test_accounts_file:
# Most params are not relevant for pre-created accounts
return preprov_creds.PreProvisionedCredentialProvider(
name=name, identity_version=identity_version,
- **_get_preprov_provider_params())
+ **get_preprov_provider_params())
else:
raise exceptions.InvalidConfiguration(
'A valid credential provider is needed')
@@ -106,7 +106,7 @@
elif CONF.auth.test_accounts_file:
check_accounts = preprov_creds.PreProvisionedCredentialProvider(
identity_version=identity_version, name='check_admin',
- **_get_preprov_provider_params())
+ **get_preprov_provider_params())
if not check_accounts.admin_available():
is_admin = False
else:
@@ -131,7 +131,7 @@
if CONF.auth.test_accounts_file:
check_accounts = preprov_creds.PreProvisionedCredentialProvider(
identity_version=identity_version, name='check_alt',
- **_get_preprov_provider_params())
+ **get_preprov_provider_params())
else:
raise exceptions.InvalidConfiguration(
'A valid credential provider is needed')