From 23883fa0c9a16318eb7c5dfe64587cbc98f84c3e Mon Sep 17 00:00:00 2001 From: chenhb Date: Wed, 12 Aug 2020 14:17:44 +0800 Subject: [PATCH] restart the task expect to restart one finished or aborted task, or restart some failure scenarios of workloads. Change-Id: Ifebef92bc0f549e4d0c778261bfc9b40e90d39af --- etc/rally.bash_completion | 1 + rally/cli/commands/task.py | 74 +++++++++++++++++++++++++++- tests/functional/test_cli_task.py | 46 +++++++++++++++++ tests/unit/cli/commands/test_task.py | 61 +++++++++++++++++++++++ 4 files changed, 181 insertions(+), 1 deletion(-) diff --git a/etc/rally.bash_completion b/etc/rally.bash_completion index be16872ef2..b2ff505ae4 100644 --- a/etc/rally.bash_completion +++ b/etc/rally.bash_completion @@ -50,6 +50,7 @@ _rally() OPTS["task_import"]="--file --deployment --tag" OPTS["task_list"]="--deployment --all-deployments --status --tag --uuids-only" OPTS["task_report"]="--out --open --html --html-static --json --uuid --deployment" + OPTS["task_restart"]="--deployment --uuid --scenario --tag --no-use --abort-on-sla-failure" OPTS["task_results"]="--uuid" OPTS["task_sla-check"]="--uuid --json" OPTS["task_start"]="--deployment --task --task-args --task-args-file --tag --no-use --abort-on-sla-failure" diff --git a/rally/cli/commands/task.py b/rally/cli/commands/task.py index 543b774f2d..59ec370352 100644 --- a/rally/cli/commands/task.py +++ b/rally/cli/commands/task.py @@ -201,6 +201,12 @@ class TaskCommands(object): args_file=task_args_file) print("Running Rally version", version.version_string()) + return self._start_task(api, deployment, task_config=input_task, + tags=tags, do_use=do_use, + abort_on_sla_failure=abort_on_sla_failure) + + def _start_task(self, api, deployment, task_config, tags=None, + do_use=False, abort_on_sla_failure=False): try: task_instance = api.task.create(deployment=deployment, tags=tags) tags = "[tags: '%s']" % "', '".join(tags) if tags else "" @@ -215,7 +221,7 @@ class TaskCommands(object): if do_use: self.use(api, task_instance["uuid"]) - api.task.start(deployment=deployment, config=input_task, + api.task.start(deployment=deployment, config=task_config, task=task_instance["uuid"], abort_on_sla_failure=abort_on_sla_failure) @@ -227,6 +233,72 @@ class TaskCommands(object): return 2 return 0 + @cliutils.args("--deployment", dest="deployment", type=str, + metavar="", required=False, + help="UUID or name of a deployment.") + @cliutils.args("--uuid", type=str, dest="task_id", help="UUID of task.") + @cliutils.args("--scenario", type=str, dest="scenarios", nargs="+", + help="scenario name of workload") + @cliutils.args("--tag", nargs="+", dest="tags", type=str, required=False, + help="Mark the task with a tag or a few tags.") + @cliutils.args("--no-use", action="store_false", dest="do_use", + help="Don't set new task as default for future operations.") + @cliutils.args("--abort-on-sla-failure", action="store_true", + dest="abort_on_sla_failure", + help="Abort the execution of a task when any SLA check " + "for it fails for subtask or workload.") + @envutils.with_default_deployment(cli_arg_name="deployment") + @envutils.with_default_task_id + @plugins.ensure_plugins_are_loaded + def restart(self, api, deployment=None, task_id=None, scenarios=None, + tags=None, do_use=False, abort_on_sla_failure=False): + """Restart a task or some scenarios in workloads of task.""" + if scenarios is not None: + scenarios = (isinstance(scenarios, list) and scenarios + or [scenarios]) + task = api.task.get(task_id=task_id, detailed=True) + if task["status"] == consts.TaskStatus.CRASHED or task["status"] == ( + consts.TaskStatus.VALIDATION_FAILED): + print("-" * 80) + print("\nUnable to restart task.") + validation = task["validation_result"] + if logging.is_debug(): + print(yaml.safe_load(validation["trace"])) + else: + print(validation["etype"]) + print(validation["msg"]) + print("\nFor more details run:\nrally -d task detailed %s" + % task["uuid"]) + return 1 + retask = {"version": 2, "title": task["title"], + "description": task["description"], + "tags": task["tags"], "subtasks": []} + for subtask in task["subtasks"]: + workloads = [] + for workload in subtask["workloads"]: + if scenarios is None or workload["name"] in scenarios: + workloads.append({ + "scenario": {workload["name"]: workload["args"]}, + "contexts": workload["contexts"], + "runner": { + workload["runner_type"]: workload["runner"]}, + "hooks": workload["hooks"], + "sla": workload["sla"] + }) + if workloads: + retask["subtasks"].append({ + "title": subtask["title"], + "description": subtask["description"], + "workloads": workloads}) + + if retask["subtasks"]: + return self._start_task(api, deployment, retask, tags=tags, + do_use=do_use, + abort_on_sla_failure=abort_on_sla_failure) + else: + print("Not Found matched scenario.") + return 1 + @cliutils.args("--uuid", type=str, dest="task_id", help="UUID of task.") @envutils.with_default_task_id @cliutils.args( diff --git a/tests/functional/test_cli_task.py b/tests/functional/test_cli_task.py index 1508706564..561aa0b140 100644 --- a/tests/functional/test_cli_task.py +++ b/tests/functional/test_cli_task.py @@ -1050,6 +1050,52 @@ class TaskTestCase(testtools.TestCase): " ".join(task_uuids), html_report)) self.assertTrue(os.path.exists(html_report)) + def test_restart(self): + rally = utils.Rally() + deployment_id = utils.get_global("RALLY_DEPLOYMENT", rally.env) + cfg = self._get_sample_task_config() + config = utils.TaskConfig(cfg) + output = rally(("task start --task %(task_file)s " + "--deployment %(deployment_id)s") % + {"task_file": config.filename, + "deployment_id": deployment_id}) + + output = rally("task restart") + result = re.search( + r"(?P[0-9a-f\-]{36}): started", output) + self.assertIsNotNone(result) + + def test_restart_with_scenario(self): + rally = utils.Rally() + deployment_id = utils.get_global("RALLY_DEPLOYMENT", rally.env) + cfg = self._get_sample_task_config() + config = utils.TaskConfig(cfg) + output = rally(("task start --task %(task_file)s " + "--deployment %(deployment_id)s") % + {"task_file": config.filename, + "deployment_id": deployment_id}) + + output = rally( + "task restart --scenario Dummy.dummy_random_fail_in_atomic") + result = re.search( + r"(?P[0-9a-f\-]{36}): started", output) + self.assertIsNotNone(result) + result = re.search( + r"test scenario Dummy\.dummy_random_fail_in_atomic", output) + self.assertIsNotNone(result) + + def test_restart_with_fake_scenario(self): + rally = utils.Rally() + deployment_id = utils.get_global("RALLY_DEPLOYMENT", rally.env) + cfg = self._get_sample_task_config() + config = utils.TaskConfig(cfg) + rally(("task start --task %(task_file)s " + "--deployment %(deployment_id)s") % + {"task_file": config.filename, + "deployment_id": deployment_id}) + self.assertRaises(utils.RallyCliError, rally, + "task restart --scenario fake.fake_scenario") + class SLATestCase(testtools.TestCase): diff --git a/tests/unit/cli/commands/test_task.py b/tests/unit/cli/commands/test_task.py index 71f302b2cd..c16f48e2a9 100644 --- a/tests/unit/cli/commands/test_task.py +++ b/tests/unit/cli/commands/test_task.py @@ -322,6 +322,67 @@ class TaskCommandsTestCase(test.TestCase): self.assertFalse(mock__detailed.called) + @mock.patch("rally.cli.commands.task.TaskCommands._start_task") + @ddt.data({"scenario": None}, + {"scenario": "scenario_name"}, + {"scenario": "none_name"}) + @ddt.unpack + def test_restart(self, mock__start_task, scenario): + self.fake_api.task.get.return_value = { + "status": "finished", + "title": "fake_task", + "description": "this is a test", + "tags": [], + "subtasks": [ + {"title": "subtask", "description": "", + "workloads": [ + { + "name": "scenario_name", + "args": {}, + "contexts": {}, + "runner": {"times": 20, "concurrency": 5}, + "runner_type": "constant", + "hooks": [], + "sla": {} + }]} + ] + } + if scenario == "none_name": + self.assertEqual( + 1, + self.task.restart(self.fake_api, "deployment_uuid", + "task_uuid", scenarios=scenario) + ) + else: + self.assertEqual( + mock__start_task.return_value, + self.task.restart(self.fake_api, "deployment_uuid", + "task_uuid", scenarios=scenario) + ) + self.fake_api.task.get.assert_called_once_with(task_id="task_uuid", + detailed=True) + + def test_restart_by_crashed_task(self): + self.fake_api.task.get.return_value = { + "uuid": "task_uuid", + "status": "crashed", + "title": "fake_task", + "description": "this is a test", + "tags": [], + "subtasks": [], + "validation_result": { + "trace": {}, + "etype": "", + "msg": "" + } + } + self.assertEqual( + 1, + self.task.restart(self.fake_api, "deployment_uuid", "task_uuid") + ) + self.fake_api.task.get.assert_called_once_with(task_id="task_uuid", + detailed=True) + def test_abort(self): test_uuid = "17860c43-2274-498d-8669-448eff7b073f" self.task.abort(self.fake_api, test_uuid)