Merge "Add a dynamic loading of Actions handlers in the Watcher Applier"
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -33,17 +33,38 @@ the appropriate commands to Nova for this type of
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user