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:
parent
ed438d2eb2
commit
8bac4fd42a
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
62
watcher/applier/execution/base.py
Normal file
62
watcher/applier/execution/base.py
Normal 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()
|
57
watcher/applier/execution/default.py
Normal file
57
watcher/applier/execution/default.py
Normal 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
|
@ -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
|
|
@ -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()
|
|
@ -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()
|
|
@ -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"
|
||||||
|
@ -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()
|
||||||
|
@ -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,32 +42,32 @@ 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)
|
||||||
else:
|
else:
|
||||||
|
36
watcher/applier/primitives/factory.py
Normal file
36
watcher/applier/primitives/factory.py
Normal 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
|
@ -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'))
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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 ""
|
||||||
|
|
||||||
|
@ -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 ""
|
||||||
|
|
||||||
|
@ -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)
|
@ -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)
|
|
@ -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)
|
|
@ -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):
|
||||||
|
@ -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():
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user