Merge "Patch to add test that checks vGPU in instance" into mcp/caracal
diff --git a/tempest/lib/common/preprov_creds.py b/tempest/lib/common/preprov_creds.py
index cdc66bf..54f9cd5 100644
--- a/tempest/lib/common/preprov_creds.py
+++ b/tempest/lib/common/preprov_creds.py
@@ -93,6 +93,8 @@
             object_storage_reseller_admin_role)
         self.accounts_dir = accounts_lock_dir
         self._creds = {}
+        self._used_projects_file = os.path.join(self.accounts_dir,
+                                                'used_projects')
 
     @classmethod
     def _append_role(cls, role, account_hash, hash_dict):
@@ -197,19 +199,47 @@
             return True
         return False
 
+    def _process_project(self, hash_, used_projects=None, use=True):
+        if used_projects is None:
+            used_projects = self._get_used_projects()
+        if 'tenant_name' in self.hash_dict['creds'][hash_]:
+            project = self.hash_dict['creds'][hash_]['tenant_name']
+        else:
+            project = self.hash_dict['creds'][hash_]['project_name']
+        method = 'append' if use else 'remove'
+        getattr(used_projects, method)(project)
+        with open(self._used_projects_file, 'w') as file:
+            file.write('\n'.join(used_projects) + '\n')
+
+    def _get_used_projects(self):
+        used_projects = []
+        try:
+            with open(self._used_projects_file) as file:
+                for line in file:
+                    line = line.strip()
+                    if line:
+                        used_projects.append(line)
+        except FileNotFoundError:
+            pass
+        return used_projects
+
     @lockutils.synchronized('test_accounts_io', external=True)
     def _get_free_hash(self, hashes):
+        used_projects = self._get_used_projects()
+        hashes = self._exclude_used_projects(hashes, used_projects)
         # Cast as a list because in some edge cases a set will be passed in
         hashes = list(hashes)
         if not os.path.isdir(self.accounts_dir):
             os.mkdir(self.accounts_dir)
             # Create File from first hash (since none are in use)
             self._create_hash_file(hashes[0])
+            self._process_project(hashes[0], used_projects)
             return hashes[0]
         names = []
         for _hash in hashes:
             res = self._create_hash_file(_hash)
             if res:
+                self._process_project(_hash, used_projects)
                 return _hash
             else:
                 path = os.path.join(self.accounts_dir, _hash)
@@ -247,7 +277,6 @@
             hashes = temp_list
         else:
             hashes = self.hash_dict['creds'].keys()
-        hashes = self._exclude_used_projects(hashes)
         # NOTE(mtreinish): admin is a special case because of the increased
         # privilege set which could potentially cause issues on tests where
         # that is not expected. So unless the admin role isn't specified do
@@ -280,6 +309,7 @@
 
     @lockutils.synchronized('test_accounts_io', external=True)
     def remove_hash(self, hash_string):
+        self._process_project(hash_string, use=False)
         hash_path = os.path.join(self.accounts_dir, hash_string)
         if not os.path.isfile(hash_path):
             LOG.warning('Expected an account lock file %s to remove, but '
@@ -403,7 +433,7 @@
         # TODO(gmann): Implement alt reader hash.
         return
 
-    def get_creds_by_roles(self, roles, force_new=False, scope=None):
+    def get_creds_by_roles(self, roles, force_new=True, scope=None):
         roles = list(set(roles))
         exist_creds = self._creds.get(str(roles).encode(
             'utf-8'), None)
@@ -488,8 +518,8 @@
         hash_dict['projects'][key_to_add].append(account_hash)
         return hash_dict
 
-    def _exclude_used_projects(self, hashes):
+    def _exclude_used_projects(self, hashes, used_projects):
         excluded_accounts = []
-        for project in [cred.tenant_name for cred in self._creds.values()]:
+        for project in used_projects:
             excluded_accounts.extend(self.hash_dict['projects'][project])
-        return hashes - set(excluded_accounts)
+        return set(hashes) - set(excluded_accounts)
diff --git a/tempest/tests/lib/common/test_preprov_creds.py b/tempest/tests/lib/common/test_preprov_creds.py
index f6d3778..c1a8e72 100644
--- a/tempest/tests/lib/common/test_preprov_creds.py
+++ b/tempest/tests/lib/common/test_preprov_creds.py
@@ -116,6 +116,17 @@
             hash_list.append(temp_hash)
         return hash_list
 
+    def _remove_hash(self, hash_list, hash_index):
+        test_account_class = preprov_creds.PreProvisionedCredentialProvider(
+            **self.fixed_params)
+        remove_mock = self.useFixture(fixtures.MockPatch('os.remove'))
+        rmdir_mock = self.useFixture(fixtures.MockPatch('os.rmdir'))
+        test_account_class.remove_hash(hash_list[hash_index])
+        hash_path = os.path.join(self.fixed_params['accounts_lock_dir'],
+                                 hash_list[hash_index])
+        return {'remove_mock': remove_mock, 'hash_path': hash_path,
+                'rmdir_mock': rmdir_mock}
+
     def test_get_hash(self):
         # Test with all accounts to make sure we try all combinations
         # and hide no race conditions
@@ -182,8 +193,12 @@
                         create=True) as open_mock:
             test_account_class._get_free_hash(hash_list)
             lock_path = os.path.join(self.fixed_params['accounts_lock_dir'],
-                                     hash_list[0])
-            open_mock.assert_called_once_with(lock_path, 'w')
+                                     list(set(hash_list))[0])
+            accounts_lock_dir_calls = \
+                [mock.call(test_account_class._used_projects_file),
+                 mock.call(lock_path, 'w'),
+                 mock.call(test_account_class._used_projects_file, 'w')]
+            open_mock.assert_has_calls(accounts_lock_dir_calls, any_order=True)
         mkdir_path = os.path.join(self.fixed_params['accounts_lock_dir'])
         mkdir_mock.mock.assert_called_once_with(mkdir_path)
 
