[Verify] Adding possibility to specify concurrency for tests

The `rally verify start` command doesn't allow us to specify
concurrency for Tempest tests. The tests always are run in parallel
mode and use concurrency equal to the count of CPU cores. However,
users may want to use specific concurrency for running tests based
on their deployments and available resources.

This patch adds possibility to specify desired concurrency for tests.

Closes-Bug: #1524968

Change-Id: I3ba2c987c739b13bb03005b1988ee6bd01e8a08d
This commit is contained in:
Yaroslav Lobankov 2015-12-11 19:46:20 +03:00
parent 180b57acc3
commit 1f3851f409
8 changed files with 56 additions and 56 deletions

View File

@ -56,7 +56,7 @@ _rally()
OPTS["verify_results"]="--uuid --html --json --output-file" OPTS["verify_results"]="--uuid --html --json --output-file"
OPTS["verify_show"]="--uuid --sort-by --detailed" OPTS["verify_show"]="--uuid --sort-by --detailed"
OPTS["verify_showconfig"]="--deployment" OPTS["verify_showconfig"]="--deployment"
OPTS["verify_start"]="--deployment --set --regex --tests-file --tempest-config --no-use --system-wide-install" OPTS["verify_start"]="--deployment --set --regex --tests-file --tempest-config --no-use --system-wide-install --concurrency"
OPTS["verify_uninstall"]="--deployment" OPTS["verify_uninstall"]="--deployment"
OPTS["verify_use"]="--verification" OPTS["verify_use"]="--verification"

View File

@ -358,7 +358,7 @@ class Verification(object):
@classmethod @classmethod
def verify(cls, deployment, set_name, regex, tests_file, def verify(cls, deployment, set_name, regex, tests_file,
tempest_config, system_wide_install=False): tempest_config, system_wide_install=False, concur=0):
"""Start verifying. """Start verifying.
:param deployment: UUID or name of a deployment :param deployment: UUID or name of a deployment
@ -370,9 +370,10 @@ class Verification(object):
when installing Tempest; whether or not to when installing Tempest; whether or not to
use the local env instead of the Tempest use the local env instead of the Tempest
virtual env when running the tests virtual env when running the tests
:param concur: How many processes to use to run Tempest tests.
The default value (0) auto-detects CPU count
:returns: Verification object :returns: Verification object
""" """
deployment_uuid = objects.Deployment.get(deployment)["uuid"] deployment_uuid = objects.Deployment.get(deployment)["uuid"]
verification = objects.Verification(deployment_uuid=deployment_uuid) verification = objects.Verification(deployment_uuid=deployment_uuid)
verifier = tempest.Tempest(deployment_uuid, verification=verification, verifier = tempest.Tempest(deployment_uuid, verification=verification,
@ -389,7 +390,7 @@ class Verification(object):
LOG.info("Starting verification of deployment: %s" % deployment_uuid) LOG.info("Starting verification of deployment: %s" % deployment_uuid)
verification.set_running() verification.set_running()
verifier.verify(set_name=set_name, regex=regex, verifier.verify(set_name=set_name, regex=regex,
tests_file=tests_file) tests_file=tests_file, concur=concur)
return verification return verification

View File

@ -63,10 +63,13 @@ class VerifyCommands(object):
"requirements have to be already installed in " "requirements have to be already installed in "
"the local env!", "the local env!",
required=False, action="store_true") required=False, action="store_true")
@cliutils.args("--concurrency", dest="concur", type=int, required=False,
help="How many processes to use to run Tempest tests. "
"The default value (0) auto-detects your CPU count")
@envutils.with_default_deployment(cli_arg_name="deployment") @envutils.with_default_deployment(cli_arg_name="deployment")
def start(self, set_name="", deployment=None, regex=None, def start(self, set_name="", deployment=None, regex=None,
tests_file=None, tempest_config=None, do_use=True, tests_file=None, tempest_config=None, do_use=True,
system_wide_install=False): system_wide_install=False, concur=0):
"""Start set of tests. """Start set of tests.
:param set_name: Name of tempest test set :param set_name: Name of tempest test set
@ -79,6 +82,8 @@ class VerifyCommands(object):
when installing Tempest; whether or not to when installing Tempest; whether or not to
use the local env instead of the Tempest use the local env instead of the Tempest
virtual env when running the tests virtual env when running the tests
:param concur: How many processes to use to run Tempest tests.
The default value (0) auto-detects CPU count
""" """
if regex and set_name: if regex and set_name:
@ -109,7 +114,7 @@ class VerifyCommands(object):
verification = api.Verification.verify(deployment, set_name, regex, verification = api.Verification.verify(deployment, set_name, regex,
tests_file, tempest_config, tests_file, tempest_config,
system_wide_install) system_wide_install, concur)
if do_use: if do_use:
self.use(verification["uuid"]) self.use(verification["uuid"])

