Add a dynamic loading of Actions handlers in the Watcher Applier

In watcher, an audit generates a set of actions which
aims at achieving a given goal (lower energy consumption, ...).
It is possible to configure different strategies in order to achieve
each goal. Each strategy is written as a Python class which produces
a set of actions. Today, the set of possible actions is fixed for a
given version of Watcher and enables optimization algorithms to
include actions such as instance migration, changing hypervisor state,
changing power state (ACPI level, ...).

The objective of this patchset is to give the ability to load
the actions dynamically in order to apply the Action Plan.

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

Change-Id: Idf295b94dca549ac65d4636e8889c8ab2ecc0df6
This commit is contained in:
Jean-Emile DARTOIS 2016-01-11 16:02:28 +01:00
parent ed438d2eb2
commit 8bac4fd42a
29 changed files with 305 additions and 450 deletions

View File

@ -46,6 +46,11 @@ watcher_strategies =
basic = watcher.decision_engine.strategy.strategies.basic_consolidation:BasicConsolidation basic = watcher.decision_engine.strategy.strategies.basic_consolidation:BasicConsolidation
outlet_temp_control = watcher.decision_engine.strategy.strategies.outlet_temp_control:OutletTempControl outlet_temp_control = watcher.decision_engine.strategy.strategies.outlet_temp_control:OutletTempControl
watcher_actions =
migrate = watcher.applier.primitives.migration:Migrate
nop = watcher.applier.primitives.nop:Nop
change_nova_service_state = watcher.applier.primitives.change_nova_service_state:ChangeNovaServiceState
watcher_planners = watcher_planners =
default = watcher.decision_engine.planner.default:DefaultPlanner default = watcher.decision_engine.planner.default:DefaultPlanner

View File

@ -17,24 +17,23 @@
# limitations under the License. # limitations under the License.
# #
from watcher.applier.base import BaseApplier from watcher.applier import base
from watcher.applier.execution.executor import ActionPlanExecutor from watcher.applier.execution import default
from watcher.objects import Action from watcher import objects
from watcher.objects import ActionPlan
class DefaultApplier(BaseApplier): class DefaultApplier(base.BaseApplier):
def __init__(self, manager_applier, context): def __init__(self, manager_applier, context):
super(DefaultApplier, self).__init__() super(DefaultApplier, self).__init__()
self.manager_applier = manager_applier self.manager_applier = manager_applier
self.context = context self.context = context
self.executor = ActionPlanExecutor(manager_applier, context) self.executor = default.DefaultActionPlanExecutor(manager_applier,
context)
def execute(self, action_plan_uuid): def execute(self, action_plan_uuid):
action_plan = ActionPlan.get_by_uuid(self.context, action_plan_uuid) action_plan = objects.ActionPlan.get_by_uuid(self.context,
action_plan_uuid)
# todo(jed) remove direct access to dbapi need filter in object # todo(jed) remove direct access to dbapi need filter in object
actions = Action.dbapi.get_action_list(self.context, filters = {'action_plan_id': action_plan.id}
filters={ actions = objects.Action.dbapi.get_action_list(self.context, filters)
'action_plan_id':
action_plan.id})
return self.executor.execute(actions) return self.executor.execute(actions)

View File

@ -0,0 +1,62 @@
# -*- 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 abc
import six
from watcher.applier.messaging import events
from watcher.applier.primitives import factory
from watcher.common.messaging.events import event
from watcher import objects
@six.add_metaclass(abc.ABCMeta)
class BaseActionPlanExecutor(object):
def __init__(self, manager_applier, context):
self._manager_applier = manager_applier
self._context = context
self._action_factory = factory.ActionFactory()
@property
def context(self):
return self._context
@property
def manager_applier(self):
return self._manager_applier
@property
def action_factory(self):
return self._action_factory
def notify(self, action, state):
db_action = objects.Action.get_by_uuid(self.context, action.uuid)
db_action.state = state
db_action.save()
ev = event.Event()
ev.type = events.Events.LAUNCH_ACTION
ev.data = {}
payload = {'action_uuid': action.uuid,
'action_state': state}
self.manager_applier.topic_status.publish_event(ev.type.name,
payload)
@abc.abstractmethod
def execute(self, actions):
raise NotImplementedError()

View File

