Creating Director and related codes. 33/16433/5
authorlihuan <lihuansse@tongji.edu.cn>
Wed, 6 Jul 2016 09:29:00 +0000 (17:29 +0800)
committerlihuan <lihuansse@tongji.edu.cn>
Fri, 8 Jul 2016 07:01:02 +0000 (15:01 +0800)
Add a new scenario type 'ScenarioGeneral' that support orchestrating general HA test scenarios.
Director, ActionPlayer and RollbackPlayer are uesed to execute the test scenario (or test flow).

JIRA: YARDSTICK-288

Change-Id: Ied2ccd4712f3c3efde6771bfa4538c1e9e137c11
Signed-off-by: lihuan <lihuansse@tongji.edu.cn>
tests/unit/benchmark/scenarios/availability/test_director.py [new file with mode: 0644]
tests/unit/benchmark/scenarios/availability/test_scenario_general.py [new file with mode: 0644]
yardstick/benchmark/scenarios/availability/__init__.py
yardstick/benchmark/scenarios/availability/actionplayers.py [new file with mode: 0644]
yardstick/benchmark/scenarios/availability/actionrollbackers.py [new file with mode: 0644]
yardstick/benchmark/scenarios/availability/director.py [new file with mode: 0644]
yardstick/benchmark/scenarios/availability/scenario_general.py [new file with mode: 0644]

diff --git a/tests/unit/benchmark/scenarios/availability/test_director.py b/tests/unit/benchmark/scenarios/availability/test_director.py
new file mode 100644 (file)
index 0000000..887ddd6
--- /dev/null
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+
+##############################################################################
+# Copyright (c) 2016 Huan Li and others
+# lihuansse@tongji.edu.cn
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+# Unittest for yardstick.benchmark.scenarios.availability.director
+
+import mock
+import unittest
+
+from yardstick.benchmark.scenarios.availability.director import Director
+from yardstick.benchmark.scenarios.availability import  actionplayers
+
+
+@mock.patch('yardstick.benchmark.scenarios.availability.director.basemonitor')
+@mock.patch('yardstick.benchmark.scenarios.availability.director.baseattacker')
+@mock.patch('yardstick.benchmark.scenarios.availability.director.baseoperation')
+@mock.patch('yardstick.benchmark.scenarios.availability.director.baseresultchecker')
+class DirectorTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.scenario_cfg = {
+            'type': "general_scenario",
+            'options': {
+                'attackers':[{
+                    'fault_type': "general-attacker",
+                    'key': "kill-process"}],
+                'monitors': [{
+                    'monitor_type': "general-monitor",
+                    'key': "service_status"}],
+                'operations': [{
+                    'operation_type': 'general-operation',
+                    'key' : 'service_status'}],
+                'resultCheckers': [{
+                    'checker_type': 'general-result-checker',
+                    'key' : 'process-checker',}],
+                'steps':[
+                    {
+                        'actionKey': "service_status",
+                        'actionType': "operation",
+                        'index': 1},
+                    {
+                        'actionKey': "kill-process",
+                        'actionType': "attacker",
+                        'index': 2},
+                    {
+                        'actionKey': "process-checker",
+                        'actionType': "resultchecker",
+                        'index': 3},
+                    {
+                        'actionKey': "service_status",
+                        'actionType': "monitor",
+                        'index': 4},
+                    ]
+            }
+        }
+        host = {
+            "ip": "10.20.0.5",
+            "user": "root",
+            "key_filename": "/root/.ssh/id_rsa"
+        }
+        self.ctx = {"nodes": {"node1": host}}
+
+    def test_director_all_successful(self, mock_checer, mock_opertion, mock_attacker, mock_monitor):
+        ins = Director(self.scenario_cfg, self.ctx)
+        opertion_action = ins.createActionPlayer("operation", "service_status")
+        attacker_action = ins.createActionPlayer("attacker", "kill-process")
+        checker_action = ins.createActionPlayer("resultchecker", "process-checker")
+        monitor_action = ins.createActionPlayer("monitor", "service_status")
+
+        opertion_rollback = ins.createActionRollbacker("operation", "service_status")
+        attacker_rollback = ins.createActionRollbacker("attacker", "kill-process")
+        ins.executionSteps.append(opertion_rollback)
+        ins.executionSteps.append(attacker_rollback)
+
+        opertion_action.action()
+        attacker_action.action()
+        checker_action.action()
+        monitor_action.action()
+
+        attacker_rollback.rollback()
+        opertion_rollback.rollback()
+
+        ins.stopMonitors()
+        ins.verify()
+        ins.knockoff()
+
+    def test_director_get_wrong_item(self, mock_checer, mock_opertion, mock_attacker, mock_monitor):
+        ins = Director(self.scenario_cfg, self.ctx)
+        ins.createActionPlayer("wrong_type", "wrong_key")
+        ins.createActionRollbacker("wrong_type", "wrong_key")
+
+
+
+
+
+
diff --git a/tests/unit/benchmark/scenarios/availability/test_scenario_general.py b/tests/unit/benchmark/scenarios/availability/test_scenario_general.py
new file mode 100644 (file)
index 0000000..c17edea
--- /dev/null
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+
+##############################################################################
+# Copyright (c) 2016 Huan Li and others
+# lihuansse@tongji.edu.cn
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+# Unittest for yardstick.benchmark.scenarios.availability.scenario_general
+
+import mock
+import unittest
+
+from yardstick.benchmark.scenarios.availability.scenario_general import ScenarioGeneral
+
+
+@mock.patch('yardstick.benchmark.scenarios.availability.scenario_general.Director')
+class ScenarioGeneralTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self.scenario_cfg = {
+            'type': "general_scenario",
+            'options': {
+                'attackers':[{
+                    'fault_type': "general-attacker",
+                    'key': "kill-process"}],
+                'monitors': [{
+                    'monitor_type': "general-monitor",
+                    'key': "service_status"}],
+                'steps':[
+                    {
+                        'actionKey': "kill-process",
+                        'actionType': "attacker",
+                        'index': 1},
+                    {
+                        'actionKey': "service_status",
+                        'actionType': "monitor",
+                        'index': 2}]
+            }
+        }
+
+    def test_scenario_general_all_successful(self, mock_director):
+        ins = ScenarioGeneral(self.scenario_cfg, None)
+        ins.setup()
+        ins.run(None)
+        ins.teardown()
+
+    def test_scenario_general_exception(self, mock_director):
+        ins = ScenarioGeneral(self.scenario_cfg, None)
+        mock_obj = mock.Mock()
+        mock_obj.createActionPlayer.side_effect = KeyError('Wrong')
+        ins.director = mock_obj
+        ins.run(None)
+        ins.teardown()
+
+    def test_scenario_general_case_fail(self, mock_director):
+        ins = ScenarioGeneral(self.scenario_cfg, None)
+        mock_obj = mock.Mock()
+        mock_obj.verify.return_value = False
+        ins.director = mock_obj
+        ins.run(None)
+        ins.teardown()
\ No newline at end of file
index fdad0fe..c3b3aae 100755 (executable)
@@ -12,7 +12,6 @@ class ActionType:
     ATTACKER = "attacker"
     MONITOR = "monitor"
     RESULTCHECKER = "resultchecker"
