Merge "Added efficacy indicators to /action_plans"

This commit is contained in:
Jenkins
2016-06-15 09:00:03 +00:00
committed by Gerrit Code Review
23 changed files with 380 additions and 103 deletions

View File

@@ -49,13 +49,14 @@ standard workflow model description formats such as
`Business Process Model and Notation 2.0 (BPMN 2.0) <http://www.omg.org/spec/BPMN/2.0/>`_ `Business Process Model and Notation 2.0 (BPMN 2.0) <http://www.omg.org/spec/BPMN/2.0/>`_
or `Unified Modeling Language (UML) <http://www.uml.org/>`_. or `Unified Modeling Language (UML) <http://www.uml.org/>`_.
To see the life-cycle and description of To see the life-cycle and description of
:ref:`Action Plan <action_plan_definition>` states, visit :ref:`the Action Plan state :ref:`Action Plan <action_plan_definition>` states, visit :ref:`the Action Plan state
machine <action_plan_state_machine>`. machine <action_plan_state_machine>`.
""" # noqa """ # noqa
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)

View File

@@ -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.")

View File

@@ -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",

View File

@@ -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'

View File

@@ -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.

View File

@@ -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)

View File

@@ -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

View File

@@ -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,
)

View File

@@ -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,
)

View File

@@ -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()

View File

@@ -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(

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)

View File

@@ -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'),

View File

@@ -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

View File

@@ -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",

View File

@@ -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",

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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']

View File

@@ -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