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:
parent
717de8291e
commit
b2142102a1
@ -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")
|
||||
|
@ -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"))
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user