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
|
||||
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
|
||||
|
||||
|
@ -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)
|
||||
|
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.
|
||||
#
|
||||
|
||||
from enum import Enum
|
||||
import enum
|
||||
|
||||
|
||||
class Events(Enum):
|
||||
class Events(enum.Enum):
|
||||
LAUNCH_ACTION_PLAN = "launch_action_plan"
|
||||
LAUNCH_ACTION = "launch_action"
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
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.
|
||||
#
|
||||
|
||||
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'))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
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):
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 ""
|
||||
|
||||
|
@ -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 ""
|
||||
|
||||
|
@ -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)
|
@ -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):
|
||||
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):
|
||||
|
@ -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():
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user