Merge "Enabled config parameters to plugins"

This commit is contained in:
Jenkins
2016-05-30 09:40:30 +00:00
committed by Gerrit Code Review
22 changed files with 453 additions and 129 deletions

View File

@@ -23,18 +23,26 @@ import abc
import six
from watcher.common import clients
from watcher.common.loader import loadable
@six.add_metaclass(abc.ABCMeta)
class BaseAction(object):
class BaseAction(loadable.Loadable):
# NOTE(jed) by convention we decided
# that the attribute "resource_id" is the unique id of
# the resource to which the Action applies to allow us to use it in the
# watcher dashboard and will be nested in input_parameters
RESOURCE_ID = 'resource_id'
def __init__(self, osc=None):
""":param osc: an OpenStackClients instance"""
def __init__(self, config, osc=None):
"""Constructor
:param config: A mapping containing the configuration of this action
:type config: dict
:param osc: an OpenStackClients instance, defaults to None
:type osc: :py:class:`~.OpenStackClients` instance, optional
"""
super(BaseAction, self).__init__(config)
self._input_parameters = {}
self._osc = osc
@@ -56,6 +64,15 @@ class BaseAction(object):
def resource_id(self):
return self.input_parameters[self.RESOURCE_ID]
@classmethod
def get_config_opts(cls):
"""Defines the configuration options to be associated to this loadable
:return: A list of configuration options relative to this Loadable
:rtype: list of :class:`oslo_config.cfg.Opt` instances
"""
return []
@abc.abstractmethod
def execute(self):
"""Executes the main logic of the action

View File

@@ -23,18 +23,38 @@ import six
from watcher.applier.actions import factory
from watcher.applier.messaging import event_types
from watcher.common import clients
from watcher.common.loader import loadable
from watcher.common.messaging.events import event
from watcher import objects
@six.add_metaclass(abc.ABCMeta)
class BaseWorkFlowEngine(object):
def __init__(self, context=None, applier_manager=None):
class BaseWorkFlowEngine(loadable.Loadable):
def __init__(self, config, context=None, applier_manager=None):
"""Constructor
:param config: A mapping containing the configuration of this
workflow engine
:type config: dict
:param osc: an OpenStackClients object, defaults to None
:type osc: :py:class:`~.OpenStackClients` instance, optional
"""
super(BaseWorkFlowEngine, self).__init__(config)
self._context = context
self._applier_manager = applier_manager
self._action_factory = factory.ActionFactory()
self._osc = None
@classmethod
def get_config_opts(cls):
"""Defines the configuration options to be associated to this loadable
:return: A list of configuration options relative to this Loadable
:rtype: list of :class:`oslo_config.cfg.Opt` instances
"""
return []
@property
def context(self):
return self._context

View File

@@ -1,5 +1,5 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
# Copyright (c) 2016 b<>com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,33 +16,82 @@
from __future__ import unicode_literals
from oslo_config import cfg
from oslo_log import log
from stevedore.driver import DriverManager
from stevedore import ExtensionManager
from stevedore import driver as drivermanager
from stevedore import extension as extensionmanager
from watcher.common import exception
from watcher.common.loader.base import BaseLoader
from watcher.common.loader import base
from watcher.common import utils
LOG = log.getLogger(__name__)
class DefaultLoader(BaseLoader):
def __init__(self, namespace):
class DefaultLoader(base.BaseLoader):
def __init__(self, namespace, conf=cfg.CONF):
"""Entry point loader for Watcher using Stevedore
:param namespace: namespace of the entry point(s) to load or list
:type namespace: str
:param conf: ConfigOpts instance, defaults to cfg.CONF
"""
super(DefaultLoader, self).__init__()
self.namespace = namespace
self.conf = conf
def load(self, name, **kwargs):
try:
LOG.debug("Loading in namespace %s => %s ", self.namespace, name)
driver_manager = DriverManager(namespace=self.namespace,
name=name)
loaded = driver_manager.driver
driver_manager = drivermanager.DriverManager(
namespace=self.namespace,
name=name,
invoke_on_load=False,
)
driver_cls = driver_manager.driver
config = self._load_plugin_config(name, driver_cls)
driver = driver_cls(config, **kwargs)
except Exception as exc:
LOG.exception(exc)
raise exception.LoadingError(name=name)
return loaded(**kwargs)
return driver
def _reload_config(self):
self.conf()
def get_entry_name(self, name):
return ".".join([self.namespace, name])
def _load_plugin_config(self, name, driver_cls):
"""Load the config of the plugin"""
config = utils.Struct()
config_opts = driver_cls.get_config_opts()
if not config_opts:
return config
group_name = self.get_entry_name(name)
self.conf.register_opts(config_opts, group=group_name)
# Finalise the opt import by re-checking the configuration
# against the provided config files
self._reload_config()
config_group = self.conf.get(group_name)
if not config_group:
raise exception.LoadingError(name=name)
config.update({
name: value for name, value in config_group.items()
})
return config
def list_available(self):
extension_manager = ExtensionManager(namespace=self.namespace)
extension_manager = extensionmanager.ExtensionManager(
namespace=self.namespace)
return {ext.name: ext.plugin for ext in extension_manager.extensions}

View File

@@ -1,5 +1,5 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
# Copyright (c) 2016 b<>com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,16 +13,29 @@
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class FakeLoadable(object):
@classmethod
def namespace(cls):
return "TESTING"
class Loadable(object):
"""Generic interface for dynamically loading a driver/entry point.
This defines the contract in order to let the loader manager inject
the configuration parameters during the loading.
"""
def __init__(self, config):
self.config = config
@classmethod
def get_name(cls):
return 'fake'
@abc.abstractmethod
def get_config_opts(cls):
"""Defines the configuration options to be associated to this loadable
:return: A list of configuration options relative to this Loadable
:rtype: list of :class:`oslo_config.cfg.Opt` instances
"""
raise NotImplementedError

View File

@@ -41,6 +41,29 @@ CONF.register_opts(UTILS_OPTS)
LOG = logging.getLogger(__name__)
class Struct(dict):
"""Specialized dict where you access an item like an attribute
>>> struct = Struct()
>>> struct['a'] = 1
>>> struct.b = 2
>>> assert struct.a == 1
>>> assert struct['b'] == 2
"""
def __getattr__(self, name):
try:
return self[name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
try:
self[name] = value
except KeyError:
raise AttributeError(name)
def safe_rstrip(value, chars=None):
"""Removes trailing characters from a string if that does not make it empty

