Merge "Port hooks to the new format"

This commit is contained in:
Jenkins 2017-10-06 03:33:22 +00:00 committed by Gerrit Code Review
commit 4a7cc010b1
18 changed files with 502 additions and 290 deletions

View File

@ -253,28 +253,25 @@
times: 20
concurrency: 2
hooks:
- name: sys_call
description: Run script
args: sh rally-jobs/extra/hook_example_script.sh
- description: Run script
action:
sys_call: sh rally-jobs/extra/hook_example_script.sh
trigger:
name: event
args:
event:
unit: iteration
at: [2, 5, 8, 13, 17]
- name: sys_call
description: Show time
args: date +%Y-%m-%dT%H:%M:%S
- description: Show time
action:
sys_call: date +%Y-%m-%dT%H:%M:%S
trigger:
name: event
args:
event:
unit: time
at: [0, 2, 5, 6, 9]
- name: sys_call
description: Show system name
args: uname -a
- description: Show system name
action:
sys_call: uname -a
trigger:
name: event
args:
event:
unit: iteration
at: [2, 3, 4, 5, 6, 8, 10, 12, 13, 15, 17, 18]
sla:
@ -290,12 +287,11 @@
times: 10
concurrency: 2
hooks:
- name: sys_call
description: test hook
args: /bin/true
- description: test hook
action:
sys_call: /bin/true
trigger:
name: periodic
args:
periodic:
unit: iteration
step: 2
start: 4
@ -312,12 +308,11 @@
serial:
times: 10
hooks:
- name: sys_call
description: Get system name
args: uname -a
- description: Show system name
action:
sys_call: uname -a
trigger:
name: event
args:
event:
unit: time
at: [0, 2, 4, 6, 8, 10]
sla:
@ -332,12 +327,11 @@
serial:
times: 10
hooks:
- name: sys_call
description: test hook
args: /bin/true
- description: test hook
action:
sys_call: /bin/true
trigger:
name: periodic
args:
periodic:
unit: time
step: 2
start: 0

View File

