[validation] Use jsonschema validator in Hook and Trigger plugins

Change-Id: I62ede78d76bd0a70d1a9149e849c47399a03ebb7
This commit is contained in:
Anton Studenov 2017-04-04 14:12:13 +03:00
parent ae414bf1d0
commit 7fb348c31b
10 changed files with 187 additions and 171 deletions

View File

@ -35,6 +35,7 @@ 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__)
@ -283,6 +284,8 @@ class TaskEngine(object):
namespace = scenario_cls.get_namespace()
scenario_context = copy.deepcopy(
scenario_cls.get_default_context())
# TODO(astudenov): replace old validation
try:
runner.ScenarioRunner.validate(workload.runner)
context.ContextManager.validate(workload.context,
@ -291,14 +294,32 @@ class TaskEngine(object):
namespace=namespace,
allow_hidden=True)
sla.SLA.validate(workload.sla)
for hook_conf in workload.hooks:
hook.Hook.validate(hook_conf)
except (exceptions.RallyException,
jsonschema.ValidationError) as e:
kw = workload.make_exception_args(six.text_type(e))
raise exceptions.InvalidTaskConfig(**kw)
results = []
for hook_conf in workload.hooks:
results.extend(hook.Hook.validate(
name=hook_conf["name"],
credentials=None,
config=None,
plugin_cfg=hook_conf["args"]))
trigger_conf = hook_conf["trigger"]
results.extend(trigger.Trigger.validate(
name=trigger_conf["name"],
credentials=None,
config=None,
plugin_cfg=trigger_conf["args"]))
if results:
msg = "\n ".join([str(r) for r in results])
kw = workload.make_exception_args(msg)
raise exceptions.InvalidTaskConfig(**kw)
def _validate_config_semantic_helper(self, admin, user_context,
workloads, platform):
with user_context as ctx:

View File

@ -17,13 +17,13 @@ import abc
import collections
import threading
import jsonschema
import six
from rally.common.i18n import _, _LE
from rally.common import logging
from rally.common.plugin import plugin
from rally.common import utils as rutils
from rally.common import validation
from rally import consts
from rally import exceptions
from rally.task.processing import charts
@ -108,11 +108,14 @@ class HookExecutor(object):
return results
@validation.add_default("jsonschema")
@plugin.base()
@six.add_metaclass(abc.ABCMeta)
class Hook(plugin.Plugin):
class Hook(plugin.Plugin, validation.ValidatablePluginMixin):
"""Factory for hook classes."""
CONFIG_SCHEMA = {"type": "null"}
def __init__(self, task, config, triggered_by):
self.task = task
self.config = config
@ -127,13 +130,6 @@ class Hook(plugin.Plugin):
"triggered_by": self._triggered_by,
}
@staticmethod
def validate(config):
config_schema = Hook.get(config["name"]).CONFIG_SCHEMA
jsonschema.validate(config["args"], config_schema)
trigger.Trigger.validate(config["trigger"])
def _thread_method(self):
# Run hook synchronously
self.run_sync()

View File

@ -15,23 +15,26 @@
import abc
import jsonschema
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
LOG = logging.getLogger(__name__)
@validation.add_default("jsonschema")
@plugin.base()
@six.add_metaclass(abc.ABCMeta)
class Trigger(plugin.Plugin):
class Trigger(plugin.Plugin, validation.ValidatablePluginMixin):
"""Factory for trigger classes."""
CONFIG_SCHEMA = {"type": "null"}
def __init__(self, context, task, hook_cls):
self.context = context
self.config = self.context["trigger"]["args"]
@ -39,11 +42,6 @@ class Trigger(plugin.Plugin):
self.hook_cls = hook_cls
self._runs = []
@staticmethod
def validate(config):
config_schema = Trigger.get(config["name"]).CONFIG_SCHEMA
jsonschema.validate(config["args"], config_schema)
@abc.abstractmethod
def get_listening_event(self):
"""Returns event type to listen."""

