在前一章中, 学习了Computer API及Conductor调用RPC做下一步处理。那么, 这里就有个问题, Openstack中RPC是怎么工作的?
想要知道RPC是怎么工作的, 那么第一个要了解的是什么是RPC?
RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
从Openstack的文档中可以看到,Openstack的内部消息是基于AMQP协议的。 而且从代码结构上看, 它支持Kombu, Qpid, ZMQ三种类型。在系统中, 现在使用的是Qpid。
发送
使用方法
就以前一章的Conductor为例, 它是一个RPC的使用者。
def build_instances(self, context, instances, image, filter_properties,
admin_password, injected_files, requested_networks,
security_groups, block_device_mapping, legacy_bdm=True):
instances_p = [jsonutils.to_primitive(inst) for inst in instances]
image_p = jsonutils.to_primitive(image)
cctxt = self.client.prepare(version='1.5')
cctxt.cast(context, 'build_instances',
instances=instances_p, image=image_p,
filter_properties=filter_properties,
admin_password=admin_password,
injected_files=injected_files,
requested_networks=requested_networks,
security_groups=security_groups,
block_device_mapping=block_device_mapping,
legacy_bdm=legacy_bdm)
这是RPC的基本使用方法,直接使用cctxt.cast来发送消息。而cctxt是从调用client的prepare方法返回的。那么client又是怎么来的呢?
RPCClient
看Conductorr 的\__init__
方法。
class ConductorAPI(rpcclient.RpcProxy):
def __init__(self):
version_cap = self.VERSION_ALIASES.get(CONF.upgrade_levels.conductor,
CONF.upgrade_levels.conductor)
super(ConductorAPI, self).__init__(
topic=CONF.conductor.topic,
default_version=self.BASE_RPC_API_VERSION,
serializer=objects_base.NovaObjectSerializer(),
version_cap=version_cap)
self.client = self.get_client()
class RpcProxy(proxy.RpcProxy):
def get_client(self, namespace=None, server_params=None):
return RPCClient(self,
namespace=namespace,
server_params=server_params)
从这里可以看出, client是RPCClient的实例。
再看看RPCClient的实现, 这里代码不长,就全放在下面。
from nova.openstack.common.rpc import proxy
class RPCClient(object):
def __init__(self, proxy, namespace=None, server_params=None):
super(RPCClient, self).__init__()
#RPC的proxy, 相当于client的lib
self.proxy = proxy
self.namespace = namespace
self.server_params = server_params
self.kwargs = {}
self.fanout = None
def prepare(self, **kwargs):
# Clone ourselves
ret = self.__class__(self.proxy, self.namespace, self.server_params)
ret.kwargs.update(self.kwargs)
ret.fanout = self.fanout
# Update according to supplied kwargs
ret.kwargs.update(kwargs)
server = ret.kwargs.pop('server', None)
if server:
ret.kwargs['topic'] = '%s.%s' % (self.proxy.topic, server)
fanout = ret.kwargs.pop('fanout', None)
if fanout:
ret.fanout = True
return ret
def _invoke(self, cast_or_call, ctxt, method, **kwargs):
try:
msg = self.proxy.make_namespaced_msg(method,
self.namespace,
**kwargs)
return cast_or_call(ctxt, msg, **self.kwargs)
finally:
self.kwargs = {}
self.fanout = None
def cast(self, ctxt, method, **kwargs):
if self.server_params:
def cast_to_server(ctxt, msg, **kwargs):
if self.fanout:
return self.proxy.fanout_cast_to_server(
ctxt, self.server_params, msg, **kwargs)
else:
return self.proxy.cast_to_server(
ctxt, self.server_params, msg, **kwargs)
caster = cast_to_server
else:
caster = self.proxy.fanout_cast if self.fanout else self.proxy.cast
self._invoke(caster, ctxt, method, **kwargs)
def call(self, ctxt, method, **kwargs):
return self._invoke(self.proxy.call, ctxt, method, **kwargs)
def can_send_version(self, version):
return self.proxy.can_send_version(version)
代码不复杂, 最终的调用还是proxy, 而ConductorAPI本身就是RpcProxy的子类, 现在看看RpcProxy类的实现。
RpcProxy
from nova.openstack.common import rpc
from nova.openstack.common.rpc import common as rpc_common
from nova.openstack.common.rpc import serializer as rpc_serializer
class RpcProxy(object):
"""A helper class for rpc clients.
This class is a wrapper around the RPC client API. It allows you to
specify the topic and API version in a single place. This is intended to
be used as a base class for a class that implements the client side of an
rpc API.
"""
# The default namespace, which can be overriden in a subclass.
RPC_API_NAMESPACE = None
def __init__(self, topic, default_version, version_cap=None,
serializer=None):
"""Initialize an RpcProxy.
:param topic: The topic to use for all messages.
:param default_version: The default API version to request in all
outgoing messages. This can be overridden on a per-message
basis.
:param version_cap: Optionally cap the maximum version used for sent
messages.
:param serializer: Optionaly (de-)serialize entities with a
provided helper.
"""
self.topic = topic
self.default_version = default_version
self.version_cap = version_cap
if serializer is None:
serializer = rpc_serializer.NoOpSerializer()
self.serializer = serializer
super(RpcProxy, self).__init__()
def _set_version(self, msg, vers):
"""Helper method to set the version in a message.
:param msg: The message having a version added to it.
:param vers: The version number to add to the message.
"""
v = vers if vers else self.default_version
if (self.version_cap and not
rpc_common.version_is_compatible(self.version_cap, v)):
raise rpc_common.RpcVersionCapError(version_cap=self.version_cap)
msg['version'] = v
def _get_topic(self, topic):
"""Return the topic to use for a message."""
return topic if topic else self.topic
def can_send_version(self, version):
"""Check to se