refactor atomics actions

We decided to still use atomic_actions field.
First step, save new atomic actions format in database,
and convert the new format to old format where the atomic
actions needs to be used.
Blueprint: improve-atomic-actions

Co-Authored-By: chenhb-zte <chen.haibing1@zte.com.cn>

Change-Id: I8aecd5bad319aa61d5f8a20a2c2fe9bacdc0e81a
This commit is contained in:
Andrey Kurilin 2016-09-08 15:48:01 +03:00 committed by chenhb
parent b5c8d4e088
commit 8af32076d0
15 changed files with 271 additions and 144 deletions

View File

@ -41,12 +41,10 @@ class CalculateAtomic(scenario.Scenario, utils.RandomNameGeneratorMixin):
:param number_of_atomics: int number of atomics to run :param number_of_atomics: int number of atomics to run
""" """
tmp_name = "tmp_actions" tmp_name = "tmp_actions"
atomic_inst = atomic.ActionTimerMixin()
calc_atomic_name = "calculate_%s_atomics" % number_of_atomics calc_atomic_name = "calculate_%s_atomics" % number_of_atomics
with atomic.ActionTimer(self, calc_atomic_name): with atomic.ActionTimer(self, calc_atomic_name):
for _ in range(number_of_atomics): for _ in range(number_of_atomics):
with atomic.ActionTimer(self, tmp_name): with atomic.ActionTimer(atomic_inst, tmp_name):
pass pass
self._atomic_actions = {
calc_atomic_name: self._atomic_actions[calc_atomic_name]}

View File

@ -410,9 +410,39 @@ class Task(object):
def add_subtask(self, **subtask): def add_subtask(self, **subtask):
return Subtask(self.task["uuid"], **subtask) return Subtask(self.task["uuid"], **subtask)
def get_results(self): def _get_results(self):
return db.task_result_get_all_by_uuid(self.task["uuid"]) return db.task_result_get_all_by_uuid(self.task["uuid"])
def get_results(self):
results = self._get_results()
for result in results:
for itr in result["data"]["raw"]:
itr["atomic_actions"] = self.convert_atomic_actions(
itr["atomic_actions"])
return results
"""TODO(chenhb): Remove this method after replacing old format.
Now we do not convert children actions, because our output
string and report only show one layer actions.
"""
@staticmethod
def convert_atomic_actions(atomic_actions):
"""Convert atomic actions to old format. """
if isinstance(atomic_actions, dict):
return atomic_actions
old_style = collections.OrderedDict()
for action in atomic_actions:
duration = action["finished_at"] - action["started_at"]
if action["name"] in old_style:
name_template = action["name"] + " (%i)"
i = 2
while name_template % i in old_style:
i += 1
old_style[name_template % i] = duration
else:
old_style[action["name"]] = duration
return old_style
@classmethod @classmethod
def extend_results(cls, results, serializable=False): def extend_results(cls, results, serializable=False):
"""Modify and extend results with aggregated data. """Modify and extend results with aggregated data.
@ -461,6 +491,8 @@ class Task(object):
atomic = collections.OrderedDict() atomic = collections.OrderedDict()
for itr in scenario["data"]["raw"]: for itr in scenario["data"]["raw"]:
itr["atomic_actions"] = cls.convert_atomic_actions(
itr["atomic_actions"])
for atomic_name, duration in itr["atomic_actions"].items(): for atomic_name, duration in itr["atomic_actions"].items():
duration = duration or 0 duration = duration or 0
if atomic_name not in atomic: if atomic_name not in atomic:

View File

