336 lines
13 KiB
Python
336 lines
13 KiB
Python
# Copyright 2014: Mirantis Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import ddt
|
|
import mock
|
|
|
|
from rally.plugins.common.runners import constant
|
|
from rally.task import runner
|
|
from tests.unit import fakes
|
|
from tests.unit import test
|
|
|
|
|
|
RUNNERS_BASE = "rally.task.runner."
|
|
RUNNERS = "rally.plugins.common.runners."
|
|
|
|
|
|
@ddt.ddt
|
|
class ConstantScenarioRunnerTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(ConstantScenarioRunnerTestCase, self).setUp()
|
|
self.config = {"times": 4, "concurrency": 2,
|
|
"timeout": 2, "type": "constant",
|
|
"max_cpu_count": 2}
|
|
self.context = fakes.FakeContext({"task": {"uuid": "uuid"}}).context
|
|
self.args = {"a": 1}
|
|
self.task = mock.MagicMock()
|
|
|
|
@ddt.data(({"times": 4,
|
|
"concurrency": 2,
|
|
"timeout": 2,
|
|
"max_cpu_count": 2}, True),
|
|
({"times": 4,
|
|
"concurrency": 5,
|
|
"timeout": 2,
|
|
"max_cpu_count": 2}, False),
|
|
({"foo": "bar"}, False))
|
|
@ddt.unpack
|
|
def test_validate(self, config, valid):
|
|
results = runner.ScenarioRunner.validate(
|
|
"constant", None, None, config)
|
|
if valid:
|
|
self.assertEqual([], results)
|
|
else:
|
|
self.assertGreater(len(results), 0)
|
|
|
|
@mock.patch(RUNNERS + "constant.time")
|
|
@mock.patch(RUNNERS + "constant.threading.Thread")
|
|
@mock.patch(RUNNERS + "constant.multiprocessing.Queue")
|
|
@mock.patch(RUNNERS + "constant.runner")
|
|
def test__worker_process(self, mock_runner, mock_queue, mock_thread,
|
|
mock_time):
|
|
|
|
mock_thread_instance = mock.MagicMock(
|
|
isAlive=mock.MagicMock(return_value=False))
|
|
mock_thread.return_value = mock_thread_instance
|
|
|
|
mock_event = mock.MagicMock(
|
|
is_set=mock.MagicMock(return_value=False))
|
|
|
|
mock_event_queue = mock.MagicMock()
|
|
|
|
times = 4
|
|
|
|
fake_ram_int = iter(range(10))
|
|
|
|
context = {"users": [{"tenant_id": "t1", "credential": "c1",
|
|
"id": "uuid1"}]}
|
|
info = {"processes_to_start": 1, "processes_counter": 1}
|
|
|
|
constant._worker_process(mock_queue, fake_ram_int, 1, 2, times, None,
|
|
context, "Dummy", "dummy", (),
|
|
mock_event_queue, mock_event, info)
|
|
|
|
self.assertEqual(times + 1, mock_thread.call_count)
|
|
self.assertEqual(times + 1, mock_thread_instance.start.call_count)
|
|
self.assertEqual(times + 1, mock_thread_instance.join.call_count)
|
|
# NOTE(rvasilets): `times` + 1 here because `times` the number of
|
|
# scenario repetition and one more need on "initialization" stage
|
|
# of the thread stuff.
|
|
self.assertEqual(times, mock_runner._get_scenario_context.call_count)
|
|
|
|
for i in range(times):
|
|
scenario_context = mock_runner._get_scenario_context(i, context)
|
|
call = mock.call(
|
|
args=(mock_queue, "Dummy", "dummy", scenario_context, (),
|
|
mock_event_queue),
|
|
target=mock_runner._worker_thread,
|
|
)
|
|
self.assertIn(call, mock_thread.mock_calls)
|
|
|
|
@mock.patch(RUNNERS_BASE + "_run_scenario_once")
|
|
def test__worker_thread(self, mock__run_scenario_once):
|
|
mock_queue = mock.MagicMock()
|
|
|
|
mock_event_queue = mock.MagicMock()
|
|
|
|
args = ("fake_cls", "fake_method_name", "fake_context_obj", {},
|
|
mock_event_queue)
|
|
|
|
runner._worker_thread(mock_queue, *args)
|
|
|
|
self.assertEqual(1, mock_queue.put.call_count)
|
|
|
|
expected_calls = [mock.call(*args)]
|
|
self.assertEqual(expected_calls, mock__run_scenario_once.mock_calls)
|
|
|
|
def test__run_scenario(self):
|
|
runner_obj = constant.ConstantScenarioRunner(self.task, self.config)
|
|
|
|
runner_obj._run_scenario(
|
|
fakes.FakeScenario, "do_it", self.context, self.args)
|
|
self.assertEqual(self.config["times"], len(runner_obj.result_queue))
|
|
for result_batch in runner_obj.result_queue:
|
|
for result in result_batch:
|
|
self.assertIsNotNone(result)
|
|
|
|
def test__run_scenario_exception(self):
|
|
runner_obj = constant.ConstantScenarioRunner(self.task, self.config)
|
|
|
|
runner_obj._run_scenario(fakes.FakeScenario, "something_went_wrong",
|
|
self.context, self.args)
|
|
self.assertEqual(self.config["times"], len(runner_obj.result_queue))
|
|
for result_batch in runner_obj.result_queue:
|
|
for result in result_batch:
|
|
self.assertIsNotNone(result)
|
|
self.assertIn("error", runner_obj.result_queue[0][0])
|
|
|
|
def test__run_scenario_aborted(self):
|
|
runner_obj = constant.ConstantScenarioRunner(self.task, self.config)
|
|
|
|
runner_obj.abort()
|
|
runner_obj._run_scenario(fakes.FakeScenario, "do_it", self.context,
|
|
self.args)
|
|
self.assertEqual(0, len(runner_obj.result_queue))
|
|
|
|
@mock.patch(RUNNERS + "constant.multiprocessing.Queue")
|
|
@mock.patch(RUNNERS + "constant.multiprocessing.cpu_count")
|
|
@mock.patch(RUNNERS + "constant.ConstantScenarioRunner._log_debug_info")
|
|
@mock.patch(
|
|
RUNNERS + "constant.ConstantScenarioRunner._create_process_pool")
|
|
@mock.patch(RUNNERS + "constant.ConstantScenarioRunner._join_processes")
|
|
def test_that_cpu_count_is_adjusted_properly(
|
|
self,
|
|
mock__join_processes,
|
|
mock__create_process_pool,
|
|
mock__log_debug_info,
|
|
mock_cpu_count, mock_queue):
|
|
|
|
samples = [
|
|
{
|
|
"input": {"times": 20, "concurrency": 20, "type": "constant",
|
|
"max_cpu_count": 1},
|
|
"real_cpu": 2,
|
|
"expected": {
|
|
# max_cpu_used equals to min(max_cpu_count, real_cpu)
|
|
"max_cpu_used": 1,
|
|
# processes_to_start equals to
|
|
# min(max_cpu_used, times, concurrency))
|
|
"processes_to_start": 1,
|
|
"concurrency_per_worker": 20,
|
|
"concurrency_overhead": 0,
|
|
}
|
|
},
|
|
{
|
|
"input": {"times": 20, "concurrency": 15, "type": "constant",
|
|
"max_cpu_count": 3},
|
|
"real_cpu": 2,
|
|
"expected": {
|
|
"max_cpu_used": 2,
|
|
"processes_to_start": 2,
|
|
"concurrency_per_worker": 7,
|
|
"concurrency_overhead": 1,
|
|
}
|
|
},
|
|
{
|
|
"input": {"times": 20, "concurrency": 1, "type": "constant",
|
|
"max_cpu_count": 3},
|
|
"real_cpu": 2,
|
|
"expected": {
|
|
"max_cpu_used": 2,
|
|
"processes_to_start": 1,
|
|
"concurrency_per_worker": 1,
|
|
"concurrency_overhead": 0,
|
|
}
|
|
},
|
|
{
|
|
"input": {"times": 2, "concurrency": 5, "type": "constant",
|
|
"max_cpu_count": 4},
|
|
"real_cpu": 4,
|
|
"expected": {
|
|
"max_cpu_used": 4,
|
|
"processes_to_start": 2,
|
|
"concurrency_per_worker": 2,
|
|
"concurrency_overhead": 1,
|
|
}
|
|
}
|
|
]
|
|
|
|
for sample in samples:
|
|
mock__log_debug_info.reset_mock()
|
|
mock_cpu_count.reset_mock()
|
|
mock__create_process_pool.reset_mock()
|
|
mock__join_processes.reset_mock()
|
|
mock_queue.reset_mock()
|
|
|
|
mock_cpu_count.return_value = sample["real_cpu"]
|
|
|
|
runner_obj = constant.ConstantScenarioRunner(self.task,
|
|
sample["input"])
|
|
|
|
runner_obj._run_scenario(fakes.FakeScenario, "do_it", self.context,
|
|
self.args)
|
|
|
|
mock_cpu_count.assert_called_once_with()
|
|
mock__log_debug_info.assert_called_once_with(
|
|
times=sample["input"]["times"],
|
|
concurrency=sample["input"]["concurrency"],
|
|
timeout=0,
|
|
max_cpu_used=sample["expected"]["max_cpu_used"],
|
|
processes_to_start=sample["expected"]["processes_to_start"],
|
|
concurrency_per_worker=(
|
|
sample["expected"]["concurrency_per_worker"]),
|
|
concurrency_overhead=(
|
|
sample["expected"]["concurrency_overhead"]))
|
|
args, kwargs = mock__create_process_pool.call_args
|
|
self.assertIn(sample["expected"]["processes_to_start"], args)
|
|
self.assertIn(constant._worker_process, args)
|
|
mock__join_processes.assert_called_once_with(
|
|
mock__create_process_pool.return_value,
|
|
mock_queue.return_value, mock_queue.return_value)
|
|
|
|
def test_abort(self):
|
|
runner_obj = constant.ConstantScenarioRunner(self.task, self.config)
|
|
self.assertFalse(runner_obj.aborted.is_set())
|
|
runner_obj.abort()
|
|
self.assertTrue(runner_obj.aborted.is_set())
|
|
|
|
|
|
@ddt.ddt
|
|
class ConstantForDurationScenarioRunnerTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(ConstantForDurationScenarioRunnerTestCase, self).setUp()
|
|
self.config = {"duration": 0, "concurrency": 2,
|
|
"timeout": 2, "type": "constant_for_duration"}
|
|
self.context = fakes.FakeContext({"task": {"uuid": "uuid"}}).context
|
|
self.context["iteration"] = 14
|
|
self.args = {"a": 1}
|
|
self.task = mock.MagicMock()
|
|
|
|
@ddt.data(({"duration": 0,
|
|
"concurrency": 2,
|
|
"timeout": 2}, True),
|
|
({"foo": "bar"}, False))
|
|
@ddt.unpack
|
|
def test_validate(self, config, valid):
|
|
results = runner.ScenarioRunner.validate(
|
|
"constant_for_duration", None, None, config)
|
|
if valid:
|
|
self.assertEqual([], results)
|
|
else:
|
|
self.assertGreater(len(results), 0)
|
|
|
|
def test_run_scenario_constantly_for_duration(self):
|
|
runner_obj = constant.ConstantForDurationScenarioRunner(
|
|
mock.MagicMock(), self.config)
|
|
|
|
runner_obj._run_scenario(fakes.FakeScenario, "do_it",
|
|
self.context, self.args)
|
|
# NOTE(mmorais/msimonin): when duration is 0, scenario executes exactly
|
|
# 1 time per unit of parrallelism
|
|
expected_times = self.config["concurrency"]
|
|
self.assertEqual(expected_times, len(runner_obj.result_queue))
|
|
for result_batch in runner_obj.result_queue:
|
|
for result in result_batch:
|
|
self.assertIsNotNone(result)
|
|
|
|
def test_run_scenario_constantly_for_duration_exception(self):
|
|
runner_obj = constant.ConstantForDurationScenarioRunner(
|
|
mock.MagicMock(), self.config)
|
|
|
|
runner_obj._run_scenario(fakes.FakeScenario, "something_went_wrong",
|
|
self.context, self.args)
|
|
# NOTE(mmorais/msimonin): when duration is 0, scenario executes exactly
|
|
# 1 time per unit of parrallelism
|
|
expected_times = self.config["concurrency"]
|
|
self.assertEqual(expected_times, len(runner_obj.result_queue))
|
|
for result_batch in runner_obj.result_queue:
|
|
for result in result_batch:
|
|
self.assertIsNotNone(result)
|
|
self.assertIn("error", runner_obj.result_queue[0][0])
|
|
|
|
def test_run_scenario_constantly_for_duration_timeout(self):
|
|
runner_obj = constant.ConstantForDurationScenarioRunner(
|
|
mock.MagicMock(), self.config)
|
|
|
|
runner_obj._run_scenario(fakes.FakeScenario, "raise_timeout",
|
|
self.context, self.args)
|
|
# NOTE(mmorais/msimonin): when duration is 0, scenario executes exactly
|
|
# 1 time per unit of parrallelism
|
|
expected_times = self.config["concurrency"]
|
|
self.assertEqual(expected_times, len(runner_obj.result_queue))
|
|
for result_batch in runner_obj.result_queue:
|
|
for result in result_batch:
|
|
self.assertIsNotNone(result)
|
|
self.assertIn("error", runner_obj.result_queue[0][0])
|
|
|
|
def test__run_scenario_constantly_aborted(self):
|
|
runner_obj = constant.ConstantForDurationScenarioRunner(self.task,
|
|
self.config)
|
|
|
|
runner_obj.abort()
|
|
runner_obj._run_scenario(fakes.FakeScenario, "do_it",
|
|
self.context, self.args)
|
|
self.assertEqual(0, len(runner_obj.result_queue))
|
|
|
|
def test_abort(self):
|
|
runner_obj = constant.ConstantForDurationScenarioRunner(None,
|
|
self.config)
|
|
self.assertFalse(runner_obj.aborted.is_set())
|
|
runner_obj.abort()
|
|
self.assertTrue(runner_obj.aborted.is_set())
|