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
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 =
default = watcher.decision_engine.planner.default:DefaultPlanner

View File

@ -17,24 +17,23 @@
# limitations under the License.
#
from watcher.applier.base import BaseApplier
from watcher.applier.execution.executor import ActionPlanExecutor
from watcher.objects import Action
from watcher.objects import ActionPlan
from watcher.applier import base
from watcher.applier.execution import default
from watcher import objects
class DefaultApplier(BaseApplier):
class DefaultApplier(base.BaseApplier):
def __init__(self, manager_applier, context):
super(DefaultApplier, self).__init__()
self.manager_applier = manager_applier
self.context = context
self.executor = ActionPlanExecutor(manager_applier, context)
self.executor = default.DefaultActionPlanExecutor(manager_applier,
context)
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
actions = Action.dbapi.get_action_list(self.context,
filters={
'action_plan_id':
action_plan.id})
filters = {'action_plan_id': action_plan.id}
actions = objects.Action.dbapi.get_action_list(self.context, filters)
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.
#
from enum import Enum
import enum
class Events(Enum):
class Events(enum.Enum):
LAUNCH_ACTION_PLAN = "launch_action_plan"
LAUNCH_ACTION = "launch_action"

View File

@ -18,17 +18,38 @@
#
import abc
import six
from watcher.applier.promise import Promise
from watcher.applier import promise
@six.add_metaclass(abc.ABCMeta)
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
def execute(self):
raise NotImplementedError()
@Promise
@promise.Promise
@abc.abstractmethod
def undo(self):
raise NotImplementedError()

View File

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

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.
#
from oslo_config import cfg
from watcher.applier.primitives.base import BasePrimitive
from watcher.applier.promise import Promise
from watcher.common.keystone import KeystoneClient
from watcher.common.nova import NovaClient
from watcher.decision_engine.planner.default import Primitives
CONF = cfg.CONF
from watcher.applier.primitives import base
from watcher.applier import promise
from watcher.common import exception
from watcher.common import keystone as kclient
from watcher.common import nova as nclient
class Migrate(BasePrimitive):
def __init__(self, vm_uuid=None,
migration_type=None,
source_hypervisor=None,
destination_hypervisor=None):
super(BasePrimitive, self).__init__()
self.instance_uuid = vm_uuid
self.migration_type = migration_type
self.source_hypervisor = source_hypervisor
self.destination_hypervisor = destination_hypervisor
class Migrate(base.BasePrimitive):
def __init__(self):
super(Migrate, self).__init__()
self.instance_uuid = self.applies_to
self.migration_type = self.input_parameters.get('migration_type')
def migrate(self, destination):
keystone = KeystoneClient()
wrapper = NovaClient(keystone.get_credentials(),
session=keystone.get_session())
keystone = kclient.KeystoneClient()
wrapper = nclient.NovaClient(keystone.get_credentials(),
session=keystone.get_session())
instance = wrapper.find_instance(self.instance_uuid)
if instance:
# todo(jed) remove Primitves
if self.migration_type is Primitives.COLD_MIGRATE:
if self.migration_type is 'live':
return wrapper.live_migrate_instance(
instance_id=self.instance_uuid,
dest_hostname=destination,
block_migration=True)
elif self.migration_type is Primitives.LIVE_MIGRATE:
return wrapper.live_migrate_instance(
instance_id=self.instance_uuid,
dest_hostname=destination,
block_migration=False)
instance_id=self.instance_uuid, dest_hostname=destination)
else:
raise exception.InvalidParameterValue(err=self.migration_type)
else:
raise exception.InstanceNotFound(name=self.instance_uuid)
@Promise
@promise.Promise
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):
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 watcher.applier.primitives.base import BasePrimitive
from watcher.applier.promise import Promise
from watcher.applier.primitives import base
from watcher.applier import promise
LOG = log.getLogger(__name__)
class Nop(BasePrimitive):
class Nop(base.BasePrimitive):
@Promise
@promise.Promise
def execute(self):
LOG.debug("executing NOP command")
LOG.debug("executing action NOP message:%s ",
self.input_parameters.get('message'))
return True
@Promise
@promise.Promise
def undo(self):
LOG.debug("undo NOP command")
LOG.debug("undo action NOP")
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
class VMNotFound(WatcherException):
message = _("The VM could not be found")
class InstanceNotFound(WatcherException):
message = _("The instance '%(name)s' is not found")
class HypervisorNotFound(WatcherException):
message = _("The hypervisor could not be found")
message = _("The hypervisor is not found")
class LoadingError(WatcherException):

View File

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

View File