@ -27,7 +27,7 @@ LOG = logging.getLogger(__name__)
@hook.configure(name="sys_call")
class SysCallHook(hook.Hook):
class SysCallHook(hook.HookAction):
"""Performs system call."""
CONFIG_SCHEMA = {

View File

@ -14,11 +14,11 @@
# under the License.
from rally import consts
from rally.task import trigger
from rally.task import hook
@trigger.configure(name="event")
class EventTrigger(trigger.Trigger):
@hook.configure(name="event")
class EventTrigger(hook.HookTrigger):
"""Triggers hook on specified event and list of values."""
CONFIG_SCHEMA = {

View File

@ -14,11 +14,11 @@
# under the License.
from rally import consts
from rally.task import trigger
from rally.task import hook
@trigger.configure(name="periodic")
class PeriodicTrigger(trigger.Trigger):
@hook.configure(name="periodic")
class PeriodicTrigger(hook.HookTrigger):
"""Periodically triggers hook with specified range and step."""
CONFIG_SCHEMA = {

View File

@ -33,7 +33,6 @@ from rally.task import hook
from rally.task import runner
from rally.task import scenario
from rally.task import sla
from rally.task import trigger
LOG = logging.getLogger(__name__)
@ -312,19 +311,22 @@ class TaskEngine(object):
vtype=vtype))
for hook_conf in workload["hooks"]:
results.extend(hook.Hook.validate(
name=hook_conf["config"]["name"],
action_name, action_cfg = list(
hook_conf["config"]["action"].items())[0]
results.extend(hook.HookAction.validate(
name=action_name,
context=vcontext,
config=None,
plugin_cfg=hook_conf["config"]["args"],
plugin_cfg=action_cfg,
vtype=vtype))
trigger_conf = hook_conf["config"]["trigger"]
results.extend(trigger.Trigger.validate(
name=trigger_conf["name"],
trigger_name, trigger_cfg = list(
hook_conf["config"]["trigger"].items())[0]
results.extend(hook.HookTrigger.validate(
name=trigger_name,
context=vcontext,
config=None,
plugin_cfg=trigger_conf["args"],
plugin_cfg=trigger_cfg,
vtype=vtype))
if results:
@ -495,26 +497,6 @@ class TaskConfig(object):
"""
HOOK_CONFIG = {
"type": "object",
"properties": {
"name": {"type": "string"},
"description": {"type": "string"},
"args": {},
"trigger": {
"type": "object",
"properties": {
"name": {"type": "string"},
"args": {},
},
"required": ["name", "args"],
"additionalProperties": False,
}
},
"required": ["name", "args", "trigger"],
"additionalProperties": False,
}
CONFIG_SCHEMA_V1 = {
"type": "object",
"$schema": consts.JSON_SCHEMA,
@ -537,12 +519,33 @@ class TaskConfig(object):
"sla": {"type": "object"},
"hooks": {
"type": "array",
"items": HOOK_CONFIG,
"items": {"$ref": "#/definitions/hook"},
}
},
"additionalProperties": False
}
}
},
"definitions": {
"hook": {
"type": "object",
"properties": {
"name": {"type": "string"},
"description": {"type": "string"},
"args": {},
"trigger": {
"type": "object",
"properties": {
"name": {"type": "string"},
"args": {},
},
"required": ["name", "args"],
"additionalProperties": False,
}
},
"required": ["name", "args", "trigger"],
"additionalProperties": False,
}
}
}
@ -595,7 +598,7 @@ class TaskConfig(object):
"sla": {"type": "object"},
"hooks": {
"type": "array",
"items": HOOK_CONFIG,
"items": {"$ref": "#/definitions/hook"},
},
"contexts": {"type": "object"}
},
@ -627,7 +630,7 @@ class TaskConfig(object):
"sla": {"type": "object"},
"hooks": {
"type": "array",
"items": HOOK_CONFIG,
"items": {"$ref": "#/definitions/hook"},
},
"contexts": {"type": "object"}
},
@ -638,6 +641,43 @@ class TaskConfig(object):
},
"additionalProperties": False,
"required": ["title", "workloads"]
},
"hook": {
"type": "object",
"oneOf": [
{
"properties": {
"name": {"type": "string"},
"description": {"type": "string"},
"args": {},
"trigger": {
"type": "object",
"properties": {
"name": {"type": "string"},
"args": {},
},
"required": ["name", "args"],
"additionalProperties": False,
}
},
"required": ["name", "args", "trigger"],
"additionalProperties": False
},
{
"properties": {
"action": {
"type": "object",
"minProperties": 1,
"maxProperties": 1,
"patternProperties": {".*": {}}
},
"trigger": {"$ref": "#/definitions/singleEntity"},
"description": {"type": "string"},
},
"required": ["action", "trigger"],
"additionalProperties": False
},
]
}
}
}
@ -718,8 +758,23 @@ class TaskConfig(object):
else:
wconf["runner"] = {"serial": {}}
wconf.setdefault("sla", {"failure_rate": {"max": 0}})
wconf.setdefault("hooks", [])
wconf["hooks"] = [{"config": h} for h in wconf["hooks"]]
hooks = wconf.get("hooks", [])
wconf["hooks"] = []
for hook_cfg in hooks:
if "name" in hook_cfg:
LOG.warning("The deprecated format of hook is found. "
"Check task format documentation for more "
"details.")
trigger_cfg = hook_cfg["trigger"]
wconf["hooks"].append({"config": {
"description": hook_cfg["description"],
"action": {hook_cfg["name"]: hook_cfg["args"]},
"trigger": {
trigger_cfg["name"]: trigger_cfg["args"]}}
})
else:
wconf["hooks"].append({"config": hook_cfg})
workloads.append(wconf)
sconf["workloads"] = workloads
@ -757,6 +812,18 @@ class TaskConfig(object):
if "runner" in subtask:
runner_type = subtask["runner"].pop("type")
subtask["runner"] = {runner_type: subtask["runner"]}
if "hooks" in subtask:
hooks = subtask["hooks"]
subtask["hooks"] = []
for hook_cfg in hooks:
trigger_cfg = hook_cfg["trigger"]
subtask["hooks"].append(
{"description": hook_cfg["description"],
"action": {
hook_cfg["name"]: hook_cfg["args"]},
"trigger": {
trigger_cfg["name"]: trigger_cfg["args"]}}
)
subtasks.append(subtask)
return {"title": "Task (adopted from task format v1)",
"subtasks": subtasks}

View File

@ -27,7 +27,6 @@ from rally.common import validation
from rally import consts
from rally import exceptions
from rally.task.processing import charts
from rally.task import trigger
from rally.task import utils
@ -47,9 +46,11 @@ class HookExecutor(object):
self.triggers = collections.defaultdict(list)
for hook in config.get("hooks", []):
hook_cfg = hook["config"]
hook_cls = Hook.get(hook_cfg["name"])
trigger_obj = trigger.Trigger.get(
hook_cfg["trigger"]["name"])(hook_cfg, self.task, hook_cls)
action_name = list(hook_cfg["action"].keys())[0]
trigger_name = list(hook_cfg["trigger"].keys())[0]
action_cls = HookAction.get(action_name)
trigger_obj = HookTrigger.get(
trigger_name)(hook_cfg, self.task, action_cls)
event_type = trigger_obj.get_listening_event()
self.triggers[event_type].append(trigger_obj)
@ -112,7 +113,7 @@ class HookExecutor(object):
@validation.add_default("jsonschema")
@plugin.base()
@six.add_metaclass(abc.ABCMeta)
class Hook(plugin.Plugin, validation.ValidatablePluginMixin):
class HookAction(plugin.Plugin, validation.ValidatablePluginMixin):
"""Factory for hook classes."""
CONFIG_SCHEMA = {"type": "null"}
@ -205,3 +206,56 @@ class Hook(plugin.Plugin, validation.ValidatablePluginMixin):
# hook is still running, wait for result
self._thread.join()
return self._result
@validation.add_default("jsonschema")
@plugin.base()
@six.add_metaclass(abc.ABCMeta)
class HookTrigger(plugin.Plugin, validation.ValidatablePluginMixin):
"""Factory for hook trigger classes."""
CONFIG_SCHEMA = {"type": "null"}
def __init__(self, hook_cfg, task, hook_cls):
self.hook_cfg = hook_cfg
self.config = self.hook_cfg["trigger"][self.get_name()]
self.task = task
self.hook_cls = hook_cls
self._runs = []
@abc.abstractmethod
def get_listening_event(self):
"""Returns event type to listen."""
def on_event(self, event_type, value=None):
"""Launch hook on specified event."""
LOG.info(_("Hook action %s is triggered for Task %s by %s=%s")
% (self.hook_cls.get_name(), self.task["uuid"],
event_type, value))
action_cfg = list(self.hook_cfg["action"].values())[0]
action = self.hook_cls(self.task, action_cfg,
{"event_type": event_type, "value": value})
action.run_async()
self._runs.append(action)
def get_results(self):
results = {"config": self.hook_cfg,
"results": [],
"summary": {}}
for action in self._runs:
action_result = action.result()
results["results"].append(action_result)
results["summary"].setdefault(action_result["status"], 0)
results["summary"][action_result["status"]] += 1
return results
class Hook(HookAction):
"""DEPRECATED! USE `rally.task.hook.HookAction` instead."""
def __init__(self, *args, **kwargs):
super(Hook, self).__init__(*args, **kwargs)
LOG.warning("Please contact Rally plugin maintainer. The plugin '%s' "
"inherits the deprecated base class(Hook), "
"`rally.task.hook.HookAction` should be used instead."
% self.get_name())

View File

@ -33,7 +33,7 @@ def _process_hooks(hooks):
"""Prepare hooks data for report."""
hooks_ctx = []
for hook in hooks:
hook_ctx = {"name": hook["config"]["name"],
hook_ctx = {"name": list(hook["config"]["action"].keys())[0],
"desc": hook["config"].get("description", ""),
"additive": [], "complete": []}

View File

@ -13,56 +13,28 @@
# License for the specific language governing permissions and limitations
# under the License.
import abc
import six
from rally.common.i18n import _
from rally.common import logging
from rally.common.plugin import plugin
from rally.common import validation
configure = plugin.configure
from rally.task import hook
LOG = logging.getLogger(__name__)
@validation.add_default("jsonschema")
@plugin.base()
@six.add_metaclass(abc.ABCMeta)
class Trigger(plugin.Plugin, validation.ValidatablePluginMixin):
"""Factory for trigger classes."""
class Trigger(hook.HookTrigger):
"""DEPRECATED!!! USE `rally.task.hook.HookTrigger` instead."""
CONFIG_SCHEMA = {"type": "null"}
def __init__(self, *args, **kwargs):
super(Trigger, self).__init__(*args, **kwargs)
LOG.warning("Please contact Rally plugin maintainer. The plugin '%s' "
"inherits the deprecated base class(Trigger), "
"`rally.task.hook.HookTrigger` should be used instead."
% self.get_name())
def __init__(self, context, task, hook_cls):
self.context = context
self.config = self.context["trigger"]["args"]
self.task = task
self.hook_cls = hook_cls
self._runs = []
@abc.abstractmethod
def get_listening_event(self):
"""Returns event type to listen."""
def on_event(self, event_type, value=None):
"""Launch hook on specified event."""
LOG.info(_("Hook %s is triggered for Task %s by %s=%s")
% (self.hook_cls.__name__, self.task["uuid"],
event_type, value))
hook = self.hook_cls(self.task, self.context.get("args", {}),
{"event_type": event_type, "value": value})
hook.run_async()
self._runs.append(hook)
def get_results(self):
results = {"config": self.context,
"results": [],
"summary": {}}
for hook in self._runs:
hook_result = hook.result()
results["results"].append(hook_result)
results["summary"].setdefault(hook_result["status"], 0)
results["summary"][hook_result["status"]] += 1
return results
@property
def context(self):
action_name, action_cfg = list(self.hook_cfg["action"].items())[0]
trigger_name, trigger_cfg = list(self.hook_cfg["trigger"].items())[0]
return {"description": self.hook_cfg["description"],
"name": action_name,
"args": action_cfg,
"trigger": {"name": trigger_name,
"args": trigger_cfg}}

View File

@ -31,7 +31,7 @@ class SysCallHookTestCase(test.TestCase):
@ddt.data(("ls", True), (50, False))
@ddt.unpack
def test_validate(self, config, valid):
results = hook.Hook.validate(
results = hook.HookAction.validate(
"sys_call", None, None, config)
if valid:
self.assertEqual([], results)

View File

@ -16,8 +16,8 @@
import ddt
import mock
from rally.plugins.common.trigger import event
from rally.task import trigger
from rally.plugins.common.hook.triggers import event
from rally.task import hook
from tests.unit import test
@ -28,8 +28,8 @@ class EventTriggerTestCase(test.TestCase):
super(EventTriggerTestCase, self).setUp()
self.hook_cls = mock.MagicMock(__name__="name")
self.trigger = event.EventTrigger(
{"trigger": {"name": "event",
"args": {"unit": "iteration", "at": [1, 4, 5]}}},
{"trigger": {"event": {"unit": "iteration", "at": [1, 4, 5]}},
"action": {"foo": {}}},
mock.MagicMock(), self.hook_cls)
@ddt.data((dict(unit="time", at=[0, 3, 5]), True),
@ -51,7 +51,7 @@ class EventTriggerTestCase(test.TestCase):
(dict(at=[1, 2, 3]), False))
@ddt.unpack
def test_validate(self, config, valid):
results = trigger.Trigger.validate("event", None, None, config)
results = hook.HookTrigger.validate("event", None, None, config)
if valid:
self.assertEqual([], results)
else:

View File

@ -16,8 +16,8 @@
import ddt
import mock
from rally.plugins.common.trigger import periodic
from rally.task import trigger
from rally.plugins.common.hook.triggers import periodic
from rally.task import hook
from tests.unit import test
@ -28,8 +28,8 @@ class PeriodicTriggerTestCase(test.TestCase):
super(PeriodicTriggerTestCase, self).setUp()
self.hook_cls = mock.MagicMock(__name__="name")
self.trigger = periodic.PeriodicTrigger(
{"trigger": {"name": "periodic",
"args": {"unit": "iteration", "step": 2}}},
{"trigger": {"periodic": {"unit": "iteration", "step": 2}},
"action": {"foo": {}}},
mock.MagicMock(), self.hook_cls)
@ddt.data((dict(unit="time", step=1), True),
@ -52,7 +52,7 @@ class PeriodicTriggerTestCase(test.TestCase):
(dict(step=1), False))
@ddt.unpack
def test_validate(self, config, valid):
results = trigger.Trigger.validate("periodic", None, None, config)
results = hook.HookTrigger.validate("periodic", None, None, config)
if valid:
self.assertEqual([], results)
else:
@ -74,9 +74,9 @@ class PeriodicTriggerTestCase(test.TestCase):
@ddt.unpack
def test_on_event_start_end(self, value, should_call):
trigger = periodic.PeriodicTrigger(
{"trigger": {"name": "periodic",
"args": {"unit": "time",
"step": 3, "start": 2, "end": 9}}},
{"trigger": {"periodic": {"unit": "time",
"step": 3, "start": 2, "end": 9}},
"action": {"foo": {}}},
mock.MagicMock(), self.hook_cls)
trigger.on_event("time", value)
self.assertEqual(should_call, self.hook_cls.called)

View File

@ -40,7 +40,8 @@ class FaultInjectionHookTestCase(test.TestCase):
(dict(), False))
@ddt.unpack
def test_config_schema(self, config, valid):
results = hook.Hook.validate("fault_injection", None, None, config)
results = hook.HookAction.validate("fault_injection", None, None,
config)
if valid:
self.assertEqual([], results)
else:

View File

@ -87,48 +87,65 @@ class PlotTestCase(test.TestCase):
@ddt.data(
{"hooks": [], "expected": []},
{"hooks": [
{"config": {
"trigger": {"args": {"at": [2, 5], "unit": "iteration"},
"name": "event"},
"args": "foo cmd", "description": "Foo", "name": "sys_call"},
"results": [
{"status": "success", "finished_at": 1475589987.525735,
"triggered_by": {"event_type": "iteration", "value": 2},
"started_at": 1475589987.433399,
"output": {
"additive": [
{"chart_plugin": "StatsTable", "title": "Foo table",
"data": [["A", 158], ["B", 177]]}],
"complete": []}},
{"status": "success", "finished_at": 1475589993.457818,
"triggered_by": {"event_type": "iteration", "value": 5},
"started_at": 1475589993.432734,
"output": {
"additive": [
{"chart_plugin": "StatsTable", "title": "Foo table",
"data": [["A", 243], ["B", 179]]}],
"complete": []}}],
"summary": {"success": 2}},
{"config": {"trigger": {"args": {"at": [1, 2, 4], "unit": "time"},
"name": "event"},
"args": "bar cmd", "name": "sys_call"},
"results": [
{"status": "success", "finished_at": 1475589988.437791,
"triggered_by": {"event_type": "time", "value": 1},
"started_at": 1475589988.434244,
"output": {"additive": [],
"complete": [
{"chart_plugin": "Pie", "title": "Bar Pie",
"data": [["F", 4], ["G", 2]]}]}},
{"status": "success",
"finished_at": 1475589989.437589,
"triggered_by": {"event_type": "time", "value": 2},
"started_at": 1475589989.433964,
"output": {"additive": [],
"complete": [
{"chart_plugin": "Pie", "title": "Bar Pie",
"data": [["F", 42], ["G", 24]]}]}}],
"summary": {"success": 2}}],
{
"config": {
"description": "Foo",
"action": {"sys_call": "foo cmd"},
"trigger": {"event": {"at": [2, 5], "unit": "iteration"}}},
"results": [
{
"status": "success",
"started_at": 1475589987.433399,
"finished_at": 1475589987.525735,
"triggered_by": {"event_type": "iteration",
"value": 2},
"output": {
"additive": [
{"chart_plugin": "StatsTable",
"title": "Foo table",
"data": [["A", 158], ["B", 177]]}],
"complete": []}},
{
"status": "success",
"started_at": 1475589993.432734,
"finished_at": 1475589993.457818,
"triggered_by": {"event_type": "iteration",
"value": 5},
"output": {
"additive": [
{"chart_plugin": "StatsTable",
"title": "Foo table",
"data": [["A", 243], ["B", 179]]}],
"complete": []}}],
"summary": {"success": 2}},
{
"config": {
"action": {"sys_call": "bar cmd"},
"trigger": {"event": {"at": [1, 2, 4], "unit": "time"}}},
"results": [
{
"status": "success",
"started_at": 1475589988.434244,
"finished_at": 1475589988.437791,
"triggered_by": {"event_type": "time", "value": 1},
"output": {
"additive": [],
"complete": [
{"chart_plugin": "Pie",
"title": "Bar Pie",
"data": [["F", 4], ["G", 2]]}]}},
{
"status": "success",
"started_at": 1475589989.433964,
"finished_at": 1475589989.437589,
"triggered_by": {"event_type": "time", "value": 2},
"output": {
"additive": [],
"complete": [
{"chart_plugin": "Pie",
"title": "Bar Pie",
"data": [["F", 42], ["G", 24]]}]}}],
"summary": {"success": 2}}],
"expected": [
{"additive": [
{"data": {"cols": ["Action", "Min (sec)", "Median (sec)",

View File

@ -128,8 +128,8 @@ class TaskEngineTestCase(test.TestCase):
@mock.patch("rally.task.engine.scenario.Scenario.get")
@mock.patch("rally.task.sla.SLA.validate")
@mock.patch("rally.task.trigger.Trigger.validate")
@mock.patch("rally.task.hook.Hook.validate")
@mock.patch("rally.task.hook.HookTrigger.validate")
@mock.patch("rally.task.hook.HookAction.validate")
@mock.patch("rally.task.engine.TaskConfig")
@mock.patch("rally.task.engine.runner.ScenarioRunner.validate")
@mock.patch("rally.task.engine.context.Context.validate")
@ -137,15 +137,15 @@ class TaskEngineTestCase(test.TestCase):
self, mock_context_validate,
mock_scenario_runner_validate,
mock_task_config,
mock_hook_validate,
mock_trigger_validate,
mock_hook_action_validate,
mock_hook_trigger_validate,
mock_sla_validate,
mock_scenario_get):
mock_context_validate.return_value = []
mock_sla_validate.return_value = []
mock_hook_validate.return_value = []
mock_trigger_validate.return_value = []
mock_hook_action_validate.return_value = []
mock_hook_trigger_validate.return_value = []
default_context = {"foo": "foo_conf"}
scenario_cls = mock_scenario_get.return_value
scenario_cls.get_platform.return_value = "default"
@ -153,9 +153,8 @@ class TaskEngineTestCase(test.TestCase):
scenario_name = "Foo.bar"
runner_type = "MegaRunner"
hook_conf = {"name": "c",
"args": "c_args",
"trigger": {"name": "d", "args": "d_args"}}
hook_conf = {"action": {"c": "c_args"},
"trigger": {"d": "d_args"}}
workload = {"name": scenario_name,
"runner": {"type": runner_type},
"context": {"a": "a_conf"},
@ -186,10 +185,10 @@ class TaskEngineTestCase(test.TestCase):
mock_sla_validate.assert_called_once_with(
config=None, context=None,
name="foo_sla", plugin_cfg="sla_conf", vtype=None)
mock_hook_validate.assert_called_once_with(
mock_hook_action_validate.assert_called_once_with(
config=None, context=None, name="c", plugin_cfg="c_args",
vtype=None)
mock_trigger_validate.assert_called_once_with(
mock_hook_trigger_validate.assert_called_once_with(
config=None, context=None, name="d", plugin_cfg="d_args",
vtype=None)
@ -270,21 +269,21 @@ class TaskEngineTestCase(test.TestCase):
@mock.patch("rally.task.engine.json.dumps")
@mock.patch("rally.task.engine.scenario.Scenario.get")
@mock.patch("rally.task.hook.Hook.validate")
@mock.patch("rally.task.trigger.Trigger.validate")
@mock.patch("rally.task.hook.HookAction.validate")
@mock.patch("rally.task.hook.HookTrigger.validate")
@mock.patch("rally.task.engine.TaskConfig")
def test__validate_config_syntax__wrong_hook(
self, mock_task_config, mock_trigger_validate, mock_hook_validate,
self, mock_task_config, mock_hook_trigger_validate,
mock_hook_action_validate,
mock_scenario_get, mock_dumps):
mock_dumps.return_value = "<JSON>"
mock_trigger_validate.return_value = []
mock_hook_validate.return_value = ["hook_error"]
mock_hook_trigger_validate.return_value = []
mock_hook_action_validate.return_value = ["hook_error"]
scenario_cls = mock_scenario_get.return_value
scenario_cls.get_default_context.return_value = {}
mock_task_instance = mock.MagicMock()
hook_conf = {"name": "c",
"args": "c_args",
"trigger": {"name": "d", "args": "d_args"}}
hook_conf = {"action": {"c": "c_args"},
"trigger": {"d": "d_args"}}
mock_task_instance.subtasks = [{"workloads": [
self._make_workload(name="sca"),
self._make_workload(name="sca", position=1,
@ -303,21 +302,21 @@ class TaskEngineTestCase(test.TestCase):
@mock.patch("rally.task.engine.json.dumps")
@mock.patch("rally.task.engine.scenario.Scenario.get")
@mock.patch("rally.task.trigger.Trigger.validate")
@mock.patch("rally.task.hook.Hook.validate")
@mock.patch("rally.task.hook.HookTrigger.validate")
@mock.patch("rally.task.hook.HookAction.validate")
@mock.patch("rally.task.engine.TaskConfig")
def test__validate_config_syntax__wrong_trigger(
self, mock_task_config, mock_hook_validate, mock_trigger_validate,
self, mock_task_config, mock_hook_action_validate,
mock_hook_trigger_validate,
mock_scenario_get, mock_dumps):
mock_dumps.return_value = "<JSON>"
mock_trigger_validate.return_value = ["trigger_error"]
mock_hook_validate.return_value = []
mock_hook_trigger_validate.return_value = ["trigger_error"]
mock_hook_action_validate.return_value = []
scenario_cls = mock_scenario_get.return_value
scenario_cls.get_default_context.return_value = {}
mock_task_instance = mock.MagicMock()
hook_conf = {"name": "c",
"args": "c_args",
"trigger": {"name": "d", "args": "d_args"}}
hook_conf = {"action": {"c": "c_args"},
"trigger": {"d": "d_args"}}
mock_task_instance.subtasks = [{"workloads": [
self._make_workload(name="sca"),
self._make_workload(name="sca", position=1,
@ -954,7 +953,7 @@ class ResultConsumerTestCase(test.TestCase):
self.assertEqual(4, mock_task_get_status.call_count)
class TaskTestCase(test.TestCase):
class TaskConfigTestCase(test.TestCase):
@mock.patch("jsonschema.validate")
def test_validate_json(self, mock_validate):
config = {}
@ -996,19 +995,75 @@ class TaskTestCase(test.TestCase):
config = collections.OrderedDict()
config["a.task"] = [{"s": 1, "context": {"foo": "bar"}}, {"s": 2}]
config["b.task"] = [{"s": 3, "sla": {"key": "value"}}]
config["c.task"] = [{"s": 5,
"hooks": [{"name": "foo",
"args": "bar",
"description": "DESCR!!!",
"trigger": {
"name": "mega-trigger",
"args": {"some": "thing"}
}}]
}]
self.assertEqual(
{"title": "Task (adopted from task format v1)",
"subtasks": [{"title": "a.task",
"scenario": {"a.task": {}},
"s": 1,
"contexts": {"foo": "bar"}},
{"title": "a.task",
"s": 2,
"scenario": {"a.task": {}},
"contexts": {}},
{"title": "b.task",
"s": 3,
"scenario": {"b.task": {}},
"sla": {"key": "value"},
"contexts": {}}]},
"subtasks": [
{
"title": "a.task",
"scenario": {"a.task": {}},
"s": 1,
"contexts": {"foo": "bar"}
},
{
"title": "a.task",
"s": 2,
"scenario": {"a.task": {}},
"contexts": {}
},
{
"title": "b.task",
"s": 3,
"scenario": {"b.task": {}},
"sla": {"key": "value"},
"contexts": {}
},
{
"title": "c.task",
"s": 5,
"scenario": {"c.task": {}},
"contexts": {},
"hooks": [
{"description": "DESCR!!!",
"action": {"foo": "bar"},
"trigger": {"mega-trigger": {"some": "thing"}}}
]
}]},
TaskConfig._adopt_task_format_v1(config))
def test_hook_config_compatibility(self):
cfg = {
"title": "foo",
"version": 2,
"subtasks": [
{
"title": "foo",
"scenario": {"xxx": {}},
"runner": {"yyy": {}},
"hooks": [
{"description": "descr",
"name": "hook_action",
"args": {"k1": "v1"},
"trigger": {
"name": "hook_trigger",
"args": {"k2": "v2"}
}}
]
}
]
}
task = engine.TaskConfig(cfg)
workload = task.subtasks[0]["workloads"][0]
self.assertEqual(
{"description": "descr",
"action": {"hook_action": {"k1": "v1"}},
"trigger": {"hook_trigger": {"k2": "v2"}}},
workload["hooks"][0]["config"])

View File

@ -25,7 +25,7 @@ from tests.unit import test
@hook.configure(name="dummy_hook")
class DummyHook(hook.Hook):
class DummyHookAction(hook.HookAction):
CONFIG_SCHEMA = {
"type": "object",
"properties": {
@ -56,14 +56,12 @@ class HookExecutorTestCase(test.TestCase):
self.conf = {
"hooks": [
{"config": {
"name": "dummy_hook",
"description": "dummy_action",
"args": {
"status": consts.HookStatus.SUCCESS,
"action": {
"dummy_hook": {"status": consts.HookStatus.SUCCESS}
},
"trigger": {
"name": "event",
"args": {
"event": {
"unit": "iteration",
"at": [1],
}
@ -92,7 +90,7 @@ class HookExecutorTestCase(test.TestCase):
@mock.patch("rally.task.hook.HookExecutor._timer_method")
@mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer)
def test_result_optional(self, mock_timer, mock__timer_method):
hook_args = self.conf["hooks"][0]["config"]["args"]
hook_args = list(self.conf["hooks"][0]["config"]["action"].values())[0]
hook_args["error"] = ["Exception", "Description", "Traceback"]
hook_args["output"] = {"additive": None, "complete": None}
@ -120,9 +118,10 @@ class HookExecutorTestCase(test.TestCase):
hook_executor.results())
@mock.patch("rally.task.hook.HookExecutor._timer_method")
@mock.patch.object(DummyHook, "run", side_effect=Exception("My err msg"))
@mock.patch.object(DummyHookAction, "run",
side_effect=Exception("My err msg"))
@mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer)
def test_failed_result(self, mock_timer, mock_dummy_hook_run,
def test_failed_result(self, mock_timer, mock_dummy_hook_action_run,
mock__timer_method):
hook_executor = hook.HookExecutor(self.conf, self.task)
hook_executor.on_event(event_type="iteration", value=1)
@ -141,7 +140,8 @@ class HookExecutorTestCase(test.TestCase):
@mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer)
def test_time_event(self, mock_timer):
trigger_args = self.conf["hooks"][0]["config"]["trigger"]["args"]
trigger_args = list(
self.conf["hooks"][0]["config"]["trigger"].values())[0]
trigger_args["unit"] = "time"
hook_executor = hook.HookExecutor(self.conf, self.task)
@ -160,7 +160,7 @@ class HookExecutorTestCase(test.TestCase):
@mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer)
def test_time_periodic(self, mock_timer):
self.conf["hooks"][0]["config"]["trigger"] = {
"name": "periodic", "args": {"unit": "time", "step": 2}}
"periodic": {"unit": "time", "step": 2}}
hook_executor = hook.HookExecutor(self.conf, self.task)
for i in range(1, 7):
@ -196,7 +196,8 @@ class HookExecutorTestCase(test.TestCase):
@mock.patch("rally.common.utils.Stopwatch", autospec=True)
@mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer)
def test_timer_thread(self, mock_timer, mock_stopwatch):
trigger_args = self.conf["hooks"][0]["config"]["trigger"]["args"]
trigger_args = list(
self.conf["hooks"][0]["config"]["trigger"].values())[0]
trigger_args["unit"] = "time"
hook_executor = hook.HookExecutor(self.conf, self.task)
@ -235,7 +236,7 @@ class HookTestCase(test.TestCase):
@ddt.data(({"status": "foo"}, True), (3, False))
@ddt.unpack
def test_validate(self, config, valid):
results = hook.Hook.validate(
results = hook.HookAction.validate(
"dummy_hook", None, None, config)
if valid:
self.assertEqual([], results)
@ -246,8 +247,8 @@ class HookTestCase(test.TestCase):
def test_result(self, mock_timer):
task = mock.MagicMock()
triggered_by = {"event_type": "iteration", "value": 1}
dummy_hook = DummyHook(task, {"status": consts.HookStatus.SUCCESS},
triggered_by)
dummy_hook = DummyHookAction(
task, {"status": consts.HookStatus.SUCCESS}, triggered_by)
dummy_hook.run_sync()
self.assertEqual(
@ -259,11 +260,80 @@ class HookTestCase(test.TestCase):
def test_result_not_started(self):
task = mock.MagicMock()
triggered_by = {"event_type": "iteration", "value": 1}
dummy_hook = DummyHook(task, {"status": consts.HookStatus.SUCCESS},
triggered_by)
dummy_hook = DummyHookAction(task,
{"status": consts.HookStatus.SUCCESS},
triggered_by)
self.assertEqual(
{"started_at": 0.0,
"finished_at": 0.0,
"triggered_by": triggered_by,
"status": consts.HookStatus.SUCCESS}, dummy_hook.result())
@ddt.ddt
class TriggerTestCase(test.TestCase):
def setUp(self):
super(TriggerTestCase, self).setUp()
@hook.configure(name="dummy_trigger")
class DummyTrigger(hook.HookTrigger):
CONFIG_SCHEMA = {"type": "array",
"minItems": 1,
"uniqueItems": True,
"items": {
"type": "integer",
"minimum": 0,
}}
def get_listening_event(self):
return "dummy"
def on_event(self, event_type, value=None):
if value not in self.config:
return
super(DummyTrigger, self).on_event(event_type, value)
self.DummyTrigger = DummyTrigger
self.addCleanup(DummyTrigger.unregister)
@ddt.data(([5], True), ("str", False))
@ddt.unpack
def test_validate(self, config, valid):
results = hook.HookTrigger.validate(
"dummy_trigger", None, None, config)
if valid:
self.assertEqual([], results)
else:
self.assertEqual(1, len(results))
def test_on_event_and_get_results(self):
# get_results requires launched hooks, so if we want to test it, we
# need to duplicate all calls on_event. It is redundant, so let's merge
# test_on_event and test_get_results in one test.
right_values = [5, 7, 12, 13]
cfg = {"trigger": {self.DummyTrigger.get_name(): right_values},
"action": {"fake": {}}}
task = mock.MagicMock()
hook_cls = mock.MagicMock(__name__="fake")
dummy_trigger = self.DummyTrigger(cfg, task, hook_cls)
for i in range(0, 20):
dummy_trigger.on_event("fake", i)
self.assertEqual(
[mock.call(task, {}, {"event_type": "fake", "value": i})
for i in right_values],
hook_cls.call_args_list)
self.assertEqual(len(right_values),
hook_cls.return_value.run_async.call_count)
hook_status = hook_cls.return_value.result.return_value["status"]
self.assertEqual(
{"config": cfg,
"results": [hook_cls.return_value.result.return_value] *
len(right_values),
"summary": {hook_status: len(right_values)}},
dummy_trigger.get_results())

View File

@ -15,68 +15,50 @@
"""Tests for Trigger base class."""
import ddt
import mock
from rally.task import trigger
from tests.unit import test
@trigger.configure(name="dummy_trigger")
class DummyTrigger(trigger.Trigger):
CONFIG_SCHEMA = {"type": "array",
"minItems": 1,
"uniqueItems": True,
"items": {
"type": "integer",
"minimum": 0,
}}
def get_listening_event(self):
return "dummy"
def on_event(self, event_type, value=None):
if value not in self.config:
return
super(DummyTrigger, self).on_event(event_type, value)
@ddt.ddt
class TriggerTestCase(test.TestCase):
@ddt.data(([5], True), ("str", False))
@ddt.unpack
def test_validate(self, config, valid):
results = trigger.Trigger.validate(
"dummy_trigger", None, None, config)
if valid:
self.assertEqual([], results)
else:
self.assertEqual(1, len(results))
def setUp(self):
super(TriggerTestCase, self).setUp()
def test_on_event_and_get_results(self):
# get_results requires launched hooks, so if we want to test it, we
# need to duplicate all calls on_event. It is redundant, so let's merge
# test_on_event and test_get_results in one test.
right_values = [5, 7, 12, 13]
@trigger.hook.configure(self.id())
class DummyTrigger(trigger.Trigger):
def get_listening_event(self):
return "dummy"
cfg = {"trigger": {"args": right_values}}
task = mock.MagicMock()
hook_cls = mock.MagicMock(__name__="fake")
dummy_trigger = DummyTrigger(cfg, task, hook_cls)
for i in range(0, 20):
dummy_trigger.on_event("fake", i)
self.addCleanup(DummyTrigger.unregister)
self.DummyTrigger = DummyTrigger
@mock.patch("rally.task.trigger.LOG.warning")
def test_warning(self, mock_log_warning):
self.DummyTrigger({"trigger": {self.id(): {}}}, None, None)
mock_log_warning.assert_called_once_with(
"Please contact Rally plugin maintainer. The plugin '%s'"
" inherits the deprecated base class(Trigger), "
"`rally.task.hook.HookTrigger` should be used instead." %
self.id())
def test_context(self):
action_name = "mega_action"
action_cfg = {"action_arg": "action_value"}
trigger_name = self.id()
trigger_cfg = {"trigger_arg": "trigger_value"}
descr = "descr"
trigger_obj = self.DummyTrigger({
"trigger": {trigger_name: trigger_cfg},
"action": {action_name: action_cfg},
"description": descr}, None, None)
self.assertEqual(
[mock.call(task, {}, {"event_type": "fake", "value": i})
for i in right_values],
hook_cls.call_args_list)
self.assertEqual(len(right_values),
hook_cls.return_value.run_async.call_count)
hook_status = hook_cls.return_value.result.return_value["status"]
self.assertEqual(
{"config": cfg,
"results": [hook_cls.return_value.result.return_value] *
len(right_values),
"summary": {hook_status: len(right_values)}},
dummy_trigger.get_results())
{"name": action_name,
"args": action_cfg,
"trigger": {"name": trigger_name,
"args": trigger_cfg},
"description": descr}, trigger_obj.context)