Added efficacy indicators to /action_plans
I this changeset, I added the efficacy indicators both at the DB and at the API level alongside the associated logic. Partially Implements: blueprint efficacy-indicator Change-Id: I824553637621da67966103c1b0c01348b09bd836
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -37,59 +37,62 @@ applied.
|
||||
|
||||
Two approaches to dealing with this can be envisaged:
|
||||
|
||||
- **fully automated mode**: only the :ref:`Solution <solution_definition>`
|
||||
with the highest ranking (i.e., the highest
|
||||
:ref:`Optimization Efficiency <efficiency_definition>`)
|
||||
will be sent to the :ref:`Watcher Planner <watcher_planner_definition>` and
|
||||
translated into concrete :ref:`Actions <action_definition>`.
|
||||
- **manual mode**: several :ref:`Solutions <solution_definition>` are proposed
|
||||
to the :ref:`Administrator <administrator_definition>` with a detailed
|
||||
measurement of the estimated
|
||||
:ref:`Optimization Efficiency <efficiency_definition>` and he/she decides
|
||||
which one will be launched.
|
||||
- **fully automated mode**: only the :ref:`Solution <solution_definition>`
|
||||
with the highest ranking (i.e., the highest
|
||||
:ref:`Optimization Efficacy <efficacy_definition>`) will be sent to the
|
||||
:ref:`Watcher Planner <watcher_planner_definition>` and translated into
|
||||
concrete :ref:`Actions <action_definition>`.
|
||||
- **manual mode**: several :ref:`Solutions <solution_definition>` are proposed
|
||||
to the :ref:`Administrator <administrator_definition>` with a detailed
|
||||
measurement of the estimated :ref:`Optimization Efficacy
|
||||
<efficacy_definition>` 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'
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user