View File

@ -16,11 +16,11 @@
import subprocess
import ddt
import jsonschema
import mock
from rally import consts
from rally.plugins.common.hook import sys_call
from rally.task import hook
from tests.unit import fakes
from tests.unit import test
@ -28,39 +28,15 @@ from tests.unit import test
@ddt.ddt
class SysCallHookTestCase(test.TestCase):
def test_validate(self):
sys_call.SysCallHook.validate(
{
"name": "sys_call",
"description": "list folder",
"args": "ls",
"trigger": {
"name": "event",
"args": {
"unit": "iteration",
"at": [10]
}
}
}
)
def test_validate_error(self):
conf = {
"name": "sys_call",
"description": "list folder",
"args": {
"cmd": 50,
},
"trigger": {
"name": "event",
"args": {
"unit": "iteration",
"at": [10]
}
}
}
self.assertRaises(
jsonschema.ValidationError, sys_call.SysCallHook.validate, conf)
@ddt.data(("ls", True), (50, False))
@ddt.unpack
def test_validate(self, config, valid):
results = hook.Hook.validate(
"sys_call", None, None, config)
if valid:
self.assertEqual([], results)
else:
self.assertEqual(1, len(results))
@ddt.data(
{"stdout": "foo output",

View File

@ -14,17 +14,13 @@
# under the License.
import ddt
import jsonschema
import mock
from rally.plugins.common.trigger import event
from rally.task import trigger
from tests.unit import test
def create_config(**kwargs):
return {"name": "event", "args": kwargs}
@ddt.ddt
class EventTriggerTestCase(test.TestCase):
@ -36,30 +32,30 @@ class EventTriggerTestCase(test.TestCase):
"args": {"unit": "iteration", "at": [1, 4, 5]}}},
mock.MagicMock(), self.hook_cls)
@ddt.data((create_config(unit="time", at=[0, 3, 5]), True),
(create_config(unit="time", at=[2, 2]), False),
(create_config(unit="time", at=[-1]), False),
(create_config(unit="time", at=[1.5]), False),
(create_config(unit="time", at=[]), False),
(create_config(unit="time", wrong_prop=None), False),
(create_config(unit="time"), False),
(create_config(unit="iteration", at=[1, 5, 13]), True),
(create_config(unit="iteration", at=[1, 1]), False),
(create_config(unit="iteration", at=[0]), False),
(create_config(unit="iteration", at=[-1]), False),
(create_config(unit="iteration", at=[1.5]), False),
(create_config(unit="iteration", at=[]), False),
(create_config(unit="iteration", wrong_prop=None), False),
(create_config(unit="iteration"), False),
(create_config(unit="wrong-unit", at=[1, 2, 3]), False),
(create_config(at=[1, 2, 3]), False))
@ddt.data((dict(unit="time", at=[0, 3, 5]), True),
(dict(unit="time", at=[2, 2]), False),
(dict(unit="time", at=[-1]), False),
(dict(unit="time", at=[1.5]), False),
(dict(unit="time", at=[]), False),
(dict(unit="time", wrong_prop=None), False),
(dict(unit="time"), False),
(dict(unit="iteration", at=[1, 5, 13]), True),
(dict(unit="iteration", at=[1, 1]), False),
(dict(unit="iteration", at=[0]), False),
(dict(unit="iteration", at=[-1]), False),
(dict(unit="iteration", at=[1.5]), False),
(dict(unit="iteration", at=[]), False),
(dict(unit="iteration", wrong_prop=None), False),
(dict(unit="iteration"), False),
(dict(unit="wrong-unit", at=[1, 2, 3]), False),
(dict(at=[1, 2, 3]), False))
@ddt.unpack
def test_config_schema(self, config, valid):
def test_validate(self, config, valid):
results = trigger.Trigger.validate("event", None, None, config)
if valid:
event.EventTrigger.validate(config)
self.assertEqual([], results)
else:
self.assertRaises(jsonschema.ValidationError,
event.EventTrigger.validate, config)
self.assertEqual(1, len(results))
def test_get_listening_event(self):
event_type = self.trigger.get_listening_event()