@ -0,0 +1,57 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.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.
#
from oslo_log import log
from watcher._i18n import _LE
from watcher.applier.execution import base
from watcher.applier.execution import deploy_phase
from watcher.objects import action_plan
LOG = log.getLogger(__name__)
class DefaultActionPlanExecutor(base.BaseActionPlanExecutor):
def __init__(self, manager_applier, context):
super(DefaultActionPlanExecutor, self).__init__(manager_applier,
context)
self.deploy = deploy_phase.DeployPhase(self)
def execute(self, actions):
for action in actions:
try:
self.notify(action, action_plan.Status.ONGOING)
loaded_action = self.action_factory.make_action(action)
result = self.deploy.execute_primitive(loaded_action)
if result is False:
self.notify(action, action_plan.Status.FAILED)
self.deploy.rollback()
return False
else:
self.deploy.populate(loaded_action)
self.notify(action, action_plan.Status.SUCCEEDED)
except Exception as e:
LOG.expection(e)
LOG.debug('The ActionPlanExecutor failed to execute the action'
' %s ', action)
LOG.error(_LE("Trigger a rollback"))
self.notify(action, action_plan.Status.FAILED)
self.deploy.rollback()
return False
return True

View File

@ -1,76 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.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.
#
from oslo_log import log
from watcher.applier.execution.deploy_phase import DeployPhase
from watcher.applier.mapping.default import DefaultActionMapper
from watcher.applier.messaging.events import Events
from watcher.common.messaging.events.event import Event
from watcher.objects import Action
from watcher.objects.action_plan import Status
LOG = log.getLogger(__name__)
class ActionPlanExecutor(object):
def __init__(self, manager_applier, context):
self.manager_applier = manager_applier
self.context = context
self.deploy = DeployPhase(self)
self.mapper = DefaultActionMapper()
def get_primitive(self, action):
return self.mapper.build_primitive_from_action(action)
def notify(self, action, state):
db_action = Action.get_by_uuid(self.context, action.uuid)
db_action.state = state
db_action.save()
event = Event()
event.type = Events.LAUNCH_ACTION
event.data = {}
payload = {'action_uuid': action.uuid,
'action_state': state}
self.manager_applier.topic_status.publish_event(event.type.name,
payload)
def execute(self, actions):
for action in actions:
try:
self.notify(action, Status.ONGOING)
primitive = self.get_primitive(action)
result = self.deploy.execute_primitive(primitive)
if result is False:
self.notify(action, Status.FAILED)
self.deploy.rollback()
return False
else:
self.deploy.populate(primitive)
self.notify(action, Status.SUCCEEDED)
except Exception as e:
LOG.debug(
'The applier module failed to execute the action{0} with '
'the exception {1} '.format(
action,
unicode(e)))
LOG.error("Trigger a rollback")
self.notify(action, Status.FAILED)
self.deploy.rollback()
return False
return True

View File

@ -1,33 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.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 abc
import six
@six.add_metaclass(abc.ABCMeta)
class BaseActionMapper(object):
@abc.abstractmethod
def build_primitive_from_action(self, action):
"""Transform an action to a primitive
:type action: watcher.decision_engine.action.BaseAction
:return: the associated Primitive
"""
raise NotImplementedError()

View File

@ -1,47 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.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.
#
from watcher.applier.mapping.base import BaseActionMapper
from watcher.applier.primitives.change_nova_service_state import \
ChangeNovaServiceState
from watcher.applier.primitives.migration import Migrate
from watcher.applier.primitives.nop import Nop
from watcher.applier.primitives.power_state import ChangePowerState
from watcher.common.exception import ActionNotFound
from watcher.decision_engine.planner.default import Primitives
class DefaultActionMapper(BaseActionMapper):
def build_primitive_from_action(self, action):
if action.action_type == Primitives.COLD_MIGRATE.value:
return Migrate(action.applies_to, Primitives.COLD_MIGRATE,
action.src,
action.dst)
elif action.action_type == Primitives.LIVE_MIGRATE.value:
return Migrate(action.applies_to, Primitives.COLD_MIGRATE,
action.src,
action.dst)
elif action.action_type == Primitives.HYPERVISOR_STATE.value:
return ChangeNovaServiceState(action.applies_to, action.parameter)
elif action.action_type == Primitives.POWER_STATE.value:
return ChangePowerState()
elif action.action_type == Primitives.NOP.value:
return Nop()
else:
raise ActionNotFound()

View File

