Add Voluptuous to validate the action parameters

We want a simplest way to validate the input parameters of an
Action through a schema.

APIImpact
DocImpact
Partially implements: blueprint watcher-add-actions-via-conf

Change-Id: I139775f467fe7778c7354b0cfacf796fc27ffcb2
This commit is contained in:
Jean-Emile DARTOIS 2016-01-26 15:31:18 +01:00 committed by Vincent Françoise
parent 33ee575936
commit e3198d25a5
32 changed files with 771 additions and 503 deletions

View File

@ -17,6 +17,7 @@ oslo.utils>=3.4.0 # Apache-2.0
PasteDeploy>=1.5.0 PasteDeploy>=1.5.0
pbr>=1.6 pbr>=1.6
pecan>=1.0.0 pecan>=1.0.0
voluptuous>=0.8.6 # BSD License
python-ceilometerclient>=2.2.1 python-ceilometerclient>=2.2.1
python-cinderclient>=1.3.1 python-cinderclient>=1.3.1
python-glanceclient>=1.2.0 python-glanceclient>=1.2.0

View File

@ -129,13 +129,10 @@ class Action(base.APIBase):
alarm = types.uuid alarm = types.uuid
"""An alarm UUID related to this action""" """An alarm UUID related to this action"""
applies_to = wtypes.text
"""Applies to"""
action_type = wtypes.text action_type = wtypes.text
"""Action type""" """Action type"""
input_parameters = wtypes.DictType(wtypes.text, wtypes.text) input_parameters = types.jsontype
"""One or more key/value pairs """ """One or more key/value pairs """
next_uuid = wsme.wsproperty(types.uuid, _get_next_uuid, next_uuid = wsme.wsproperty(types.uuid, _get_next_uuid,

View File

@ -27,10 +27,15 @@ from watcher.common import clients
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class BaseAction(object): class BaseAction(object):
# NOTE(jed) by convention we decided
# that the attribute "resource_id" is the unique id of
# the resource to which the Action applies to allow us to use it in the
# watcher dashboard and will be nested in input_parameters
RESOURCE_ID = 'resource_id'
def __init__(self, osc=None): def __init__(self, osc=None):
""":param osc: an OpenStackClients instance""" """:param osc: an OpenStackClients instance"""
self._input_parameters = {} self._input_parameters = {}
self._applies_to = ""
self._osc = osc self._osc = osc
@property @property
@ -48,12 +53,8 @@ class BaseAction(object):
self._input_parameters = p self._input_parameters = p
@property @property
def applies_to(self): def resource_id(self):
return self._applies_to return self.input_parameters[self.RESOURCE_ID]
@applies_to.setter
def applies_to(self, a):
self._applies_to = a
@abc.abstractmethod @abc.abstractmethod
def execute(self): def execute(self):
@ -70,3 +71,11 @@ class BaseAction(object):
@abc.abstractmethod @abc.abstractmethod
def postcondition(self): def postcondition(self):
raise NotImplementedError() raise NotImplementedError()
@abc.abstractproperty
def schema(self):
raise NotImplementedError()
def validate_parameters(self):
self.schema(self.input_parameters)
return True

View File

@ -16,7 +16,8 @@
# 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 six
import voluptuous
from watcher._i18n import _ from watcher._i18n import _
from watcher.applier.actions import base from watcher.applier.actions import base
@ -27,13 +28,27 @@ from watcher.decision_engine.model import hypervisor_state as hstate
class ChangeNovaServiceState(base.BaseAction): class ChangeNovaServiceState(base.BaseAction):
STATE = 'state'
@property
def schema(self):
return voluptuous.Schema({
voluptuous.Required(self.RESOURCE_ID):
voluptuous.All(
voluptuous.Any(*six.string_types),
voluptuous.Length(min=1)),
voluptuous.Required(self.STATE):
voluptuous.Any(*[state.value
for state in list(hstate.HypervisorState)]),
})
@property @property
def host(self): def host(self):
return self.applies_to return self.resource_id
@property @property
def state(self): def state(self):
return self.input_parameters.get('state') return self.input_parameters.get(self.STATE)
def execute(self): def execute(self):
target_state = None target_state = None

View File

@ -33,5 +33,10 @@ class ActionFactory(object):
loaded_action = self.action_loader.load(name=object_action.action_type, loaded_action = self.action_loader.load(name=object_action.action_type,
osc=osc) osc=osc)
loaded_action.input_parameters = object_action.input_parameters loaded_action.input_parameters = object_action.input_parameters
loaded_action.applies_to = object_action.applies_to LOG.debug("Checking the input parameters")
# NOTE(jed) if we change the schema of an action and we try to reload
# an older version of the Action, the validation can fail.
# We need to add the versioning of an Action or a migration tool.
# We can also create an new Action which extends the previous one.
loaded_action.validate_parameters()
return loaded_action return loaded_action

View File

@ -18,30 +18,63 @@
# #
from oslo_log import log from oslo_log import log
import six
import voluptuous
from watcher._i18n import _
from watcher.applier.actions import base from watcher.applier.actions import base
from watcher.common import exception from watcher.common import exception
from watcher.common import nova_helper from watcher.common import nova_helper
from watcher.common import utils
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class Migrate(base.BaseAction): class Migrate(base.BaseAction):
# input parameters constants
MIGRATION_TYPE = 'migration_type'
LIVE_MIGRATION = 'live'
DST_HYPERVISOR = 'dst_hypervisor'
SRC_HYPERVISOR = 'src_hypervisor'
def check_resource_id(self, value):
if (value is not None and
len(value) > 0 and not
utils.is_uuid_like(value)):
raise voluptuous.Invalid(_("The parameter"
" resource_id is invalid."))
@property
def schema(self):
return voluptuous.Schema({
voluptuous.Required(self.RESOURCE_ID): self.check_resource_id,
voluptuous.Required(self.MIGRATION_TYPE,
default=self.LIVE_MIGRATION):
voluptuous.Any(*[self.LIVE_MIGRATION]),
voluptuous.Required(self.DST_HYPERVISOR):
voluptuous.All(voluptuous.Any(*six.string_types),
voluptuous.Length(min=1)),
voluptuous.Required(self.SRC_HYPERVISOR):
voluptuous.All(voluptuous.Any(*six.string_types),
voluptuous.Length(min=1)),
})
@property @property
def instance_uuid(self): def instance_uuid(self):
return self.applies_to return self.resource_id
@property @property
def migration_type(self): def migration_type(self):
return self.input_parameters.get('migration_type') return self.input_parameters.get(self.MIGRATION_TYPE)
@property @property
def dst_hypervisor(self): def dst_hypervisor(self):
return self.input_parameters.get('dst_hypervisor') return self.input_parameters.get(self.DST_HYPERVISOR)
@property @property
def src_hypervisor(self): def src_hypervisor(self):
return self.input_parameters.get('src_hypervisor') return self.input_parameters.get(self.SRC_HYPERVISOR)
def migrate(self, destination): def migrate(self, destination):
nova = nova_helper.NovaHelper(osc=self.osc) nova = nova_helper.NovaHelper(osc=self.osc)

View File

@ -18,6 +18,8 @@
# #
from oslo_log import log from oslo_log import log
import six
import voluptuous
from watcher.applier.actions import base from watcher.applier.actions import base
@ -27,9 +29,18 @@ LOG = log.getLogger(__name__)
class Nop(base.BaseAction): class Nop(base.BaseAction):
MESSAGE = 'message'
@property
def schema(self):
return voluptuous.Schema({
voluptuous.Required(self.MESSAGE): voluptuous.Any(
voluptuous.Any(*six.string_types), None)
})
@property @property
def message(self): def message(self):
return self.input_parameters.get('message') return self.input_parameters.get(self.MESSAGE)
def execute(self): def execute(self):
LOG.debug("executing action NOP message:%s ", self.message) LOG.debug("executing action NOP message:%s ", self.message)

View File

@ -18,19 +18,29 @@
# #
import time import time
from oslo_log import log from oslo_log import log
import voluptuous
from watcher.applier.actions import base from watcher.applier.actions import base
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class Sleep(base.BaseAction): class Sleep(base.BaseAction):
DURATION = 'duration'
@property
def schema(self):
return voluptuous.Schema({
voluptuous.Required(self.DURATION, default=1):
voluptuous.All(float, voluptuous.Range(min=0))
})
@property @property
def duration(self): def duration(self):
return int(self.input_parameters.get('duration')) return int(self.input_parameters.get(self.DURATION))
def execute(self): def execute(self):
LOG.debug("Starting action Sleep duration:%s ", self.duration) LOG.debug("Starting action Sleep duration:%s ", self.duration)

View File

@ -279,3 +279,7 @@ class HypervisorNotFound(WatcherException):
class LoadingError(WatcherException): class LoadingError(WatcherException):
msg_fmt = _("Error loading plugin '%(name)s'") msg_fmt = _("Error loading plugin '%(name)s'")
class ReservedWord(WatcherException):
msg_fmt = _("The identifier '%(name)s' is a reserved word")

View File

@ -160,7 +160,6 @@ class Action(Base):
nullable=False) nullable=False)
# only for the first version # only for the first version
action_type = Column(String(255), nullable=False) action_type = Column(String(255), nullable=False)
applies_to = Column(String(255), nullable=True)
input_parameters = Column(JSONEncodedDict, nullable=True) input_parameters = Column(JSONEncodedDict, nullable=True)
state = Column(String(20), nullable=True) state = Column(String(20), nullable=True)
# todo(jed) remove parameter alarm # todo(jed) remove parameter alarm

View File

@ -38,14 +38,12 @@ class DefaultPlanner(base.BasePlanner):
def create_action(self, def create_action(self,
action_plan_id, action_plan_id,
action_type, action_type,
applies_to,
input_parameters=None): input_parameters=None):
uuid = utils.generate_uuid() uuid = utils.generate_uuid()
action = { action = {
'uuid': uuid, 'uuid': uuid,
'action_plan_id': int(action_plan_id), 'action_plan_id': int(action_plan_id),
'action_type': action_type, 'action_type': action_type,
'applies_to': applies_to,
'input_parameters': input_parameters, 'input_parameters': input_parameters,
'state': objects.action.State.PENDING, 'state': objects.action.State.PENDING,
'alarm': None, 'alarm': None,
@ -63,8 +61,6 @@ class DefaultPlanner(base.BasePlanner):
json_action = self.create_action(action_plan_id=action_plan.id, json_action = self.create_action(action_plan_id=action_plan.id,
action_type=action.get( action_type=action.get(
'action_type'), 'action_type'),
applies_to=action.get(
'applies_to'),
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')],

View File

@ -87,13 +87,13 @@ class BaseSolution(object):
@abc.abstractmethod @abc.abstractmethod
def add_action(self, def add_action(self,
action_type, action_type,
applies_to, resource_id,
input_parameters=None): input_parameters=None):
"""Add a new Action in the Action Plan """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'
:param applies_to: the unique id of the resource to which the :param resource_id: the unique id of the resource to which the
`Action` applies. `Action` applies.
:param input_parameters: An array of input parameters provided as :param input_parameters: An array of input parameters provided as
key-value pairs of strings. Each key-pair contains names and key-value pairs of strings. Each key-pair contains names and

View File

@ -18,12 +18,14 @@
# #
from oslo_log import log from oslo_log import log
from watcher.decision_engine.solution.base import BaseSolution from watcher.applier.actions import base as baction
from watcher.common import exception
from watcher.decision_engine.solution import base
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class DefaultSolution(BaseSolution): class DefaultSolution(base.BaseSolution):
def __init__(self): def __init__(self):
"""Stores a set of actions generated by a strategy """Stores a set of actions generated by a strategy
@ -34,12 +36,17 @@ class DefaultSolution(BaseSolution):
self._actions = [] self._actions = []
def add_action(self, action_type, def add_action(self, action_type,
applies_to, input_parameters=None,
input_parameters=None): resource_id=None):
# todo(jed) add https://pypi.python.org/pypi/schema
if input_parameters is not None:
if baction.BaseAction.RESOURCE_ID in input_parameters.keys():
raise exception.ReservedWord(name=baction.BaseAction.
RESOURCE_ID)
if resource_id is not None:
input_parameters[baction.BaseAction.RESOURCE_ID] = resource_id
action = { action = {
'action_type': action_type, 'action_type': action_type,
'applies_to': applies_to,
'input_parameters': input_parameters 'input_parameters': input_parameters
} }
self._actions.append(action) self._actions.append(action)

View File

@ -329,14 +329,14 @@ class BasicConsolidation(BaseStrategy):
0, 0,
0) 0)
def add_change_service_state(self, applies_to, state): def add_change_service_state(self, resource_id, state):
parameters = {'state': state} parameters = {'state': state}
self.solution.add_action(action_type=self.CHANGE_NOVA_SERVICE_STATE, self.solution.add_action(action_type=self.CHANGE_NOVA_SERVICE_STATE,
applies_to=applies_to, resource_id=resource_id,
input_parameters=parameters) input_parameters=parameters)
def add_migration(self, def add_migration(self,
applies_to, resource_id,
migration_type, migration_type,
src_hypervisor, src_hypervisor,
dst_hypervisor): dst_hypervisor):
@ -344,7 +344,7 @@ class BasicConsolidation(BaseStrategy):
'src_hypervisor': src_hypervisor, 'src_hypervisor': src_hypervisor,
'dst_hypervisor': dst_hypervisor} 'dst_hypervisor': dst_hypervisor}
self.solution.add_action(action_type=self.MIGRATION, self.solution.add_action(action_type=self.MIGRATION,
applies_to=applies_to, resource_id=resource_id,
input_parameters=parameters) input_parameters=parameters)
def score_of_nodes(self, current_model, score): def score_of_nodes(self, current_model, score):

