Merge "Make default Planner generic to handle new action"
This commit is contained in:
@@ -205,6 +205,9 @@ By doing so, your action will be saved within the Watcher Database, ready to be
|
|||||||
processed by the planner for creating an action plan which can then be executed
|
processed by the planner for creating an action plan which can then be executed
|
||||||
by the Watcher Applier via its workflow engine.
|
by the Watcher Applier via its workflow engine.
|
||||||
|
|
||||||
|
At the last, remember to add the action into the weights in ``watcher.conf``,
|
||||||
|
otherwise you will get an error when the action be referenced in a strategy.
|
||||||
|
|
||||||
|
|
||||||
Scheduling of an action plugin
|
Scheduling of an action plugin
|
||||||
==============================
|
==============================
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
from watcher._i18n import _LW
|
from watcher._i18n import _LW
|
||||||
@@ -30,18 +31,29 @@ LOG = log.getLogger(__name__)
|
|||||||
class DefaultPlanner(base.BasePlanner):
|
class DefaultPlanner(base.BasePlanner):
|
||||||
"""Default planner implementation
|
"""Default planner implementation
|
||||||
|
|
||||||
This implementation comes with basic rules with a fixed set of action types
|
This implementation comes with basic rules with a set of action types that
|
||||||
that are weighted. An action having a lower weight will be scheduled before
|
are weighted. An action having a lower weight will be scheduled before the
|
||||||
the other ones.
|
other ones. The set of action types can be specified by 'weights' in the
|
||||||
|
``watcher.conf``. You need to associate a different weight to all available
|
||||||
|
actions into the configuration file, otherwise you will get an error when
|
||||||
|
the new action will be referenced in the solution produced by a strategy.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
priorities = {
|
weights_dict = {
|
||||||
'nop': 0,
|
'nop': 0,
|
||||||
'sleep': 1,
|
'sleep': 1,
|
||||||
'change_nova_service_state': 2,
|
'change_nova_service_state': 2,
|
||||||
'migrate': 3,
|
'migrate': 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_config_opts(cls):
|
||||||
|
return [cfg.DictOpt(
|
||||||
|
'weights',
|
||||||
|
help="These weights are used to schedule the actions",
|
||||||
|
default=cls.weights_dict),
|
||||||
|
]
|
||||||
|
|
||||||
def create_action(self,
|
def create_action(self,
|
||||||
action_plan_id,
|
action_plan_id,
|
||||||
action_type,
|
action_type,
|
||||||
@@ -59,6 +71,7 @@ class DefaultPlanner(base.BasePlanner):
|
|||||||
|
|
||||||
def schedule(self, context, audit_id, solution):
|
def schedule(self, context, audit_id, solution):
|
||||||
LOG.debug('Create an action plan for the audit uuid: %s ', audit_id)
|
LOG.debug('Create an action plan for the audit uuid: %s ', audit_id)
|
||||||
|
priorities = self.config.weights
|
||||||
action_plan = self._create_action_plan(context, audit_id, solution)
|
action_plan = self._create_action_plan(context, audit_id, solution)
|
||||||
|
|
||||||
actions = list(solution.actions)
|
actions = list(solution.actions)
|
||||||
@@ -68,7 +81,7 @@ class DefaultPlanner(base.BasePlanner):
|
|||||||
action_plan_id=action_plan.id,
|
action_plan_id=action_plan.id,
|
||||||
action_type=action.get('action_type'),
|
action_type=action.get('action_type'),
|
||||||
input_parameters=action.get('input_parameters'))
|
input_parameters=action.get('input_parameters'))
|
||||||
to_schedule.append((self.priorities[action.get('action_type')],
|
to_schedule.append((priorities[action.get('action_type')],
|
||||||
json_action))
|
json_action))
|
||||||
|
|
||||||
self._create_efficacy_indicators(
|
self._create_efficacy_indicators(
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class TestCase(BaseTestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestCase, self).setUp()
|
super(TestCase, self).setUp()
|
||||||
|
self.useFixture(conf_fixture.ConfReloadFixture())
|
||||||
self.app = testing.load_test_app(os.path.join(
|
self.app = testing.load_test_app(os.path.join(
|
||||||
os.path.dirname(__file__),
|
os.path.dirname(__file__),
|
||||||
'config.py'
|
'config.py'
|
||||||
|
|||||||
@@ -39,3 +39,22 @@ class ConfFixture(fixtures.Fixture):
|
|||||||
self.conf.set_default('verbose', True)
|
self.conf.set_default('verbose', True)
|
||||||
config.parse_args([], default_config_files=[])
|
config.parse_args([], default_config_files=[])
|
||||||
self.addCleanup(self.conf.reset)
|
self.addCleanup(self.conf.reset)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfReloadFixture(ConfFixture):
|
||||||
|
"""Fixture to manage reloads of conf settings."""
|
||||||
|
|
||||||
|
def __init__(self, conf=cfg.CONF):
|
||||||
|
self.conf = conf
|
||||||
|
self._original_parse_cli_opts = self.conf._parse_cli_opts
|
||||||
|
|
||||||
|
def _fake_parser(self, *args, **kw):
|
||||||
|
return cfg.ConfigOpts._parse_cli_opts(self.conf, [])
|
||||||
|
|
||||||
|
def _restore_parser(self):
|
||||||
|
self.conf._parse_cli_opts = self._original_parse_cli_opts
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ConfReloadFixture, self).setUp()
|
||||||
|
self.conf._parse_cli_opts = self._fake_parser
|
||||||
|
self.addCleanup(self._restore_parser)
|
||||||
|
|||||||
@@ -76,9 +76,9 @@ class TestActionScheduling(base.DbTestCase):
|
|||||||
with mock.patch.object(
|
with mock.patch.object(
|
||||||
pbase.DefaultPlanner, "create_action",
|
pbase.DefaultPlanner, "create_action",
|
||||||
wraps=default_planner.create_action) as m_create_action:
|
wraps=default_planner.create_action) as m_create_action:
|
||||||
action_plan = default_planner.schedule(
|
default_planner.config.weights = {'migrate': 3}
|
||||||
self.context, audit.id, solution
|
action_plan = default_planner.schedule(self.context,
|
||||||
)
|
audit.id, solution)
|
||||||
|
|
||||||
self.assertIsNotNone(action_plan.uuid)
|
self.assertIsNotNone(action_plan.uuid)
|
||||||
self.assertEqual(1, m_create_action.call_count)
|
self.assertEqual(1, m_create_action.call_count)
|
||||||
@@ -107,9 +107,9 @@ class TestActionScheduling(base.DbTestCase):
|
|||||||
with mock.patch.object(
|
with mock.patch.object(
|
||||||
pbase.DefaultPlanner, "create_action",
|
pbase.DefaultPlanner, "create_action",
|
||||||
wraps=default_planner.create_action) as m_create_action:
|
wraps=default_planner.create_action) as m_create_action:
|
||||||
action_plan = default_planner.schedule(
|
default_planner.config.weights = {'migrate': 3, 'nop': 0}
|
||||||
self.context, audit.id, solution
|
action_plan = default_planner.schedule(self.context,
|
||||||
)
|
audit.id, solution)
|
||||||
self.assertIsNotNone(action_plan.uuid)
|
self.assertIsNotNone(action_plan.uuid)
|
||||||
self.assertEqual(2, m_create_action.call_count)
|
self.assertEqual(2, m_create_action.call_count)
|
||||||
# check order
|
# check order
|
||||||
@@ -118,12 +118,45 @@ class TestActionScheduling(base.DbTestCase):
|
|||||||
self.assertEqual("nop", actions[0].action_type)
|
self.assertEqual("nop", actions[0].action_type)
|
||||||
self.assertEqual("migrate", actions[1].action_type)
|
self.assertEqual("migrate", actions[1].action_type)
|
||||||
|
|
||||||
|
def test_schedule_actions_with_unknown_action(self):
|
||||||
|
default_planner = pbase.DefaultPlanner(mock.Mock())
|
||||||
|
audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
|
||||||
|
solution = dsol.DefaultSolution(
|
||||||
|
goal=mock.Mock(), strategy=mock.Mock())
|
||||||
|
|
||||||
|
parameters = {
|
||||||
|
"src_uuid_hypervisor": "server1",
|
||||||
|
"dst_uuid_hypervisor": "server2",
|
||||||
|
}
|
||||||
|
solution.add_action(action_type="migrate",
|
||||||
|
resource_id="b199db0c-1408-4d52-b5a5-5ca14de0ff36",
|
||||||
|
input_parameters=parameters)
|
||||||
|
|
||||||
|
solution.add_action(action_type="new_action_type",
|
||||||
|
resource_id="",
|
||||||
|
input_parameters={})
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
pbase.DefaultPlanner, "create_action",
|
||||||
|
wraps=default_planner.create_action) as m_create_action:
|
||||||
|
default_planner.config.weights = {'migrate': 0}
|
||||||
|
self.assertRaises(KeyError, default_planner.schedule,
|
||||||
|
self.context, audit.id, solution)
|
||||||
|
self.assertEqual(2, m_create_action.call_count)
|
||||||
|
|
||||||
|
|
||||||
class TestDefaultPlanner(base.DbTestCase):
|
class TestDefaultPlanner(base.DbTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestDefaultPlanner, self).setUp()
|
super(TestDefaultPlanner, self).setUp()
|
||||||
self.default_planner = pbase.DefaultPlanner(mock.Mock())
|
self.default_planner = pbase.DefaultPlanner(mock.Mock())
|
||||||
|
self.default_planner.config.weights = {
|
||||||
|
'nop': 0,
|
||||||
|
'sleep': 1,
|
||||||
|
'change_nova_service_state': 2,
|
||||||
|
'migrate': 3
|
||||||
|
}
|
||||||
|
|
||||||
obj_utils.create_test_audit_template(self.context)
|
obj_utils.create_test_audit_template(self.context)
|
||||||
|
|
||||||
p = mock.patch.object(db_api.BaseConnection, 'create_action_plan')
|
p = mock.patch.object(db_api.BaseConnection, 'create_action_plan')
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class TestListOpts(base.TestCase):
|
|||||||
'api', 'watcher_decision_engine', 'watcher_applier',
|
'api', 'watcher_decision_engine', 'watcher_applier',
|
||||||
'watcher_planner', 'nova_client', 'glance_client',
|
'watcher_planner', 'nova_client', 'glance_client',
|
||||||
'cinder_client', 'ceilometer_client', 'neutron_client',
|
'cinder_client', 'ceilometer_client', 'neutron_client',
|
||||||
'watcher_clients_auth']
|
'watcher_clients_auth', 'watcher_planners.default']
|
||||||
|
|
||||||
def test_run_list_opts(self):
|
def test_run_list_opts(self):
|
||||||
expected_sections = self.base_sections
|
expected_sections = self.base_sections
|
||||||
|
|||||||
Reference in New Issue
Block a user