diff --git a/watcher/api/controllers/v1/action_plan.py b/watcher/api/controllers/v1/action_plan.py
index 079d44caa..cabbf64fb 100644
--- a/watcher/api/controllers/v1/action_plan.py
+++ b/watcher/api/controllers/v1/action_plan.py
@@ -49,13 +49,14 @@ standard workflow model description formats such as
`Business Process Model and Notation 2.0 (BPMN 2.0) `_
or `Unified Modeling Language (UML) `_.
-To see the life-cycle and description of
+To see the life-cycle and description of
:ref:`Action Plan ` states, visit :ref:`the Action Plan state
machine `.
""" # noqa
import datetime
+from oslo_log import log
import pecan
from pecan import rest
import wsme
@@ -66,6 +67,7 @@ from watcher._i18n import _
from watcher.api.controllers import base
from watcher.api.controllers import link
from watcher.api.controllers.v1 import collection
+from watcher.api.controllers.v1 import efficacy_indicator as efficacyindicator
from watcher.api.controllers.v1 import types
from watcher.api.controllers.v1 import utils as api_utils
from watcher.applier import rpcapi
@@ -73,6 +75,8 @@ from watcher.common import exception
from watcher import objects
from watcher.objects import action_plan as ap_objects
+LOG = log.getLogger(__name__)
+
class ActionPlanPatchType(types.JsonPatchType):
@@ -113,6 +117,7 @@ class ActionPlan(base.APIBase):
_audit_uuid = None
_first_action_uuid = None
+ _efficacy_indicators = None
def _get_audit_uuid(self):
return self._audit_uuid
@@ -143,6 +148,34 @@ class ActionPlan(base.APIBase):
except exception.ActionNotFound:
self._first_action_uuid = None
+ def _get_efficacy_indicators(self):
+ if self._efficacy_indicators is None:
+ self._set_efficacy_indicators(wtypes.Unset)
+ return self._efficacy_indicators
+
+ def _set_efficacy_indicators(self, value):
+ efficacy_indicators = []
+ if value == wtypes.Unset and not self._efficacy_indicators:
+ try:
+ _efficacy_indicators = objects.EfficacyIndicator.list(
+ pecan.request.context,
+ filters={"action_plan_uuid": self.uuid})
+
+ for indicator in _efficacy_indicators:
+ efficacy_indicator = efficacyindicator.EfficacyIndicator(
+ context=pecan.request.context,
+ name=indicator.name,
+ description=indicator.description,
+ unit=indicator.unit,
+ value=indicator.value,
+ )
+ efficacy_indicators.append(efficacy_indicator.as_dict())
+ self._efficacy_indicators = efficacy_indicators
+ except exception.EfficacyIndicatorNotFound as exc:
+ LOG.exception(exc)
+ elif value and self._efficacy_indicators != value:
+ self._efficacy_indicators = value
+
uuid = wtypes.wsattr(types.uuid, readonly=True)
"""Unique UUID for this action plan"""
@@ -155,6 +188,14 @@ class ActionPlan(base.APIBase):
mandatory=True)
"""The UUID of the audit this port belongs to"""
+ efficacy_indicators = wsme.wsproperty(
+ types.jsontype, _get_efficacy_indicators, _set_efficacy_indicators,
+ mandatory=True)
+ """The list of efficacy indicators associated to this action plan"""
+
+ global_efficacy = wtypes.wsattr(types.jsontype, readonly=True)
+ """The global efficacy of this action plan"""
+
state = wtypes.text
"""This action plan state"""
@@ -163,7 +204,6 @@ class ActionPlan(base.APIBase):
def __init__(self, **kwargs):
super(ActionPlan, self).__init__()
-
self.fields = []
fields = list(objects.ActionPlan.fields)
for field in fields:
@@ -175,6 +215,7 @@ class ActionPlan(base.APIBase):
self.fields.append('audit_uuid')
self.fields.append('first_action_uuid')
+ self.fields.append('efficacy_indicators')
setattr(self, 'audit_uuid', kwargs.get('audit_id', wtypes.Unset))
setattr(self, 'first_action_uuid',
@@ -184,12 +225,13 @@ class ActionPlan(base.APIBase):
def _convert_with_links(action_plan, url, expand=True):
if not expand:
action_plan.unset_fields_except(
- ['uuid', 'state', 'updated_at',
- 'audit_uuid', 'first_action_uuid'])
+ ['uuid', 'state', 'efficacy_indicators', 'global_efficacy',
+ 'updated_at', 'audit_uuid', 'first_action_uuid'])
- action_plan.links = [link.Link.make_link(
- 'self', url,
- 'action_plans', action_plan.uuid),
+ action_plan.links = [
+ link.Link.make_link(
+ 'self', url,
+ 'action_plans', action_plan.uuid),
link.Link.make_link(
'bookmark', url,
'action_plans', action_plan.uuid,
@@ -211,6 +253,12 @@ class ActionPlan(base.APIBase):
updated_at=datetime.datetime.utcnow())
sample._first_action_uuid = '57eaf9ab-5aaa-4f7e-bdf7-9a140ac7a720'
sample._audit_uuid = 'abcee106-14d3-4515-b744-5a26885cf6f6'
+ sample._efficacy_indicators = [{'description': 'Test indicator',
+ 'name': 'test_indicator',
+ 'unit': '%'}]
+ sample._global_efficacy = {'description': 'Global efficacy',
+ 'name': 'test_global_efficacy',
+ 'unit': '%'}
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
diff --git a/watcher/common/exception.py b/watcher/common/exception.py
index 7fb131eab..8c517150f 100644
--- a/watcher/common/exception.py
+++ b/watcher/common/exception.py
@@ -311,6 +311,11 @@ class InvalidIndicatorValue(WatcherException):
"and spec type '%(spec_type)s' is invalid.")
+class GlobalEfficacyComputationError(WatcherException):
+ msg_fmt = _("Could not compute the global efficacy for the '%(goal)s' "
+ "goal using the '%(strategy)s' strategy.")
+
+
class NoMetricValuesForVM(WatcherException):
msg_fmt = _("No values returned by %(resource_id)s for %(metric_name)s.")
diff --git a/watcher/decision_engine/planner/default.py b/watcher/decision_engine/planner/default.py
index db55f804d..9d568ecc5 100644
--- a/watcher/decision_engine/planner/default.py
+++ b/watcher/decision_engine/planner/default.py
@@ -59,19 +59,21 @@ class DefaultPlanner(base.BasePlanner):
def schedule(self, context, audit_id, solution):
LOG.debug('Create an action plan for the audit uuid: %s ', audit_id)
- action_plan = self._create_action_plan(context, audit_id)
+ action_plan = self._create_action_plan(context, audit_id, solution)
actions = list(solution.actions)
to_schedule = []
for action in actions:
- json_action = self.create_action(action_plan_id=action_plan.id,
- action_type=action.get(
- 'action_type'),
- input_parameters=action.get(
- 'input_parameters'))
+ json_action = self.create_action(
+ 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')],
json_action))
+ self._create_efficacy_indicators(
+ context, action_plan.id, solution.efficacy_indicators)
+
# scheduling
scheduled = sorted(to_schedule, key=lambda x: (x[0]))
if len(scheduled) == 0:
@@ -96,19 +98,38 @@ class DefaultPlanner(base.BasePlanner):
return action_plan
- def _create_action_plan(self, context, audit_id):
+ def _create_action_plan(self, context, audit_id, solution):
action_plan_dict = {
'uuid': utils.generate_uuid(),
'audit_id': audit_id,
'first_action_id': None,
- 'state': objects.action_plan.State.RECOMMENDED
+ 'state': objects.action_plan.State.RECOMMENDED,
+ 'global_efficacy': solution.global_efficacy,
}
new_action_plan = objects.ActionPlan(context, **action_plan_dict)
new_action_plan.create(context)
- new_action_plan.save()
+
return new_action_plan
+ def _create_efficacy_indicators(self, context, action_plan_id, indicators):
+ efficacy_indicators = []
+ for indicator in indicators:
+ efficacy_indicator_dict = {
+ 'uuid': utils.generate_uuid(),
+ 'name': indicator.name,
+ 'description': indicator.description,
+ 'unit': indicator.unit,
+ 'value': indicator.value,
+ 'action_plan_id': action_plan_id,
+ }
+ new_efficacy_indicator = objects.EfficacyIndicator(
+ context, **efficacy_indicator_dict)
+ new_efficacy_indicator.create(context)
+
+ efficacy_indicators.append(new_efficacy_indicator)
+ return efficacy_indicators
+
def _create_action(self, context, _action, parent_action):
try:
LOG.debug("Creating the %s in watcher db",
diff --git a/watcher/decision_engine/solution/base.py b/watcher/decision_engine/solution/base.py
index cb0b5fcc9..22f56e4be 100644
--- a/watcher/decision_engine/solution/base.py
+++ b/watcher/decision_engine/solution/base.py
@@ -37,59 +37,62 @@ applied.
Two approaches to dealing with this can be envisaged:
-- **fully automated mode**: only the :ref:`Solution `
- with the highest ranking (i.e., the highest
- :ref:`Optimization Efficiency `)
- will be sent to the :ref:`Watcher Planner ` and
- translated into concrete :ref:`Actions `.
-- **manual mode**: several :ref:`Solutions ` are proposed
- to the :ref:`Administrator ` with a detailed
- measurement of the estimated
- :ref:`Optimization Efficiency ` and he/she decides
- which one will be launched.
+- **fully automated mode**: only the :ref:`Solution `
+ with the highest ranking (i.e., the highest
+ :ref:`Optimization Efficacy `) will be sent to the
+ :ref:`Watcher Planner ` and translated into
+ concrete :ref:`Actions `.
+- **manual mode**: several :ref:`Solutions ` are proposed
+ to the :ref:`Administrator ` with a detailed
+ measurement of the estimated :ref:`Optimization Efficacy
+ ` and he/she decides which one will be launched.
"""
import abc
import six
+from watcher.decision_engine.solution import efficacy
+
@six.add_metaclass(abc.ABCMeta)
class BaseSolution(object):
- def __init__(self):
- self._origin = None
- self._model = None
- self._efficacy = 0
+ def __init__(self, goal, strategy):
+ """Base Solution constructor
+
+ :param goal: Goal associated to this solution
+ :type goal: :py:class:`~.base.Goal` instance
+ :param strategy: Strategy associated to this solution
+ :type strategy: :py:class:`~.BaseStrategy` instance
+ """
+ self.goal = goal
+ self.strategy = strategy
+ self.origin = None
+ self.model = None
+ self.efficacy = efficacy.Efficacy(self.goal, self.strategy)
@property
- def efficacy(self):
- return self._efficacy
-
- @efficacy.setter
- def efficacy(self, e):
- self._efficacy = e
+ def global_efficacy(self):
+ return self.efficacy.global_efficacy
@property
- def model(self):
- return self._model
+ def efficacy_indicators(self):
+ return self.efficacy.indicators
- @model.setter
- def model(self, m):
- self._model = m
+ def compute_global_efficacy(self):
+ """Compute the global efficacy given a map of efficacy indicators"""
+ self.efficacy.compute_global_efficacy()
- @property
- def origin(self):
- return self._origin
+ def set_efficacy_indicators(self, **indicators_map):
+ """Set the efficacy indicators mapping (no validation)
- @origin.setter
- def origin(self, m):
- self._origin = m
+ :param indicators_map: mapping between the indicator name and its value
+ :type indicators_map: dict {`str`: `object`}
+ """
+ self.efficacy.set_efficacy_indicators(**indicators_map)
@abc.abstractmethod
- def add_action(self,
- action_type,
- resource_id,
- input_parameters=None):
- """Add a new Action in the Action Plan
+ def add_action(self, action_type, resource_id, input_parameters=None):
+ """Add a new Action in the Solution
:param action_type: the unique id of an action type defined in
entry point 'watcher_actions'
diff --git a/watcher/decision_engine/solution/default.py b/watcher/decision_engine/solution/default.py
index 9784c3525..a64950cb5 100644
--- a/watcher/decision_engine/solution/default.py
+++ b/watcher/decision_engine/solution/default.py
@@ -22,19 +22,21 @@ from watcher.decision_engine.solution import base
class DefaultSolution(base.BaseSolution):
- def __init__(self):
+ def __init__(self, goal, strategy):
"""Stores a set of actions generated by a strategy
The DefaultSolution class store a set of actions generated by a
strategy in order to achieve the goal.
+
+ :param goal: Goal associated to this solution
+ :type goal: :py:class:`~.base.Goal` instance
+ :param strategy: Strategy associated to this solution
+ :type strategy: :py:class:`~.BaseStrategy` instance
"""
- super(DefaultSolution, self).__init__()
+ super(DefaultSolution, self).__init__(goal, strategy)
self._actions = []
- def add_action(self, action_type,
- input_parameters=None,
- resource_id=None):
-
+ def add_action(self, action_type, input_parameters=None, resource_id=None):
if input_parameters is not None:
if baction.BaseAction.RESOURCE_ID in input_parameters.keys():
raise exception.ReservedWord(name=baction.BaseAction.
diff --git a/watcher/decision_engine/solution/efficacy.py b/watcher/decision_engine/solution/efficacy.py
index 827a0d87d..108e78fe3 100644
--- a/watcher/decision_engine/solution/efficacy.py
+++ b/watcher/decision_engine/solution/efficacy.py
@@ -14,8 +14,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import numbers
+
+from oslo_log import log as logging
+
+from watcher._i18n import _
+from watcher.common import exception
from watcher.common import utils
+LOG = logging.getLogger(__name__)
+
+
+class IndicatorsMap(utils.Struct):
+ pass
+
class Indicator(utils.Struct):
@@ -24,4 +36,70 @@ class Indicator(utils.Struct):
self.name = name
self.description = description
self.unit = unit
+ if not isinstance(value, numbers.Number):
+ raise exception.InvalidIndicatorValue(
+ _("An indicator value should be a number"))
self.value = value
+
+
+class Efficacy(object):
+ """Solution efficacy"""
+
+ def __init__(self, goal, strategy):
+ """Solution efficacy
+
+ :param goal: Goal associated to this solution
+ :type goal: :py:class:`~.base.Goal` instance
+ :param strategy: Strategy associated to this solution
+ :type strategy: :py:class:`~.BaseStrategy` instance
+ """
+ self.goal = goal
+ self.strategy = strategy
+
+ self._efficacy_spec = self.goal.efficacy_specification
+
+ # Used to store in DB the info related to the efficacy indicators
+ self.indicators = []
+ # Used to compute the global efficacy
+ self._indicators_mapping = IndicatorsMap()
+ self.global_efficacy = None
+
+ def set_efficacy_indicators(self, **indicators_map):
+ """Set the efficacy indicators
+
+ :param indicators_map: kwargs where the key is the name of the efficacy
+ indicator as defined in the associated
+ :py:class:`~.IndicatorSpecification` and the
+ value is a number.
+ :type indicators_map: dict {str: numerical value}
+ """
+ self._indicators_mapping.update(indicators_map)
+
+ def compute_global_efficacy(self):
+ self._efficacy_spec.validate_efficacy_indicators(
+ self._indicators_mapping)
+ try:
+ self.global_efficacy = (
+ self._efficacy_spec.get_global_efficacy_indicator(
+ self._indicators_mapping))
+
+ indicators_specs_map = {
+ indicator_spec.name: indicator_spec
+ for indicator_spec in self._efficacy_spec.indicators_specs}
+
+ indicators = []
+ for indicator_name, value in self._indicators_mapping.items():
+ related_indicator_spec = indicators_specs_map[indicator_name]
+ indicators.append(
+ Indicator(
+ name=related_indicator_spec.name,
+ description=related_indicator_spec.description,
+ unit=related_indicator_spec.unit,
+ value=value))
+
+ self.indicators = indicators
+ except Exception as exc:
+ LOG.exception(exc)
+ raise exception.GlobalEfficacyComputationError(
+ goal=self.goal.name,
+ strategy=self.strategy.name)
diff --git a/watcher/decision_engine/strategy/strategies/base.py b/watcher/decision_engine/strategy/strategies/base.py
index 92863193c..86c8bc7c2 100644
--- a/watcher/decision_engine/strategy/strategies/base.py
+++ b/watcher/decision_engine/strategy/strategies/base.py
@@ -66,11 +66,12 @@ class BaseStrategy(loadable.Loadable):
super(BaseStrategy, self).__init__(config)
self._name = self.get_name()
self._display_name = self.get_display_name()
+ self._goal = self.get_goal()
# default strategy level
self._strategy_level = level.StrategyLevel.conservative
self._cluster_state_collector = None
# the solution given by the strategy
- self._solution = default.DefaultSolution()
+ self._solution = default.DefaultSolution(goal=self.goal, strategy=self)
self._osc = osc
self._collector_manager = None
self._model = None
@@ -99,7 +100,7 @@ class BaseStrategy(loadable.Loadable):
@classmethod
@abc.abstractmethod
def get_goal_name(cls):
- """The goal name for the strategy"""
+ """The goal name the strategy achieves"""
raise NotImplementedError()
@classmethod
@@ -151,6 +152,8 @@ class BaseStrategy(loadable.Loadable):
self.do_execute()
self.post_execute()
+ self.solution.compute_global_efficacy()
+
return self.solution
@property
diff --git a/watcher/decision_engine/strategy/strategies/basic_consolidation.py b/watcher/decision_engine/strategy/strategies/basic_consolidation.py
index b88f20f9d..07a82a97a 100644
--- a/watcher/decision_engine/strategy/strategies/basic_consolidation.py
+++ b/watcher/decision_engine/strategy/strategies/basic_consolidation.py
@@ -141,7 +141,6 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
def check_migration(self, src_hypervisor, dest_hypervisor, vm_to_mig):
"""Check if the migration is possible
- :param self.model: the current state of the cluster
:param src_hypervisor: the current node of the virtual machine
:param dest_hypervisor: the destination of the virtual machine
:param vm_to_mig: the virtual machine
@@ -185,7 +184,6 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
check the threshold value defined by the ratio of
aggregated CPU capacity of VMs on one node to CPU capacity
of this node must not exceed the threshold value.
- :param self.model: the current state of the cluster
:param dest_hypervisor: the destination of the virtual machine
:param total_cores
:param total_disk
@@ -216,7 +214,6 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
total_memory_used):
"""Calculate weight of every resource
- :param self.model:
:param element:
:param total_cores_used:
:param total_disk_used:
@@ -482,4 +479,7 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
LOG.debug(infos)
def post_execute(self):
- pass
+ self.solution.set_efficacy_indicators(
+ released_compute_nodes_count=self.number_of_migrations,
+ vm_migrations_count=self.number_of_released_nodes,
+ )
diff --git a/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py b/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py
index ca4ae6d58..bca5ed178 100644
--- a/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py
+++ b/watcher/decision_engine/strategy/strategies/vm_workload_consolidation.py
@@ -332,7 +332,7 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
:param model: model_root object
:return: {'cpu': <0,1>, 'ram': <0,1>, 'disk': <0,1>}
"""
- hypervisors = self.model.get_all_hypervisors().values()
+ hypervisors = model.get_all_hypervisors().values()
rcu = {}
counters = {}
for hypervisor in hypervisors:
@@ -517,7 +517,7 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
:param original_model: root_model object
"""
LOG.info(_LI('Executing Smart Strategy'))
- model = self.get_prediction_model(self.model)
+ model = self.get_prediction_model()
rcu = self.get_relative_cluster_utilization(model)
self.ceilometer_vm_data_cache = dict()
@@ -546,9 +546,9 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
LOG.debug(info)
- self.solution.model = model
- self.solution.efficacy = rcu_after['cpu']
-
def post_execute(self):
- # TODO(v-francoise): Add the indicators to the solution
- pass
+ # self.solution.efficacy = rcu_after['cpu']
+ self.solution.set_efficacy_indicators(
+ released_compute_nodes_count=self.number_of_migrations,
+ vm_migrations_count=self.number_of_released_hypervisors,
+ )
diff --git a/watcher/decision_engine/strategy/strategies/workload_stabilization.py b/watcher/decision_engine/strategy/strategies/workload_stabilization.py
index b7a2a16e6..bd47565d9 100644
--- a/watcher/decision_engine/strategy/strategies/workload_stabilization.py
+++ b/watcher/decision_engine/strategy/strategies/workload_stabilization.py
@@ -362,7 +362,6 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
def fill_solution(self):
self.solution.model = self.model
- self.solution.efficacy = 100
return self.solution
def pre_execute(self):
@@ -403,11 +402,10 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
break
if balanced:
break
- return self.fill_solution()
def post_execute(self):
"""Post-execution phase
This can be used to compute the global efficacy
"""
- self.solution.model = self.model
+ self.fill_solution()
diff --git a/watcher/decision_engine/sync.py b/watcher/decision_engine/sync.py
index 092baf06d..cd7676ec5 100644
--- a/watcher/decision_engine/sync.py
+++ b/watcher/decision_engine/sync.py
@@ -277,8 +277,8 @@ class Syncer(object):
display_name=goal_cls.get_translatable_display_name(),
efficacy_specification=tuple(
IndicatorSpec(**indicator.to_dict())
- for indicator in goal_cls.get_efficacy_specification()
- .get_indicators_specifications()))
+ for indicator in goal_cls.get_efficacy_specification(
+ ).get_indicators_specifications()))
for _, strategy_cls in implemented_strategies.items():
strategies_map[strategy_cls.get_name()] = StrategyMapping(
diff --git a/watcher/objects/action_plan.py b/watcher/objects/action_plan.py
index 4386eae2f..52aece120 100644
--- a/watcher/objects/action_plan.py
+++ b/watcher/objects/action_plan.py
@@ -73,6 +73,7 @@ from watcher.common import utils
from watcher.db import api as dbapi
from watcher.objects import action as action_objects
from watcher.objects import base
+from watcher.objects import efficacy_indicator as indicator_objects
from watcher.objects import utils as obj_utils
@@ -98,6 +99,7 @@ class ActionPlan(base.WatcherObject):
'audit_id': obj_utils.int_or_none,
'first_action_id': obj_utils.int_or_none,
'state': obj_utils.str_or_none,
+ 'global_efficacy': obj_utils.dict_or_none,
}
@staticmethod
@@ -192,7 +194,7 @@ class ActionPlan(base.WatcherObject):
self._from_db_object(self, db_action_plan)
def destroy(self, context=None):
- """Delete the Action from the DB.
+ """Delete the action plan from the DB.
:param context: Security context. NOTE: This should only
be used internally by the indirection_api.
@@ -201,6 +203,14 @@ class ActionPlan(base.WatcherObject):
A context should be set when instantiating the
object, e.g.: Action(context)
"""
+ related_efficacy_indicators = indicator_objects.EfficacyIndicator.list(
+ context=self._context,
+ filters={"action_plan_uuid": self.uuid})
+
+ # Cascade soft_delete of related efficacy indicators
+ for related_efficacy_indicator in related_efficacy_indicators:
+ related_efficacy_indicator.destroy()
+
self.dbapi.destroy_action_plan(self.uuid)
self.obj_reset_changes()
@@ -260,6 +270,14 @@ class ActionPlan(base.WatcherObject):
for related_action in related_actions:
related_action.soft_delete()
+ related_efficacy_indicators = indicator_objects.EfficacyIndicator.list(
+ context=self._context,
+ filters={"action_plan_uuid": self.uuid})
+
+ # Cascade soft_delete of related efficacy indicators
+ for related_efficacy_indicator in related_efficacy_indicators:
+ related_efficacy_indicator.soft_delete()
+
self.dbapi.soft_delete_action_plan(self.uuid)
self.state = State.DELETED
self.save()
diff --git a/watcher/objects/efficacy_indicator.py b/watcher/objects/efficacy_indicator.py
index 39de4eb45..2275f0017 100644
--- a/watcher/objects/efficacy_indicator.py
+++ b/watcher/objects/efficacy_indicator.py
@@ -195,5 +195,3 @@ class EfficacyIndicator(base.WatcherObject):
object, e.g.: Audit(context)
"""
self.dbapi.soft_delete_efficacy_indicator(self.uuid)
- self.state = "DELETED"
- self.save()
diff --git a/watcher/tests/api/v1/test_actions_plans.py b/watcher/tests/api/v1/test_actions_plans.py
index ee0682f32..a82158575 100644
--- a/watcher/tests/api/v1/test_actions_plans.py
+++ b/watcher/tests/api/v1/test_actions_plans.py
@@ -13,13 +13,14 @@
import datetime
import itertools
import mock
-
+import pecan
from oslo_config import cfg
from wsme import types as wtypes
from watcher.api.controllers.v1 import action_plan as api_action_plan
from watcher.applier import rpcapi as aapi
+from watcher.common import context
from watcher.common import utils
from watcher.db import api as db_api
from watcher import objects
@@ -31,7 +32,11 @@ from watcher.tests.objects import utils as obj_utils
class TestActionPlanObject(base.TestCase):
- def test_action_plan_init(self):
+ @mock.patch.object(objects.EfficacyIndicator,
+ 'list', mock.Mock(return_value=[]))
+ @mock.patch.object(pecan, 'request')
+ def test_action_plan_init(self, m_request):
+ m_request.context = context.make_context()
act_plan_dict = api_utils.action_plan_post_data()
del act_plan_dict['state']
del act_plan_dict['audit_id']
@@ -47,7 +52,8 @@ class TestListActionPlan(api_base.FunctionalTest):
self.assertEqual([], response['action_plans'])
def _assert_action_plans_fields(self, action_plan):
- action_plan_fields = ['state']
+ action_plan_fields = ['uuid', 'audit_uuid', 'state', 'global_efficacy',
+ 'efficacy_indicators']
for field in action_plan_fields:
self.assertIn(field, action_plan)
@@ -71,10 +77,18 @@ class TestListActionPlan(api_base.FunctionalTest):
self.assertEqual([], response['action_plans'])
def test_get_one_ok(self):
- action_plan = obj_utils.create_action_plan_without_audit(self.context)
+ action_plan = obj_utils.create_test_action_plan(self.context)
+ obj_utils.create_test_efficacy_indicator(
+ self.context, action_plan_id=action_plan['id'])
response = self.get_json('/action_plans/%s' % action_plan['uuid'])
self.assertEqual(action_plan.uuid, response['uuid'])
self._assert_action_plans_fields(response)
+ self.assertEqual(
+ [{'description': 'Test indicator',
+ 'name': 'test_indicator',
+ 'value': 0.0,
+ 'unit': '%'}],
+ response['efficacy_indicators'])
def test_get_one_with_first_action(self):
action_plan = obj_utils.create_test_action_plan(self.context)
diff --git a/watcher/tests/db/utils.py b/watcher/tests/db/utils.py
index a76980a2f..55702636a 100644
--- a/watcher/tests/db/utils.py
+++ b/watcher/tests/db/utils.py
@@ -120,6 +120,7 @@ def get_test_action_plan(**kwargs):
'uuid': kwargs.get('uuid', '76be87bd-3422-43f9-93a0-e85a577e3061'),
'state': kwargs.get('state', 'ONGOING'),
'audit_id': kwargs.get('audit_id', 1),
+ 'global_efficacy': kwargs.get('global_efficacy', {}),
'first_action_id': kwargs.get('first_action_id', 1),
'created_at': kwargs.get('created_at'),
'updated_at': kwargs.get('updated_at'),
diff --git a/watcher/tests/decision_engine/fake_strategies.py b/watcher/tests/decision_engine/fake_strategies.py
index 369d4fe61..7d78a2507 100644
--- a/watcher/tests/decision_engine/fake_strategies.py
+++ b/watcher/tests/decision_engine/fake_strategies.py
@@ -47,7 +47,13 @@ class FakeStrategy(base_strategy.BaseStrategy):
def get_config_opts(cls):
return []
- def execute(self, original_model):
+ def pre_execute(self):
+ pass
+
+ def do_execute(self):
+ pass
+
+ def post_execute(self):
pass
diff --git a/watcher/tests/decision_engine/planner/test_default_planner.py b/watcher/tests/decision_engine/planner/test_default_planner.py
index d27ab7999..edcfa7656 100644
--- a/watcher/tests/decision_engine/planner/test_default_planner.py
+++ b/watcher/tests/decision_engine/planner/test_default_planner.py
@@ -53,14 +53,17 @@ class SolutionFakerSingleHyp(object):
current_state_cluster.generate_scenario_3_with_2_hypervisors())
sercon.ceilometer = mock.MagicMock(
get_statistics=metrics.mock_get_statistics)
+
return sercon.execute()
class TestActionScheduling(base.DbTestCase):
+
def test_schedule_actions(self):
default_planner = pbase.DefaultPlanner(mock.Mock())
audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
- solution = dsol.DefaultSolution()
+ solution = dsol.DefaultSolution(
+ goal=mock.Mock(), strategy=mock.Mock())
parameters = {
"src_uuid_hypervisor": "server1",
@@ -86,7 +89,8 @@ class TestActionScheduling(base.DbTestCase):
def test_schedule_two_actions(self):
default_planner = pbase.DefaultPlanner(mock.Mock())
audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
- solution = dsol.DefaultSolution()
+ solution = dsol.DefaultSolution(
+ goal=mock.Mock(), strategy=mock.Mock())
parameters = {
"src_uuid_hypervisor": "server1",
diff --git a/watcher/tests/decision_engine/solution/test_default_solution.py b/watcher/tests/decision_engine/solution/test_default_solution.py
index 0c67333ad..41ee13a1b 100644
--- a/watcher/tests/decision_engine/solution/test_default_solution.py
+++ b/watcher/tests/decision_engine/solution/test_default_solution.py
@@ -14,13 +14,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import mock
+
from watcher.decision_engine.solution import default
from watcher.tests import base
class TestDefaultSolution(base.BaseTestCase):
def test_default_solution(self):
- solution = default.DefaultSolution()
+ solution = default.DefaultSolution(
+ goal=mock.Mock(), strategy=mock.Mock())
parameters = {
"src_uuid_hypervisor": "server1",
"dst_uuid_hypervisor": "server2",
diff --git a/watcher/tests/decision_engine/strategy/selector/test_strategy_selector.py b/watcher/tests/decision_engine/strategy/selector/test_strategy_selector.py
index e80f1499a..a1ada2899 100644
--- a/watcher/tests/decision_engine/strategy/selector/test_strategy_selector.py
+++ b/watcher/tests/decision_engine/strategy/selector/test_strategy_selector.py
@@ -60,7 +60,6 @@ class TestStrategySelector(base.TestCase):
@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")
+ strategy_selector = default_selector.DefaultStrategySelector("dummy")
self.assertRaises(exception.NoAvailableStrategyForGoal,
strategy_selector.select)
diff --git a/watcher/tests/decision_engine/strategy/strategies/test_basic_consolidation.py b/watcher/tests/decision_engine/strategy/strategies/test_basic_consolidation.py
index aefca9e20..a028dd1af 100644
--- a/watcher/tests/decision_engine/strategy/strategies/test_basic_consolidation.py
+++ b/watcher/tests/decision_engine/strategy/strategies/test_basic_consolidation.py
@@ -180,7 +180,7 @@ class TestBasicConsolidation(base.BaseTestCase):
) as mock_score_call:
mock_score_call.return_value = 0
solution = self.strategy.execute()
- self.assertEqual(0, solution.efficacy)
+ self.assertEqual(0, solution.efficacy.global_efficacy.value)
def test_check_parameters(self):
model = self.fake_cluster.generate_scenario_3_with_2_hypervisors()
diff --git a/watcher/tests/decision_engine/strategy/strategies/test_outlet_temp_control.py b/watcher/tests/decision_engine/strategy/strategies/test_outlet_temp_control.py
index ccb9bf8e6..c6796467d 100644
--- a/watcher/tests/decision_engine/strategy/strategies/test_outlet_temp_control.py
+++ b/watcher/tests/decision_engine/strategy/strategies/test_outlet_temp_control.py
@@ -37,7 +37,6 @@ class TestOutletTempControl(base.BaseTestCase):
super(TestOutletTempControl, self).setUp()
# fake metrics
self.fake_metrics = faker_metrics_collector.FakerMetricsCollector()
-
# fake cluster
self.fake_cluster = faker_cluster_state.FakerModelCollector()
diff --git a/watcher/tests/objects/test_action_plan.py b/watcher/tests/objects/test_action_plan.py
index df10880bc..9ed9980da 100644
--- a/watcher/tests/objects/test_action_plan.py
+++ b/watcher/tests/objects/test_action_plan.py
@@ -74,19 +74,69 @@ class TestActionPlanObject(base.DbTestCase):
self.assertEqual(self.context, action_plan._context)
def test_destroy(self):
+ efficacy_indicator = utils.get_test_efficacy_indicator(
+ action_plan_id=self.fake_action_plan['id'])
uuid = self.fake_action_plan['uuid']
- with mock.patch.object(self.dbapi, 'get_action_plan_by_uuid',
- autospec=True) as mock_get_action_plan:
- mock_get_action_plan.return_value = self.fake_action_plan
- with mock.patch.object(self.dbapi, 'destroy_action_plan',
- autospec=True) as mock_destroy_action_plan:
- action_plan = objects.ActionPlan.get_by_uuid(
- self.context, uuid)
- action_plan.destroy()
- mock_get_action_plan.assert_called_once_with(
- self.context, uuid)
- mock_destroy_action_plan.assert_called_once_with(uuid)
- self.assertEqual(self.context, action_plan._context)
+
+ with mock.patch.multiple(
+ self.dbapi, autospec=True,
+ get_action_plan_by_uuid=mock.DEFAULT,
+ destroy_action_plan=mock.DEFAULT,
+ get_efficacy_indicator_list=mock.DEFAULT,
+ destroy_efficacy_indicator=mock.DEFAULT,
+ ) as m_dict:
+ m_get_action_plan = m_dict['get_action_plan_by_uuid']
+ m_destroy_action_plan = m_dict['destroy_action_plan']
+ m_get_efficacy_indicator_list = (
+ m_dict['get_efficacy_indicator_list'])
+ m_destroy_efficacy_indicator = m_dict['destroy_efficacy_indicator']
+ m_get_action_plan.return_value = self.fake_action_plan
+ m_get_efficacy_indicator_list.return_value = [efficacy_indicator]
+ action_plan = objects.ActionPlan.get_by_uuid(self.context, uuid)
+ action_plan.destroy()
+ m_get_action_plan.assert_called_once_with(self.context, uuid)
+ m_get_efficacy_indicator_list.assert_called_once_with(
+ self.context, filters={"action_plan_uuid": uuid},
+ limit=None, marker=None, sort_dir=None, sort_key=None)
+ m_destroy_action_plan.assert_called_once_with(uuid)
+ m_destroy_efficacy_indicator.assert_called_once_with(
+ efficacy_indicator['uuid'])
+ self.assertEqual(self.context, action_plan._context)
+
+ def test_soft_delete(self):
+ efficacy_indicator = utils.get_test_efficacy_indicator(
+ action_plan_id=self.fake_action_plan['id'])
+ uuid = self.fake_action_plan['uuid']
+
+ with mock.patch.multiple(
+ self.dbapi, autospec=True,
+ get_action_plan_by_uuid=mock.DEFAULT,
+ soft_delete_action_plan=mock.DEFAULT,
+ update_action_plan=mock.DEFAULT,
+ get_efficacy_indicator_list=mock.DEFAULT,
+ soft_delete_efficacy_indicator=mock.DEFAULT,
+ ) as m_dict:
+ m_get_action_plan = m_dict['get_action_plan_by_uuid']
+ m_soft_delete_action_plan = m_dict['soft_delete_action_plan']
+ m_get_efficacy_indicator_list = (
+ m_dict['get_efficacy_indicator_list'])
+ m_soft_delete_efficacy_indicator = (
+ m_dict['soft_delete_efficacy_indicator'])
+ m_update_action_plan = m_dict['update_action_plan']
+ m_get_action_plan.return_value = self.fake_action_plan
+ m_get_efficacy_indicator_list.return_value = [efficacy_indicator]
+ action_plan = objects.ActionPlan.get_by_uuid(self.context, uuid)
+ action_plan.soft_delete()
+ m_get_action_plan.assert_called_once_with(self.context, uuid)
+ m_get_efficacy_indicator_list.assert_called_once_with(
+ self.context, filters={"action_plan_uuid": uuid},
+ limit=None, marker=None, sort_dir=None, sort_key=None)
+ m_soft_delete_action_plan.assert_called_once_with(uuid)
+ m_soft_delete_efficacy_indicator.assert_called_once_with(
+ efficacy_indicator['uuid'])
+ m_update_action_plan.assert_called_once_with(
+ uuid, {'state': 'DELETED'})
+ self.assertEqual(self.context, action_plan._context)
def test_save(self):
uuid = self.fake_action_plan['uuid']
diff --git a/watcher/tests/objects/utils.py b/watcher/tests/objects/utils.py
index 176d4e749..9ee6082a7 100644
--- a/watcher/tests/objects/utils.py
+++ b/watcher/tests/objects/utils.py
@@ -189,3 +189,30 @@ def create_test_strategy(context, **kw):
strategy = get_test_strategy(context, **kw)
strategy.create()
return strategy
+
+
+def get_test_efficacy_indicator(context, **kw):
+ """Return a EfficacyIndicator object with appropriate attributes.
+
+ NOTE: The object leaves the attributes marked as changed, such
+ that a create() could be used to commit it to the DB.
+ """
+ db_efficacy_indicator = db_utils.get_test_efficacy_indicator(**kw)
+ # Let DB generate ID if it isn't specified explicitly
+ if 'id' not in kw:
+ del db_efficacy_indicator['id']
+ efficacy_indicator = objects.EfficacyIndicator(context)
+ for key in db_efficacy_indicator:
+ setattr(efficacy_indicator, key, db_efficacy_indicator[key])
+ return efficacy_indicator
+
+
+def create_test_efficacy_indicator(context, **kw):
+ """Create and return a test efficacy indicator object.
+
+ Create a efficacy indicator in the DB and return a EfficacyIndicator object
+ with appropriate attributes.
+ """
+ efficacy_indicator = get_test_efficacy_indicator(context, **kw)
+ efficacy_indicator.create()
+ return efficacy_indicator