@ -13,7 +13,6 @@
# 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 collections
import functools import functools
from rally.common import utils from rally.common import utils
@ -22,7 +21,7 @@ from rally.common import utils
class ActionTimerMixin(object): class ActionTimerMixin(object):
def __init__(self): def __init__(self):
self._atomic_actions = collections.OrderedDict() self._atomic_actions = []
def atomic_actions(self): def atomic_actions(self):
"""Returns the content of each atomic action.""" """Returns the content of each atomic action."""
@ -48,27 +47,26 @@ class ActionTimer(utils.Timer):
""" """
super(ActionTimer, self).__init__() super(ActionTimer, self).__init__()
self.instance = instance self.instance = instance
self.name = self._get_atomic_action_name(instance, name) self.name = name
self.instance._atomic_actions[self.name] = None self._root = self._find_parent(self.instance._atomic_actions)
self.atomic_action = {"name": self.name,
"children": [],
"started_at": None}
self._root.append(self.atomic_action)
@classmethod def _find_parent(self, atomic_actions):
def _get_atomic_action_name(cls, instance, name): if atomic_actions and "finished_at" not in atomic_actions[-1]:
# TODO(boris-42): It was quite bad idea to store atomic actions return self._find_parent(atomic_actions[-1]["children"])
# inside {}. We should refactor this in 0.2.0 release else:
# and store them inside array, that will allow us to return atomic_actions
# store atomic actions with the same name
if name not in instance._atomic_actions:
return name
name_template = name + " (%i)" def __enter__(self):
i = 2 super(ActionTimer, self).__enter__()
while name_template % i in instance._atomic_actions: self.atomic_action["started_at"] = self.start
i += 1
return name_template % i
def __exit__(self, type_, value, tb): def __exit__(self, type_, value, tb):
super(ActionTimer, self).__exit__(type_, value, tb) super(ActionTimer, self).__exit__(type_, value, tb)
self.instance._atomic_actions[self.name] = self.duration() self.atomic_action["finished_at"] = self.finish
def action_timer(name): def action_timer(name):

View File

@ -40,7 +40,7 @@ def format_result_on_timeout(exc, timeout):
"duration": timeout, "duration": timeout,
"idle_duration": 0, "idle_duration": 0,
"output": {"additive": [], "complete": []}, "output": {"additive": [], "complete": []},
"atomic_actions": {}, "atomic_actions": [],
"error": utils.format_exc(exc) "error": utils.format_exc(exc)
} }
@ -234,7 +234,7 @@ class ScenarioRunner(plugin.Plugin):
_RESULT_SCHEMA = { _RESULT_SCHEMA = {
"fields": [("duration", float), ("timestamp", float), "fields": [("duration", float), ("timestamp", float),
("idle_duration", float), ("output", dict), ("idle_duration", float), ("output", dict),
("atomic_actions", dict), ("error", list)] ("atomic_actions", list), ("error", list)]
} }
def _result_has_valid_schema(self, result): def _result_has_valid_schema(self, result):
@ -257,15 +257,28 @@ class ScenarioRunner(plugin.Plugin):
"proper_type": proper_type.__name__}) "proper_type": proper_type.__name__})
return False return False
for action, value in result["atomic_actions"].items(): actions_list = copy.deepcopy(result["atomic_actions"])
if not isinstance(value, float): for action in actions_list:
LOG.warning( for key in ("name", "started_at", "finished_at", "children"):
"Task %(uuid)s | Atomic action %(action)s has wrong type " if key not in action:
"'%(type)s', should be 'float'" LOG.warning(
% {"uuid": self.task["uuid"], "Task %(uuid)s | Atomic action %(action)s "
"action": action, "missing key '%(key)s'"
"type": type(value)}) % {"uuid": self.task["uuid"],
return False "action": action,
"key": key})
return False
for key in ("started_at", "finished_at"):
if not isinstance(action[key], float):
LOG.warning(
"Task %(uuid)s | Atomic action %(action)s has "
"wrong type '%(type)s', should be 'float'"
% {"uuid": self.task["uuid"],
"action": action,
"type": type(action[key])})
return False
if action["children"]:
actions_list.extend(action["children"])
for e in result["error"]: for e in result["error"]:
if not isinstance(e, str): if not isinstance(e, str):

View File

