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

View File

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

View File

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

View File

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

View File

@ -33,7 +33,6 @@ from rally.task import hook
from rally.task import runner from rally.task import runner
from rally.task import scenario from rally.task import scenario
from rally.task import sla from rally.task import sla
from rally.task import trigger
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -312,19 +311,22 @@ class TaskEngine(object):
vtype=vtype)) vtype=vtype))
for hook_conf in workload["hooks"]: for hook_conf in workload["hooks"]:
results.extend(hook.Hook.validate( action_name, action_cfg = list(
name=hook_conf["config"]["name"], hook_conf["config"]["action"].items())[0]
results.extend(hook.HookAction.validate(
name=action_name,
context=vcontext, context=vcontext,
config=None, config=None,
plugin_cfg=hook_conf["config"]["args"], plugin_cfg=action_cfg,
vtype=vtype)) vtype=vtype))
trigger_conf = hook_conf["config"]["trigger"] trigger_name, trigger_cfg = list(
results.extend(trigger.Trigger.validate( hook_conf["config"]["trigger"].items())[0]
name=trigger_conf["name"], results.extend(hook.HookTrigger.validate(
name=trigger_name,
context=vcontext, context=vcontext,
config=None, config=None,
plugin_cfg=trigger_conf["args"], plugin_cfg=trigger_cfg,
vtype=vtype)) vtype=vtype))
if results: 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 = { CONFIG_SCHEMA_V1 = {
"type": "object", "type": "object",
"$schema": consts.JSON_SCHEMA, "$schema": consts.JSON_SCHEMA,
@ -537,12 +519,33 @@ class TaskConfig(object):
"sla": {"type": "object"}, "sla": {"type": "object"},
"hooks": { "hooks": {
"type": "array", "type": "array",
"items": HOOK_CONFIG, "items": {"$ref": "#/definitions/hook"},
} }
}, },
"additionalProperties": False "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"}, "sla": {"type": "object"},
"hooks": { "hooks": {
"type": "array", "type": "array",
"items": HOOK_CONFIG, "items": {"$ref": "#/definitions/hook"},
}, },
"contexts": {"type": "object"} "contexts": {"type": "object"}
}, },
@ -627,7 +630,7 @@ class TaskConfig(object):
"sla": {"type": "object"}, "sla": {"type": "object"},
"hooks": { "hooks": {
"type": "array", "type": "array",
"items": HOOK_CONFIG, "items": {"$ref": "#/definitions/hook"},
}, },
"contexts": {"type": "object"} "contexts": {"type": "object"}
}, },
@ -638,6 +641,43 @@ class TaskConfig(object):
}, },
"additionalProperties": False, "additionalProperties": False,
"required": ["title", "workloads"] "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: else:
wconf["runner"] = {"serial": {}} wconf["runner"] = {"serial": {}}
wconf.setdefault("sla", {"failure_rate": {"max": 0}}) 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) workloads.append(wconf)
sconf["workloads"] = workloads sconf["workloads"] = workloads
@ -757,6 +812,18 @@ class TaskConfig(object):
if "runner" in subtask: if "runner" in subtask:
runner_type = subtask["runner"].pop("type") runner_type = subtask["runner"].pop("type")
subtask["runner"] = {runner_type: subtask["runner"]} 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) subtasks.append(subtask)
return {"title": "Task (adopted from task format v1)", return {"title": "Task (adopted from task format v1)",
"subtasks": subtasks} "subtasks": subtasks}

View File