@ -17,9 +17,6 @@
# limitations under the License.
#
import json
import enum
from oslo_log import log
from watcher._i18n import _LW
@ -30,14 +27,6 @@ from watcher import objects
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):
priorities = {
'nop': 0,
@ -56,7 +45,7 @@ class DefaultPlanner(base.BasePlanner):
'action_plan_id': int(action_plan_id),
'action_type': action_type,
'applies_to': applies_to,
'input_parameters': json.dumps(input_parameters),
'input_parameters': input_parameters,
'state': objects.action.Status.PENDING,
'alarm': None,
'next': None,

View File

@ -49,5 +49,5 @@ class PlannerManager(object):
def load(self):
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)

View File

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

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: python-watcher 0.21.1.dev32\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: fr\n"
@ -71,7 +71,11 @@ msgstr ""
msgid "Error parsing HTTP response: %s"
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"
msgstr ""
@ -261,11 +265,12 @@ msgid "the cluster state is not defined"
msgstr ""
#: watcher/common/exception.py:295
msgid "The VM could not be found"
msgstr ""
#, python-format
msgid "The instance '%(name)s' is not found"
msgstr "L'instance '%(name)s' n'a pas été trouvée"
#: watcher/common/exception.py:299
msgid "The hypervisor could not be found"
msgid "The hypervisor is not found"
msgstr ""
#: watcher/common/exception.py:303
@ -348,7 +353,7 @@ msgstr ""
msgid "'obj' argument type is not valid"
msgstr ""
#: watcher/decision_engine/planner/default.py:86
#: watcher/decision_engine/planner/default.py:75
msgid "The action plan is empty"
msgstr ""
@ -536,3 +541,9 @@ msgstr ""
#~ msgid "The Meta-Action could not be found"
#~ msgstr ""
#~ msgid "The VM could not be found"
#~ msgstr ""
#~ msgid "The hypervisor could not be found"
#~ msgstr ""

View File

@ -7,9 +7,9 @@
#, fuzzy
msgid ""
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"
"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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -70,7 +70,11 @@ msgstr ""
msgid "Error parsing HTTP response: %s"
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"
msgstr ""
@ -259,11 +263,12 @@ msgid "the cluster state is not defined"
msgstr ""
#: watcher/common/exception.py:295
msgid "The VM could not be found"
#, python-format
msgid "The instance '%(name)s' is not found"
msgstr ""
#: watcher/common/exception.py:299
msgid "The hypervisor could not be found"
msgid "The hypervisor is not found"
msgstr ""
#: watcher/common/exception.py:303
@ -346,7 +351,7 @@ msgstr ""
msgid "'obj' argument type is not valid"
msgstr ""
#: watcher/decision_engine/planner/default.py:86
#: watcher/decision_engine/planner/default.py:75
msgid "The action plan is empty"
msgstr ""

View File

@ -18,21 +18,17 @@
#
import mock
from watcher.applier.execution.executor import ActionPlanExecutor
from watcher import objects
from watcher.applier.execution import default
from watcher.common import utils
from watcher.decision_engine.planner.default import Primitives
from watcher.objects.action import Action
from watcher.objects.action import Status
from watcher.tests.db.base import DbTestCase
from watcher import objects
from watcher.tests.db import base
class TestCommandExecutor(DbTestCase):
class TestDefaultActionPlanExecutor(base.DbTestCase):
def setUp(self):
super(TestCommandExecutor, self).setUp()
self.applier = mock.MagicMock()
self.executor = ActionPlanExecutor(self.applier, self.context)
super(TestDefaultActionPlanExecutor, self).setUp()
self.executor = default.DefaultActionPlanExecutor(mock.MagicMock(),
self.context)
def test_execute(self):
actions = mock.MagicMock()
@ -44,19 +40,17 @@ class TestCommandExecutor(DbTestCase):
action = {
'uuid': utils.generate_uuid(),
'action_plan_id': 0,
'action_type': Primitives.NOP.value,
'action_type': "nop",
'applies_to': '',
'src': '',
'dst': '',
'parameter': '',
'description': '',
'state': Status.PENDING,
'input_parameters': {'state': 'OFFLINE'},
'state': objects.action.Status.PENDING,
'alarm': None,
'next': None,
}
new_action = objects.Action(self.context, **action)
new_action.create(self.context)
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)
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):
fake_cluster = FakerModelCollector()
model = fake_cluster.generate_scenario_1()
self.assertRaises(exception.VMNotFound,
self.assertRaises(exception.InstanceNotFound,
model.get_vm_from_id, "valeur_qcq")
def test_assert_vm_raise(self):

View File

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

View File

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