@ -17,9 +17,9 @@
# limitations under the License. # limitations under the License.
# #
from enum import Enum import enum
class Events(Enum): class Events(enum.Enum):
LAUNCH_ACTION_PLAN = "launch_action_plan" LAUNCH_ACTION_PLAN = "launch_action_plan"
LAUNCH_ACTION = "launch_action" LAUNCH_ACTION = "launch_action"

View File

@ -18,17 +18,38 @@
# #
import abc import abc
import six import six
from watcher.applier.promise import Promise
from watcher.applier import promise
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class BasePrimitive(object): class BasePrimitive(object):
@Promise def __init__(self):
self._input_parameters = None
self._applies_to = None
@property
def input_parameters(self):
return self._input_parameters
@input_parameters.setter
def input_parameters(self, p):
self._input_parameters = p
@property
def applies_to(self):
return self._applies_to
@applies_to.setter
def applies_to(self, a):
self._applies_to = a
@promise.Promise
@abc.abstractmethod @abc.abstractmethod
def execute(self): def execute(self):
raise NotImplementedError() raise NotImplementedError()
@Promise @promise.Promise
@abc.abstractmethod @abc.abstractmethod
def undo(self): def undo(self):
raise NotImplementedError() raise NotImplementedError()

View File

@ -18,30 +18,21 @@
# #
from oslo_config import cfg
from watcher._i18n import _ from watcher._i18n import _
from watcher.applier.primitives.base import BasePrimitive from watcher.applier.primitives import base
from watcher.applier.promise import Promise from watcher.applier import promise
from watcher.common.exception import IllegalArgumentException from watcher.common import exception
from watcher.common.keystone import KeystoneClient from watcher.common import keystone as kclient
from watcher.common.nova import NovaClient from watcher.common import nova as nclient
from watcher.decision_engine.model.hypervisor_state import HypervisorState from watcher.decision_engine.model import hypervisor_state as hstate
CONF = cfg.CONF
class ChangeNovaServiceState(BasePrimitive): class ChangeNovaServiceState(base.BasePrimitive):
def __init__(self, host, state): def __init__(self):
"""This class allows us to change the state of nova-compute service. """This class allows us to change the state of nova-compute service."""
super(ChangeNovaServiceState, self).__init__()
:param host: the uuid of the host self._host = self.applies_to
:param state: (enabled/disabled) self._state = self.input_parameters.get('state')
"""
super(BasePrimitive, self).__init__()
self._host = host
self._state = state
@property @property
def host(self): def host(self):
@ -51,31 +42,31 @@ class ChangeNovaServiceState(BasePrimitive):
def state(self): def state(self):
return self._state return self._state
@Promise @promise.Promise
def execute(self): def execute(self):
target_state = None target_state = None
if self.state == HypervisorState.OFFLINE.value: if self.state == hstate.HypervisorState.OFFLINE.value:
target_state = False target_state = False
elif self.status == HypervisorState.ONLINE.value: elif self.status == hstate.HypervisorState.ONLINE.value:
target_state = True target_state = True
return self.nova_manage_service(target_state) return self.nova_manage_service(target_state)
@Promise @promise.Promise
def undo(self): def undo(self):
target_state = None target_state = None
if self.state == HypervisorState.OFFLINE.value: if self.state == hstate.HypervisorState.OFFLINE.value:
target_state = True target_state = True
elif self.state == HypervisorState.ONLINE.value: elif self.state == hstate.HypervisorState.ONLINE.value:
target_state = False target_state = False
return self.nova_manage_service(target_state) return self.nova_manage_service(target_state)
def nova_manage_service(self, state): def nova_manage_service(self, state):
if state is None: if state is None:
raise IllegalArgumentException( raise exception.IllegalArgumentException(
_("The target state is not defined")) _("The target state is not defined"))
keystone = KeystoneClient() keystone = kclient.KeystoneClient()
wrapper = NovaClient(keystone.get_credentials(), wrapper = nclient.NovaClient(keystone.get_credentials(),
session=keystone.get_session()) session=keystone.get_session())
if state is True: if state is True:
return wrapper.enable_service_nova_compute(self.host) return wrapper.enable_service_nova_compute(self.host)

View File

