[Verify] New command to install Tempest plugins

This command allows us to install a Tempest plugin from a repository.

E.g.
  $ rally verify installplugin --source https://github.com/MBonell/hello-world-tempest-plugin
  $ rally verify installplugin --source /home/ubuntu/my-plugin

Implements Blueprint: install-tempest-plugins

Change-Id: I03ac6063b6b9d7f860379b99f3b524adedba6df9
This commit is contained in:
Marcela Bonell 2015-10-26 16:05:51 -06:00 committed by Yaroslav Lobankov
parent 31d738a125
commit f6a02afcda
9 changed files with 146 additions and 15 deletions

View File

@ -52,6 +52,7 @@ _rally()
OPTS["verify_genconfig"]="--deployment --tempest-config --override"
OPTS["verify_import"]="--deployment --set --file --no-use"
OPTS["verify_install"]="--deployment --source --version --system-wide"
OPTS["verify_installplugin"]="--deployment --source --version --system-wide"
OPTS["verify_list"]=""
OPTS["verify_reinstall"]="--deployment --source --version --system-wide"
OPTS["verify_results"]="--uuid --html --json --output-file"
@ -88,4 +89,4 @@ _rally()
return 0
}
complete -o filenames -F _rally rally
complete -o filenames -F _rally rally

View File

