Extend tags feature of tasks

* Add ability to setup multiple tags for a task.

  The database supports an unlimited number of tags per tasks. It would be
  nice to support that feature from user side too.

* The tags feature is a bit useless, if there is no way to filter by them.
  This patch adds this ability to `rally task list` command.

Change-Id: I959fa904a38f193f77f25b53ba3ee86760fff91c
This commit is contained in:
Andrey Kurilin 2017-06-07 17:02:36 +03:00
parent 91577f0f1f
commit 26cc4be584
9 changed files with 170 additions and 97 deletions

View File

@ -33,7 +33,7 @@ _rally()
OPTS["task_detailed"]="--uuid --iterations-data" OPTS["task_detailed"]="--uuid --iterations-data"
OPTS["task_export"]="--uuid --type --to" OPTS["task_export"]="--uuid --type --to"
OPTS["task_import"]="--file --deployment --tag" OPTS["task_import"]="--file --deployment --tag"
OPTS["task_list"]="--deployment --all-deployments --status --uuids-only" OPTS["task_list"]="--deployment --all-deployments --status --tag --uuids-only"
OPTS["task_report"]="--out --open --html --html-static --uuid" OPTS["task_report"]="--out --open --html --html-static --uuid"
OPTS["task_results"]="--uuid" OPTS["task_results"]="--uuid"
OPTS["task_sla-check"]="--uuid --json" OPTS["task_sla-check"]="--uuid --json"

View File

@ -381,14 +381,14 @@ class _Task(APIGroup):
@api_wrapper(path=API_REQUEST_PREFIX + "/task/create", @api_wrapper(path=API_REQUEST_PREFIX + "/task/create",
method="POST") method="POST")
def create(self, deployment, tag): def create(self, deployment, tags=None):
"""Create a task without starting it. """Create a task without starting it.
Task is a list of benchmarks that will be called one by one, results of Task is a list of benchmarks that will be called one by one, results of
execution will be stored in DB. execution will be stored in DB.
:param deployment: UUID or name of the deployment :param deployment: UUID or name of the deployment
:param tag: tag for this task :param tags: a list of tags for this task
:returns: Task object :returns: Task object
""" """
deployment = objects.Deployment.get(deployment) deployment = objects.Deployment.get(deployment)
@ -399,7 +399,7 @@ class _Task(APIGroup):
status=deployment["status"]) status=deployment["status"])
return objects.Task(deployment_uuid=deployment["uuid"], return objects.Task(deployment_uuid=deployment["uuid"],
tag=tag).to_dict() tags=tags).to_dict()
@api_wrapper(path=API_REQUEST_PREFIX + "/task/validate", @api_wrapper(path=API_REQUEST_PREFIX + "/task/validate",
method="GET") method="GET")
@ -548,7 +548,7 @@ class _Task(APIGroup):
@api_wrapper(path=API_REQUEST_PREFIX + "/task/import_results", @api_wrapper(path=API_REQUEST_PREFIX + "/task/import_results",
method="POST") method="POST")
def import_results(self, deployment, task_results, tag=None): def import_results(self, deployment, task_results, tags=None):
"""Import json results of a test into rally database""" """Import json results of a test into rally database"""
deployment = objects.Deployment.get(deployment) deployment = objects.Deployment.get(deployment)
if deployment["status"] != consts.DeployStatus.DEPLOY_FINISHED: if deployment["status"] != consts.DeployStatus.DEPLOY_FINISHED:
@ -557,7 +557,8 @@ class _Task(APIGroup):
uuid=deployment["uuid"], uuid=deployment["uuid"],
status=deployment["status"]) status=deployment["status"])
task_inst = objects.Task(deployment_uuid=deployment["uuid"], tag=tag) task_inst = objects.Task(deployment_uuid=deployment["uuid"],
tags=tags)
task_inst.update_status(consts.TaskStatus.RUNNING) task_inst.update_status(consts.TaskStatus.RUNNING)
for result in task_results: for result in task_results:
subtask_obj = task_inst.add_subtask(title=result["key"]["name"]) subtask_obj = task_inst.add_subtask(title=result["key"]["name"])

View File