View File

@ -297,54 +297,48 @@ class Tempest(object):
shutil.rmtree(self.path()) shutil.rmtree(self.path())
@logging.log_verification_wrapper(LOG.info, _("Run verification.")) @logging.log_verification_wrapper(LOG.info, _("Run verification."))
def _prepare_and_run(self, set_name, regex, tests_file): def _prepare_and_run(self, set_name, regex, tests_file, concur):
if not self.is_configured(): if not self.is_configured():
self.generate_config_file() self.generate_config_file()
testr_args = "--concurrency %d" % concur
if set_name == "full": if set_name == "full":
testr_arg = "" pass
elif set_name in consts.TempestTestsAPI:
testr_args += " tempest.api.%s" % set_name
elif regex:
testr_args += " %s" % regex
else: else:
if set_name in consts.TempestTestsAPI: testr_args += " --load-list %s" % os.path.abspath(tests_file)
testr_arg = "tempest.api.%s" % set_name
elif tests_file:
testr_arg = "--load-list %s" % os.path.abspath(tests_file)
else:
testr_arg = set_name or regex
self.verification.start_verifying(set_name) self.verification.start_verifying(set_name)
try: try:
self.run(testr_arg) self.run(testr_args)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
LOG.info(_("Test set '%s' has been finished with errors. " LOG.info(_("Test run has been finished with errors. "
"Check logs for details.") % set_name) "Check logs for details."))
def run(self, testr_arg=None, log_file=None, tempest_conf=None): def run(self, testr_args="", log_file=None, tempest_conf=None):
"""Launch tempest with given arguments """Run Tempest.
:param testr_arg: argument which will be transmitted into testr :param testr_args: Arguments which will be passed to testr
:type testr_arg: str :param log_file: Path to a file for raw subunit stream logs.
:param log_file: file name for raw subunit results of tests. If not If not specified, the value from "self.log_file_raw"
specified, value from "self.log_file_raw" will be used as the path to the file
will be chosen. :param tempest_conf: User specified Tempest config file location
:type log_file: str
:param tempest_conf: User specified tempest.conf location
:type tempest_conf: str
:raises subprocess.CalledProcessError: if tests has been finished
with error.
""" """
if tempest_conf and os.path.isfile(tempest_conf): if tempest_conf and os.path.isfile(tempest_conf):
self.config_file = tempest_conf self.config_file = tempest_conf
LOG.info(_("Tempest config file: %s") % self.config_file) LOG.info(_("Tempest config file: %s") % self.config_file)
test_cmd = ( test_cmd = (
"%(venv)s testr run --parallel --subunit %(arg)s " "%(venv)s testr run --subunit --parallel %(testr_args)s "
"| tee %(log_file)s " "| tee %(log_file)s "
"| %(venv)s subunit-trace -f -n" % "| %(venv)s subunit-trace -f -n" %
{ {
"venv": self.venv_wrapper, "venv": self.venv_wrapper,
"arg": testr_arg, "testr_args": testr_args,
"log_file": log_file or self.log_file_raw "log_file": log_file or self.log_file_raw
}) })
LOG.debug("Test(s) started by the command: %s" % test_cmd) LOG.debug("Test(s) started by the command: %s" % test_cmd)
@ -393,8 +387,8 @@ class Tempest(object):
else: else:
self.verification.set_failed() self.verification.set_failed()
def verify(self, set_name, regex, tests_file): def verify(self, set_name, regex, tests_file, concur):
self._prepare_and_run(set_name, regex, tests_file) self._prepare_and_run(set_name, regex, tests_file, concur)
self._save_results() self._save_results()
def import_results(self, set_name, log_file): def import_results(self, set_name, log_file):

View File