-    RESULTCOMPARER = "comparer"
     OPERATION = "operation"
 
 
diff --git a/yardstick/benchmark/scenarios/availability/actionplayers.py b/yardstick/benchmark/scenarios/availability/actionplayers.py
new file mode 100644 (file)
index 0000000..4206264
--- /dev/null
@@ -0,0 +1,54 @@
+##############################################################################
+# Copyright (c) 2016 Juan Qiu and others
+# juan_ qiu@tongji.edu.cn
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+class ActionPlayer(object):
+    """
+    Abstract the action functions of attacker,
+    monitor, operation, resultchecker and mybe others in future
+    """
+
+    def action(self):
+        pass
+
+
+class AttackerPlayer(ActionPlayer):
+
+    def __init__(self, attacker):
+        self.underlyingAttacker = attacker
+
+    def action(self):
+        self.underlyingAttacker.inject_fault()
+
+
+class OperationPlayer(ActionPlayer):
+
+    def __init__(self, operation):
+        self.underlyingOperation = operation
+
+    def action(self):
+        self.underlyingOperation.run()
+
+
+class MonitorPlayer(ActionPlayer):
+
+    def __init__(self, monitor):
+        self.underlyingmonitor = monitor
+
+    def action(self):
+        self.underlyingmonitor.start_monitor()
+
+
+class ResultCheckerPlayer(ActionPlayer):
+
+    def __init__(self, resultChecker):
+        self.underlyingresultChecker = resultChecker
+
+    def action(self):
+        self.underlyingresultChecker.verify()
diff --git a/yardstick/benchmark/scenarios/availability/actionrollbackers.py b/yardstick/benchmark/scenarios/availability/actionrollbackers.py
new file mode 100644 (file)
index 0000000..4b732a1
--- /dev/null
@@ -0,0 +1,45 @@
+##############################################################################
+# Copyright (c) 2016 Juan Qiu and others
+# juan_ qiu@tongji.edu.cn
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import logging
+
+LOG = logging.getLogger(__name__)
+
+
+class ActionRollbacker(object):
+    """
+    Abstract the rollback functions of attacker, operation
+    and mybe others in future
+    """
+
+    def rollback(self):
+        pass
+
+
+class AttackerRollbacker(ActionRollbacker):
+
+    def __init__(self, attacker):
+        self.underlyingAttacker = attacker
+
+    def rollback(self):
+        LOG.debug(
+            "\033[93m recovering attacker %s \033[0m"
+            % (self.underlyingAttacker.key))
+        self.underlyingAttacker.recover()
+
+
+class OperationRollbacker(ActionRollbacker):
+
+    def __init__(self, operation):
+        self.underlyingOperation = operation
+
+    def rollback(self):
+        LOG.debug(
+            "\033[93m rollback operation %s \033[0m"
+            % (self.underlyingOperation.key))
+        self.underlyingOperation.rollback()
diff --git a/yardstick/benchmark/scenarios/availability/director.py b/yardstick/benchmark/scenarios/availability/director.py
new file mode 100644 (file)
index 0000000..267933d
--- /dev/null
@@ -0,0 +1,106 @@
+##############################################################################
+# Copyright (c) 2016 Juan Qiu and others
+# juan_ qiu@tongji.edu.cn
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import logging
+
+from yardstick.benchmark.scenarios.availability.monitor import basemonitor
+from yardstick.benchmark.scenarios.availability.attacker import baseattacker
+from yardstick.benchmark.scenarios.availability.operation import baseoperation
+from yardstick.benchmark.scenarios.availability.result_checker \
+    import baseresultchecker
+from yardstick.benchmark.scenarios.availability import ActionType
+from yardstick.benchmark.scenarios.availability import actionplayers
+from yardstick.benchmark.scenarios.availability import actionrollbackers
+
+LOG = logging.getLogger(__name__)
+
+
+class Director(object):
+    """
+    Director is used to direct a test scenaio
+    including the creation of  action players, test result verification
+    and rollback of actions.
+    """
+
+    def __init__(self, scenario_cfg, context_cfg):
+
+        # A stack store Rollbacker that will be called after
+        # all actionplayers finish.
+        self.executionSteps = []
+
+        self.scenario_cfg = scenario_cfg
+        self.context_cfg = context_cfg
+        nodes = self.context_cfg.get("nodes", None)
+        # setup attackers
+        if "attackers" in self.scenario_cfg["options"]:
+            LOG.debug("start init attackers...")
+            attacker_cfgs = self.scenario_cfg["options"]["attackers"]
+            self.attackerMgr = baseattacker.AttackerMgr()
+            self.attackerMgr.init_attackers(attacker_cfgs, nodes)
+        # setup monitors
+        if "monitors" in self.scenario_cfg["options"]:
+            LOG.debug("start init monitors...")
+            monitor_cfgs = self.scenario_cfg["options"]["monitors"]
+            self.monitorMgr = basemonitor.MonitorMgr()
+            self.monitorMgr.init_monitors(monitor_cfgs, nodes)
+        # setup operations
+        if "operations" in self.scenario_cfg["options"]:
+            LOG.debug("start init operations...")
+            operation_cfgs = self.scenario_cfg["options"]["operations"]
+            self.operationMgr = baseoperation.OperationMgr()
+            self.operationMgr.init_operations(operation_cfgs, nodes)
+        # setup result checker
+        if "resultCheckers" in self.scenario_cfg["options"]:
+            LOG.debug("start init resultCheckers...")
+            result_check_cfgs = self.scenario_cfg["options"]["resultCheckers"]
+            self.resultCheckerMgr = baseresultchecker.ResultCheckerMgr()
+            self.resultCheckerMgr.init_ResultChecker(result_check_cfgs, nodes)
+
+    def createActionPlayer(self, type, key):
+        LOG.debug(
+            "the type of current action is %s, the key is %s" % (type, key))
+        if type == ActionType.ATTACKER:
+            return actionplayers.AttackerPlayer(self.attackerMgr[key])
+        if type == ActionType.MONITOR:
+            return actionplayers.MonitorPlayer(self.monitorMgr[key])
+        if type == ActionType.RESULTCHECKER:
+            return actionplayers.ResultCheckerPlayer(
+                self.resultCheckerMgr[key])
+        if type == ActionType.OPERATION:
+            return actionplayers.OperationPlayer(self.operationMgr[key])
+        LOG.debug("something run when creatactionplayer")
+
+    def createActionRollbacker(self, type, key):
+        LOG.debug(
+            "the type of current action is %s, the key is %s" % (type, key))
+        if type == ActionType.ATTACKER:
+            return actionrollbackers.AttackerRollbacker(self.attackerMgr[key])
+        if type == ActionType.OPERATION:
+            return actionrollbackers.OperationRollbacker(
+                self.operationMgr[key])
+        LOG.debug("no rollbacker created for %s" % (key))
+
+    def verify(self):
+        result = True
+        if hasattr(self, 'monitorMgr'):
+            result &= self.monitorMgr.verify_SLA()
+        if hasattr(self, 'resultCheckerMgr'):
+            result &= self.resultCheckerMgr.verify()
+        if result:
+            LOG.debug("monitors are passed")
+        return result
+
+    def stopMonitors(self):
+        if "monitors" in self.scenario_cfg["options"]:
+            self.monitorMgr.wait_monitors()
+
+    def knockoff(self):
+        LOG.debug("knock off ....")
+        while self.executionSteps:
+            singleStep = self.executionSteps.pop()
+            singleStep.rollback()
diff --git a/yardstick/benchmark/scenarios/availability/scenario_general.py b/yardstick/benchmark/scenarios/availability/scenario_general.py
new file mode 100644 (file)
index 0000000..0a128aa
--- /dev/null
@@ -0,0 +1,68 @@
+##############################################################################
+# Copyright (c) 2016 Juan Qiu and others
+# juan_ qiu@tongji.edu.cn
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+import logging
+import traceback
+
+from yardstick.benchmark.scenarios import base
+from yardstick.benchmark.scenarios.availability.director import Director
+
+LOG = logging.getLogger(__name__)
+
+
+class ScenarioGeneral(base.Scenario):
+    """Support orchestrating general HA test scenarios."""
+
+    __scenario_type__ = "GeneralHA"
+
+    def __init__(self, scenario_cfg, context_cfg):
+        LOG.debug(
+            "scenario_cfg:%s context_cfg:%s" % (scenario_cfg, context_cfg))
+        self.scenario_cfg = scenario_cfg
+        self.context_cfg = context_cfg
+
+    def setup(self):
+        self.director = Director(self.scenario_cfg, self.context_cfg)
+
+    def run(self, args):
+        steps = self.scenario_cfg["options"]["steps"]
+        orderedSteps = sorted(steps, key=lambda x: x['index'])
+        for step in orderedSteps:
+            LOG.debug(
+                "\033[94m running step: {0} .... \033[0m"
+                .format(orderedSteps.index(step)+1))
+            try:
+                actionPlayer = self.director.createActionPlayer(
+                    step['actionType'], step['actionKey'])
+                actionPlayer.action()
+                actionRollbacker = self.director.createActionRollbacker(
+                    step['actionType'], step['actionKey'])
+                if actionRollbacker:
+                    self.director.executionSteps.append(actionRollbacker)
+            except Exception, e:
+                LOG.debug(e.message)
+                traceback.print_exc()
+                LOG.debug(
+                    "\033[91m exception when running step: {0} .... \033[0m"
+                    .format(orderedSteps.index(step)))
+                break
+            finally:
+                pass
+
+        self.director.stopMonitors()
+        if self.director.verify():
+            LOG.debug(
+                "\033[92m congratulations, "
+                "the test cases scenario is pass! \033[0m")
+        else:
+            LOG.debug(
+                "\033[91m aoh,the test cases scenario failed,"
+                "please check the detail debug information! \033[0m")
+
+    def teardown(self):
+        self.director.knockoff()