Merge "Refactored Strategy selector to select from DB"
This commit is contained in:
@@ -123,7 +123,7 @@ function create_watcher_conf {
|
|||||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
||||||
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST
|
iniset $WATCHER_CONF oslo_messaging_rabbit rabbit_host $RABBIT_HOST
|
||||||
|
|
||||||
iniset $WATCHER_CONF watcher_goals goals "DUMMY:dummy,BASIC_CONSOLIDATION:basic"
|
iniset $WATCHER_CONF watcher_goals goals "DUMMY:dummy,SERVER_CONSOLIDATION:basic"
|
||||||
|
|
||||||
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR
|
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR
|
||||||
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR "watcher_clients_auth"
|
configure_auth_token_middleware $WATCHER_CONF watcher $WATCHER_AUTH_CACHE_DIR "watcher_clients_auth"
|
||||||
|
|||||||
@@ -287,7 +287,11 @@ class MetricCollectorNotDefined(WatcherException):
|
|||||||
|
|
||||||
|
|
||||||
class ClusterStateNotDefined(WatcherException):
|
class ClusterStateNotDefined(WatcherException):
|
||||||
msg_fmt = _("the cluster state is not defined")
|
msg_fmt = _("The cluster state is not defined")
|
||||||
|
|
||||||
|
|
||||||
|
class NoAvailableStrategyForGoal(WatcherException):
|
||||||
|
msg_fmt = _("No strategy could be found to achieve the '%(goal)s' goal.")
|
||||||
|
|
||||||
|
|
||||||
# Model
|
# Model
|
||||||
|
|||||||
@@ -30,26 +30,20 @@ class DefaultStrategyContext(base.BaseStrategyContext):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(DefaultStrategyContext, self).__init__()
|
super(DefaultStrategyContext, self).__init__()
|
||||||
LOG.debug("Initializing Strategy Context")
|
LOG.debug("Initializing Strategy Context")
|
||||||
self._strategy_selector = default.DefaultStrategySelector()
|
|
||||||
self._collector_manager = manager.CollectorManager()
|
self._collector_manager = manager.CollectorManager()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def collector(self):
|
def collector(self):
|
||||||
return self._collector_manager
|
return self._collector_manager
|
||||||
|
|
||||||
@property
|
|
||||||
def strategy_selector(self):
|
|
||||||
return self._strategy_selector
|
|
||||||
|
|
||||||
def execute_strategy(self, audit_uuid, request_context):
|
def execute_strategy(self, audit_uuid, request_context):
|
||||||
audit = objects.Audit.get_by_uuid(request_context, audit_uuid)
|
audit = objects.Audit.get_by_uuid(request_context, audit_uuid)
|
||||||
|
|
||||||
# Retrieve the Audit Template
|
# Retrieve the Audit Template
|
||||||
audit_template = objects.\
|
audit_template = objects.AuditTemplate.get_by_id(
|
||||||
AuditTemplate.get_by_id(request_context, audit.audit_template_id)
|
request_context, audit.audit_template_id)
|
||||||
|
|
||||||
osc = clients.OpenStackClients()
|
osc = clients.OpenStackClients()
|
||||||
|
|
||||||
# todo(jed) retrieve in audit_template parameters (threshold,...)
|
# todo(jed) retrieve in audit_template parameters (threshold,...)
|
||||||
# todo(jed) create ActionPlan
|
# todo(jed) create ActionPlan
|
||||||
collector_manager = self.collector.get_cluster_model_collector(osc=osc)
|
collector_manager = self.collector.get_cluster_model_collector(osc=osc)
|
||||||
@@ -57,8 +51,13 @@ class DefaultStrategyContext(base.BaseStrategyContext):
|
|||||||
# todo(jed) remove call to get_latest_cluster_data_model
|
# todo(jed) remove call to get_latest_cluster_data_model
|
||||||
cluster_data_model = collector_manager.get_latest_cluster_data_model()
|
cluster_data_model = collector_manager.get_latest_cluster_data_model()
|
||||||
|
|
||||||
selected_strategy = self.strategy_selector.define_from_goal(
|
strategy_selector = default.DefaultStrategySelector(
|
||||||
audit_template.goal, osc=osc)
|
goal_name=objects.Goal.get_by_name(
|
||||||
|
request_context, audit_template.goal).name,
|
||||||
|
strategy_name=None,
|
||||||
|
osc=osc)
|
||||||
|
|
||||||
|
selected_strategy = strategy_selector.select()
|
||||||
|
|
||||||
# todo(jed) add parameters and remove cluster_data_model
|
# todo(jed) add parameters and remove cluster_data_model
|
||||||
return selected_strategy.execute(cluster_data_model)
|
return selected_strategy.execute(cluster_data_model)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import six
|
|||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class BaseSelector(object):
|
class BaseSelector(object):
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def define_from_goal(self, goal_name):
|
def select(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|||||||
@@ -44,19 +44,48 @@ CONF.register_opts(WATCHER_GOALS_OPTS, goals_opt_group)
|
|||||||
|
|
||||||
class DefaultStrategySelector(base.BaseSelector):
|
class DefaultStrategySelector(base.BaseSelector):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, goal_name, strategy_name=None, osc=None):
|
||||||
|
"""Default strategy selector
|
||||||
|
|
||||||
|
:param goal_name: Name of the goal
|
||||||
|
:param strategy_name: Name of the strategy
|
||||||
|
:param osc: an OpenStackClients instance
|
||||||
|
"""
|
||||||
super(DefaultStrategySelector, self).__init__()
|
super(DefaultStrategySelector, self).__init__()
|
||||||
|
self.goal_name = goal_name
|
||||||
|
self.strategy_name = strategy_name
|
||||||
|
self.osc = osc
|
||||||
self.strategy_loader = default.DefaultStrategyLoader()
|
self.strategy_loader = default.DefaultStrategyLoader()
|
||||||
|
|
||||||
def define_from_goal(self, goal_name, osc=None):
|
def select(self):
|
||||||
""":param osc: an OpenStackClients instance"""
|
"""Selects a strategy
|
||||||
|
|
||||||
|
:raises: :py:class:`~.LoadingError` if it failed to load a strategy
|
||||||
|
:returns: A :py:class:`~.BaseStrategy` instance
|
||||||
|
"""
|
||||||
strategy_to_load = None
|
strategy_to_load = None
|
||||||
try:
|
try:
|
||||||
strategy_to_load = CONF.watcher_goals.goals[goal_name]
|
if self.strategy_name:
|
||||||
return self.strategy_loader.load(strategy_to_load, osc=osc)
|
strategy_to_load = self.strategy_name
|
||||||
except KeyError as exc:
|
else:
|
||||||
|
available_strategies = self.strategy_loader.list_available()
|
||||||
|
available_strategies_for_goal = list(
|
||||||
|
key for key, strat in available_strategies.items()
|
||||||
|
if strat.get_goal_name() == self.goal_name)
|
||||||
|
|
||||||
|
if not available_strategies_for_goal:
|
||||||
|
raise exception.NoAvailableStrategyForGoal(
|
||||||
|
goal=self.goal_name)
|
||||||
|
|
||||||
|
# TODO(v-francoise): We should do some more work here to select
|
||||||
|
# a strategy out of a given goal instead of just choosing the
|
||||||
|
# 1st one
|
||||||
|
strategy_to_load = available_strategies_for_goal[0]
|
||||||
|
return self.strategy_loader.load(strategy_to_load, osc=self.osc)
|
||||||
|
except exception.NoAvailableStrategyForGoal:
|
||||||
|
raise
|
||||||
|
except Exception as exc:
|
||||||
LOG.exception(exc)
|
LOG.exception(exc)
|
||||||
raise exception.WatcherException(
|
raise exception.LoadingError(
|
||||||
_("Incorrect mapping: could not find "
|
_("Could not load any strategy for goal %(goal)s"),
|
||||||
"associated strategy for '%s'") % goal_name
|
goal=self.goal_name)
|
||||||
)
|
|
||||||
|
|||||||
@@ -28,11 +28,12 @@ from watcher.tests.objects import utils as obj_utils
|
|||||||
class TestDefaultAuditHandler(base.DbTestCase):
|
class TestDefaultAuditHandler(base.DbTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestDefaultAuditHandler, self).setUp()
|
super(TestDefaultAuditHandler, self).setUp()
|
||||||
self.audit_template = obj_utils.create_test_audit_template(
|
obj_utils.create_test_goal(self.context, id=1, name="DUMMY")
|
||||||
|
audit_template = obj_utils.create_test_audit_template(
|
||||||
self.context)
|
self.context)
|
||||||
self.audit = obj_utils.create_test_audit(
|
self.audit = obj_utils.create_test_audit(
|
||||||
self.context,
|
self.context,
|
||||||
audit_template_id=self.audit_template.id)
|
audit_template_id=audit_template.id)
|
||||||
|
|
||||||
@mock.patch.object(manager.CollectorManager, "get_cluster_model_collector")
|
@mock.patch.object(manager.CollectorManager, "get_cluster_model_collector")
|
||||||
def test_trigger_audit_without_errors(self, mock_collector):
|
def test_trigger_audit_without_errors(self, mock_collector):
|
||||||
|
|||||||
@@ -31,19 +31,19 @@ from watcher.tests.objects import utils as obj_utils
|
|||||||
class TestStrategyContext(base.DbTestCase):
|
class TestStrategyContext(base.DbTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestStrategyContext, self).setUp()
|
super(TestStrategyContext, self).setUp()
|
||||||
self.audit_template = obj_utils. \
|
obj_utils.create_test_goal(self.context, id=1, name="DUMMY")
|
||||||
create_test_audit_template(self.context)
|
audit_template = obj_utils.create_test_audit_template(
|
||||||
self.audit = obj_utils. \
|
self.context)
|
||||||
create_test_audit(self.context,
|
self.audit = obj_utils.create_test_audit(
|
||||||
audit_template_id=self.audit_template.id)
|
self.context, audit_template_id=audit_template.id)
|
||||||
|
|
||||||
strategy_context = DefaultStrategyContext()
|
strategy_context = DefaultStrategyContext()
|
||||||
|
|
||||||
@mock.patch.object(DefaultStrategySelector, 'define_from_goal')
|
@mock.patch.object(DefaultStrategySelector, 'select')
|
||||||
@mock.patch.object(CollectorManager, "get_cluster_model_collector",
|
@mock.patch.object(CollectorManager, "get_cluster_model_collector",
|
||||||
mock.Mock())
|
mock.Mock())
|
||||||
def test_execute_strategy(self, mock_call):
|
def test_execute_strategy(self, mock_call):
|
||||||
mock_call.return_value = DummyStrategy()
|
mock_call.return_value = DummyStrategy()
|
||||||
solution = self.strategy_context.execute_strategy(self.audit.uuid,
|
solution = self.strategy_context.execute_strategy(
|
||||||
self.context)
|
self.audit.uuid, self.context)
|
||||||
self.assertIsInstance(solution, DefaultSolution)
|
self.assertIsInstance(solution, DefaultSolution)
|
||||||
|
|||||||
@@ -13,37 +13,54 @@
|
|||||||
# implied.
|
# implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from mock import patch
|
|
||||||
|
import mock
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from watcher.common.exception import WatcherException
|
from watcher.common import exception
|
||||||
from watcher.decision_engine.strategy.loading.default import \
|
from watcher.decision_engine.strategy.loading import default as default_loader
|
||||||
DefaultStrategyLoader
|
from watcher.decision_engine.strategy.selection import (
|
||||||
from watcher.decision_engine.strategy.selection.default import \
|
default as default_selector)
|
||||||
DefaultStrategySelector
|
from watcher.decision_engine.strategy import strategies
|
||||||
from watcher.tests.base import TestCase
|
from watcher.tests import base
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
class TestStrategySelector(TestCase):
|
class TestStrategySelector(base.TestCase):
|
||||||
strategy_selector = DefaultStrategySelector()
|
|
||||||
|
|
||||||
@patch.object(DefaultStrategyLoader, 'load')
|
def setUp(self):
|
||||||
def test_define_from_goal(self, mock_call):
|
super(TestStrategySelector, self).setUp()
|
||||||
cfg.CONF.set_override('goals',
|
|
||||||
{"DUMMY": "fake"}, group='watcher_goals',
|
@mock.patch.object(default_loader.DefaultStrategyLoader, 'load')
|
||||||
enforce_type=True)
|
def test_select_with_strategy_name(self, m_load):
|
||||||
expected_goal = 'DUMMY'
|
expected_goal = 'DUMMY'
|
||||||
expected_strategy = CONF.watcher_goals.goals[expected_goal]
|
expected_strategy = "dummy"
|
||||||
self.strategy_selector.define_from_goal(expected_goal, osc=None)
|
strategy_selector = default_selector.DefaultStrategySelector(
|
||||||
mock_call.assert_called_once_with(expected_strategy, osc=None)
|
expected_goal, expected_strategy, osc=None)
|
||||||
|
strategy_selector.select()
|
||||||
|
m_load.assert_called_once_with(expected_strategy, osc=None)
|
||||||
|
|
||||||
@patch.object(DefaultStrategyLoader, 'load')
|
@mock.patch.object(default_loader.DefaultStrategyLoader, 'load')
|
||||||
def test_define_from_goal_with_incorrect_mapping(self, mock_call):
|
@mock.patch.object(default_loader.DefaultStrategyLoader, 'list_available')
|
||||||
cfg.CONF.set_override('goals', {}, group='watcher_goals',
|
def test_select_with_goal_name_only(self, m_list_available, m_load):
|
||||||
enforce_type=True)
|
m_list_available.return_value = {"dummy": strategies.DummyStrategy}
|
||||||
self.assertRaises(WatcherException,
|
expected_goal = 'DUMMY'
|
||||||
self.strategy_selector.define_from_goal,
|
expected_strategy = "dummy"
|
||||||
"DUMMY")
|
strategy_selector = default_selector.DefaultStrategySelector(
|
||||||
self.assertEqual(0, mock_call.call_count)
|
expected_goal, osc=None)
|
||||||
|
strategy_selector.select()
|
||||||
|
m_load.assert_called_once_with(expected_strategy, osc=None)
|
||||||
|
|
||||||
|
def test_select_non_existing_strategy(self):
|
||||||
|
strategy_selector = default_selector.DefaultStrategySelector(
|
||||||
|
"DUMMY", "NOT_FOUND")
|
||||||
|
self.assertRaises(exception.LoadingError, strategy_selector.select)
|
||||||
|
|
||||||
|
@mock.patch.object(default_loader.DefaultStrategyLoader, 'list_available')
|
||||||
|
def test_select_no_available_strategy_for_goal(self, m_list_available):
|
||||||
|
m_list_available.return_value = {}
|
||||||
|
strategy_selector = default_selector.DefaultStrategySelector(
|
||||||
|
"DUMMY")
|
||||||
|
self.assertRaises(exception.NoAvailableStrategyForGoal,
|
||||||
|
strategy_selector.select)
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class TestAuditTemplate(base.BaseInfraOptimTest):
|
|||||||
new_name = 'my at new name %s' % uuid.uuid4()
|
new_name = 'my at new name %s' % uuid.uuid4()
|
||||||
new_description = 'my new at description'
|
new_description = 'my new at description'
|
||||||
new_host_aggregate = 10
|
new_host_aggregate = 10
|
||||||
new_goal = 'BASIC_CONSOLIDATION'
|
new_goal = 'SERVER_CONSOLIDATION'
|
||||||
new_extra = {'key1': 'new-value1', 'key2': 'new-value2'}
|
new_extra = {'key1': 'new-value1', 'key2': 'new-value2'}
|
||||||
|
|
||||||
patch = [{'path': '/name',
|
patch = [{'path': '/name',
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ CONF = config.CONF
|
|||||||
class TestExecuteBasicStrategy(base.BaseInfraOptimScenarioTest):
|
class TestExecuteBasicStrategy(base.BaseInfraOptimScenarioTest):
|
||||||
"""Tests for action plans"""
|
"""Tests for action plans"""
|
||||||
|
|
||||||
BASIC_GOAL = "BASIC_CONSOLIDATION"
|
BASIC_GOAL = "SERVER_CONSOLIDATION"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def skip_checks(cls):
|
def skip_checks(cls):
|
||||||
|
|||||||
Reference in New Issue
Block a user