Merge "Add a dynamic loading of Actions handlers in the Watcher Applier"

This commit is contained in:
Jenkins
2016-01-15 15:44:56 +00:00
committed by Gerrit Code Review
29 changed files with 305 additions and 450 deletions

View File

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

View File

@@ -0,0 +1,62 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2016 b<>com
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import abc
import six
from watcher.applier.messaging import events
from watcher.applier.primitives import factory
from watcher.common.messaging.events import event
from watcher import objects
@six.add_metaclass(abc.ABCMeta)
class BaseActionPlanExecutor(object):
def __init__(self, manager_applier, context):
self._manager_applier = manager_applier
self._context = context
self._action_factory = factory.ActionFactory()
@property
def context(self):
return self._context
@property
def manager_applier(self):
return self._manager_applier
@property
def action_factory(self):
return self._action_factory
def notify(self, action, state):
db_action = objects.Action.get_by_uuid(self.context, action.uuid)
db_action.state = state
db_action.save()
ev = event.Event()
ev.type = events.Events.LAUNCH_ACTION
ev.data = {}
payload = {'action_uuid': action.uuid,
'action_state': state}
self.manager_applier.topic_status.publish_event(ev.type.name,
payload)
@abc.abstractmethod
def execute(self, actions):
raise NotImplementedError()

View File

@@ -0,0 +1,57 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from oslo_log import log
from watcher._i18n import _LE
from watcher.applier.execution import base
from watcher.applier.execution import deploy_phase
from watcher.objects import action_plan
LOG = log.getLogger(__name__)
class DefaultActionPlanExecutor(base.BaseActionPlanExecutor):
def __init__(self, manager_applier, context):
super(DefaultActionPlanExecutor, self).__init__(manager_applier,
context)
self.deploy = deploy_phase.DeployPhase(self)
def execute(self, actions):
for action in actions:
try:
self.notify(action, action_plan.Status.ONGOING)
loaded_action = self.action_factory.make_action(action)
result = self.deploy.execute_primitive(loaded_action)
if result is False:
self.notify(action, action_plan.Status.FAILED)
self.deploy.rollback()
return False
else:
self.deploy.populate(loaded_action)
self.notify(action, action_plan.Status.SUCCEEDED)
except Exception as e:
LOG.expection(e)
LOG.debug('The ActionPlanExecutor failed to execute the action'
' %s ', action)
LOG.error(_LE("Trigger a rollback"))
self.notify(action, action_plan.Status.FAILED)
self.deploy.rollback()
return False
return True

View File

@@ -1,76 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from oslo_log import log
from watcher.applier.execution.deploy_phase import DeployPhase
from watcher.applier.mapping.default import DefaultActionMapper
from watcher.applier.messaging.events import Events
from watcher.common.messaging.events.event import Event
from watcher.objects import Action
from watcher.objects.action_plan import Status
LOG = log.getLogger(__name__)
class ActionPlanExecutor(object):
def __init__(self, manager_applier, context):
self.manager_applier = manager_applier
self.context = context
self.deploy = DeployPhase(self)
self.mapper = DefaultActionMapper()
def get_primitive(self, action):
return self.mapper.build_primitive_from_action(action)
def notify(self, action, state):
db_action = Action.get_by_uuid(self.context, action.uuid)
db_action.state = state
db_action.save()
event = Event()
event.type = Events.LAUNCH_ACTION
event.data = {}
payload = {'action_uuid': action.uuid,
'action_state': state}
self.manager_applier.topic_status.publish_event(event.type.name,
payload)
def execute(self, actions):
for action in actions:
try:
self.notify(action, Status.ONGOING)
primitive = self.get_primitive(action)
result = self.deploy.execute_primitive(primitive)
if result is False:
self.notify(action, Status.FAILED)
self.deploy.rollback()
return False
else:
self.deploy.populate(primitive)
self.notify(action, Status.SUCCEEDED)
except Exception as e:
LOG.debug(
'The applier module failed to execute the action{0} with '
'the exception {1} '.format(
action,
unicode(e)))
LOG.error("Trigger a rollback")
self.notify(action, Status.FAILED)
self.deploy.rollback()
return False
return True

View File

@@ -1,33 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class BaseActionMapper(object):
@abc.abstractmethod
def build_primitive_from_action(self, action):
"""Transform an action to a primitive
:type action: watcher.decision_engine.action.BaseAction
:return: the associated Primitive
"""
raise NotImplementedError()

View File

@@ -1,47 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from watcher.applier.mapping.base import BaseActionMapper
from watcher.applier.primitives.change_nova_service_state import \
ChangeNovaServiceState
from watcher.applier.primitives.migration import Migrate
from watcher.applier.primitives.nop import Nop
from watcher.applier.primitives.power_state import ChangePowerState
from watcher.common.exception import ActionNotFound
from watcher.decision_engine.planner.default import Primitives
class DefaultActionMapper(BaseActionMapper):
def build_primitive_from_action(self, action):
if action.action_type == Primitives.COLD_MIGRATE.value:
return Migrate(action.applies_to, Primitives.COLD_MIGRATE,
action.src,
action.dst)
elif action.action_type == Primitives.LIVE_MIGRATE.value:
return Migrate(action.applies_to, Primitives.COLD_MIGRATE,
action.src,
action.dst)
elif action.action_type == Primitives.HYPERVISOR_STATE.value:
return ChangeNovaServiceState(action.applies_to, action.parameter)
elif action.action_type == Primitives.POWER_STATE.value:
return ChangePowerState()
elif action.action_type == Primitives.NOP.value:
return Nop()
else:
raise ActionNotFound()

View File

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

View File

@@ -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()

View File

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

View File

@@ -0,0 +1,36 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2016 b<>com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from __future__ import unicode_literals
from oslo_log import log
from watcher.applier.primitives.loading import default
LOG = log.getLogger(__name__)
class ActionFactory(object):
def __init__(self):
self.action_loader = default.DefaultActionLoader()
def make_action(self, object_action):
LOG.debug("Creating instance of %s", object_action.action_type)
loaded_action = self.action_loader.load(name=object_action.action_type)
loaded_action.input_parameters = object_action.input_parameters
loaded_action.applies_to = object_action.applies_to
return loaded_action

View File

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

View File

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

View File

@@ -1,32 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# Authors: Jean-Emile DARTOIS <jean-emile.dartois@b-com.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from watcher.applier.primitives.base import BasePrimitive
from watcher.applier.promise import Promise
class ChangePowerState(BasePrimitive):
@Promise
def execute(self):
raise NotImplementedError # pragma:no cover
@Promise
def undo(self):
raise NotImplementedError # pragma:no cover