@ -64,32 +64,6 @@ def should_be_overridden(func):
return func return func
# TODO(andreykurilin): remove _DevNullDict and _ServiceWithoutAtomic when we
# start support inner atomics
class _DevNullDict(dict):
"""Do not keep anything."""
def __setitem__(self, key, value):
pass
class _ServiceWithoutAtomic(object):
def __init__(self, service):
self._service = service
self._atomic_actions = _DevNullDict()
def atomic_actions(self):
return self._atomic_actions
def __getattr__(self, name):
return getattr(self._service, name)
def __str__(self):
return "'%s' without atomic actions" % self._service.__name__
def __repr__(self):
return "<%s>" % str(self)
def method_wrapper(func): def method_wrapper(func):
"""Wraps service's methods with some magic """Wraps service's methods with some magic
@ -139,9 +113,6 @@ def method_wrapper(func):
raise TypeError(message) raise TypeError(message)
if kwargs.pop("no_atomic", False):
instance = _ServiceWithoutAtomic(instance)
return func(instance, *args, **kwargs) return func(instance, *args, **kwargs)
return wrapper return wrapper

View File

@ -26,6 +26,7 @@ import six
from rally.common.i18n import _ from rally.common.i18n import _
from rally.common.plugin import plugin from rally.common.plugin import plugin
from rally.task import utils
configure = plugin.configure configure = plugin.configure
@ -58,6 +59,11 @@ class SLAChecker(object):
:param iteration: iteration result object :param iteration: iteration result object
""" """
if isinstance(iteration, dict):
atomic_actions = iteration.get("atomic_actions", None)
if isinstance(atomic_actions, list):
iteration["atomic_actions"] = utils.WrapperForAtomicActions(
atomic_actions)
return all([sla.add_iteration(iteration) for sla in self.sla_criteria]) return all([sla.add_iteration(iteration) for sla in self.sla_criteria])
def merge(self, other): def merge(self, other):

View File

@ -23,6 +23,7 @@ import six
from rally.common.i18n import _ from rally.common.i18n import _
from rally.common import logging from rally.common import logging
from rally.common import objects
from rally import consts from rally import consts
from rally import exceptions from rally import exceptions
@ -410,3 +411,38 @@ class ActionBuilder(object):
self._build(binding["action"], times, self._build(binding["action"], times,
*(binding["args"] + args), **dft_kwargs)) *(binding["args"] + args), **dft_kwargs))
return bound_actions return bound_actions
# TODO(andreykurilin): We need to implement some wrapper for atomic actions,
# we can use these wrapper to simulate new and old format.
class WrapperForAtomicActions(list):
LOG_INFO = "Atomic actions format is changed. It is a list now."
def __init__(self, atomic_actions):
super(WrapperForAtomicActions, self).__init__(atomic_actions)
self.__atomic_actions = atomic_actions
self.__old_atomic_actions = objects.Task.convert_atomic_actions(
self.__atomic_actions)
def items(self):
LOG.warning(self.LOG_INFO)
return self.__old_atomic_actions.items()
def get(self, name, default=None):
LOG.warning(self.LOG_INFO)
return self.__old_atomic_actions.get(name, default)
def __iter__(self):
return iter(self.__atomic_actions)
def __len__(self):
return len(self.__atomic_actions)
def __getitem__(self, item):
if isinstance(item, int):
# it is a call to list:
return self.__atomic_actions[item]
else:
LOG.warning(self.LOG_INFO)
return self.__old_atomic_actions[item]

View File

