Merge "Remove cleanup code according to TODO comment"
diff --git a/etc/accounts.yaml.sample b/etc/accounts.yaml.sample
new file mode 100644
index 0000000..d191769
--- /dev/null
+++ b/etc/accounts.yaml.sample
@@ -0,0 +1,7 @@
+- username: 'user_1'
+ tenant_name: 'test_tenant_1'
+ password: 'test_password'
+
+- username: 'user_2'
+ tenant_name: 'test_tenant_2'
+ password: 'test_password'
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 96ef11a..e60b731 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -105,6 +105,17 @@
#syslog_log_facility=LOG_USER
+[auth]
+
+#
+# Options defined in tempest.config
+#
+
+# Path to the yaml file that contains the list of credentials
+# to use for running tests (string value)
+#test_accounts_file=etc/accounts.yaml
+
+
[baremetal]
#
@@ -386,6 +397,9 @@
# If false, skip all nova v3 tests. (boolean value)
#api_v3=false
+# If false skip all v2 api tests with xml (boolean value)
+#xml_api_v2=true
+
# If false, skip disk config tests (boolean value)
#disk_config=true
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index a3295eb..343a39a 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -37,6 +37,9 @@
def setUpClass(cls):
cls.set_network_resources()
super(BaseComputeTest, cls).setUpClass()
+ if getattr(cls, '_interface', None) == 'xml' and cls._api_version == 2:
+ if not CONF.compute_feature_enabled.xml_api_v2:
+ raise cls.skipException('XML API is not enabled')
# TODO(andreaf) WE should care also for the alt_manager here
# but only once client lazy load in the manager is done
diff --git a/tempest/api/compute/v3/admin/test_flavors_access.py b/tempest/api/compute/v3/admin/test_flavors_access.py
index c641bf6..09b6ebd 100644
--- a/tempest/api/compute/v3/admin/test_flavors_access.py
+++ b/tempest/api/compute/v3/admin/test_flavors_access.py
@@ -38,7 +38,6 @@
cls.vcpus = 1
cls.disk = 10
- @test.skip_because(bug='1265416')
@test.attr(type='gate')
def test_flavor_access_list_with_private_flavor(self):
# Test to list flavor access successfully by querying private flavor
@@ -58,7 +57,6 @@
self.assertEqual(str(new_flavor_id), str(first_flavor['flavor_id']))
self.assertEqual(self.adm_tenant_id, first_flavor['tenant_id'])
- @test.skip_because(bug='1265416')
@test.attr(type='gate')
def test_flavor_access_add_remove(self):
# Test to add and remove flavor access to a given tenant.
diff --git a/tempest/api/compute/v3/admin/test_flavors_access_negative.py b/tempest/api/compute/v3/admin/test_flavors_access_negative.py
index 02ecb24..0fdfabd 100644
--- a/tempest/api/compute/v3/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/v3/admin/test_flavors_access_negative.py
@@ -55,7 +55,6 @@
self.client.list_flavor_access,
new_flavor_id)
- @test.skip_because(bug='1265416')
@test.attr(type=['negative', 'gate'])
def test_flavor_non_admin_add(self):
# Test to add flavor access as a user without admin privileges.
@@ -72,7 +71,6 @@
new_flavor['id'],
self.tenant_id)
- @test.skip_because(bug='1265416')
@test.attr(type=['negative', 'gate'])
def test_flavor_non_admin_remove(self):
# Test to remove flavor access as a user without admin privileges.
@@ -93,7 +91,6 @@
new_flavor['id'],
self.tenant_id)
- @test.skip_because(bug='1265416')
@test.attr(type=['negative', 'gate'])
def test_add_flavor_access_duplicate(self):
# Create a new flavor.
@@ -118,7 +115,6 @@
new_flavor['id'],
self.tenant_id)
- @test.skip_because(bug='1265416')
@test.attr(type=['negative', 'gate'])
def test_remove_flavor_access_not_found(self):
# Create a new flavor.
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index 02f8c05..c33589a 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -13,14 +13,18 @@
# License for the specific language governing permissions and limitations
# under the License.
+import functools
import os
import shlex
import subprocess
+import testtools
+
import tempest.cli.output_parser
from tempest import config
from tempest import exceptions
from tempest.openstack.common import log as logging
+from tempest.openstack.common import versionutils
import tempest.test
@@ -29,6 +33,65 @@
CONF = config.CONF
+def execute(cmd, action, flags='', params='', fail_ok=False,
+ merge_stderr=False):
+ """Executes specified command for the given action."""
+ cmd = ' '.join([os.path.join(CONF.cli.cli_dir, cmd),
+ flags, action, params])
+ LOG.info("running: '%s'" % cmd)
+ cmd = shlex.split(cmd.encode('utf-8'))
+ result = ''
+ result_err = ''
+ stdout = subprocess.PIPE
+ stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
+ proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
+ result, result_err = proc.communicate()
+ if not fail_ok and proc.returncode != 0:
+ raise exceptions.CommandFailed(proc.returncode,
+ cmd,
+ result,
+ result_err)
+ return result
+
+
+def check_client_version(client, version):
+ """Checks if the client's version is compatible with the given version
+
+ @param client: The client to check.
+ @param version: The version to compare against.
+ @return: True if the client version is compatible with the given version
+ parameter, False otherwise.
+ """
+ current_version = execute(client, '', params='--version',
+ merge_stderr=True)
+
+ if not current_version.strip():
+ raise exceptions.TempestException('"%s --version" output was empty' %
+ client)
+
+ return versionutils.is_compatible(version, current_version,
+ same_major=False)
+
+
+def min_client_version(*args, **kwargs):
+ """A decorator to skip tests if the client used isn't of the right version.
+
+ @param client: The client command to run. For python-novaclient, this is
+ 'nova', for python-cinderclient this is 'cinder', etc.
+ @param version: The minimum version required to run the CLI test.
+ """
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapper(*func_args, **func_kwargs):
+ if not check_client_version(kwargs['client'], kwargs['version']):
+ msg = "requires %s client version >= %s" % (kwargs['client'],
+ kwargs['version'])
+ raise testtools.TestCase.skipException(msg)
+ return func(*func_args, **func_kwargs)
+ return wrapper
+ return decorator
+
+
class ClientTestBase(tempest.test.BaseTestCase):
@classmethod
def setUpClass(cls):
@@ -50,7 +113,7 @@
def nova_manage(self, action, flags='', params='', fail_ok=False,
merge_stderr=False):
"""Executes nova-manage command for the given action."""
- return self.cmd(
+ return execute(
'nova-manage', action, flags, params, fail_ok, merge_stderr)
def keystone(self, action, flags='', params='', admin=True, fail_ok=False):
@@ -114,28 +177,7 @@
CONF.identity.admin_password,
CONF.identity.uri))
flags = creds + ' ' + flags
- return self.cmd(cmd, action, flags, params, fail_ok, merge_stderr)
-
- def cmd(self, cmd, action, flags='', params='', fail_ok=False,
- merge_stderr=False):
- """Executes specified command for the given action."""
- cmd = ' '.join([os.path.join(CONF.cli.cli_dir, cmd),
- flags, action, params])
- LOG.info("running: '%s'" % cmd)
- cmd = shlex.split(cmd.encode('utf-8'))
- result = ''
- result_err = ''
- stdout = subprocess.PIPE
- stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
- proc = subprocess.Popen(
- cmd, stdout=stdout, stderr=stderr)
- result, result_err = proc.communicate()
- if not fail_ok and proc.returncode != 0:
- raise exceptions.CommandFailed(proc.returncode,
- cmd,
- result,
- result_err)
- return result
+ return execute(cmd, action, flags, params, fail_ok, merge_stderr)
def assertTableStruct(self, items, field_names):
"""Verify that all items has keys listed in field_names."""
diff --git a/tempest/cli/simple_read_only/test_heat.py b/tempest/cli/simple_read_only/test_heat.py
index 7a952fc..bd79fa6 100644
--- a/tempest/cli/simple_read_only/test_heat.py
+++ b/tempest/cli/simple_read_only/test_heat.py
@@ -85,6 +85,7 @@
def test_heat_help(self):
self.heat('help')
+ @tempest.cli.min_client_version(client='heat', version='0.2.7')
def test_heat_bash_completion(self):
self.heat('bash-completion')
diff --git a/tempest/cli/simple_read_only/test_nova.py b/tempest/cli/simple_read_only/test_nova.py
index 7085cc9..9bac7a6 100644
--- a/tempest/cli/simple_read_only/test_nova.py
+++ b/tempest/cli/simple_read_only/test_nova.py
@@ -144,6 +144,7 @@
def test_admin_secgroup_list_rules(self):
self.nova('secgroup-list-rules')
+ @tempest.cli.min_client_version(client='nova', version='2.18')
def test_admin_server_group_list(self):
self.nova('server-group-list')
diff --git a/tempest/common/accounts.py b/tempest/common/accounts.py
new file mode 100644
index 0000000..80f5f91
--- /dev/null
+++ b/tempest/common/accounts.py
@@ -0,0 +1,130 @@
+# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import hashlib
+import os
+import yaml
+
+from tempest import auth
+from tempest.common import cred_provider
+from tempest import config
+from tempest import exceptions
+from tempest.openstack.common import lockutils
+from tempest.openstack.common import log as logging
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+def read_accounts_yaml(path):
+ yaml_file = open(path, 'r')
+ accounts = yaml.load(yaml_file)
+ return accounts
+
+
+class Accounts(cred_provider.CredentialProvider):
+
+ def __init__(self, name):
+ super(Accounts, self).__init__(name)
+ accounts = read_accounts_yaml(CONF.auth.test_accounts_file)
+ self.hash_dict = self.get_hash_dict(accounts)
+ self.accounts_dir = os.path.join(CONF.lock_path, 'test_accounts')
+ self.isolated_creds = {}
+
+ @classmethod
+ def get_hash_dict(cls, accounts):
+ hash_dict = {}
+ for account in accounts:
+ temp_hash = hashlib.md5()
+ temp_hash.update(str(account))
+ hash_dict[temp_hash.hexdigest()] = account
+ return hash_dict
+
+ def _create_hash_file(self, hash):
+ path = os.path.join(os.path.join(self.accounts_dir, hash))
+ if not os.path.isfile(path):
+ open(path, 'w').close()
+ return True
+ return False
+
+ @lockutils.synchronized('test_accounts_io', external=True)
+ def _get_free_hash(self, 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])
+ return hashes[0]
+ for hash in hashes:
+ res = self._create_hash_file(hash)
+ if res:
+ return hash
+ msg = 'Insufficient number of users provided'
+ raise exceptions.InvalidConfiguration(msg)
+
+ def _get_creds(self):
+ free_hash = self._get_free_hash(self.hashes.keys())
+ return self.hash_dict[free_hash]
+
+ @lockutils.synchronized('test_accounts_io', external=True)
+ def remove_hash(self, hash):
+ hash_path = os.path.join(self.accounts_dir, hash)
+ if not os.path.isfile(hash_path):
+ LOG.warning('Expected an account lock file %s to remove, but '
+ 'one did not exist')
+ else:
+ os.remove(hash_path)
+ if not os.listdir(self.accounts_dir):
+ os.rmdir(self.accounts_dir)
+
+ def get_hash(self, creds):
+ for hash in self.hash_dict:
+ # NOTE(mtreinish) Assuming with v3 that username, tenant, password
+ # is unique enough
+ cred_dict = {
+ 'username': creds.username,
+ 'tenant_name': creds.tenant_name,
+ 'password': creds.password
+ }
+ if self.hash_dict[hash] == cred_dict:
+ return hash
+ raise AttributeError('Invalid credentials %s' % creds)
+
+ def remove_credentials(self, creds):
+ hash = self.get_hash(creds)
+ self.remove_hash(hash, self.accounts_dir)
+
+ def get_primary_creds(self):
+ if self.credentials.get('primary'):
+ return self.credentials.get('primary')
+ creds = self._get_creds()
+ primary_credential = auth.get_credentials(**creds)
+ self.credentials['primary'] = primary_credential
+ return primary_credential
+
+ def get_alt_creds(self):
+ if self.credentials.get('alt'):
+ return self.credentials.get('alt')
+ creds = self._get_creds()
+ alt_credential = auth.get_credentials(**creds)
+ self.credentials['alt'] = alt_credential
+ return alt_credential
+
+ def clear_isolated_creds(self):
+ for creds in self.credentials.values():
+ self.remove_credentials(creds)
+
+ def get_admin_creds(self):
+ msg = ('If admin credentials are available tenant_isolation should be'
+ ' used instead')
+ raise NotImplementedError(msg)
diff --git a/tempest/config.py b/tempest/config.py
index 7d195bf..39c2be1 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -29,6 +29,18 @@
conf.register_opt(opt, group=opt_group.name)
+auth_group = cfg.OptGroup(name='auth',
+ title="Options for authentication and credentials")
+
+
+AuthGroup = [
+ cfg.StrOpt('test_accounts_file',
+ default='etc/accounts.yaml',
+ help="Path to the yaml file that contains the list of "
+ "credentials to use for running tests"),
+]
+
+
identity_group = cfg.OptGroup(name='identity',
title="Keystone Configuration Options")
@@ -261,6 +273,9 @@
cfg.BoolOpt('api_v3',
default=False,
help="If false, skip all nova v3 tests."),
+ cfg.BoolOpt('xml_api_v2',
+ default=True,
+ help="If false skip all v2 api tests with xml"),
cfg.BoolOpt('disk_config',
default=True,
help="If false, skip disk config tests"),
@@ -1012,6 +1027,7 @@
def register_opts():
+ register_opt_group(cfg.CONF, auth_group, AuthGroup)
register_opt_group(cfg.CONF, compute_group, ComputeGroup)
register_opt_group(cfg.CONF, compute_features_group,
ComputeFeaturesGroup)
@@ -1060,6 +1076,7 @@
DEFAULT_CONFIG_FILE = "tempest.conf"
def _set_attrs(self):
+ self.auth = cfg.CONF.auth
self.compute = cfg.CONF.compute
self.compute_feature_enabled = cfg.CONF['compute-feature-enabled']
self.identity = cfg.CONF.identity
diff --git a/tempest/tests/cli/test_cli.py b/tempest/tests/cli/test_cli.py
new file mode 100644
index 0000000..1fd5ccb
--- /dev/null
+++ b/tempest/tests/cli/test_cli.py
@@ -0,0 +1,59 @@
+# Copyright 2014 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+import testtools
+
+from tempest import cli
+from tempest import exceptions
+from tempest.tests import base
+
+
+class TestMinClientVersion(base.TestCase):
+ """Tests for the min_client_version decorator.
+ """
+
+ def _test_min_version(self, required, installed, expect_skip):
+
+ @cli.min_client_version(client='nova', version=required)
+ def fake(self, expect_skip):
+ if expect_skip:
+ # If we got here, the decorator didn't raise a skipException as
+ # expected so we need to fail.
+ self.fail('Should not have gotten past the decorator.')
+
+ with mock.patch.object(cli, 'execute',
+ return_value=installed) as mock_cmd:
+ if expect_skip:
+ self.assertRaises(testtools.TestCase.skipException, fake,
+ self, expect_skip)
+ else:
+ fake(self, expect_skip)
+ mock_cmd.assert_called_once_with('nova', '', params='--version',
+ merge_stderr=True)
+
+ def test_min_client_version(self):
+ # required, installed, expect_skip
+ cases = (('2.17.0', '2.17.0', False),
+ ('2.17.0', '2.18.0', False),
+ ('2.18.0', '2.17.0', True))
+
+ for case in cases:
+ self._test_min_version(*case)
+
+ @mock.patch.object(cli, 'execute', return_value=' ')
+ def test_check_client_version_empty_output(self, mock_execute):
+ # Tests that an exception is raised if the command output is empty.
+ self.assertRaises(exceptions.TempestException,
+ cli.check_client_version, 'nova', '2.18.0')
diff --git a/tempest/tests/common/test_accounts.py b/tempest/tests/common/test_accounts.py
new file mode 100644
index 0000000..c24bfb6
--- /dev/null
+++ b/tempest/tests/common/test_accounts.py
@@ -0,0 +1,187 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import hashlib
+import os
+import tempfile
+
+import mock
+from oslo.config import cfg
+from oslotest import mockpatch
+
+from tempest import auth
+from tempest.common import accounts
+from tempest.common import http
+from tempest import config
+from tempest import exceptions
+from tempest.tests import base
+from tempest.tests import fake_config
+from tempest.tests import fake_identity
+
+
+class TestAccount(base.TestCase):
+
+ def setUp(self):
+ super(TestAccount, self).setUp()
+ self.useFixture(fake_config.ConfigFixture())
+ self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+ self.temp_dir = tempfile.mkdtemp()
+ cfg.CONF.set_default('lock_path', self.temp_dir)
+ self.addCleanup(os.rmdir, self.temp_dir)
+ self.test_accounts = [
+ {'username': 'test_user1', 'tenant_name': 'test_tenant1',
+ 'password': 'p'},
+ {'username': 'test_user2', 'tenant_name': 'test_tenant2',
+ 'password': 'p'},
+ {'username': 'test_user3', 'tenant_name': 'test_tenant3',
+ 'password': 'p'},
+ {'username': 'test_user4', 'tenant_name': 'test_tenant4',
+ 'password': 'p'},
+ {'username': 'test_user5', 'tenant_name': 'test_tenant5',
+ 'password': 'p'},
+ {'username': 'test_user6', 'tenant_name': 'test_tenant6',
+ 'password': 'p'},
+ ]
+ self.useFixture(mockpatch.Patch(
+ 'tempest.common.accounts.read_accounts_yaml',
+ return_value=self.test_accounts))
+ cfg.CONF.set_default('test_accounts_file', '', group='auth')
+
+ def _get_hash_list(self, accounts_list):
+ hash_list = []
+ for account in accounts_list:
+ hash = hashlib.md5()
+ hash.update(str(account))
+ hash_list.append(hash.hexdigest())
+ return hash_list
+
+ def test_get_hash(self):
+ self.stubs.Set(http.ClosingHttp, 'request',
+ fake_identity._fake_v2_response)
+ test_account_class = accounts.Accounts('test_name')
+ hash_list = self._get_hash_list(self.test_accounts)
+ test_cred_dict = self.test_accounts[3]
+ test_creds = auth.get_credentials(**test_cred_dict)
+ results = test_account_class.get_hash(test_creds)
+ self.assertEqual(hash_list[3], results)
+
+ def test_get_hash_dict(self):
+ test_account_class = accounts.Accounts('test_name')
+ hash_dict = test_account_class.get_hash_dict(self.test_accounts)
+ hash_list = self._get_hash_list(self.test_accounts)
+ for hash in hash_list:
+ self.assertIn(hash, hash_dict.keys())
+ self.assertIn(hash_dict[hash], self.test_accounts)
+
+ def test_create_hash_file_previous_file(self):
+ # Emulate the lock existing on the filesystem
+ self.useFixture(mockpatch.Patch('os.path.isfile', return_value=True))
+ with mock.patch('__builtin__.open', mock.mock_open(), create=True):
+ test_account_class = accounts.Accounts('test_name')
+ res = test_account_class._create_hash_file('12345')
+ self.assertFalse(res, "_create_hash_file should return False if the "
+ "pseudo-lock file already exists")
+
+ def test_create_hash_file_no_previous_file(self):
+ # Emulate the lock not existing on the filesystem
+ self.useFixture(mockpatch.Patch('os.path.isfile', return_value=False))
+ with mock.patch('__builtin__.open', mock.mock_open(), create=True):
+ test_account_class = accounts.Accounts('test_name')
+ res = test_account_class._create_hash_file('12345')
+ self.assertTrue(res, "_create_hash_file should return True if the "
+ "pseudo-lock doesn't already exist")
+
+ @mock.patch('tempest.openstack.common.lockutils.lock')
+ def test_get_free_hash_no_previous_accounts(self, lock_mock):
+ # Emulate no pre-existing lock
+ self.useFixture(mockpatch.Patch('os.path.isdir', return_value=False))
+ hash_list = self._get_hash_list(self.test_accounts)
+ mkdir_mock = self.useFixture(mockpatch.Patch('os.mkdir'))
+ self.useFixture(mockpatch.Patch('os.path.isfile', return_value=False))
+ test_account_class = accounts.Accounts('test_name')
+ with mock.patch('__builtin__.open', mock.mock_open(),
+ create=True) as open_mock:
+ test_account_class._get_free_hash(hash_list)
+ lock_path = os.path.join(accounts.CONF.lock_path, 'test_accounts',
+ hash_list[0])
+ open_mock.assert_called_once_with(lock_path, 'w')
+ mkdir_path = os.path.join(accounts.CONF.lock_path, 'test_accounts')
+ mkdir_mock.mock.assert_called_once_with(mkdir_path)
+
+ @mock.patch('tempest.openstack.common.lockutils.lock')
+ def test_get_free_hash_no_free_accounts(self, lock_mock):
+ hash_list = self._get_hash_list(self.test_accounts)
+ # Emulate pre-existing lock dir
+ self.useFixture(mockpatch.Patch('os.path.isdir', return_value=True))
+ # Emulate all lcoks in list are in use
+ self.useFixture(mockpatch.Patch('os.path.isfile', return_value=True))
+ test_account_class = accounts.Accounts('test_name')
+ self.assertRaises(exceptions.InvalidConfiguration,
+ test_account_class._get_free_hash, hash_list)
+
+ @mock.patch('tempest.openstack.common.lockutils.lock')
+ def test_get_free_hash_some_in_use_accounts(self, lock_mock):
+ # Emulate no pre-existing lock
+ self.useFixture(mockpatch.Patch('os.path.isdir', return_value=True))
+ hash_list = self._get_hash_list(self.test_accounts)
+ test_account_class = accounts.Accounts('test_name')
+
+ def _fake_is_file(path):
+ # Fake isfile() to return that the path exists unless a specific
+ # hash is in the path
+ if hash_list[3] in path:
+ return False
+ return True
+
+ self.stubs.Set(os.path, 'isfile', _fake_is_file)
+ with mock.patch('__builtin__.open', mock.mock_open(),
+ create=True) as open_mock:
+ test_account_class._get_free_hash(hash_list)
+ lock_path = os.path.join(accounts.CONF.lock_path, 'test_accounts',
+ hash_list[3])
+ open_mock.assert_called_once_with(lock_path, 'w')
+
+ @mock.patch('tempest.openstack.common.lockutils.lock')
+ def test_remove_hash_last_account(self, lock_mock):
+ hash_list = self._get_hash_list(self.test_accounts)
+ # Pretend the pseudo-lock is there
+ self.useFixture(mockpatch.Patch('os.path.isfile', return_value=True))
+ # Pretend the lock dir is empty
+ self.useFixture(mockpatch.Patch('os.listdir', return_value=[]))
+ test_account_class = accounts.Accounts('test_name')
+ remove_mock = self.useFixture(mockpatch.Patch('os.remove'))
+ rmdir_mock = self.useFixture(mockpatch.Patch('os.rmdir'))
+ test_account_class.remove_hash(hash_list[2])
+ hash_path = os.path.join(accounts.CONF.lock_path, 'test_accounts',
+ hash_list[2])
+ lock_path = os.path.join(accounts.CONF.lock_path, 'test_accounts')
+ remove_mock.mock.assert_called_once_with(hash_path)
+ rmdir_mock.mock.assert_called_once_with(lock_path)
+
+ @mock.patch('tempest.openstack.common.lockutils.lock')
+ def test_remove_hash_not_last_account(self, lock_mock):
+ hash_list = self._get_hash_list(self.test_accounts)
+ # Pretend the pseudo-lock is there
+ self.useFixture(mockpatch.Patch('os.path.isfile', return_value=True))
+ # Pretend the lock dir is empty
+ self.useFixture(mockpatch.Patch('os.listdir', return_value=[
+ hash_list[1], hash_list[4]]))
+ test_account_class = accounts.Accounts('test_name')
+ remove_mock = self.useFixture(mockpatch.Patch('os.remove'))
+ rmdir_mock = self.useFixture(mockpatch.Patch('os.rmdir'))
+ test_account_class.remove_hash(hash_list[2])
+ hash_path = os.path.join(accounts.CONF.lock_path, 'test_accounts',
+ hash_list[2])
+ remove_mock.mock.assert_called_once_with(hash_path)
+ rmdir_mock.mock.assert_not_called()
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index 536cbcf..9e56916 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -61,3 +61,4 @@
def __init__(self, parse_conf=True, config_path=None):
cfg.CONF([], default_config_files=[])
self._set_attrs()
+ self.lock_path = cfg.CONF.lock_path