View File

@ -18,12 +18,12 @@
# #
from oslo_log import log from oslo_log import log
from watcher.decision_engine.strategy.strategies.base import BaseStrategy from watcher.decision_engine.strategy.strategies import base
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class DummyStrategy(BaseStrategy): class DummyStrategy(base.BaseStrategy):
DEFAULT_NAME = "dummy" DEFAULT_NAME = "dummy"
DEFAULT_DESCRIPTION = "Dummy Strategy" DEFAULT_DESCRIPTION = "Dummy Strategy"
@ -35,17 +35,15 @@ class DummyStrategy(BaseStrategy):
super(DummyStrategy, self).__init__(name, description, osc) super(DummyStrategy, self).__init__(name, description, osc)
def execute(self, model): def execute(self, model):
LOG.debug("Executing Dummy strategy")
parameters = {'message': 'hello World'} parameters = {'message': 'hello World'}
self.solution.add_action(action_type=self.NOP, self.solution.add_action(action_type=self.NOP,
applies_to="",
input_parameters=parameters) input_parameters=parameters)
parameters = {'message': 'Welcome'} parameters = {'message': 'Welcome'}
self.solution.add_action(action_type=self.NOP, self.solution.add_action(action_type=self.NOP,
applies_to="",
input_parameters=parameters) input_parameters=parameters)
self.solution.add_action(action_type=self.SLEEP, self.solution.add_action(action_type=self.SLEEP,
applies_to="", input_parameters={'duration': 5.0})
input_parameters={'duration': '5'})
return self.solution return self.solution

View File

@ -20,16 +20,16 @@ from oslo_log import log
from watcher._i18n import _LE from watcher._i18n import _LE
from watcher.common import exception as wexc from watcher.common import exception as wexc
from watcher.decision_engine.model.resource import ResourceType from watcher.decision_engine.model import resource
from watcher.decision_engine.model.vm_state import VMState from watcher.decision_engine.model import vm_state
from watcher.decision_engine.strategy.strategies.base import BaseStrategy from watcher.decision_engine.strategy.strategies import base
from watcher.metrics_engine.cluster_history.ceilometer import \ from watcher.metrics_engine.cluster_history import ceilometer as ceil
CeilometerClusterHistory
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class OutletTempControl(BaseStrategy): class OutletTempControl(base.BaseStrategy):
DEFAULT_NAME = "outlet_temp_control" DEFAULT_NAME = "outlet_temp_control"
DEFAULT_DESCRIPTION = "outlet temperature based migration strategy" DEFAULT_DESCRIPTION = "outlet temperature based migration strategy"
@ -81,7 +81,7 @@ class OutletTempControl(BaseStrategy):
@property @property
def ceilometer(self): def ceilometer(self):
if self._ceilometer is None: if self._ceilometer is None:
self._ceilometer = CeilometerClusterHistory(osc=self.osc) self._ceilometer = ceil.CeilometerClusterHistory(osc=self.osc)
return self._ceilometer return self._ceilometer
@ceilometer.setter @ceilometer.setter
@ -104,7 +104,7 @@ class OutletTempControl(BaseStrategy):
return vcpus_used, memory_mb_used, disk_gb_used return vcpus_used, memory_mb_used, disk_gb_used
def group_hosts_by_outlet_temp(self, model): def group_hosts_by_outlet_temp(self, model):
'''Group hosts based on outlet temp meters''' """Group hosts based on outlet temp meters"""
hypervisors = model.get_all_hypervisors() hypervisors = model.get_all_hypervisors()
size_cluster = len(hypervisors) size_cluster = len(hypervisors)
@ -137,7 +137,7 @@ class OutletTempControl(BaseStrategy):
return hosts_need_release, hosts_target return hosts_need_release, hosts_target
def choose_vm_to_migrate(self, model, hosts): def choose_vm_to_migrate(self, model, hosts):
'''pick up an active vm instance to migrate from provided hosts''' """pick up an active vm instance to migrate from provided hosts"""
for hvmap in hosts: for hvmap in hosts:
mig_src_hypervisor = hvmap['hv'] mig_src_hypervisor = hvmap['hv']
@ -147,7 +147,7 @@ class OutletTempControl(BaseStrategy):
try: try:
# select the first active VM to migrate # select the first active VM to migrate
vm = model.get_vm_from_id(vm_id) vm = model.get_vm_from_id(vm_id)
if vm.state != VMState.ACTIVE.value: if vm.state != vm_state.VMState.ACTIVE.value:
LOG.info(_LE("VM not active, skipped: %s"), LOG.info(_LE("VM not active, skipped: %s"),
vm.uuid) vm.uuid)
continue continue
@ -159,11 +159,11 @@ class OutletTempControl(BaseStrategy):
return None return None
def filter_dest_servers(self, model, hosts, vm_to_migrate): def filter_dest_servers(self, model, hosts, vm_to_migrate):
'''Only return hosts with sufficient available resources''' """Only return hosts with sufficient available resources"""
cap_cores = model.get_resource_from_id(ResourceType.cpu_cores) cap_cores = model.get_resource_from_id(resource.ResourceType.cpu_cores)
cap_disk = model.get_resource_from_id(ResourceType.disk) cap_disk = model.get_resource_from_id(resource.ResourceType.disk)
cap_mem = model.get_resource_from_id(ResourceType.memory) cap_mem = model.get_resource_from_id(resource.ResourceType.memory)
required_cores = cap_cores.get_capacity(vm_to_migrate) required_cores = cap_cores.get_capacity(vm_to_migrate)
required_disk = cap_disk.get_capacity(vm_to_migrate) required_disk = cap_disk.get_capacity(vm_to_migrate)
@ -239,10 +239,10 @@ class OutletTempControl(BaseStrategy):
mig_src_hypervisor, mig_src_hypervisor,
mig_dst_hypervisor): mig_dst_hypervisor):
parameters = {'migration_type': 'live', parameters = {'migration_type': 'live',
'src_hypervisor': mig_src_hypervisor, 'src_hypervisor': mig_src_hypervisor.uuid,
'dst_hypervisor': mig_dst_hypervisor} 'dst_hypervisor': mig_dst_hypervisor.uuid}
self.solution.add_action(action_type=self.MIGRATION, self.solution.add_action(action_type=self.MIGRATION,
applies_to=vm_src, resource_id=vm_src.uuid,
input_parameters=parameters) input_parameters=parameters)
self.solution.model = current_model self.solution.model = current_model

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: python-watcher 0.21.1.dev32\n" "Project-Id-Version: python-watcher 0.21.1.dev32\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2016-01-26 11:26+0100\n" "POT-Creation-Date: 2016-02-09 09:07+0100\n"
"PO-Revision-Date: 2015-12-11 15:42+0100\n" "PO-Revision-Date: 2015-12-11 15:42+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: fr\n" "Language: fr\n"
@ -24,7 +24,7 @@ msgstr ""
msgid "Invalid state: %(state)s" msgid "Invalid state: %(state)s"
msgstr "État invalide : %(state)s" msgstr "État invalide : %(state)s"
#: watcher/api/controllers/v1/action_plan.py:420 #: watcher/api/controllers/v1/action_plan.py:422
#, python-format #, python-format
msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)" msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)"
msgstr "Transition d'état non autorisée : (%(initial_state)s -> %(new_state)s)" msgstr "Transition d'état non autorisée : (%(initial_state)s -> %(new_state)s)"
@ -85,21 +85,30 @@ msgstr ""
msgid "Error parsing HTTP response: %s" msgid "Error parsing HTTP response: %s"
msgstr "" msgstr ""
#: watcher/applier/actions/change_nova_service_state.py:58 #: watcher/applier/actions/change_nova_service_state.py:69
msgid "The target state is not defined" msgid "The target state is not defined"
msgstr "" msgstr ""
#: watcher/applier/workflow_engine/default.py:126 #: watcher/applier/actions/migration.py:43
msgid "The parameter resource_id is invalid."
msgstr "Le paramètre resource_id est invalide"
#: watcher/applier/actions/migration.py:86
#, python-format
msgid "Migration of type %(migration_type)s is not supported."
msgstr ""
#: watcher/applier/workflow_engine/default.py:128
#, python-format #, python-format
msgid "The WorkFlow Engine has failed to execute the action %s" msgid "The WorkFlow Engine has failed to execute the action %s"
msgstr "Le moteur de workflow a echoué lors de l'éxécution de l'action %s" msgstr "Le moteur de workflow a echoué lors de l'éxécution de l'action %s"
#: watcher/applier/workflow_engine/default.py:144 #: watcher/applier/workflow_engine/default.py:146
#, python-format #, python-format
msgid "Revert action %s" msgid "Revert action %s"
msgstr "Annulation de l'action %s" msgstr "Annulation de l'action %s"
#: watcher/applier/workflow_engine/default.py:150 #: watcher/applier/workflow_engine/default.py:152
msgid "Oops! We need disaster recover plan" msgid "Oops! We need disaster recover plan"
msgstr "Oops! Nous avons besoin d'un plan de reprise d'activité" msgstr "Oops! Nous avons besoin d'un plan de reprise d'activité"
@ -119,184 +128,210 @@ msgstr "Sert sur 0.0.0.0:%(port)s, accessible à http://127.0.0.1:%(port)s"
msgid "serving on http://%(host)s:%(port)s" msgid "serving on http://%(host)s:%(port)s"
msgstr "Sert sur http://%(host)s:%(port)s" msgstr "Sert sur http://%(host)s:%(port)s"
#: watcher/common/exception.py:51 #: watcher/common/clients.py:29
msgid "Version of Nova API to use in novaclient."
msgstr ""
#: watcher/common/clients.py:34
msgid "Version of Glance API to use in glanceclient."
msgstr ""
#: watcher/common/clients.py:39
msgid "Version of Cinder API to use in cinderclient."
msgstr ""
#: watcher/common/clients.py:44
msgid "Version of Ceilometer API to use in ceilometerclient."
msgstr ""
#: watcher/common/clients.py:50
msgid "Version of Neutron API to use in neutronclient."
msgstr ""
#: watcher/common/exception.py:59
#, python-format
msgid "Unexpected keystone client error occurred: %s"
msgstr ""
#: watcher/common/exception.py:72
msgid "An unknown exception occurred" msgid "An unknown exception occurred"
msgstr "" msgstr ""
#: watcher/common/exception.py:71 #: watcher/common/exception.py:92
msgid "Exception in string format operation" msgid "Exception in string format operation"
msgstr "" msgstr ""
#: watcher/common/exception.py:101 #: watcher/common/exception.py:122
msgid "Not authorized" msgid "Not authorized"
msgstr "" msgstr ""
#: watcher/common/exception.py:106 #: watcher/common/exception.py:127
msgid "Operation not permitted" msgid "Operation not permitted"
msgstr "" msgstr ""
#: watcher/common/exception.py:110 #: watcher/common/exception.py:131
msgid "Unacceptable parameters" msgid "Unacceptable parameters"
msgstr "" msgstr ""
#: watcher/common/exception.py:115 #: watcher/common/exception.py:136
#, python-format #, python-format
msgid "The %(name)s %(id)s could not be found" msgid "The %(name)s %(id)s could not be found"
msgstr "" msgstr ""
#: watcher/common/exception.py:119 #: watcher/common/exception.py:140
#, fuzzy #, fuzzy
msgid "Conflict" msgid "Conflict"
msgstr "Conflit" msgstr "Conflit"
#: watcher/common/exception.py:124 #: watcher/common/exception.py:145
#, python-format #, python-format
msgid "The %(name)s resource %(id)s could not be found" msgid "The %(name)s resource %(id)s could not be found"
msgstr "La ressource %(name)s / %(id)s est introuvable" msgstr "La ressource %(name)s / %(id)s est introuvable"
#: watcher/common/exception.py:129 #: watcher/common/exception.py:150
#, python-format #, python-format
msgid "Expected an uuid or int but received %(identity)s" msgid "Expected an uuid or int but received %(identity)s"
msgstr "" msgstr ""
#: watcher/common/exception.py:133 #: watcher/common/exception.py:154
#, python-format #, python-format
msgid "Goal %(goal)s is not defined in Watcher configuration file" msgid "Goal %(goal)s is not defined in Watcher configuration file"
msgstr "" msgstr ""
#: watcher/common/exception.py:139 #: watcher/common/exception.py:158
#, python-format
msgid "%(err)s"
msgstr "%(err)s"
#: watcher/common/exception.py:143
#, python-format #, python-format
msgid "Expected a uuid but received %(uuid)s" msgid "Expected a uuid but received %(uuid)s"
msgstr "" msgstr ""
#: watcher/common/exception.py:147 #: watcher/common/exception.py:162
#, python-format #, python-format
msgid "Expected a logical name but received %(name)s" msgid "Expected a logical name but received %(name)s"
msgstr "" msgstr ""
#: watcher/common/exception.py:151 #: watcher/common/exception.py:166
#, python-format #, python-format
msgid "Expected a logical name or uuid but received %(name)s" msgid "Expected a logical name or uuid but received %(name)s"
msgstr "" msgstr ""
#: watcher/common/exception.py:155 #: watcher/common/exception.py:170
#, python-format #, python-format
msgid "AuditTemplate %(audit_template)s could not be found" msgid "AuditTemplate %(audit_template)s could not be found"
msgstr "" msgstr ""
#: watcher/common/exception.py:159 #: watcher/common/exception.py:174
#, python-format #, python-format
msgid "An audit_template with UUID %(uuid)s or name %(name)s already exists" msgid "An audit_template with UUID %(uuid)s or name %(name)s already exists"
msgstr "" msgstr ""
#: watcher/common/exception.py:164 #: watcher/common/exception.py:179
#, python-format #, python-format
msgid "AuditTemplate %(audit_template)s is referenced by one or multiple audit" msgid "AuditTemplate %(audit_template)s is referenced by one or multiple audit"
msgstr "" msgstr ""
#: watcher/common/exception.py:169 #: watcher/common/exception.py:184
#, python-format #, python-format
msgid "Audit %(audit)s could not be found" msgid "Audit %(audit)s could not be found"
msgstr "" msgstr ""
#: watcher/common/exception.py:173 #: watcher/common/exception.py:188
#, python-format #, python-format
msgid "An audit with UUID %(uuid)s already exists" msgid "An audit with UUID %(uuid)s already exists"
msgstr "" msgstr ""
#: watcher/common/exception.py:177 #: watcher/common/exception.py:192
#, python-format #, python-format
msgid "Audit %(audit)s is referenced by one or multiple action plans" msgid "Audit %(audit)s is referenced by one or multiple action plans"
msgstr "" msgstr ""
#: watcher/common/exception.py:182 #: watcher/common/exception.py:197
#, python-format #, python-format
msgid "ActionPlan %(action_plan)s could not be found" msgid "ActionPlan %(action_plan)s could not be found"
msgstr "" msgstr ""
#: watcher/common/exception.py:186 #: watcher/common/exception.py:201
#, python-format #, python-format
msgid "An action plan with UUID %(uuid)s already exists" msgid "An action plan with UUID %(uuid)s already exists"
msgstr "" msgstr ""
#: watcher/common/exception.py:190 #: watcher/common/exception.py:205
#, python-format #, python-format
msgid "Action Plan %(action_plan)s is referenced by one or multiple actions" msgid "Action Plan %(action_plan)s is referenced by one or multiple actions"
msgstr "" msgstr ""
#: watcher/common/exception.py:195 #: watcher/common/exception.py:210
#, python-format #, python-format
msgid "Action %(action)s could not be found" msgid "Action %(action)s could not be found"
msgstr "" msgstr ""
#: watcher/common/exception.py:199 #: watcher/common/exception.py:214
#, python-format #, python-format
msgid "An action with UUID %(uuid)s already exists" msgid "An action with UUID %(uuid)s already exists"
msgstr "" msgstr ""
#: watcher/common/exception.py:203 #: watcher/common/exception.py:218
#, python-format #, python-format
msgid "Action plan %(action_plan)s is referenced by one or multiple goals" msgid "Action plan %(action_plan)s is referenced by one or multiple goals"
msgstr "" msgstr ""
#: watcher/common/exception.py:208 #: watcher/common/exception.py:223
msgid "Filtering actions on both audit and action-plan is prohibited" msgid "Filtering actions on both audit and action-plan is prohibited"
msgstr "" msgstr ""
#: watcher/common/exception.py:217 #: watcher/common/exception.py:232
#, python-format #, python-format
msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s" msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s"
msgstr "" msgstr ""
#: watcher/common/exception.py:224 #: watcher/common/exception.py:239
msgid "Illegal argument" msgid "Illegal argument"
msgstr "" msgstr ""
#: watcher/common/exception.py:228 #: watcher/common/exception.py:243
msgid "No such metric" msgid "No such metric"
msgstr "" msgstr ""
#: watcher/common/exception.py:232 #: watcher/common/exception.py:247
msgid "No rows were returned" msgid "No rows were returned"
msgstr "" msgstr ""
#: watcher/common/exception.py:236 #: watcher/common/exception.py:251
#, python-format
msgid "%(client)s connection failed. Reason: %(reason)s"
msgstr ""
#: watcher/common/exception.py:255
msgid "'Keystone API endpoint is missing''" msgid "'Keystone API endpoint is missing''"
msgstr "" msgstr ""
#: watcher/common/exception.py:240 #: watcher/common/exception.py:259
msgid "The list of hypervisor(s) in the cluster is empty" msgid "The list of hypervisor(s) in the cluster is empty"
msgstr "" msgstr ""
#: watcher/common/exception.py:244 #: watcher/common/exception.py:263
msgid "The metrics resource collector is not defined" msgid "The metrics resource collector is not defined"
msgstr "" msgstr ""
#: watcher/common/exception.py:248 #: watcher/common/exception.py:267
msgid "the cluster state is not defined" msgid "the cluster state is not defined"
msgstr "" msgstr ""
#: watcher/common/exception.py:254 #: watcher/common/exception.py:273
#, python-format #, python-format
msgid "The instance '%(name)s' is not found" msgid "The instance '%(name)s' is not found"
msgstr "L'instance '%(name)s' n'a pas été trouvée" msgstr "L'instance '%(name)s' n'a pas été trouvée"
#: watcher/common/exception.py:258 #: watcher/common/exception.py:277
msgid "The hypervisor is not found" msgid "The hypervisor is not found"
msgstr "" msgstr ""
#: watcher/common/exception.py:262 #: watcher/common/exception.py:281
#, fuzzy, python-format #, fuzzy, python-format
msgid "Error loading plugin '%(name)s'" msgid "Error loading plugin '%(name)s'"
msgstr "Erreur lors du chargement du module '%(name)s'" msgstr "Erreur lors du chargement du module '%(name)s'"
#: watcher/common/keystone.py:59 #: watcher/common/exception.py:285
msgid "No Keystone service catalog loaded" #, fuzzy, python-format
msgid "The identifier '%(name)s' is a reserved word"
msgstr "" msgstr ""
#: watcher/common/service.py:83 #: watcher/common/service.py:83
@ -347,18 +382,22 @@ msgid ""
"template uuid instead" "template uuid instead"
msgstr "" msgstr ""
#: watcher/db/sqlalchemy/api.py:277 #: watcher/db/sqlalchemy/api.py:278
msgid "Cannot overwrite UUID for an existing AuditTemplate." msgid "Cannot overwrite UUID for an existing Audit Template."
msgstr "" msgstr ""
#: watcher/db/sqlalchemy/api.py:386 watcher/db/sqlalchemy/api.py:586 #: watcher/db/sqlalchemy/api.py:388
msgid "Cannot overwrite UUID for an existing Audit." msgid "Cannot overwrite UUID for an existing Audit."
msgstr "" msgstr ""
#: watcher/db/sqlalchemy/api.py:477 #: watcher/db/sqlalchemy/api.py:480
msgid "Cannot overwrite UUID for an existing Action." msgid "Cannot overwrite UUID for an existing Action."
msgstr "" msgstr ""
#: watcher/db/sqlalchemy/api.py:590
msgid "Cannot overwrite UUID for an existing Action Plan."
msgstr ""
#: watcher/db/sqlalchemy/migration.py:73 #: watcher/db/sqlalchemy/migration.py:73
msgid "" msgid ""
"Watcher database schema is already under version control; use upgrade() " "Watcher database schema is already under version control; use upgrade() "
@ -370,44 +409,44 @@ msgstr ""
msgid "'obj' argument type is not valid" msgid "'obj' argument type is not valid"
msgstr "" msgstr ""
#: watcher/decision_engine/planner/default.py:76 #: watcher/decision_engine/planner/default.py:72
msgid "The action plan is empty" msgid "The action plan is empty"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/selection/default.py:59 #: watcher/decision_engine/strategy/selection/default.py:60
#, python-format #, python-format
msgid "Incorrect mapping: could not find associated strategy for '%s'" msgid "Incorrect mapping: could not find associated strategy for '%s'"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:267 #: watcher/decision_engine/strategy/strategies/basic_consolidation.py:269
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:314 #: watcher/decision_engine/strategy/strategies/basic_consolidation.py:316
#, python-format #, python-format
msgid "No values returned by %(resource_id)s for %(metric_name)s" msgid "No values returned by %(resource_id)s for %(metric_name)s"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:424 #: watcher/decision_engine/strategy/strategies/basic_consolidation.py:426
msgid "Initializing Sercon Consolidation" msgid "Initializing Sercon Consolidation"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:468 #: watcher/decision_engine/strategy/strategies/basic_consolidation.py:470
msgid "The workloads of the compute nodes of the cluster is zero" msgid "The workloads of the compute nodes of the cluster is zero"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:125 #: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:127
#, python-format #, python-format
msgid "%s: no outlet temp data" msgid "%s: no outlet temp data"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:149 #: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:151
#, python-format #, python-format
msgid "VM not active, skipped: %s" msgid "VM not active, skipped: %s"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:206 #: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:208
msgid "No hosts under outlet temp threshold found" msgid "No hosts under outlet temp threshold found"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:229 #: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:231
msgid "No proper target host could be found" msgid "No proper target host could be found"
msgstr "" msgstr ""
@ -582,9 +621,20 @@ msgstr ""
#~ msgid "Description cannot be empty" #~ msgid "Description cannot be empty"
#~ msgstr "" #~ msgstr ""
#~ msgid "" #~ msgid "The hypervisor state is invalid."
#~ "Failed to remove trailing character. " #~ msgstr "L'état de l'hyperviseur est invalide"
#~ "Returning original object. Supplied object "
#~ "is not a string: %s," #~ msgid "%(err)s"
#~ msgstr "%(err)s"
#~ msgid "No Keystone service catalog loaded"
#~ msgstr ""
#~ msgid "Cannot overwrite UUID for an existing AuditTemplate."
#~ msgstr ""
#~ msgid ""
#~ "This identifier is reserved word and "
#~ "cannot be used as variables '%(name)s'"
#~ msgstr "" #~ msgstr ""

