Enabled config parameters to plugins
In this changeset, I added the possibility for all plugins to define configuration parameters for themselves. Partially Implements: blueprint plugins-parameters Change-Id: I676b2583b3b4841c64c862b2b0c234b4eb5fd0fd
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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])
|
||||
|
||||
Reference in New Issue
Block a user