@ -229,13 +229,27 @@ class TaskTestCase(test.TestCase):
@mock.patch("rally.common.objects.task.db.task_result_get_all_by_uuid", @mock.patch("rally.common.objects.task.db.task_result_get_all_by_uuid",
return_value="foo_results") return_value="foo_results")
def test_get_results(self, mock_task_result_get_all_by_uuid): def test__get_results(self, mock_task_result_get_all_by_uuid):
task = objects.Task(task=self.task) task = objects.Task(task=self.task)
results = task.get_results() results = task._get_results()
mock_task_result_get_all_by_uuid.assert_called_once_with( mock_task_result_get_all_by_uuid.assert_called_once_with(
self.task["uuid"]) self.task["uuid"])
self.assertEqual(results, "foo_results") self.assertEqual(results, "foo_results")
def test_get_results(self):
task = objects.Task(task=self.task)
task._get_results = mock.MagicMock()
return_value = [{"data": {"raw": [
{"atomic_actions": [
{"name": "some",
"started_at": 1.0,
"finished_at": 2.0,
"children": []}]}]}}]
task._get_results.return_value = return_value
self.assertEqual([{"data": {"raw": [
{"atomic_actions": {"some": 1.0}}]}}],
task.get_results())
@mock.patch("rally.common.objects.task.db.task_update") @mock.patch("rally.common.objects.task.db.task_update")
def test_set_failed(self, mock_task_update): def test_set_failed(self, mock_task_update):
mock_task_update.return_value = self.task mock_task_update.return_value = self.task
@ -310,6 +324,23 @@ class TaskTestCase(test.TestCase):
consts.TaskStatus.SOFT_ABORTING) consts.TaskStatus.SOFT_ABORTING)
) )
@ddt.data(
{"atomic_actions": [{"name": "some", "started_at": 1.0,
"finished_at": 2.0, "children": []}],
"expected": {"some": 1.0}},
{"atomic_actions": [{"name": "some", "started_at": 1.0,
"finished_at": 2.0, "children": []},
{"name": "some", "started_at": 2.0,
"finished_at": 3.0, "children": []}],
"expected": {"some": 1.0, "some (2)": 1.0}},
{"atomic_actions": {"some": 1.0},
"expected": {"some": 1.0}}
)
@ddt.unpack
def test_convert_atomic_actions(self, atomic_actions, expected):
self.assertEqual(expected,
objects.Task.convert_atomic_actions(atomic_actions))
class SubtaskTestCase(test.TestCase): class SubtaskTestCase(test.TestCase):

View File

@ -28,7 +28,7 @@ class SerialScenarioRunnerTestCase(test.TestCase):
times = 5 times = 5
result = {"duration": 10., "idle_duration": 0., "error": [], result = {"duration": 10., "idle_duration": 0., "error": [],
"output": {"additive": [], "complete": []}, "output": {"additive": [], "complete": []},
"atomic_actions": {}, "atomic_actions": [],
"timestamp": 1.} "timestamp": 1.}
mock__run_scenario_once.return_value = result mock__run_scenario_once.return_value = result
deque_as_queue_inst = mock_deque_as_queue.return_value deque_as_queue_inst = mock_deque_as_queue.return_value

View File