@ -60,7 +60,7 @@ class VerifyCommandsTestCase(test.TestCase):
mock_verification_verify.assert_called_once_with( mock_verification_verify.assert_called_once_with(
deployment_id, default_set_name, default_regex, deployment_id, default_set_name, default_regex,
default_tests_file, None, False) default_tests_file, None, False, 0)
@mock.patch("rally.osclients.Clients") @mock.patch("rally.osclients.Clients")
@mock.patch("rally.api.Verification.verify") @mock.patch("rally.api.Verification.verify")
@ -80,7 +80,7 @@ class VerifyCommandsTestCase(test.TestCase):
mock_verification_verify.assert_called_once_with( mock_verification_verify.assert_called_once_with(
deployment_id, default_set_name, default_regex, deployment_id, default_set_name, default_regex,
default_tests_file, tempest_config.name, False) default_tests_file, tempest_config.name, False, 0)
tempest_config.close() tempest_config.close()
@mock.patch("rally.api.Verification.verify") @mock.patch("rally.api.Verification.verify")
@ -93,7 +93,7 @@ class VerifyCommandsTestCase(test.TestCase):
tests_file=tests_file, do_use=False) tests_file=tests_file, do_use=False)
mock_verification_verify.assert_called_once_with( mock_verification_verify.assert_called_once_with(
deployment_id, "", None, tests_file, None, False) deployment_id, "", None, tests_file, None, False, 0)
@mock.patch("rally.api.Verification.verify") @mock.patch("rally.api.Verification.verify")
def test_start_with_wrong_set_name(self, mock_verification_verify): def test_start_with_wrong_set_name(self, mock_verification_verify):

View File