View File

@ -7,9 +7,9 @@
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: python-watcher 0.23.2.dev1\n" "Project-Id-Version: python-watcher 0.23.3.dev2\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2016-01-26 11:26+0100\n" "POT-Creation-Date: 2016-02-09 09:07+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -23,7 +23,7 @@ msgstr ""
msgid "Invalid state: %(state)s" msgid "Invalid state: %(state)s"
msgstr "" msgstr ""
#: watcher/api/controllers/v1/action_plan.py:420 #: watcher/api/controllers/v1/action_plan.py:422
#, python-format #, python-format
msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)" msgid "State transition not allowed: (%(initial_state)s -> %(new_state)s)"
msgstr "" msgstr ""
@ -84,25 +84,30 @@ msgstr ""
msgid "Error parsing HTTP response: %s" msgid "Error parsing HTTP response: %s"
msgstr "" msgstr ""
#: watcher/applier/actions/change_nova_service_state.py:58 #: watcher/applier/actions/change_nova_service_state.py:69
msgid "The target state is not defined" msgid "The target state is not defined"
msgstr "" msgstr ""
#: watcher/applier/actions/migration.py:60 #: watcher/applier/actions/migration.py:43
msgid "The parameter resource_id is invalid."
msgstr ""
#: watcher/applier/actions/migration.py:86
#, python-format
msgid "Migration of type %(migration_type)s is not supported." msgid "Migration of type %(migration_type)s is not supported."
msgstr "" msgstr ""
#: watcher/applier/workflow_engine/default.py:126 #: watcher/applier/workflow_engine/default.py:128
#, python-format #, python-format
msgid "The WorkFlow Engine has failed to execute the action %s" msgid "The WorkFlow Engine has failed to execute the action %s"
msgstr "" msgstr ""
#: watcher/applier/workflow_engine/default.py:144 #: watcher/applier/workflow_engine/default.py:146
#, python-format #, python-format
msgid "Revert action %s" msgid "Revert action %s"
msgstr "" msgstr ""
#: watcher/applier/workflow_engine/default.py:150 #: watcher/applier/workflow_engine/default.py:152
msgid "Oops! We need disaster recover plan" msgid "Oops! We need disaster recover plan"
msgstr "" msgstr ""
@ -122,178 +127,209 @@ msgstr ""
msgid "serving on http://%(host)s:%(port)s" msgid "serving on http://%(host)s:%(port)s"
msgstr "" msgstr ""
#: watcher/common/exception.py:51 #: watcher/common/clients.py:29
msgid "Version of Nova API to use in novaclient."
msgstr ""
#: watcher/common/clients.py:34
msgid "Version of Glance API to use in glanceclient."
msgstr ""
#: watcher/common/clients.py:39
msgid "Version of Cinder API to use in cinderclient."
msgstr ""
#: watcher/common/clients.py:44
msgid "Version of Ceilometer API to use in ceilometerclient."
msgstr ""
#: watcher/common/clients.py:50
msgid "Version of Neutron API to use in neutronclient."
msgstr ""
#: watcher/common/exception.py:59
#, python-format
msgid "Unexpected keystone client error occurred: %s"
msgstr ""
#: watcher/common/exception.py:72
msgid "An unknown exception occurred" msgid "An unknown exception occurred"
msgstr "" msgstr ""
#: watcher/common/exception.py:71 #: watcher/common/exception.py:92
msgid "Exception in string format operation" msgid "Exception in string format operation"
msgstr "" msgstr ""
#: watcher/common/exception.py:101 #: watcher/common/exception.py:122
msgid "Not authorized" msgid "Not authorized"
msgstr "" msgstr ""
#: watcher/common/exception.py:106 #: watcher/common/exception.py:127
msgid "Operation not permitted" msgid "Operation not permitted"
msgstr "" msgstr ""
#: watcher/common/exception.py:110 #: watcher/common/exception.py:131
msgid "Unacceptable parameters" msgid "Unacceptable parameters"
msgstr "" msgstr ""
#: watcher/common/exception.py:115 #: watcher/common/exception.py:136
#, python-format #, python-format
msgid "The %(name)s %(id)s could not be found" msgid "The %(name)s %(id)s could not be found"
msgstr "" msgstr ""
#: watcher/common/exception.py:119 #: watcher/common/exception.py:140
msgid "Conflict" msgid "Conflict"
msgstr "" msgstr ""
#: watcher/common/exception.py:124 #: watcher/common/exception.py:145
#, python-format #, python-format
msgid "The %(name)s resource %(id)s could not be found" msgid "The %(name)s resource %(id)s could not be found"
msgstr "" msgstr ""
#: watcher/common/exception.py:129 #: watcher/common/exception.py:150
#, python-format #, python-format
msgid "Expected an uuid or int but received %(identity)s" msgid "Expected an uuid or int but received %(identity)s"
msgstr "" msgstr ""
#: watcher/common/exception.py:133 #: watcher/common/exception.py:154
#, python-format #, python-format
msgid "Goal %(goal)s is not defined in Watcher configuration file" msgid "Goal %(goal)s is not defined in Watcher configuration file"
msgstr "" msgstr ""
#: watcher/common/exception.py:143 #: watcher/common/exception.py:158
#, python-format #, python-format
msgid "Expected a uuid but received %(uuid)s" msgid "Expected a uuid but received %(uuid)s"
msgstr "" msgstr ""
#: watcher/common/exception.py:147 #: watcher/common/exception.py:162
#, python-format #, python-format
msgid "Expected a logical name but received %(name)s" msgid "Expected a logical name but received %(name)s"
msgstr "" msgstr ""
#: watcher/common/exception.py:151 #: watcher/common/exception.py:166
#, python-format #, python-format
msgid "Expected a logical name or uuid but received %(name)s" msgid "Expected a logical name or uuid but received %(name)s"
msgstr "" msgstr ""
#: watcher/common/exception.py:155 #: watcher/common/exception.py:170
#, python-format #, python-format
msgid "AuditTemplate %(audit_template)s could not be found" msgid "AuditTemplate %(audit_template)s could not be found"
msgstr "" msgstr ""
#: watcher/common/exception.py:159 #: watcher/common/exception.py:174
#, python-format #, python-format
msgid "An audit_template with UUID %(uuid)s or name %(name)s already exists" msgid "An audit_template with UUID %(uuid)s or name %(name)s already exists"
msgstr "" msgstr ""
#: watcher/common/exception.py:164 #: watcher/common/exception.py:179
#, python-format #, python-format
msgid "AuditTemplate %(audit_template)s is referenced by one or multiple audit" msgid "AuditTemplate %(audit_template)s is referenced by one or multiple audit"
msgstr "" msgstr ""
#: watcher/common/exception.py:169 #: watcher/common/exception.py:184
#, python-format #, python-format
msgid "Audit %(audit)s could not be found" msgid "Audit %(audit)s could not be found"
msgstr "" msgstr ""
#: watcher/common/exception.py:173 #: watcher/common/exception.py:188
#, python-format #, python-format
msgid "An audit with UUID %(uuid)s already exists" msgid "An audit with UUID %(uuid)s already exists"
msgstr "" msgstr ""
#: watcher/common/exception.py:177 #: watcher/common/exception.py:192
#, python-format #, python-format
msgid "Audit %(audit)s is referenced by one or multiple action plans" msgid "Audit %(audit)s is referenced by one or multiple action plans"
msgstr "" msgstr ""
#: watcher/common/exception.py:182 #: watcher/common/exception.py:197
#, python-format #, python-format
msgid "ActionPlan %(action_plan)s could not be found" msgid "ActionPlan %(action_plan)s could not be found"
msgstr "" msgstr ""
#: watcher/common/exception.py:186 #: watcher/common/exception.py:201
#, python-format #, python-format
msgid "An action plan with UUID %(uuid)s already exists" msgid "An action plan with UUID %(uuid)s already exists"
msgstr "" msgstr ""
#: watcher/common/exception.py:190 #: watcher/common/exception.py:205
#, python-format #, python-format
msgid "Action Plan %(action_plan)s is referenced by one or multiple actions" msgid "Action Plan %(action_plan)s is referenced by one or multiple actions"
msgstr "" msgstr ""
#: watcher/common/exception.py:195 #: watcher/common/exception.py:210
#, python-format #, python-format
msgid "Action %(action)s could not be found" msgid "Action %(action)s could not be found"
msgstr "" msgstr ""
#: watcher/common/exception.py:199 #: watcher/common/exception.py:214
#, python-format #, python-format
msgid "An action with UUID %(uuid)s already exists" msgid "An action with UUID %(uuid)s already exists"
msgstr "" msgstr ""
#: watcher/common/exception.py:203 #: watcher/common/exception.py:218
#, python-format #, python-format
msgid "Action plan %(action_plan)s is referenced by one or multiple goals" msgid "Action plan %(action_plan)s is referenced by one or multiple goals"
msgstr "" msgstr ""
#: watcher/common/exception.py:208 #: watcher/common/exception.py:223
msgid "Filtering actions on both audit and action-plan is prohibited" msgid "Filtering actions on both audit and action-plan is prohibited"
msgstr "" msgstr ""
#: watcher/common/exception.py:217 #: watcher/common/exception.py:232
#, python-format #, python-format
msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s" msgid "Couldn't apply patch '%(patch)s'. Reason: %(reason)s"
msgstr "" msgstr ""
#: watcher/common/exception.py:224 #: watcher/common/exception.py:239
msgid "Illegal argument" msgid "Illegal argument"
msgstr "" msgstr ""
#: watcher/common/exception.py:228 #: watcher/common/exception.py:243
msgid "No such metric" msgid "No such metric"
msgstr "" msgstr ""
#: watcher/common/exception.py:232 #: watcher/common/exception.py:247
msgid "No rows were returned" msgid "No rows were returned"
msgstr "" msgstr ""
#: watcher/common/exception.py:236 #: watcher/common/exception.py:251
#, python-format
msgid "%(client)s connection failed. Reason: %(reason)s"
msgstr ""
#: watcher/common/exception.py:255
msgid "'Keystone API endpoint is missing''" msgid "'Keystone API endpoint is missing''"
msgstr "" msgstr ""
#: watcher/common/exception.py:240 #: watcher/common/exception.py:259
msgid "The list of hypervisor(s) in the cluster is empty" msgid "The list of hypervisor(s) in the cluster is empty"
msgstr "" msgstr ""
#: watcher/common/exception.py:244 #: watcher/common/exception.py:263
msgid "The metrics resource collector is not defined" msgid "The metrics resource collector is not defined"
msgstr "" msgstr ""
#: watcher/common/exception.py:248 #: watcher/common/exception.py:267
msgid "the cluster state is not defined" msgid "the cluster state is not defined"
msgstr "" msgstr ""
#: watcher/common/exception.py:254 #: watcher/common/exception.py:273
#, python-format #, python-format
msgid "The instance '%(name)s' is not found" msgid "The instance '%(name)s' is not found"
msgstr "" msgstr ""
#: watcher/common/exception.py:258 #: watcher/common/exception.py:277
msgid "The hypervisor is not found" msgid "The hypervisor is not found"
msgstr "" msgstr ""
#: watcher/common/exception.py:262 #: watcher/common/exception.py:281
#, python-format #, python-format
msgid "Error loading plugin '%(name)s'" msgid "Error loading plugin '%(name)s'"
msgstr "" msgstr ""
#: watcher/common/keystone.py:59 #: watcher/common/exception.py:285
msgid "No Keystone service catalog loaded" #, python-format
msgid "The identifier '%(name)s' is a reserved word"
msgstr "" msgstr ""
#: watcher/common/service.py:83 #: watcher/common/service.py:83
@ -344,18 +380,22 @@ msgid ""
"template uuid instead" "template uuid instead"
msgstr "" msgstr ""
#: watcher/db/sqlalchemy/api.py:277 #: watcher/db/sqlalchemy/api.py:278
msgid "Cannot overwrite UUID for an existing AuditTemplate." msgid "Cannot overwrite UUID for an existing Audit Template."
msgstr "" msgstr ""
#: watcher/db/sqlalchemy/api.py:386 watcher/db/sqlalchemy/api.py:586 #: watcher/db/sqlalchemy/api.py:388
msgid "Cannot overwrite UUID for an existing Audit." msgid "Cannot overwrite UUID for an existing Audit."
msgstr "" msgstr ""
#: watcher/db/sqlalchemy/api.py:477 #: watcher/db/sqlalchemy/api.py:480
msgid "Cannot overwrite UUID for an existing Action." msgid "Cannot overwrite UUID for an existing Action."
msgstr "" msgstr ""
#: watcher/db/sqlalchemy/api.py:590
msgid "Cannot overwrite UUID for an existing Action Plan."
msgstr ""
#: watcher/db/sqlalchemy/migration.py:73 #: watcher/db/sqlalchemy/migration.py:73
msgid "" msgid ""
"Watcher database schema is already under version control; use upgrade() " "Watcher database schema is already under version control; use upgrade() "
@ -367,44 +407,44 @@ msgstr ""
msgid "'obj' argument type is not valid" msgid "'obj' argument type is not valid"
msgstr "" msgstr ""
#: watcher/decision_engine/planner/default.py:76 #: watcher/decision_engine/planner/default.py:72
msgid "The action plan is empty" msgid "The action plan is empty"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/selection/default.py:59 #: watcher/decision_engine/strategy/selection/default.py:60
#, python-format #, python-format
msgid "Incorrect mapping: could not find associated strategy for '%s'" msgid "Incorrect mapping: could not find associated strategy for '%s'"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:267 #: watcher/decision_engine/strategy/strategies/basic_consolidation.py:269
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:314 #: watcher/decision_engine/strategy/strategies/basic_consolidation.py:316
#, python-format #, python-format
msgid "No values returned by %(resource_id)s for %(metric_name)s" msgid "No values returned by %(resource_id)s for %(metric_name)s"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:424 #: watcher/decision_engine/strategy/strategies/basic_consolidation.py:426
msgid "Initializing Sercon Consolidation" msgid "Initializing Sercon Consolidation"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/basic_consolidation.py:468 #: watcher/decision_engine/strategy/strategies/basic_consolidation.py:470
msgid "The workloads of the compute nodes of the cluster is zero" msgid "The workloads of the compute nodes of the cluster is zero"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:125 #: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:127
#, python-format #, python-format
msgid "%s: no outlet temp data" msgid "%s: no outlet temp data"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:149 #: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:151
#, python-format #, python-format
msgid "VM not active, skipped: %s" msgid "VM not active, skipped: %s"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:206 #: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:208
msgid "No hosts under outlet temp threshold found" msgid "No hosts under outlet temp threshold found"
msgstr "" msgstr ""
#: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:229 #: watcher/decision_engine/strategy/strategies/outlet_temp_control.py:231
msgid "No proper target host could be found" msgid "No proper target host could be found"
msgstr "" msgstr ""