View File

@@ -47,12 +47,24 @@ See :doc:`../architecture` for more details on this component.
import abc
import six
from watcher.common.loader import loadable
@six.add_metaclass(abc.ABCMeta)
class BasePlanner(object):
class BasePlanner(loadable.Loadable):
@classmethod
def get_config_opts(cls):
"""Defines the configuration options to be associated to this loadable
:return: A list of configuration options relative to this Loadable
:rtype: list of :class:`oslo_config.cfg.Opt` instances
"""
return []
@abc.abstractmethod
def schedule(self, context, audit_uuid, solution):
"""The planner receives a solution to schedule
"""The planner receives a solution to schedule
:param solution: A solution provided by a strategy for scheduling
:type solution: :py:class:`~.BaseSolution` subclass instance
@@ -60,7 +72,7 @@ class BasePlanner(object):
:type audit_uuid: str
:return: Action plan with an ordered sequence of actions such that all
security, dependency, and performance requirements are met.
:rtype: :py:class:`watcher.objects.action_plan.ActionPlan` instance
:rtype: :py:class:`watcher.objects.ActionPlan` instance
"""
# example: directed acyclic graph
raise NotImplementedError()

View File

@@ -17,10 +17,10 @@
from __future__ import unicode_literals
from watcher.common.loader.default import DefaultLoader
from watcher.common.loader import default
class DefaultPlannerLoader(DefaultLoader):
class DefaultPlannerLoader(default.DefaultLoader):
def __init__(self):
super(DefaultPlannerLoader, self).__init__(
namespace='watcher_planners')

View File

@@ -41,20 +41,22 @@ import six
from watcher._i18n import _
from watcher.common import clients
from watcher.common.loader import loadable
from watcher.decision_engine.solution import default
from watcher.decision_engine.strategy.common import level
@six.add_metaclass(abc.ABCMeta)
class BaseStrategy(object):
class BaseStrategy(loadable.Loadable):
"""A base class for all the strategies
A Strategy is an algorithm implementation which is able to find a
Solution for a given Goal.
"""
def __init__(self, osc=None):
def __init__(self, config, osc=None):
""":param osc: an OpenStackClients instance"""
super(BaseStrategy, self).__init__(config)
self._name = self.get_name()
self._display_name = self.get_display_name()
# default strategy level
@@ -104,6 +106,15 @@ class BaseStrategy(object):
# other services
raise NotImplementedError()
@classmethod
def get_config_opts(cls):
"""Defines the configuration options to be associated to this loadable
:return: A list of configuration options relative to this Loadable
:rtype: list of :class:`oslo_config.cfg.Opt` instances
"""
return []
@abc.abstractmethod
def execute(self, original_model):
"""Execute a strategy

