我们主要分析Transport为rabbit方式的RPC-server构建,当我们执行如下命令时:
[[email protected](keystone_admin)]# rabbitmqctl list_consumers
Listingconsumers ...
alarm_notifier <[email protected]> 1 true 0 []
alarm_notifier.jun <[email protected]> 2 true 0 []
alarm_notifier_fanout_bcc5514c42a24c78928bf08dc4bc844e <[email protected]> 3 true 0 []
cert <[email protected]> 1 true 0 []
cert.jun <[email protected]> 2 true 0 []
cert_fanout_8c23d4a227af4488a5ee22249422a5e8 <[email protected]> 3 true 0 []
……….
reply_32583cb2867d476db776e2d4d2093606 <[email protected]> 1 true 0 []
reply_9b28643406614d8280db511872854dbb <[email protected]> 1 true 0 []
reply_eb5af11fd3aa42dda88eea22446d3fc1 <[email protected]> 1 true 0 []
scheduler <[email protected]> 1 true 0 []
scheduler.jun <[email protected]> 2 true 0 []
scheduler_fanout_eb88ae2d696c40538615fb8e7af84f2f <[email protected]> 3 true 0 []
我们可以利用linux的systemd技术,在linux系统启动时,加载OpenStack组件服务,从而形成RPC的consumer。所以我们可以从systemd的相关文件找到OpenStack组件服务的启动入口,在这里我们主要关注OpenStack组件服务中RPC-server的构建过程,且以Nova-scheduler组件为例进行分析。
[root@jun~(keystone_admin)]# cat/etc/systemd/system/multi-user.target.wants/openstack-nova-scheduler.service
[Unit]
Description=OpenStackNova Scheduler Server
After=syslog.targetnetwork.target
[Service]
Type=notify
NotifyAccess=all
TimeoutStartSec=0
Restart=always
User=nova
ExecStart=/usr/bin/nova-scheduler
[Install]
WantedBy=multi-user.target
Systemd通过执行/usr/bin/nova-scheduler命令来启动Nova-scheduler组件,那么/usr/bin/nova-scheduler做了什么呢?我们继续往下分析。
[root@jun~(keystone_admin)]# cat /usr/bin/nova-scheduler
#!/usr/bin/python
#PBR Generated from u'console_scripts'
importsys
fromnova.cmd.scheduler import main
if__name__ == "__main__":
sys.exit(main())
原来这个文件执行/usr/lib/python2.7/site-packages/nova/cmd/scheduler.py中的main函数。如下所示:
"""Starterscript for Nova Scheduler."""
…….
CONF= cfg.CONF
CONF.import_opt('scheduler_topic','nova.scheduler.rpcapi')
defmain():
config.parse_args(sys.argv)
logging.setup(CONF, "nova")
utils.monkey_patch()
objects.register_all()
gmr.TextGuruMeditation.setup_autorun(version)
server =service.Service.create(binary='nova-scheduler',
topic=CONF.scheduler_topic)
service.serve(server)
service.wait()
其中,红色标记中的内容与RPC-server的创建有关,所以我们重点关注红色部分的函数调用内容。
1. config.parse_args(sys.argv)
defparse_args(argv, default_config_files=None):
log.set_defaults(_DEFAULT_LOGGING_CONTEXT_FORMAT, _DEFAULT_LOG_LEVELS)
log.register_options(CONF)
options.set_defaults(CONF,connection=_DEFAULT_SQL_CONNECTION,
sqlite_db='nova.sqlite')
rpc.set_defaults(control_exchange='nova')
debugger.register_cli_opts()
CONF(argv[1:],
project='nova',
version=version.version_string(),
default_config_files=default_config_files)
rpc.init(CONF)
由于control_exchange的默认值为:’openstack’,所以通过set_defaults函数修改为:’nova’。因此随后的get_transport函数中获得的control_exchange的值为:’nova’。
rpc.init(CONF)代码如下:
definit(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)
A. get_transport方法通过从用户配置文件中搜集到与transport相关的配置文件来构造且返回一个Transport对象。如下所示:
defget_transport(conf, url=None, allowed_remote_exmods=None, aliases=None):
"""A factory method forTransport objects.
This method will construct a Transportobject from transport configuration
gleaned from the user's configuration and,optionally, a transport URL.
If a transport URL is supplied as aparameter, any transport configuration
contained in it takes precedence. If notransport URL is supplied, but
there is a transport URL supplied in theuser's configuration then that
URL will take the place of the URLparameter. In both cases, any
configuration not supplied in the transportURL may be taken from
individual configuration parameters in theuser's configuration.
An example transport URL might be::
rabbit://me:passwd@host:5672/virtual_host
and can either be passed as a string or aTransportURL object.
:param conf: the user configuration
:type conf: cfg.ConfigOpts
:param url: a transport URL
:type url: str or TransportURL
:param allowed_remote_exmods: a list ofmodules which a client using this
transportwill deserialize remote exceptions
from
:type allowed_remote_exmods: list
:param aliases: A map of transport alias totransport name
:type aliases: dict
"""
allowed_remote_exmods =allowed_remote_exmods or []
conf.register_opts(_transport_opts)
if not isinstance(url, TransportURL):
url = url or conf.transport_url
parsed = TransportURL.parse(conf, url,aliases)
if not parsed.transport:
raise InvalidTransportURL(url, 'Noscheme specified in "%s"' % url)
url = parsed
kwargs = dict(default_exchange=conf.control_exchange,
allowed_remote_exmods=allowed_remote_exmods)
try:
mgr =driver.DriverManager('oslo.messaging.drivers',
url.transport.split('+')[0],
invoke_on_load=True,
invoke_args=[conf, url],
invoke_kwds=kwargs)
except RuntimeError as ex:
raise DriverLoadFailure(url.transport,ex)
return Transport(mgr.driver)
这里get_transport方法将_transport_opts对象register到conf对象中,而_transport_opts对象中的control_exchange属性在上面的set_defaults方法中设置为:’nova’,不是默认值:’openstack’。
driver.DriverManager是stevedore组件中的方法,stevedore组件可以在运行时发现和载入所谓的”插件”,它在Setuptools的entrypoints基础上,构造一层抽象层,使开发者可以更加容易地在运行时发现和载入插件。
在这里我们利用oslo_messaging中的entry points来发现和载入插件。在我的OpenStack环境中(kilo版本),entry points文件为:/usr/lib/python2.7/site-packages/oslo.messaging-1.8.2-py2.7.egg-info/entry_points.txt。
[oslo.messaging.zmq.matchmaker]
local= oslo_messaging._drivers.matchmaker:MatchMakerLocalhost
ring= oslo_messaging._drivers.matchmaker_ring:MatchMakerRing
redis= oslo_messaging._drivers.matchmaker_redis:MatchMakerRedis
[oslo.config.opts]
oslo.messaging= oslo_messaging.opts:list_opts
[oslo.messaging.notify.drivers]
log= oslo_messaging.notify._impl_log:LogDriver
messagingv2= oslo_messaging.notify._impl_messaging:MessagingV2Driver
noop= oslo_messaging.notify._impl_noop:NoOpDriver
routing= oslo_messaging.notify._impl_routing:RoutingDriver
test= oslo_messaging.notify._impl_test:TestDriver
messaging= oslo_messaging.notify._impl_messaging:MessagingDriver
[console_scripts]
oslo-messaging-zmq-receiver= oslo_messaging._cmd.zmq_receiver:main
[oslo.messaging.drivers]
qpid= oslo_messaging._drivers.impl_qpid:QpidDriver
amqp= oslo_messaging._drivers.protocols.amqp.driver:ProtonDriver
kombu= oslo_messaging._drivers.impl_rabbit:RabbitDriver
rabbit =oslo_messaging._drivers.impl_rabbit:RabbitDriver
fake= oslo_messaging._drivers.impl_fake:FakeDriver
zmq= oslo_messaging._drivers.impl_zmq:ZmqDriver
[oslo.messaging.executors]
aioeventlet= oslo_messaging._executors.impl_aioeventlet:AsyncioEventletExecutor
threading= oslo_messaging._executors.impl_thread:ThreadExecutor
blocking= oslo_messaging._executors.impl_blocking:BlockingExecutor
eventlet= oslo_messaging._executors.impl_eventlet:EventletExecutor
其中[*]中的内容为entry points的namespace名称,它下面的内容为插件相关内容,如oslo.messaging.drivers即为namespace名称,其中它下面包括6个插件。其中每个插件都符合”名字 = 模块:可导入对象”。上述driver.DriverManager方法的第一个参数:” oslo.messaging.drivers”即为namespace名称。第二个参数为插件名,其中该场景下使用的是rabbit插件,即:rabbit =oslo_messaging._drivers.impl_rabbit:RabbitDriver。然后利用mgr.driver(即oslo_messaging._drivers.impl_rabbit.RabbitDriver)去构造Transport对象。
B.NOTIFIER = messaging.Notifier(TRANSPORT, serializer=serializer)创建一个Notifier对象通过TRANSPORT发送通知消息。通知消息遵循如下的格式:
{'message_id':six.text_type(uuid.uuid4()), #消息id号
'publisher_id': 'compute.host1', #发送者id
'timestamp': timeutils.utcnow(), #时间戳
'priority': 'WARN', #通知优先级
'event_type': 'compute.create_instance', #通知类型
'payload': {'instance_id': 12, ... } #通知内容
}
可以在不同的优先级别上发送通知,这些优先级包括sample,critical,error,warn,info,debug和audit等。
总结:本篇文章从linux的systemd入手,找到Nova-scheduler的启动流程,然后主要分析/usr/lib/python2.7/site-packages/nova/cmd/scheduler.py中的main函数的config.parse_args(sys.argv)初始化过程。即创建一个Notify对象,该对象通过某种transport(这里是rabbit)发送通知消息。其中
server =service.Service.create(binary='nova-scheduler',
topic=CONF.scheduler_topic)
service.serve(server)
service.wait()
对于这三行代码,我们将在后面进行详细分析。