@ -486,6 +486,28 @@ class Verification(object):
verifier.uninstall()
verifier.install()
@classmethod
def install_tempest_plugin(cls, deployment, source=None, version=None,
system_wide=False):
"""Install Tempest plugin.
:param deployment: UUID or name of a deployment
:param source: Path/URL to repo to clone Tempest plugin from
:param version: Branch, commit ID or tag to checkout before Tempest
plugin installation
:param system_wide: Whether or not to install plugin in Tempest
virtual env
"""
deployment_uuid = objects.Deployment.get(deployment)["uuid"]
verifier = tempest.Tempest(deployment_uuid,
plugin_source=source,
plugin_version=version,
system_wide=system_wide)
cls._check_tempest_tree_existence(verifier)
verifier.install_plugin()
@classmethod
def discover_tests(cls, deployment, pattern=""):
"""Get a list of discovered tests.

View File

@ -467,6 +467,34 @@ class VerifyCommands(object):
api.Verification.reinstall_tempest(deployment, source,
version, system_wide)
@cliutils.args("--deployment", type=str, dest="deployment",
required=False, help="UUID or name of a deployment")
@cliutils.args("--source", type=str, dest="source", required=True,
help="Path/URL to repo to clone Tempest plugin from")
@envutils.with_default_deployment(cli_arg_name="deployment")
@cliutils.args("--version", type=str, dest="version", required=False,
help="Branch, commit ID or tag to checkout before Tempest "
"plugin installation")
@cliutils.args("--system-wide", dest="system_wide",
help="Don't install plugin in Tempest virtual env. "
"Note that all Tempest plugin requirements have "
"to be already installed in the local env!",
required=False, action="store_true")
@envutils.with_default_deployment(cli_arg_name="deployment")
def installplugin(self, deployment=None, source=None, version=None,
system_wide=False):
"""Install Tempest plugin.
:param deployment: UUID or name of a deployment
:param source: Path/URL to repo to clone Tempest plugin from
:param version: Branch, commit ID or tag to checkout before Tempest
plugin installation
:param system_wide: Whether or not to install plugin in Tempest
virtual env
"""
api.Verification.install_tempest_plugin(deployment, source,
version, system_wide)
@cliutils.args("--deployment", dest="deployment", type=str, required=False,
metavar="<uuid>", help="UUID or name of a deployment")
@cliutils.args("--pattern", dest="pattern", type=str,

View File

@ -61,6 +61,10 @@
<a href="{{ reinstall.stdout_file }}">Tempest re-installation</a>
<code>$ {{ reinstall.cmd }}</code>
<span class="{{ installplugin.status }}">[{{ installplugin.status }}]</span>
<a href="{{ installplugin.stdout_file }}">Tempest plugin installation</a>
<code>$ {{ installplugin.cmd }}</code>
<span class="{{ genconfig.status }}">[{{ genconfig.status }}]</span>
<a href="{{ genconfig.stdout_file }}">Tempest config generation</a>
<code>$ {{ genconfig.cmd }}</code>

View File

@ -58,16 +58,20 @@ class Tempest(object):
".rally/tempest/base")
def __init__(self, deployment, verification=None, tempest_config=None,
source=None, version=None, system_wide=False):
self.tempest_source = source or TEMPEST_SOURCE
self.version = version
source=None, version=None, plugin_source=None,
plugin_version=None, system_wide=False):
self.deployment = deployment
self.verification = verification
self._path = os.path.join(os.path.expanduser("~"),
".rally/tempest",
"for-deployment-%s" % deployment)
self.config_file = tempest_config or self.path("tempest.conf")
self.tempest_source = source or TEMPEST_SOURCE
self.version = version
self.plugin_source = plugin_source
self.plugin_version = plugin_version
self.log_file_raw = self.path("subunit.stream")
self.verification = verification
self._env = None
self._base_repo = None
self._system_wide = system_wide
@ -290,6 +294,17 @@ class Tempest(object):
if os.path.exists(self.path()):
shutil.rmtree(self.path())
def install_plugin(self):
"""Install Tempest plugin for local Tempest repo."""
LOG.info(_("Installing Tempest plugin from %s for "
"deployment: %s") % (self.plugin_source, self.deployment))
egg = os.path.basename(self.plugin_source.strip("/"))
version = self.plugin_version or "master"
cmd = [self.venv_wrapper, "pip", "install", "-e",
"git+{0}@{1}#egg={2}".format(self.plugin_source, version, egg)]
check_output(cmd, cwd=self.path())
LOG.info(_("Tempest plugin has been successfully installed!"))
@logging.log_verification_wrapper(LOG.info, _("Run verification."))
def _prepare_and_run(self, set_name, regex, tests_file, concur, failing):
if not self.is_configured():

View File

@ -46,6 +46,8 @@ EXPECTED_FAILURES = {
"This test fails because 'novnc' console type is unavailable."
}
TEMPEST_PLUGIN = "https://github.com/MBonell/hello-world-tempest-plugin"
# NOTE(andreykurilin): this variable is used to generate output file names
# with prefix ${CALL_COUNT}_ .
_call_count = 0
@ -62,10 +64,10 @@ def call_rally(cmd, print_output=False, output_type=None):
data = {"cmd": "rally --rally-debug %s" % cmd}
stdout_file = "{base}/{prefix}_{cmd}.txt.gz"
if "--xfails-file" in cmd:
if "--xfails-file" in cmd or "--source" in cmd:
cmd_items = cmd.split()
for num, item in enumerate(cmd_items):
if EXPECTED_FAILURES_FILE in item:
if EXPECTED_FAILURES_FILE in item or TEMPEST_PLUGIN in item:
cmd_items[num] = os.path.basename(item)
break
cmd = " ".join(cmd_items)
@ -233,15 +235,9 @@ def main():
render_vars = {"verifications": []}
# Install Tempest
# Install the latest Tempest version
render_vars["install"] = call_rally("verify install")
# Discover tests depending on Tempest suite
discover_cmd = "verify discover"
if args.mode == "light":
discover_cmd += " --pattern smoke"
render_vars["discover"] = call_rally(discover_cmd)
# Get Rally deployment ID
rally_deployment_id = subprocess.check_output(
"rally deployment list | awk '/devstack/ {print $2}'",
@ -251,10 +247,21 @@ def main():
"cd /home/jenkins/.rally/tempest/for-deployment-%s "
"git log --skip 1 -n 1 | awk '/commit/ {print $2}' | head -1"
% rally_deployment_id, shell=True, stderr=subprocess.STDOUT).strip()
# Reinstall Tempest with providing the --version arg to the command
# Install the penultimate Tempest version
render_vars["reinstall"] = call_rally(
"verify reinstall --version %s" % tempest_commit_id)
# Install a simple Tempest plugin
render_vars["installplugin"] = call_rally(
"verify installplugin --source %s" % TEMPEST_PLUGIN)
# Discover tests depending on Tempest suite
discover_cmd = "verify discover"
if args.mode == "light":
discover_cmd += " --pattern smoke"
render_vars["discover"] = call_rally(discover_cmd)
# Generate and show Tempest config file
render_vars["genconfig"] = call_rally("verify genconfig")
render_vars["showconfig"] = call_rally("verify showconfig")

View File

@ -482,6 +482,24 @@ class VerifyCommandsTestCase(test.TestCase):
mock_verification_reinstall_tempest.assert_called_once_with(
deployment_uuid, None, version, False)
@mock.patch("rally.api.Verification.install_tempest_plugin")
def test_install_plugin_from_url(
self, mock_verification_install_tempest_plugin):
deployment_uuid = "83514de2-a770-4e28-82dd-2826b725e733"
url = "https://github.com/fake/plugin"
self.verify.installplugin(deployment_uuid, url)
mock_verification_install_tempest_plugin.assert_called_once_with(
deployment_uuid, url, None, False)
@mock.patch("rally.api.Verification.install_tempest_plugin")
def test_install_plugin_from_path(
self, mock_verification_install_tempest_plugin):
deployment_uuid = "83514de2-a770-4e28-82dd-2826b725e733"
path = "/tmp/fake/plugin"
self.verify.installplugin(deployment_uuid, path)
mock_verification_install_tempest_plugin.assert_called_once_with(
deployment_uuid, path, None, False)
@mock.patch("rally.api.Verification.discover_tests")
def test_discover(self, mock_verification_discover_tests):
deployment_uuid = "97725f22-1cd2-46a5-8c62-3cdc36ed6d2a"

View File

@ -495,6 +495,26 @@ class VerificationAPITestCase(BaseDeploymentTestCase):
self.tempest.uninstall.assert_called_once_with()
self.tempest.install.assert_called_once_with()
@mock.patch("os.path.exists", return_value=True)
@mock.patch("rally.common.objects.Deployment.get")
@mock.patch("rally.verification.tempest.tempest.Tempest")
def test_install_tempest_plugin_from_url(
self, mock_tempest, mock_deployment_get, mock_exists):
mock_tempest.return_value = self.tempest
api.Verification.install_tempest_plugin(self.deployment_uuid,
"https://fake/plugin")
self.tempest.install_plugin.assert_called_once_with()
@mock.patch("os.path.exists", return_value=True)
@mock.patch("rally.common.objects.Deployment.get")
@mock.patch("rally.verification.tempest.tempest.Tempest")
def test_install_tempest_plugin_from_path(
self, mock_tempest, mock_deployment_get, mock_exists):
mock_tempest.return_value = self.tempest
api.Verification.install_tempest_plugin(self.deployment_uuid,
"/tmp/fake/plugin")
self.tempest.install_plugin.assert_called_once_with()
@mock.patch("os.path.exists", return_value=True)
@mock.patch("rally.common.objects.Deployment.get")
@mock.patch("rally.verification.tempest.tempest.Tempest")

View File

@ -17,6 +17,7 @@ import copy
import os
import subprocess
import ddt
import mock
from rally import exceptions
@ -326,6 +327,21 @@ class TempestInstallAndUninstallTestCase(BaseTestCase):
mock_move.assert_called_once_with(source, dest)
@ddt.ddt
class TempestInstallPluginsTestCase(BaseTestCase):
@mock.patch(TEMPEST_PATH + ".tempest.check_output")
@ddt.data("https://github.com/fake-plugin", "/tmp/fake-plugin")
def test_install_plugin(self, plugin_source, mock_tempest_check_output):
self.verifier.plugin_source = plugin_source
self.verifier.install_plugin()
cmd = [self.verifier.venv_wrapper, "pip", "install", "-e",
"git+{0}@master#egg={1}".format(plugin_source, "fake-plugin")]
mock_tempest_check_output.assert_called_with(cmd,
cwd=self.verifier.path())
class TempestVerifyTestCase(BaseTestCase):
def _get_fake_call(self, testr_args,
concur_args="--parallel --concurrency 0"):