View File

@@ -71,16 +71,14 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
MIGRATION = "migrate"
CHANGE_NOVA_SERVICE_STATE = "change_nova_service_state"
def __init__(self, osc=None):
def __init__(self, config=None, osc=None):
"""Basic offline Consolidation using live migration
:param name: The name of the strategy (Default: "basic")
:param description: The description of the strategy
(Default: "Basic offline consolidation")
:param osc: An :py:class:`~watcher.common.clients.OpenStackClients`
instance
:param config: A mapping containing the configuration of this strategy
:type config: dict
:param osc: :py:class:`~.OpenStackClients` instance
"""
super(BasicConsolidation, self).__init__(osc)
super(BasicConsolidation, self).__init__(config, osc)
# set default value for the number of released nodes
self.number_of_released_nodes = 0

View File

@@ -48,8 +48,14 @@ class DummyStrategy(base.DummyBaseStrategy):
NOP = "nop"
SLEEP = "sleep"
def __init__(self, osc=None):
super(DummyStrategy, self).__init__(osc)
def __init__(self, config=None, osc=None):
"""Dummy Strategy implemented for demo and testing purposes
:param config: A mapping containing the configuration of this strategy
:type config: dict
:param osc: :py:class:`~.OpenStackClients` instance
"""
super(DummyStrategy, self).__init__(config, osc)
def execute(self, original_model):
LOG.debug("Executing Dummy strategy")

View File

@@ -78,14 +78,15 @@ class OutletTempControl(base.ThermalOptimizationBaseStrategy):
MIGRATION = "migrate"
def __init__(self, osc=None):
def __init__(self, config=None, osc=None):
"""Outlet temperature control using live migration
:param name: the name of the strategy
:param description: a description of the strategy
:param osc: an OpenStackClients object
:param config: A mapping containing the configuration of this strategy
:type config: dict
:param osc: an OpenStackClients object, defaults to None
:type osc: :py:class:`~.OpenStackClients` instance, optional
"""
super(OutletTempControl, self).__init__(osc)
super(OutletTempControl, self).__init__(config, osc)
# the migration plan will be triggered when the outlet temperature
# reaches threshold
# TODO(zhenzanz): Threshold should be configurable for each audit

View File

