Add Goal in BaseStrategy + Goal API reads from DB
In this changeset, I changed the Strategy base class to add new abstract class methods. I also added an abstract strategy class per Goal type (dummy, server consolidation, thermal optimization). This changeset also includes an update of the /goals Watcher API endpoint to now use the new Goal model (DB entries) instead of reading from the configuration file. Partially Implements: blueprint get-goal-from-strategy Change-Id: Iecfed58c72f3f9df4e9d27e50a3a274a1fc0a75f
This commit is contained in:
@@ -39,6 +39,7 @@ which are dynamically loaded by Watcher at launch time.
|
||||
import abc
|
||||
import six
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.common import clients
|
||||
from watcher.decision_engine.solution import default
|
||||
from watcher.decision_engine.strategy.common import level
|
||||
@@ -52,10 +53,10 @@ class BaseStrategy(object):
|
||||
Solution for a given Goal.
|
||||
"""
|
||||
|
||||
def __init__(self, name=None, description=None, osc=None):
|
||||
def __init__(self, osc=None):
|
||||
""":param osc: an OpenStackClients instance"""
|
||||
self._name = name
|
||||
self.description = description
|
||||
self._name = self.get_name()
|
||||
self._display_name = self.get_display_name()
|
||||
# default strategy level
|
||||
self._strategy_level = level.StrategyLevel.conservative
|
||||
self._cluster_state_collector = None
|
||||
@@ -63,6 +64,46 @@ class BaseStrategy(object):
|
||||
self._solution = default.DefaultSolution()
|
||||
self._osc = osc
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_name(cls):
|
||||
"""The name of the strategy"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_display_name(cls):
|
||||
"""The goal display name for the strategy"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_translatable_display_name(cls):
|
||||
"""The translatable msgid of the strategy"""
|
||||
# Note(v-francoise): Defined here to be used as the translation key for
|
||||
# other services
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_goal_name(cls):
|
||||
"""The goal name for the strategy"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_goal_display_name(cls):
|
||||
"""The translated display name related to the goal of the strategy"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def get_translatable_goal_display_name(cls):
|
||||
"""The translatable msgid related to the goal of the strategy"""
|
||||
# Note(v-francoise): Defined here to be used as the translation key for
|
||||
# other services
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def execute(self, original_model):
|
||||
"""Execute a strategy
|
||||
@@ -88,12 +129,12 @@ class BaseStrategy(object):
|
||||
self._solution = s
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def id(self):
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, n):
|
||||
self._name = n
|
||||
@property
|
||||
def display_name(self):
|
||||
return self._display_name
|
||||
|
||||
@property
|
||||
def strategy_level(self):
|
||||
@@ -110,3 +151,51 @@ class BaseStrategy(object):
|
||||
@state_collector.setter
|
||||
def state_collector(self, s):
|
||||
self._cluster_state_collector = s
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class DummyBaseStrategy(BaseStrategy):
|
||||
|
||||
@classmethod
|
||||
def get_goal_name(cls):
|
||||
return "DUMMY"
|
||||
|
||||
@classmethod
|
||||
def get_goal_display_name(cls):
|
||||
return _("Dummy goal")
|
||||
|
||||
@classmethod
|
||||
def get_translatable_goal_display_name(cls):
|
||||
return "Dummy goal"
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ServerConsolidationBaseStrategy(BaseStrategy):
|
||||
|
||||
@classmethod
|
||||
def get_goal_name(cls):
|
||||
return "SERVER_CONSOLIDATION"
|
||||
|
||||
@classmethod
|
||||
def get_goal_display_name(cls):
|
||||
return _("Server consolidation")
|
||||
|
||||
@classmethod
|
||||
def get_translatable_goal_display_name(cls):
|
||||
return "Server consolidation"
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ThermalOptimizationBaseStrategy(BaseStrategy):
|
||||
|
||||
@classmethod
|
||||
def get_goal_name(cls):
|
||||
return "THERMAL_OPTIMIZATION"
|
||||
|
||||
@classmethod
|
||||
def get_goal_display_name(cls):
|
||||
return _("Thermal optimization")
|
||||
|
||||
@classmethod
|
||||
def get_translatable_goal_display_name(cls):
|
||||
return "Thermal optimization"
|
||||
|
||||
@@ -29,7 +29,7 @@ order to both minimize energy consumption and comply to the various SLAs.
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from watcher._i18n import _LE, _LI, _LW
|
||||
from watcher._i18n import _, _LE, _LI, _LW
|
||||
from watcher.common import exception
|
||||
from watcher.decision_engine.model import hypervisor_state as hyper_state
|
||||
from watcher.decision_engine.model import resource
|
||||
@@ -41,7 +41,7 @@ from watcher.metrics_engine.cluster_history import ceilometer as \
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class BasicConsolidation(base.BaseStrategy):
|
||||
class BasicConsolidation(base.ServerConsolidationBaseStrategy):
|
||||
"""Basic offline consolidation using live migration
|
||||
|
||||
*Description*
|
||||
@@ -65,17 +65,13 @@ class BasicConsolidation(base.BaseStrategy):
|
||||
<None>
|
||||
"""
|
||||
|
||||
DEFAULT_NAME = "basic"
|
||||
DEFAULT_DESCRIPTION = "Basic offline consolidation"
|
||||
|
||||
HOST_CPU_USAGE_METRIC_NAME = 'compute.node.cpu.percent'
|
||||
INSTANCE_CPU_USAGE_METRIC_NAME = 'cpu_util'
|
||||
|
||||
MIGRATION = "migrate"
|
||||
CHANGE_NOVA_SERVICE_STATE = "change_nova_service_state"
|
||||
|
||||
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION,
|
||||
osc=None):
|
||||
def __init__(self, osc=None):
|
||||
"""Basic offline Consolidation using live migration
|
||||
|
||||
:param name: The name of the strategy (Default: "basic")
|
||||
@@ -84,7 +80,7 @@ class BasicConsolidation(base.BaseStrategy):
|
||||
:param osc: An :py:class:`~watcher.common.clients.OpenStackClients`
|
||||
instance
|
||||
"""
|
||||
super(BasicConsolidation, self).__init__(name, description, osc)
|
||||
super(BasicConsolidation, self).__init__(osc)
|
||||
|
||||
# set default value for the number of released nodes
|
||||
self.number_of_released_nodes = 0
|
||||
@@ -114,6 +110,18 @@ class BasicConsolidation(base.BaseStrategy):
|
||||
# TODO(jed) bound migration attempts (80 %)
|
||||
self.bound_migration = 0.80
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "basic"
|
||||
|
||||
@classmethod
|
||||
def get_display_name(cls):
|
||||
return _("Basic offline consolidation")
|
||||
|
||||
@classmethod
|
||||
def get_translatable_display_name(cls):
|
||||
return "Basic offline consolidation"
|
||||
|
||||
@property
|
||||
def ceilometer(self):
|
||||
if self._ceilometer is None:
|
||||
|
||||
@@ -18,12 +18,13 @@
|
||||
#
|
||||
from oslo_log import log
|
||||
|
||||
from watcher._i18n import _
|
||||
from watcher.decision_engine.strategy.strategies import base
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class DummyStrategy(base.BaseStrategy):
|
||||
class DummyStrategy(base.DummyBaseStrategy):
|
||||
"""Dummy strategy used for integration testing via Tempest
|
||||
|
||||
*Description*
|
||||
@@ -44,15 +45,11 @@ class DummyStrategy(base.BaseStrategy):
|
||||
<None>
|
||||
"""
|
||||
|
||||
DEFAULT_NAME = "dummy"
|
||||
DEFAULT_DESCRIPTION = "Dummy Strategy"
|
||||
|
||||
NOP = "nop"
|
||||
SLEEP = "sleep"
|
||||
|
||||
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION,
|
||||
osc=None):
|
||||
super(DummyStrategy, self).__init__(name, description, osc)
|
||||
def __init__(self, osc=None):
|
||||
super(DummyStrategy, self).__init__(osc)
|
||||
|
||||
def execute(self, original_model):
|
||||
LOG.debug("Executing Dummy strategy")
|
||||
@@ -67,3 +64,15 @@ class DummyStrategy(base.BaseStrategy):
|
||||
self.solution.add_action(action_type=self.SLEEP,
|
||||
input_parameters={'duration': 5.0})
|
||||
return self.solution
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "dummy"
|
||||
|
||||
@classmethod
|
||||
def get_display_name(cls):
|
||||
return _("Dummy strategy")
|
||||
|
||||
@classmethod
|
||||
def get_translatable_display_name(cls):
|
||||
return "Dummy strategy"
|
||||
|
||||
@@ -30,7 +30,7 @@ telemetries to measure thermal/workload status of server.
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from watcher._i18n import _LE
|
||||
from watcher._i18n import _, _LE
|
||||
from watcher.common import exception as wexc
|
||||
from watcher.decision_engine.model import resource
|
||||
from watcher.decision_engine.model import vm_state
|
||||
@@ -41,7 +41,7 @@ from watcher.metrics_engine.cluster_history import ceilometer as ceil
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class OutletTempControl(base.BaseStrategy):
|
||||
class OutletTempControl(base.ThermalOptimizationBaseStrategy):
|
||||
"""[PoC] Outlet temperature control using live migration
|
||||
|
||||
*Description*
|
||||
@@ -71,8 +71,6 @@ class OutletTempControl(base.BaseStrategy):
|
||||
https://github.com/openstack/watcher-specs/blob/master/specs/mitaka/approved/outlet-temperature-based-strategy.rst
|
||||
""" # noqa
|
||||
|
||||
DEFAULT_NAME = "outlet_temp_control"
|
||||
DEFAULT_DESCRIPTION = "outlet temperature based migration strategy"
|
||||
# The meter to report outlet temperature in ceilometer
|
||||
METER_NAME = "hardware.ipmi.node.outlet_temperature"
|
||||
# Unit: degree C
|
||||
@@ -80,15 +78,14 @@ class OutletTempControl(base.BaseStrategy):
|
||||
|
||||
MIGRATION = "migrate"
|
||||
|
||||
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION,
|
||||
osc=None):
|
||||
def __init__(self, osc=None):
|
||||
"""Outlet temperature control using live migration
|
||||
|
||||
:param name: the name of the strategy
|
||||
:param description: a description of the strategy
|
||||
:param osc: an OpenStackClients object
|
||||
"""
|
||||
super(OutletTempControl, self).__init__(name, description, osc)
|
||||
super(OutletTempControl, self).__init__(osc)
|
||||
# the migration plan will be triggered when the outlet temperature
|
||||
# reaches threshold
|
||||
# TODO(zhenzanz): Threshold should be configurable for each audit
|
||||
@@ -96,6 +93,18 @@ class OutletTempControl(base.BaseStrategy):
|
||||
self._meter = self.METER_NAME
|
||||
self._ceilometer = None
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "outlet_temperature"
|
||||
|
||||
@classmethod
|
||||
def get_display_name(cls):
|
||||
return _("Outlet temperature based strategy")
|
||||
|
||||
@classmethod
|
||||
def get_translatable_display_name(cls):
|
||||
return "Outlet temperature based strategy"
|
||||
|
||||
@property
|
||||
def ceilometer(self):
|
||||
if self._ceilometer is None:
|
||||
|
||||
@@ -22,7 +22,7 @@ from copy import deepcopy
|
||||
from oslo_log import log
|
||||
import six
|
||||
|
||||
from watcher._i18n import _LE, _LI
|
||||
from watcher._i18n import _, _LE, _LI
|
||||
from watcher.common import exception
|
||||
from watcher.decision_engine.model import hypervisor_state as hyper_state
|
||||
from watcher.decision_engine.model import resource
|
||||
@@ -34,9 +34,11 @@ from watcher.metrics_engine.cluster_history import ceilometer \
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class VMWorkloadConsolidation(base.BaseStrategy):
|
||||
class VMWorkloadConsolidation(base.ServerConsolidationBaseStrategy):
|
||||
"""VM Workload Consolidation Strategy.
|
||||
|
||||
*Description*
|
||||
|
||||
A load consolidation strategy based on heuristic first-fit
|
||||
algorithm which focuses on measured CPU utilization and tries to
|
||||
minimize hosts which have too much or too little load respecting
|
||||
@@ -67,19 +69,39 @@ class VMWorkloadConsolidation(base.BaseStrategy):
|
||||
correctly on all hypervisors within the cluster.
|
||||
This strategy assumes it is possible to live migrate any VM from
|
||||
an active hypervisor to any other active hypervisor.
|
||||
"""
|
||||
|
||||
DEFAULT_NAME = 'vm_workload_consolidation'
|
||||
DEFAULT_DESCRIPTION = 'VM Workload Consolidation Strategy'
|
||||
*Requirements*
|
||||
|
||||
def __init__(self, name=DEFAULT_NAME, description=DEFAULT_DESCRIPTION,
|
||||
osc=None):
|
||||
super(VMWorkloadConsolidation, self).__init__(name, description, osc)
|
||||
* You must have at least 2 physical compute nodes to run this strategy.
|
||||
|
||||
*Limitations*
|
||||
|
||||
<None>
|
||||
|
||||
*Spec URL*
|
||||
|
||||
https://github.com/openstack/watcher-specs/blob/master/specs/mitaka/implemented/zhaw-load-consolidation.rst
|
||||
""" # noqa
|
||||
|
||||
def __init__(self, osc=None):
|
||||
super(VMWorkloadConsolidation, self).__init__(osc)
|
||||
self._ceilometer = None
|
||||
self.number_of_migrations = 0
|
||||
self.number_of_released_hypervisors = 0
|
||||
self.ceilometer_vm_data_cache = dict()
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
return "vm_workload_consolidation"
|
||||
|
||||
@classmethod
|
||||
def get_display_name(cls):
|
||||
return _("VM Workload Consolidation Strategy")
|
||||
|
||||
@classmethod
|
||||
def get_translatable_display_name(cls):
|
||||
return "VM Workload Consolidation Strategy"
|
||||
|
||||
@property
|
||||
def ceilometer(self):
|
||||
if self._ceilometer is None:
|
||||
|
||||
@@ -153,21 +153,16 @@ class Syncer(object):
|
||||
strategy_loader = default.DefaultStrategyLoader()
|
||||
implemented_strategies = strategy_loader.list_available()
|
||||
|
||||
# TODO(v-francoise): At this point I only register the goals, but later
|
||||
# on this will be extended to also populate the strategies map.
|
||||
for _, strategy_cls in implemented_strategies.items():
|
||||
# This mapping is a temporary trick where I use the strategy
|
||||
# DEFAULT_NAME as the goal name because we used to have a 1-to-1
|
||||
# mapping between the goal and the strategy.
|
||||
# TODO(v-francoise): Dissociate the goal name and the strategy name
|
||||
goals_map[strategy_cls.DEFAULT_NAME] = {
|
||||
"name": strategy_cls.DEFAULT_NAME,
|
||||
"display_name": strategy_cls.DEFAULT_DESCRIPTION}
|
||||
goals_map[strategy_cls.get_goal_name()] = {
|
||||
"name": strategy_cls.get_goal_name(),
|
||||
"display_name":
|
||||
strategy_cls.get_translatable_goal_display_name()}
|
||||
|
||||
strategies_map[strategy_cls.__name__] = {
|
||||
"name": strategy_cls.__name__,
|
||||
"goal_name": strategy_cls.DEFAULT_NAME,
|
||||
"display_name": strategy_cls.DEFAULT_DESCRIPTION}
|
||||
strategies_map[strategy_cls.get_name()] = {
|
||||
"name": strategy_cls.get_name(),
|
||||
"goal_name": strategy_cls.get_goal_name(),
|
||||
"display_name": strategy_cls.get_translatable_display_name()}
|
||||
|
||||
return discovered_map
|
||||
|
||||
|
||||
Reference in New Issue
Block a user