@ -194,7 +194,8 @@ class TaskCommands(object):
help="Path to the file with input task args (dict in " help="Path to the file with input task args (dict in "
"JSON/YAML). These args are used " "JSON/YAML). These args are used "
"to render the Jinja2 template in the input task.") "to render the Jinja2 template in the input task.")
@cliutils.args("--tag", help="Tag for this task") @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", @cliutils.args("--no-use", action="store_false", dest="do_use",
help="Don't set new task as default for future operations.") help="Don't set new task as default for future operations.")
@cliutils.args("--abort-on-sla-failure", action="store_true", @cliutils.args("--abort-on-sla-failure", action="store_true",
@ -204,7 +205,7 @@ class TaskCommands(object):
@envutils.with_default_deployment(cli_arg_name="deployment") @envutils.with_default_deployment(cli_arg_name="deployment")
@plugins.ensure_plugins_are_loaded @plugins.ensure_plugins_are_loaded
def start(self, api, task_file, deployment=None, task_args=None, def start(self, api, task_file, deployment=None, task_args=None,
task_args_file=None, tag=None, do_use=False, task_args_file=None, tags=None, do_use=False,
abort_on_sla_failure=False): abort_on_sla_failure=False):
"""Start benchmark task. """Start benchmark task.
@ -221,26 +222,25 @@ class TaskCommands(object):
used to render the Jinja2 template in used to render the Jinja2 template in
the input task. the input task.
:param deployment: UUID or name of the deployment :param deployment: UUID or name of the deployment
:param tag: optional tag for this task :param tags: optional tag for this task
:param do_use: if True, the new task will be stored as the default one :param do_use: if True, the new task will be stored as the default one
for future operations for future operations
:param abort_on_sla_failure: if True, the execution of a benchmark :param abort_on_sla_failure: if True, the execution of a benchmark
scenario will stop when any SLA check scenario will stop when any SLA check
for it fails for it fails
""" """
input_task = self._load_and_validate_task(api, task_file, input_task = self._load_and_validate_task(api, task_file,
raw_args=task_args, raw_args=task_args,
args_file=task_args_file) args_file=task_args_file)
print("Running Rally version", version.version_string()) print("Running Rally version", version.version_string())
try: try:
task_instance = api.task.create(deployment=deployment, tag=tag) task_instance = api.task.create(deployment=deployment, tags=tags)
tags = "[tags: '%s']" % "', '".join(tags) if tags else ""
print(cliutils.make_header( print(cliutils.make_header(
_("Task %(tag)s %(uuid)s: started") _("Task %(tags)s %(uuid)s: started")
% {"uuid": task_instance["uuid"], % {"uuid": task_instance["uuid"], "tags": tags}))
"tag": task_instance["tag"]}))
print("Benchmarking... This can take a while...\n") print("Benchmarking... This can take a while...\n")
print("To track task status use:\n") print("To track task status use:\n")
print("\trally task status\n\tor\n\trally task detailed\n") print("\trally task status\n\tor\n\trally task detailed\n")
@ -498,11 +498,13 @@ class TaskCommands(object):
@cliutils.args("--status", type=str, dest="status", @cliutils.args("--status", type=str, dest="status",
help="List tasks with specified status." help="List tasks with specified status."
" Available statuses: %s" % ", ".join(consts.TaskStatus)) " Available statuses: %s" % ", ".join(consts.TaskStatus))
@cliutils.args("--tag", nargs="+", dest="tags", type=str, required=False,
help="Tags to filter tasks by.")
@cliutils.args("--uuids-only", action="store_true", @cliutils.args("--uuids-only", action="store_true",
dest="uuids_only", help="List task UUIDs only.") dest="uuids_only", help="List task UUIDs only.")
@envutils.with_default_deployment(cli_arg_name="deployment") @envutils.with_default_deployment(cli_arg_name="deployment")
def list(self, api, deployment=None, all_deployments=False, status=None, def list(self, api, deployment=None, all_deployments=False, status=None,
uuids_only=False): tags=None, uuids_only=False):
"""List tasks, started and finished. """List tasks, started and finished.
Displayed tasks can be filtered by status or deployment. By Displayed tasks can be filtered by status or deployment. By
@ -518,10 +520,10 @@ class TaskCommands(object):
filters = {} filters = {}
headers = ["uuid", "deployment_name", "created_at", "duration", headers = ["uuid", "deployment_name", "created_at", "duration",
"status", "tag"] "status", "tags"]
if status in consts.TaskStatus: if status in consts.TaskStatus:
filters.setdefault("status", status) filters["status"] = status
elif status: elif status:
print(_("Error: Invalid task status '%s'.\n" print(_("Error: Invalid task status '%s'.\n"
"Available statuses: %s") % ( "Available statuses: %s") % (
@ -530,7 +532,10 @@ class TaskCommands(object):
return(1) return(1)
if not all_deployments: if not all_deployments:
filters.setdefault("deployment", deployment) filters["deployment"] = deployment
if tags:
filters["tags"] = tags
task_list = api.task.list(**filters) task_list = api.task.list(**filters)
@ -540,9 +545,16 @@ class TaskCommands(object):
print_header=False, print_header=False,
print_border=False) print_border=False)
elif task_list: elif task_list:
def tags_formatter(t):
if not t["tags"]:
return ""
return "'%s'" % "', '".join(t["tags"])
cliutils.print_list( cliutils.print_list(
task_list, task_list,
headers, sortby_index=headers.index("created_at")) headers,
sortby_index=headers.index("created_at"),
formatters={"tags": tags_formatter})
else: else:
if status: if status:
print(_("There are no tasks in '%s' status. " print(_("There are no tasks in '%s' status. "
@ -885,26 +897,26 @@ class TaskCommands(object):
@cliutils.args("--deployment", dest="deployment", type=str, @cliutils.args("--deployment", dest="deployment", type=str,
metavar="<uuid>", required=False, metavar="<uuid>", required=False,
help="UUID or name of a deployment.") help="UUID or name of a deployment.")
@cliutils.args("--tag", help="Tag for this task") @cliutils.args("--tag", nargs="+", dest="tags", type=str, required=False,
help="Mark the task with a tag or a few tags.")
@envutils.with_default_deployment(cli_arg_name="deployment") @envutils.with_default_deployment(cli_arg_name="deployment")
@cliutils.alias("import") @cliutils.alias("import")
@cliutils.suppress_warnings @cliutils.suppress_warnings
def import_results(self, api, deployment=None, task_file=None, tag=None): def import_results(self, api, deployment=None, task_file=None, tags=None):
"""Import json results of a test into rally database """Import json results of a test into rally database
:param task_file: list, pathes files with tasks results :param task_file: list, pathes files with tasks results
:param deployment: UUID or name of the deployment :param deployment: UUID or name of the deployment
:param tag: optional tag for this task :param tags: optional tag for this task
""" """
if os.path.exists(os.path.expanduser(task_file)): if os.path.exists(os.path.expanduser(task_file)):
tasks_results = self._load_task_results_file(api, task_file) tasks_results = self._load_task_results_file(api, task_file)
task = api.task.import_results(deployment=deployment, task = api.task.import_results(deployment=deployment,
task_results=tasks_results, task_results=tasks_results,
tag=tag) tags=tags)
print(_("Task UUID: %s.") % task["uuid"]) print(_("Task UUID: %s.") % task["uuid"])
else: else:
print(_("ERROR: Invalid file name passed: %s" print(_("ERROR: Invalid file name passed: %s") % task_file,
) % task_file,
file=sys.stderr) file=sys.stderr)
return 1 return 1

View File

@ -197,7 +197,7 @@ def task_update_status(task_uuid, status, allowed_statuses):
status) status)
def task_list(status=None, deployment=None): def task_list(status=None, deployment=None, tags=None):
"""Get a list of tasks. """Get a list of tasks.
:param status: Task status to filter the returned list on. If set to :param status: Task status to filter the returned list on. If set to
@ -205,9 +205,12 @@ def task_list(status=None, deployment=None):
:param deployment: Deployment UUID to filter the returned list on. :param deployment: Deployment UUID to filter the returned list on.
If set to None, tasks from all deployments will be If set to None, tasks from all deployments will be
returned. returned.
:param tags: A list of tags to filter tasks by.
:returns: A list of dicts with data on the tasks. :returns: A list of dicts with data on the tasks.
""" """
return get_impl().task_list(status=status, deployment=deployment) return get_impl().task_list(status=status,
deployment=deployment,
tags=tags)
def task_delete(uuid, status=None): def task_delete(uuid, status=None):

View File

@ -205,8 +205,7 @@ class Connection(object):
return task return task
def _make_old_task(self, task): def _make_old_task(self, task):
tags = self._tags_get(task.uuid, consts.TagType.TASK) tags = sorted(self._tags_get(task.uuid, consts.TagType.TASK))
tag = tags[0] if tags else ""
return { return {
"id": task.id, "id": task.id,
@ -215,7 +214,7 @@ class Connection(object):
"status": task.status, "status": task.status,
"created_at": task.created_at, "created_at": task.created_at,
"updated_at": task.updated_at, "updated_at": task.updated_at,
"tag": tag, "tags": tags,
"verification_log": json.dumps(task.validation_result) "verification_log": json.dumps(task.validation_result)
} }
@ -302,9 +301,9 @@ class Connection(object):
task["results"] = self._task_result_get_all_by_uuid(task["uuid"]) task["results"] = self._task_result_get_all_by_uuid(task["uuid"])
return task return task
# @db_api.serialize @db_api.serialize
def task_create(self, values): def task_create(self, values):
new_tag = values.pop("tag", None) tags = values.pop("tags", None)
# TODO(ikhudoshyn): currently 'input_task' # TODO(ikhudoshyn): currently 'input_task'
# does not come in 'values' # does not come in 'values'
# After completely switching to the new # After completely switching to the new
@ -317,33 +316,31 @@ class Connection(object):
task.update(values) task.update(values)
task.save() task.save()
if new_tag: if tags:
for t in set(tags):
tag = models.Tag() tag = models.Tag()
tag.update({ tag.update({"uuid": task.uuid,
"uuid": task.uuid,
"type": consts.TagType.TASK, "type": consts.TagType.TASK,
"tag": new_tag "tag": t})
})
tag.save() tag.save()
task.tags = sorted(self._tags_get(task.uuid, consts.TagType.TASK))
return self._make_old_task(task) return task
# @db_api.serialize # @db_api.serialize
def task_update(self, uuid, values): def task_update(self, uuid, values):
session = get_session() session = get_session()
values.pop("uuid", None) values.pop("uuid", None)
new_tag = values.pop("tag", None) tags = values.pop("tags", None)
with session.begin(): with session.begin():
task = self._task_get(uuid, session=session) task = self._task_get(uuid, session=session)
task.update(values) task.update(values)
if new_tag: if tags:
for t in set(tags):
tag = models.Tag() tag = models.Tag()
tag.update({ tag.update({"uuid": task.uuid,
"uuid": uuid,
"type": consts.TagType.TASK, "type": consts.TagType.TASK,
"tag": new_tag "tag": t})
})
tag.save() tag.save()
return self._make_old_task(task) return self._make_old_task(task)
@ -365,7 +362,9 @@ class Connection(object):
return result return result
# @db_api.serialize # @db_api.serialize
def task_list(self, status=None, deployment=None): def task_list(self, status=None, deployment=None, tags=None):
session = get_session()
with session.begin():
query = self.model_query(models.Task) query = self.model_query(models.Task)
filters = {} filters = {}
@ -374,10 +373,14 @@ class Connection(object):
if deployment is not None: if deployment is not None:
filters["deployment_uuid"] = self.deployment_get( filters["deployment_uuid"] = self.deployment_get(
deployment)["uuid"] deployment)["uuid"]
if filters: if filters:
query = query.filter_by(**filters) query = query.filter_by(**filters)
if tags:
uuids = self._uuids_by_tags_get(
consts.TagType.TASK, tags)
query = query.filter(models.Task.uuid.in_(uuids))
return [self._make_old_task(task) for task in query.all()] return [self._make_old_task(task) for task in query.all()]
def task_delete(self, uuid, status=None): def task_delete(self, uuid, status=None):

View File

@ -406,8 +406,9 @@ class Task(object):
return db.task_get_status(uuid) return db.task_get_status(uuid)
@staticmethod @staticmethod
def list(status=None, deployment=None): def list(status=None, deployment=None, tags=None):
return [Task(db_task) for db_task in db.task_list(status, deployment)] return [Task(db_task) for db_task in db.task_list(
status, deployment=deployment, tags=tags)]
@staticmethod @staticmethod
def delete_by_uuid(uuid, status=None): def delete_by_uuid(uuid, status=None):

View File

@ -19,9 +19,11 @@ import os.path
import ddt import ddt
import mock import mock
import six
import rally import rally
from rally import api from rally import api
from rally.cli import cliutils
from rally.cli.commands import task from rally.cli.commands import task
from rally.common import yamlutils as yaml from rally.common import yamlutils as yaml
from rally import consts from rally import consts
@ -201,7 +203,7 @@ class TaskCommandsTestCase(test.TestCase):
mock_version): mock_version):
deployment_id = "e0617de9-77d1-4875-9b49-9d5789e29f20" deployment_id = "e0617de9-77d1-4875-9b49-9d5789e29f20"
task_path = "path_to_config.json" task_path = "path_to_config.json"
fake_task = fakes.FakeTask(uuid="some_new_uuid", tag="tag") fake_task = fakes.FakeTask(uuid="some_new_uuid", tags=["tag"])
self.fake_api.task.create.return_value = fake_task self.fake_api.task.create.return_value = fake_task
self.fake_api.task.validate.return_value = fakes.FakeTask( self.fake_api.task.validate.return_value = fakes.FakeTask(
some="json", uuid="some_uuid", temporary=True) some="json", uuid="some_uuid", temporary=True)
@ -209,7 +211,7 @@ class TaskCommandsTestCase(test.TestCase):
self.task.start(self.fake_api, task_path, deployment_id, do_use=True) self.task.start(self.fake_api, task_path, deployment_id, do_use=True)
mock_version.version_string.assert_called_once_with() mock_version.version_string.assert_called_once_with()
self.fake_api.task.create.assert_called_once_with( self.fake_api.task.create.assert_called_once_with(
deployment=deployment_id, tag=None) deployment=deployment_id, tags=None)
self.fake_api.task.start.assert_called_once_with( self.fake_api.task.start.assert_called_once_with(
deployment=deployment_id, deployment=deployment_id,
config=mock__load_and_validate_task.return_value, config=mock__load_and_validate_task.return_value,
@ -238,7 +240,8 @@ class TaskCommandsTestCase(test.TestCase):
status=consts.DeployStatus.DEPLOY_INIT) status=consts.DeployStatus.DEPLOY_INIT)
self.fake_api.task.create.side_effect = exc self.fake_api.task.create.side_effect = exc
self.assertEqual(1, self.task.start(self.fake_api, task_path, self.assertEqual(1, self.task.start(self.fake_api, task_path,
deployment="any", tag="some_tag")) deployment="any",
tags=["some_tag"]))
self.assertFalse(mock_detailed.called) self.assertFalse(mock_detailed.called)
@mock.patch("rally.cli.commands.task.TaskCommands.detailed") @mock.patch("rally.cli.commands.task.TaskCommands.detailed")
@ -246,9 +249,9 @@ class TaskCommandsTestCase(test.TestCase):
return_value="some_config") return_value="some_config")
def test_start_with_task_args(self, mock__load_and_validate_task, def test_start_with_task_args(self, mock__load_and_validate_task,
mock_detailed): mock_detailed):
fake_task = fakes.FakeTask(uuid="new_uuid", tag="some_tag") fake_task = fakes.FakeTask(uuid="new_uuid", tags=["some_tag"])
self.fake_api.task.create.return_value = fakes.FakeTask( self.fake_api.task.create.return_value = fakes.FakeTask(
uuid="new_uuid", tag="some_tag") uuid="new_uuid", tags=["some_tag"])
self.fake_api.task.validate.return_value = fakes.FakeTask( self.fake_api.task.validate.return_value = fakes.FakeTask(
uuid="some_id") uuid="some_id")
@ -257,7 +260,7 @@ class TaskCommandsTestCase(test.TestCase):
task_args_file = "task_args_file" task_args_file = "task_args_file"
self.task.start(self.fake_api, task_path, deployment="any", self.task.start(self.fake_api, task_path, deployment="any",
task_args=task_args, task_args_file=task_args_file, task_args=task_args, task_args_file=task_args_file,
tag="some_tag") tags=["some_tag"])
mock__load_and_validate_task.assert_called_once_with( mock__load_and_validate_task.assert_called_once_with(
self.fake_api, task_path, raw_args=task_args, self.fake_api, task_path, raw_args=task_args,
@ -272,7 +275,7 @@ class TaskCommandsTestCase(test.TestCase):
self.fake_api, self.fake_api,
task_id=fake_task["uuid"]) task_id=fake_task["uuid"])
self.fake_api.task.create.assert_called_once_with( self.fake_api.task.create.assert_called_once_with(
deployment="any", tag="some_tag") deployment="any", tags=["some_tag"])
@mock.patch("rally.cli.commands.task.envutils.get_global") @mock.patch("rally.cli.commands.task.envutils.get_global")
def test_start_no_deployment_id(self, mock_get_global): def test_start_no_deployment_id(self, mock_get_global):
@ -292,7 +295,7 @@ class TaskCommandsTestCase(test.TestCase):
self.assertRaises(exceptions.InvalidTaskException, self.assertRaises(exceptions.InvalidTaskException,
self.task.start, self.fake_api, "task_path", self.task.start, self.fake_api, "task_path",
"deployment", tag="tag") "deployment", tags=["tag"])
self.assertFalse(self.fake_api.task.create.called) self.assertFalse(self.fake_api.task.create.called)
self.assertFalse(self.fake_api.task.start.called) self.assertFalse(self.fake_api.task.start.called)
@ -304,10 +307,10 @@ class TaskCommandsTestCase(test.TestCase):
self.assertRaises(KeyError, self.assertRaises(KeyError,
self.task.start, self.fake_api, "task_path", self.task.start, self.fake_api, "task_path",
"deployment", tag="tag") "deployment", tags=["tag"])
self.fake_api.task.create.assert_called_once_with( self.fake_api.task.create.assert_called_once_with(
deployment="deployment", tag="tag") deployment="deployment", tags=["tag"])
self.fake_api.task.start.assert_called_once_with( self.fake_api.task.start.assert_called_once_with(
deployment="deployment", config=task_cfg, deployment="deployment", config=task_cfg,
@ -835,7 +838,7 @@ class TaskCommandsTestCase(test.TestCase):
created_at=dt.datetime.now(), created_at=dt.datetime.now(),
updated_at=dt.datetime.now(), updated_at=dt.datetime.now(),
status="c", status="c",
tag="d", tags=["d"],
deployment_name="some_name")] deployment_name="some_name")]
self.task.list(self.fake_api, status="running") self.task.list(self.fake_api, status="running")
self.fake_api.task.list.assert_called_once_with( self.fake_api.task.list.assert_called_once_with(
@ -843,11 +846,12 @@ class TaskCommandsTestCase(test.TestCase):
status=consts.TaskStatus.RUNNING) status=consts.TaskStatus.RUNNING)
headers = ["uuid", "deployment_name", "created_at", "duration", headers = ["uuid", "deployment_name", "created_at", "duration",
"status", "tag"] "status", "tags"]
mock_print_list.assert_called_once_with( mock_print_list.assert_called_once_with(
self.fake_api.task.list.return_value, headers, self.fake_api.task.list.return_value, headers,
sortby_index=headers.index("created_at")) sortby_index=headers.index("created_at"),
formatters=mock.ANY)
@mock.patch("rally.cli.commands.task.cliutils.print_list") @mock.patch("rally.cli.commands.task.cliutils.print_list")
@mock.patch("rally.cli.commands.task.envutils.get_global", @mock.patch("rally.cli.commands.task.envutils.get_global",
@ -884,6 +888,54 @@ class TaskCommandsTestCase(test.TestCase):
self.fake_api.task.list.assert_called_once_with( self.fake_api.task.list.assert_called_once_with(
deployment="d", status=consts.TaskStatus.RUNNING) deployment="d", status=consts.TaskStatus.RUNNING)
@mock.patch("rally.cli.commands.task.envutils.get_global",
return_value="123456789")
def test_list_output(self, mock_get_global):
self.fake_api.task.list.return_value = [
fakes.FakeTask(uuid="UUID-1",
created_at="2007-01-01T00:00:01",
duration="0:00:00.000009",
status="init",
tags=[],
deployment_name="some_name"),
fakes.FakeTask(uuid="UUID-2",
created_at="2007-02-01T00:00:01",
duration="0:00:00.000010",
status="finished",
tags=["tag-1", "tag-2"],
deployment_name="some_name")]
# It is a hard task to mock default value of function argument, so we
# need to apply this workaround
original_print_list = cliutils.print_list
print_list_calls = []
def print_list(*args, **kwargs):
print_list_calls.append(six.StringIO())
kwargs["out"] = print_list_calls[-1]
original_print_list(*args, **kwargs)
with mock.patch.object(task.cliutils, "print_list",
new=print_list):
self.task.list(self.fake_api, status="running")
self.assertEqual(1, len(print_list_calls))
self.assertEqual(
"+--------+-----------------+---------------------"
"+----------------+----------+------------------+\n"
"| uuid | deployment_name | created_at "
"| duration | status | tags |\n"
"+--------+-----------------+---------------------"
"+----------------+----------+------------------+\n"
"| UUID-1 | some_name | 2007-01-01T00:00:01 "
"| 0:00:00.000009 | init | |\n"
"| UUID-2 | some_name | 2007-02-01T00:00:01 "
"| 0:00:00.000010 | finished | 'tag-1', 'tag-2' |\n"
"+--------+-----------------+---------------------"
"+----------------+----------+------------------+\n",
print_list_calls[0].getvalue())
def test_delete(self): def test_delete(self):
task_uuid = "8dcb9c5e-d60b-4022-8975-b5987c7833f7" task_uuid = "8dcb9c5e-d60b-4022-8975-b5987c7833f7"
force = False force = False
@ -1117,13 +1169,14 @@ class TaskCommandsTestCase(test.TestCase):
self.task.import_results(self.fake_api, self.task.import_results(self.fake_api,
"deployment_uuid", "deployment_uuid",
"task_file", "tag") "task_file", tags=["tag"])
self.task._load_task_results_file.assert_called_once_with( self.task._load_task_results_file.assert_called_once_with(
self.fake_api, "task_file" self.fake_api, "task_file"
) )
self.fake_api.task.import_results.assert_called_once_with( self.fake_api.task.import_results.assert_called_once_with(
deployment="deployment_uuid", task_results=["results"], tag="tag") deployment="deployment_uuid", task_results=["results"],
tags=["tag"])
# not exist # not exist
mock_os_path.exists.return_value = False mock_os_path.exists.return_value = False
@ -1131,5 +1184,5 @@ class TaskCommandsTestCase(test.TestCase):
1, 1,
self.task.import_results(self.fake_api, self.task.import_results(self.fake_api,
"deployment_uuid", "deployment_uuid",
"task_file", "tag") "task_file", ["tag"])
) )