View File

@ -17,32 +17,30 @@
# limitations under the License. # limitations under the License.
# #
from oslo_config import cfg
from oslo_log import log from oslo_log import log
from watcher.decision_engine.model.hypervisor import Hypervisor from watcher.decision_engine.model import hypervisor as obj_hypervisor
from watcher.decision_engine.model.model_root import ModelRoot from watcher.decision_engine.model import model_root
from watcher.decision_engine.model.resource import Resource from watcher.decision_engine.model import resource
from watcher.decision_engine.model.resource import ResourceType from watcher.decision_engine.model import vm as obj_vm
from watcher.decision_engine.model.vm import VM from watcher.metrics_engine.cluster_model_collector import api
from watcher.metrics_engine.cluster_model_collector.api import \
BaseClusterModelCollector
CONF = cfg.CONF
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class NovaClusterModelCollector(BaseClusterModelCollector): class NovaClusterModelCollector(api.BaseClusterModelCollector):
def __init__(self, wrapper): def __init__(self, wrapper):
super(NovaClusterModelCollector, self).__init__()
self.wrapper = wrapper self.wrapper = wrapper
def get_latest_cluster_data_model(self): def get_latest_cluster_data_model(self):
LOG.debug("Getting latest cluster data model")
cluster = ModelRoot() cluster = model_root.ModelRoot()
mem = Resource(ResourceType.memory) mem = resource.Resource(resource.ResourceType.memory)
num_cores = Resource(ResourceType.cpu_cores) num_cores = resource.Resource(resource.ResourceType.cpu_cores)
disk = Resource(ResourceType.disk) disk = resource.Resource(resource.ResourceType.disk)
cluster.create_resource(mem) cluster.create_resource(mem)
cluster.create_resource(num_cores) cluster.create_resource(num_cores)
cluster.create_resource(disk) cluster.create_resource(disk)
@ -52,7 +50,7 @@ class NovaClusterModelCollector(BaseClusterModelCollector):
for h in hypervisors: for h in hypervisors:
service = self.wrapper.nova.services.find(id=h.service['id']) service = self.wrapper.nova.services.find(id=h.service['id'])
# create hypervisor in cluster_model_collector # create hypervisor in cluster_model_collector
hypervisor = Hypervisor() hypervisor = obj_hypervisor.Hypervisor()
hypervisor.uuid = service.host hypervisor.uuid = service.host
hypervisor.hostname = h.hypervisor_hostname hypervisor.hostname = h.hypervisor_hostname
# set capacity # set capacity
@ -65,7 +63,7 @@ class NovaClusterModelCollector(BaseClusterModelCollector):
vms = self.wrapper.get_vms_by_hypervisor(str(service.host)) vms = self.wrapper.get_vms_by_hypervisor(str(service.host))
for v in vms: for v in vms:
# create VM in cluster_model_collector # create VM in cluster_model_collector
vm = VM() vm = obj_vm.VM()
vm.uuid = v.id vm.uuid = v.id
# nova/nova/compute/vm_states.py # nova/nova/compute/vm_states.py
vm.state = getattr(v, 'OS-EXT-STS:vm_state') vm.state = getattr(v, 'OS-EXT-STS:vm_state')