@@ -224,43 +239,33 @@
                                      hash_list[3])
             open_mock.assert_has_calls([mock.call(lock_path, 'w')])
 
-    @mock.patch('oslo_concurrency.lockutils.lock')
-    def test_remove_hash_last_account(self, lock_mock):
+    @mock.patch('tempest.lib.common.preprov_creds.'
+                'PreProvisionedCredentialProvider._process_project')
+    def test_remove_hash_last_account(self, _process_project_mock):
         hash_list = self._get_hash_list(self.test_accounts)
         # Pretend the pseudo-lock is there
         self.useFixture(
             fixtures.MockPatch('os.path.isfile', return_value=True))
         # Pretend the lock dir is empty
         self.useFixture(fixtures.MockPatch('os.listdir', return_value=[]))
-        test_account_class = preprov_creds.PreProvisionedCredentialProvider(
-            **self.fixed_params)
-        remove_mock = self.useFixture(fixtures.MockPatch('os.remove'))
-        rmdir_mock = self.useFixture(fixtures.MockPatch('os.rmdir'))
-        test_account_class.remove_hash(hash_list[2])
-        hash_path = os.path.join(self.fixed_params['accounts_lock_dir'],
-                                 hash_list[2])
+        result = self._remove_hash(hash_list, 2)
         lock_path = self.fixed_params['accounts_lock_dir']
-        remove_mock.mock.assert_called_once_with(hash_path)
-        rmdir_mock.mock.assert_called_once_with(lock_path)
+        result['remove_mock'].mock.assert_called_once_with(result['hash_path'])
+        result['rmdir_mock'].mock.assert_called_once_with(lock_path)
 
-    @mock.patch('oslo_concurrency.lockutils.lock')
-    def test_remove_hash_not_last_account(self, lock_mock):
+    @mock.patch('tempest.lib.common.preprov_creds.'
+                'PreProvisionedCredentialProvider._process_project')
+    def test_remove_hash_not_last_account(self, _process_project_mock):
         hash_list = self._get_hash_list(self.test_accounts)
         # Pretend the pseudo-lock is there
         self.useFixture(fixtures.MockPatch(
             'os.path.isfile', return_value=True))
-        # Pretend the lock dir is empty
+        # Pretend the lock dir is not empty
         self.useFixture(fixtures.MockPatch('os.listdir', return_value=[
             hash_list[1], hash_list[4]]))
-        test_account_class = preprov_creds.PreProvisionedCredentialProvider(
-            **self.fixed_params)
-        remove_mock = self.useFixture(fixtures.MockPatch('os.remove'))
-        rmdir_mock = self.useFixture(fixtures.MockPatch('os.rmdir'))
-        test_account_class.remove_hash(hash_list[2])
-        hash_path = os.path.join(self.fixed_params['accounts_lock_dir'],
-                                 hash_list[2])
-        remove_mock.mock.assert_called_once_with(hash_path)
-        rmdir_mock.mock.assert_not_called()
+        result = self._remove_hash(hash_list, 2)
+        result['remove_mock'].mock.assert_called_once_with(result['hash_path'])
+        result['rmdir_mock'].mock.assert_not_called()
 
     def test_is_multi_user(self):
         test_accounts_class = preprov_creds.PreProvisionedCredentialProvider(