@ -27,7 +27,6 @@ from rally.common import validation
from rally import consts from rally import consts
from rally import exceptions from rally import exceptions
from rally.task.processing import charts from rally.task.processing import charts
from rally.task import trigger
from rally.task import utils from rally.task import utils
@ -47,9 +46,11 @@ class HookExecutor(object):
self.triggers = collections.defaultdict(list) self.triggers = collections.defaultdict(list)
for hook in config.get("hooks", []): for hook in config.get("hooks", []):
hook_cfg = hook["config"] hook_cfg = hook["config"]
hook_cls = Hook.get(hook_cfg["name"]) action_name = list(hook_cfg["action"].keys())[0]
trigger_obj = trigger.Trigger.get( trigger_name = list(hook_cfg["trigger"].keys())[0]
hook_cfg["trigger"]["name"])(hook_cfg, self.task, hook_cls) 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() event_type = trigger_obj.get_listening_event()
self.triggers[event_type].append(trigger_obj) self.triggers[event_type].append(trigger_obj)
@ -112,7 +113,7 @@ class HookExecutor(object):
@validation.add_default("jsonschema") @validation.add_default("jsonschema")
@plugin.base() @plugin.base()
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class Hook(plugin.Plugin, validation.ValidatablePluginMixin): class HookAction(plugin.Plugin, validation.ValidatablePluginMixin):
"""Factory for hook classes.""" """Factory for hook classes."""
CONFIG_SCHEMA = {"type": "null"} CONFIG_SCHEMA = {"type": "null"}
@ -205,3 +206,56 @@ class Hook(plugin.Plugin, validation.ValidatablePluginMixin):
# hook is still running, wait for result # hook is still running, wait for result
self._thread.join() self._thread.join()
return self._result 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.""" """Prepare hooks data for report."""
hooks_ctx = [] hooks_ctx = []
for hook in hooks: for hook in hooks:
hook_ctx = {"name": hook["config"]["name"], hook_ctx = {"name": list(hook["config"]["action"].keys())[0],
"desc": hook["config"].get("description", ""), "desc": hook["config"].get("description", ""),
"additive": [], "complete": []} "additive": [], "complete": []}

View File

@ -13,56 +13,28 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import abc
import six
from rally.common.i18n import _
from rally.common import logging from rally.common import logging
from rally.common.plugin import plugin from rally.task import hook
from rally.common import validation
configure = plugin.configure
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@validation.add_default("jsonschema") class Trigger(hook.HookTrigger):
@plugin.base() """DEPRECATED!!! USE `rally.task.hook.HookTrigger` instead."""
@six.add_metaclass(abc.ABCMeta)
class Trigger(plugin.Plugin, validation.ValidatablePluginMixin):
"""Factory for trigger classes."""
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): @property
self.context = context def context(self):
self.config = self.context["trigger"]["args"] action_name, action_cfg = list(self.hook_cfg["action"].items())[0]
self.task = task trigger_name, trigger_cfg = list(self.hook_cfg["trigger"].items())[0]
self.hook_cls = hook_cls return {"description": self.hook_cfg["description"],
self._runs = [] "name": action_name,
"args": action_cfg,
@abc.abstractmethod "trigger": {"name": trigger_name,
def get_listening_event(self): "args": trigger_cfg}}
"""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

View File

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

View File

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

View File

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

View File

@ -40,7 +40,8 @@ class FaultInjectionHookTestCase(test.TestCase):
(dict(), False)) (dict(), False))
@ddt.unpack @ddt.unpack
def test_config_schema(self, config, valid): 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: if valid:
self.assertEqual([], results) self.assertEqual([], results)
else: else:

View File

