Merge "Added efficacy indicators to /action_plans"
This commit is contained in:
@@ -56,6 +56,7 @@ machine <action_plan_state_machine>`.
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from oslo_log import log
|
||||||
import pecan
|
import pecan
|
||||||
from pecan import rest
|
from pecan import rest
|
||||||
import wsme
|
import wsme
|
||||||
@@ -66,6 +67,7 @@ from watcher._i18n import _
|
|||||||
from watcher.api.controllers import base
|
from watcher.api.controllers import base
|
||||||
from watcher.api.controllers import link
|
from watcher.api.controllers import link
|
||||||
from watcher.api.controllers.v1 import collection
|
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 types
|
||||||
from watcher.api.controllers.v1 import utils as api_utils
|
from watcher.api.controllers.v1 import utils as api_utils
|
||||||
from watcher.applier import rpcapi
|
from watcher.applier import rpcapi
|
||||||
@@ -73,6 +75,8 @@ from watcher.common import exception
|
|||||||
from watcher import objects
|
from watcher import objects
|
||||||
from watcher.objects import action_plan as ap_objects
|
from watcher.objects import action_plan as ap_objects
|
||||||
|
|
||||||
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ActionPlanPatchType(types.JsonPatchType):
|
class ActionPlanPatchType(types.JsonPatchType):
|
||||||
|
|
||||||
@@ -113,6 +117,7 @@ class ActionPlan(base.APIBase):
|
|||||||
|
|
||||||
_audit_uuid = None
|
_audit_uuid = None
|
||||||
_first_action_uuid = None
|
_first_action_uuid = None
|
||||||
|
_efficacy_indicators = None
|
||||||
|
|
||||||
def _get_audit_uuid(self):
|
def _get_audit_uuid(self):
|
||||||
return self._audit_uuid
|
return self._audit_uuid
|
||||||
@@ -143,6 +148,34 @@ class ActionPlan(base.APIBase):
|
|||||||
except exception.ActionNotFound:
|
except exception.ActionNotFound:
|
||||||
self._first_action_uuid = None
|
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)
|
uuid = wtypes.wsattr(types.uuid, readonly=True)
|
||||||
"""Unique UUID for this action plan"""
|
"""Unique UUID for this action plan"""
|
||||||
|
|
||||||
@@ -155,6 +188,14 @@ class ActionPlan(base.APIBase):
|
|||||||
mandatory=True)
|
mandatory=True)
|
||||||
"""The UUID of the audit this port belongs to"""
|
"""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
|
state = wtypes.text
|
||||||
"""This action plan state"""
|
"""This action plan state"""
|
||||||
|
|
||||||
@@ -163,7 +204,6 @@ class ActionPlan(base.APIBase):
|
|||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(ActionPlan, self).__init__()
|
super(ActionPlan, self).__init__()
|
||||||
|
|
||||||
self.fields = []
|
self.fields = []
|
||||||
fields = list(objects.ActionPlan.fields)
|
fields = list(objects.ActionPlan.fields)
|
||||||
for field in fields:
|
for field in fields:
|
||||||
@@ -175,6 +215,7 @@ class ActionPlan(base.APIBase):
|
|||||||
|
|
||||||
self.fields.append('audit_uuid')
|
self.fields.append('audit_uuid')
|
||||||
self.fields.append('first_action_uuid')
|
self.fields.append('first_action_uuid')
|
||||||
|
self.fields.append('efficacy_indicators')
|
||||||
|
|
||||||
setattr(self, 'audit_uuid', kwargs.get('audit_id', wtypes.Unset))
|
setattr(self, 'audit_uuid', kwargs.get('audit_id', wtypes.Unset))
|
||||||
setattr(self, 'first_action_uuid',
|
setattr(self, 'first_action_uuid',
|
||||||
@@ -184,12 +225,13 @@ class ActionPlan(base.APIBase):
|
|||||||
def _convert_with_links(action_plan, url, expand=True):
|
def _convert_with_links(action_plan, url, expand=True):
|
||||||
if not expand:
|
if not expand:
|
||||||
action_plan.unset_fields_except(
|
action_plan.unset_fields_except(
|
||||||
['uuid', 'state', 'updated_at',
|
['uuid', 'state', 'efficacy_indicators', 'global_efficacy',
|
||||||
'audit_uuid', 'first_action_uuid'])
|
'updated_at', 'audit_uuid', 'first_action_uuid'])
|
||||||
|
|
||||||
action_plan.links = [link.Link.make_link(
|
action_plan.links = [
|
||||||
'self', url,
|
link.Link.make_link(
|
||||||
'action_plans', action_plan.uuid),
|
'self', url,
|
||||||
|
'action_plans', action_plan.uuid),
|
||||||
link.Link.make_link(
|
link.Link.make_link(
|
||||||
'bookmark', url,
|
'bookmark', url,
|
||||||
'action_plans', action_plan.uuid,
|
'action_plans', action_plan.uuid,
|
||||||
@@ -211,6 +253,12 @@ class ActionPlan(base.APIBase):
|
|||||||
updated_at=datetime.datetime.utcnow())
|
updated_at=datetime.datetime.utcnow())
|
||||||
sample._first_action_uuid = '57eaf9ab-5aaa-4f7e-bdf7-9a140ac7a720'
|
sample._first_action_uuid = '57eaf9ab-5aaa-4f7e-bdf7-9a140ac7a720'
|
||||||
sample._audit_uuid = 'abcee106-14d3-4515-b744-5a26885cf6f6'
|
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)
|
return cls._convert_with_links(sample, 'http://localhost:9322', expand)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -311,6 +311,11 @@ class InvalidIndicatorValue(WatcherException):
|
|||||||
"and spec type '%(spec_type)s' is invalid.")
|
"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):
|
class NoMetricValuesForVM(WatcherException):
|
||||||
msg_fmt = _("No values returned by %(resource_id)s for %(metric_name)s.")
|
msg_fmt = _("No values returned by %(resource_id)s for %(metric_name)s.")
|
||||||
|
|
||||||
|
|||||||
@@ -59,19 +59,21 @@ class DefaultPlanner(base.BasePlanner):
|
|||||||
|
|
||||||
def schedule(self, context, audit_id, solution):
|
def schedule(self, context, audit_id, solution):
|
||||||
LOG.debug('Create an action plan for the audit uuid: %s ', audit_id)
|
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)
|
actions = list(solution.actions)
|
||||||
to_schedule = []
|
to_schedule = []
|
||||||
for action in actions:
|
for action in actions:
|
||||||
json_action = self.create_action(action_plan_id=action_plan.id,
|
json_action = self.create_action(
|
||||||
action_type=action.get(
|
action_plan_id=action_plan.id,
|
||||||
'action_type'),
|
action_type=action.get('action_type'),
|
||||||
input_parameters=action.get(
|
input_parameters=action.get('input_parameters'))
|
||||||
'input_parameters'))
|
|
||||||
to_schedule.append((self.priorities[action.get('action_type')],
|
to_schedule.append((self.priorities[action.get('action_type')],
|
||||||
json_action))
|
json_action))
|
||||||
|
|
||||||
|
self._create_efficacy_indicators(
|
||||||
|
context, action_plan.id, solution.efficacy_indicators)
|
||||||
|
|
||||||
# scheduling
|
# scheduling
|
||||||
scheduled = sorted(to_schedule, key=lambda x: (x[0]))
|
scheduled = sorted(to_schedule, key=lambda x: (x[0]))
|
||||||
if len(scheduled) == 0:
|
if len(scheduled) == 0:
|
||||||
@@ -96,19 +98,38 @@ class DefaultPlanner(base.BasePlanner):
|
|||||||
|
|
||||||
return action_plan
|
return action_plan
|
||||||
|
|
||||||
def _create_action_plan(self, context, audit_id):
|
def _create_action_plan(self, context, audit_id, solution):
|
||||||
action_plan_dict = {
|
action_plan_dict = {
|
||||||
'uuid': utils.generate_uuid(),
|
'uuid': utils.generate_uuid(),
|
||||||
'audit_id': audit_id,
|
'audit_id': audit_id,
|
||||||
'first_action_id': None,
|
'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 = objects.ActionPlan(context, **action_plan_dict)
|
||||||
new_action_plan.create(context)
|
new_action_plan.create(context)
|
||||||
new_action_plan.save()
|
|
||||||
return new_action_plan
|
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):
|
def _create_action(self, context, _action, parent_action):
|
||||||
try:
|
try:
|
||||||
LOG.debug("Creating the %s in watcher db",
|
LOG.debug("Creating the %s in watcher db",
|
||||||
|
|||||||
@@ -37,59 +37,62 @@ applied.
|
|||||||
|
|
||||||
Two approaches to dealing with this can be envisaged:
|
Two approaches to dealing with this can be envisaged:
|
||||||
|
|
||||||
- **fully automated mode**: only the :ref:`Solution <solution_definition>`
|
- **fully automated mode**: only the :ref:`Solution <solution_definition>`
|
||||||
with the highest ranking (i.e., the highest
|
with the highest ranking (i.e., the highest
|
||||||
:ref:`Optimization Efficiency <efficiency_definition>`)
|
:ref:`Optimization Efficacy <efficacy_definition>`) will be sent to the
|
||||||
will be sent to the :ref:`Watcher Planner <watcher_planner_definition>` and
|
:ref:`Watcher Planner <watcher_planner_definition>` and translated into
|
||||||
translated into concrete :ref:`Actions <action_definition>`.
|
concrete :ref:`Actions <action_definition>`.
|
||||||
- **manual mode**: several :ref:`Solutions <solution_definition>` are proposed
|
- **manual mode**: several :ref:`Solutions <solution_definition>` are proposed
|
||||||
to the :ref:`Administrator <administrator_definition>` with a detailed
|
to the :ref:`Administrator <administrator_definition>` with a detailed
|
||||||
measurement of the estimated
|
measurement of the estimated :ref:`Optimization Efficacy
|
||||||
:ref:`Optimization Efficiency <efficiency_definition>` and he/she decides
|
<efficacy_definition>` and he/she decides which one will be launched.
|
||||||
which one will be launched.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
from watcher.decision_engine.solution import efficacy
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
class BaseSolution(object):
|
class BaseSolution(object):
|
||||||
def __init__(self):
|
def __init__(self, goal, strategy):
|
||||||
self._origin = None
|
"""Base Solution constructor
|
||||||
self._model = None
|
|
||||||
self._efficacy = 0
|
: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
|
@property
|
||||||
def efficacy(self):
|
def global_efficacy(self):
|
||||||
return self._efficacy
|
return self.efficacy.global_efficacy
|
||||||
|
|
||||||
@efficacy.setter
|
|
||||||
def efficacy(self, e):
|
|
||||||
self._efficacy = e
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model(self):
|
def efficacy_indicators(self):
|
||||||
return self._model
|
return self.efficacy.indicators
|
||||||
|
|
||||||
@model.setter
|
def compute_global_efficacy(self):
|
||||||
def model(self, m):
|
"""Compute the global efficacy given a map of efficacy indicators"""
|
||||||
self._model = m
|
self.efficacy.compute_global_efficacy()
|
||||||
|
|
||||||
@property
|
def set_efficacy_indicators(self, **indicators_map):
|
||||||
def origin(self):
|
"""Set the efficacy indicators mapping (no validation)
|
||||||
return self._origin
|
|
||||||
|
|
||||||
@origin.setter
|
:param indicators_map: mapping between the indicator name and its value
|
||||||
def origin(self, m):
|
:type indicators_map: dict {`str`: `object`}
|
||||||
self._origin = m
|
"""
|
||||||
|
self.efficacy.set_efficacy_indicators(**indicators_map)
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def add_action(self,
|
def add_action(self, action_type, resource_id, input_parameters=None):
|
||||||
action_type,
|
"""Add a new Action in the Solution
|
||||||
resource_id,
|
|
||||||
input_parameters=None):
|
|
||||||
"""Add a new Action in the Action Plan
|
|
||||||
|
|
||||||
:param action_type: the unique id of an action type defined in
|
:param action_type: the unique id of an action type defined in
|
||||||
entry point 'watcher_actions'
|
entry point 'watcher_actions'
|
||||||
|
|||||||
@@ -22,19 +22,21 @@ from watcher.decision_engine.solution import base
|
|||||||
|
|
||||||
|
|
||||||
class DefaultSolution(base.BaseSolution):
|
class DefaultSolution(base.BaseSolution):
|
||||||
def __init__(self):
|
def __init__(self, goal, strategy):
|
||||||
"""Stores a set of actions generated by a strategy
|
"""Stores a set of actions generated by a strategy
|
||||||
|
|
||||||
The DefaultSolution class store a set of actions generated by a
|
The DefaultSolution class store a set of actions generated by a
|
||||||
strategy in order to achieve the goal.
|
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 = []
|
self._actions = []
|
||||||
|
|
||||||
def add_action(self, action_type,
|
def add_action(self, action_type, input_parameters=None, resource_id=None):
|
||||||
input_parameters=None,
|
|
||||||
resource_id=None):
|
|
||||||
|
|
||||||
if input_parameters is not None:
|
if input_parameters is not None:
|
||||||
if baction.BaseAction.RESOURCE_ID in input_parameters.keys():
|
if baction.BaseAction.RESOURCE_ID in input_parameters.keys():
|
||||||
raise exception.ReservedWord(name=baction.BaseAction.
|
raise exception.ReservedWord(name=baction.BaseAction.
|
||||||
|
|||||||
@@ -14,8 +14,20 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import numbers
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from watcher._i18n import _
|
||||||
|
from watcher.common import exception
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class IndicatorsMap(utils.Struct):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Indicator(utils.Struct):
|
class Indicator(utils.Struct):
|
||||||
|
|
||||||
@@ -24,4 +36,70 @@ class Indicator(utils.Struct):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.description = description
|
self.description = description
|
||||||
self.unit = unit
|
self.unit = unit
|
||||||
|
if not isinstance(value, numbers.Number):
|
||||||
|
raise exception.InvalidIndicatorValue(
|
||||||
|
_("An indicator value should be a number"))
|
||||||
self.value = value
|
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)
|
||||||
|
|||||||
@@ -66,11 +66,12 @@ class BaseStrategy(loadable.Loadable):
|
|||||||
super(BaseStrategy, self).__init__(config)
|
super(BaseStrategy, self).__init__(config)
|
||||||
self._name = self.get_name()
|
self._name = self.get_name()
|
||||||
self._display_name = self.get_display_name()
|
self._display_name = self.get_display_name()
|
||||||
|
self._goal = self.get_goal()
|
||||||
# default strategy level
|
# default strategy level
|
||||||
self._strategy_level = level.StrategyLevel.conservative
|
self._strategy_level = level.StrategyLevel.conservative
|
||||||
self._cluster_state_collector = None
|
self._cluster_state_collector = None
|
||||||
# the solution given by the strategy
|
# the solution given by the strategy
|
||||||
self._solution = default.DefaultSolution()
|
self._solution = default.DefaultSolution(goal=self.goal, strategy=self)
|
||||||
self._osc = osc
|
self._osc = osc
|
||||||
self._collector_manager = None
|
self._collector_manager = None
|
||||||
self._model = None
|
self._model = None
|
||||||
@@ -99,7 +100,7 @@ class BaseStrategy(loadable.Loadable):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_goal_name(cls):
|
def get_goal_name(cls):
|
||||||
"""The goal name for the strategy"""
|
"""The goal name the strategy achieves"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -151,6 +152,8 @@ class BaseStrategy(loadable.Loadable):
|
|||||||
self.do_execute()
|
self.do_execute()
|
||||||
self.post_execute()
|
self.post_execute()
|
||||||
|
|
||||||
|
self.solution.compute_global_efficacy()
|
||||||
|
|
||||||
return self.solution
|
return self.solution
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -141,7 +141,6 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
def check_migration(self, src_hypervisor, dest_hypervisor, vm_to_mig):
|
def check_migration(self, src_hypervisor, dest_hypervisor, vm_to_mig):
|
||||||
"""Check if the migration is possible
|
"""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 src_hypervisor: the current node of the virtual machine
|
||||||
:param dest_hypervisor: the destination of the virtual machine
|
:param dest_hypervisor: the destination of the virtual machine
|
||||||
:param vm_to_mig: 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
|
check the threshold value defined by the ratio of
|
||||||
aggregated CPU capacity of VMs on one node to CPU capacity
|
aggregated CPU capacity of VMs on one node to CPU capacity
|
||||||
of this node must not exceed the threshold value.
|
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 dest_hypervisor: the destination of the virtual machine
|
||||||
:param total_cores
|
:param total_cores
|
||||||
:param total_disk
|
:param total_disk
|
||||||
@@ -216,7 +214,6 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
total_memory_used):
|
total_memory_used):
|
||||||
"""Calculate weight of every resource
|
"""Calculate weight of every resource
|
||||||
|
|
||||||
:param self.model:
|
|
||||||
:param element:
|
:param element:
|
||||||
:param total_cores_used:
|
:param total_cores_used:
|
||||||
:param total_disk_used:
|
:param total_disk_used:
|
||||||
@@ -482,4 +479,7 @@ class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
LOG.debug(infos)
|
LOG.debug(infos)
|
||||||
|
|
||||||
def post_execute(self):
|
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,
|
||||||
|
)
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
:param model: model_root object
|
:param model: model_root object
|
||||||
:return: {'cpu': <0,1>, 'ram': <0,1>, 'disk': <0,1>}
|
:return: {'cpu': <0,1>, 'ram': <0,1>, 'disk': <0,1>}
|
||||||
"""
|
"""
|
||||||
hypervisors = self.model.get_all_hypervisors().values()
|
hypervisors = model.get_all_hypervisors().values()
|
||||||
rcu = {}
|
rcu = {}
|
||||||
counters = {}
|
counters = {}
|
||||||
for hypervisor in hypervisors:
|
for hypervisor in hypervisors:
|
||||||
@@ -517,7 +517,7 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
:param original_model: root_model object
|
:param original_model: root_model object
|
||||||
"""
|
"""
|
||||||
LOG.info(_LI('Executing Smart Strategy'))
|
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)
|
rcu = self.get_relative_cluster_utilization(model)
|
||||||
self.ceilometer_vm_data_cache = dict()
|
self.ceilometer_vm_data_cache = dict()
|
||||||
|
|
||||||
@@ -546,9 +546,9 @@ class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
|||||||
|
|
||||||
LOG.debug(info)
|
LOG.debug(info)
|
||||||
|
|
||||||
self.solution.model = model
|
|
||||||
self.solution.efficacy = rcu_after['cpu']
|
|
||||||
|
|
||||||
def post_execute(self):
|
def post_execute(self):
|
||||||
# TODO(v-francoise): Add the indicators to the solution
|
# self.solution.efficacy = rcu_after['cpu']
|
||||||
pass
|
self.solution.set_efficacy_indicators(
|
||||||
|
released_compute_nodes_count=self.number_of_migrations,
|
||||||
|
vm_migrations_count=self.number_of_released_hypervisors,
|
||||||
|
)
|
||||||
|
|||||||
@@ -362,7 +362,6 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
|
|||||||
|
|
||||||
def fill_solution(self):
|
def fill_solution(self):
|
||||||
self.solution.model = self.model
|
self.solution.model = self.model
|
||||||
self.solution.efficacy = 100
|
|
||||||
return self.solution
|
return self.solution
|
||||||
|
|
||||||
def pre_execute(self):
|
def pre_execute(self):
|
||||||
@@ -403,11 +402,10 @@ class WorkloadStabilization(base.WorkloadStabilizationBaseStrategy):
|
|||||||
break
|
break
|
||||||
if balanced:
|
if balanced:
|
||||||
break
|
break
|
||||||
return self.fill_solution()
|
|
||||||
|
|
||||||
def post_execute(self):
|
def post_execute(self):
|
||||||
"""Post-execution phase
|
"""Post-execution phase
|
||||||
|
|
||||||
This can be used to compute the global efficacy
|
This can be used to compute the global efficacy
|
||||||
"""
|
"""
|
||||||
self.solution.model = self.model
|
self.fill_solution()
|
||||||
|
|||||||
@@ -277,8 +277,8 @@ class Syncer(object):
|
|||||||
display_name=goal_cls.get_translatable_display_name(),
|
display_name=goal_cls.get_translatable_display_name(),
|
||||||
efficacy_specification=tuple(
|
efficacy_specification=tuple(
|
||||||
IndicatorSpec(**indicator.to_dict())
|
IndicatorSpec(**indicator.to_dict())
|
||||||
for indicator in goal_cls.get_efficacy_specification()
|
for indicator in goal_cls.get_efficacy_specification(
|
||||||
.get_indicators_specifications()))
|
).get_indicators_specifications()))
|
||||||
|
|
||||||
for _, strategy_cls in implemented_strategies.items():
|
for _, strategy_cls in implemented_strategies.items():
|
||||||
strategies_map[strategy_cls.get_name()] = StrategyMapping(
|
strategies_map[strategy_cls.get_name()] = StrategyMapping(
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ from watcher.common import utils
|
|||||||
from watcher.db import api as dbapi
|
from watcher.db import api as dbapi
|
||||||
from watcher.objects import action as action_objects
|
from watcher.objects import action as action_objects
|
||||||
from watcher.objects import base
|
from watcher.objects import base
|
||||||
|
from watcher.objects import efficacy_indicator as indicator_objects
|
||||||
from watcher.objects import utils as obj_utils
|
from watcher.objects import utils as obj_utils
|
||||||
|
|
||||||
|
|
||||||
@@ -98,6 +99,7 @@ class ActionPlan(base.WatcherObject):
|
|||||||
'audit_id': obj_utils.int_or_none,
|
'audit_id': obj_utils.int_or_none,
|
||||||
'first_action_id': obj_utils.int_or_none,
|
'first_action_id': obj_utils.int_or_none,
|
||||||
'state': obj_utils.str_or_none,
|
'state': obj_utils.str_or_none,
|
||||||
|
'global_efficacy': obj_utils.dict_or_none,
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -192,7 +194,7 @@ class ActionPlan(base.WatcherObject):
|
|||||||
self._from_db_object(self, db_action_plan)
|
self._from_db_object(self, db_action_plan)
|
||||||
|
|
||||||
def destroy(self, context=None):
|
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
|
:param context: Security context. NOTE: This should only
|
||||||
be used internally by the indirection_api.
|
be used internally by the indirection_api.
|
||||||
@@ -201,6 +203,14 @@ class ActionPlan(base.WatcherObject):
|
|||||||
A context should be set when instantiating the
|
A context should be set when instantiating the
|
||||||
object, e.g.: Action(context)
|
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.dbapi.destroy_action_plan(self.uuid)
|
||||||
self.obj_reset_changes()
|
self.obj_reset_changes()
|
||||||
|
|
||||||
@@ -260,6 +270,14 @@ class ActionPlan(base.WatcherObject):
|
|||||||
for related_action in related_actions:
|
for related_action in related_actions:
|
||||||
related_action.soft_delete()
|
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.dbapi.soft_delete_action_plan(self.uuid)
|
||||||
self.state = State.DELETED
|
self.state = State.DELETED
|
||||||
self.save()
|
self.save()
|
||||||
|
|||||||
@@ -195,5 +195,3 @@ class EfficacyIndicator(base.WatcherObject):
|
|||||||
object, e.g.: Audit(context)
|
object, e.g.: Audit(context)
|
||||||
"""
|
"""
|
||||||
self.dbapi.soft_delete_efficacy_indicator(self.uuid)
|
self.dbapi.soft_delete_efficacy_indicator(self.uuid)
|
||||||
self.state = "DELETED"
|
|
||||||
self.save()
|
|
||||||
|
|||||||
@@ -13,13 +13,14 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import itertools
|
import itertools
|
||||||
import mock
|
import mock
|
||||||
|
import pecan
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from wsme import types as wtypes
|
from wsme import types as wtypes
|
||||||
|
|
||||||
from watcher.api.controllers.v1 import action_plan as api_action_plan
|
from watcher.api.controllers.v1 import action_plan as api_action_plan
|
||||||
from watcher.applier import rpcapi as aapi
|
from watcher.applier import rpcapi as aapi
|
||||||
|
from watcher.common import context
|
||||||
from watcher.common import utils
|
from watcher.common import utils
|
||||||
from watcher.db import api as db_api
|
from watcher.db import api as db_api
|
||||||
from watcher import objects
|
from watcher import objects
|
||||||
@@ -31,7 +32,11 @@ from watcher.tests.objects import utils as obj_utils
|
|||||||
|
|
||||||
class TestActionPlanObject(base.TestCase):
|
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()
|
act_plan_dict = api_utils.action_plan_post_data()
|
||||||
del act_plan_dict['state']
|
del act_plan_dict['state']
|
||||||
del act_plan_dict['audit_id']
|
del act_plan_dict['audit_id']
|
||||||
@@ -47,7 +52,8 @@ class TestListActionPlan(api_base.FunctionalTest):
|
|||||||
self.assertEqual([], response['action_plans'])
|
self.assertEqual([], response['action_plans'])
|
||||||
|
|
||||||
def _assert_action_plans_fields(self, action_plan):
|
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:
|
for field in action_plan_fields:
|
||||||
self.assertIn(field, action_plan)
|
self.assertIn(field, action_plan)
|
||||||
|
|
||||||
@@ -71,10 +77,18 @@ class TestListActionPlan(api_base.FunctionalTest):
|
|||||||
self.assertEqual([], response['action_plans'])
|
self.assertEqual([], response['action_plans'])
|
||||||
|
|
||||||
def test_get_one_ok(self):
|
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'])
|
response = self.get_json('/action_plans/%s' % action_plan['uuid'])
|
||||||
self.assertEqual(action_plan.uuid, response['uuid'])
|
self.assertEqual(action_plan.uuid, response['uuid'])
|
||||||
self._assert_action_plans_fields(response)
|
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):
|
def test_get_one_with_first_action(self):
|
||||||
action_plan = obj_utils.create_test_action_plan(self.context)
|
action_plan = obj_utils.create_test_action_plan(self.context)
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ def get_test_action_plan(**kwargs):
|
|||||||
'uuid': kwargs.get('uuid', '76be87bd-3422-43f9-93a0-e85a577e3061'),
|
'uuid': kwargs.get('uuid', '76be87bd-3422-43f9-93a0-e85a577e3061'),
|
||||||
'state': kwargs.get('state', 'ONGOING'),
|
'state': kwargs.get('state', 'ONGOING'),
|
||||||
'audit_id': kwargs.get('audit_id', 1),
|
'audit_id': kwargs.get('audit_id', 1),
|
||||||
|
'global_efficacy': kwargs.get('global_efficacy', {}),
|
||||||
'first_action_id': kwargs.get('first_action_id', 1),
|
'first_action_id': kwargs.get('first_action_id', 1),
|
||||||
'created_at': kwargs.get('created_at'),
|
'created_at': kwargs.get('created_at'),
|
||||||
'updated_at': kwargs.get('updated_at'),
|
'updated_at': kwargs.get('updated_at'),
|
||||||
|
|||||||
@@ -47,7 +47,13 @@ class FakeStrategy(base_strategy.BaseStrategy):
|
|||||||
def get_config_opts(cls):
|
def get_config_opts(cls):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def execute(self, original_model):
|
def pre_execute(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def do_execute(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def post_execute(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -53,14 +53,17 @@ class SolutionFakerSingleHyp(object):
|
|||||||
current_state_cluster.generate_scenario_3_with_2_hypervisors())
|
current_state_cluster.generate_scenario_3_with_2_hypervisors())
|
||||||
sercon.ceilometer = mock.MagicMock(
|
sercon.ceilometer = mock.MagicMock(
|
||||||
get_statistics=metrics.mock_get_statistics)
|
get_statistics=metrics.mock_get_statistics)
|
||||||
|
|
||||||
return sercon.execute()
|
return sercon.execute()
|
||||||
|
|
||||||
|
|
||||||
class TestActionScheduling(base.DbTestCase):
|
class TestActionScheduling(base.DbTestCase):
|
||||||
|
|
||||||
def test_schedule_actions(self):
|
def test_schedule_actions(self):
|
||||||
default_planner = pbase.DefaultPlanner(mock.Mock())
|
default_planner = pbase.DefaultPlanner(mock.Mock())
|
||||||
audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
|
audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
|
||||||
solution = dsol.DefaultSolution()
|
solution = dsol.DefaultSolution(
|
||||||
|
goal=mock.Mock(), strategy=mock.Mock())
|
||||||
|
|
||||||
parameters = {
|
parameters = {
|
||||||
"src_uuid_hypervisor": "server1",
|
"src_uuid_hypervisor": "server1",
|
||||||
@@ -86,7 +89,8 @@ class TestActionScheduling(base.DbTestCase):
|
|||||||
def test_schedule_two_actions(self):
|
def test_schedule_two_actions(self):
|
||||||
default_planner = pbase.DefaultPlanner(mock.Mock())
|
default_planner = pbase.DefaultPlanner(mock.Mock())
|
||||||
audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
|
audit = db_utils.create_test_audit(uuid=utils.generate_uuid())
|
||||||
solution = dsol.DefaultSolution()
|
solution = dsol.DefaultSolution(
|
||||||
|
goal=mock.Mock(), strategy=mock.Mock())
|
||||||
|
|
||||||
parameters = {
|
parameters = {
|
||||||
"src_uuid_hypervisor": "server1",
|
"src_uuid_hypervisor": "server1",
|
||||||
|
|||||||
@@ -14,13 +14,16 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
from watcher.decision_engine.solution import default
|
from watcher.decision_engine.solution import default
|
||||||
from watcher.tests import base
|
from watcher.tests import base
|
||||||
|
|
||||||
|
|
||||||
class TestDefaultSolution(base.BaseTestCase):
|
class TestDefaultSolution(base.BaseTestCase):
|
||||||
def test_default_solution(self):
|
def test_default_solution(self):
|
||||||
solution = default.DefaultSolution()
|
solution = default.DefaultSolution(
|
||||||
|
goal=mock.Mock(), strategy=mock.Mock())
|
||||||
parameters = {
|
parameters = {
|
||||||
"src_uuid_hypervisor": "server1",
|
"src_uuid_hypervisor": "server1",
|
||||||
"dst_uuid_hypervisor": "server2",
|
"dst_uuid_hypervisor": "server2",
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ class TestStrategySelector(base.TestCase):
|
|||||||
@mock.patch.object(default_loader.DefaultStrategyLoader, 'list_available')
|
@mock.patch.object(default_loader.DefaultStrategyLoader, 'list_available')
|
||||||
def test_select_no_available_strategy_for_goal(self, m_list_available):
|
def test_select_no_available_strategy_for_goal(self, m_list_available):
|
||||||
m_list_available.return_value = {}
|
m_list_available.return_value = {}
|
||||||
strategy_selector = default_selector.DefaultStrategySelector(
|
strategy_selector = default_selector.DefaultStrategySelector("dummy")
|
||||||
"dummy")
|
|
||||||
self.assertRaises(exception.NoAvailableStrategyForGoal,
|
self.assertRaises(exception.NoAvailableStrategyForGoal,
|
||||||
strategy_selector.select)
|
strategy_selector.select)
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ class TestBasicConsolidation(base.BaseTestCase):
|
|||||||
) as mock_score_call:
|
) as mock_score_call:
|
||||||
mock_score_call.return_value = 0
|
mock_score_call.return_value = 0
|
||||||
solution = self.strategy.execute()
|
solution = self.strategy.execute()
|
||||||
self.assertEqual(0, solution.efficacy)
|
self.assertEqual(0, solution.efficacy.global_efficacy.value)
|
||||||
|
|
||||||
def test_check_parameters(self):
|
def test_check_parameters(self):
|
||||||
model = self.fake_cluster.generate_scenario_3_with_2_hypervisors()
|
model = self.fake_cluster.generate_scenario_3_with_2_hypervisors()
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ class TestOutletTempControl(base.BaseTestCase):
|
|||||||
super(TestOutletTempControl, self).setUp()
|
super(TestOutletTempControl, self).setUp()
|
||||||
# fake metrics
|
# fake metrics
|
||||||
self.fake_metrics = faker_metrics_collector.FakerMetricsCollector()
|
self.fake_metrics = faker_metrics_collector.FakerMetricsCollector()
|
||||||
|
|
||||||
# fake cluster
|
# fake cluster
|
||||||
self.fake_cluster = faker_cluster_state.FakerModelCollector()
|
self.fake_cluster = faker_cluster_state.FakerModelCollector()
|
||||||
|
|
||||||
|
|||||||
@@ -74,19 +74,69 @@ class TestActionPlanObject(base.DbTestCase):
|
|||||||
self.assertEqual(self.context, action_plan._context)
|
self.assertEqual(self.context, action_plan._context)
|
||||||
|
|
||||||
def test_destroy(self):
|
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']
|
uuid = self.fake_action_plan['uuid']
|
||||||
with mock.patch.object(self.dbapi, 'get_action_plan_by_uuid',
|
|
||||||
autospec=True) as mock_get_action_plan:
|
with mock.patch.multiple(
|
||||||
mock_get_action_plan.return_value = self.fake_action_plan
|
self.dbapi, autospec=True,
|
||||||
with mock.patch.object(self.dbapi, 'destroy_action_plan',
|
get_action_plan_by_uuid=mock.DEFAULT,
|
||||||
autospec=True) as mock_destroy_action_plan:
|
destroy_action_plan=mock.DEFAULT,
|
||||||
action_plan = objects.ActionPlan.get_by_uuid(
|
get_efficacy_indicator_list=mock.DEFAULT,
|
||||||
self.context, uuid)
|
destroy_efficacy_indicator=mock.DEFAULT,
|
||||||
action_plan.destroy()
|
) as m_dict:
|
||||||
mock_get_action_plan.assert_called_once_with(
|
m_get_action_plan = m_dict['get_action_plan_by_uuid']
|
||||||
self.context, uuid)
|
m_destroy_action_plan = m_dict['destroy_action_plan']
|
||||||
mock_destroy_action_plan.assert_called_once_with(uuid)
|
m_get_efficacy_indicator_list = (
|
||||||
self.assertEqual(self.context, action_plan._context)
|
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):
|
def test_save(self):
|
||||||
uuid = self.fake_action_plan['uuid']
|
uuid = self.fake_action_plan['uuid']
|
||||||
|
|||||||
@@ -189,3 +189,30 @@ def create_test_strategy(context, **kw):
|
|||||||
strategy = get_test_strategy(context, **kw)
|
strategy = get_test_strategy(context, **kw)
|
||||||
strategy.create()
|
strategy.create()
|
||||||
return strategy
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user