@@ -84,8 +84,8 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
https://github.com/openstack/watcher-specs/blob/master/specs/mitaka/implemented/zhaw-load-consolidation.rst
""" # noqa
def __init__(self, osc=None):
super(VMWorkloadConsolidation, self).__init__(osc)
def __init__(self, config=None, osc=None):
super(VMWorkloadConsolidation, self).__init__(config, osc)
self._ceilometer = None
self.number_of_migrations = 0
self.number_of_released_hypervisors = 0

View File

@@ -18,14 +18,27 @@
from keystoneauth1 import loading as ka_loading
import watcher.api.app
from watcher.applier.actions.loading import default as action_loader
from watcher.applier import manager as applier_manager
from watcher.applier.workflow_engine.loading import default as \
workflow_engine_loader
from watcher.common import clients
from watcher.decision_engine import manager as decision_engine_manger
from watcher.decision_engine.planner.loading import default as planner_loader
from watcher.decision_engine.planner import manager as planner_manager
from watcher.decision_engine.strategy.loading import default as strategy_loader
PLUGIN_LOADERS = (
action_loader.DefaultActionLoader,
planner_loader.DefaultPlannerLoader,
strategy_loader.DefaultStrategyLoader,
workflow_engine_loader.DefaultWorkFlowEngineLoader,
)
def list_opts():
return [
watcher_opts = [
('api', watcher.api.app.API_SERVICE_OPTS),
('watcher_decision_engine',
decision_engine_manger.WATCHER_DECISION_ENGINE_OPTS),
@@ -41,3 +54,22 @@ def list_opts():
ka_loading.get_auth_plugin_conf_options('password') +
ka_loading.get_session_conf_options()))
]
watcher_opts += list_plugin_opts()
return watcher_opts
def list_plugin_opts():
plugins_opts = []
for plugin_loader_cls in PLUGIN_LOADERS:
plugin_loader = plugin_loader_cls()
plugins_map = plugin_loader.list_available()
for plugin_name, plugin_cls in plugins_map.items():
plugin_opts = plugin_cls.get_config_opts()
if plugin_opts:
plugins_opts.append(
(plugin_loader.get_entry_name(plugin_name), plugin_opts))
return plugins_opts

View File

@@ -54,7 +54,8 @@ class TestChangeNovaServiceState(base.TestCase):
baction.BaseAction.RESOURCE_ID: "compute-1",
"state": hstate.HypervisorState.ENABLED.value,
}
self.action = change_nova_service_state.ChangeNovaServiceState()
self.action = change_nova_service_state.ChangeNovaServiceState(
mock.Mock())
self.action.input_parameters = self.input_parameters
def test_parameters_down(self):

View File

@@ -58,7 +58,7 @@ class TestMigration(base.TestCase):
"dst_hypervisor": "hypervisor2-hostname",
baction.BaseAction.RESOURCE_ID: self.INSTANCE_UUID,
}
self.action = migration.Migrate()
self.action = migration.Migrate(mock.Mock())
self.action.input_parameters = self.input_parameters
self.input_parameters_cold = {
@@ -67,7 +67,7 @@ class TestMigration(base.TestCase):
"dst_hypervisor": "hypervisor2-hostname",
baction.BaseAction.RESOURCE_ID: self.INSTANCE_UUID,
}
self.action_cold = migration.Migrate()
self.action_cold = migration.Migrate(mock.Mock())
self.action_cold.input_parameters = self.input_parameters_cold
def test_parameters(self):

View File

@@ -14,6 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
import mock
import voluptuous
from watcher.applier.actions import sleep
@@ -23,7 +25,7 @@ from watcher.tests import base
class TestSleep(base.TestCase):
def setUp(self):
super(TestSleep, self).setUp()
self.s = sleep.Sleep()
self.s = sleep.Sleep(mock.Mock())
def test_parameters_duration(self):
self.s.input_parameters = {self.s.DURATION: 1.0}

View File

@@ -20,10 +20,9 @@ import abc
import mock
import six
from stevedore import driver
from stevedore import extension
from watcher.applier.actions import base as abase
from watcher.applier.actions import factory
from watcher.applier.workflow_engine import default as tflow
from watcher.common import exception
from watcher.common import utils
@@ -31,6 +30,10 @@ from watcher import objects
from watcher.tests.db import base
class ExpectedException(Exception):
pass
@six.add_metaclass(abc.ABCMeta)
class FakeAction(abase.BaseAction):
def schema(self):
@@ -46,21 +49,14 @@ class FakeAction(abase.BaseAction):
pass
def execute(self):
raise Exception()
@classmethod
def namespace(cls):
return "TESTING"
@classmethod
def get_name(cls):
return 'fake_action'
raise ExpectedException()
class TestDefaultWorkFlowEngine(base.DbTestCase):
def setUp(self):
super(TestDefaultWorkFlowEngine, self).setUp()
self.engine = tflow.DefaultWorkFlowEngine(
config=mock.Mock(),
context=self.context,
applier_manager=mock.MagicMock())
@@ -86,6 +82,7 @@ class TestDefaultWorkFlowEngine(base.DbTestCase):
new_action = objects.Action(self.context, **action)
new_action.create(self.context)
new_action.save()
return new_action
def check_action_state(self, action, expected_state):
@@ -175,18 +172,13 @@ class TestDefaultWorkFlowEngine(base.DbTestCase):
self.check_action_state(second, objects.action.State.SUCCEEDED)
self.check_action_state(third, objects.action.State.FAILED)
@mock.patch("watcher.common.loader.default.DriverManager")
def test_execute_with_action_exception(self, m_driver):
m_driver.return_value = driver.DriverManager.make_test_instance(
extension=extension.Extension(name=FakeAction.get_name(),
entry_point="%s:%s" % (
FakeAction.__module__,
FakeAction.__name__),
plugin=FakeAction,
obj=None),
namespace=FakeAction.namespace())
actions = [self.create_action("dontcare", {}, None)]
@mock.patch.object(factory.ActionFactory, "make_action")
def test_execute_with_action_exception(self, m_make_action):
actions = [self.create_action("fake_action", {}, None)]
m_make_action.return_value = FakeAction(mock.Mock())
self.assertRaises(exception.WorkflowExecutionException,
self.engine.execute, actions)
exc = self.assertRaises(exception.WorkflowExecutionException,
self.engine.execute, actions)
self.assertIsInstance(exc.kwargs['error'], ExpectedException)
self.check_action_state(actions[0], objects.action.State.FAILED)

View File

@@ -18,36 +18,88 @@ from __future__ import unicode_literals
import mock
from oslotest.base import BaseTestCase
from stevedore.driver import DriverManager
from oslo_config import cfg
from stevedore import driver as drivermanager
from stevedore.extension import Extension
from watcher.common import exception
from watcher.common.loader.default import DefaultLoader
from watcher.tests.common.loader.FakeLoadable import FakeLoadable
from watcher.common.loader import default
from watcher.common.loader import loadable
from watcher.tests import base
class TestLoader(BaseTestCase):
@mock.patch("watcher.common.loader.default.DriverManager")
def test_load_driver_no_opt(self, m_driver_manager):
m_driver_manager.return_value = DriverManager.make_test_instance(
extension=Extension(name=FakeLoadable.get_name(),
entry_point="%s:%s" % (
FakeLoadable.__module__,
FakeLoadable.__name__),
plugin=FakeLoadable,
obj=None),
namespace=FakeLoadable.namespace())
class FakeLoadable(loadable.Loadable):
loader_manager = DefaultLoader(namespace='TESTING')
loaded_driver = loader_manager.load(name='fake')
@classmethod
def get_config_opts(cls):
return []
self.assertEqual(FakeLoadable.get_name(), loaded_driver.get_name())
@mock.patch("watcher.common.loader.default.DriverManager")
def test_load_driver_bad_plugin(self, m_driver_manager):
class FakeLoadableWithOpts(loadable.Loadable):
@classmethod
def get_config_opts(cls):
return [
cfg.StrOpt("test_opt", default="fake_with_opts"),
]
class TestLoader(base.TestCase):
def setUp(self):
super(TestLoader, self).setUp()
def _fake_parse(self, *args, **kw):
return cfg.ConfigOpts._parse_cli_opts(cfg.CONF, [])
cfg.CONF._parse_cli_opts = _fake_parse
def test_load_loadable_no_opt(self):
fake_driver = drivermanager.DriverManager.make_test_instance(
extension=Extension(
name="fake",
entry_point="%s:%s" % (FakeLoadable.__module__,
FakeLoadable.__name__),
plugin=FakeLoadable,
obj=None),
namespace="TESTING")
loader_manager = default.DefaultLoader(namespace='TESTING')
with mock.patch.object(drivermanager,
"DriverManager") as m_driver_manager:
m_driver_manager.return_value = fake_driver
loaded_driver = loader_manager.load(name='fake')
self.assertIsInstance(loaded_driver, FakeLoadable)
@mock.patch("watcher.common.loader.default.drivermanager.DriverManager")
def test_load_loadable_bad_plugin(self, m_driver_manager):
m_driver_manager.side_effect = Exception()
loader_manager = DefaultLoader(namespace='TESTING')
loader_manager = default.DefaultLoader(namespace='TESTING')
self.assertRaises(exception.LoadingError, loader_manager.load,
name='bad_driver')
def test_load_loadable_with_opts(self):
fake_driver = drivermanager.DriverManager.make_test_instance(
extension=Extension(
name="fake",
entry_point="%s:%s" % (FakeLoadableWithOpts.__module__,
FakeLoadableWithOpts.__name__),
plugin=FakeLoadableWithOpts,
obj=None),
namespace="TESTING")
loader_manager = default.DefaultLoader(namespace='TESTING')
with mock.patch.object(drivermanager,
"DriverManager") as m_driver_manager:
m_driver_manager.return_value = fake_driver
loaded_driver = loader_manager.load(name='fake')
self.assertIsInstance(loaded_driver, FakeLoadableWithOpts)
self.assertEqual(
"fake_with_opts", loaded_driver.config.get("test_opt"))
self.assertEqual(
"fake_with_opts", loaded_driver.config.test_opt)

View File

@@ -14,8 +14,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_config import cfg
from watcher.decision_engine.strategy.strategies import base as base_strategy
CONF = cfg.CONF
class FakeStrategy(base_strategy.BaseStrategy):
@@ -48,6 +52,10 @@ class FakeStrategy(base_strategy.BaseStrategy):
def get_translatable_goal_display_name(cls):
return cls.GOAL_DISPLAY_NAME
@classmethod
def get_config_opts(cls):
return []
def execute(self, original_model):
pass
@@ -58,6 +66,12 @@ class FakeDummy1Strategy1(FakeStrategy):
NAME = "STRATEGY_1"
DISPLAY_NAME = "Strategy 1"
@classmethod
def get_config_opts(cls):
return [
cfg.StrOpt('test_opt', help="Option used for testing."),
]
class FakeDummy1Strategy2(FakeStrategy):
GOAL_NAME = "DUMMY_1"

View File

@@ -57,7 +57,7 @@ class SolutionFakerSingleHyp(object):
class TestActionScheduling(base.DbTestCase):
def test_schedule_actions(self):
default_planner = pbase.DefaultPlanner()
default_planner = pbase.DefaultPlanner(mock.Mock())
audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
solution = dsol.DefaultSolution()
@@ -83,7 +83,7 @@ class TestActionScheduling(base.DbTestCase):
self.assertEqual("migrate", actions[0].action_type)
def test_schedule_two_actions(self):
default_planner = pbase.DefaultPlanner()
default_planner = pbase.DefaultPlanner(mock.Mock())
audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
solution = dsol.DefaultSolution()
@@ -115,9 +115,10 @@ class TestActionScheduling(base.DbTestCase):
class TestDefaultPlanner(base.DbTestCase):
def setUp(self):
super(TestDefaultPlanner, self).setUp()
self.default_planner = pbase.DefaultPlanner()
self.default_planner = pbase.DefaultPlanner(mock.Mock())
obj_utils.create_test_audit_template(self.context)
p = mock.patch.object(db_api.BaseConnection, 'create_action_plan')

View File

@@ -15,8 +15,9 @@
# limitations under the License.
from __future__ import unicode_literals
from mock import patch
import mock
from stevedore import extension
from watcher.common import exception
from watcher.decision_engine.strategy.loading import default as default_loading
from watcher.decision_engine.strategy.strategies import dummy_strategy
@@ -25,38 +26,38 @@ from watcher.tests import base
class TestDefaultStrategyLoader(base.TestCase):
strategy_loader = default_loading.DefaultStrategyLoader()
def test_load_strategy_with_empty_model(self):
self.assertRaises(
exception.LoadingError, self.strategy_loader.load, None)
strategy_loader = default_loading.DefaultStrategyLoader()
self.assertRaises(exception.LoadingError, strategy_loader.load, None)
def test_load_strategy_is_basic(self):
strategy_loader = default_loading.DefaultStrategyLoader()
expected_strategy = 'basic'
selected_strategy = self.strategy_loader.load(expected_strategy)
selected_strategy = strategy_loader.load(expected_strategy)
self.assertEqual(
selected_strategy.id,
expected_strategy,
'The default strategy should be basic')
@patch("watcher.common.loader.default.ExtensionManager")
def test_strategy_loader(self, m_extension_manager):
def test_strategy_loader(self):
dummy_strategy_name = "dummy"
# Set up the fake Stevedore extensions
m_extension_manager.return_value = extension.\
ExtensionManager.make_test_instance(
extensions=[extension.Extension(
name=dummy_strategy_name,
entry_point="%s:%s" % (
dummy_strategy.DummyStrategy.__module__,
dummy_strategy.DummyStrategy.__name__),
plugin=dummy_strategy.DummyStrategy,
obj=None,
)],
namespace="watcher_strategies",
)
strategy_loader = default_loading.DefaultStrategyLoader()
loaded_strategy = strategy_loader.load("dummy")
fake_extmanager_call = extension.ExtensionManager.make_test_instance(
extensions=[extension.Extension(
name=dummy_strategy_name,
entry_point="%s:%s" % (
dummy_strategy.DummyStrategy.__module__,
dummy_strategy.DummyStrategy.__name__),
plugin=dummy_strategy.DummyStrategy,
obj=None,
)],
namespace="watcher_strategies",
)
with mock.patch.object(extension, "ExtensionManager") as m_ext_manager:
m_ext_manager.return_value = fake_extmanager_call
strategy_loader = default_loading.DefaultStrategyLoader()
loaded_strategy = strategy_loader.load("dummy")
self.assertEqual("dummy", loaded_strategy.id)
self.assertEqual("Dummy strategy", loaded_strategy.display_name)
@@ -67,5 +68,5 @@ class TestDefaultStrategyLoader(base.TestCase):
self.assertIsInstance(loaded_strategy, dummy_strategy.DummyStrategy)
def test_endpoints(self):
for endpoint in self.strategy_loader.list_available():
for endpoint in strategy_loader.list_available():
self.assertIsNotNone(self.strategy_loader.load(endpoint))

View File

@@ -14,18 +14,97 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import
from __future__ import unicode_literals
import mock
from stevedore import extension
import watcher.opts as opt
from watcher.tests.base import BaseTestCase
from watcher import opts
from watcher.tests import base
from watcher.tests.decision_engine import fake_strategies
class TestListOpts(BaseTestCase):
class TestListOpts(base.TestCase):
def setUp(self):
super(TestListOpts, self).setUp()
self.base_sections = [
'api', 'watcher_decision_engine', 'watcher_applier',
'watcher_planner', 'nova_client', 'glance_client',
'cinder_client', 'ceilometer_client', 'neutron_client',
'watcher_clients_auth']
def test_run_list_opts(self):
result = opt.list_opts()
expected_sections = self.base_sections
result = opts.list_opts()
self.assertIsNotNone(result)
for section_name, options in result:
self.assertIn(section_name, expected_sections)
self.assertTrue(len(options))
def test_list_opts_no_opts(self):
expected_sections = self.base_sections
# Set up the fake Stevedore extensions
fake_extmanager_call = extension.ExtensionManager.make_test_instance(
extensions=[extension.Extension(
name=fake_strategies.FakeDummy1Strategy2.get_name(),
entry_point="%s:%s" % (
fake_strategies.FakeDummy1Strategy2.__module__,
fake_strategies.FakeDummy1Strategy2.__name__),
plugin=fake_strategies.FakeDummy1Strategy2,
obj=None,
)],
namespace="watcher_strategies",
)
def m_list_available(namespace):
if namespace == "watcher_strategies":
return fake_extmanager_call
else:
return extension.ExtensionManager.make_test_instance(
extensions=[], namespace=namespace)
with mock.patch.object(extension, "ExtensionManager") as m_ext_manager:
m_ext_manager.side_effect = m_list_available
result = opts.list_opts()
self.assertIsNotNone(result)
for section_name, options in result:
self.assertIn(section_name, expected_sections)
self.assertTrue(len(options))
def test_list_opts_with_opts(self):
expected_sections = self.base_sections + [
'watcher_strategies.STRATEGY_1']
# Set up the fake Stevedore extensions
fake_extmanager_call = extension.ExtensionManager.make_test_instance(
extensions=[extension.Extension(
name=fake_strategies.FakeDummy1Strategy1.get_name(),
entry_point="%s:%s" % (
fake_strategies.FakeDummy1Strategy1.__module__,
fake_strategies.FakeDummy1Strategy1.__name__),
plugin=fake_strategies.FakeDummy1Strategy1,
obj=None,
)],
namespace="watcher_strategies",
)
def m_list_available(namespace):
if namespace == "watcher_strategies":
return fake_extmanager_call
else:
return extension.ExtensionManager.make_test_instance(
extensions=[], namespace=namespace)
with mock.patch.object(extension, "ExtensionManager") as m_ext_manager:
m_ext_manager.side_effect = m_list_available
result = opts.list_opts()
self.assertIsNotNone(result)
for section_name, options in result:
self.assertIn(section_name, expected_sections)
self.assertTrue(len(options))
result_map = dict(result)
strategy_opts = result_map['watcher_strategies.STRATEGY_1']
self.assertEqual(['test_opt'], [opt.name for opt in strategy_opts])