View File

@ -42,7 +42,6 @@ class Action(base.WatcherObject):
'uuid': obj_utils.str_or_none, 'uuid': obj_utils.str_or_none,
'action_plan_id': obj_utils.int_or_none, 'action_plan_id': obj_utils.int_or_none,
'action_type': obj_utils.str_or_none, 'action_type': obj_utils.str_or_none,
'applies_to': obj_utils.str_or_none,
'input_parameters': obj_utils.dict_or_none, 'input_parameters': obj_utils.dict_or_none,
'state': obj_utils.str_or_none, 'state': obj_utils.str_or_none,
# todo(jed) remove parameter alarm # todo(jed) remove parameter alarm

View File

@ -0,0 +1,54 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2016 b<>com
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import voluptuous
from watcher.applier.actions import base as baction
from watcher.applier.actions import change_nova_service_state
from watcher.decision_engine.model import hypervisor_state as hstate
from watcher.tests import base
class TestChangeNovaServiceState(base.TestCase):
def setUp(self):
super(TestChangeNovaServiceState, self).setUp()
self.a = change_nova_service_state.ChangeNovaServiceState()
def test_parameters_down(self):
self.a.input_parameters = {
baction.BaseAction.RESOURCE_ID: "compute-1",
self.a.STATE: hstate.HypervisorState.OFFLINE.value}
self.assertEqual(True, self.a.validate_parameters())
def test_parameters_up(self):
self.a.input_parameters = {
baction.BaseAction.RESOURCE_ID: "compute-1",
self.a.STATE: hstate.HypervisorState.ONLINE.value}
self.assertEqual(True, self.a.validate_parameters())
def test_parameters_exception_wrong_state(self):
self.a.input_parameters = {
baction.BaseAction.RESOURCE_ID: "compute-1",
self.a.STATE: 'error'}
self.assertRaises(voluptuous.Invalid, self.a.validate_parameters)
def test_parameters_resource_id_empty(self):
self.a.input_parameters = {
self.a.STATE: None}
self.assertRaises(voluptuous.Invalid, self.a.validate_parameters)
def test_parameters_applies_add_extra(self):
self.a.input_parameters = {"extra": "failed"}
self.assertRaises(voluptuous.Invalid, self.a.validate_parameters)

View File

@ -0,0 +1,78 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2016 b<>com
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import voluptuous
from watcher.applier.actions import base as baction
from watcher.applier.actions import migration
from watcher.tests import base
class TestMigration(base.TestCase):
def setUp(self):
super(TestMigration, self).setUp()
self.mig = migration.Migrate()
def test_parameters(self):
params = {baction.BaseAction.RESOURCE_ID:
"45a37aeb-95ab-4ddb-a305-7d9f62c2f5ba",
self.mig.MIGRATION_TYPE: 'live',
self.mig.DST_HYPERVISOR: 'compute-2',
self.mig.SRC_HYPERVISOR: 'compute3'}
self.mig.input_parameters = params
self.assertEqual(True, self.mig.validate_parameters())
def test_parameters_exception_resource_id(self):
parameters = {baction.BaseAction.RESOURCE_ID: "EFEF",
'migration_type': 'live',
'src_hypervisor': 'compute-2',
'dst_hypervisor': 'compute3'}
self.mig.input_parameters = parameters
self.assertRaises(voluptuous.Invalid, self.mig.validate_parameters)
def test_parameters_exception_migration_type(self):
parameters = {baction.BaseAction.RESOURCE_ID:
"45a37aeb-95ab-4ddb-a305-7d9f62c2f5ba",
'migration_type': 'cold',
'src_hypervisor': 'compute-2',
'dst_hypervisor': 'compute3'}
self.mig.input_parameters = parameters
self.assertRaises(voluptuous.Invalid, self.mig.validate_parameters)
def test_parameters_exception_src_hypervisor(self):
parameters = {baction.BaseAction.RESOURCE_ID:
"45a37aeb-95ab-4ddb-a305-7d9f62c2f5ba",
'migration_type': 'cold',
'src_hypervisor': None,
'dst_hypervisor': 'compute3'}
self.mig.input_parameters = parameters
self.assertRaises(voluptuous.Invalid, self.mig.validate_parameters)
def test_parameters_exception_dst_hypervisor(self):
parameters = {baction.BaseAction.RESOURCE_ID:
"45a37aeb-95ab-4ddb-a305-7d9f62c2f5ba",
'migration_type': 'cold',
'src_hypervisor': 'compute-1',
'dst_hypervisor': None}
self.mig.input_parameters = parameters
self.assertRaises(voluptuous.Invalid, self.mig.validate_parameters)
def test_parameters_exception_empty_fields(self):
parameters = {baction.BaseAction.RESOURCE_ID: None,
'migration_type': None,
'src_hypervisor': None,
'dst_hypervisor': None}
self.mig.input_parameters = parameters
self.assertRaises(voluptuous.Invalid, self.mig.validate_parameters)

View File

@ -0,0 +1,42 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2016 b<>com
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import voluptuous
from watcher.applier.actions import sleep
from watcher.tests import base
class TestSleep(base.TestCase):
def setUp(self):
super(TestSleep, self).setUp()
self.s = sleep.Sleep()
def test_parameters_duration(self):
self.s.input_parameters = {self.s.DURATION: 1.0}
self.assertEqual(True, self.s.validate_parameters())
def test_parameters_duration_empty(self):
self.s.input_parameters = {self.s.DURATION: None}
self.assertRaises(voluptuous.Invalid, self.s.validate_parameters)
def test_parameters_wrong_parameter(self):
self.s.input_parameters = {self.s.DURATION: "ef"}
self.assertRaises(voluptuous.Invalid, self.s.validate_parameters)
def test_parameters_add_field(self):
self.s.input_parameters = {self.s.DURATION: 1.0, "not_required": "nop"}
self.assertRaises(voluptuous.Invalid, self.s.validate_parameters)

View File

@ -32,6 +32,12 @@ from watcher.tests.db import base
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class FakeAction(abase.BaseAction): class FakeAction(abase.BaseAction):
def schema(self):
pass
def postcondition(self):
pass
def precondition(self): def precondition(self):
pass pass
@ -62,12 +68,11 @@ class TestDefaultWorkFlowEngine(base.DbTestCase):
result = self.engine.execute(actions) result = self.engine.execute(actions)
self.assertEqual(result, True) self.assertEqual(result, True)
def create_action(self, action_type, applies_to, parameters, next): def create_action(self, action_type, parameters, next):
action = { action = {
'uuid': utils.generate_uuid(), 'uuid': utils.generate_uuid(),
'action_plan_id': 0, 'action_plan_id': 0,
'action_type': action_type, 'action_type': action_type,
'applies_to': applies_to,
'input_parameters': parameters, 'input_parameters': parameters,
'state': objects.action.State.PENDING, 'state': objects.action.State.PENDING,
'alarm': None, 'alarm': None,
@ -92,15 +97,15 @@ class TestDefaultWorkFlowEngine(base.DbTestCase):
self.assertEqual(result, True) self.assertEqual(result, True)
def test_execute_with_one_action(self): def test_execute_with_one_action(self):
actions = [self.create_action("nop", "", {'message': 'test'}, None)] actions = [self.create_action("nop", {'message': 'test'}, None)]
result = self.engine.execute(actions) result = self.engine.execute(actions)
self.assertEqual(result, True) self.assertEqual(result, True)
self.check_actions_state(actions, objects.action.State.SUCCEEDED) self.check_actions_state(actions, objects.action.State.SUCCEEDED)
def test_execute_with_two_actions(self): def test_execute_with_two_actions(self):
actions = [] actions = []
next = self.create_action("sleep", "", {'duration': '0'}, None) next = self.create_action("sleep", {'duration': 0.0}, None)
first = self.create_action("nop", "", {'message': 'test'}, next.id) first = self.create_action("nop", {'message': 'test'}, next.id)
actions.append(first) actions.append(first)
actions.append(next) actions.append(next)
@ -111,9 +116,9 @@ class TestDefaultWorkFlowEngine(base.DbTestCase):
def test_execute_with_three_actions(self): def test_execute_with_three_actions(self):
actions = [] actions = []
next2 = self.create_action("nop", "vm1", {'message': 'next'}, None) next2 = self.create_action("nop", {'message': 'next'}, None)
next = self.create_action("sleep", "vm1", {'duration': '0'}, next2.id) next = self.create_action("sleep", {'duration': 0.0}, next2.id)
first = self.create_action("nop", "vm1", {'message': 'hello'}, next.id) first = self.create_action("nop", {'message': 'hello'}, next.id)
self.check_action_state(first, objects.action.State.PENDING) self.check_action_state(first, objects.action.State.PENDING)
self.check_action_state(next, objects.action.State.PENDING) self.check_action_state(next, objects.action.State.PENDING)
self.check_action_state(next2, objects.action.State.PENDING) self.check_action_state(next2, objects.action.State.PENDING)
@ -128,12 +133,9 @@ class TestDefaultWorkFlowEngine(base.DbTestCase):
def test_execute_with_exception(self): def test_execute_with_exception(self):
actions = [] actions = []
next2 = self.create_action("no_exist", next2 = self.create_action("no_exist", {'message': 'next'}, None)
"vm1", {'message': 'next'}, None) next = self.create_action("sleep", {'duration': 0.0}, next2.id)
next = self.create_action("sleep", "vm1", first = self.create_action("nop", {'message': 'hello'}, next.id)
{'duration': '0'}, next2.id)
first = self.create_action("nop", "vm1",
{'message': 'hello'}, next.id)
self.check_action_state(first, objects.action.State.PENDING) self.check_action_state(first, objects.action.State.PENDING)
self.check_action_state(next, objects.action.State.PENDING) self.check_action_state(next, objects.action.State.PENDING)
@ -158,7 +160,7 @@ class TestDefaultWorkFlowEngine(base.DbTestCase):
plugin=FakeAction, plugin=FakeAction,
obj=None), obj=None),
namespace=FakeAction.namespace()) namespace=FakeAction.namespace())
actions = [self.create_action("dontcare", "vm1", {}, None)] actions = [self.create_action("dontcare", {}, None)]
result = self.engine.execute(actions) result = self.engine.execute(actions)
self.assertEqual(result, False) self.assertEqual(result, False)
self.check_action_state(actions[0], objects.action.State.FAILED) self.check_action_state(actions[0], objects.action.State.FAILED)

View File

@ -84,10 +84,12 @@ def get_test_action(**kwargs):
'uuid': kwargs.get('uuid', '10a47dd1-4874-4298-91cf-eff046dbdb8d'), 'uuid': kwargs.get('uuid', '10a47dd1-4874-4298-91cf-eff046dbdb8d'),
'action_plan_id': kwargs.get('action_plan_id', 1), 'action_plan_id': kwargs.get('action_plan_id', 1),
'action_type': kwargs.get('action_type', 'nop'), 'action_type': kwargs.get('action_type', 'nop'),
'applies_to': kwargs.get('applies_to', 'input_parameters':
'10a47dd1-4874-4298-91cf-eff046dbdb8d'), kwargs.get('input_parameters',
'input_parameters': kwargs.get('input_parameters', {'key1': 'val1', {'key1': 'val1',
'key2': 'val2'}), 'key2': 'val2',
'resource_id':
'10a47dd1-4874-4298-91cf-eff046dbdb8d'}),
'state': kwargs.get('state', 'PENDING'), 'state': kwargs.get('state', 'PENDING'),
'alarm': kwargs.get('alarm', None), 'alarm': kwargs.get('alarm', None),
'next': kwargs.get('next', 2), 'next': kwargs.get('next', 2),

View File