@ -0,0 +1,36 @@
# -*- 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.
#
from __future__ import unicode_literals
from oslo_log import log
from watcher.applier.primitives.loading import default
LOG = log.getLogger(__name__)
class ActionFactory(object):
def __init__(self):
self.action_loader = default.DefaultActionLoader()
def make_action(self, object_action):
LOG.debug("Creating instance of %s", object_action.action_type)
loaded_action = self.action_loader.load(name=object_action.action_type)
loaded_action.input_parameters = object_action.input_parameters
loaded_action.applies_to = object_action.applies_to
return loaded_action

View File

@ -17,50 +17,38 @@
# limitations under the License. # limitations under the License.
# #
from oslo_config import cfg
from watcher.applier.primitives.base import BasePrimitive from watcher.applier.primitives import base
from watcher.applier.promise import Promise from watcher.applier import promise
from watcher.common.keystone import KeystoneClient from watcher.common import exception
from watcher.common.nova import NovaClient from watcher.common import keystone as kclient
from watcher.decision_engine.planner.default import Primitives from watcher.common import nova as nclient
CONF = cfg.CONF
class Migrate(BasePrimitive): class Migrate(base.BasePrimitive):
def __init__(self, vm_uuid=None, def __init__(self):
migration_type=None, super(Migrate, self).__init__()
source_hypervisor=None, self.instance_uuid = self.applies_to
destination_hypervisor=None): self.migration_type = self.input_parameters.get('migration_type')
super(BasePrimitive, self).__init__()
self.instance_uuid = vm_uuid
self.migration_type = migration_type
self.source_hypervisor = source_hypervisor
self.destination_hypervisor = destination_hypervisor
def migrate(self, destination): def migrate(self, destination):
keystone = KeystoneClient() keystone = kclient.KeystoneClient()
wrapper = NovaClient(keystone.get_credentials(), wrapper = nclient.NovaClient(keystone.get_credentials(),
session=keystone.get_session()) session=keystone.get_session())
instance = wrapper.find_instance(self.instance_uuid) instance = wrapper.find_instance(self.instance_uuid)
if instance: if instance:
# todo(jed) remove Primitves if self.migration_type is 'live':
if self.migration_type is Primitives.COLD_MIGRATE:
return wrapper.live_migrate_instance( return wrapper.live_migrate_instance(
instance_id=self.instance_uuid, instance_id=self.instance_uuid, dest_hostname=destination)
dest_hostname=destination, else:
block_migration=True) raise exception.InvalidParameterValue(err=self.migration_type)
elif self.migration_type is Primitives.LIVE_MIGRATE: else:
return wrapper.live_migrate_instance( raise exception.InstanceNotFound(name=self.instance_uuid)
instance_id=self.instance_uuid,
dest_hostname=destination,
block_migration=False)
@Promise @promise.Promise
def execute(self): def execute(self):
return self.migrate(self.destination_hypervisor) return self.migrate(self.input_parameters.get('dst_hypervisor_uuid'))
@Promise @promise.Promise
def undo(self): def undo(self):
return self.migrate(self.source_hypervisor) return self.migrate(self.input_parameters.get('src_hypervisor_uuid'))

View File

@ -20,21 +20,22 @@
from oslo_log import log from oslo_log import log
from watcher.applier.primitives.base import BasePrimitive from watcher.applier.primitives import base
from watcher.applier.promise import Promise from watcher.applier import promise
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class Nop(BasePrimitive): class Nop(base.BasePrimitive):
@Promise @promise.Promise
def execute(self): def execute(self):
LOG.debug("executing NOP command") LOG.debug("executing action NOP message:%s ",
self.input_parameters.get('message'))
return True return True
@Promise @promise.Promise
def undo(self): def undo(self):
LOG.debug("undo NOP command") LOG.debug("undo action NOP")
return True return True

View File

@ -1,32 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.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.
#
from watcher.applier.primitives.base import BasePrimitive
from watcher.applier.promise import Promise
class ChangePowerState(BasePrimitive):
@Promise
def execute(self):
raise NotImplementedError # pragma:no cover
@Promise
def undo(self):
raise NotImplementedError # pragma:no cover

View File

@ -291,12 +291,12 @@ class ClusterStateNotDefined(WatcherException):
# Model # Model
class VMNotFound(WatcherException): class InstanceNotFound(WatcherException):
message = _("The VM could not be found") message = _("The instance '%(name)s' is not found")
class HypervisorNotFound(WatcherException): class HypervisorNotFound(WatcherException):
message = _("The hypervisor could not be found") message = _("The hypervisor is not found")
class LoadingError(WatcherException): class LoadingError(WatcherException):