View File

@ -14,17 +14,13 @@
# under the License.
import ddt
import jsonschema
import mock
from rally.plugins.common.trigger import periodic
from rally.task import trigger
from tests.unit import test
def create_config(**kwargs):
return {"name": "periodic", "args": kwargs}
@ddt.ddt
class PeriodicTriggerTestCase(test.TestCase):
@ -36,31 +32,31 @@ class PeriodicTriggerTestCase(test.TestCase):
"args": {"unit": "iteration", "step": 2}}},
mock.MagicMock(), self.hook_cls)
@ddt.data((create_config(unit="time", step=1), True),
(create_config(unit="time", step=0), False),
(create_config(unit="time", step=1, start=0), True),
(create_config(unit="time", step=1, start=-1), False),
(create_config(unit="time", step=1, start=0, end=1), True),
(create_config(unit="time", step=1, start=0, end=0), False),
(create_config(unit="time", wrong_prop=None), False),
(create_config(unit="time"), False),
(create_config(unit="iteration", step=1), True),
(create_config(unit="iteration", step=0), False),
(create_config(unit="iteration", step=1, start=1), True),
(create_config(unit="iteration", step=1, start=0), False),
(create_config(unit="iteration", step=1, start=1, end=1), True),
(create_config(unit="iteration", step=1, start=1, end=0), False),
(create_config(unit="iteration", wrong_prop=None), False),
(create_config(unit="iteration"), False),
(create_config(unit="wrong-unit", step=1), False),
(create_config(step=1), False))
@ddt.data((dict(unit="time", step=1), True),
(dict(unit="time", step=0), False),
(dict(unit="time", step=1, start=0), True),
(dict(unit="time", step=1, start=-1), False),
(dict(unit="time", step=1, start=0, end=1), True),
(dict(unit="time", step=1, start=0, end=0), False),
(dict(unit="time", wrong_prop=None), False),
(dict(unit="time"), False),
(dict(unit="iteration", step=1), True),
(dict(unit="iteration", step=0), False),
(dict(unit="iteration", step=1, start=1), True),
(dict(unit="iteration", step=1, start=0), False),
(dict(unit="iteration", step=1, start=1, end=1), True),
(dict(unit="iteration", step=1, start=1, end=0), False),
(dict(unit="iteration", wrong_prop=None), False),
(dict(unit="iteration"), False),
(dict(unit="wrong-unit", step=1), False),
(dict(step=1), False))
@ddt.unpack
def test_config_schema(self, config, valid):
def test_validate(self, config, valid):
results = trigger.Trigger.validate("periodic", None, None, config)
if valid:
periodic.PeriodicTrigger.validate(config)
self.assertEqual([], results)
else:
self.assertRaises(jsonschema.ValidationError,
periodic.PeriodicTrigger.validate, config)
self.assertEqual(1, len(results))
def test_get_listening_event(self):
event_type = self.trigger.get_listening_event()

View File

@ -15,30 +15,16 @@
import ddt
import jsonschema
import mock
from os_faults.api import error
from rally import consts
from rally.plugins.openstack.hook import fault_injection
from rally.task import hook
from tests.unit import fakes
from tests.unit import test
def create_config(**kwargs):
return {
"name": "fault_injection",
"args": kwargs,
"trigger": {
"name": "event",
"args": {
"unit": "iteration",
"at": [10]
}
}
}
@ddt.ddt
class FaultInjectionHookTestCase(test.TestCase):
@ -46,19 +32,18 @@ class FaultInjectionHookTestCase(test.TestCase):
super(FaultInjectionHookTestCase, self).setUp()
self.task = {"deployment_uuid": "foo_uuid"}
@ddt.data((create_config(action="foo"), True),
(create_config(action="foo", verify=True), True),
(create_config(action=10), False),
(create_config(action="foo", verify=10), False),
(create_config(), False))
@ddt.data((dict(action="foo"), True),
(dict(action="foo", verify=True), True),
(dict(action=10), False),
(dict(action="foo", verify=10), False),
(dict(), False))
@ddt.unpack
def test_config_schema(self, config, valid):
results = hook.Hook.validate("fault_injection", None, None, config)
if valid:
fault_injection.FaultInjectionHook.validate(config)
self.assertEqual([], results)
else:
self.assertRaises(jsonschema.ValidationError,
fault_injection.FaultInjectionHook.validate,
config)
self.assertEqual(1, len(results))
@mock.patch("rally.common.objects.Deployment.get")
@mock.patch("os_faults.human_api")