@ -18,55 +18,60 @@
# #
import uuid import uuid
from watcher.decision_engine.model.hypervisor import Hypervisor from watcher.decision_engine.model import hypervisor as modelhyp
from watcher.decision_engine.model.vm_state import VMState from watcher.decision_engine.model import vm_state
from watcher.tests import base from watcher.tests import base
from watcher.tests.decision_engine.strategy.strategies.faker_cluster_state import \ from watcher.tests.decision_engine.strategy.strategies import \
FakerModelCollector faker_cluster_state
class TestMapping(base.BaseTestCase): class TestMapping(base.BaseTestCase):
VM1_UUID = "73b09e16-35b7-4922-804e-e8f5d9b740fc"
VM2_UUID = "a4cab39b-9828-413a-bf88-f76921bf1517"
def test_get_node_from_vm(self): def test_get_node_from_vm(self):
fake_cluster = FakerModelCollector() fake_cluster = faker_cluster_state.FakerModelCollector()
model = fake_cluster.generate_scenario_3_with_2_hypervisors() model = fake_cluster.generate_scenario_3_with_2_hypervisors()
vms = model.get_all_vms() vms = model.get_all_vms()
keys = list(vms.keys()) keys = list(vms.keys())
vm = vms[keys[0]] vm = vms[keys[0]]
if vm.uuid != 'VM_0': if vm.uuid != self.VM1_UUID:
vm = vms[keys[1]] vm = vms[keys[1]]
node = model.mapping.get_node_from_vm(vm) node = model.mapping.get_node_from_vm(vm)
self.assertEqual(node.uuid, 'Node_0') self.assertEqual(node.uuid, 'Node_0')
def test_get_node_from_vm_id(self): def test_get_node_from_vm_id(self):
fake_cluster = FakerModelCollector() fake_cluster = faker_cluster_state.FakerModelCollector()
model = fake_cluster.generate_scenario_3_with_2_hypervisors() model = fake_cluster.generate_scenario_3_with_2_hypervisors()
hyps = model.mapping.get_node_vms_from_id("BLABLABLA") hyps = model.mapping.get_node_vms_from_id("BLABLABLA")
self.assertEqual(hyps.__len__(), 0) self.assertEqual(hyps.__len__(), 0)
def test_get_all_vms(self): def test_get_all_vms(self):
fake_cluster = FakerModelCollector() fake_cluster = faker_cluster_state.FakerModelCollector()
model = fake_cluster.generate_scenario_3_with_2_hypervisors() model = fake_cluster.generate_scenario_3_with_2_hypervisors()
vms = model.get_all_vms() vms = model.get_all_vms()
self.assertEqual(vms.__len__(), 2) self.assertEqual(vms.__len__(), 2)
self.assertEqual(vms['VM_0'].state, VMState.ACTIVE.value) self.assertEqual(vms[self.VM1_UUID].state,
self.assertEqual(vms['VM_0'].uuid, 'VM_0') vm_state.VMState.ACTIVE.value)
self.assertEqual(vms['VM_1'].state, VMState.ACTIVE.value) self.assertEqual(vms[self.VM1_UUID].uuid, self.VM1_UUID)
self.assertEqual(vms['VM_1'].uuid, 'VM_1') self.assertEqual(vms[self.VM2_UUID].state,
vm_state.VMState.ACTIVE.value)
self.assertEqual(vms[self.VM2_UUID].uuid, self.VM2_UUID)
def test_get_mapping(self): def test_get_mapping(self):
fake_cluster = FakerModelCollector() fake_cluster = faker_cluster_state.FakerModelCollector()
model = fake_cluster.generate_scenario_3_with_2_hypervisors() model = fake_cluster.generate_scenario_3_with_2_hypervisors()
mapping_vm = model.mapping.get_mapping_vm() mapping_vm = model.mapping.get_mapping_vm()
self.assertEqual(mapping_vm.__len__(), 2) self.assertEqual(mapping_vm.__len__(), 2)
self.assertEqual(mapping_vm['VM_0'], 'Node_0') self.assertEqual(mapping_vm[self.VM1_UUID], 'Node_0')
self.assertEqual(mapping_vm['VM_1'], 'Node_1') self.assertEqual(mapping_vm[self.VM2_UUID], 'Node_1')
def test_migrate_vm(self): def test_migrate_vm(self):
fake_cluster = FakerModelCollector() fake_cluster = faker_cluster_state.FakerModelCollector()
model = fake_cluster.generate_scenario_3_with_2_hypervisors() model = fake_cluster.generate_scenario_3_with_2_hypervisors()
vms = model.get_all_vms() vms = model.get_all_vms()
keys = list(vms.keys()) keys = list(vms.keys())
@ -81,13 +86,13 @@ class TestMapping(base.BaseTestCase):
self.assertEqual(model.mapping.migrate_vm(vm1, hyp0, hyp1), True) self.assertEqual(model.mapping.migrate_vm(vm1, hyp0, hyp1), True)
def test_unmap_from_id_log_warning(self): def test_unmap_from_id_log_warning(self):
fake_cluster = FakerModelCollector() fake_cluster = faker_cluster_state.FakerModelCollector()
model = fake_cluster.generate_scenario_3_with_2_hypervisors() model = fake_cluster.generate_scenario_3_with_2_hypervisors()
vms = model.get_all_vms() vms = model.get_all_vms()
keys = list(vms.keys()) keys = list(vms.keys())
vm0 = vms[keys[0]] vm0 = vms[keys[0]]
id = "{0}".format(uuid.uuid4()) id = "{0}".format(uuid.uuid4())
hypervisor = Hypervisor() hypervisor = modelhyp.Hypervisor()
hypervisor.uuid = id hypervisor.uuid = id
model.mapping.unmap_from_id(hypervisor.uuid, vm0.uuid) model.mapping.unmap_from_id(hypervisor.uuid, vm0.uuid)
@ -95,7 +100,7 @@ class TestMapping(base.BaseTestCase):
# hypervisor.uuid)), 1) # hypervisor.uuid)), 1)
def test_unmap_from_id(self): def test_unmap_from_id(self):
fake_cluster = FakerModelCollector() fake_cluster = faker_cluster_state.FakerModelCollector()
model = fake_cluster.generate_scenario_3_with_2_hypervisors() model = fake_cluster.generate_scenario_3_with_2_hypervisors()
vms = model.get_all_vms() vms = model.get_all_vms()
keys = list(vms.keys()) keys = list(vms.keys())

View File

@ -68,7 +68,7 @@ class TestActionScheduling(base.DbTestCase):
"dst_uuid_hypervisor": "server2", "dst_uuid_hypervisor": "server2",
} }
solution.add_action(action_type="migrate", solution.add_action(action_type="migrate",
applies_to="b199db0c-1408-4d52-b5a5-5ca14de0ff36", resource_id="b199db0c-1408-4d52-b5a5-5ca14de0ff36",
input_parameters=parameters) input_parameters=parameters)
with mock.patch.object( with mock.patch.object(
@ -94,11 +94,11 @@ class TestActionScheduling(base.DbTestCase):
"dst_uuid_hypervisor": "server2", "dst_uuid_hypervisor": "server2",
} }
solution.add_action(action_type="migrate", solution.add_action(action_type="migrate",
applies_to="b199db0c-1408-4d52-b5a5-5ca14de0ff36", resource_id="b199db0c-1408-4d52-b5a5-5ca14de0ff36",
input_parameters=parameters) input_parameters=parameters)
solution.add_action(action_type="nop", solution.add_action(action_type="nop",
applies_to="", resource_id="",
input_parameters={}) input_parameters={})
with mock.patch.object( with mock.patch.object(

View File

@ -13,27 +13,29 @@
# implied. # implied.
# 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.
from watcher.decision_engine.solution.default import DefaultSolution
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 = DefaultSolution() solution = default.DefaultSolution()
parameters = { parameters = {
"src_uuid_hypervisor": "server1", "src_uuid_hypervisor": "server1",
"dst_uuid_hypervisor": "server2", "dst_uuid_hypervisor": "server2",
} }
solution.add_action(action_type="nop", solution.add_action(action_type="nop",
applies_to="b199db0c-1408-4d52-b5a5-5ca14de0ff36", resource_id="b199db0c-1408-4d52-b5a5-5ca14de0ff36",
input_parameters=parameters) input_parameters=parameters)
self.assertEqual(len(solution.actions), 1) self.assertEqual(len(solution.actions), 1)
expected_action_type = "nop" expected_action_type = "nop"
expected_applies_to = "b199db0c-1408-4d52-b5a5-5ca14de0ff36" expected_parameters = {
expected_parameters = parameters "src_uuid_hypervisor": "server1",
"dst_uuid_hypervisor": "server2",
"resource_id": "b199db0c-1408-4d52-b5a5-5ca14de0ff36"
}
self.assertEqual(solution.actions[0].get('action_type'), self.assertEqual(solution.actions[0].get('action_type'),
expected_action_type) expected_action_type)
self.assertEqual(solution.actions[0].get('applies_to'),
expected_applies_to)
self.assertEqual(solution.actions[0].get('input_parameters'), self.assertEqual(solution.actions[0].get('input_parameters'),
expected_parameters) expected_parameters)

View File

@ -16,81 +16,24 @@
# 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.
# #
from watcher.decision_engine.model import hypervisor
import random from watcher.decision_engine.model import model_root as modelroot
from watcher.decision_engine.model import resource
from watcher.decision_engine.model.hypervisor import Hypervisor from watcher.decision_engine.model import vm as modelvm
from watcher.decision_engine.model.model_root import ModelRoot from watcher.metrics_engine.cluster_model_collector import api
from watcher.decision_engine.model.resource import Resource
from watcher.decision_engine.model.resource import ResourceType
from watcher.decision_engine.model.vm import VM
from watcher.metrics_engine.cluster_model_collector.api import \
BaseClusterModelCollector
class FakerModelCollector(BaseClusterModelCollector): class FakerModelCollector(api.BaseClusterModelCollector):
def __init__(self): def __init__(self):
pass pass
def get_latest_cluster_data_model(self): def get_latest_cluster_data_model(self):
return self.generate_scenario_1() return self.generate_scenario_1()
def generate_random(self, count_nodes, number_of_vm_per_node):
vms = []
current_state_cluster = ModelRoot()
# number of nodes
count_node = count_nodes
# number max of vm per hypervisor
node_count_vm = number_of_vm_per_node
# total number of virtual machine
count_vm = (count_node * node_count_vm)
# define ressouce ( CPU, MEM disk, ... )
mem = Resource(ResourceType.memory)
# 2199.954 Mhz
num_cores = Resource(ResourceType.cpu_cores)
disk = Resource(ResourceType.disk)
current_state_cluster.create_resource(mem)
current_state_cluster.create_resource(num_cores)
current_state_cluster.create_resource(disk)
for i in range(0, count_node):
node_uuid = "Node_{0}".format(i)
hypervisor = Hypervisor()
hypervisor.uuid = node_uuid
hypervisor.hostname = "host_{0}".format(i)
mem.set_capacity(hypervisor, 132)
disk.set_capacity(hypervisor, 250)
num_cores.set_capacity(hypervisor, 40)
current_state_cluster.add_hypervisor(hypervisor)
for i in range(0, count_vm):
vm_uuid = "VM_{0}".format(i)
vm = VM()
vm.uuid = vm_uuid
mem.set_capacity(vm, 8)
disk.set_capacity(vm, 10)
num_cores.set_capacity(vm, 10)
vms.append(vm)
current_state_cluster.add_vm(vm)
j = 0
for node_id in current_state_cluster.get_all_hypervisors():
for i in range(0, random.randint(0, node_count_vm)):
# todo(jed) check if enough capacity
current_state_cluster.get_mapping().map(
current_state_cluster.get_hypervisor_from_id(node_id),
vms[j])
j += 1
return current_state_cluster
def generate_scenario_1(self): def generate_scenario_1(self):
vms = [] vms = []
current_state_cluster = ModelRoot() current_state_cluster = modelroot.ModelRoot()
# number of nodes # number of nodes
count_node = 5 count_node = 5
# number max of vm per node # number max of vm per node
@ -99,10 +42,10 @@ class FakerModelCollector(BaseClusterModelCollector):
count_vm = (count_node * node_count_vm) count_vm = (count_node * node_count_vm)
# define ressouce ( CPU, MEM disk, ... ) # define ressouce ( CPU, MEM disk, ... )
mem = Resource(ResourceType.memory) mem = resource.Resource(resource.ResourceType.memory)
# 2199.954 Mhz # 2199.954 Mhz
num_cores = Resource(ResourceType.cpu_cores) num_cores = resource.Resource(resource.ResourceType.cpu_cores)
disk = Resource(ResourceType.disk) disk = resource.Resource(resource.ResourceType.disk)
current_state_cluster.create_resource(mem) current_state_cluster.create_resource(mem)
current_state_cluster.create_resource(num_cores) current_state_cluster.create_resource(num_cores)
@ -110,7 +53,7 @@ class FakerModelCollector(BaseClusterModelCollector):
for i in range(0, count_node): for i in range(0, count_node):
node_uuid = "Node_{0}".format(i) node_uuid = "Node_{0}".format(i)
node = Hypervisor() node = hypervisor.Hypervisor()
node.uuid = node_uuid node.uuid = node_uuid
node.hostname = "hostname_{0}".format(i) node.hostname = "hostname_{0}".format(i)
@ -121,7 +64,7 @@ class FakerModelCollector(BaseClusterModelCollector):
for i in range(0, count_vm): for i in range(0, count_vm):
vm_uuid = "VM_{0}".format(i) vm_uuid = "VM_{0}".format(i)
vm = VM() vm = modelvm.VM()
vm.uuid = vm_uuid vm.uuid = vm_uuid
mem.set_capacity(vm, 2) mem.set_capacity(vm, 2)
disk.set_capacity(vm, 20) disk.set_capacity(vm, 20)
@ -168,130 +111,68 @@ class FakerModelCollector(BaseClusterModelCollector):
model.get_hypervisor_from_id(h_id), model.get_hypervisor_from_id(h_id),
model.get_vm_from_id(vm_id)) model.get_vm_from_id(vm_id))
def generate_scenario_2(self):
vms = []
current_state_cluster = ModelRoot()
# number of nodes
count_node = 10
# number max of vm per node
node_count_vm = 7
# total number of virtual machine
count_vm = (count_node * node_count_vm)
# define ressouce ( CPU, MEM disk, ... )
mem = Resource(ResourceType.memory)
# 2199.954 Mhz
num_cores = Resource(ResourceType.cpu_cores)
disk = Resource(ResourceType.disk)
current_state_cluster.create_resource(mem)
current_state_cluster.create_resource(num_cores)
current_state_cluster.create_resource(disk)
for i in range(0, count_node):
node_uuid = "Node_{0}".format(i)
node = Hypervisor()
node.uuid = node_uuid
node.hostname = "hostname_{0}".format(i)
mem.set_capacity(node, 132)
disk.set_capacity(node, 250)
num_cores.set_capacity(node, 40)
current_state_cluster.add_hypervisor(node)
for i in range(0, count_vm):
vm_uuid = "VM_{0}".format(i)
vm = VM()
vm.uuid = vm_uuid
mem.set_capacity(vm, 10)
disk.set_capacity(vm, 25)
num_cores.set_capacity(vm, 16)
vms.append(vm)
current_state_cluster.add_vm(vm)
indice = 0
for j in range(0, 2):
node_uuid = "Node_{0}".format(j)
for i in range(indice, 3):
vm_uuid = "VM_{0}".format(i)
self.map(current_state_cluster, node_uuid, vm_uuid)
for j in range(2, 5):
node_uuid = "Node_{0}".format(j)
for i in range(indice, 4):
vm_uuid = "VM_{0}".format(i)
self.map(current_state_cluster, node_uuid, vm_uuid)
for j in range(5, 10):
node_uuid = "Node_{0}".format(j)
for i in range(indice, 4):
vm_uuid = "VM_{0}".format(i)
self.map(current_state_cluster, node_uuid, vm_uuid)
return current_state_cluster
def generate_scenario_3_with_2_hypervisors(self): def generate_scenario_3_with_2_hypervisors(self):
vms = [] vms = []
current_state_cluster = ModelRoot() root = modelroot.ModelRoot()
# number of nodes # number of nodes
count_node = 2 count_node = 2
# number max of vm per node
node_count_vm = 1
# total number of virtual machine
count_vm = (count_node * node_count_vm)
# define ressouce ( CPU, MEM disk, ... ) # define ressouce ( CPU, MEM disk, ... )
mem = Resource(ResourceType.memory) mem = resource.Resource(resource.ResourceType.memory)
# 2199.954 Mhz # 2199.954 Mhz
num_cores = Resource(ResourceType.cpu_cores) num_cores = resource.Resource(resource.ResourceType.cpu_cores)
disk = Resource(ResourceType.disk) disk = resource.Resource(resource.ResourceType.disk)
current_state_cluster.create_resource(mem) root.create_resource(mem)
current_state_cluster.create_resource(num_cores) root.create_resource(num_cores)
current_state_cluster.create_resource(disk) root.create_resource(disk)
for i in range(0, count_node): for i in range(0, count_node):
node_uuid = "Node_{0}".format(i) node_uuid = "Node_{0}".format(i)
node = Hypervisor() node = hypervisor.Hypervisor()
node.uuid = node_uuid node.uuid = node_uuid
node.hostname = "hostname_{0}".format(i) node.hostname = "hostname_{0}".format(i)
mem.set_capacity(node, 132) mem.set_capacity(node, 132)
disk.set_capacity(node, 250) disk.set_capacity(node, 250)
num_cores.set_capacity(node, 40) num_cores.set_capacity(node, 40)
current_state_cluster.add_hypervisor(node) root.add_hypervisor(node)
for i in range(0, count_vm): vm1 = modelvm.VM()
vm_uuid = "VM_{0}".format(i) vm1.uuid = "73b09e16-35b7-4922-804e-e8f5d9b740fc"
vm = VM() mem.set_capacity(vm1, 2)
vm.uuid = vm_uuid disk.set_capacity(vm1, 20)
mem.set_capacity(vm, 2) num_cores.set_capacity(vm1, 10)
disk.set_capacity(vm, 20) vms.append(vm1)
num_cores.set_capacity(vm, 10) root.add_vm(vm1)
vms.append(vm)
current_state_cluster.add_vm(vm)
current_state_cluster.get_mapping().map( vm2 = modelvm.VM()
current_state_cluster.get_hypervisor_from_id("Node_0"), vm2.uuid = "a4cab39b-9828-413a-bf88-f76921bf1517"
current_state_cluster.get_vm_from_id("VM_0")) mem.set_capacity(vm2, 2)
disk.set_capacity(vm2, 20)
num_cores.set_capacity(vm2, 10)
vms.append(vm2)
root.add_vm(vm2)
current_state_cluster.get_mapping().map( root.get_mapping().map(root.get_hypervisor_from_id("Node_0"),
current_state_cluster.get_hypervisor_from_id("Node_1"), root.get_vm_from_id(str(vm1.uuid)))
current_state_cluster.get_vm_from_id("VM_1"))
return current_state_cluster root.get_mapping().map(root.get_hypervisor_from_id("Node_1"),
root.get_vm_from_id(str(vm2.uuid)))
return root
def generate_scenario_4_with_1_hypervisor_no_vm(self): def generate_scenario_4_with_1_hypervisor_no_vm(self):
current_state_cluster = ModelRoot() current_state_cluster = modelroot.ModelRoot()
# number of nodes # number of nodes
count_node = 1 count_node = 1
# define ressouce ( CPU, MEM disk, ... ) # define ressouce ( CPU, MEM disk, ... )
mem = Resource(ResourceType.memory) mem = resource.Resource(resource.ResourceType.memory)
# 2199.954 Mhz # 2199.954 Mhz
num_cores = Resource(ResourceType.cpu_cores) num_cores = resource.Resource(resource.ResourceType.cpu_cores)
disk = Resource(ResourceType.disk) disk = resource.Resource(resource.ResourceType.disk)
current_state_cluster.create_resource(mem) current_state_cluster.create_resource(mem)
current_state_cluster.create_resource(num_cores) current_state_cluster.create_resource(num_cores)
@ -299,7 +180,7 @@ class FakerModelCollector(BaseClusterModelCollector):
for i in range(0, count_node): for i in range(0, count_node):
node_uuid = "Node_{0}".format(i) node_uuid = "Node_{0}".format(i)
node = Hypervisor() node = hypervisor.Hypervisor()
node.uuid = node_uuid node.uuid = node_uuid
node.hostname = "hostname_{0}".format(i) node.hostname = "hostname_{0}".format(i)
@ -312,17 +193,17 @@ class FakerModelCollector(BaseClusterModelCollector):
def generate_scenario_5_with_vm_disk_0(self): def generate_scenario_5_with_vm_disk_0(self):
vms = [] vms = []
current_state_cluster = ModelRoot() current_state_cluster = modelroot.ModelRoot()
# number of nodes # number of nodes
count_node = 1 count_node = 1
# number of vms # number of vms
count_vm = 1 count_vm = 1
# define ressouce ( CPU, MEM disk, ... ) # define ressouce ( CPU, MEM disk, ... )
mem = Resource(ResourceType.memory) mem = resource.Resource(resource.ResourceType.memory)
# 2199.954 Mhz # 2199.954 Mhz
num_cores = Resource(ResourceType.cpu_cores) num_cores = resource.Resource(resource.ResourceType.cpu_cores)
disk = Resource(ResourceType.disk) disk = resource.Resource(resource.ResourceType.disk)
current_state_cluster.create_resource(mem) current_state_cluster.create_resource(mem)
current_state_cluster.create_resource(num_cores) current_state_cluster.create_resource(num_cores)
@ -330,7 +211,7 @@ class FakerModelCollector(BaseClusterModelCollector):
for i in range(0, count_node): for i in range(0, count_node):
node_uuid = "Node_{0}".format(i) node_uuid = "Node_{0}".format(i)
node = Hypervisor() node = hypervisor.Hypervisor()
node.uuid = node_uuid node.uuid = node_uuid
node.hostname = "hostname_{0}".format(i) node.hostname = "hostname_{0}".format(i)
@ -341,7 +222,7 @@ class FakerModelCollector(BaseClusterModelCollector):
for i in range(0, count_vm): for i in range(0, count_vm):
vm_uuid = "VM_{0}".format(i) vm_uuid = "VM_{0}".format(i)
vm = VM() vm = modelvm.VM()
vm.uuid = vm_uuid vm.uuid = vm_uuid
mem.set_capacity(vm, 2) mem.set_capacity(vm, 2)
disk.set_capacity(vm, 0) disk.set_capacity(vm, 0)