View File

@ -66,7 +66,7 @@ class ModelRoot(object):
def get_vm_from_id(self, uuid): def get_vm_from_id(self, uuid):
if str(uuid) not in self._vms.keys(): if str(uuid) not in self._vms.keys():
raise exception.VMNotFound(uuid) raise exception.InstanceNotFound(name=uuid)
return self._vms[str(uuid)] return self._vms[str(uuid)]
def get_all_vms(self): def get_all_vms(self):

View File

@ -17,9 +17,6 @@
# limitations under the License. # limitations under the License.
# #
import json
import enum
from oslo_log import log from oslo_log import log
from watcher._i18n import _LW from watcher._i18n import _LW
@ -30,14 +27,6 @@ from watcher import objects
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
class Primitives(enum.Enum):
LIVE_MIGRATE = 'MIGRATE'
COLD_MIGRATE = 'MIGRATE'
POWER_STATE = 'POWERSTATE'
HYPERVISOR_STATE = 'HYPERVISOR_STATE'
NOP = 'NOP'
class DefaultPlanner(base.BasePlanner): class DefaultPlanner(base.BasePlanner):
priorities = { priorities = {
'nop': 0, 'nop': 0,
@ -56,7 +45,7 @@ class DefaultPlanner(base.BasePlanner):
'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, 'applies_to': applies_to,
'input_parameters': json.dumps(input_parameters), 'input_parameters': input_parameters,
'state': objects.action.Status.PENDING, 'state': objects.action.Status.PENDING,
'alarm': None, 'alarm': None,
'next': None, 'next': None,

View File

@ -49,5 +49,5 @@ class PlannerManager(object):
def load(self): def load(self):
selected_planner = CONF.watcher_planner.planner selected_planner = CONF.watcher_planner.planner
LOG.debug("Loading {0}".format(selected_planner)) LOG.debug("Loading %s", selected_planner)
return self.loader.load(name=selected_planner) return self.loader.load(name=selected_planner)

View File

@ -149,8 +149,8 @@ class OutletTempControl(BaseStrategy):
LOG.info(_LE("VM not active, skipped: %s"), LOG.info(_LE("VM not active, skipped: %s"),
vm.uuid) vm.uuid)
continue continue
return (mig_src_hypervisor, vm) return mig_src_hypervisor, vm
except wexc.VMNotFound as e: except wexc.InstanceNotFound as e:
LOG.info("VM not found Error: %s" % e.message) LOG.info("VM not found Error: %s" % e.message)
pass pass

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-14 14:51+0100\n" "POT-Creation-Date: 2016-01-15 10:25+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"
@ -71,7 +71,11 @@ msgstr ""
msgid "Error parsing HTTP response: %s" msgid "Error parsing HTTP response: %s"
msgstr "" msgstr ""
#: watcher/applier/primitives/change_nova_service_state.py:75 #: watcher/applier/execution/default.py:52
msgid "Trigger a rollback"
msgstr ""
#: watcher/applier/primitives/change_nova_service_state.py:66
msgid "The target state is not defined" msgid "The target state is not defined"
msgstr "" msgstr ""
@ -261,11 +265,12 @@ msgid "the cluster state is not defined"
msgstr "" msgstr ""
#: watcher/common/exception.py:295 #: watcher/common/exception.py:295
msgid "The VM could not be found" #, python-format
msgstr "" msgid "The instance '%(name)s' is not found"
msgstr "L'instance '%(name)s' n'a pas été trouvée"
#: watcher/common/exception.py:299 #: watcher/common/exception.py:299
msgid "The hypervisor could not be found" msgid "The hypervisor is not found"
msgstr "" msgstr ""
#: watcher/common/exception.py:303 #: watcher/common/exception.py:303
@ -348,7 +353,7 @@ msgstr ""
msgid "'obj' argument type is not valid" msgid "'obj' argument type is not valid"
msgstr "" msgstr ""
#: watcher/decision_engine/planner/default.py:86 #: watcher/decision_engine/planner/default.py:75
msgid "The action plan is empty" msgid "The action plan is empty"
msgstr "" msgstr ""
@ -536,3 +541,9 @@ msgstr ""
#~ msgid "The Meta-Action could not be found" #~ msgid "The Meta-Action could not be found"
#~ msgstr "" #~ msgstr ""
#~ msgid "The VM could not be found"
#~ msgstr ""
#~ msgid "The hypervisor could not be found"
#~ msgstr ""

View File

@ -7,9 +7,9 @@
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: python-watcher 0.22.1.dev16\n" "Project-Id-Version: python-watcher 0.22.1.dev19\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2016-01-14 14:51+0100\n" "POT-Creation-Date: 2016-01-15 10:25+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"
@ -70,7 +70,11 @@ msgstr ""
msgid "Error parsing HTTP response: %s" msgid "Error parsing HTTP response: %s"
msgstr "" msgstr ""
#: watcher/applier/primitives/change_nova_service_state.py:75 #: watcher/applier/execution/default.py:52
msgid "Trigger a rollback"
msgstr ""
#: watcher/applier/primitives/change_nova_service_state.py:66
msgid "The target state is not defined" msgid "The target state is not defined"
msgstr "" msgstr ""
@ -259,11 +263,12 @@ msgid "the cluster state is not defined"
msgstr "" msgstr ""
#: watcher/common/exception.py:295 #: watcher/common/exception.py:295
msgid "The VM could not be found" #, python-format
msgid "The instance '%(name)s' is not found"
msgstr "" msgstr ""
#: watcher/common/exception.py:299 #: watcher/common/exception.py:299
msgid "The hypervisor could not be found" msgid "The hypervisor is not found"
msgstr "" msgstr ""
#: watcher/common/exception.py:303 #: watcher/common/exception.py:303
@ -346,7 +351,7 @@ msgstr ""
msgid "'obj' argument type is not valid" msgid "'obj' argument type is not valid"
msgstr "" msgstr ""
#: watcher/decision_engine/planner/default.py:86 #: watcher/decision_engine/planner/default.py:75
msgid "The action plan is empty" msgid "The action plan is empty"
msgstr "" msgstr ""

View File

@ -18,21 +18,17 @@
# #
import mock import mock
from watcher.applier.execution.executor import ActionPlanExecutor from watcher.applier.execution import default
from watcher import objects
from watcher.common import utils from watcher.common import utils
from watcher.decision_engine.planner.default import Primitives from watcher import objects
from watcher.objects.action import Action from watcher.tests.db import base
from watcher.objects.action import Status
from watcher.tests.db.base import DbTestCase
class TestCommandExecutor(DbTestCase): class TestDefaultActionPlanExecutor(base.DbTestCase):
def setUp(self): def setUp(self):
super(TestCommandExecutor, self).setUp() super(TestDefaultActionPlanExecutor, self).setUp()
self.applier = mock.MagicMock() self.executor = default.DefaultActionPlanExecutor(mock.MagicMock(),
self.executor = ActionPlanExecutor(self.applier, self.context) self.context)
def test_execute(self): def test_execute(self):
actions = mock.MagicMock() actions = mock.MagicMock()
@ -44,19 +40,17 @@ class TestCommandExecutor(DbTestCase):
action = { action = {
'uuid': utils.generate_uuid(), 'uuid': utils.generate_uuid(),
'action_plan_id': 0, 'action_plan_id': 0,
'action_type': Primitives.NOP.value, 'action_type': "nop",
'applies_to': '', 'applies_to': '',
'src': '', 'input_parameters': {'state': 'OFFLINE'},
'dst': '', 'state': objects.action.Status.PENDING,
'parameter': '',
'description': '',
'state': Status.PENDING,
'alarm': None, 'alarm': None,
'next': None, 'next': None,
} }
new_action = objects.Action(self.context, **action) new_action = objects.Action(self.context, **action)
new_action.create(self.context) new_action.create(self.context)
new_action.save() new_action.save()
actions.append(Action.get_by_uuid(self.context, action['uuid'])) actions.append(objects.Action.get_by_uuid(self.context,
action['uuid']))
result = self.executor.execute(actions) result = self.executor.execute(actions)
self.assertEqual(result, True) self.assertEqual(result, True)

View File

@ -1,59 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.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 mock
from watcher.applier.mapping.default import DefaultActionMapper
from watcher.decision_engine.planner.default import Primitives
from watcher.tests import base
class TestDefaultActionMapper(base.TestCase):
def setUp(self):
super(TestDefaultActionMapper, self).setUp()
self.mapper = DefaultActionMapper()
def test_build_command_cold(self):
action = mock.MagicMock()
action.action_type = Primitives.COLD_MIGRATE.value
cmd = self.mapper.build_primitive_from_action(action)
self.assertIsNotNone(cmd)
def test_build_command_live(self):
action = mock.MagicMock()
action.action_type = Primitives.LIVE_MIGRATE.value
cmd = self.mapper.build_primitive_from_action(action)
self.assertIsNotNone(cmd)
def test_build_command_h_s(self):
action = mock.MagicMock()
action.action_type = Primitives.HYPERVISOR_STATE.value
cmd = self.mapper.build_primitive_from_action(action)
self.assertIsNotNone(cmd)
def test_build_command_p_s(self):
action = mock.MagicMock()
action.action_type = Primitives.POWER_STATE.value
cmd = self.mapper.build_primitive_from_action(action)
self.assertIsNotNone(cmd)
def test_build_command_exception_attribute(self):
action = mock.MagicMock
self.assertRaises(AttributeError,
self.mapper.build_primitive_from_action,
action)

View File

@ -1,59 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.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 mock
from watcher.applier.mapping.default import DefaultActionMapper
from watcher.decision_engine.planner.default import Primitives
from watcher.tests import base
class TestDefaultActionMapper(base.TestCase):
def setUp(self):
super(TestDefaultActionMapper, self).setUp()
self.mapper = DefaultActionMapper()
def test_build_command_cold(self):
action = mock.MagicMock()
action.action_type = Primitives.COLD_MIGRATE.value
cmd = self.mapper.build_primitive_from_action(action)
self.assertIsNotNone(cmd)
def test_build_command_live(self):
action = mock.MagicMock()
action.action_type = Primitives.LIVE_MIGRATE.value
cmd = self.mapper.build_primitive_from_action(action)
self.assertIsNotNone(cmd)
def test_build_command_h_s(self):
action = mock.MagicMock()
action.action_type = Primitives.HYPERVISOR_STATE.value
cmd = self.mapper.build_primitive_from_action(action)
self.assertIsNotNone(cmd)
def test_build_command_p_s(self):
action = mock.MagicMock()
action.action_type = Primitives.POWER_STATE.value
cmd = self.mapper.build_primitive_from_action(action)
self.assertIsNotNone(cmd)
def test_build_command_exception_attribute(self):
action = mock.MagicMock
self.assertRaises(AttributeError,
self.mapper.build_primitive_from_action,
action)

View File

@ -129,7 +129,7 @@ class TestModel(base.BaseTestCase):
def test_vm_from_id_raise(self): def test_vm_from_id_raise(self):
fake_cluster = FakerModelCollector() fake_cluster = FakerModelCollector()
model = fake_cluster.generate_scenario_1() model = fake_cluster.generate_scenario_1()
self.assertRaises(exception.VMNotFound, self.assertRaises(exception.InstanceNotFound,
model.get_vm_from_id, "valeur_qcq") model.get_vm_from_id, "valeur_qcq")
def test_assert_vm_raise(self): def test_assert_vm_raise(self):

View File

@ -20,7 +20,9 @@ from watcher.tests import base
class TestDefaultPlannerLoader(base.TestCase): class TestDefaultPlannerLoader(base.TestCase):
loader = default.DefaultPlannerLoader() def setUp(self):
super(TestDefaultPlannerLoader, self).setUp()
self.loader = default.DefaultPlannerLoader()
def test_endpoints(self): def test_endpoints(self):
for endpoint in self.loader.list_available(): for endpoint in self.loader.list_available():

View File

@ -16,13 +16,13 @@
from oslo_config import cfg from oslo_config import cfg
from watcher.decision_engine.planner.default import DefaultPlanner from watcher.decision_engine.planner import default
from watcher.decision_engine.planner.manager import PlannerManager from watcher.decision_engine.planner import manager as planner
from watcher.tests import base from watcher.tests import base
class TestPlannerManager(base.TestCase): class TestPlannerManager(base.TestCase):
def test_load(self): def test_load(self):
cfg.CONF.set_override('planner', "default", group='watcher_planner') cfg.CONF.set_override('planner', "default", group='watcher_planner')
manager = PlannerManager() manager = planner.PlannerManager()
self.assertIsInstance(manager.load(), DefaultPlanner) self.assertIsInstance(manager.load(), default.DefaultPlanner)