Jean-Emile DARTOIS 8bac4fd42a 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
2016-01-15 16:08:18 +01:00

304 lines
7.7 KiB
Python

# -*- encoding: utf-8 -*-
# Copyright (c) 2015 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.
"""Watcher base exception handling.
Includes decorator for re-raising Watcher-type exceptions.
SHOULD include dedicated exception logging.
"""
from oslo_config import cfg
from oslo_log import log as logging
import six
from watcher._i18n import _, _LE
LOG = logging.getLogger(__name__)
exc_log_opts = [
cfg.BoolOpt('fatal_exception_format_errors',
default=False,
help='Make exception message format errors fatal.'),
]
CONF = cfg.CONF
CONF.register_opts(exc_log_opts)
def _cleanse_dict(original):
"""Strip all admin_password, new_pass, rescue_pass keys from a dict"""
return dict((k, v) for k, v in original.items() if "_pass" not in k)
class WatcherException(Exception):
"""Base Watcher Exception
To correctly use this class, inherit from it and define
a 'message' property. That message will get printf'd
with the keyword arguments provided to the constructor.
"""
message = _("An unknown exception occurred")
code = 500
headers = {}
safe = False
def __init__(self, message=None, **kwargs):
self.kwargs = kwargs
if 'code' not in self.kwargs:
try:
self.kwargs['code'] = self.code
except AttributeError:
pass
if not message:
try:
message = self.message % kwargs
except Exception as e:
# kwargs doesn't match a variable in the message
# log the issue and the kwargs
LOG.exception(_LE('Exception in string format operation'))
for name, value in kwargs.items():
LOG.error("%s: %s", name, value)
if CONF.fatal_exception_format_errors:
raise e
else:
# at least get the core message out if something happened
message = self.message
super(WatcherException, self).__init__(message)
def __str__(self):
"""Encode to utf-8 then wsme api can consume it as well"""
if not six.PY3:
return unicode(self.args[0]).encode('utf-8')
else:
return self.args[0]
def __unicode__(self):
return self.message
def format_message(self):
if self.__class__.__name__.endswith('_Remote'):
return self.args[0]
else:
return six.text_type(self)
class NotAuthorized(WatcherException):
message = _("Not authorized")
code = 403
class OperationNotPermitted(NotAuthorized):
message = _("Operation not permitted")
class Invalid(WatcherException):
message = _("Unacceptable parameters")
code = 400
class ObjectNotFound(WatcherException):
message = _("The %(name)s %(id)s could not be found")
class Conflict(WatcherException):
message = _('Conflict')
code = 409
class ResourceNotFound(ObjectNotFound):
message = _("The %(name)s resource %(id)s could not be found")
code = 404
class InvalidIdentity(Invalid):
message = _("Expected an uuid or int but received %(identity)s")
class InvalidGoal(Invalid):
message = _("Goal %(goal)s is not defined in Watcher configuration file")
# Cannot be templated as the error syntax varies.
# msg needs to be constructed when raised.
class InvalidParameterValue(Invalid):
message = _("%(err)s")
class InvalidUUID(Invalid):
message = _("Expected a uuid but received %(uuid)s")
class InvalidName(Invalid):
message = _("Expected a logical name but received %(name)s")
class InvalidUuidOrName(Invalid):
message = _("Expected a logical name or uuid but received %(name)s")
class AuditTemplateNotFound(ResourceNotFound):
message = _("AuditTemplate %(audit_template)s could not be found")
class AuditTemplateAlreadyExists(Conflict):
message = _("An audit_template with UUID %(uuid)s or name %(name)s "
"already exists")
class AuditTemplateReferenced(Invalid):
message = _("AuditTemplate %(audit_template)s is referenced by one or "
"multiple audit")
class AuditNotFound(ResourceNotFound):
message = _("Audit %(audit)s could not be found")
class AuditAlreadyExists(Conflict):
message = _("An audit with UUID %(uuid)s already exists")
class AuditReferenced(Invalid):
message = _("Audit %(audit)s is referenced by one or multiple action "
"plans")
class ActionPlanNotFound(ResourceNotFound):
message = _("ActionPlan %(action plan)s could not be found")
class ActionPlanAlreadyExists(Conflict):
message = _("An action plan with UUID %(uuid)s already exists")
class ActionPlanReferenced(Invalid):
message = _("Action Plan %(action_plan)s is referenced by one or "
"multiple actions")
class ActionNotFound(ResourceNotFound):
message = _("Action %(action)s could not be found")
class ActionAlreadyExists(Conflict):
message = _("An action with UUID %(uuid)s already exists")
class ActionReferenced(Invalid):
message = _("Action plan %(action_plan)s is referenced by one or "
"multiple goals")
class ActionFilterCombinationProhibited(Invalid):
message = _("Filtering actions on both audit and action-plan is "
"prohibited")
class HTTPNotFound(ResourceNotFound):
pass
class PatchError(Invalid):
message = _("Couldn't apply patch '%(patch)s'. Reason: %(reason)s")
# decision engine
class BaseException(Exception):
def __init__(self, desc=""):
if (not isinstance(desc, six.string_types)):
raise ValueError(_("Description must be an instance of str"))
desc = desc.strip()
self._desc = desc
def get_description(self):
return self._desc
def get_message(self):
return _("An exception occurred without a description")
def __str__(self):
return self.get_message()
class IllegalArgumentException(BaseException):
def __init__(self, desc):
desc = desc or _("Description cannot be empty")
super(IllegalArgumentException, self).__init__(desc)
def get_message(self):
return self._desc
class NoSuchMetric(BaseException):
def __init__(self, desc):
desc = desc or _("No such metric")
super(NoSuchMetric, self).__init__(desc)
def get_message(self):
return self._desc
class NoDataFound(BaseException):
def __init__(self, desc):
desc = desc or _("No rows were returned")
super(NoDataFound, self).__init__(desc)
def get_message(self):
return self._desc
class KeystoneFailure(WatcherException):
message = _("'Keystone API endpoint is missing''")
class ClusterEmpty(WatcherException):
message = _("The list of hypervisor(s) in the cluster is empty")
class MetricCollectorNotDefined(WatcherException):
message = _("The metrics resource collector is not defined")
class ClusterStateNotDefined(WatcherException):
message = _("the cluster state is not defined")
# Model
class InstanceNotFound(WatcherException):
message = _("The instance '%(name)s' is not found")
class HypervisorNotFound(WatcherException):
message = _("The hypervisor is not found")
class LoadingError(WatcherException):
message = _("Error loading plugin '%(name)s'")