@ -58,7 +58,7 @@ class StackTestCase(test.ScenarioTestCase):
@mock.patch("rally.plugins.openstack.services.heat.main.open") @mock.patch("rally.plugins.openstack.services.heat.main.open")
@mock.patch("rally.plugins.openstack.services.heat.main.Stack._wait") @mock.patch("rally.plugins.openstack.services.heat.main.Stack._wait")
def test_create(self, mock_stack__wait, mock_open, mock_task_atomic): def test_create(self, mock_stack__wait, mock_open, mock_task_atomic):
mock_scenario = mock.MagicMock() mock_scenario = mock.MagicMock(_atomic_actions=[])
mock_scenario.generate_random_name.return_value = "fake_name" mock_scenario.generate_random_name.return_value = "fake_name"
mock_open().read.return_value = "fake_content" mock_open().read.return_value = "fake_content"
mock_new_stack = { mock_new_stack = {
@ -87,7 +87,8 @@ class StackTestCase(test.ScenarioTestCase):
@mock.patch("rally.plugins.openstack.services.heat.main.open") @mock.patch("rally.plugins.openstack.services.heat.main.open")
@mock.patch("rally.plugins.openstack.services.heat.main.Stack._wait") @mock.patch("rally.plugins.openstack.services.heat.main.Stack._wait")
def test_update(self, mock_stack__wait, mock_open, mock_task_atomic): def test_update(self, mock_stack__wait, mock_open, mock_task_atomic):
mock_scenario = mock.MagicMock(stack_id="fake_id") mock_scenario = mock.MagicMock(
stack_id="fake_id", _atomic_actions=[])
mock_parameters = mock.Mock() mock_parameters = mock.Mock()
mock_open().read.return_value = "fake_content" mock_open().read.return_value = "fake_content"
stack = main.Stack( stack = main.Stack(

View File

@ -13,8 +13,6 @@
# 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 collections
import mock import mock
from rally.task import atomic from rally.task import atomic
@ -39,8 +37,17 @@ class AtomicActionTestCase(test.TestCase):
with atomic.ActionTimer(inst, "some"): with atomic.ActionTimer(inst, "some"):
pass pass
expected = [("test", 20), ("test (2)", 12), ("some", 4)] expected = [{"name": "test",
self.assertEqual(collections.OrderedDict(expected), "started_at": 1,
"finished_at": 21,
"children": [{"name": "test",
"started_at": 3,
"finished_at": 15,
"children": [{"name": "some",
"started_at": 6,
"finished_at": 10,
"children": []}]}]}]
self.assertEqual(expected,
inst.atomic_actions()) inst.atomic_actions())
@mock.patch("time.time", side_effect=[1, 3]) @mock.patch("time.time", side_effect=[1, 3])
@ -56,8 +63,8 @@ class AtomicActionTestCase(test.TestCase):
except TestException: except TestException:
pass pass
expected = [("test", 2)] self.assertEqual([{"name": "test", "children": [],
self.assertEqual(collections.OrderedDict(expected), "started_at": 1, "finished_at": 3}],
inst.atomic_actions()) inst.atomic_actions())
@mock.patch("time.time", side_effect=[1, 3]) @mock.patch("time.time", side_effect=[1, 3])
@ -71,7 +78,8 @@ class AtomicActionTestCase(test.TestCase):
inst = Some() inst = Some()
self.assertEqual(5, inst.some_func(2, 3)) self.assertEqual(5, inst.some_func(2, 3))
self.assertEqual(collections.OrderedDict({"some": 2}), self.assertEqual([{"name": "some", "children": [],
"started_at": 1, "finished_at": 3}],
inst.atomic_actions()) inst.atomic_actions())
@mock.patch("time.time", side_effect=[1, 3]) @mock.patch("time.time", side_effect=[1, 3])
@ -88,7 +96,8 @@ class AtomicActionTestCase(test.TestCase):
inst = TestTimer() inst = TestTimer()
self.assertRaises(TestException, inst.some_func) self.assertRaises(TestException, inst.some_func)
self.assertEqual(collections.OrderedDict({"test": 2}), self.assertEqual([{"name": "test", "children": [],
"started_at": 1, "finished_at": 3}],
inst.atomic_actions()) inst.atomic_actions())
@mock.patch("time.time", side_effect=[1, 3, 1, 3]) @mock.patch("time.time", side_effect=[1, 3, 1, 3])
@ -107,20 +116,22 @@ class AtomicActionTestCase(test.TestCase):
inst = TestAtomicTimer() inst = TestAtomicTimer()
self.assertEqual(5, inst.some_func(2, 3)) self.assertEqual(5, inst.some_func(2, 3))
self.assertEqual(collections.OrderedDict({"some": 2}), self.assertEqual([{"name": "some", "children": [],
"started_at": 1, "finished_at": 3}],
inst.atomic_actions()) inst.atomic_actions())
inst = TestAtomicTimer() inst = TestAtomicTimer()
self.assertEqual(5, inst.some_func(2, 3, atomic_action=False)) self.assertEqual(5, inst.some_func(2, 3, atomic_action=False))
self.assertEqual(collections.OrderedDict(), self.assertEqual([],
inst.atomic_actions()) inst.atomic_actions())
inst = TestAtomicTimer() inst = TestAtomicTimer()
self.assertEqual(5, inst.other_func(2, 3)) self.assertEqual(5, inst.other_func(2, 3))
self.assertEqual(collections.OrderedDict(), self.assertEqual([],
inst.atomic_actions()) inst.atomic_actions())
inst = TestAtomicTimer() inst = TestAtomicTimer()
self.assertEqual(5, inst.other_func(2, 3, foo=True)) self.assertEqual(5, inst.other_func(2, 3, foo=True))
self.assertEqual(collections.OrderedDict({"some": 2}), self.assertEqual([{"name": "some", "children": [],
"started_at": 1, "finished_at": 3}],
inst.atomic_actions()) inst.atomic_actions())

View File

@ -39,7 +39,7 @@ class ScenarioRunnerHelpersTestCase(test.TestCase):
"duration": 100, "duration": 100,
"idle_duration": 0, "idle_duration": 0,
"output": {"additive": [], "complete": []}, "output": {"additive": [], "complete": []},
"atomic_actions": {}, "atomic_actions": [],
"error": mock_format_exc.return_value "error": mock_format_exc.return_value
} }
@ -85,7 +85,7 @@ class ScenarioRunnerHelpersTestCase(test.TestCase):
"idle_duration": 0, "idle_duration": 0,
"error": [], "error": [],
"output": {"additive": [], "complete": []}, "output": {"additive": [], "complete": []},
"atomic_actions": {} "atomic_actions": []
} }
self.assertEqual(expected_result, result) self.assertEqual(expected_result, result)
@ -108,7 +108,7 @@ class ScenarioRunnerHelpersTestCase(test.TestCase):
"description": "Complete description", "description": "Complete description",
"title": "Complete", "title": "Complete",
"chart_plugin": "BarPlugin"}]}, "chart_plugin": "BarPlugin"}]},
"atomic_actions": {} "atomic_actions": []
} }
self.assertEqual(expected_result, result) self.assertEqual(expected_result, result)
@ -123,7 +123,7 @@ class ScenarioRunnerHelpersTestCase(test.TestCase):
"timestamp": fakes.FakeTimer().timestamp(), "timestamp": fakes.FakeTimer().timestamp(),
"idle_duration": 0, "idle_duration": 0,
"output": {"additive": [], "complete": []}, "output": {"additive": [], "complete": []},
"atomic_actions": {} "atomic_actions": []
} }
self.assertEqual(expected_result, result) self.assertEqual(expected_result, result)
self.assertEqual(expected_error[:2], self.assertEqual(expected_error[:2],
@ -253,67 +253,97 @@ class ScenarioRunnerTestCase(test.TestCase):
@ddt.data( @ddt.data(
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"output": {"additive": [], "complete": []}, "output": {"additive": [], "complete": []},
"error": ["err1", "err2"], "atomic_actions": {}}, "error": ["err1", "err2"], "atomic_actions": []},
"expected": True}, "expected": True},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []}, "error": [], "output": {"additive": [], "complete": []},
"atomic_actions": {"foo": 4.2}}, "atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 5.2, "children": []}]},
"expected": True}, "expected": True},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": ["a1", "a2"], "error": [], "output": {"additive": ["a1", "a2"],
"complete": ["c1", "c2"]}, "complete": ["c1", "c2"]},
"atomic_actions": {"foo": 4.2}}, "atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 5.2, "children": []}]},
"validate_output_calls": [("additive", "a1"), ("additive", "a2"), "validate_output_calls": [("additive", "a1"), ("additive", "a2"),
("complete", "c1"), ("complete", "c2")], ("complete", "c1"), ("complete", "c2")],
"expected": True}, "expected": True},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": ["a1", "a2"], "error": [], "output": {"additive": ["a1", "a2"],
"complete": ["c1", "c2"]}, "complete": ["c1", "c2"]},
"atomic_actions": {"foo": 4.2}}, "atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 5.2, "children": []}]},
"validate_output_return_value": "validation error message"}, "validate_output_return_value": "validation error message"},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [42], "output": {"additive": [], "complete": []}, "error": [42], "output": {"additive": [], "complete": []},
"atomic_actions": {"foo": 4.2}}}, "atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 5.2, "children": []}]}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []}, "error": [], "output": {"additive": [], "complete": []},
"atomic_actions": {"foo": 42}}}, "atomic_actions": [{"name": "foo", "started_at": 10,
"finished_at": 52, "children": []}]}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []}, "error": [], "output": {"additive": [], "complete": []},
"atomic_actions": {"foo": "non-float"}}}, "atomic_actions": [{"name": "non-float", "started_at": 1.0,
"children": []}]}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []},
"atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 4.0,
"children": [{"name": "foo1",
"started_at": 2.0,
"finished_at": 3.0,
"children": []}]}]},
"expected": True},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []},
"atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 4.0,
"children": [{"name": "foo1",
"started_at": 20,
"finished_at": 30,
"children": []}]}]}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []},
"atomic_actions": [{"name": "foo", "started_at": 1.0,
"finished_at": 4.0,
"children": [{"name": "foo1",
"started_at": 2.0,
"finished_at": 3.0}]}]}},
{"data": {"duration": 1, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []}, "error": [], "output": {"additive": [], "complete": []},
"atomic_actions": {}}}, "atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []}, "error": [], "output": {"additive": [], "complete": []},
"atomic_actions": {}}}, "atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1,
"error": [], "output": {"additive": [], "complete": []}, "error": [], "output": {"additive": [], "complete": []},
"atomic_actions": {}}}, "atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": "foo", "output": {"additive": [], "complete": []}, "error": "foo", "output": {"additive": [], "complete": []},
"atomic_actions": {}}}, "atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": []}, "error": [], "output": {"additive": []},
"atomic_actions": {}}}, "atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"complete": []}, "error": [], "output": {"complete": []},
"atomic_actions": {}}}, "atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {}, "atomic_actions": {}}}, "error": [], "output": {}, "atomic_actions": []}},
{"data": {"timestamp": 1.0, "idle_duration": 1.0, "error": [], {"data": {"timestamp": 1.0, "idle_duration": 1.0, "error": [],
"output": {"additive": [], "complete": []}, "output": {"additive": [], "complete": []},
"atomic_actions": {}}}, "atomic_actions": []}},
{"data": {"duration": 1.0, "idle_duration": 1.0, "error": [], {"data": {"duration": 1.0, "idle_duration": 1.0, "error": [],
"output": {"additive": [], "complete": []}, "output": {"additive": [], "complete": []},
"atomic_actions": {}}}, "atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "error": [], {"data": {"duration": 1.0, "timestamp": 1.0, "error": [],
"output": {"additive": [], "complete": []}, "output": {"additive": [], "complete": []},
"atomic_actions": {}}}, "atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"output": {"additive": [], "complete": []}, "output": {"additive": [], "complete": []},
"atomic_actions": {}}}, "atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "atomic_actions": {}}}, "error": [], "atomic_actions": []}},
{"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0, {"data": {"duration": 1.0, "timestamp": 1.0, "idle_duration": 1.0,
"error": [], "output": {"additive": [], "complete": []}}}, "error": [], "output": {"additive": [], "complete": []}}},
{"data": []}, {"data": []},

