[core] Refactor validation

* do not save validation errors from CLI layer to database. It includes
  such redundant data as IOError (when task file doesn't exist)
* include validation at task start by default. It is bad sign to allow
  starting tasks without validation.

Change-Id: I0d30ff31fb6fbc467ef3cda01e7de1f46c5f79fe
This commit is contained in:
Andrey Kurilin 2017-04-05 23:08:05 +03:00
parent 7cfe24b249
commit 8b28bdee7b
6 changed files with 448 additions and 295 deletions

View File

@ -241,6 +241,7 @@ class _Task(object):
task["results"] = objects.Task.extend_results(task["results"])
return task
# TODO(andreykurilin): move it to some kind of utils
@classmethod
def render_template(cls, task_template, template_dir="./", **kwargs):
"""Render jinja2 task template to Rally input task.
@ -346,40 +347,78 @@ class _Task(object):
tag=tag)
@classmethod
def validate(cls, deployment, config, task_instance=None):
def validate(cls, deployment, config, task_instance=None, task=None):
"""Validate a task config against specified deployment.
:param deployment: UUID or name of the deployment
:param deployment: UUID or name of the deployment (will be ignored in
case of transmitting task_instance or task arguments)
:param config: a dict with a task configuration
:param task_instance: DEPRECATED. Use "task" argument to transmit task
uuid instead
"""
if task_instance is not None:
LOG.warning("Transmitting task object in `task validate` is "
"deprecated since Rally 0.10. To use pre-created "
"task, transmit task UUID instead via `task` "
"argument.")
task = objects.Task.get(task_instance["uuid"])
deployment = task["deployment_uuid"]
elif task:
task = objects.Task.get(task)
deployment = task["deployment_uuid"]
else:
task = objects.Task(deployment_uuid=deployment, temporary=True)
deployment = objects.Deployment.get(deployment)
task = task_instance or objects.Task(
deployment_uuid=deployment["uuid"], temporary=True)
benchmark_engine = engine.TaskEngine(config, task, deployment)
benchmark_engine = engine.TaskEngine(config, task, deployment)
benchmark_engine.validate()
@classmethod
def start(cls, deployment, config, task=None, abort_on_sla_failure=False):
"""Start a task.
"""Validate and start a task.
Task is a list of benchmarks that will be called one by one, results of
execution will be stored in DB.
:param deployment: UUID or name of the deployment
:param deployment: UUID or name of the deployment (will be ignored in
case of transmitting existing task)
:param config: a dict with a task configuration
:param task: Task object. If None, it will be created
:param task: Task UUID to use pre-created task. If None, new task will
be created
:param abort_on_sla_failure: If set to True, the task execution will
stop when any SLA check for it fails
"""
if task and isinstance(task, objects.Task):
LOG.warning("Transmitting task object in `task start` is "
"deprecated since Rally 0.10. To use pre-created "
"task, transmit task UUID instead.")
if task.is_temporary:
raise ValueError(_(
"Unable to run a temporary task. Please check your code."))
task = objects.Task.get(task["uuid"])
elif task is not None:
task = objects.Task.get(task)
if task is not None:
deployment = task["deployment_uuid"]
deployment = objects.Deployment.get(deployment)
if deployment["status"] != consts.DeployStatus.DEPLOY_FINISHED:
raise exceptions.DeploymentNotFinishedStatus(
name=deployment["name"],
uuid=deployment["uuid"],
status=deployment["status"])
task = task or objects.Task(deployment_uuid=deployment["uuid"])
if task is None:
task = objects.Task(deployment_uuid=deployment["uuid"])
if task.is_temporary:
raise ValueError(_(
"Unable to run a temporary task. Please check your code."))
benchmark_engine = engine.TaskEngine(
config, task, deployment,
abort_on_sla_failure=abort_on_sla_failure)
benchmark_engine.validate()
LOG.info("Task %s config is valid." % task["uuid"])
LOG.info("Benchmark Task %s on Deployment %s" % (task["uuid"],
deployment["uuid"]))
@ -393,6 +432,8 @@ class _Task(object):
deployment.update_status(consts.DeployStatus.DEPLOY_INCONSISTENT)
raise
return task["uuid"], task.get_status(task["uuid"])
@classmethod
def abort(cls, task_uuid, soft=False, async=True):
"""Abort running task.

View File

@ -19,7 +19,6 @@ from __future__ import print_function
import json
import os
import sys
import traceback
import webbrowser
import jsonschema
@ -47,7 +46,7 @@ LOG = logging.getLogger(__name__)
class FailedToLoadTask(exceptions.RallyException):
msg_fmt = _("Failed to load task")
msg_fmt = _("Invalid %(source)s passed:\n\n\t %(msg)s")
class TaskCommands(object):
@ -55,101 +54,86 @@ class TaskCommands(object):
"""
def _load_task(self, api, task_file, task_args=None, task_args_file=None):
"""Load tasks template from file and render it with passed args.
def _load_and_validate_task(self, api, task_file, args_file=None,
raw_args=None):
"""Load, render and validate tasks template from file with passed args.
:param task_file: Path to file with input task
:param task_args: JSON or YAML representation of dict with args that
will be used to render input task with jinja2
:param task_args_file: Path to file with JSON or YAML representation
of dict, that will be used to render input
with jinja2. If both specified task_args and
task_args_file they will be merged. task_args
has bigger priority so it will update values
from task_args_file.
:param raw_args: JSON or YAML representation of dict with args that
will be used to render input task with jinja2
:param args_file: Path to file with JSON or YAML representation
of dict, that will be used to render input with jinja2. If both
specified task_args and task_args_file they will be merged.
raw_args has bigger priority so it will update values
from args_file.
:returns: Str with loaded and rendered task
"""
print(cliutils.make_header("Preparing input task"))
def print_invalid_header(source_name, args):
print(_("Invalid %(source)s passed: \n\n %(args)s \n")
% {"source": source_name, "args": args},
file=sys.stderr)
def parse_task_args(src_name, args):
try:
kw = args and yaml.safe_load(args)
kw = {} if kw is None else kw
except yaml.ParserError as e:
print_invalid_header(src_name, args)
print(_("%(source)s has to be YAML or JSON. Details:"
"\n\n%(err)s\n")
% {"source": src_name, "err": e},
file=sys.stderr)
raise TypeError()
if not isinstance(kw, dict):
print_invalid_header(src_name, args)
print(_("%(src)s has to be dict, actually %(src_type)s\n")
% {"src": src_name, "src_type": type(kw)},
file=sys.stderr)
raise TypeError()
return kw
try:
kw = {}
if task_args_file:
with open(task_args_file) as f:
kw.update(parse_task_args("task_args_file", f.read()))
kw.update(parse_task_args("task_args", task_args))
except TypeError:
raise FailedToLoadTask()
if not os.path.isfile(task_file):
raise FailedToLoadTask(source="--task",
msg="File '%s' doesn't exist." % task_file)
with open(task_file) as f:
input_task = f.read()
task_dir = os.path.expanduser(os.path.dirname(task_file)) or "./"
task_args = {}
if args_file:
if not os.path.isfile(args_file):
raise FailedToLoadTask(
source="--task-args-file",
msg="File '%s' doesn't exist." % args_file)
with open(args_file) as f:
try:
task_args.update(yaml.safe_load(f.read()))
except yaml.ParserError as e:
raise FailedToLoadTask(
source="--task-args-file",
msg="File '%s' has to be YAML or JSON. Details:\n\n%s"
% (args_file, e))
if raw_args:
try:
input_task = f.read()
task_dir = os.path.expanduser(
os.path.dirname(task_file)) or "./"
rendered_task = api.task.render_template(input_task,
task_dir, **kw)
except Exception as e:
print(_("Failed to render task template:\n%(task)s\n%(err)s\n")
% {"task": input_task, "err": e},
file=sys.stderr)
raise FailedToLoadTask()
data = yaml.safe_load(raw_args)
if isinstance(data, (six.text_type, six.string_types)):
raise yaml.ParserError("String '%s' doesn't look like a "
"dictionary." % raw_args)
task_args.update(data)
except yaml.ParserError as e:
args = [keypair.split("=", 1)
for keypair in raw_args.split(",")]
if len([a for a in args if len(a) != 1]) != len(args):
raise FailedToLoadTask(
source="--task-args",
msg="Value has to be YAML or JSON. Details:\n\n%s" % e)
else:
task_args.update(dict(args))
print(_("Task is:\n%s\n") % rendered_task)
try:
parsed_task = yaml.safe_load(rendered_task)
except Exception as e:
print(_("Wrong format of rendered input task. It should be "
"YAML or JSON.\n%s") % e,
file=sys.stderr)
raise FailedToLoadTask()
print(_("Task syntax is correct :)"))
return parsed_task
def _load_and_validate_task(self, api, task, task_args, task_args_file,
deployment, task_instance=None):
try:
input_task = self._load_task(api, task, task_args, task_args_file)
except Exception as err:
if task_instance:
task_instance.set_validation_failed({
"etype": err.__class__.__name__,
"msg": str(err),
"trace": json.dumps(traceback.format_exc())})
raise
api.task.validate(deployment, input_task, task_instance)
print(_("Task config is valid :)"))
return input_task
rendered_task = api.task.render_template(input_task, task_dir,
**task_args)
except Exception as e:
raise FailedToLoadTask(
source="--task",
msg="Failed to render task template.\n\n%s" % e)
print(_("Task is:\n%s\n") % rendered_task.strip())
try:
parsed_task = yaml.safe_load(rendered_task)
except Exception as e:
raise FailedToLoadTask(
source="--task",
msg="Wrong format of rendered input task. It should be YAML or"
" JSON. Details:\n\n%s" % e)
print(_("Task syntax is correct :)"))
return parsed_task
@cliutils.args("--deployment", dest="deployment", type=str,
metavar="<uuid>", required=False,
help="UUID or name of a deployment.")
@cliutils.args("--task", "--filename", metavar="<path>",
dest="task_file",
help="Path to the input task file.")
@cliutils.args("--task-args", metavar="<json>", dest="task_args",
help="Input task args (JSON dict). These args are used "
@ -160,7 +144,7 @@ class TaskCommands(object):
"to render the Jinja2 template in the input task.")
@envutils.with_default_deployment(cli_arg_name="deployment")
@plugins.ensure_plugins_are_loaded
def validate(self, api, task, deployment=None, task_args=None,
def validate(self, api, task_file, deployment=None, task_args=None,
task_args_file=None):
"""Validate a task configuration file.
@ -171,7 +155,7 @@ class TaskCommands(object):
be merged. task_args has a higher priority so it will override
values from task_args_file.
:param task: Path to the input task file.
:param task_file: Path to the input task file.
:param task_args: Input task args (JSON dict). These args are
used to render the Jinja2 template in the
input task.
@ -181,19 +165,20 @@ class TaskCommands(object):
the input task.
:param deployment: UUID or name of the deployment
"""
try:
self._load_and_validate_task(api, task, task_args, task_args_file,
deployment)
except (exceptions.InvalidTaskException, FailedToLoadTask) as e:
print(e, file=sys.stderr)
return(1)
task = self._load_and_validate_task(api, task_file, raw_args=task_args,
args_file=task_args_file)
api.task.validate(deployment, config=task)
print(_("Task config is valid :)"))
@cliutils.args("--deployment", dest="deployment", type=str,
metavar="<uuid>", required=False,
help="UUID or name of a deployment.")
@cliutils.args("--task", "--filename", metavar="<path>",
help="Path to the input task file")
dest="task_file",
help="Path to the input task file.")
@cliutils.args("--task-args", dest="task_args", metavar="<json>",
help="Input task args (JSON dict). These args are used "
"to render the Jinja2 template in the input task.")
@ -210,7 +195,7 @@ class TaskCommands(object):
"any SLA check for it fails.")
@envutils.with_default_deployment(cli_arg_name="deployment")
@plugins.ensure_plugins_are_loaded
def start(self, api, task, 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,
abort_on_sla_failure=False):
"""Start benchmark task.
@ -219,7 +204,7 @@ class TaskCommands(object):
be merged. task_args has a higher priority so it will override
values from task_args_file.
:param task: Path to the input task file.
:param task_file: Path to the input task file.
:param task_args: Input task args (JSON dict). These args are
used to render the Jinja2 template in the
input task.
@ -236,18 +221,18 @@ class TaskCommands(object):
for it fails
"""
input_task = self._load_and_validate_task(api, task_file,
raw_args=task_args,
args_file=task_args_file)
print("Running Rally version", version.version_string())
try:
task_instance = api.task.create(deployment, tag)
print("Running Rally version", version.version_string())
input_task = self._load_and_validate_task(
api, task, task_args, task_args_file, deployment,
task_instance=task_instance)
print(cliutils.make_header(
_("Task %(tag)s %(uuid)s: started")
% {"uuid": task_instance["uuid"],
"tag": task_instance["tag"]}))
_("Task %(tag)s %(uuid)s: started")
% {"uuid": task_instance["uuid"],
"tag": task_instance["tag"]}))
print("Benchmarking... This can take a while...\n")
print("To track task status use:\n")
print("\trally task status\n\tor\n\trally task detailed\n")
@ -255,20 +240,14 @@ class TaskCommands(object):
if do_use:
self.use(api, task_instance["uuid"])
api.task.start(deployment, input_task, task=task_instance,
api.task.start(deployment, input_task, task=task_instance["uuid"],
abort_on_sla_failure=abort_on_sla_failure)
self.detailed(api, task_id=task_instance["uuid"])
except exceptions.DeploymentNotFinishedStatus as e:
print(_("Cannot start a task on unfinished deployment: %s") % e)
return 1
except (exceptions.InvalidTaskException, FailedToLoadTask) as e:
task_instance.set_validation_failed({
"etype": type(e).__name__,
"msg": str(e),
"trace": json.dumps(traceback.format_exc())})
print(e, file=sys.stderr)
return(1)
self.detailed(api, task_id=task_instance["uuid"])
@cliutils.args("--uuid", type=str, dest="task_id", help="UUID of task.")
@envutils.with_default_task_id

View File

@ -389,6 +389,8 @@ class Task(object):
def _update(self, values):
if not self.is_temporary:
self.task = db.task_update(self.task["uuid"], values)
else:
self.task.update(values)
def update_status(self, status, allowed_statuses=None):
if allowed_statuses:

View File

@ -20,10 +20,11 @@ import os.path
import ddt
import mock
import yaml
import rally
from rally import api
from rally.cli.commands import task
from rally.common import yamlutils as yaml
from rally import consts
from rally import exceptions
from tests.unit import fakes
@ -41,86 +42,136 @@ class TaskCommandsTestCase(test.TestCase):
with mock.patch("rally.api.API.check_db_revision"):
self.real_api = api.API()
@mock.patch("rally.cli.commands.task.os.path.isfile")
@mock.patch("rally.cli.commands.task.open", create=True)
def test__load_task(self, mock_open):
def test__load_and_validate_task(self, mock_open, mock_isfile):
input_task = "{'ab': {{test}}}"
input_args = "{'test': 2}"
# NOTE(boris-42): Such order of files is because we are reading
# file with args before file with template.
mock_open.side_effect = [
mock.mock_open(read_data="{'test': 1}").return_value,
mock.mock_open(read_data=input_task).return_value
mock.mock_open(read_data=input_task).return_value,
mock.mock_open(read_data="{'test': 1}").return_value
]
task_conf = self.task._load_task(
self.real_api, "in_task", task_args_file="in_args_path")
task_conf = self.task._load_and_validate_task(
self.real_api, "in_task", args_file="in_args_path")
self.assertEqual({"ab": 1}, task_conf)
mock_open.side_effect = [
mock.mock_open(read_data=input_task).return_value
]
task_conf = self.task._load_task(
self.real_api, "in_task", task_args=input_args)
task_conf = self.task._load_and_validate_task(
self.real_api, "in_task", raw_args=input_args)
self.assertEqual(task_conf, {"ab": 2})
mock_open.side_effect = [
mock.mock_open(read_data="{'test': 1}").return_value,
mock.mock_open(read_data=input_task).return_value
mock.mock_open(read_data=input_task).return_value,
mock.mock_open(read_data="{'test': 1}").return_value
]
task_conf = self.task._load_task(
self.real_api, "in_task", task_args=input_args,
task_args_file="any_file")
task_conf = self.task._load_and_validate_task(
self.real_api, "in_task", raw_args=input_args,
args_file="any_file")
self.assertEqual(task_conf, {"ab": 2})
@mock.patch("rally.cli.commands.task.open", create=True)
def test__load_task_wrong_task_args_file(self, mock_open):
mock_open.side_effect = [
mock.mock_open(read_data="{'test': {}").return_value
mock.mock_open(read_data=input_task).return_value,
mock.mock_open(read_data="{'test': 1}").return_value
]
self.assertRaises(task.FailedToLoadTask,
self.task._load_task,
self.fake_api, "in_task",
task_args_file="in_args_path")
task_conf = self.task._load_and_validate_task(
self.real_api, "in_task", raw_args="test=2",
args_file="any_file")
self.assertEqual(task_conf, {"ab": 2})
@mock.patch("rally.cli.commands.task.os.path.isfile")
def test__load_task_wrong_task_args_file(self, mock_isfile):
mock_isfile.side_effect = (True, False)
# use real file to avoid mocking open
task_file = __file__
e = self.assertRaises(task.FailedToLoadTask,
self.task._load_and_validate_task,
self.fake_api, task_file=task_file,
args_file="in_args_path")
self.assertEqual("Invalid --task-args-file passed:\n\n\t File "
"'in_args_path' doesn't exist.", e.format_message())
@mock.patch("rally.cli.commands.task.yaml.safe_load")
@mock.patch("rally.cli.commands.task.os.path.isfile")
def test__load_task_wrong_input_task_args(self, mock_isfile,
mock_safe_load):
mock_safe_load.side_effect = yaml.ParserError("foo")
mock_isfile.side_effect = (True, False)
# use real file to avoid mocking open
task_file = __file__
e = self.assertRaises(task.FailedToLoadTask,
self.task._load_and_validate_task, self.real_api,
task_file, raw_args="{'test': {}")
self.assertEqual("Invalid --task-args passed:\n\n\t Value has to be "
"YAML or JSON. Details:\n\nfoo", e.format_message())
mock_safe_load.assert_called_once_with("{'test': {}")
# the case #2
mock_safe_load.reset_mock()
mock_isfile.side_effect = (True, False)
e = self.assertRaises(task.FailedToLoadTask,
self.task._load_and_validate_task, self.real_api,
task_file, raw_args="[]")
self.assertEqual("Invalid --task-args passed:\n\n\t Value has to be "
"YAML or JSON. Details:\n\nfoo", e.format_message())
mock_safe_load.assert_called_once_with("[]")
# the case #3
mock_safe_load.reset_mock()
mock_isfile.side_effect = (True, False)
e = self.assertRaises(task.FailedToLoadTask,
self.task._load_and_validate_task, self.real_api,
task_file, raw_args="foo")
self.assertEqual("Invalid --task-args passed:\n\n\t Value has to be "
"YAML or JSON. Details:\n\nfoo", e.format_message())
mock_safe_load.assert_called_once_with("foo")
@mock.patch("rally.cli.commands.task.os.path.isfile")
@mock.patch("rally.cli.commands.task.open", create=True)
def test__load_task_wrong_task_args_file_exception(self, mock_open):
mock_open.side_effect = IOError
self.assertRaises(IOError, self.task._load_task, self.fake_api,
"in_task", task_args_file="in_args_path")
def test__load_task_task_render_raise_exc(self, mock_open, mock_isfile):
mock_isfile.side_effect = (True, False)
def test__load_task_wrong_input_task_args(self):
self.assertRaises(task.FailedToLoadTask,
self.task._load_task, self.real_api, "in_task",
"{'test': {}")
self.assertRaises(task.FailedToLoadTask,
self.task._load_task, self.real_api, "in_task", "[]")
@mock.patch("rally.cli.commands.task.open", create=True)
def test__load_task_task_render_raise_exc(self, mock_open):
mock_open.side_effect = [
mock.mock_open(read_data="{'test': {{t}}}").return_value
]
self.assertRaises(task.FailedToLoadTask,
self.task._load_task, self.real_api, "in_task")
e = self.assertRaises(task.FailedToLoadTask,
self.task._load_and_validate_task, self.real_api,
"in_task")
self.assertEqual("Invalid --task passed:\n\n\t Failed to render task "
"template.\n\nPlease specify template task argument: "
"t", e.format_message())
@mock.patch("rally.cli.commands.task.yaml")
@mock.patch("rally.cli.commands.task.os.path.isfile")
@mock.patch("rally.cli.commands.task.open", create=True)
def test__load_task_task_not_in_yaml(self, mock_open):
def test__load_task_task_not_in_yaml(self, mock_open, mock_isfile,
mock_yaml):
mock_open.side_effect = [
mock.mock_open(read_data="{'test': {}").return_value
]
self.fake_api.task.render_template.return_value = "||"
mock_isfile.side_effect = (True, )
mock_yaml.safe_load.side_effect = Exception("ERROR!!!PANIC!!!")
self.assertRaises(task.FailedToLoadTask,
self.task._load_task, self.fake_api, "in_task")
e = self.assertRaises(task.FailedToLoadTask,
self.task._load_and_validate_task, self.fake_api,
"in_task")
self.assertEqual("Invalid --task passed:\n\n\t Wrong format of "
"rendered input task. It should be YAML or JSON. "
"Details:\n\nERROR!!!PANIC!!!", e.format_message())
def test_load_task_including_other_template(self):
@mock.patch("rally.cli.commands.task.os.path.isfile", return_value=True)
def test_load_task_including_other_template(self, mock_isfile):
other_template_path = os.path.join(
os.path.dirname(__file__),
"..", "..", "..", "..", "samples/tasks/scenarios/nova/boot.json")
os.path.dirname(rally.__file__), os.pardir,
"samples/tasks/scenarios/nova/boot.json")
input_task = "{%% include \"%s\" %%}" % os.path.basename(
other_template_path)
expect = self.task._load_task(self.real_api, other_template_path)
expect = self.task._load_and_validate_task(self.real_api,
other_template_path)
with mock.patch("rally.cli.commands.task.open",
create=True) as mock_open:
@ -129,42 +180,29 @@ class TaskCommandsTestCase(test.TestCase):
]
input_task_file = os.path.join(
os.path.dirname(other_template_path), "input_task.json")
actual = self.task._load_task(self.real_api, input_task_file)
actual = self.task._load_and_validate_task(self.real_api,
input_task_file)
self.assertEqual(expect, actual)
@mock.patch("rally.cli.commands.task.os.path.isfile", return_value=True)
@mock.patch("rally.cli.commands.task.TaskCommands._load_task",
return_value={"uuid": "some_uuid"})
def test__load_and_validate_task(self, mock__load_task,
mock_os_path_isfile):
deployment = "some_deployment_uuid"
self.fake_api.task.validate.return_value = fakes.FakeTask()
self.task._load_and_validate_task(self.fake_api, "some_task",
"task_args", "task_args_file",
deployment)
mock__load_task.assert_called_once_with(
self.fake_api, "some_task", "task_args", "task_args_file")
self.fake_api.task.validate.assert_called_once_with(
deployment, mock__load_task.return_value, None)
def test__load_and_validate_file(self):
deployment = "some_deployment_uuid"
self.assertRaises(IOError, self.task._load_and_validate_task,
self.fake_api, "some_task", "task_args",
"task_args_file", deployment)
def test__load_and_validate_file_failed(self):
e = self.assertRaises(task.FailedToLoadTask,
self.task._load_and_validate_task,
api=self.fake_api, task_file="some_task",
raw_args="task_args", args_file="task_args_file")
self.assertEqual("Invalid --task passed:\n\n\t File 'some_task' "
"doesn't exist.", e.format_message())
@mock.patch("rally.cli.commands.task.version")
@mock.patch("rally.cli.commands.task.os.path.isfile", return_value=True)
@mock.patch("rally.cli.commands.task.TaskCommands.use")
@mock.patch("rally.cli.commands.task.TaskCommands.detailed")
@mock.patch("rally.cli.commands.task.TaskCommands._load_task",
@mock.patch("rally.cli.commands.task.TaskCommands._load_and_validate_task",
return_value={"some": "json"})
def test_start(self, mock__load_task, mock_detailed, mock_use,
mock_os_path_isfile, mock_version):
def test_start(self, mock__load_and_validate_task, mock_detailed, mock_use,
mock_version):
deployment_id = "e0617de9-77d1-4875-9b49-9d5789e29f20"
task_path = "path_to_config.json"
self.fake_api.task.create.return_value = fakes.FakeTask(
uuid="some_new_uuid", tag="tag")
fake_task = fakes.FakeTask(uuid="some_new_uuid", tag="tag")
self.fake_api.task.create.return_value = fake_task
self.fake_api.task.validate.return_value = fakes.FakeTask(
some="json", uuid="some_uuid", temporary=True)
@ -173,26 +211,25 @@ class TaskCommandsTestCase(test.TestCase):
self.fake_api.task.create.assert_called_once_with(
deployment_id, None)
self.fake_api.task.start.assert_called_once_with(
deployment_id, mock__load_task.return_value,
task=self.fake_api.task.validate.return_value,
deployment_id, mock__load_and_validate_task.return_value,
task=fake_task["uuid"],
abort_on_sla_failure=False)
mock__load_task.assert_called_once_with(
self.fake_api, task_path, None, None)
mock__load_and_validate_task.assert_called_once_with(
self.fake_api, task_path, args_file=None, raw_args=None)
mock_use.assert_called_once_with(self.fake_api, "some_new_uuid")
mock_detailed.assert_called_once_with(self.fake_api,
task_id="some_new_uuid")
task_id=fake_task["uuid"])
@mock.patch("rally.cli.commands.task.os.path.isfile", return_value=True)
@mock.patch("rally.cli.commands.task.TaskCommands.detailed")
@mock.patch("rally.cli.commands.task.TaskCommands._load_task",
@mock.patch("rally.cli.commands.task.TaskCommands._load_and_validate_task",
return_value="some_config")
def test_start_on_unfinished_deployment(
self, mock__load_task, mock_detailed, mock_os_path_isfile):
def test_start_on_unfinished_deployment(self, mock__load_and_validate_task,
mock_detailed):
deployment_id = "e0617de9-77d1-4875-9b49-9d5789e29f20"
deployment_name = "xxx_name"
task_path = "path_to_config.json"
self.fake_api.task.create.return_value = fakes.FakeTask(
uuid="some_new_uuid", tag="tag")
fake_task = fakes.FakeTask(uuid="some_new_uuid", tag="tag")
self.fake_api.task.create.return_value = fake_task
exc = exceptions.DeploymentNotFinishedStatus(
name=deployment_name,
@ -201,13 +238,14 @@ class TaskCommandsTestCase(test.TestCase):
self.fake_api.task.create.side_effect = exc
self.assertEqual(1, self.task.start(self.fake_api, task_path,
deployment="any", tag="some_tag"))
self.assertFalse(mock_detailed.called)
@mock.patch("rally.cli.commands.task.os.path.isfile", return_value=True)
@mock.patch("rally.cli.commands.task.TaskCommands.detailed")
@mock.patch("rally.cli.commands.task.TaskCommands._load_task",
@mock.patch("rally.cli.commands.task.TaskCommands._load_and_validate_task",
return_value="some_config")
def test_start_with_task_args(self, mock__load_task, mock_detailed,
mock_os_path_isfile):
def test_start_with_task_args(self, mock__load_and_validate_task,
mock_detailed):
fake_task = fakes.FakeTask(uuid="new_uuid", tag="some_tag")
self.fake_api.task.create.return_value = fakes.FakeTask(
uuid="new_uuid", tag="some_tag")
self.fake_api.task.validate.return_value = fakes.FakeTask(
@ -219,17 +257,18 @@ class TaskCommandsTestCase(test.TestCase):
self.task.start(self.fake_api, task_path, deployment="any",
task_args=task_args, task_args_file=task_args_file,
tag="some_tag")
mock__load_task.assert_called_once_with(
self.fake_api, task_path, task_args, task_args_file)
self.fake_api.task.validate.assert_called_once_with(
"any", mock__load_task.return_value, {})
mock__load_and_validate_task.assert_called_once_with(
self.fake_api, task_path, raw_args=task_args,
args_file=task_args_file)
self.fake_api.task.start.assert_called_once_with(
"any", mock__load_task.return_value,
task=self.fake_api.task.create.return_value,
"any", mock__load_and_validate_task.return_value,
task=fake_task["uuid"],
abort_on_sla_failure=False)
mock_detailed.assert_called_once_with(
self.fake_api,
task_id=self.fake_api.task.create.return_value["uuid"])
task_id=fake_task["uuid"])
self.fake_api.task.create.assert_called_once_with("any", "some_tag")
@mock.patch("rally.cli.commands.task.envutils.get_global")
@ -238,25 +277,41 @@ class TaskCommandsTestCase(test.TestCase):
self.assertRaises(exceptions.InvalidArgumentsException,
self.task.start, "path_to_config.json", None)
@mock.patch("rally.cli.commands.task.os.path.isfile", return_value=True)
@mock.patch("rally.cli.commands.task.TaskCommands._load_task",
return_value={"some": "json"})
def test_start_invalid_task(self, mock__load_task, mock_os_path_isfile):
self.fake_api.task.create.return_value = fakes.FakeTask(
temporary=False, tag="tag", uuid="uuid")
exc = exceptions.InvalidTaskException
self.fake_api.task.start.side_effect = exc
result = self.task.start(self.fake_api, "task_path", "deployment",
tag="tag")
self.assertEqual(1, result)
@mock.patch("rally.cli.commands.task.TaskCommands.detailed")
@mock.patch("rally.cli.commands.task.TaskCommands._load_and_validate_task")
def test_start_invalid_task(self, mock__load_and_validate_task,
mock_detailed):
task_obj = fakes.FakeTask(temporary=False, tag="tag", uuid="uuid")
self.fake_api.task.create.return_value = task_obj
exc = exceptions.InvalidTaskException("foo")
mock__load_and_validate_task.side_effect = exc
self.assertRaises(exceptions.InvalidTaskException,
self.task.start, self.fake_api, "task_path",
"deployment", tag="tag")
self.assertFalse(self.fake_api.task.create.called)
self.assertFalse(self.fake_api.task.start.called)
# the case 2
task_cfg = {"some": "json"}
mock__load_and_validate_task.side_effect = (task_cfg, )
self.fake_api.task.start.side_effect = KeyError()
self.assertRaises(KeyError,
self.task.start, self.fake_api, "task_path",
"deployment", tag="tag")
self.fake_api.task.create.assert_called_once_with("deployment", "tag")
self.fake_api.task.start.assert_called_once_with(
"deployment", mock__load_task.return_value,
task=self.fake_api.task.create.return_value,
"deployment", task_cfg,
task=task_obj["uuid"],
abort_on_sla_failure=False)
self.assertFalse(mock_detailed.called)
def test_abort(self):
test_uuid = "17860c43-2274-498d-8669-448eff7b073f"
self.task.abort(self.fake_api, test_uuid)
@ -894,35 +949,37 @@ class TaskCommandsTestCase(test.TestCase):
create=True)
def test_validate(self, mock_open, mock_os_path_isfile):
self.fake_api.task.render_template = self.real_api.task.render_template
self.task.validate(self.fake_api, "path_to_config.json", "fake_id")
self.fake_api.task.validate.assert_called_once_with(
"fake_id", {"some": "json"}, None)
@mock.patch("rally.cli.commands.task.os.path.isfile", return_value=True)
@mock.patch("rally.cli.commands.task.TaskCommands._load_task",
self.task.validate(self.fake_api, "path_to_config.json", "fake_id")
self.fake_api.task.validate.assert_called_once_with(
"fake_id", {"some": "json"})
@mock.patch("rally.cli.commands.task.TaskCommands._load_and_validate_task",
side_effect=task.FailedToLoadTask)
def test_validate_failed_to_load_task(self, mock__load_task,
mock_os_path_isfile):
def test_validate_failed_to_load_task(self, mock__load_and_validate_task):
args = "args"
args_file = "args_file"
result = self.task.validate(self.real_api,
"path_to_task", "fake_deployment_id",
task_args=args, task_args_file=args_file)
self.assertEqual(1, result)
mock__load_task.assert_called_once_with(
self.real_api, "path_to_task", args, args_file)
mock__load_and_validate_task.side_effect = KeyError("foo")
@mock.patch("rally.cli.commands.task.os.path.isfile", return_value=True)
@mock.patch("rally.cli.commands.task.TaskCommands._load_task")
def test_validate_invalid(self, mock__load_task, mock_os_path_isfile):
exc = exceptions.InvalidTaskException
self.assertRaises(KeyError, self.task.validate, self.real_api,
"path_to_task", "fake_deployment_id",
task_args=args, task_args_file=args_file)
self.assertFalse(self.fake_api.task.validate.called)
mock__load_and_validate_task.assert_called_once_with(
self.real_api, "path_to_task", raw_args=args, args_file=args_file)
@mock.patch("rally.cli.commands.task.TaskCommands._load_and_validate_task")
def test_validate_invalid(self, mock__load_and_validate_task):
exc = exceptions.InvalidTaskException("foo")
self.fake_api.task.validate.side_effect = exc
result = self.task.validate(self.fake_api,
"path_to_task", "deployment")
self.assertEqual(1, result)
self.assertRaises(exceptions.InvalidTaskException,
self.task.validate, self.fake_api, "path_to_task",
"deployment")
self.fake_api.task.validate.assert_called_once_with(
"deployment", mock__load_task.return_value, None)
"deployment", mock__load_and_validate_task.return_value)
@mock.patch("rally.common.fileutils._rewrite_env_file")
def test_use(self, mock__rewrite_env_file):

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import itertools
import multiprocessing
import random
import re
@ -1840,7 +1841,6 @@ class FakeUserContext(FakeContext):
class FakeDeployment(dict):
update_status = mock.Mock()
def __init__(self, **kwargs):
namespace = kwargs.pop("namespace", "openstack")
@ -1849,6 +1849,7 @@ class FakeDeployment(dict):
"users": kwargs.pop("users", [])}],
"default": [{"admin": None, "users": []}]}
dict.__init__(self, **kwargs)
self.update_status = mock.Mock()
def get_platforms(self):
return [platform for platform in self["credentials"]]
@ -1857,18 +1858,16 @@ class FakeDeployment(dict):
return self["credentials"][namespace][0]
class FakeTask(dict):
class FakeTask(dict, object):
def __init__(self, task=None, temporary=False, **kwargs):
self.is_temporary = temporary
self.task = task or kwargs
self.set_failed = mock.Mock()
self.set_validation_failed = mock.Mock()
def __getitem__(self, key):
if key in self:
return self[key]
return self.task[key]
task = task or {}
for k, v in itertools.chain(task.items(), kwargs.items()):
self[k] = v
self.task = self
def to_dict(self):
return self

View File

@ -53,31 +53,77 @@ class TaskAPITestCase(test.TestCase):
def setUp(self):
super(TaskAPITestCase, self).setUp()
self.task_uuid = "b0d9cd6c-2c94-4417-a238-35c7019d0257"
self.task = {
"uuid": self.task_uuid,
}
self.task = {"uuid": self.task_uuid}
@mock.patch("rally.api.objects.Task")
@mock.patch("rally.api.objects.Deployment.get",
return_value=fakes.FakeDeployment(uuid="deployment_uuid",
admin="fake_admin",
users=["fake_user"]))
@mock.patch("rally.api.objects.Deployment.get")
@mock.patch("rally.api.engine.TaskEngine")
def test_validate(
self, mock_task_engine, mock_deployment_get, mock_task):
api._Task.validate(mock_deployment_get.return_value["uuid"], "config")
def test_validate(self, mock_task_engine, mock_deployment_get, mock_task):
fake_deployment = fakes.FakeDeployment(
uuid="deployment_uuid_1", admin="fake_admin", users=["fake_user"])
mock_deployment_get.return_value = fake_deployment
mock_task_engine.assert_has_calls([
mock.call("config", mock_task.return_value,
mock_deployment_get.return_value),
mock.call().validate()
])
#######################################################################
# The case #1 -- create temporary task
#######################################################################
api._Task.validate(fake_deployment["uuid"], "config")
mock_task_engine.assert_called_once_with(
"config", mock_task.return_value, fake_deployment),
mock_task_engine.return_value.validate.assert_called_once_with()
mock_task.assert_called_once_with(
temporary=True,
deployment_uuid=mock_deployment_get.return_value["uuid"])
temporary=True, deployment_uuid=fake_deployment["uuid"])
mock_deployment_get.assert_called_once_with(fake_deployment["uuid"])
self.assertFalse(mock_task.get.called)
#######################################################################
# The case #2 -- validate pre-created task
#######################################################################
mock_task_engine.reset_mock()
mock_task.reset_mock()
mock_deployment_get.reset_mock()
fake_task = fakes.FakeTask(deployment_uuid="deployment_uuid_2")
mock_task.get.return_value = fake_task
task_uuid = "task-id"
api._Task.validate(fake_deployment["uuid"], "config", task=task_uuid)
mock_task_engine.assert_called_once_with("config", fake_task,
fake_deployment)
mock_task_engine.return_value.validate.assert_called_once_with()
self.assertFalse(mock_task.called)
# check that deployment uuid is taken from task
mock_deployment_get.assert_called_once_with(
mock_deployment_get.return_value["uuid"])
fake_task["deployment_uuid"])
mock_task.get.assert_called_once_with(task_uuid)
#######################################################################
# The case #3 -- validate deprecated way for pre-created task
#######################################################################
mock_task_engine.reset_mock()
mock_task.reset_mock()
mock_deployment_get.reset_mock()
task_instance = fakes.FakeTask(uuid="task-id")
api._Task.validate(fake_deployment["uuid"], "config",
task_instance=task_instance)
mock_task_engine.assert_called_once_with("config", fake_task,
fake_deployment)
mock_task_engine.return_value.validate.assert_called_once_with()
self.assertFalse(mock_task.called)
# check that deployment uuid is taken from task
mock_deployment_get.assert_called_once_with(
fake_task["deployment_uuid"])
mock_task.get.assert_called_once_with(task_instance["uuid"])
@mock.patch("rally.api.objects.Task")
@mock.patch("rally.api.objects.Deployment",
@ -170,16 +216,22 @@ class TaskAPITestCase(test.TestCase):
self.assertRaises(exceptions.DeploymentNotFinishedStatus,
api._Task.create, deployment_id, tag)
@mock.patch("rally.api.objects.Task",
return_value=fakes.FakeTask(uuid="some_uuid"))
@mock.patch("rally.api.objects.Deployment.get",
return_value=fakes.FakeDeployment(uuid="deployment_uuid",
admin="fake_admin",
users=["fake_user"]))
@mock.patch("rally.api.objects.Task")
@mock.patch("rally.api.objects.Deployment.get")
@mock.patch("rally.api.engine.TaskEngine")
def test_start(self, mock_task_engine, mock_deployment_get,
mock_task):
api._Task.start(mock_deployment_get.return_value["uuid"], "config")
fake_task = fakes.FakeTask(uuid="some_uuid")
fake_task.get_status = mock.Mock()
mock_task.return_value = fake_task
mock_deployment_get.return_value = fakes.FakeDeployment(
uuid="deployment_uuid", admin="fake_admin", users=["fake_user"],
status=consts.DeployStatus.DEPLOY_FINISHED)
self.assertEqual(
(fake_task["uuid"], fake_task.get_status.return_value),
api._Task.start(mock_deployment_get.return_value["uuid"], "config")
)
mock_task_engine.assert_has_calls([
mock.call("config", mock_task.return_value,
@ -194,29 +246,52 @@ class TaskAPITestCase(test.TestCase):
mock_deployment_get.assert_called_once_with(
mock_deployment_get.return_value["uuid"])
@mock.patch("rally.api.objects.Task",
return_value=fakes.FakeTask(uuid="some_uuid", task={},
temporary=True))
@mock.patch("rally.api.objects.Deployment.get",
return_value=fakes.FakeDeployment(uuid="deployment_uuid",
admin="fake_admin",
users=["fake_user"]))
def test_start_temporary_task(self, mock_deployment_get,
mock_task):
@mock.patch("rally.api.objects.Deployment.get")
def test_start_temporary_task(self, mock_deployment_get):
fake_deployment = fakes.FakeDeployment(
uuid="deployment_uuid", admin="fake_admin", users=["fake_user"],
status=consts.DeployStatus.DEPLOY_FINISHED,
name="foo")
mock_deployment_get.return_value = fake_deployment
fake_task = objects.Task(task={"deployment_uuid": "deployment_uuid",
"uuid": "some_uuid"}, temporary=True)
self.assertRaises(ValueError, api._Task.start,
mock_deployment_get.return_value["uuid"], "config")
fake_deployment, "config", task=fake_task)
@mock.patch("rally.api.objects.task.db.task_get")
@mock.patch("rally.api.objects.Deployment.get")
def test_start_with_inconsistent_deployment(self, mock_deployment_get,
mock_task_get):
deployment_uuid = "deployment_uuid"
fake_deployment = fakes.FakeDeployment(
uuid=deployment_uuid, admin="fake_admin", users=["fake_user"],
status=consts.DeployStatus.DEPLOY_INCONSISTENT,
name="foo")
mock_deployment_get.return_value = fake_deployment
fake_task_dict = {"deployment_uuid": deployment_uuid,
"uuid": "some_uuid"}
fake_task = objects.Task(task=fake_task_dict)
mock_task_get.return_value = fake_task_dict
self.assertRaises(exceptions.DeploymentNotFinishedStatus,
api._Task.start, deployment_uuid, "config",
task=fake_task)
@mock.patch("rally.api.objects.Task")
@mock.patch("rally.api.objects.Deployment.get")
@mock.patch("rally.api.engine.TaskEngine")
def test_start_exception(self, mock_task_engine, mock_deployment_get,
mock_task):
fake_deployment = fakes.FakeDeployment(
status=consts.DeployStatus.DEPLOY_FINISHED,
name="foo", uuid="deployment_uuid")
mock_deployment_get.return_value = fake_deployment
mock_task.return_value.is_temporary = False
mock_task_engine.return_value.run.side_effect = TypeError
self.assertRaises(TypeError, api._Task.start, "deployment_uuid",
"config")
mock_deployment_get().update_status.assert_called_once_with(
fake_deployment.update_status.assert_called_once_with(
consts.DeployStatus.DEPLOY_INCONSISTENT)
@ddt.data(True, False)