View File

@ -16,26 +16,26 @@
# 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.
# #
from collections import Counter import collections
import mock import mock
from watcher.applier.actions.loading import default
from watcher.common import exception from watcher.common import exception
from watcher.decision_engine.model.model_root import ModelRoot from watcher.decision_engine.model import model_root
from watcher.decision_engine.strategy.strategies.basic_consolidation import \ from watcher.decision_engine.strategy import strategies
BasicConsolidation
from watcher.tests import base from watcher.tests import base
from watcher.tests.decision_engine.strategy.strategies.faker_cluster_state \ from watcher.tests.decision_engine.strategy.strategies \
import FakerModelCollector import faker_cluster_state
from watcher.tests.decision_engine.strategy.strategies.faker_metrics_collector\ from watcher.tests.decision_engine.strategy.strategies \
import FakerMetricsCollector import faker_metrics_collector
class TestBasicConsolidation(base.BaseTestCase): class TestBasicConsolidation(base.BaseTestCase):
# fake metrics # fake metrics
fake_metrics = FakerMetricsCollector() fake_metrics = faker_metrics_collector.FakerMetricsCollector()
# fake cluster # fake cluster
fake_cluster = FakerModelCollector() fake_cluster = faker_cluster_state.FakerModelCollector()
def test_cluster_size(self): def test_cluster_size(self):
size_cluster = len( size_cluster = len(
@ -45,7 +45,7 @@ class TestBasicConsolidation(base.BaseTestCase):
def test_basic_consolidation_score_hypervisor(self): def test_basic_consolidation_score_hypervisor(self):
cluster = self.fake_cluster.generate_scenario_1() cluster = self.fake_cluster.generate_scenario_1()
sercon = BasicConsolidation() sercon = strategies.BasicConsolidation()
sercon.ceilometer = mock.MagicMock( sercon.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics) statistic_aggregation=self.fake_metrics.mock_get_statistics)
@ -67,7 +67,7 @@ class TestBasicConsolidation(base.BaseTestCase):
def test_basic_consolidation_score_vm(self): def test_basic_consolidation_score_vm(self):
cluster = self.fake_cluster.generate_scenario_1() cluster = self.fake_cluster.generate_scenario_1()
sercon = BasicConsolidation() sercon = strategies.BasicConsolidation()
sercon.ceilometer = mock.MagicMock( sercon.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics) statistic_aggregation=self.fake_metrics.mock_get_statistics)
vm_0 = cluster.get_vm_from_id("VM_0") vm_0 = cluster.get_vm_from_id("VM_0")
@ -90,7 +90,7 @@ class TestBasicConsolidation(base.BaseTestCase):
def test_basic_consolidation_score_vm_disk(self): def test_basic_consolidation_score_vm_disk(self):
cluster = self.fake_cluster.generate_scenario_5_with_vm_disk_0() cluster = self.fake_cluster.generate_scenario_5_with_vm_disk_0()
sercon = BasicConsolidation() sercon = strategies.BasicConsolidation()
sercon.ceilometer = mock.MagicMock( sercon.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics) statistic_aggregation=self.fake_metrics.mock_get_statistics)
vm_0 = cluster.get_vm_from_id("VM_0") vm_0 = cluster.get_vm_from_id("VM_0")
@ -99,7 +99,7 @@ class TestBasicConsolidation(base.BaseTestCase):
def test_basic_consolidation_weight(self): def test_basic_consolidation_weight(self):
cluster = self.fake_cluster.generate_scenario_1() cluster = self.fake_cluster.generate_scenario_1()
sercon = BasicConsolidation() sercon = strategies.BasicConsolidation()
sercon.ceilometer = mock.MagicMock( sercon.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics) statistic_aggregation=self.fake_metrics.mock_get_statistics)
vm_0 = cluster.get_vm_from_id("VM_0") vm_0 = cluster.get_vm_from_id("VM_0")
@ -114,24 +114,24 @@ class TestBasicConsolidation(base.BaseTestCase):
vm_0_weight_assert) vm_0_weight_assert)
def test_calculate_migration_efficacy(self): def test_calculate_migration_efficacy(self):
sercon = BasicConsolidation() sercon = strategies.BasicConsolidation()
sercon.calculate_migration_efficacy() sercon.calculate_migration_efficacy()
def test_exception_model(self): def test_exception_model(self):
sercon = BasicConsolidation() sercon = strategies.BasicConsolidation()
self.assertRaises(exception.ClusterStateNotDefined, sercon.execute, self.assertRaises(exception.ClusterStateNotDefined, sercon.execute,
None) None)
def test_exception_cluster_empty(self): def test_exception_cluster_empty(self):
sercon = BasicConsolidation() sercon = strategies.BasicConsolidation()
model = ModelRoot() model = model_root.ModelRoot()
self.assertRaises(exception.ClusterEmpty, sercon.execute, self.assertRaises(exception.ClusterEmpty, sercon.execute,
model) model)
def test_calculate_score_vm_raise_cluster_state_not_found(self): def test_calculate_score_vm_raise_cluster_state_not_found(self):
metrics = FakerMetricsCollector() metrics = faker_metrics_collector.FakerMetricsCollector()
metrics.empty_one_metric("CPU_COMPUTE") metrics.empty_one_metric("CPU_COMPUTE")
sercon = BasicConsolidation() sercon = strategies.BasicConsolidation()
sercon.ceilometer = mock.MagicMock( sercon.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics) statistic_aggregation=self.fake_metrics.mock_get_statistics)
@ -139,8 +139,8 @@ class TestBasicConsolidation(base.BaseTestCase):
sercon.calculate_score_vm, "VM_1", None) sercon.calculate_score_vm, "VM_1", None)
def test_check_migration(self): def test_check_migration(self):
sercon = BasicConsolidation() sercon = strategies.BasicConsolidation()
fake_cluster = FakerModelCollector() fake_cluster = faker_cluster_state.FakerModelCollector()
model = fake_cluster.generate_scenario_3_with_2_hypervisors() model = fake_cluster.generate_scenario_3_with_2_hypervisors()
all_vms = model.get_all_vms() all_vms = model.get_all_vms()
@ -151,8 +151,8 @@ class TestBasicConsolidation(base.BaseTestCase):
sercon.check_migration(model, hyp0, hyp0, vm0) sercon.check_migration(model, hyp0, hyp0, vm0)
def test_threshold(self): def test_threshold(self):
sercon = BasicConsolidation() sercon = strategies.BasicConsolidation()
fake_cluster = FakerModelCollector() fake_cluster = faker_cluster_state.FakerModelCollector()
model = fake_cluster.generate_scenario_3_with_2_hypervisors() model = fake_cluster.generate_scenario_3_with_2_hypervisors()
all_hyps = model.get_all_hypervisors() all_hyps = model.get_all_hypervisors()
@ -165,19 +165,19 @@ class TestBasicConsolidation(base.BaseTestCase):
self.assertEqual(sercon.get_threshold_cores(), threshold_cores + 1) self.assertEqual(sercon.get_threshold_cores(), threshold_cores + 1)
def test_number_of(self): def test_number_of(self):
sercon = BasicConsolidation() sercon = strategies.BasicConsolidation()
sercon.get_number_of_released_nodes() sercon.get_number_of_released_nodes()
sercon.get_number_of_migrations() sercon.get_number_of_migrations()
def test_basic_consolidation_migration(self): def test_basic_consolidation_migration(self):
sercon = BasicConsolidation() sercon = strategies.BasicConsolidation()
sercon.ceilometer = mock.MagicMock( sercon.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics) statistic_aggregation=self.fake_metrics.mock_get_statistics)
solution = sercon.execute( solution = sercon.execute(
self.fake_cluster.generate_scenario_3_with_2_hypervisors()) self.fake_cluster.generate_scenario_3_with_2_hypervisors())
actions_counter = Counter( actions_counter = collections.Counter(
[action.get('action_type') for action in solution.actions]) [action.get('action_type') for action in solution.actions])
expected_num_migrations = 1 expected_num_migrations = 1
@ -189,26 +189,31 @@ class TestBasicConsolidation(base.BaseTestCase):
self.assertEqual(num_migrations, expected_num_migrations) self.assertEqual(num_migrations, expected_num_migrations)
self.assertEqual(num_hypervisor_state_change, expected_power_state) self.assertEqual(num_hypervisor_state_change, expected_power_state)
def test_execute_cluster_empty(self):
current_state_cluster = FakerModelCollector()
sercon = BasicConsolidation("sercon", "Basic offline consolidation")
sercon.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics)
model = current_state_cluster.generate_random(0, 0)
self.assertRaises(exception.ClusterEmpty, sercon.execute, model)
# calculate_weight # calculate_weight
def test_execute_no_workload(self): def test_execute_no_workload(self):
sercon = BasicConsolidation() sercon = strategies.BasicConsolidation()
sercon.ceilometer = mock.MagicMock( sercon.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics) statistic_aggregation=self.fake_metrics.mock_get_statistics)
current_state_cluster = FakerModelCollector() current_state_cluster = faker_cluster_state.FakerModelCollector()
model = current_state_cluster. \ model = current_state_cluster. \
generate_scenario_4_with_1_hypervisor_no_vm() generate_scenario_4_with_1_hypervisor_no_vm()
with mock.patch.object(BasicConsolidation, 'calculate_weight') \ with mock.patch.object(strategies.BasicConsolidation,
'calculate_weight') \
as mock_score_call: as mock_score_call:
mock_score_call.return_value = 0 mock_score_call.return_value = 0
solution = sercon.execute(model) solution = sercon.execute(model)
self.assertEqual(solution.efficacy, 100) self.assertEqual(solution.efficacy, 100)
def test_check_parameters(self):
sercon = strategies.BasicConsolidation()
sercon.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics)
solution = sercon.execute(
self.fake_cluster.generate_scenario_3_with_2_hypervisors())
loader = default.DefaultActionLoader()
for action in solution.actions:
loaded_action = loader.load(action['action_type'])
loaded_action.input_parameters = action['input_parameters']
loaded_action.validate_parameters()