View File

@ -14,7 +14,6 @@
import mock import mock
from rally import exceptions from rally import exceptions
from rally.task import atomic
from rally.task import service from rally.task import service
from tests.unit import test from tests.unit import test
@ -246,42 +245,3 @@ class MethodWrapperTestCase(test.TestCase):
Some().foo(some=2, another=3) Some().foo(some=2, another=3)
Some().foo(1, some=2, another=3) Some().foo(1, some=2, another=3)
self.assertRaises(TypeError, Some().foo, 1, 2) self.assertRaises(TypeError, Some().foo, 1, 2)
def test_disabling_atomics(self):
class Some(service.UnifiedService):
def discover_impl(self):
return mock.MagicMock, None
@atomic.action_timer("some")
def foo(slf):
pass
def bar(slf):
pass
some = Some(mock.MagicMock(version="777"))
some.foo(no_atomic=True)
self.assertNotIn("some", some._atomic_actions)
# check that we are working with correct variable
some.foo()
self.assertIn("some", some._atomic_actions)
class ServiceWithoutAtomicTestCase(test.TestCase):
def test_access(self):
class Some(atomic.ActionTimerMixin):
def __getattr__(self, attr):
return self
some_cls = Some()
# add something to atomic actions dict to simplify comparison
# (empty fake dict != not empty _atomic_actions dict)
with atomic.ActionTimer(some_cls, "some"):
pass
wrapped_service = service._ServiceWithoutAtomic(some_cls)
self.assertNotEqual(some_cls.atomic_actions(),
wrapped_service.atomic_actions())
self.assertNotEqual(some_cls._atomic_actions,
wrapped_service._atomic_actions)
self.assertEqual(some_cls, wrapped_service.some_var)

