blob: e10817bb528124dca8efe3a19b04ec0f53ce546e [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):
31 yaml_file = open(path, 'r')
32 accounts = yaml.load(yaml_file)
33 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)
48 self.accounts_dir = os.path.join(CONF.oslo_concurrency.lock_path, 'test_accounts')
49 self.isolated_creds = {}
50
51 @classmethod
52 def _append_role(cls, role, account_hash, hash_dict):
53 if role in hash_dict['roles']:
54 hash_dict['roles'][role].append(account_hash)
55 else:
56 hash_dict['roles'][role] = [account_hash]
57 return hash_dict
58
59 @classmethod
60 def get_hash_dict(cls, accounts):
61 hash_dict = {'roles': {}, 'creds': {}}
62 # Loop over the accounts read from the yaml file
63 for account in accounts:
64 roles = []
65 types = []
66 if 'roles' in account:
67 roles = account.pop('roles')
68 if 'types' in account:
69 types = account.pop('types')
70 temp_hash = hashlib.md5()
71 temp_hash.update(str(account))
72 temp_hash_key = temp_hash.hexdigest()
73 hash_dict['creds'][temp_hash_key] = account
74 for role in roles:
75 hash_dict = cls._append_role(role, temp_hash_key,
76 hash_dict)
77 # If types are set for the account append the matching role
78 # subdict with the hash
79 for type in types:
80 if type == 'admin':
81 hash_dict = cls._append_role(CONF.identity.admin_role,
82 temp_hash_key, hash_dict)
83 elif type == 'operator':
84 hash_dict = cls._append_role(
85 CONF.object_storage.operator_role, temp_hash_key,
86 hash_dict)
87 elif type == 'reseller_admin':
88 hash_dict = cls._append_role(
89 CONF.object_storage.reseller_admin_role,
90 temp_hash_key,
91 hash_dict)
92 return hash_dict
93
94 def is_multi_user(self):
95 # Default credentials is not a valid option with locking Account
96 if self.use_default_creds:
97 raise exceptions.InvalidConfiguration(
98 "Account file %s doesn't exist" % CONF.auth.test_accounts_file)
99 else:
100 return len(self.hash_dict['creds']) > 1
101
102 def is_multi_tenant(self):
103 return self.is_multi_user()
104
105 def _create_hash_file(self, hash_string):
106 path = os.path.join(os.path.join(self.accounts_dir, hash_string))
107 if not os.path.isfile(path):
108 with open(path, 'w') as fd:
109 fd.write(self.name)
110 return True
111 return False
112
113 @lockutils.synchronized('test_accounts_io', external=True)
114 def _get_free_hash(self, hashes):
115 # Cast as a list because in some edge cases a set will be passed in
116 hashes = list(hashes)
117 if not os.path.isdir(self.accounts_dir):
118 os.mkdir(self.accounts_dir)
119 # Create File from first hash (since none are in use)
120 self._create_hash_file(hashes[0])
121 return hashes[0]
122 names = []
123 for _hash in hashes:
124 res = self._create_hash_file(_hash)
125 if res:
126 return _hash
127 else:
128 path = os.path.join(os.path.join(self.accounts_dir,
129 _hash))
130 with open(path, 'r') as fd:
131 names.append(fd.read())
132 msg = ('Insufficient number of users provided. %s have allocated all '
133 'the credentials for this allocation request' % ','.join(names))
134 raise exceptions.InvalidConfiguration(msg)
135
136 def _get_match_hash_list(self, roles=None):
137 hashes = []
138 if roles:
139 # Loop over all the creds for each role in the subdict and generate
140 # a list of cred lists for each role
141 for role in roles:
142 temp_hashes = self.hash_dict['roles'].get(role, None)
143 if not temp_hashes:
144 raise exceptions.InvalidConfiguration(
145 "No credentials with role: %s specified in the "
146 "accounts ""file" % role)
147 hashes.append(temp_hashes)
148 # Take the list of lists and do a boolean and between each list to
149 # find the creds which fall under all the specified roles
150 temp_list = set(hashes[0])
151 for hash_list in hashes[1:]:
152 temp_list = temp_list & set(hash_list)
153 hashes = temp_list
154 else:
155 hashes = self.hash_dict['creds'].keys()
156 # NOTE(mtreinish): admin is a special case because of the increased
157 # privlege set which could potentially cause issues on tests where that
158 # is not expected. So unless the admin role isn't specified do not
159 # allocate admin.
160 admin_hashes = self.hash_dict['roles'].get(CONF.identity.admin_role,
161 None)
162 if ((not roles or CONF.identity.admin_role not in roles) and
163 admin_hashes):
164 useable_hashes = [x for x in hashes if x not in admin_hashes]
165 else:
166 useable_hashes = hashes
167 return useable_hashes
168
169 def _get_creds(self, roles=None):
170 if self.use_default_creds:
171 raise exceptions.InvalidConfiguration(
172 "Account file %s doesn't exist" % CONF.auth.test_accounts_file)
173 useable_hashes = self._get_match_hash_list(roles)
174 free_hash = self._get_free_hash(useable_hashes)
175 return self.hash_dict['creds'][free_hash]
176
177 @lockutils.synchronized('test_accounts_io', external=True)
178 def remove_hash(self, hash_string):
179 hash_path = os.path.join(self.accounts_dir, hash_string)
180 if not os.path.isfile(hash_path):
181 LOG.warning('Expected an account lock file %s to remove, but '
182 'one did not exist' % hash_path)
183 else:
184 os.remove(hash_path)
185 if not os.listdir(self.accounts_dir):
186 os.rmdir(self.accounts_dir)
187
188 def get_hash(self, creds):
189 for _hash in self.hash_dict['creds']:
190 # Comparing on the attributes that are expected in the YAML
191 if all([getattr(creds, k) == self.hash_dict['creds'][_hash][k] for
192 k in creds.get_init_attributes()]):
193 return _hash
194 raise AttributeError('Invalid credentials %s' % creds)
195
196 def remove_credentials(self, creds):
197 _hash = self.get_hash(creds)
198 self.remove_hash(_hash)
199
200 def get_primary_creds(self):
201 if self.isolated_creds.get('primary'):
202 return self.isolated_creds.get('primary')
203 creds = self._get_creds()
204 primary_credential = cred_provider.get_credentials(**creds)
205 self.isolated_creds['primary'] = primary_credential
206 return primary_credential
207
208 def get_alt_creds(self):
209 if self.isolated_creds.get('alt'):
210 return self.isolated_creds.get('alt')
211 creds = self._get_creds()
212 alt_credential = cred_provider.get_credentials(**creds)
213 self.isolated_creds['alt'] = alt_credential
214 return alt_credential
215
216 def get_creds_by_roles(self, roles, force_new=False):
217 roles = list(set(roles))
218 exist_creds = self.isolated_creds.get(str(roles), None)
219 # The force kwarg is used to allocate an additional set of creds with
220 # the same role list. The index used for the previously allocation
221 # in the isolated_creds dict will be moved.
222 if exist_creds and not force_new:
223 return exist_creds
224 elif exist_creds and force_new:
225 new_index = str(roles) + '-' + str(len(self.isolated_creds))
226 self.isolated_creds[new_index] = exist_creds
227 creds = self._get_creds(roles=roles)
228 role_credential = cred_provider.get_credentials(**creds)
229 self.isolated_creds[str(roles)] = role_credential
230 return role_credential
231
232 def clear_isolated_creds(self):
233 for creds in self.isolated_creds.values():
234 self.remove_credentials(creds)
235
236 def get_admin_creds(self):
237 return self.get_creds_by_roles([CONF.identity.admin_role])
238
239 def is_role_available(self, role):
240 if self.use_default_creds:
241 return False
242 else:
243 if self.hash_dict['roles'].get(role):
244 return True
245 return False
246
247 def admin_available(self):
248 return self.is_role_available(CONF.identity.admin_role)
249
250
251class NotLockingAccounts(Accounts):
252 """Credentials provider which always returns the first and second
253 configured accounts as primary and alt users.
254 This credential provider can be used in case of serial test execution
255 to preserve the current behaviour of the serial tempest run.
256 """
257
258 def _unique_creds(self, cred_arg=None):
259 """Verify that the configured credentials are valid and distinct """
260 if self.use_default_creds:
261 try:
262 user = self.get_primary_creds()
263 alt_user = self.get_alt_creds()
264 return getattr(user, cred_arg) != getattr(alt_user, cred_arg)
265 except exceptions.InvalidCredentials as ic:
266 msg = "At least one of the configured credentials is " \
267 "not valid: %s" % ic.message
268 raise exceptions.InvalidConfiguration(msg)
269 else:
270 # TODO(andreaf) Add a uniqueness check here
271 return len(self.hash_dict['creds']) > 1
272
273 def is_multi_user(self):
274 return self._unique_creds('username')
275
276 def is_multi_tenant(self):
277 return self._unique_creds('tenant_id')
278
279 def get_creds(self, id, roles=None):
280 try:
281 hashes = self._get_match_hash_list(roles)
282 # No need to sort the dict as within the same python process
283 # the HASH seed won't change, so subsequent calls to keys()
284 # will return the same result
285 _hash = hashes[id]
286 except IndexError:
287 msg = 'Insufficient number of users provided'
288 raise exceptions.InvalidConfiguration(msg)
289 return self.hash_dict['creds'][_hash]
290
291 def get_primary_creds(self):
292 if self.isolated_creds.get('primary'):
293 return self.isolated_creds.get('primary')
294 if not self.use_default_creds:
295 creds = self.get_creds(0)
296 primary_credential = cred_provider.get_credentials(**creds)
297 else:
298 primary_credential = cred_provider.get_configured_credentials(
299 'user')
300 self.isolated_creds['primary'] = primary_credential
301 return primary_credential
302
303 def get_alt_creds(self):
304 if self.isolated_creds.get('alt'):
305 return self.isolated_creds.get('alt')
306 if not self.use_default_creds:
307 creds = self.get_creds(1)
308 alt_credential = cred_provider.get_credentials(**creds)
309 else:
310 alt_credential = cred_provider.get_configured_credentials(
311 'alt_user')
312 self.isolated_creds['alt'] = alt_credential
313 return alt_credential
314
315 def clear_isolated_creds(self):
316 self.isolated_creds = {}
317
318 def get_admin_creds(self):
319 if not self.use_default_creds:
320 return self.get_creds_by_roles([CONF.identity.admin_role])
321 else:
322 creds = cred_provider.get_configured_credentials(
323 "identity_admin", fill_in=False)
324 self.isolated_creds['admin'] = creds
325 return creds
326
327 def get_creds_by_roles(self, roles, force_new=False):
328 roles = list(set(roles))
329 exist_creds = self.isolated_creds.get(str(roles), None)
330 index = 0
331 if exist_creds and not force_new:
332 return exist_creds
333 elif exist_creds and force_new:
334 new_index = str(roles) + '-' + str(len(self.isolated_creds))
335 self.isolated_creds[new_index] = exist_creds
336 # Figure out how many existing creds for this roles set are present
337 # use this as the index the returning hash list to ensure separate
338 # creds are returned with force_new being True
339 for creds_names in self.isolated_creds:
340 if str(roles) in creds_names:
341 index = index + 1
342 if not self.use_default_creds:
343 creds = self.get_creds(index, roles=roles)
344 role_credential = cred_provider.get_credentials(**creds)
345 self.isolated_creds[str(roles)] = role_credential
346 else:
347 msg = "Default credentials can not be used with specifying "\
348 "credentials by roles"
349 raise exceptions.InvalidConfiguration(msg)
350 return role_credential