本文会讲述 Neutron 是如何启动一个 Web Server 的。Neutron Server 对外提供的 Service API (RESTful)并不是一个实实在在的模块。对于用户来说,它们只是一批 RESTful 接口,对于 Neutron 来说,它们只是资源(network、subnet、port 等)的 CRUD 的一种外在体现。
Neutron Plugin 与 Neutron Agent 之间的通信机制是 RPC,Neutron Plugin 既是 RPC Producer,也是 RPC Consumer。从进程的角度来讲,Neutron Plugin 是 Neutron Server 的一部分,因此本文也会讲述 Neutron Server 是如何创建一个 RPC Consumer的。
Neutron 启动一个 Web Server
想要启动一个Web Server,Neutron Server 首先作为一个普通的进程启动起来,然后它有两种选择:
- 在当前进程以协程的方式,开启 Web Server
- 另外启动进程,在新的进程中开启 Web Server
Web Server 的启动过程
在 neutron/setup.cfg 文件中有这么一句话,定义了 Neutron Server 的启动函数名称:
neutron-server = neutron.cmd.eventlet.server:main
Neutron Server 的启动函数的实现如下:
#neutron/neutron/cmd/eventlet/server/__init__.py
# 使用的是 Paste + PasteDeploy + Routes + WebOb 框架
def main():
server.boot_server(wsgi_eventlet.eventlet_wsgi_server)
函数 wsgi_eventlet.eventlet_wsgi_server() 的代码如下:
def eventlet_wsgi_server():
# 启动了一个 Web Server
neutron_api = service.serve_wsgi(service.NeutronApiService)
start_api_and_rpc_workers(neutron_api)
Web Server 启动过程中的关键参数
# neutron/neutron/wsgi.py
# 创建一个协程池
self.pool = eventlet.GreenPool(1)
# 启动一个协程,启动函数是 _run,它的两个参数是:application、socket
pool.spawn(_run,application,socket)
# 启动函数 _run 的定义
def _run(self, application, socket):
"""Start a WSGI server in a new green thread."""
# 启动一个符合 WSGI 规范的 Web Server
eventlet.wsgi.server(socket, application,
max_size=self.num_threads,
log=LOG,
keepalive=CONF.wsgi_keep_alive,
log_format=CONF.wsgi_log_format,
socket_timeout=self.client_socket_timeout)
代码的含义是:
- 创建一个协程池
- 协程的启动函数就是 _run
- _run 函数的本质是创建一个符合 WSGI 规范的 Web Server
- 这个 Web Server 的 WSGI Applicaiton 就是传入的参数 application
- 这个 Web Server 绑定的 Socket 就是传入的 socket
Socket 的 IP 和端口号,在配置文件中配置
neutron/tests/etc/neutron.conf
# 绑定到 Web Server 的 IP 地址
bind_host = 0.0.0.0
# 绑定到 Web Server 的端口号
bind_port = 9696
除了 Server IP 和 Server 端口号,最重要的参数就是 WSGI Application 了,因为 WSGI Application 才是真正处理 HTTP 请求的实体。_run 函数的参数 application,在 Neutron Server 启动的过程中,是靠如下函数加载的,涉及 RESTful API 的发布与处理,以及 Neutron Plugins 的加载。
# neutron/neutron/service.py
def _run_wsgi(app_name):
app = config.load_paste_app(app_name)
Neutron 将它的 Service 分为两大类:Core Service 和 Extension Service。Core Service 包括 networks、subnets、ports、subnetpools 等资源的 RESTful API,Extension Service 包括 routers、segments、trunks、policies 等其他各种资源的 RESTful API。
Core Service API(RESTful)的处理流程
Core Service 的 WSGI Application 到底是什么。在 api-paste-ini 中,有如下定义:
# neutron/etc/api-paste.ini
[app:neutronapiapp_v2_0]
paste.app_factory = neutron.api.v2.router:APIRouter.factory
也就是说,Core Sevice 的 WSGI Application 将由 neutron.api.v2.router:APIRouter.factory 这个函数创建。Core Service 的 WSGI Application 是一个 class APIRouter 的实例。
Core Service 处理 HTTP Request 的基本流程
- Web Client 发送 HTTP Request 请求到达 Web Server(即客户端调用 Neutron Server 的 Core Server API(RESTful))
- Web Server 调用它的 WSGI Application(class APIRouter 的实例)的 __call__ 函数,即调用 class APIRouter 父类 class Router 的 __call__ 函数
- class Router 的 __call__ 函数,返回了 class RoutesMiddleware 的一个实例
- Web Server 继续调用 class RoutesMiddleware 实例的 __call__ 函数
- class RoutesMiddleware 的 __call__ 函数,反过来调用 class Router 的静态函数 _dispatch
class Router 的 _dispatch 函数代码:
match = req.environ['wsgiorg.routing_args'][1]
app = match['controller']
对于 Core Service 的 WSGI Application 而言,一个 HTTP Request 过来以后,基本的处理流程是在父类的 class Router 中完成了。class Router 处理到最后,是从它的成员变量 map 中查找对应的处理函数并执行它。
class Router 的成员变量 map 实在子类 class APIRouter 中完成构建的。class APIRouter 中 __init__ 函数完成了三件事情:
- 初始化 map 成员变量
- 加载了 ML2 Plugin
- 实例化了 Core Service 的 Extension Manger
针对一个资源,Neutron 提供了 6 个 Service API。分为两大类:
- 针对复数资源(比如 networks)的操作,成为 collections action,包括:index、create、bulk create
- 针对单个资源(资源后面带上 ID 参数,比如 networks/{network_id})的操作成为 member action,包括 show、update、delete
class Controller 的成员函数,最终会调用 ML2Plugin 相应的成员函数。
Plugin 的加载
当一个 HTTP Request 过来以后,最终处理这个 Request 的函数都是相应的 Plugin (插件)的函数。Neutron 的 Plugin 最终会在 NeutronMananer 的 __init__ 函数中加载:
class NeutronManager(object):
def __init__(self, options=None, config_file=None):
# 加载 Core Service Plugin
plugin_provider = cfg.CONF.core_plugin
plugin = self._get_plugin_instance(CORE_PLUGINS_NAMESPACE,
plugin_provider)
# load services from the core plugin first
self._load_services_from_core_plugin(plugin)
self._load_service_plugins()