View File

@ -13,10 +13,12 @@
# 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 collections
import datetime as dt import datetime as dt
from jsonschema import exceptions as schema_exceptions from jsonschema import exceptions as schema_exceptions
import mock import mock
import six
from rally import exceptions from rally import exceptions
from rally.task import utils from rally.task import utils
@ -524,3 +526,38 @@ class WaitForStatusTestCase(test.TestCase):
utils.wait_for_status, utils.wait_for_status,
resource=res, ready_statuses=["ready"], resource=res, ready_statuses=["ready"],
update_resource=upd, timeout=2, id_attr="uuid") update_resource=upd, timeout=2, id_attr="uuid")
class WrapperForAtomicActionsTestCase(test.TestCase):
def test_dict_atomic(self):
atomic_actions = collections.OrderedDict(
[("action_1", 1), ("action_2", 2)])
atomic_wrapper = utils.WrapperForAtomicActions(atomic_actions)
self.assertEqual(1, atomic_wrapper["action_1"])
self.assertEqual(2, atomic_wrapper["action_2"])
self.assertEqual(atomic_actions.items(),
atomic_wrapper.items())
self.assertEqual(1, atomic_wrapper.get("action_1"))
self.assertIsNone(atomic_wrapper.get("action_3"))
self.assertEqual(2, len(atomic_wrapper))
def test_list_atomic(self):
atomic_actions = [{"name": "action_1", "started_at": 1,
"finished_at": 2, "children": []},
{"name": "action_2", "started_at": 2,
"finished_at": 4, "children": []}]
atomic_wrapper = utils.WrapperForAtomicActions(atomic_actions)
self.assertEqual(1, atomic_wrapper["action_1"])
self.assertEqual(2, atomic_wrapper["action_2"])
self.assertEqual(
collections.OrderedDict(
[("action_1", 1), ("action_2", 2)]).items(),
atomic_wrapper.items())
self.assertEqual(atomic_actions[0], atomic_wrapper[0])
self.assertEqual(atomic_actions[1], atomic_wrapper[1])
self.assertEqual(1, atomic_wrapper.get("action_1"))
self.assertIsNone(None, atomic_wrapper.get("action_3"))
self.assertEqual(2, len(atomic_wrapper))
self.assertEqual(atomic_actions[0], six.next(iter(atomic_wrapper)))

View File

@ -22,6 +22,7 @@ from oslotest import base
from oslotest import mockpatch from oslotest import mockpatch
from rally.common import db from rally.common import db
from rally.common import objects
from rally import plugins from rally import plugins
from tests.unit import fakes from tests.unit import fakes
@ -46,7 +47,9 @@ class TestCase(base.BaseTestCase):
plugins.load() plugins.load()
def _test_atomic_action_timer(self, atomic_actions, name): def _test_atomic_action_timer(self, atomic_actions, name):
action_duration = atomic_actions.get(name) _old_atomic_actions = objects.Task.convert_atomic_actions(
atomic_actions)
action_duration = _old_atomic_actions.get(name)
self.assertIsNotNone(action_duration) self.assertIsNotNone(action_duration)
self.assertIsInstance(action_duration, float) self.assertIsInstance(action_duration, float)