View File

@ -166,6 +166,7 @@ class TaskEngineTestCase(test.TestCase):
"`nonexist2, nonexist1`.", str(exc))
@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.engine.TaskConfig")
@mock.patch("rally.task.engine.runner.ScenarioRunner.validate")
@ -175,18 +176,24 @@ class TaskEngineTestCase(test.TestCase):
mock_scenario_runner_validate,
mock_task_config,
mock_hook_validate,
mock_trigger_validate,
mock_scenario_get
):
mock_hook_validate.return_value = []
mock_trigger_validate.return_value = []
default_context = {"foo": 1}
scenario_cls = mock_scenario_get.return_value
scenario_cls.get_namespace.return_value = "default"
scenario_cls.get_default_context.return_value = default_context
mock_task_instance = mock.MagicMock()
mock_subtask = mock.MagicMock()
hook_conf = {"name": "c",
"args": "c_args",
"trigger": {"name": "d", "args": "d_args"}}
mock_subtask.workloads = [
engine.Workload({"name": "sca", "context": "a"}, 0),
engine.Workload({"name": "sca", "runner": "b"}, 1),
engine.Workload({"name": "sca", "hooks": ["c"]}, 2),
engine.Workload({"name": "sca", "hooks": [hook_conf]}, 2),
]
mock_task_instance.subtasks = [mock_subtask]
eng = engine.TaskEngine(mock.MagicMock(), mock.MagicMock(),
@ -206,7 +213,10 @@ class TaskEngineTestCase(test.TestCase):
namespace="default", allow_hidden=True)],
any_order=True
)
mock_hook_validate.assert_called_once_with("c")
mock_hook_validate.assert_called_once_with(
config=None, credentials=None, name="c", plugin_cfg="c_args")
mock_trigger_validate.assert_called_once_with(
config=None, credentials=None, name="d", plugin_cfg="d_args")
@mock.patch("rally.task.engine.scenario.Scenario.get")
@mock.patch("rally.task.engine.TaskConfig")
@ -256,6 +266,67 @@ class TaskEngineTestCase(test.TestCase):
self.assertRaises(exceptions.InvalidTaskConfig,
eng._validate_config_syntax, mock_task_instance)
@mock.patch("rally.task.engine.scenario.Scenario.get")
@mock.patch("rally.task.hook.Hook.validate")
@mock.patch("rally.task.engine.TaskConfig")
@mock.patch("rally.task.engine.runner.ScenarioRunner.validate")
@mock.patch("rally.task.engine.context.ContextManager")
def test__validate_config_syntax__wrong_hook(
self, mock_context_manager, mock_scenario_runner_validate,
mock_task_config, mock_hook_validate, mock_scenario_get):
result = validation.ValidationResult(False, "hook_error")
mock_hook_validate.return_value = [result]
scenario_cls = mock_scenario_get.return_value
scenario_cls.get_default_context.return_value = {}
mock_task_instance = mock.MagicMock()
mock_subtask = mock.MagicMock()
hook_conf = {"name": "c",
"args": "c_args",
"trigger": {"name": "d", "args": "d_args"}}
mock_subtask.workloads = [
engine.Workload({"name": "sca", "context": "a"}, 0),
engine.Workload({"name": "sca", "runner": "b"}, 1),
engine.Workload({"name": "sca", "hooks": [hook_conf]}, 2),
]
mock_task_instance.subtasks = [mock_subtask]
eng = engine.TaskEngine(mock.MagicMock(), mock.MagicMock(),
mock.Mock())
self.assertRaises(exceptions.InvalidTaskConfig,
eng._validate_config_syntax, mock_task_instance)
@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.engine.TaskConfig")
@mock.patch("rally.task.engine.runner.ScenarioRunner.validate")
@mock.patch("rally.task.engine.context.ContextManager")
def test__validate_config_syntax__wrong_trigger(
self, mock_context_manager, mock_scenario_runner_validate,
mock_task_config, mock_hook_validate, mock_trigger_validate,
mock_scenario_get):
result = validation.ValidationResult(False, "trigger_error")
mock_trigger_validate.return_value = [result]
mock_hook_validate.return_value = []
scenario_cls = mock_scenario_get.return_value
scenario_cls.get_default_context.return_value = {}
mock_task_instance = mock.MagicMock()
mock_subtask = mock.MagicMock()
hook_conf = {"name": "c",
"args": "c_args",
"trigger": {"name": "d", "args": "d_args"}}
mock_subtask.workloads = [
engine.Workload({"name": "sca", "context": "a"}, 0),
engine.Workload({"name": "sca", "runner": "b"}, 1),
engine.Workload({"name": "sca", "hooks": [hook_conf]}, 2),
]
mock_task_instance.subtasks = [mock_subtask]
eng = engine.TaskEngine(mock.MagicMock(), mock.MagicMock(),
mock.Mock())
self.assertRaises(exceptions.InvalidTaskConfig,
eng._validate_config_syntax, mock_task_instance)
@mock.patch("rally.task.engine.scenario.Scenario.validate")
@mock.patch("rally.task.engine.TaskConfig")
def test__validate_config_semantic_helper(self, mock_task_config,

View File

@ -15,7 +15,7 @@
"""Tests for HookExecutor and Hook classes."""
import jsonschema
import ddt
import mock
from rally import consts
@ -228,40 +228,18 @@ class HookExecutorTestCase(test.TestCase):
])
@ddt.ddt
class HookTestCase(test.TestCase):
def test_validate(self):
hook.Hook.validate(
{
"name": "dummy_hook",
"description": "dummy_action",
"args": {
"status": consts.HookStatus.SUCCESS,
},
"trigger": {
"name": "event",
"args": {
"unit": "iteration",
"at": [1],
}
}
}
)
def test_validate_error(self):
conf = {
"name": "dummy_hook",
"description": "dummy_action",
"args": 3,
"trigger": {
"name": "event",
"args": {
"unit": "iteration",
"at": [1],
}
}
}
self.assertRaises(jsonschema.ValidationError, hook.Hook.validate, conf)
@ddt.data(({"status": "foo"}, True), (3, False))
@ddt.unpack
def test_validate(self, config, valid):
results = hook.Hook.validate(
"dummy_hook", None, None, config)
if valid:
self.assertEqual([], results)
else:
self.assertEqual(1, len(results))
@mock.patch("rally.common.utils.Timer", side_effect=fakes.FakeTimer)
def test_result(self, mock_timer):

View File

@ -16,7 +16,6 @@
"""Tests for Trigger base class."""
import ddt
import jsonschema
import mock
from rally.task import trigger
@ -45,15 +44,15 @@ class DummyTrigger(trigger.Trigger):
@ddt.ddt
class TriggerTestCase(test.TestCase):
@ddt.data(({"name": "dummy_trigger", "args": [5]}, True),
({"name": "dummy_trigger", "args": ["str"]}, False))
@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:
trigger.Trigger.validate(config)
self.assertEqual([], results)
else:
self.assertRaises(jsonschema.ValidationError,
trigger.Trigger.validate, config)
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