Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 1 | # Copyright (c) 2014 Hewlett-Packard Development Company, L.P. |
| 2 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 3 | # you may not use this file except in compliance with the License. |
| 4 | # You may obtain a copy of the License at |
| 5 | # |
| 6 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 7 | # |
| 8 | # Unless required by applicable law or agreed to in writing, software |
| 9 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 11 | # See the License for the specific language governing permissions and |
| 12 | # limitations under the License. |
| 13 | |
Andrea Frittoli (andreaf) | 848e348 | 2015-10-12 14:17:21 +0100 | [diff] [blame] | 14 | from oslo_concurrency import lockutils |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 15 | |
| 16 | from tempest import clients |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 17 | from tempest import config |
Andrea Frittoli (andreaf) | db9672e | 2016-02-23 14:07:24 -0500 | [diff] [blame] | 18 | from tempest.lib import auth |
Matthew Treinish | c51b712 | 2017-07-17 12:28:07 -0400 | [diff] [blame] | 19 | from tempest.lib.common import dynamic_creds |
Matthew Treinish | b19c55d | 2017-07-17 12:38:35 -0400 | [diff] [blame] | 20 | from tempest.lib.common import preprov_creds |
Matthew Treinish | 4217a70 | 2016-10-07 17:27:11 -0400 | [diff] [blame] | 21 | from tempest.lib import exceptions |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 22 | |
| 23 | CONF = config.CONF |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 24 | |
| 25 | |
| 26 | """This module provides factories of credential and credential providers |
| 27 | |
Andrea Frittoli (andreaf) | 1370baf | 2016-04-29 14:26:22 -0500 | [diff] [blame] | 28 | Credentials providers and clients are (going to be) part of tempest.lib, |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 29 | and so they may not hold any dependency to tempest configuration. |
| 30 | |
| 31 | Methods in this module collect the relevant configuration details and pass |
| 32 | them to credentials providers and clients, so that test can have easy |
| 33 | access to these features. |
| 34 | |
| 35 | Client managers with hard-coded configured credentials are also moved here, |
| 36 | to avoid circular dependencies.""" |
| 37 | |
| 38 | # === Credential Providers |
| 39 | |
| 40 | |
Andrea Frittoli (andreaf) | 848e348 | 2015-10-12 14:17:21 +0100 | [diff] [blame] | 41 | # Subset of the parameters of credential providers that depend on configuration |
Andrea Frittoli | d199caf | 2017-07-18 17:12:38 +0100 | [diff] [blame] | 42 | def _get_common_provider_params(identity_version): |
Andrea Frittoli | dcd9100 | 2017-07-18 11:34:13 +0100 | [diff] [blame] | 43 | if identity_version == 'v3': |
| 44 | identity_uri = CONF.identity.uri_v3 |
| 45 | elif identity_version == 'v2': |
| 46 | identity_uri = CONF.identity.uri |
Andrea Frittoli | 17347f0 | 2017-07-26 16:18:30 +0100 | [diff] [blame] | 47 | else: |
| 48 | raise exceptions.InvalidIdentityVersion( |
| 49 | identity_version=identity_version) |
Andrea Frittoli (andreaf) | 848e348 | 2015-10-12 14:17:21 +0100 | [diff] [blame] | 50 | return { |
Andrea Frittoli | d199caf | 2017-07-18 17:12:38 +0100 | [diff] [blame] | 51 | 'identity_version': identity_version, |
Andrea Frittoli | dcd9100 | 2017-07-18 11:34:13 +0100 | [diff] [blame] | 52 | 'identity_uri': identity_uri, |
Andrea Frittoli (andreaf) | 848e348 | 2015-10-12 14:17:21 +0100 | [diff] [blame] | 53 | 'credentials_domain': CONF.auth.default_credentials_domain_name, |
| 54 | 'admin_role': CONF.identity.admin_role |
| 55 | } |
| 56 | |
| 57 | |
Andrea Frittoli | d199caf | 2017-07-18 17:12:38 +0100 | [diff] [blame] | 58 | def get_dynamic_provider_params(identity_version, admin_creds=None): |
| 59 | """Dynamic provider parameters setup from config |
| 60 | |
| 61 | This helper returns a dict of parameter that can be used to initialise |
| 62 | a `DynamicCredentialProvider` according to tempest configuration. |
| 63 | Parameters that are not configuration specific (name, network_resources) |
| 64 | are not returned. |
| 65 | |
| 66 | :param identity_version: 'v2' or 'v3' |
| 67 | :param admin_creds: An object of type `auth.Credentials`. If None, it |
| 68 | is built from the configuration file as well. |
junboli | 4ddc5ee | 2017-07-28 09:23:05 +0800 | [diff] [blame] | 69 | :return: A dict with the parameters |
Andrea Frittoli | d199caf | 2017-07-18 17:12:38 +0100 | [diff] [blame] | 70 | """ |
| 71 | _common_params = _get_common_provider_params(identity_version) |
| 72 | admin_creds = admin_creds or get_configured_admin_credentials( |
| 73 | fill_in=True, identity_version=identity_version) |
Andrea Frittoli | dcd9100 | 2017-07-18 11:34:13 +0100 | [diff] [blame] | 74 | if identity_version == 'v3': |
| 75 | endpoint_type = CONF.identity.v3_endpoint_type |
| 76 | elif identity_version == 'v2': |
| 77 | endpoint_type = CONF.identity.v2_admin_endpoint_type |
Andrea Frittoli | d199caf | 2017-07-18 17:12:38 +0100 | [diff] [blame] | 78 | return dict(_common_params, **dict([ |
| 79 | ('admin_creds', admin_creds), |
| 80 | ('identity_admin_domain_scope', CONF.identity.admin_domain_scope), |
| 81 | ('identity_admin_role', CONF.identity.admin_role), |
| 82 | ('extra_roles', CONF.auth.tempest_roles), |
| 83 | ('neutron_available', CONF.service_available.neutron), |
| 84 | ('project_network_cidr', CONF.network.project_network_cidr), |
| 85 | ('project_network_mask_bits', CONF.network.project_network_mask_bits), |
| 86 | ('public_network_id', CONF.network.public_network_id), |
| 87 | ('create_networks', (CONF.auth.create_isolated_networks and not |
| 88 | CONF.network.shared_physical_network)), |
ghanshyam | b20f7e6 | 2017-12-10 07:10:22 +0300 | [diff] [blame^] | 89 | ('resource_prefix', 'tempest'), |
Andrea Frittoli | dcd9100 | 2017-07-18 11:34:13 +0100 | [diff] [blame] | 90 | ('identity_admin_endpoint_type', endpoint_type) |
Andrea Frittoli | d199caf | 2017-07-18 17:12:38 +0100 | [diff] [blame] | 91 | ])) |
Andrea Frittoli (andreaf) | 848e348 | 2015-10-12 14:17:21 +0100 | [diff] [blame] | 92 | |
| 93 | |
Andrea Frittoli | d199caf | 2017-07-18 17:12:38 +0100 | [diff] [blame] | 94 | def get_preprov_provider_params(identity_version): |
| 95 | """Pre-provisioned provider parameters setup from config |
| 96 | |
| 97 | This helper returns a dict of parameter that can be used to initialise |
| 98 | a `PreProvisionedCredentialProvider` according to tempest configuration. |
| 99 | Parameters that are not configuration specific (name) are not returned. |
| 100 | |
| 101 | :param identity_version: 'v2' or 'v3' |
junboli | 4ddc5ee | 2017-07-28 09:23:05 +0800 | [diff] [blame] | 102 | :return: A dict with the parameters |
Andrea Frittoli | d199caf | 2017-07-18 17:12:38 +0100 | [diff] [blame] | 103 | """ |
| 104 | _common_params = _get_common_provider_params(identity_version) |
Andrea Frittoli (andreaf) | 848e348 | 2015-10-12 14:17:21 +0100 | [diff] [blame] | 105 | reseller_admin_role = CONF.object_storage.reseller_admin_role |
| 106 | return dict(_common_params, **dict([ |
| 107 | ('accounts_lock_dir', lockutils.get_lock_path(CONF)), |
| 108 | ('test_accounts_file', CONF.auth.test_accounts_file), |
| 109 | ('object_storage_operator_role', CONF.object_storage.operator_role), |
| 110 | ('object_storage_reseller_admin_role', reseller_admin_role) |
| 111 | ])) |
| 112 | |
| 113 | |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 114 | def get_credentials_provider(name, network_resources=None, |
| 115 | force_tenant_isolation=False, |
| 116 | identity_version=None): |
Andrea Frittoli | d199caf | 2017-07-18 17:12:38 +0100 | [diff] [blame] | 117 | """Return the right implementation of CredentialProvider based on config |
| 118 | |
| 119 | This helper returns the right implementation of CredentialProvider based on |
| 120 | config and on the value of force_tenant_isolation. |
| 121 | |
| 122 | :param name: When provided, it makes it possible to associate credential |
| 123 | artifacts back to the owner (test class). |
| 124 | :param network_resources: Dictionary of network resources to be allocated |
| 125 | for each test account. Only valid for the dynamic |
| 126 | credentials provider. |
| 127 | :param force_tenant_isolation: Always return a `DynamicCredentialProvider`, |
| 128 | regardless of the configuration. |
| 129 | :param identity_version: Use the specified identity API version, regardless |
| 130 | of the configuration. Valid values are 'v2', 'v3'. |
| 131 | """ |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 132 | # If a test requires a new account to work, it can have it via forcing |
| 133 | # dynamic credentials. A new account will be produced only for that test. |
| 134 | # In case admin credentials are not available for the account creation, |
| 135 | # the test should be skipped else it would fail. |
| 136 | identity_version = identity_version or CONF.identity.auth_version |
| 137 | if CONF.auth.use_dynamic_credentials or force_tenant_isolation: |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 138 | return dynamic_creds.DynamicCredentialProvider( |
| 139 | name=name, |
| 140 | network_resources=network_resources, |
Andrea Frittoli | d199caf | 2017-07-18 17:12:38 +0100 | [diff] [blame] | 141 | **get_dynamic_provider_params(identity_version)) |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 142 | else: |
Matthew Treinish | d89db1b | 2015-12-16 17:29:14 -0500 | [diff] [blame] | 143 | if CONF.auth.test_accounts_file: |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 144 | # Most params are not relevant for pre-created accounts |
| 145 | return preprov_creds.PreProvisionedCredentialProvider( |
Andrea Frittoli | d199caf | 2017-07-18 17:12:38 +0100 | [diff] [blame] | 146 | name=name, |
| 147 | **get_preprov_provider_params(identity_version)) |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 148 | else: |
Matthew Treinish | 40847ac | 2016-01-04 13:16:03 -0500 | [diff] [blame] | 149 | raise exceptions.InvalidConfiguration( |
| 150 | 'A valid credential provider is needed') |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 151 | |
| 152 | |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 153 | def is_admin_available(identity_version): |
Andrea Frittoli | 00c3588 | 2017-07-19 13:35:27 +0100 | [diff] [blame] | 154 | """Helper to check for admin credentials |
| 155 | |
| 156 | Helper function to check if a set of admin credentials is available so we |
| 157 | can do a single call from skip_checks. |
| 158 | This helper depends on identity_version as there may be admin credentials |
| 159 | available for v2 but not for v3. |
| 160 | |
| 161 | :param identity_version: 'v2' or 'v3' |
| 162 | """ |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 163 | is_admin = True |
| 164 | # If dynamic credentials is enabled admin will be available |
| 165 | if CONF.auth.use_dynamic_credentials: |
| 166 | return is_admin |
| 167 | # Check whether test accounts file has the admin specified or not |
Matthew Treinish | d89db1b | 2015-12-16 17:29:14 -0500 | [diff] [blame] | 168 | elif CONF.auth.test_accounts_file: |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 169 | check_accounts = preprov_creds.PreProvisionedCredentialProvider( |
Andrea Frittoli | d199caf | 2017-07-18 17:12:38 +0100 | [diff] [blame] | 170 | name='check_admin', |
| 171 | **get_preprov_provider_params(identity_version)) |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 172 | if not check_accounts.admin_available(): |
| 173 | is_admin = False |
| 174 | else: |
| 175 | try: |
Andrea Frittoli (andreaf) | bc0a7a6 | 2016-05-26 19:31:49 +0100 | [diff] [blame] | 176 | get_configured_admin_credentials(fill_in=False, |
| 177 | identity_version=identity_version) |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 178 | except exceptions.InvalidConfiguration: |
| 179 | is_admin = False |
| 180 | return is_admin |
| 181 | |
| 182 | |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 183 | def is_alt_available(identity_version): |
Andrea Frittoli | 00c3588 | 2017-07-19 13:35:27 +0100 | [diff] [blame] | 184 | """Helper to check for alt credentials |
| 185 | |
| 186 | Helper function to check if a second set of credentials is available (aka |
| 187 | alt credentials) so we can do a single call from skip_checks. |
| 188 | This helper depends on identity_version as there may be alt credentials |
| 189 | available for v2 but not for v3. |
| 190 | |
| 191 | :param identity_version: 'v2' or 'v3' |
| 192 | """ |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 193 | # If dynamic credentials is enabled alt will be available |
| 194 | if CONF.auth.use_dynamic_credentials: |
| 195 | return True |
| 196 | # Check whether test accounts file has the admin specified or not |
Matthew Treinish | d89db1b | 2015-12-16 17:29:14 -0500 | [diff] [blame] | 197 | if CONF.auth.test_accounts_file: |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 198 | check_accounts = preprov_creds.PreProvisionedCredentialProvider( |
Andrea Frittoli | d199caf | 2017-07-18 17:12:38 +0100 | [diff] [blame] | 199 | name='check_alt', |
| 200 | **get_preprov_provider_params(identity_version)) |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 201 | else: |
Matthew Treinish | 40847ac | 2016-01-04 13:16:03 -0500 | [diff] [blame] | 202 | raise exceptions.InvalidConfiguration( |
| 203 | 'A valid credential provider is needed') |
| 204 | |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 205 | try: |
| 206 | if not check_accounts.is_multi_user(): |
| 207 | return False |
| 208 | else: |
| 209 | return True |
| 210 | except exceptions.InvalidConfiguration: |
| 211 | return False |
| 212 | |
| 213 | # === Credentials |
| 214 | |
| 215 | # Type of credentials available from configuration |
| 216 | CREDENTIAL_TYPES = { |
| 217 | 'identity_admin': ('auth', 'admin'), |
| 218 | 'user': ('identity', None), |
| 219 | 'alt_user': ('identity', 'alt') |
| 220 | } |
| 221 | |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 222 | |
Andrea Frittoli (andreaf) | bc0a7a6 | 2016-05-26 19:31:49 +0100 | [diff] [blame] | 223 | def get_configured_admin_credentials(fill_in=True, identity_version=None): |
Andrea Frittoli | 00c3588 | 2017-07-19 13:35:27 +0100 | [diff] [blame] | 224 | """Get admin credentials from the config file |
| 225 | |
| 226 | Read credentials from configuration, builds a Credentials object based on |
| 227 | the specified or configured version |
| 228 | |
| 229 | :param fill_in: If True, a request to the Token API is submitted, and the |
| 230 | credential object is filled in with all names and IDs from |
| 231 | the token API response. |
| 232 | :param identity_version: The identity version to talk to and the type of |
| 233 | credentials object to be created. 'v2' or 'v3'. |
| 234 | :returns: An object of a sub-type of `auth.Credentials` |
| 235 | """ |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 236 | identity_version = identity_version or CONF.identity.auth_version |
| 237 | |
| 238 | if identity_version not in ('v2', 'v3'): |
| 239 | raise exceptions.InvalidConfiguration( |
| 240 | 'Unsupported auth version: %s' % identity_version) |
| 241 | |
Andrea Frittoli (andreaf) | bc0a7a6 | 2016-05-26 19:31:49 +0100 | [diff] [blame] | 242 | conf_attributes = ['username', 'password', |
| 243 | 'project_name'] |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 244 | |
| 245 | if identity_version == 'v3': |
| 246 | conf_attributes.append('domain_name') |
| 247 | # Read the parts of credentials from config |
Andrea Frittoli | cad70e2 | 2017-08-16 13:19:04 +0100 | [diff] [blame] | 248 | params = config.service_client_config() |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 249 | for attr in conf_attributes: |
Andrea Frittoli (andreaf) | bc0a7a6 | 2016-05-26 19:31:49 +0100 | [diff] [blame] | 250 | params[attr] = getattr(CONF.auth, 'admin_' + attr) |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 251 | # Build and validate credentials. We are reading configured credentials, |
| 252 | # so validate them even if fill_in is False |
| 253 | credentials = get_credentials(fill_in=fill_in, |
| 254 | identity_version=identity_version, **params) |
| 255 | if not fill_in: |
| 256 | if not credentials.is_valid(): |
Andrea Frittoli (andreaf) | bc0a7a6 | 2016-05-26 19:31:49 +0100 | [diff] [blame] | 257 | msg = ("The admin credentials are incorrectly set in the config " |
| 258 | "file for identity version %s. Double check that all " |
| 259 | "required values are assigned.") |
| 260 | raise exceptions.InvalidConfiguration(msg % identity_version) |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 261 | return credentials |
| 262 | |
| 263 | |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 264 | def get_credentials(fill_in=True, identity_version=None, **kwargs): |
Andrea Frittoli | 00c3588 | 2017-07-19 13:35:27 +0100 | [diff] [blame] | 265 | """Get credentials from dict based on config |
| 266 | |
| 267 | Wrapper around auth.get_credentials to use the configured identity version |
| 268 | if none is specified. |
| 269 | |
| 270 | :param fill_in: If True, a request to the Token API is submitted, and the |
| 271 | credential object is filled in with all names and IDs from |
| 272 | the token API response. |
| 273 | :param identity_version: The identity version to talk to and the type of |
| 274 | credentials object to be created. 'v2' or 'v3'. |
| 275 | :param kwargs: Attributes to be used to build the Credentials object. |
| 276 | :returns: An object of a sub-type of `auth.Credentials` |
| 277 | """ |
Andrea Frittoli | cad70e2 | 2017-08-16 13:19:04 +0100 | [diff] [blame] | 278 | params = dict(config.service_client_config(), **kwargs) |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 279 | identity_version = identity_version or CONF.identity.auth_version |
| 280 | # In case of "v3" add the domain from config if not specified |
Andrea Frittoli (andreaf) | 100d18d | 2016-05-05 23:34:52 +0100 | [diff] [blame] | 281 | # To honour the "default_credentials_domain_name", if not domain |
| 282 | # field is specified at all, add it the credential dict. |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 283 | if identity_version == 'v3': |
| 284 | domain_fields = set(x for x in auth.KeystoneV3Credentials.ATTRIBUTES |
| 285 | if 'domain' in x) |
| 286 | if not domain_fields.intersection(kwargs.keys()): |
| 287 | domain_name = CONF.auth.default_credentials_domain_name |
Andrea Frittoli (andreaf) | 100d18d | 2016-05-05 23:34:52 +0100 | [diff] [blame] | 288 | # NOTE(andreaf) Setting domain_name implicitly sets user and |
| 289 | # project domain names, if they are None |
| 290 | params['domain_name'] = domain_name |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 291 | |
| 292 | auth_url = CONF.identity.uri_v3 |
| 293 | else: |
| 294 | auth_url = CONF.identity.uri |
| 295 | return auth.get_credentials(auth_url, |
| 296 | fill_in=fill_in, |
| 297 | identity_version=identity_version, |
| 298 | **params) |
| 299 | |
| 300 | # === Credential / client managers |
| 301 | |
| 302 | |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 303 | class AdminManager(clients.Manager): |
Ken'ichi Ohmichi | cb67d2d | 2015-11-19 08:23:22 +0000 | [diff] [blame] | 304 | """Manager that uses admin credentials for its managed client objects""" |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 305 | |
Jordan Pittier | e4be907 | 2017-01-04 19:17:35 +0100 | [diff] [blame] | 306 | def __init__(self): |
Andrea Frittoli (andreaf) | 290b3e1 | 2015-10-08 10:25:02 +0100 | [diff] [blame] | 307 | super(AdminManager, self).__init__( |
Jordan Pittier | e4be907 | 2017-01-04 19:17:35 +0100 | [diff] [blame] | 308 | credentials=get_configured_admin_credentials()) |