Make default Planner generic to handle new action
Currently we have to amend the code of the Planner for each new added Watcher Action. This patch set will modifying the DefaultPlanner class and leverage the plugins-parameters blueprint so that we can configure weights via the configuration file. Documentation and unittest also be update in this patch. Co-Authored-By: Vincent Françoise <vincent.francoise@b-com.com> Change-Id: Ib794488fcafd1e153a7d7b1f7253686136501872 blueprint: configurable-weights-default-planner
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
|
||||
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
|
||||
==============================
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from watcher._i18n import _LW
|
||||
@@ -30,18 +31,29 @@ LOG = log.getLogger(__name__)
|
||||
class DefaultPlanner(base.BasePlanner):
|
||||
"""Default planner implementation
|
||||
|
||||
This implementation comes with basic rules with a fixed set of action types
|
||||
that are weighted. An action having a lower weight will be scheduled before
|
||||
the other ones.
|
||||
This implementation comes with basic rules with a set of action types that
|
||||
are weighted. An action having a lower weight will be scheduled before the
|
||||
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,
|
||||
'sleep': 1,
|
||||
'change_nova_service_state': 2,
|
||||
'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,
|
||||
action_plan_id,
|
||||
action_type,
|
||||
@@ -59,6 +71,7 @@ class DefaultPlanner(base.BasePlanner):
|
||||
|
||||
def schedule(self, context, audit_id, solution):
|
||||
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)
|
||||
|
||||
actions = list(solution.actions)
|
||||
@@ -68,7 +81,7 @@ class DefaultPlanner(base.BasePlanner):
|
||||
action_plan_id=action_plan.id,
|
||||
action_type=action.get('action_type'),
|
||||
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))
|
||||
|
||||
self._create_efficacy_indicators(
|
||||
|
||||
@@ -50,6 +50,7 @@ class TestCase(BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestCase, self).setUp()
|
||||
self.useFixture(conf_fixture.ConfReloadFixture())
|
||||
self.app = testing.load_test_app(os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'config.py'
|
||||
|
||||
@@ -39,3 +39,22 @@ class ConfFixture(fixtures.Fixture):
|
||||
self.conf.set_default('verbose', True)
|
||||
config.parse_args([], default_config_files=[])
|
||||
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(
|
||||
pbase.DefaultPlanner, "create_action",
|
||||
wraps=default_planner.create_action) as m_create_action:
|
||||
action_plan = default_planner.schedule(
|
||||
self.context, audit.id, solution
|
||||
)
|
||||
default_planner.config.weights = {'migrate': 3}
|
||||
action_plan = default_planner.schedule(self.context,
|
||||
audit.id, solution)
|
||||
|
||||
self.assertIsNotNone(action_plan.uuid)
|
||||
self.assertEqual(1, m_create_action.call_count)
|
||||
@@ -107,9 +107,9 @@ class TestActionScheduling(base.DbTestCase):
|
||||
with mock.patch.object(
|
||||
pbase.DefaultPlanner, "create_action",
|
||||
wraps=default_planner.create_action) as m_create_action:
|
||||
action_plan = default_planner.schedule(
|
||||
self.context, audit.id, solution
|
||||
)
|
||||
default_planner.config.weights = {'migrate': 3, 'nop': 0}
|
||||
action_plan = default_planner.schedule(self.context,
|
||||
audit.id, solution)
|
||||
self.assertIsNotNone(action_plan.uuid)
|
||||
self.assertEqual(2, m_create_action.call_count)
|
||||
# check order
|
||||
@@ -118,12 +118,45 @@ class TestActionScheduling(base.DbTestCase):
|
||||
self.assertEqual("nop", actions[0].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):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDefaultPlanner, self).setUp()
|
||||
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)
|
||||
|
||||
p = mock.patch.object(db_api.BaseConnection, 'create_action_plan')
|
||||
|
||||
@@ -29,7 +29,7 @@ class TestListOpts(base.TestCase):
|
||||
'api', 'watcher_decision_engine', 'watcher_applier',
|
||||
'watcher_planner', 'nova_client', 'glance_client',
|
||||
'cinder_client', 'ceilometer_client', 'neutron_client',
|
||||
'watcher_clients_auth']
|
||||
'watcher_clients_auth', 'watcher_planners.default']
|
||||
|
||||
def test_run_list_opts(self):
|
||||
expected_sections = self.base_sections
|
||||
|
||||
Reference in New Issue
Block a user