@ -40,7 +40,7 @@ class TempestScenarioTestCase(test.TestCase):
self.scenario._add_atomic_actions = mock.MagicMock() self.scenario._add_atomic_actions = mock.MagicMock()
def get_tests_launcher_cmd(self, tests): def get_tests_launcher_cmd(self, tests):
return ("%(venv)s testr run --parallel --subunit %(tests)s " return ("%(venv)s testr run --subunit --parallel %(tests)s "
"| tee /dev/null " "| tee /dev/null "
"| %(venv)s subunit-trace -f -n" % "| %(venv)s subunit-trace -f -n" %
{ {

View File

@ -408,7 +408,7 @@ class VerificationAPITestCase(BaseDeploymentTestCase):
self.tempest.is_installed.assert_called_once_with() self.tempest.is_installed.assert_called_once_with()
self.tempest.verify.assert_called_once_with( self.tempest.verify.assert_called_once_with(
set_name="smoke", regex=None, tests_file=None) set_name="smoke", regex=None, tests_file=None, concur=0)
@mock.patch("rally.api.objects.Deployment.get") @mock.patch("rally.api.objects.Deployment.get")
@mock.patch("rally.api.objects.Verification") @mock.patch("rally.api.objects.Verification")
@ -425,7 +425,7 @@ class VerificationAPITestCase(BaseDeploymentTestCase):
self.tempest.is_installed.assert_called_once_with() self.tempest.is_installed.assert_called_once_with()
self.tempest.install.assert_called_once_with() self.tempest.install.assert_called_once_with()
self.tempest.verify.assert_called_once_with( self.tempest.verify.assert_called_once_with(
set_name="smoke", regex=None, tests_file=None) set_name="smoke", regex=None, tests_file=None, concur=0)
@mock.patch("os.path.exists", return_value=True) @mock.patch("os.path.exists", return_value=True)
@mock.patch("rally.api.objects.Deployment.get") @mock.patch("rally.api.objects.Deployment.get")
@ -441,7 +441,7 @@ class VerificationAPITestCase(BaseDeploymentTestCase):
self.deployment_uuid, "", None, tests_file, None) self.deployment_uuid, "", None, tests_file, None)
self.tempest.verify.assert_called_once_with( self.tempest.verify.assert_called_once_with(
set_name="", regex=None, tests_file=tests_file) set_name="", regex=None, tests_file=tests_file, concur=0)
@mock.patch("rally.common.objects.Deployment.get") @mock.patch("rally.common.objects.Deployment.get")
@mock.patch("rally.api.objects.Verification") @mock.patch("rally.api.objects.Verification")

View File

@ -332,15 +332,15 @@ class TempestInstallAndUninstallTestCase(BaseTestCase):
class TempestVerifyTestCase(BaseTestCase): class TempestVerifyTestCase(BaseTestCase):
def _get_fake_call(self, testr_arg, is_set=True): def _get_fake_call(self, testr_args):
return ( return (
"%(venv)s testr run --parallel --subunit %(testr_arg)s" "%(venv)s testr run --subunit --parallel"
" | tee %(tempest_path)s/subunit.stream" " --concurrency 0 %(testr_args)s"
" | tee %(log_file)s"
" | %(venv)s subunit-trace -f -n" % { " | %(venv)s subunit-trace -f -n" % {
"venv": self.verifier.venv_wrapper, "venv": self.verifier.venv_wrapper,
"testr_arg": ("tempest.api." if is_set "testr_args": testr_args,
else "--load-list ") + testr_arg, "log_file": self.verifier.path("subunit.stream")})
"tempest_path": self.verifier.path()})
@mock.patch(TEMPEST_PATH + ".tempest.Tempest.parse_results", @mock.patch(TEMPEST_PATH + ".tempest.Tempest.parse_results",
return_value=None) return_value=None)
@ -356,9 +356,9 @@ class TempestVerifyTestCase(BaseTestCase):
mock_tempest_parse_results): mock_tempest_parse_results):
set_name = "compute" set_name = "compute"
fake_call = self._get_fake_call(set_name) fake_call = self._get_fake_call("tempest.api.%s" % set_name)
self.verifier.verify(set_name, None, None) self.verifier.verify(set_name, None, None, 0)
self.assertEqual(2, mock_tempest_is_configured.call_count) self.assertEqual(2, mock_tempest_is_configured.call_count)
mock_tempest_config.assert_called_once_with(self.verifier.deployment) mock_tempest_config.assert_called_once_with(self.verifier.deployment)
@ -386,9 +386,9 @@ class TempestVerifyTestCase(BaseTestCase):
mock_tempest_resources_context, mock_subprocess, mock_tempest_env, mock_tempest_resources_context, mock_subprocess, mock_tempest_env,
mock_tempest_parse_results): mock_tempest_parse_results):
set_name = "identity" set_name = "identity"
fake_call = self._get_fake_call(set_name) fake_call = self._get_fake_call("tempest.api.%s" % set_name)
self.verifier.verify(set_name, None, None) self.verifier.verify(set_name, None, None, 0)
mock_tempest_is_configured.assert_called_once_with() mock_tempest_is_configured.assert_called_once_with()
self.assertFalse(mock_tempest_config.called) self.assertFalse(mock_tempest_config.called)
@ -414,10 +414,10 @@ class TempestVerifyTestCase(BaseTestCase):
mock_tempest_resources_context, mock_subprocess, mock_tempest_env, mock_tempest_resources_context, mock_subprocess, mock_tempest_env,
mock_tempest_parse_results): mock_tempest_parse_results):
set_name = "identity" set_name = "identity"
fake_call = self._get_fake_call(set_name) fake_call = self._get_fake_call("tempest.api.%s" % set_name)
mock_subprocess.side_effect = subprocess.CalledProcessError mock_subprocess.side_effect = subprocess.CalledProcessError
self.verifier.verify(set_name, None, None) self.verifier.verify(set_name, None, None, 0)
mock_tempest_is_configured.assert_called_once_with() mock_tempest_is_configured.assert_called_once_with()
self.assertFalse(mock_tempest_config.called) self.assertFalse(mock_tempest_config.called)
@ -442,9 +442,9 @@ class TempestVerifyTestCase(BaseTestCase):
self, mock_tempest_is_configured, mock_tempest_resources_context, self, mock_tempest_is_configured, mock_tempest_resources_context,
mock_subprocess, mock_tempest_env, mock_tempest_parse_results): mock_subprocess, mock_tempest_env, mock_tempest_parse_results):
tests_file = "/path/to/tests/file" tests_file = "/path/to/tests/file"
fake_call = self._get_fake_call(tests_file, is_set=False) fake_call = self._get_fake_call("--load-list %s" % tests_file)
self.verifier.verify("", None, tests_file) self.verifier.verify("", None, tests_file, 0)
self.verifier.verification.start_verifying.assert_called_once_with("") self.verifier.verification.start_verifying.assert_called_once_with("")
mock_subprocess.check_call.assert_called_once_with( mock_subprocess.check_call.assert_called_once_with(