View File

@ -122,12 +122,12 @@ class TasksTestCase(test.DBTestCase):
self.assertEqual(db_task["status"], consts.TaskStatus.INIT) self.assertEqual(db_task["status"], consts.TaskStatus.INIT)
def test_task_create_with_tag(self): def test_task_create_with_tag(self):
task = self._create_task(values={"tag": "test_tag"}) task = self._create_task(values={"tags": ["test_tag"]})
db_task = self._get_task(task["uuid"]) db_task = self._get_task(task["uuid"])
self.assertIsNotNone(db_task["uuid"]) self.assertIsNotNone(db_task["uuid"])
self.assertIsNotNone(db_task["id"]) self.assertIsNotNone(db_task["id"])
self.assertEqual(db_task["status"], consts.TaskStatus.INIT) self.assertEqual(db_task["status"], consts.TaskStatus.INIT)
self.assertEqual(db_task["tag"], "test_tag") self.assertEqual(db_task["tags"], ["test_tag"])
def test_task_create_without_uuid(self): def test_task_create_without_uuid(self):
_uuid = "19be8589-48b0-4af1-a369-9bebaaa563ab" _uuid = "19be8589-48b0-4af1-a369-9bebaaa563ab"
@ -145,11 +145,11 @@ class TasksTestCase(test.DBTestCase):
task = self._create_task({}) task = self._create_task({})
db.task_update(task["uuid"], { db.task_update(task["uuid"], {
"status": consts.TaskStatus.CRASHED, "status": consts.TaskStatus.CRASHED,
"tag": "test_tag" "tags": ["test_tag"]
}) })
db_task = self._get_task(task["uuid"]) db_task = self._get_task(task["uuid"])
self.assertEqual(db_task["status"], consts.TaskStatus.CRASHED) self.assertEqual(db_task["status"], consts.TaskStatus.CRASHED)
self.assertEqual(db_task["tag"], "test_tag") self.assertEqual(db_task["tags"], ["test_tag"])
def test_task_update_not_found(self): def test_task_update_not_found(self):
self.assertRaises(exceptions.TaskNotFound, self.assertRaises(exceptions.TaskNotFound,
@ -375,7 +375,7 @@ class TasksTestCase(test.DBTestCase):
"trace": "foo t/b", "trace": "foo t/b",
} }
task1 = self._create_task({"validation_result": validation_result, task1 = self._create_task({"validation_result": validation_result,
"tag": "bar"}) "tags": ["bar"]})
key = { key = {
"name": "atata", "name": "atata",
"description": "tatata", "description": "tatata",
@ -408,7 +408,7 @@ class TasksTestCase(test.DBTestCase):
task1_full = db.task_get_detailed(task1["uuid"]) task1_full = db.task_get_detailed(task1["uuid"])
self.assertEqual(validation_result, self.assertEqual(validation_result,
json.loads(task1_full["verification_log"])) json.loads(task1_full["verification_log"]))
self.assertEqual("bar", task1_full["tag"]) self.assertEqual(["bar"], task1_full["tags"])
results = task1_full["results"] results = task1_full["results"]
self.assertEqual(1, len(results)) self.assertEqual(1, len(results))
self.assertEqual(key, results[0]["key"]) self.assertEqual(key, results[0]["key"])

View File

@ -224,17 +224,18 @@ class TaskAPITestCase(test.TestCase):
self.assertEqual("2", self.task_inst.render_template( self.assertEqual("2", self.task_inst.render_template(
task_template=template)) task_template=template))
@mock.patch("rally.common.objects.Deployment.get", @mock.patch("rally.common.objects.Deployment.get")
return_value={
"uuid": "b0d9cd6c-2c94-4417-a238-35c7019d0257",
"status": consts.DeployStatus.DEPLOY_FINISHED})
@mock.patch("rally.common.objects.Task") @mock.patch("rally.common.objects.Task")
def test_create(self, mock_task, mock_deployment_get): def test_create(self, mock_task, mock_deployment_get):
tag = "a" mock_deployment_get.return_value = {
"uuid": "b0d9cd6c-2c94-4417-a238-35c7019d0257",
"status": consts.DeployStatus.DEPLOY_FINISHED}
tags = ["a"]
self.task_inst.create( self.task_inst.create(
deployment=mock_deployment_get.return_value["uuid"], tag=tag) deployment=mock_deployment_get.return_value["uuid"], tags=tags)
mock_task.assert_called_once_with( mock_task.assert_called_once_with(
deployment_uuid=mock_deployment_get.return_value["uuid"], tag=tag) deployment_uuid=mock_deployment_get.return_value["uuid"],
tags=tags)
@mock.patch("rally.common.objects.Deployment.get", @mock.patch("rally.common.objects.Deployment.get",
return_value={ return_value={
@ -243,10 +244,9 @@ class TaskAPITestCase(test.TestCase):
"status": consts.DeployStatus.DEPLOY_INIT}) "status": consts.DeployStatus.DEPLOY_INIT})
def test_create_on_unfinished_deployment(self, mock_deployment_get): def test_create_on_unfinished_deployment(self, mock_deployment_get):
deployment_id = mock_deployment_get.return_value["uuid"] deployment_id = mock_deployment_get.return_value["uuid"]
tag = "a"
self.assertRaises(exceptions.DeploymentNotFinishedStatus, self.assertRaises(exceptions.DeploymentNotFinishedStatus,
self.task_inst.create, deployment=deployment_id, self.task_inst.create, deployment=deployment_id,
tag=tag) tags=["a"])
@mock.patch("rally.api.objects.Task") @mock.patch("rally.api.objects.Task")
@mock.patch("rally.api.objects.Deployment.get") @mock.patch("rally.api.objects.Deployment.get")
@ -489,7 +489,7 @@ class TaskAPITestCase(test.TestCase):
) )
mock_task.assert_called_once_with(deployment_uuid="deployment_uuid", mock_task.assert_called_once_with(deployment_uuid="deployment_uuid",
tag=None) tags=None)
mock_task.return_value.update_status.assert_has_calls( mock_task.return_value.update_status.assert_has_calls(
[mock.call(consts.TaskStatus.RUNNING), [mock.call(consts.TaskStatus.RUNNING),
mock.call(consts.SubtaskStatus.FINISHED)] mock.call(consts.SubtaskStatus.FINISHED)]
@ -536,7 +536,7 @@ class TaskAPITestCase(test.TestCase):
) )
mock_task.assert_called_once_with(deployment_uuid="deployment_uuid", mock_task.assert_called_once_with(deployment_uuid="deployment_uuid",
tag=None) tags=None)
mock_task.return_value.update_status.assert_has_calls( mock_task.return_value.update_status.assert_has_calls(
[mock.call(consts.TaskStatus.RUNNING), [mock.call(consts.TaskStatus.RUNNING),
mock.call(consts.SubtaskStatus.FINISHED)] mock.call(consts.SubtaskStatus.FINISHED)]
@ -575,7 +575,7 @@ class TaskAPITestCase(test.TestCase):
self.task_inst.import_results, self.task_inst.import_results,
deployment="deployment_uuid", deployment="deployment_uuid",
task_results=[], task_results=[],
tag="tag") tags=["tag"])
class BaseDeploymentTestCase(test.TestCase): class BaseDeploymentTestCase(test.TestCase):