2abb40f9e9
The client call() and cast() methods take a request context argument which is expected to be a dictionary. The RPC endpoint methods on the server are invoked with a dictionary context argument. However, Nova passes a nova.context.RequestContext on the client side and the oslo-incubator RPC code deserializes that as a amqp.RpcContext which looks vaguely compatible with Nova's RequestContext. Support the serialization and deserialization of RequestContext objects with an additional (de)serialize_context() hook on our Serializer class. Note: this is a backwards incompatible API change because Serializer implementations which do not implement the new abstract methods would no longer be possible to instantiate. Not a problem with this commit, but shows the type of compat concerns we'll need to think about once the API is locked down for good. Change-Id: I20782bad77fa0b0e396d082df852ca355548f9b7
162 lines
5.7 KiB
Python
162 lines
5.7 KiB
Python
|
|
# Copyright 2013 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.
|
|
|
|
"""
|
|
An RPC server exposes a number of endpoints, each of which contain a set of
|
|
methods which may be invoked remotely by clients over a given transport.
|
|
|
|
To create an RPC server, you supply a transport, target and a list of
|
|
endpoints.
|
|
|
|
A transport can be obtained simply by calling the get_transport() method::
|
|
|
|
transport = messaging.get_transport(conf)
|
|
|
|
which will load the appropriate transport driver according to the user's
|
|
messaging configuration configuration. See get_transport() for more details.
|
|
|
|
The target supplied when creating an RPC server expresses the topic, server
|
|
name and - optionally - the exchange to listen on. See Target for more details
|
|
on these attributes.
|
|
|
|
Each endpoint object may have a target attribute which may have namespace and
|
|
version fields set. By default, we use the 'null namespace' and version 1.0.
|
|
Incoming method calls will be dispatched to the first endpoint with the
|
|
requested method, a matching namespace and a compatible version number.
|
|
|
|
RPC servers have start(), stop() and wait() messages to begin handling
|
|
requests, stop handling requests and wait for all in-process requests to
|
|
complete.
|
|
|
|
An RPC server class is provided for each supported I/O handling framework.
|
|
Currently BlockingRPCServer and eventlet.RPCServer are available.
|
|
|
|
A simple example of an RPC server with multiple endpoints might be::
|
|
|
|
from oslo.config import cfg
|
|
from oslo import messaging
|
|
|
|
class ServerControlEndpoint(object):
|
|
|
|
target = messaging.Target(namespace='control',
|
|
version='2.0')
|
|
|
|
def __init__(self, server):
|
|
self.server = server
|
|
|
|
def stop(self, ctx):
|
|
self.server.stop()
|
|
|
|
class TestEndpoint(object):
|
|
|
|
def test(self, ctx, arg):
|
|
return arg
|
|
|
|
transport = messaging.get_transport(cfg.CONF)
|
|
target = messaging.Target(topic='test', server='server1')
|
|
endpoints = [
|
|
ServerControlEndpoint(self),
|
|
TestEndpoint(),
|
|
]
|
|
server = messaging.get_rpc_server(transport, target, endpoints)
|
|
server.start()
|
|
server.wait()
|
|
|
|
Clients can invoke methods on the server by sending the request to a topic and
|
|
it gets sent to one of the servers listening on the topic, or by sending the
|
|
request to a specific server listening on the topic, or by sending the request
|
|
to all servers listening on the topic (known as fanout). These modes are chosen
|
|
via the server and fanout attributes on Target but the mode used is transparent
|
|
to the server.
|
|
|
|
The first parameter to method invocations is always the request context
|
|
supplied by the client.
|
|
|
|
Parameters to the method invocation are primitive types and so must be the
|
|
return values from the methods. By supplying a serializer object, a server can
|
|
deserialize a request context and arguments from - and serialize return values
|
|
to - primitive types.
|
|
"""
|
|
|
|
__all__ = [
|
|
'get_rpc_server',
|
|
'ExpectedException',
|
|
'expected_exceptions',
|
|
]
|
|
|
|
import sys
|
|
|
|
from oslo.messaging.rpc import dispatcher as rpc_dispatcher
|
|
from oslo.messaging import server as msg_server
|
|
|
|
|
|
def get_rpc_server(transport, target, endpoints,
|
|
executor='blocking', serializer=None):
|
|
"""Construct an RPC server.
|
|
|
|
The executor parameter controls how incoming messages will be received and
|
|
dispatched. By default, the most simple executor is used - the blocking
|
|
executor.
|
|
|
|
:param transport: the messaging transport
|
|
:type transport: Transport
|
|
:param target: the exchange, topic and server to listen on
|
|
:type target: Target
|
|
:param endpoints: a list of endpoint objects
|
|
:type endpoints: list
|
|
:param executor: name of a message executor - e.g. 'eventlet', 'blocking'
|
|
:type executor: str
|
|
:param serializer: an optional entity serializer
|
|
:type serializer: Serializer
|
|
"""
|
|
dispatcher = rpc_dispatcher.RPCDispatcher(endpoints, serializer)
|
|
return msg_server.MessageHandlingServer(transport, target,
|
|
dispatcher, executor)
|
|
|
|
|
|
class ExpectedException(Exception):
|
|
"""Encapsulates an expected exception raised by an RPC endpoint
|
|
|
|
Merely instantiating this exception records the current exception
|
|
information, which will be passed back to the RPC client without
|
|
exceptional logging.
|
|
"""
|
|
def __init__(self):
|
|
self.exc_info = sys.exc_info()
|
|
|
|
|
|
def expected_exceptions(*exceptions):
|
|
"""Decorator for RPC endpoint methods that raise expected exceptions.
|
|
|
|
Marking an endpoint method with this decorator allows the declaration
|
|
of expected exceptions that the RPC server should not consider fatal,
|
|
and not log as if they were generated in a real error scenario.
|
|
|
|
Note that this will cause listed exceptions to be wrapped in an
|
|
ExpectedException, which is used internally by the RPC sever. The RPC
|
|
client will see the original exception type.
|
|
"""
|
|
def outer(func):
|
|
def inner(*args, **kwargs):
|
|
try:
|
|
return func(*args, **kwargs)
|
|
except Exception as e:
|
|
if type(e) in exceptions:
|
|
raise ExpectedException()
|
|
else:
|
|
raise
|
|
return inner
|
|
return outer
|