@ -87,48 +87,65 @@ class PlotTestCase(test.TestCase):
@ddt.data( @ddt.data(
{"hooks": [], "expected": []}, {"hooks": [], "expected": []},
{"hooks": [ {"hooks": [
{"config": { {
"trigger": {"args": {"at": [2, 5], "unit": "iteration"}, "config": {
"name": "event"}, "description": "Foo",
"args": "foo cmd", "description": "Foo", "name": "sys_call"}, "action": {"sys_call": "foo cmd"},
"results": [ "trigger": {"event": {"at": [2, 5], "unit": "iteration"}}},
{"status": "success", "finished_at": 1475589987.525735, "results": [
"triggered_by": {"event_type": "iteration", "value": 2}, {
"started_at": 1475589987.433399, "status": "success",
"output": { "started_at": 1475589987.433399,
"additive": [ "finished_at": 1475589987.525735,
{"chart_plugin": "StatsTable", "title": "Foo table", "triggered_by": {"event_type": "iteration",
"data": [["A", 158], ["B", 177]]}], "value": 2},
"complete": []}}, "output": {
{"status": "success", "finished_at": 1475589993.457818, "additive": [
"triggered_by": {"event_type": "iteration", "value": 5}, {"chart_plugin": "StatsTable",
"started_at": 1475589993.432734, "title": "Foo table",
"output": { "data": [["A", 158], ["B", 177]]}],
"additive": [ "complete": []}},
{"chart_plugin": "StatsTable", "title": "Foo table", {
"data": [["A", 243], ["B", 179]]}], "status": "success",
"complete": []}}], "started_at": 1475589993.432734,
"summary": {"success": 2}}, "finished_at": 1475589993.457818,
{"config": {"trigger": {"args": {"at": [1, 2, 4], "unit": "time"}, "triggered_by": {"event_type": "iteration",
"name": "event"}, "value": 5},
"args": "bar cmd", "name": "sys_call"}, "output": {
"results": [ "additive": [
{"status": "success", "finished_at": 1475589988.437791, {"chart_plugin": "StatsTable",
"triggered_by": {"event_type": "time", "value": 1}, "title": "Foo table",
"started_at": 1475589988.434244, "data": [["A", 243], ["B", 179]]}],
"output": {"additive": [], "complete": []}}],
"complete": [ "summary": {"success": 2}},
{"chart_plugin": "Pie", "title": "Bar Pie", {
"data": [["F", 4], ["G", 2]]}]}}, "config": {
{"status": "success", "action": {"sys_call": "bar cmd"},
"finished_at": 1475589989.437589, "trigger": {"event": {"at": [1, 2, 4], "unit": "time"}}},
"triggered_by": {"event_type": "time", "value": 2}, "results": [
"started_at": 1475589989.433964, {
"output": {"additive": [], "status": "success",
"complete": [ "started_at": 1475589988.434244,
{"chart_plugin": "Pie", "title": "Bar Pie", "finished_at": 1475589988.437791,
"data": [["F", 42], ["G", 24]]}]}}], "triggered_by": {"event_type": "time", "value": 1},
"summary": {"success": 2}}], "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": [ "expected": [
{"additive": [ {"additive": [
{"data": {"cols": ["Action", "Min (sec)", "Median (sec)", {"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.engine.scenario.Scenario.get")
@mock.patch("rally.task.sla.SLA.validate") @mock.patch("rally.task.sla.SLA.validate")
@mock.patch("rally.task.trigger.Trigger.validate") @mock.patch("rally.task.hook.HookTrigger.validate")
@mock.patch("rally.task.hook.Hook.validate") @mock.patch("rally.task.hook.HookAction.validate")
@mock.patch("rally.task.engine.TaskConfig") @mock.patch("rally.task.engine.TaskConfig")
@mock.patch("rally.task.engine.runner.ScenarioRunner.validate") @mock.patch("rally.task.engine.runner.ScenarioRunner.validate")
@mock.patch("rally.task.engine.context.Context.validate") @mock.patch("rally.task.engine.context.Context.validate")
@ -137,15 +137,15 @@ class TaskEngineTestCase(test.TestCase):
self, mock_context_validate, self, mock_context_validate,
mock_scenario_runner_validate, mock_scenario_runner_validate,
mock_task_config, mock_task_config,
mock_hook_validate, mock_hook_action_validate,
mock_trigger_validate, mock_hook_trigger_validate,
mock_sla_validate, mock_sla_validate,
mock_scenario_get): mock_scenario_get):
mock_context_validate.return_value = [] mock_context_validate.return_value = []
mock_sla_validate.return_value = [] mock_sla_validate.return_value = []
mock_hook_validate.return_value = [] mock_hook_action_validate.return_value = []
mock_trigger_validate.return_value = [] mock_hook_trigger_validate.return_value = []
default_context = {"foo": "foo_conf"} default_context = {"foo": "foo_conf"}
scenario_cls = mock_scenario_get.return_value scenario_cls = mock_scenario_get.return_value
scenario_cls.get_platform.return_value = "default" scenario_cls.get_platform.return_value = "default"
@ -153,9 +153,8 @@ class TaskEngineTestCase(test.TestCase):
scenario_name = "Foo.bar" scenario_name = "Foo.bar"
runner_type = "MegaRunner" runner_type = "MegaRunner"
hook_conf = {"name": "c", hook_conf = {"action": {"c": "c_args"},
"args": "c_args", "trigger": {"d": "d_args"}}
"trigger": {"name": "d", "args": "d_args"}}
workload = {"name": scenario_name, workload = {"name": scenario_name,
"runner": {"type": runner_type}, "runner": {"type": runner_type},
"context": {"a": "a_conf"}, "context": {"a": "a_conf"},
@ -186,10 +185,10 @@ class TaskEngineTestCase(test.TestCase):
mock_sla_validate.assert_called_once_with( mock_sla_validate.assert_called_once_with(
config=None, context=None, config=None, context=None,
name="foo_sla", plugin_cfg="sla_conf", vtype=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", config=None, context=None, name="c", plugin_cfg="c_args",
vtype=None) 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", config=None, context=None, name="d", plugin_cfg="d_args",
vtype=None) vtype=None)
@ -270,21 +269,21 @@ class TaskEngineTestCase(test.TestCase):
@mock.patch("rally.task.engine.json.dumps") @mock.patch("rally.task.engine.json.dumps")
@mock.patch("rally.task.engine.scenario.Scenario.get") @mock.patch("rally.task.engine.scenario.Scenario.get")
@mock.patch("rally.task.hook.Hook.validate") @mock.patch("rally.task.hook.HookAction.validate")
@mock.patch("rally.task.trigger.Trigger.validate") @mock.patch("rally.task.hook.HookTrigger.validate")
@mock.patch("rally.task.engine.TaskConfig") @mock.patch("rally.task.engine.TaskConfig")
def test__validate_config_syntax__wrong_hook( 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_scenario_get, mock_dumps):
mock_dumps.return_value = "<JSON>" mock_dumps.return_value = "<JSON>"
mock_trigger_validate.return_value = [] mock_hook_trigger_validate.return_value = []
mock_hook_validate.return_value = ["hook_error"] mock_hook_action_validate.return_value = ["hook_error"]
scenario_cls = mock_scenario_get.return_value scenario_cls = mock_scenario_get.return_value
scenario_cls.get_default_context.return_value = {} scenario_cls.get_default_context.return_value = {}
mock_task_instance = mock.MagicMock() mock_task_instance = mock.MagicMock()
hook_conf = {"name": "c", hook_conf = {"action": {"c": "c_args"},
"args": "c_args", "trigger": {"d": "d_args"}}
"trigger": {"name": "d", "args": "d_args"}}
mock_task_instance.subtasks = [{"workloads": [ mock_task_instance.subtasks = [{"workloads": [
self._make_workload(name="sca"), self._make_workload(name="sca"),
self._make_workload(name="sca", position=1, 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.json.dumps")
@mock.patch("rally.task.engine.scenario.Scenario.get") @mock.patch("rally.task.engine.scenario.Scenario.get")
@mock.patch("rally.task.trigger.Trigger.validate") @mock.patch("rally.task.hook.HookTrigger.validate")
@mock.patch("rally.task.hook.Hook.validate") @mock.patch("rally.task.hook.HookAction.validate")
@mock.patch("rally.task.engine.TaskConfig") @mock.patch("rally.task.engine.TaskConfig")
def test__validate_config_syntax__wrong_trigger( 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_scenario_get, mock_dumps):
mock_dumps.return_value = "<JSON>" mock_dumps.return_value = "<JSON>"
mock_trigger_validate.return_value = ["trigger_error"] mock_hook_trigger_validate.return_value = ["trigger_error"]
mock_hook_validate.return_value = [] mock_hook_action_validate.return_value = []
scenario_cls = mock_scenario_get.return_value scenario_cls = mock_scenario_get.return_value
scenario_cls.get_default_context.return_value = {} scenario_cls.get_default_context.return_value = {}
mock_task_instance = mock.MagicMock() mock_task_instance = mock.MagicMock()
hook_conf = {"name": "c", hook_conf = {"action": {"c": "c_args"},
"args": "c_args", "trigger": {"d": "d_args"}}
"trigger": {"name": "d", "args": "d_args"}}
mock_task_instance.subtasks = [{"workloads": [ mock_task_instance.subtasks = [{"workloads": [
self._make_workload(name="sca"), self._make_workload(name="sca"),
self._make_workload(name="sca", position=1, self._make_workload(name="sca", position=1,
@ -954,7 +953,7 @@ class ResultConsumerTestCase(test.TestCase):
self.assertEqual(4, mock_task_get_status.call_count) self.assertEqual(4, mock_task_get_status.call_count)
class TaskTestCase(test.TestCase): class TaskConfigTestCase(test.TestCase):
@mock.patch("jsonschema.validate") @mock.patch("jsonschema.validate")
def test_validate_json(self, mock_validate): def test_validate_json(self, mock_validate):
config = {} config = {}
@ -996,19 +995,75 @@ class TaskTestCase(test.TestCase):
config = collections.OrderedDict() config = collections.OrderedDict()
config["a.task"] = [{"s": 1, "context": {"foo": "bar"}}, {"s": 2}] config["a.task"] = [{"s": 1, "context": {"foo": "bar"}}, {"s": 2}]
config["b.task"] = [{"s": 3, "sla": {"key": "value"}}] 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( self.assertEqual(
{"title": "Task (adopted from task format v1)", {"title": "Task (adopted from task format v1)",
"subtasks": [{"title": "a.task", "subtasks": [
"scenario": {"a.task": {}}, {
"s": 1, "title": "a.task",
"contexts": {"foo": "bar"}}, "scenario": {"a.task": {}},
{"title": "a.task", "s": 1,
"s": 2, "contexts": {"foo": "bar"}
"scenario": {"a.task": {}}, },
"contexts": {}}, {
{"title": "b.task", "title": "a.task",
"s": 3, "s": 2,
"scenario": {"b.task": {}}, "scenario": {"a.task": {}},
"sla": {"key": "value"}, "contexts": {}
"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)) 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") @hook.configure(name="dummy_hook")
class DummyHook(hook.Hook): class DummyHookAction(hook.HookAction):
CONFIG_SCHEMA = { CONFIG_SCHEMA = {
"type": "object", "type": "object",
"properties": { "properties": {
@ -56,14 +56,12 @@ class HookExecutorTestCase(test.TestCase):
self.conf = { self.conf = {
"hooks": [ "hooks": [
{"config": { {"config": {
"name": "dummy_hook",
"description": "dummy_action", "description": "dummy_action",
"args": { "action": {
"status": consts.HookStatus.SUCCESS, "dummy_hook": {"status": consts.HookStatus.SUCCESS}
}, },
"trigger": { "trigger": {
"name": "event", "event": {
"args": {
"unit": "iteration", "unit": "iteration",
"at": [1], "at": [1],
} }
@ -92,7 +90,7 @@ class HookExecutorTestCase(test.TestCase):
@mock.patch("rally.task.hook.HookExecutor._timer_method") @mock.patch("rally.task.hook.HookExecutor._timer_method")
@mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer) @mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer)
def test_result_optional(self, mock_timer, mock__timer_method): 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["error"] = ["Exception", "Description", "Traceback"]
hook_args["output"] = {"additive": None, "complete": None} hook_args["output"] = {"additive": None, "complete": None}
@ -120,9 +118,10 @@ class HookExecutorTestCase(test.TestCase):
hook_executor.results()) hook_executor.results())
@mock.patch("rally.task.hook.HookExecutor._timer_method") @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) @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): mock__timer_method):
hook_executor = hook.HookExecutor(self.conf, self.task) hook_executor = hook.HookExecutor(self.conf, self.task)
hook_executor.on_event(event_type="iteration", value=1) 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) @mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer)
def test_time_event(self, mock_timer): 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" trigger_args["unit"] = "time"
hook_executor = hook.HookExecutor(self.conf, self.task) 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) @mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer)
def test_time_periodic(self, mock_timer): def test_time_periodic(self, mock_timer):
self.conf["hooks"][0]["config"]["trigger"] = { 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) hook_executor = hook.HookExecutor(self.conf, self.task)
for i in range(1, 7): 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.Stopwatch", autospec=True)
@mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer) @mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer)
def test_timer_thread(self, mock_timer, mock_stopwatch): 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" trigger_args["unit"] = "time"
hook_executor = hook.HookExecutor(self.conf, self.task) hook_executor = hook.HookExecutor(self.conf, self.task)
@ -235,7 +236,7 @@ class HookTestCase(test.TestCase):
@ddt.data(({"status": "foo"}, True), (3, False)) @ddt.data(({"status": "foo"}, True), (3, False))
@ddt.unpack @ddt.unpack
def test_validate(self, config, valid): def test_validate(self, config, valid):
results = hook.Hook.validate( results = hook.HookAction.validate(
"dummy_hook", None, None, config) "dummy_hook", None, None, config)
if valid: if valid:
self.assertEqual([], results) self.assertEqual([], results)
@ -246,8 +247,8 @@ class HookTestCase(test.TestCase):
def test_result(self, mock_timer): def test_result(self, mock_timer):
task = mock.MagicMock() task = mock.MagicMock()
triggered_by = {"event_type": "iteration", "value": 1} triggered_by = {"event_type": "iteration", "value": 1}
dummy_hook = DummyHook(task, {"status": consts.HookStatus.SUCCESS}, dummy_hook = DummyHookAction(
triggered_by) task, {"status": consts.HookStatus.SUCCESS}, triggered_by)
dummy_hook.run_sync() dummy_hook.run_sync()
self.assertEqual( self.assertEqual(
@ -259,11 +260,80 @@ class HookTestCase(test.TestCase):
def test_result_not_started(self): def test_result_not_started(self):
task = mock.MagicMock() task = mock.MagicMock()
triggered_by = {"event_type": "iteration", "value": 1} triggered_by = {"event_type": "iteration", "value": 1}
dummy_hook = DummyHook(task, {"status": consts.HookStatus.SUCCESS}, dummy_hook = DummyHookAction(task,
triggered_by) {"status": consts.HookStatus.SUCCESS},
triggered_by)
self.assertEqual( self.assertEqual(
{"started_at": 0.0, {"started_at": 0.0,
"finished_at": 0.0, "finished_at": 0.0,
"triggered_by": triggered_by, "triggered_by": triggered_by,
"status": consts.HookStatus.SUCCESS}, dummy_hook.result()) "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.""" """Tests for Trigger base class."""
import ddt
import mock import mock
from rally.task import trigger from rally.task import trigger
from tests.unit import test 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): class TriggerTestCase(test.TestCase):
@ddt.data(([5], True), ("str", False)) def setUp(self):
@ddt.unpack super(TriggerTestCase, self).setUp()
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 test_on_event_and_get_results(self): @trigger.hook.configure(self.id())
# get_results requires launched hooks, so if we want to test it, we class DummyTrigger(trigger.Trigger):
# need to duplicate all calls on_event. It is redundant, so let's merge def get_listening_event(self):
# test_on_event and test_get_results in one test. return "dummy"
right_values = [5, 7, 12, 13]
cfg = {"trigger": {"args": right_values}} self.addCleanup(DummyTrigger.unregister)
task = mock.MagicMock() self.DummyTrigger = DummyTrigger
hook_cls = mock.MagicMock(__name__="fake")
dummy_trigger = DummyTrigger(cfg, task, hook_cls) @mock.patch("rally.task.trigger.LOG.warning")
for i in range(0, 20): def test_warning(self, mock_log_warning):
dummy_trigger.on_event("fake", i) 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( self.assertEqual(
[mock.call(task, {}, {"event_type": "fake", "value": i}) {"name": action_name,
for i in right_values], "args": action_cfg,
hook_cls.call_args_list) "trigger": {"name": trigger_name,
self.assertEqual(len(right_values), "args": trigger_cfg},
hook_cls.return_value.run_async.call_count) "description": descr}, trigger_obj.context)
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())