initial version
Change-Id: I699e0ab082657880998d8618fe29eb7f56c6c661
This commit is contained in:
0
watcher/common/__init__.py
Normal file
0
watcher/common/__init__.py
Normal file
30
watcher/common/config.py
Normal file
30
watcher/common/config.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
# Copyright 2012 Red Hat, Inc.
|
||||
#
|
||||
# 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_config import cfg
|
||||
|
||||
from watcher.common import rpc
|
||||
from watcher import version
|
||||
|
||||
|
||||
def parse_args(argv, default_config_files=None):
|
||||
rpc.set_defaults(control_exchange='watcher')
|
||||
cfg.CONF(argv[1:],
|
||||
project='watcher',
|
||||
version=version.version_info.release_string(),
|
||||
default_config_files=default_config_files)
|
||||
rpc.init(cfg.CONF)
|
||||
72
watcher/common/context.py
Normal file
72
watcher/common/context.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# 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_context import context
|
||||
|
||||
|
||||
class RequestContext(context.RequestContext):
|
||||
"""Extends security contexts from the OpenStack common library."""
|
||||
|
||||
def __init__(self, auth_token=None, auth_url=None, domain_id=None,
|
||||
domain_name=None, user=None, user_id=None, project=None,
|
||||
project_id=None, is_admin=False, is_public_api=False,
|
||||
read_only=False, show_deleted=False, request_id=None,
|
||||
trust_id=None, auth_token_info=None):
|
||||
"""Stores several additional request parameters:
|
||||
|
||||
:param domain_id: The ID of the domain.
|
||||
:param domain_name: The name of the domain.
|
||||
:param is_public_api: Specifies whether the request should be processed
|
||||
without authentication.
|
||||
|
||||
"""
|
||||
self.is_public_api = is_public_api
|
||||
self.user_id = user_id
|
||||
self.project = project
|
||||
self.project_id = project_id
|
||||
self.domain_id = domain_id
|
||||
self.domain_name = domain_name
|
||||
self.auth_url = auth_url
|
||||
self.auth_token_info = auth_token_info
|
||||
self.trust_id = trust_id
|
||||
|
||||
super(RequestContext, self).__init__(auth_token=auth_token,
|
||||
user=user, tenant=project,
|
||||
is_admin=is_admin,
|
||||
read_only=read_only,
|
||||
show_deleted=show_deleted,
|
||||
request_id=request_id)
|
||||
|
||||
def to_dict(self):
|
||||
return {'auth_token': self.auth_token,
|
||||
'auth_url': self.auth_url,
|
||||
'domain_id': self.domain_id,
|
||||
'domain_name': self.domain_name,
|
||||
'user': self.user,
|
||||
'user_id': self.user_id,
|
||||
'project': self.project,
|
||||
'project_id': self.project_id,
|
||||
'is_admin': self.is_admin,
|
||||
'is_public_api': self.is_public_api,
|
||||
'read_only': self.read_only,
|
||||
'show_deleted': self.show_deleted,
|
||||
'request_id': self.request_id,
|
||||
'trust_id': self.trust_id,
|
||||
'auth_token_info': self.auth_token_info}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, values):
|
||||
return cls(**values)
|
||||
|
||||
|
||||
def make_context(*args, **kwargs):
|
||||
return RequestContext(*args, **kwargs)
|
||||
253
watcher/common/exception.py
Normal file
253
watcher/common/exception.py
Normal file
@@ -0,0 +1,253 @@
|
||||
# -*- 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
|
||||
import six
|
||||
|
||||
from watcher.common.i18n import _
|
||||
from watcher.common.i18n import _LE
|
||||
from watcher.openstack.common import log as logging
|
||||
|
||||
|
||||
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.iteritems() 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.iteritems():
|
||||
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 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 ClusteStateNotDefined(WatcherException):
|
||||
message = _("the cluster state is not defined")
|
||||
|
||||
|
||||
# Model
|
||||
|
||||
class VMNotFound(WatcherException):
|
||||
message = _("The VM could not be found.")
|
||||
|
||||
|
||||
class HypervisorNotFound(WatcherException):
|
||||
message = _("The hypervisor could not be found.")
|
||||
|
||||
|
||||
class MetaActionNotFound(WatcherException):
|
||||
message = _("The Meta-Action could not be found.")
|
||||
26
watcher/common/i18n.py
Normal file
26
watcher/common/i18n.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- 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.
|
||||
#
|
||||
import oslo_i18n
|
||||
|
||||
_translators = oslo_i18n.TranslatorFactory(domain='watcher')
|
||||
oslo_i18n.enable_lazy()
|
||||
|
||||
_ = _translators.primary
|
||||
_LI = _translators.log_info
|
||||
_LW = _translators.log_warning
|
||||
_LE = _translators.log_error
|
||||
_LC = _translators.log_critical
|
||||
0
watcher/common/messaging/__init__.py
Normal file
0
watcher/common/messaging/__init__.py
Normal file
0
watcher/common/messaging/events/__init__.py
Normal file
0
watcher/common/messaging/events/__init__.py
Normal file
48
watcher/common/messaging/events/event.py
Normal file
48
watcher/common/messaging/events/event.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# -*- 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.
|
||||
|
||||
|
||||
class Event(object):
|
||||
"""Generic event to use with EventDispatcher"""
|
||||
|
||||
def __init__(self, event_type=None, data=None, request_id=None):
|
||||
"""Default constructor
|
||||
|
||||
:param event_type: the type of the event
|
||||
:param data: a dictionary which contains data
|
||||
:param request_id: a string which represent the uuid of the request
|
||||
"""
|
||||
self._type = event_type
|
||||
self._data = data
|
||||
self._request_id = request_id
|
||||
|
||||
def get_type(self):
|
||||
return self._type
|
||||
|
||||
def set_type(self, type):
|
||||
self._type = type
|
||||
|
||||
def get_data(self):
|
||||
return self._data
|
||||
|
||||
def set_data(self, data):
|
||||
self._data = data
|
||||
|
||||
def set_request_id(self, id):
|
||||
self._request_id = id
|
||||
|
||||
def get_request_id(self):
|
||||
return self._request_id
|
||||
78
watcher/common/messaging/events/event_dispatcher.py
Normal file
78
watcher/common/messaging/events/event_dispatcher.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# -*- 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.
|
||||
|
||||
|
||||
from watcher.decision_engine.framework.messaging.events import Events
|
||||
from watcher.openstack.common import log
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class EventDispatcher(object):
|
||||
"""Generic event dispatcher which listen and dispatch events"""
|
||||
|
||||
def __init__(self):
|
||||
self._events = dict()
|
||||
|
||||
def __del__(self):
|
||||
self._events = None
|
||||
|
||||
def has_listener(self, event_type, listener):
|
||||
"""Return true if listener is register to event_type """
|
||||
# Check for event type and for the listener
|
||||
if event_type in self._events.keys():
|
||||
return listener in self._events[event_type]
|
||||
else:
|
||||
return False
|
||||
|
||||
def dispatch_event(self, event):
|
||||
LOG.debug("dispatch evt : %s" % str(event.get_type()))
|
||||
"""
|
||||
Dispatch an instance of Event class
|
||||
"""
|
||||
if Events.ALL in self._events.keys():
|
||||
listeners = self._events[Events.ALL]
|
||||
for listener in listeners:
|
||||
listener(event)
|
||||
|
||||
# Dispatch the event to all the associated listeners
|
||||
if event.get_type() in self._events.keys():
|
||||
listeners = self._events[event.get_type()]
|
||||
for listener in listeners:
|
||||
listener(event)
|
||||
|
||||
def add_event_listener(self, event_type, listener):
|
||||
"""Add an event listener for an event type"""
|
||||
# Add listener to the event type
|
||||
if not self.has_listener(event_type, listener):
|
||||
listeners = self._events.get(event_type, [])
|
||||
listeners.append(listener)
|
||||
self._events[event_type] = listeners
|
||||
|
||||
def remove_event_listener(self, event_type, listener):
|
||||
"""Remove event listener. """
|
||||
# Remove the listener from the event type
|
||||
if self.has_listener(event_type, listener):
|
||||
listeners = self._events[event_type]
|
||||
|
||||
if len(listeners) == 1:
|
||||
# Only this listener remains so remove the key
|
||||
del self._events[event_type]
|
||||
|
||||
else:
|
||||
# Update listeners chain
|
||||
listeners.remove(listener)
|
||||
self._events[event_type] = listeners
|
||||
109
watcher/common/messaging/messaging_core.py
Normal file
109
watcher/common/messaging/messaging_core.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# -*- 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.
|
||||
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from watcher.common.messaging.events.event_dispatcher import \
|
||||
EventDispatcher
|
||||
from watcher.common.messaging.messaging_handler import \
|
||||
MessagingHandler
|
||||
from watcher.common.rpc import RequestContextSerializer
|
||||
|
||||
from watcher.objects.base import WatcherObjectSerializer
|
||||
from watcher.openstack.common import log
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
WATCHER_MESSAGING_OPTS = [
|
||||
cfg.StrOpt('notifier_driver',
|
||||
default='messaging', help='The name of the driver used by'
|
||||
' oslo messaging'),
|
||||
cfg.StrOpt('executor',
|
||||
default='eventlet', help='The name of a message executor, for'
|
||||
'example: eventlet, blocking'),
|
||||
cfg.StrOpt('protocol',
|
||||
default='rabbit', help='The protocol used by the message'
|
||||
' broker, for example rabbit'),
|
||||
cfg.StrOpt('user',
|
||||
default='guest', help='The username used by the message '
|
||||
'broker'),
|
||||
cfg.StrOpt('password',
|
||||
default='guest', help='The password of user used by the '
|
||||
'message broker'),
|
||||
cfg.StrOpt('host',
|
||||
default='localhost', help='The host where the message broker'
|
||||
'is installed'),
|
||||
cfg.StrOpt('port',
|
||||
default='5672', help='The port used bythe message broker'),
|
||||
cfg.StrOpt('virtual_host',
|
||||
default='', help='The virtual host used by the message '
|
||||
'broker')
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
opt_group = cfg.OptGroup(name='watcher_messaging',
|
||||
title='Options for the messaging core')
|
||||
CONF.register_group(opt_group)
|
||||
CONF.register_opts(WATCHER_MESSAGING_OPTS, opt_group)
|
||||
|
||||
|
||||
class MessagingCore(EventDispatcher):
|
||||
API_VERSION = '1.0'
|
||||
|
||||
def __init__(self, publisher_id, topic_control, topic_status):
|
||||
EventDispatcher.__init__(self)
|
||||
self.serializer = RequestContextSerializer(WatcherObjectSerializer())
|
||||
self.publisher_id = publisher_id
|
||||
self.topic_control = self.build_topic(topic_control)
|
||||
self.topic_status = self.build_topic(topic_status)
|
||||
|
||||
def build_topic(self, topic_name):
|
||||
return MessagingHandler(self.publisher_id, topic_name, self,
|
||||
self.API_VERSION, self.serializer)
|
||||
|
||||
def connect(self):
|
||||
LOG.debug("connecting to rabbitMQ broker")
|
||||
self.topic_control.start()
|
||||
self.topic_status.start()
|
||||
|
||||
def disconnect(self):
|
||||
LOG.debug("Disconnect to rabbitMQ broker")
|
||||
self.topic_control.stop()
|
||||
self.topic_status.stop()
|
||||
|
||||
def publish_control(self, event, payload):
|
||||
return self.topic_control.publish_event(event, payload)
|
||||
|
||||
def publish_status(self, event, payload, request_id=None):
|
||||
return self.topic_status.publish_event(event, payload, request_id)
|
||||
|
||||
def get_version(self):
|
||||
return self.API_VERSION
|
||||
|
||||
def check_api_version(self, context):
|
||||
api_manager_version = self.client.call(
|
||||
context.to_dict(), 'check_api_version',
|
||||
api_version=self.API_VERSION)
|
||||
return api_manager_version
|
||||
|
||||
def response(self, evt, ctx, message):
|
||||
payload = {
|
||||
'request_id': ctx['request_id'],
|
||||
'msg': message
|
||||
}
|
||||
self.publish_status(evt, payload)
|
||||
107
watcher/common/messaging/messaging_handler.py
Normal file
107
watcher/common/messaging/messaging_handler.py
Normal file
@@ -0,0 +1,107 @@
|
||||
# -*- 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.
|
||||
|
||||
import eventlet
|
||||
from oslo_config import cfg
|
||||
import oslo_messaging as om
|
||||
from threading import Thread
|
||||
from watcher.common.messaging.utils.transport_url_builder import \
|
||||
TransportUrlBuilder
|
||||
from watcher.common.rpc import JsonPayloadSerializer
|
||||
from watcher.common.rpc import RequestContextSerializer
|
||||
from watcher.openstack.common import log
|
||||
|
||||
eventlet.monkey_patch()
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class MessagingHandler(Thread):
|
||||
def __init__(self, publisher_id, topic_watcher, endpoint, version,
|
||||
serializer=None):
|
||||
Thread.__init__(self)
|
||||
self.__server = None
|
||||
self.__notifier = None
|
||||
self.__endpoints = []
|
||||
self.__topics = []
|
||||
self._publisher_id = publisher_id
|
||||
self._topic_watcher = topic_watcher
|
||||
self.__endpoints.append(endpoint)
|
||||
self.__version = version
|
||||
self.__serializer = serializer
|
||||
|
||||
def add_endpoint(self, endpoint):
|
||||
self.__endpoints.append(endpoint)
|
||||
|
||||
def remove_endpoint(self, endpoint):
|
||||
if endpoint in self.__endpoints:
|
||||
self.__endpoints.remove(endpoint)
|
||||
|
||||
def build_notifier(self):
|
||||
serializer = RequestContextSerializer(JsonPayloadSerializer())
|
||||
return om.Notifier(
|
||||
self.transport,
|
||||
driver=CONF.watcher_messaging.notifier_driver,
|
||||
publisher_id=self._publisher_id,
|
||||
topic=self._topic_watcher,
|
||||
serializer=serializer)
|
||||
|
||||
def build_server(self, targets):
|
||||
|
||||
return om.get_rpc_server(self.transport, targets,
|
||||
self.__endpoints,
|
||||
executor=CONF.
|
||||
watcher_messaging.executor,
|
||||
serializer=self.__serializer)
|
||||
|
||||
def __build_transport_url(self):
|
||||
return TransportUrlBuilder().url
|
||||
|
||||
def __config(self):
|
||||
try:
|
||||
self.transport = om.get_transport(
|
||||
cfg.CONF,
|
||||
url=self.__build_transport_url())
|
||||
self.__notifier = self.build_notifier()
|
||||
if 0 < len(self.__endpoints):
|
||||
targets = om.Target(
|
||||
topic=self._topic_watcher,
|
||||
server=CONF.watcher_messaging.host,
|
||||
version=self.__version)
|
||||
self.__server = self.build_server(targets)
|
||||
else:
|
||||
LOG.warn("you have no defined endpoint, \
|
||||
so you can only publish events")
|
||||
except Exception as e:
|
||||
LOG.error("configure : %s" % str(e.message))
|
||||
|
||||
def run(self):
|
||||
LOG.debug("configure MessagingHandler for %s" % self._topic_watcher)
|
||||
self.__config()
|
||||
if len(self.__endpoints) > 0:
|
||||
LOG.debug("Starting up server")
|
||||
self.__server.start()
|
||||
|
||||
def stop(self):
|
||||
LOG.debug('Stop up server')
|
||||
self.__server.wait()
|
||||
self.__server.stop()
|
||||
|
||||
def publish_event(self, event_type, payload, request_id=None):
|
||||
self.__notifier.info({'version_api': self.__version,
|
||||
'request_id': request_id},
|
||||
{'event_id': event_type}, payload)
|
||||
50
watcher/common/messaging/notification_handler.py
Normal file
50
watcher/common/messaging/notification_handler.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# -*- 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.
|
||||
|
||||
import eventlet
|
||||
from oslo import messaging
|
||||
|
||||
from watcher.common.messaging.utils.observable import \
|
||||
Observable
|
||||
from watcher.openstack.common import log
|
||||
|
||||
|
||||
eventlet.monkey_patch()
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class NotificationHandler(Observable):
|
||||
def __init__(self, publisher_id):
|
||||
Observable.__init__(self)
|
||||
self.publisher_id = publisher_id
|
||||
|
||||
def info(self, ctx, publisher_id, event_type, payload, metadata):
|
||||
if publisher_id == self.publisher_id:
|
||||
self.set_changed()
|
||||
self.notify(ctx, publisher_id, event_type, metadata, payload)
|
||||
return messaging.NotificationResult.HANDLED
|
||||
|
||||
def warn(self, ctx, publisher_id, event_type, payload, metadata):
|
||||
if publisher_id == self.publisher_id:
|
||||
self.set_changed()
|
||||
self.notify(ctx, publisher_id, event_type, metadata, payload)
|
||||
return messaging.NotificationResult.HANDLED
|
||||
|
||||
def error(self, ctx, publisher_id, event_type, payload, metadata):
|
||||
if publisher_id == self.publisher_id:
|
||||
self.set_changed()
|
||||
self.notify(ctx, publisher_id, event_type, metadata, payload)
|
||||
return messaging.NotificationResult.HANDLED
|
||||
0
watcher/common/messaging/utils/__init__.py
Normal file
0
watcher/common/messaging/utils/__init__.py
Normal file
62
watcher/common/messaging/utils/observable.py
Normal file
62
watcher/common/messaging/utils/observable.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# -*- 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.
|
||||
|
||||
|
||||
from watcher.common.messaging.utils.synchronization import \
|
||||
Synchronization
|
||||
from watcher.openstack.common import log
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class Observable(Synchronization):
|
||||
def __init__(self):
|
||||
self.__observers = []
|
||||
self.changed = 0
|
||||
Synchronization.__init__(self)
|
||||
|
||||
def set_changed(self):
|
||||
self.changed = 1
|
||||
|
||||
def clear_changed(self):
|
||||
self.changed = 0
|
||||
|
||||
def has_changed(self):
|
||||
return self.changed
|
||||
|
||||
def register_observer(self, observer):
|
||||
if observer not in self.__observers:
|
||||
self.__observers.append(observer)
|
||||
|
||||
def unregister_observer(self, observer):
|
||||
try:
|
||||
self.__observers.remove(observer)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def notify(self, ctx=None, publisherid=None, event_type=None,
|
||||
metadata=None, payload=None, modifier=None):
|
||||
self.mutex.acquire()
|
||||
try:
|
||||
if not self.changed:
|
||||
return
|
||||
for observer in self.__observers:
|
||||
if modifier != observer:
|
||||
observer.update(self, ctx, metadata, publisherid,
|
||||
event_type, payload)
|
||||
self.clear_changed()
|
||||
finally:
|
||||
self.mutex.release()
|
||||
22
watcher/common/messaging/utils/synchronization.py
Normal file
22
watcher/common/messaging/utils/synchronization.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- 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.
|
||||
|
||||
import threading
|
||||
|
||||
|
||||
class Synchronization(object):
|
||||
def __init__(self):
|
||||
self.mutex = threading.RLock()
|
||||
35
watcher/common/messaging/utils/transport_url_builder.py
Normal file
35
watcher/common/messaging/utils/transport_url_builder.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- 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.
|
||||
|
||||
from oslo_config import cfg
|
||||
from watcher.openstack.common import log
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class TransportUrlBuilder(object):
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return "%s://%s:%s@%s:%s/%s" % (
|
||||
CONF.watcher_messaging.protocol,
|
||||
CONF.watcher_messaging.user,
|
||||
CONF.watcher_messaging.password,
|
||||
CONF.watcher_messaging.host,
|
||||
CONF.watcher_messaging.port,
|
||||
CONF.watcher_messaging.virtual_host
|
||||
)
|
||||
66
watcher/common/paths.py
Normal file
66
watcher/common/paths.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# Copyright 2010 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
# Copyright 2012 Red Hat, Inc.
|
||||
#
|
||||
# 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 os
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
PATH_OPTS = [
|
||||
cfg.StrOpt('pybasedir',
|
||||
default=os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'../')),
|
||||
help='Directory where the watcher python module is installed.'),
|
||||
cfg.StrOpt('bindir',
|
||||
default='$pybasedir/bin',
|
||||
help='Directory where watcher binaries are installed.'),
|
||||
cfg.StrOpt('state_path',
|
||||
default='$pybasedir',
|
||||
help="Top-level directory for maintaining watcher's state."),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(PATH_OPTS)
|
||||
|
||||
|
||||
def basedir_def(*args):
|
||||
"""Return an uninterpolated path relative to $pybasedir."""
|
||||
return os.path.join('$pybasedir', *args)
|
||||
|
||||
|
||||
def bindir_def(*args):
|
||||
"""Return an uninterpolated path relative to $bindir."""
|
||||
return os.path.join('$bindir', *args)
|
||||
|
||||
|
||||
def state_path_def(*args):
|
||||
"""Return an uninterpolated path relative to $state_path."""
|
||||
return os.path.join('$state_path', *args)
|
||||
|
||||
|
||||
def basedir_rel(*args):
|
||||
"""Return a path relative to $pybasedir."""
|
||||
return os.path.join(CONF.pybasedir, *args)
|
||||
|
||||
|
||||
def bindir_rel(*args):
|
||||
"""Return a path relative to $bindir."""
|
||||
return os.path.join(CONF.bindir, *args)
|
||||
|
||||
|
||||
def state_path_rel(*args):
|
||||
"""Return a path relative to $state_path."""
|
||||
return os.path.join(CONF.state_path, *args)
|
||||
69
watcher/common/policy.py
Normal file
69
watcher/common/policy.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# Copyright (c) 2011 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Policy Engine For Watcher."""
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_config import cfg
|
||||
|
||||
from watcher.openstack.common import policy
|
||||
|
||||
_ENFORCER = None
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
@lockutils.synchronized('policy_enforcer', 'watcher-')
|
||||
def init_enforcer(policy_file=None, rules=None,
|
||||
default_rule=None, use_conf=True):
|
||||
"""Synchronously initializes the policy enforcer
|
||||
|
||||
:param policy_file: Custom policy file to use, if none is specified,
|
||||
`CONF.policy_file` will be used.
|
||||
:param rules: Default dictionary / Rules to use. It will be
|
||||
considered just in the first instantiation.
|
||||
:param default_rule: Default rule to use, CONF.default_rule will
|
||||
be used if none is specified.
|
||||
:param use_conf: Whether to load rules from config file.
|
||||
|
||||
"""
|
||||
global _ENFORCER
|
||||
|
||||
if _ENFORCER:
|
||||
return
|
||||
|
||||
_ENFORCER = policy.Enforcer(policy_file=policy_file,
|
||||
rules=rules,
|
||||
default_rule=default_rule,
|
||||
use_conf=use_conf)
|
||||
|
||||
|
||||
def get_enforcer():
|
||||
"""Provides access to the single instance of Policy enforcer."""
|
||||
|
||||
if not _ENFORCER:
|
||||
init_enforcer()
|
||||
|
||||
return _ENFORCER
|
||||
|
||||
|
||||
def enforce(rule, target, creds, do_raise=False, exc=None, *args, **kwargs):
|
||||
"""A shortcut for policy.Enforcer.enforce()
|
||||
|
||||
Checks authorization of a rule against the target and credentials.
|
||||
|
||||
"""
|
||||
enforcer = get_enforcer()
|
||||
return enforcer.enforce(rule, target, creds, do_raise=do_raise,
|
||||
exc=exc, *args, **kwargs)
|
||||
148
watcher/common/rpc.py
Normal file
148
watcher/common/rpc.py
Normal file
@@ -0,0 +1,148 @@
|
||||
# Copyright 2014 Red Hat, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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_config import cfg
|
||||
import oslo_messaging as messaging
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from watcher.common import context as watcher_context
|
||||
from watcher.common import exception
|
||||
|
||||
__all__ = [
|
||||
'init',
|
||||
'cleanup',
|
||||
'set_defaults',
|
||||
'add_extra_exmods',
|
||||
'clear_extra_exmods',
|
||||
'get_allowed_exmods',
|
||||
'RequestContextSerializer',
|
||||
'get_client',
|
||||
'get_server',
|
||||
'get_notifier',
|
||||
'TRANSPORT_ALIASES',
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
TRANSPORT = None
|
||||
NOTIFIER = None
|
||||
|
||||
ALLOWED_EXMODS = [
|
||||
exception.__name__,
|
||||
]
|
||||
EXTRA_EXMODS = []
|
||||
|
||||
# NOTE(lucasagomes): The watcher.openstack.common.rpc entries are for
|
||||
# backwards compat with IceHouse rpc_backend configuration values.
|
||||
TRANSPORT_ALIASES = {
|
||||
'watcher.openstack.common.rpc.impl_kombu': 'rabbit',
|
||||
'watcher.openstack.common.rpc.impl_qpid': 'qpid',
|
||||
'watcher.openstack.common.rpc.impl_zmq': 'zmq',
|
||||
'watcher.rpc.impl_kombu': 'rabbit',
|
||||
'watcher.rpc.impl_qpid': 'qpid',
|
||||
'watcher.rpc.impl_zmq': 'zmq',
|
||||
}
|
||||
|
||||
|
||||
def init(conf):
|
||||
global TRANSPORT, NOTIFIER
|
||||
exmods = get_allowed_exmods()
|
||||
TRANSPORT = messaging.get_transport(conf,
|
||||
allowed_remote_exmods=exmods,
|
||||
aliases=TRANSPORT_ALIASES)
|
||||
serializer = RequestContextSerializer(JsonPayloadSerializer())
|
||||
NOTIFIER = messaging.Notifier(TRANSPORT, serializer=serializer)
|
||||
|
||||
|
||||
def cleanup():
|
||||
global TRANSPORT, NOTIFIER
|
||||
assert TRANSPORT is not None
|
||||
assert NOTIFIER is not None
|
||||
TRANSPORT.cleanup()
|
||||
TRANSPORT = NOTIFIER = None
|
||||
|
||||
|
||||
def set_defaults(control_exchange):
|
||||
messaging.set_transport_defaults(control_exchange)
|
||||
|
||||
|
||||
def add_extra_exmods(*args):
|
||||
EXTRA_EXMODS.extend(args)
|
||||
|
||||
|
||||
def clear_extra_exmods():
|
||||
del EXTRA_EXMODS[:]
|
||||
|
||||
|
||||
def get_allowed_exmods():
|
||||
return ALLOWED_EXMODS + EXTRA_EXMODS
|
||||
|
||||
|
||||
class JsonPayloadSerializer(messaging.NoOpSerializer):
|
||||
@staticmethod
|
||||
def serialize_entity(context, entity):
|
||||
return jsonutils.to_primitive(entity, convert_instances=True)
|
||||
|
||||
|
||||
class RequestContextSerializer(messaging.Serializer):
|
||||
|
||||
def __init__(self, base):
|
||||
self._base = base
|
||||
|
||||
def serialize_entity(self, context, entity):
|
||||
if not self._base:
|
||||
return entity
|
||||
return self._base.serialize_entity(context, entity)
|
||||
|
||||
def deserialize_entity(self, context, entity):
|
||||
if not self._base:
|
||||
return entity
|
||||
return self._base.deserialize_entity(context, entity)
|
||||
|
||||
def serialize_context(self, context):
|
||||
return context
|
||||
|
||||
def deserialize_context(self, context):
|
||||
return watcher_context.RequestContext.from_dict(context)
|
||||
|
||||
|
||||
def get_transport_url(url_str=None):
|
||||
return messaging.TransportURL.parse(CONF, url_str, TRANSPORT_ALIASES)
|
||||
|
||||
|
||||
def get_client(target, version_cap=None, serializer=None):
|
||||
assert TRANSPORT is not None
|
||||
serializer = RequestContextSerializer(serializer)
|
||||
return messaging.RPCClient(TRANSPORT,
|
||||
target,
|
||||
version_cap=version_cap,
|
||||
serializer=serializer)
|
||||
|
||||
|
||||
def get_server(target, endpoints, serializer=None):
|
||||
assert TRANSPORT is not None
|
||||
serializer = RequestContextSerializer(serializer)
|
||||
return messaging.get_rpc_server(TRANSPORT,
|
||||
target,
|
||||
endpoints,
|
||||
executor='eventlet',
|
||||
serializer=serializer)
|
||||
|
||||
|
||||
def get_notifier(service=None, host=None, publisher_id=None):
|
||||
assert NOTIFIER is not None
|
||||
if not publisher_id:
|
||||
publisher_id = "%s.%s" % (service, host or CONF.host)
|
||||
return NOTIFIER.prepare(publisher_id=publisher_id)
|
||||
107
watcher/common/rpc_service.py
Normal file
107
watcher/common/rpc_service.py
Normal file
@@ -0,0 +1,107 @@
|
||||
# Copyright 2014 - Rackspace Hosting
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Common RPC service and API tools for Watcher."""
|
||||
|
||||
import eventlet
|
||||
from oslo_config import cfg
|
||||
import oslo_messaging as messaging
|
||||
|
||||
from watcher.common import context as watcher_context
|
||||
from watcher.common import rpc
|
||||
from watcher.objects import base as objects_base
|
||||
|
||||
|
||||
# NOTE(paulczar):
|
||||
# Ubuntu 14.04 forces librabbitmq when kombu is used
|
||||
# Unfortunately it forces a version that has a crash
|
||||
# bug. Calling eventlet.monkey_patch() tells kombu
|
||||
# to use libamqp instead.
|
||||
eventlet.monkey_patch()
|
||||
|
||||
# NOTE(asalkeld):
|
||||
# The watcher.openstack.common.rpc entries are for compatability
|
||||
# with devstack rpc_backend configuration values.
|
||||
TRANSPORT_ALIASES = {
|
||||
'watcher.openstack.common.rpc.impl_kombu': 'rabbit',
|
||||
'watcher.openstack.common.rpc.impl_qpid': 'qpid',
|
||||
'watcher.openstack.common.rpc.impl_zmq': 'zmq',
|
||||
}
|
||||
|
||||
|
||||
class RequestContextSerializer(messaging.Serializer):
|
||||
|
||||
def __init__(self, base):
|
||||
self._base = base
|
||||
|
||||
def serialize_entity(self, context, entity):
|
||||
if not self._base:
|
||||
return entity
|
||||
return self._base.serialize_entity(context, entity)
|
||||
|
||||
def deserialize_entity(self, context, entity):
|
||||
if not self._base:
|
||||
return entity
|
||||
return self._base.deserialize_entity(context, entity)
|
||||
|
||||
def serialize_context(self, context):
|
||||
return context.to_dict()
|
||||
|
||||
def deserialize_context(self, context):
|
||||
return watcher_context.RequestContext.from_dict(context)
|
||||
|
||||
|
||||
class Service(object):
|
||||
_server = None
|
||||
|
||||
def __init__(self, topic, server, handlers):
|
||||
serializer = RequestContextSerializer(
|
||||
objects_base.WatcherObjectSerializer())
|
||||
transport = messaging.get_transport(cfg.CONF,
|
||||
aliases=TRANSPORT_ALIASES)
|
||||
# TODO(asalkeld) add support for version='x.y'
|
||||
target = messaging.Target(topic=topic, server=server)
|
||||
self._server = messaging.get_rpc_server(transport, target, handlers,
|
||||
serializer=serializer)
|
||||
|
||||
def serve(self):
|
||||
self._server.start()
|
||||
self._server.wait()
|
||||
|
||||
|
||||
class API(object):
|
||||
def __init__(self, transport=None, context=None, topic=None):
|
||||
serializer = RequestContextSerializer(
|
||||
objects_base.WatcherObjectSerializer())
|
||||
if transport is None:
|
||||
exmods = rpc.get_allowed_exmods()
|
||||
transport = messaging.get_transport(cfg.CONF,
|
||||
allowed_remote_exmods=exmods,
|
||||
aliases=TRANSPORT_ALIASES)
|
||||
self._context = context
|
||||
if topic is None:
|
||||
topic = ''
|
||||
target = messaging.Target(topic=topic)
|
||||
self._client = messaging.RPCClient(transport, target,
|
||||
serializer=serializer)
|
||||
|
||||
def _call(self, method, *args, **kwargs):
|
||||
# import pdb; pdb.set_trace()
|
||||
return self._client.call(self._context, method, *args, **kwargs)
|
||||
|
||||
def _cast(self, method, *args, **kwargs):
|
||||
self._client.cast(self._context, method, *args, **kwargs)
|
||||
|
||||
def echo(self, message):
|
||||
self._cast('echo', message=message)
|
||||
136
watcher/common/service.py
Normal file
136
watcher/common/service.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2012 eNovance <licensing@enovance.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 signal
|
||||
import socket
|
||||
|
||||
from oslo import messaging
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import importutils
|
||||
|
||||
from watcher.common import config
|
||||
from watcher.common.i18n import _LE
|
||||
from watcher.common.i18n import _LI
|
||||
from watcher.common import rpc
|
||||
from watcher.objects import base as objects_base
|
||||
from watcher.openstack.common import context
|
||||
from watcher.openstack.common import log
|
||||
from watcher.openstack.common import service
|
||||
|
||||
|
||||
service_opts = [
|
||||
cfg.IntOpt('periodic_interval',
|
||||
default=60,
|
||||
help='Seconds between running periodic tasks.'),
|
||||
cfg.StrOpt('host',
|
||||
default=socket.getfqdn(),
|
||||
help='Name of this node. This can be an opaque identifier. '
|
||||
'It is not necessarily a hostname, FQDN, or IP address. '
|
||||
'However, the node name must be valid within '
|
||||
'an AMQP key, and if using ZeroMQ, a valid '
|
||||
'hostname, FQDN, or IP address.'),
|
||||
]
|
||||
|
||||
cfg.CONF.register_opts(service_opts)
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class RPCService(service.Service):
|
||||
|
||||
def __init__(self, host, manager_module, manager_class):
|
||||
super(RPCService, self).__init__()
|
||||
self.host = host
|
||||
manager_module = importutils.try_import(manager_module)
|
||||
manager_class = getattr(manager_module, manager_class)
|
||||
self.manager = manager_class(host, manager_module.MANAGER_TOPIC)
|
||||
self.topic = self.manager.topic
|
||||
self.rpcserver = None
|
||||
self.deregister = True
|
||||
|
||||
def start(self):
|
||||
super(RPCService, self).start()
|
||||
admin_context = context.RequestContext('admin', 'admin', is_admin=True)
|
||||
|
||||
target = messaging.Target(topic=self.topic, server=self.host)
|
||||
endpoints = [self.manager]
|
||||
serializer = objects_base.IronicObjectSerializer()
|
||||
self.rpcserver = rpc.get_server(target, endpoints, serializer)
|
||||
self.rpcserver.start()
|
||||
|
||||
self.handle_signal()
|
||||
self.manager.init_host()
|
||||
self.tg.add_dynamic_timer(
|
||||
self.manager.periodic_tasks,
|
||||
periodic_interval_max=cfg.CONF.periodic_interval,
|
||||
context=admin_context)
|
||||
|
||||
LOG.info(_LI('Created RPC server for service %(service)s on host '
|
||||
'%(host)s.'),
|
||||
{'service': self.topic, 'host': self.host})
|
||||
|
||||
def stop(self):
|
||||
try:
|
||||
self.rpcserver.stop()
|
||||
self.rpcserver.wait()
|
||||
except Exception as e:
|
||||
LOG.exception(_LE('Service error occurred when stopping the '
|
||||
'RPC server. Error: %s'), e)
|
||||
try:
|
||||
self.manager.del_host(deregister=self.deregister)
|
||||
except Exception as e:
|
||||
LOG.exception(_LE('Service error occurred when cleaning up '
|
||||
'the RPC manager. Error: %s'), e)
|
||||
|
||||
super(RPCService, self).stop(graceful=True)
|
||||
LOG.info(_LI('Stopped RPC server for service %(service)s on host '
|
||||
'%(host)s.'),
|
||||
{'service': self.topic, 'host': self.host})
|
||||
|
||||
def _handle_signal(self, signo, frame):
|
||||
LOG.info(_LI('Got signal SIGUSR1. Not deregistering on next shutdown '
|
||||
'of service %(service)s on host %(host)s.'),
|
||||
{'service': self.topic, 'host': self.host})
|
||||
self.deregister = False
|
||||
|
||||
def handle_signal(self):
|
||||
"""Add a signal handler for SIGUSR1.
|
||||
|
||||
The handler ensures that the manager is not deregistered when it is
|
||||
shutdown.
|
||||
"""
|
||||
signal.signal(signal.SIGUSR1, self._handle_signal)
|
||||
|
||||
|
||||
def prepare_service(argv=[]):
|
||||
config.parse_args(argv)
|
||||
cfg.set_defaults(log.log_opts,
|
||||
default_log_levels=['amqp=WARN',
|
||||
'amqplib=WARN',
|
||||
'qpid.messaging=INFO',
|
||||
'oslo.messaging=INFO',
|
||||
'sqlalchemy=WARN',
|
||||
'keystoneclient=INFO',
|
||||
'stevedore=INFO',
|
||||
'eventlet.wsgi.server=WARN',
|
||||
'iso8601=WARN',
|
||||
'paramiko=WARN',
|
||||
'requests=WARN',
|
||||
'neutronclient=WARN',
|
||||
'glanceclient=WARN',
|
||||
'watcher.openstack.common=WARN',
|
||||
])
|
||||
log.setup('watcher')
|
||||
99
watcher/common/utils.py
Normal file
99
watcher/common/utils.py
Normal file
@@ -0,0 +1,99 @@
|
||||
# -*- 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.
|
||||
|
||||
"""Utilities and helper functions."""
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
import re
|
||||
import six
|
||||
import uuid
|
||||
|
||||
|
||||
from watcher.common.i18n import _LW
|
||||
from watcher.openstack.common import log as logging
|
||||
|
||||
UTILS_OPTS = [
|
||||
cfg.StrOpt('rootwrap_config',
|
||||
default="/etc/watcher/rootwrap.conf",
|
||||
help='Path to the rootwrap configuration file to use for '
|
||||
'running commands as root.'),
|
||||
cfg.StrOpt('tempdir',
|
||||
help='Explicitly specify the temporary working directory.'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(UTILS_OPTS)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def safe_rstrip(value, chars=None):
|
||||
"""Removes trailing characters from a string if that does not make it empty
|
||||
|
||||
:param value: A string value that will be stripped.
|
||||
:param chars: Characters to remove.
|
||||
:return: Stripped value.
|
||||
|
||||
"""
|
||||
if not isinstance(value, six.string_types):
|
||||
LOG.warn(_LW("Failed to remove trailing character. Returning original "
|
||||
"object. Supplied object is not a string: %s,"), value)
|
||||
return value
|
||||
|
||||
return value.rstrip(chars) or value
|
||||
|
||||
|
||||
def generate_uuid():
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
def is_int_like(val):
|
||||
"""Check if a value looks like an int."""
|
||||
try:
|
||||
return str(int(val)) == str(val)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def is_uuid_like(val):
|
||||
"""Returns validation of a value as a UUID.
|
||||
|
||||
For our purposes, a UUID is a canonical form string:
|
||||
aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa
|
||||
|
||||
"""
|
||||
try:
|
||||
return str(uuid.UUID(val)) == val
|
||||
except (TypeError, ValueError, AttributeError):
|
||||
return False
|
||||
|
||||
|
||||
def is_hostname_safe(hostname):
|
||||
"""Determine if the supplied hostname is RFC compliant.
|
||||
|
||||
Check that the supplied hostname conforms to:
|
||||
* http://en.wikipedia.org/wiki/Hostname
|
||||
* http://tools.ietf.org/html/rfc952
|
||||
* http://tools.ietf.org/html/rfc1123
|
||||
|
||||
:param hostname: The hostname to be validated.
|
||||
:returns: True if valid. False if not.
|
||||
|
||||
"""
|
||||
m = '^[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?$'
|
||||
return (isinstance(hostname, six.string_types) and
|
||||
(re.match(m, hostname) is not None))
|
||||
Reference in New Issue
Block a user