blob: 644073979011aa8c41ca06990a8ca373aa04abe4 [file] [log] [blame]
Maru Newbyb096d9f2015-03-09 18:54:54 +00001# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import hashlib
16import os
17
Ihar Hrachyshkac695f9f2015-02-26 23:26:41 +010018from oslo_concurrency import lockutils
19from oslo_log import log as logging
Maru Newbyb096d9f2015-03-09 18:54:54 +000020import yaml
21
22from neutron.tests.tempest.common import cred_provider
23from neutron.tests.tempest import config
24from neutron.tests.tempest import exceptions
Maru Newbyb096d9f2015-03-09 18:54:54 +000025
26CONF = config.CONF
27LOG = logging.getLogger(__name__)
28
29
30def read_accounts_yaml(path):
xiexs6be90cc2015-11-27 03:41:52 -050031 with open(path, 'r') as yaml_file:
32 accounts = yaml.load(yaml_file)
Maru Newbyb096d9f2015-03-09 18:54:54 +000033 return accounts
34
35
36class Accounts(cred_provider.CredentialProvider):
37
38 def __init__(self, name):
39 super(Accounts, self).__init__(name)
40 self.name = name
41 if os.path.isfile(CONF.auth.test_accounts_file):
42 accounts = read_accounts_yaml(CONF.auth.test_accounts_file)
43 self.use_default_creds = False
44 else:
45 accounts = {}
46 self.use_default_creds = True
47 self.hash_dict = self.get_hash_dict(accounts)
Maru Newby5690a352015-03-13 18:46:40 +000048 # FIXME(dhellmann): The configuration option is not part of
49 # the API of the library, because if we change the option name
50 # or group it will break this use. Tempest needs to set this
51 # value somewhere that it owns, and then use
52 # lockutils.set_defaults() to tell oslo.concurrency what value
53 # to use.
54 self.accounts_dir = os.path.join(CONF.oslo_concurrency.lock_path,
55 'test_accounts')
Maru Newbyb096d9f2015-03-09 18:54:54 +000056 self.isolated_creds = {}
57
58 @classmethod
59 def _append_role(cls, role, account_hash, hash_dict):
60 if role in hash_dict['roles']:
61 hash_dict['roles'][role].append(account_hash)
62 else:
63 hash_dict['roles'][role] = [account_hash]
64 return hash_dict
65
66 @classmethod
67 def get_hash_dict(cls, accounts):
68 hash_dict = {'roles': {}, 'creds': {}}
69 # Loop over the accounts read from the yaml file
70 for account in accounts:
71 roles = []
72 types = []
73 if 'roles' in account:
74 roles = account.pop('roles')
75 if 'types' in account:
76 types = account.pop('types')
77 temp_hash = hashlib.md5()
78 temp_hash.update(str(account))
79 temp_hash_key = temp_hash.hexdigest()
80 hash_dict['creds'][temp_hash_key] = account
81 for role in roles:
82 hash_dict = cls._append_role(role, temp_hash_key,
83 hash_dict)
84 # If types are set for the account append the matching role
85 # subdict with the hash
86 for type in types:
87 if type == 'admin':
88 hash_dict = cls._append_role(CONF.identity.admin_role,
89 temp_hash_key, hash_dict)
90 elif type == 'operator':
91 hash_dict = cls._append_role(
92 CONF.object_storage.operator_role, temp_hash_key,
93 hash_dict)
94 elif type == 'reseller_admin':
95 hash_dict = cls._append_role(
96 CONF.object_storage.reseller_admin_role,
97 temp_hash_key,
98 hash_dict)
99 return hash_dict
100
101 def is_multi_user(self):
102 # Default credentials is not a valid option with locking Account
103 if self.use_default_creds:
104 raise exceptions.InvalidConfiguration(
105 "Account file %s doesn't exist" % CONF.auth.test_accounts_file)
106 else:
107 return len(self.hash_dict['creds']) > 1
108
109 def is_multi_tenant(self):
110 return self.is_multi_user()
111
112 def _create_hash_file(self, hash_string):
113 path = os.path.join(os.path.join(self.accounts_dir, hash_string))
114 if not os.path.isfile(path):
115 with open(path, 'w') as fd:
116 fd.write(self.name)
117 return True
118 return False
119
120 @lockutils.synchronized('test_accounts_io', external=True)
121 def _get_free_hash(self, hashes):
122 # Cast as a list because in some edge cases a set will be passed in
123 hashes = list(hashes)
124 if not os.path.isdir(self.accounts_dir):
125 os.mkdir(self.accounts_dir)
126 # Create File from first hash (since none are in use)
127 self._create_hash_file(hashes[0])
128 return hashes[0]
129 names = []
130 for _hash in hashes:
131 res = self._create_hash_file(_hash)
132 if res:
133 return _hash
134 else:
135 path = os.path.join(os.path.join(self.accounts_dir,
136 _hash))
137 with open(path, 'r') as fd:
138 names.append(fd.read())
139 msg = ('Insufficient number of users provided. %s have allocated all '
140 'the credentials for this allocation request' % ','.join(names))
141 raise exceptions.InvalidConfiguration(msg)
142
143 def _get_match_hash_list(self, roles=None):
144 hashes = []
145 if roles:
146 # Loop over all the creds for each role in the subdict and generate
147 # a list of cred lists for each role
148 for role in roles:
149 temp_hashes = self.hash_dict['roles'].get(role, None)
150 if not temp_hashes:
151 raise exceptions.InvalidConfiguration(
152 "No credentials with role: %s specified in the "
153 "accounts ""file" % role)
154 hashes.append(temp_hashes)
155 # Take the list of lists and do a boolean and between each list to
156 # find the creds which fall under all the specified roles
157 temp_list = set(hashes[0])
158 for hash_list in hashes[1:]:
159 temp_list = temp_list & set(hash_list)
160 hashes = temp_list
161 else:
162 hashes = self.hash_dict['creds'].keys()
163 # NOTE(mtreinish): admin is a special case because of the increased
164 # privlege set which could potentially cause issues on tests where that
165 # is not expected. So unless the admin role isn't specified do not
166 # allocate admin.
167 admin_hashes = self.hash_dict['roles'].get(CONF.identity.admin_role,
168 None)
169 if ((not roles or CONF.identity.admin_role not in roles) and
170 admin_hashes):
171 useable_hashes = [x for x in hashes if x not in admin_hashes]
172 else:
173 useable_hashes = hashes
174 return useable_hashes
175
176 def _get_creds(self, roles=None):
177 if self.use_default_creds:
178 raise exceptions.InvalidConfiguration(
179 "Account file %s doesn't exist" % CONF.auth.test_accounts_file)
180 useable_hashes = self._get_match_hash_list(roles)
181 free_hash = self._get_free_hash(useable_hashes)
182 return self.hash_dict['creds'][free_hash]
183
184 @lockutils.synchronized('test_accounts_io', external=True)
185 def remove_hash(self, hash_string):
186 hash_path = os.path.join(self.accounts_dir, hash_string)
187 if not os.path.isfile(hash_path):
188 LOG.warning('Expected an account lock file %s to remove, but '
189 'one did not exist' % hash_path)
190 else:
191 os.remove(hash_path)
192 if not os.listdir(self.accounts_dir):
193 os.rmdir(self.accounts_dir)
194
195 def get_hash(self, creds):
196 for _hash in self.hash_dict['creds']:
197 # Comparing on the attributes that are expected in the YAML
198 if all([getattr(creds, k) == self.hash_dict['creds'][_hash][k] for
199 k in creds.get_init_attributes()]):
200 return _hash
201 raise AttributeError('Invalid credentials %s' % creds)
202
203 def remove_credentials(self, creds):
204 _hash = self.get_hash(creds)
205 self.remove_hash(_hash)
206
207 def get_primary_creds(self):
208 if self.isolated_creds.get('primary'):
209 return self.isolated_creds.get('primary')
210 creds = self._get_creds()
211 primary_credential = cred_provider.get_credentials(**creds)
212 self.isolated_creds['primary'] = primary_credential
213 return primary_credential
214
215 def get_alt_creds(self):
216 if self.isolated_creds.get('alt'):
217 return self.isolated_creds.get('alt')
218 creds = self._get_creds()
219 alt_credential = cred_provider.get_credentials(**creds)
220 self.isolated_creds['alt'] = alt_credential
221 return alt_credential
222
223 def get_creds_by_roles(self, roles, force_new=False):
224 roles = list(set(roles))
225 exist_creds = self.isolated_creds.get(str(roles), None)
226 # The force kwarg is used to allocate an additional set of creds with
227 # the same role list. The index used for the previously allocation
228 # in the isolated_creds dict will be moved.
229 if exist_creds and not force_new:
230 return exist_creds
231 elif exist_creds and force_new:
232 new_index = str(roles) + '-' + str(len(self.isolated_creds))
233 self.isolated_creds[new_index] = exist_creds
234 creds = self._get_creds(roles=roles)
235 role_credential = cred_provider.get_credentials(**creds)
236 self.isolated_creds[str(roles)] = role_credential
237 return role_credential
238
239 def clear_isolated_creds(self):
240 for creds in self.isolated_creds.values():
241 self.remove_credentials(creds)
242
243 def get_admin_creds(self):
244 return self.get_creds_by_roles([CONF.identity.admin_role])
245
246 def is_role_available(self, role):
247 if self.use_default_creds:
248 return False
249 else:
250 if self.hash_dict['roles'].get(role):
251 return True
252 return False
253
254 def admin_available(self):
255 return self.is_role_available(CONF.identity.admin_role)
256
257
258class NotLockingAccounts(Accounts):
259 """Credentials provider which always returns the first and second
260 configured accounts as primary and alt users.
261 This credential provider can be used in case of serial test execution
262 to preserve the current behaviour of the serial tempest run.
263 """
264
265 def _unique_creds(self, cred_arg=None):
266 """Verify that the configured credentials are valid and distinct """
267 if self.use_default_creds:
268 try:
269 user = self.get_primary_creds()
270 alt_user = self.get_alt_creds()
271 return getattr(user, cred_arg) != getattr(alt_user, cred_arg)
272 except exceptions.InvalidCredentials as ic:
273 msg = "At least one of the configured credentials is " \
Martin Royef104452015-06-18 13:45:02 -0400274 "not valid: %s" % ic
Maru Newbyb096d9f2015-03-09 18:54:54 +0000275 raise exceptions.InvalidConfiguration(msg)
276 else:
277 # TODO(andreaf) Add a uniqueness check here
278 return len(self.hash_dict['creds']) > 1
279
280 def is_multi_user(self):
281 return self._unique_creds('username')
282
283 def is_multi_tenant(self):
284 return self._unique_creds('tenant_id')
285
286 def get_creds(self, id, roles=None):
287 try:
288 hashes = self._get_match_hash_list(roles)
289 # No need to sort the dict as within the same python process
290 # the HASH seed won't change, so subsequent calls to keys()
291 # will return the same result
292 _hash = hashes[id]
293 except IndexError:
294 msg = 'Insufficient number of users provided'
295 raise exceptions.InvalidConfiguration(msg)
296 return self.hash_dict['creds'][_hash]
297
298 def get_primary_creds(self):
299 if self.isolated_creds.get('primary'):
300 return self.isolated_creds.get('primary')
301 if not self.use_default_creds:
302 creds = self.get_creds(0)
303 primary_credential = cred_provider.get_credentials(**creds)
304 else:
305 primary_credential = cred_provider.get_configured_credentials(
306 'user')
307 self.isolated_creds['primary'] = primary_credential
308 return primary_credential
309
310 def get_alt_creds(self):
311 if self.isolated_creds.get('alt'):
312 return self.isolated_creds.get('alt')
313 if not self.use_default_creds:
314 creds = self.get_creds(1)
315 alt_credential = cred_provider.get_credentials(**creds)
316 else:
317 alt_credential = cred_provider.get_configured_credentials(
318 'alt_user')
319 self.isolated_creds['alt'] = alt_credential
320 return alt_credential
321
322 def clear_isolated_creds(self):
323 self.isolated_creds = {}
324
325 def get_admin_creds(self):
326 if not self.use_default_creds:
327 return self.get_creds_by_roles([CONF.identity.admin_role])
328 else:
329 creds = cred_provider.get_configured_credentials(
330 "identity_admin", fill_in=False)
331 self.isolated_creds['admin'] = creds
332 return creds
333
334 def get_creds_by_roles(self, roles, force_new=False):
335 roles = list(set(roles))
336 exist_creds = self.isolated_creds.get(str(roles), None)
337 index = 0
338 if exist_creds and not force_new:
339 return exist_creds
340 elif exist_creds and force_new:
341 new_index = str(roles) + '-' + str(len(self.isolated_creds))
342 self.isolated_creds[new_index] = exist_creds
343 # Figure out how many existing creds for this roles set are present
344 # use this as the index the returning hash list to ensure separate
345 # creds are returned with force_new being True
346 for creds_names in self.isolated_creds:
347 if str(roles) in creds_names:
348 index = index + 1
349 if not self.use_default_creds:
350 creds = self.get_creds(index, roles=roles)
351 role_credential = cred_provider.get_credentials(**creds)
352 self.isolated_creds[str(roles)] = role_credential
353 else:
354 msg = "Default credentials can not be used with specifying "\
355 "credentials by roles"
356 raise exceptions.InvalidConfiguration(msg)
357 return role_credential