Adds support for multiple tempest sources

Will now create "tempest-base" dir for each remote source.
On repo creation, will search existing directories for matching remote. If none
exits, will create a new directory

old:

 _ .rally/tempest
 |_base -> clone from source to here
 |_ ...for-deployment-<UUID1> -> copy from tempest base
 |_ ...for-deployment-<UUID2> -> copy from tempest base

new:

 _ .rally/tempest
 |_base
 ||_ tempest_base-<rand suffix specific for source> -> clone from source
 ||                                                    to here
 ||_ tempest_base-<rand suffix 2>
 |_ ...for-deployment-<UUID1> -> copy from relevant tempest_base
 |_ ...for-deployment-<UUID2> -> copy from relevant tempest_base

Change-Id: Ib9e697a2553c3e412892722653a817c3f83b481f
This commit is contained in:
Yair Fried 2015-01-19 08:24:13 +02:00
parent 717de8291e
commit b2142102a1
3 changed files with 149 additions and 16 deletions

View File

@ -276,3 +276,7 @@ class MigrateException(RallyException):
class InvalidHostException(RallyException):
msg_fmt = _("Live Migration failed: %(message)s")
class MultipleMatchesFound(RallyException):
msg_fmt = _("Found multiple %(needle)s: %(haystack)s")

View File

@ -53,7 +53,8 @@ def check_output(*args, **kwargs):
class Tempest(object):
base_repo = os.path.join(os.path.expanduser("~"), ".rally/tempest/base")
base_repo_dir = os.path.join(os.path.expanduser("~"),
".rally/tempest/base")
def __init__(self, deployment, verification=None, tempest_config=None,
source=None):
@ -67,6 +68,7 @@ class Tempest(object):
self.venv_wrapper = self.path("tools/with_venv.sh")
self.verification = verification
self._env = None
self._base_repo = None
def _generate_env(self):
env = os.environ.copy()
@ -87,6 +89,79 @@ class Tempest(object):
return os.path.join(self._path, *inner_path)
return self._path
@staticmethod
def _is_git_repo(directory):
# will suppress git output
with open(os.devnull, 'w') as devnull:
return os.path.isdir(directory) and not subprocess.call(
"git status", shell=True,
stdout=devnull, stderr=subprocess.STDOUT,
cwd=os.path.abspath(directory))
@staticmethod
def _move_contents_to_subdir(base, subdir):
"""Moves contents of directory :base into its sub-directory :subdir
:param base: source directory to move files from
:param subdir: name of subdirectory to move files to
"""
for filename in os.listdir(base):
shutil.move(filename, os.path.join(base, subdir, filename))
@property
def base_repo(self):
"""Get directory to clone tempest to
old:
_ rally/tempest
|_base -> clone from source to here
|_for-deployment-<UUID1> -> copy from relevant tempest base
|_for-deployment-<UUID2> -> copy from relevant tempest base
new:
_ rally/tempest
|_base
||_ tempest_base-<rand suffix specific for source> -> clone
|| from source to here
||_ tempest_base-<rand suffix 2>
|_for-deployment-<UUID1> -> copy from relevant tempest base
|_for-deployment-<UUID2> -> copy from relevant tempest base
"""
if os.path.exists(Tempest.base_repo_dir):
if self._is_git_repo(Tempest.base_repo_dir):
# this is the old dir structure and needs to be upgraded
directory = utils.generate_random_name("tempest_base-")
LOG.debug("Upgrading Tempest directory tree: "
"Moving Tempest base dir %s into subdirectory %s" %
(Tempest.base_repo_dir, directory))
self._move_contents_to_subdir(Tempest.base_repo_dir,
directory)
if not self._base_repo:
# Search existing tempest bases for a matching source
repos = [d for d in os.listdir(Tempest.base_repo_dir)
if self._is_git_repo(d) and
self.tempest_source == self._get_remote_origin(d)]
if len(repos) > 1:
raise exceptions.MultipleMatchesFound(
needle="git directory",
haystack=repos)
if repos:
# Use existing base with relevant source
self._base_repo = repos.pop()
if not self._base_repo:
directory = utils.generate_random_name("tempest_base-")
self._base_repo = os.path.join(
os.path.abspath(Tempest.base_repo_dir), directory)
return self._base_repo
@staticmethod
def _get_remote_origin(directory):
out = subprocess.check_output("git config --get remote.origin.url",
shell=True,
cwd=os.path.abspath(directory))
return out.strip()
def _install_venv(self):
path_to_venv = self.path(".venv")
@ -139,16 +214,16 @@ class Tempest(object):
"This could take a few minutes...")
subprocess.check_call(["git", "clone",
self.tempest_source,
Tempest.base_repo])
self.base_repo])
def install(self):
if not self.is_installed():
try:
if not os.path.exists(Tempest.base_repo):
if not os.path.exists(self.base_repo):
self._clone()
if not os.path.exists(self.path()):
shutil.copytree(Tempest.base_repo, self.path())
shutil.copytree(self.base_repo, self.path())
subprocess.check_call("git checkout master; "
"git pull", shell=True,
cwd=self.path("tempest"))

View File