View File

@ -13,16 +13,29 @@
# implied. # implied.
# 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.
from watcher.decision_engine.strategy.strategies.dummy_strategy import \
DummyStrategy from watcher.applier.actions.loading import default
from watcher.decision_engine.strategy import strategies
from watcher.tests import base from watcher.tests import base
from watcher.tests.decision_engine.strategy.strategies.faker_cluster_state\ from watcher.tests.decision_engine.strategy.strategies import \
import FakerModelCollector faker_cluster_state
class TestDummyStrategy(base.TestCase): class TestDummyStrategy(base.TestCase):
def test_dummy_strategy(self): def test_dummy_strategy(self):
tactique = DummyStrategy("basic", "Basic offline consolidation") dummy = strategies.DummyStrategy("dummy", "Dummy strategy")
fake_cluster = FakerModelCollector() fake_cluster = faker_cluster_state.FakerModelCollector()
model = fake_cluster.generate_scenario_3_with_2_hypervisors() model = fake_cluster.generate_scenario_3_with_2_hypervisors()
tactique.execute(model) solution = dummy.execute(model)
self.assertEqual(3, len(solution.actions))
def test_check_parameters(self):
dummy = strategies.DummyStrategy("dummy", "Dummy strategy")
fake_cluster = faker_cluster_state.FakerModelCollector()
model = fake_cluster.generate_scenario_3_with_2_hypervisors()
solution = dummy.execute(model)
loader = default.DefaultActionLoader()
for action in solution.actions:
loaded_action = loader.load(action['action_type'])
loaded_action.input_parameters = action['input_parameters']
loaded_action.validate_parameters()

View File

@ -16,35 +16,35 @@
# 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.
# #
from collections import Counter import collections
import mock import mock
from watcher.applier.actions.loading import default
from watcher.common import exception from watcher.common import exception
from watcher.decision_engine.model.model_root import ModelRoot from watcher.decision_engine.model import model_root
from watcher.decision_engine.model.resource import ResourceType from watcher.decision_engine.model import resource
from watcher.decision_engine.strategy.strategies.outlet_temp_control import \ from watcher.decision_engine.strategy import strategies
OutletTempControl
from watcher.tests import base from watcher.tests import base
from watcher.tests.decision_engine.strategy.strategies.faker_cluster_state \ from watcher.tests.decision_engine.strategy.strategies \
import FakerModelCollector import faker_cluster_state
from watcher.tests.decision_engine.strategy.strategies.faker_metrics_collector\ from watcher.tests.decision_engine.strategy.strategies \
import FakerMetricsCollector import faker_metrics_collector
class TestOutletTempControl(base.BaseTestCase): class TestOutletTempControl(base.BaseTestCase):
# fake metrics # fake metrics
fake_metrics = FakerMetricsCollector() fake_metrics = faker_metrics_collector.FakerMetricsCollector()
# fake cluster # fake cluster
fake_cluster = FakerModelCollector() fake_cluster = faker_cluster_state.FakerModelCollector()
def test_calc_used_res(self): def test_calc_used_res(self):
model = self.fake_cluster.generate_scenario_3_with_2_hypervisors() model = self.fake_cluster.generate_scenario_3_with_2_hypervisors()
strategy = OutletTempControl() strategy = strategies.OutletTempControl()
hypervisor = model.get_hypervisor_from_id('Node_0') hypervisor = model.get_hypervisor_from_id('Node_0')
cap_cores = model.get_resource_from_id(ResourceType.cpu_cores) cap_cores = model.get_resource_from_id(resource.ResourceType.cpu_cores)
cap_mem = model.get_resource_from_id(ResourceType.memory) cap_mem = model.get_resource_from_id(resource.ResourceType.memory)
cap_disk = model.get_resource_from_id(ResourceType.disk) cap_disk = model.get_resource_from_id(resource.ResourceType.disk)
cores_used, mem_used, disk_used = strategy.calc_used_res(model, cores_used, mem_used, disk_used = strategy.calc_used_res(model,
hypervisor, hypervisor,
cap_cores, cap_cores,
@ -55,7 +55,7 @@ class TestOutletTempControl(base.BaseTestCase):
def test_group_hosts_by_outlet_temp(self): def test_group_hosts_by_outlet_temp(self):
model = self.fake_cluster.generate_scenario_3_with_2_hypervisors() model = self.fake_cluster.generate_scenario_3_with_2_hypervisors()
strategy = OutletTempControl() strategy = strategies.OutletTempControl()
strategy.ceilometer = mock.MagicMock( strategy.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics) statistic_aggregation=self.fake_metrics.mock_get_statistics)
h1, h2 = strategy.group_hosts_by_outlet_temp(model) h1, h2 = strategy.group_hosts_by_outlet_temp(model)
@ -64,17 +64,18 @@ class TestOutletTempControl(base.BaseTestCase):
def test_choose_vm_to_migrate(self): def test_choose_vm_to_migrate(self):
model = self.fake_cluster.generate_scenario_3_with_2_hypervisors() model = self.fake_cluster.generate_scenario_3_with_2_hypervisors()
strategy = OutletTempControl() strategy = strategies.OutletTempControl()
strategy.ceilometer = mock.MagicMock( strategy.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics) statistic_aggregation=self.fake_metrics.mock_get_statistics)
h1, h2 = strategy.group_hosts_by_outlet_temp(model) h1, h2 = strategy.group_hosts_by_outlet_temp(model)
vm_to_mig = strategy.choose_vm_to_migrate(model, h1) vm_to_mig = strategy.choose_vm_to_migrate(model, h1)
self.assertEqual(vm_to_mig[0].uuid, 'Node_1') self.assertEqual(vm_to_mig[0].uuid, 'Node_1')
self.assertEqual(vm_to_mig[1].uuid, 'VM_1') self.assertEqual(vm_to_mig[1].uuid,
"a4cab39b-9828-413a-bf88-f76921bf1517")
def test_filter_dest_servers(self): def test_filter_dest_servers(self):
model = self.fake_cluster.generate_scenario_3_with_2_hypervisors() model = self.fake_cluster.generate_scenario_3_with_2_hypervisors()
strategy = OutletTempControl() strategy = strategies.OutletTempControl()
strategy.ceilometer = mock.MagicMock( strategy.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics) statistic_aggregation=self.fake_metrics.mock_get_statistics)
h1, h2 = strategy.group_hosts_by_outlet_temp(model) h1, h2 = strategy.group_hosts_by_outlet_temp(model)
@ -84,29 +85,28 @@ class TestOutletTempControl(base.BaseTestCase):
self.assertEqual(dest_hosts[0]['hv'].uuid, 'Node_0') self.assertEqual(dest_hosts[0]['hv'].uuid, 'Node_0')
def test_exception_model(self): def test_exception_model(self):
strategy = OutletTempControl() strategy = strategies.OutletTempControl()
self.assertRaises(exception.ClusterStateNotDefined, strategy.execute, self.assertRaises(exception.ClusterStateNotDefined, strategy.execute,
None) None)
def test_exception_cluster_empty(self): def test_exception_cluster_empty(self):
strategy = OutletTempControl() strategy = strategies.OutletTempControl()
model = ModelRoot() model = model_root.ModelRoot()
self.assertRaises(exception.ClusterEmpty, strategy.execute, model) self.assertRaises(exception.ClusterEmpty, strategy.execute, model)
def test_execute_cluster_empty(self): def test_execute_cluster_empty(self):
current_state_cluster = FakerModelCollector() strategy = strategies.OutletTempControl()
strategy = OutletTempControl()
strategy.ceilometer = mock.MagicMock( strategy.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics) statistic_aggregation=self.fake_metrics.mock_get_statistics)
model = current_state_cluster.generate_random(0, 0) model = model_root.ModelRoot()
self.assertRaises(exception.ClusterEmpty, strategy.execute, model) self.assertRaises(exception.ClusterEmpty, strategy.execute, model)
def test_execute_no_workload(self): def test_execute_no_workload(self):
strategy = OutletTempControl() strategy = strategies.OutletTempControl()
strategy.ceilometer = mock.MagicMock( strategy.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics) statistic_aggregation=self.fake_metrics.mock_get_statistics)
current_state_cluster = FakerModelCollector() current_state_cluster = faker_cluster_state.FakerModelCollector()
model = current_state_cluster. \ model = current_state_cluster. \
generate_scenario_4_with_1_hypervisor_no_vm() generate_scenario_4_with_1_hypervisor_no_vm()
@ -114,13 +114,25 @@ class TestOutletTempControl(base.BaseTestCase):
self.assertEqual(solution.actions, []) self.assertEqual(solution.actions, [])
def test_execute(self): def test_execute(self):
strategy = OutletTempControl() strategy = strategies.OutletTempControl()
strategy.ceilometer = mock.MagicMock( strategy.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics) statistic_aggregation=self.fake_metrics.mock_get_statistics)
model = self.fake_cluster.generate_scenario_3_with_2_hypervisors() model = self.fake_cluster.generate_scenario_3_with_2_hypervisors()
solution = strategy.execute(model) solution = strategy.execute(model)
actions_counter = Counter( actions_counter = collections.Counter(
[action.get('action_type') for action in solution.actions]) [action.get('action_type') for action in solution.actions])
num_migrations = actions_counter.get("migrate", 0) num_migrations = actions_counter.get("migrate", 0)
self.assertEqual(num_migrations, 1) self.assertEqual(num_migrations, 1)
def test_check_parameters(self):
outlet = strategies.OutletTempControl()
outlet.ceilometer = mock.MagicMock(
statistic_aggregation=self.fake_metrics.mock_get_statistics)
model = self.fake_cluster.generate_scenario_3_with_2_hypervisors()
solution = outlet.execute(model)
loader = default.DefaultActionLoader()
for action in solution.actions:
loaded_action = loader.load(action['action_type'])
loaded_action.input_parameters = action['input_parameters']
loaded_action.validate_parameters()