@ -34,6 +34,11 @@ TEMPEST_PATH = "rally.verification.tempest"
class BaseTestCase(test.TestCase):
def setUp(self):
super(BaseTestCase, self).setUp()
self.base_repo_patcher = mock.patch.object(tempest.Tempest,
"base_repo", "foo-baserepo")
self.base_repo_dir_patcher = mock.patch.object(tempest.Tempest,
"base_repo_dir",
"foo-baserepodir")
self.verifier = tempest.Tempest("fake_deployment_id",
verification=mock.MagicMock())
@ -166,21 +171,47 @@ class TempestInstallAndUninstallTestCase(BaseTestCase):
@mock.patch(TEMPEST_PATH + ".tempest.subprocess.check_call")
def test__clone_successful(self, mock_sp):
self.verifier._clone()
mock_sp.assert_called_once_with(
["git", "clone", "https://github.com/openstack/tempest",
tempest.Tempest.base_repo])
with self.base_repo_patcher:
self.verifier._clone()
mock_sp.assert_called_once_with(
["git", "clone", "https://github.com/openstack/tempest",
"foo-baserepo"])
def test__no_dir(self):
with mock.patch("os.path.isdir", return_value=False):
self.assertFalse(self.verifier._is_git_repo("fake_dir"))
@mock.patch("subprocess.call", return_value=1)
@mock.patch("os.path.isdir", return_value=True)
def test__is_not_git_repo(self, mock_isdir, mock_git_status):
self.assertFalse(self.verifier._is_git_repo("fake_dir"))
@mock.patch("subprocess.call", return_value=0)
@mock.patch("os.path.isdir", return_value=True)
def test__is_git_repo(self, mock_isdir, mock_git_status):
self.assertTrue(self.verifier._is_git_repo("fake_dir"))
@testtools.skipIf(sys.version_info < (2, 7), "Incompatible Python Version")
@mock.patch("subprocess.check_output", return_value="fake_url")
def test__get_remote_origin(self, mock_sp):
with mock_sp:
self.assertEqual("fake_url",
self.verifier._get_remote_origin("fake_dir"))
@mock.patch(TEMPEST_PATH + ".tempest.subprocess.check_call")
def test__clone_failed(self, mock_sp):
# Check that `subprocess.CalledProcessError` is not handled by `_clone`
mock_sp.side_effect = subprocess.CalledProcessError(0, None)
with self.base_repo_patcher:
# Check that `subprocess.CalledProcessError` is not handled
# by `_clone`
mock_sp.side_effect = subprocess.CalledProcessError(0, None)
self.assertRaises(subprocess.CalledProcessError, self.verifier._clone)
mock_sp.assert_called_once_with(
["git", "clone", "https://github.com/openstack/tempest",
tempest.Tempest.base_repo])
self.assertRaises(subprocess.CalledProcessError,
self.verifier._clone)
mock_sp.assert_called_once_with(
["git", "clone", "https://github.com/openstack/tempest",
"foo-baserepo"])
@mock.patch(TEMPEST_PATH + ".tempest.Tempest.base_repo")
@mock.patch(TEMPEST_PATH + ".tempest.Tempest._initialize_testr")
@mock.patch(TEMPEST_PATH + ".tempest.Tempest._install_venv")
@mock.patch(TEMPEST_PATH + ".tempest.subprocess.check_call")
@ -188,7 +219,9 @@ class TempestInstallAndUninstallTestCase(BaseTestCase):
@mock.patch(TEMPEST_PATH + ".tempest.Tempest._clone")
@mock.patch("os.path.exists", return_value=False)
def test_install_successful(self, mock_exists, mock_clone, mock_copytree,
mock_sp, mock_install_venv, mock_testr_init):
mock_sp, mock_install_venv, mock_testr_init,
mock_base_repo):
mock_base_repo.__get__ = mock.Mock(return_value="fake_dir")
self.verifier.install()
self.assertEqual([mock.call(self.verifier.path(".venv")),
@ -206,6 +239,7 @@ class TempestInstallAndUninstallTestCase(BaseTestCase):
mock_install_venv.assert_called_once_with()
mock_testr_init.assert_called_once_with()
@mock.patch(TEMPEST_PATH + ".tempest.Tempest.base_repo")
@mock.patch(TEMPEST_PATH + ".tempest.Tempest.uninstall")
@mock.patch(TEMPEST_PATH + ".tempest.Tempest._initialize_testr")
@mock.patch(TEMPEST_PATH + ".tempest.Tempest._install_venv")
@ -215,7 +249,8 @@ class TempestInstallAndUninstallTestCase(BaseTestCase):
@mock.patch("os.path.exists", return_value=False)
def test_install_failed(self, mock_exists, mock_clone, mock_copytree,
mock_sp, mock_install_venv, mock_testr_init,
mock_uninstall):
mock_uninstall, mock_base_repo):
mock_base_repo.__get__ = mock.Mock(return_value="fake_dir")
mock_sp.side_effect = subprocess.CalledProcessError(0, None)
self.assertRaises(tempest.TempestSetupFailure, self.verifier.install)
@ -243,6 +278,25 @@ class TempestInstallAndUninstallTestCase(BaseTestCase):
mock_exists.assert_called_once_with(self.verifier.path())
mock_shutil.assert_called_once_with(self.verifier.path())
@mock.patch(TEMPEST_PATH + ".tempest.Tempest._is_git_repo",
return_value=True)
@mock.patch("rally.common.utils.generate_random_name",
return_value="fake_tempest_dir")
@mock.patch("os.listdir", return_value=["fake_dir"])
@mock.patch("shutil.move")
@mock.patch("os.path.exists", return_value=True)
def test_upgrade_repo_tree(self, mock_exists, mock_move, mock_listdir,
mock_rand, mock_isgitrepo):
with self.base_repo_dir_patcher as foo_base:
self.verifier._base_repo = "fake_base"
self.verifier.base_repo
subdir = mock_rand.return_value
mock_listdir.assert_called_once_with(foo_base)
fake_dir = mock_listdir.return_value[0]
dest = os.path.join(self.base_repo_dir_patcher.new, subdir,
fake_dir)
mock_move.assert_called_once_with(fake_dir, dest)
class TempestVerifyTestCase(BaseTestCase):
def _get_fake_call(self, testr_arg):