vlambda博客
学习文章列表

neutron-server源码分析总纲

背景:本文以neutron的12.0.0版本为例进行源码分析。

neutron-server作为Neutron的控制中心,主要对Neutron四大类资源进行管理。从实现架构上可以粗略分为两大模块:neutron-api和RPC。这么分类的依据来自于neutron-server的启动方法,其资源的初始化基本都是在start_api_and_rpc_workers这个方法中完成的。

该方法的源码如下所示:

def start_api_and_rpc_workers(neutron_api): try: worker_launcher = service.start_all_workers() pool = eventlet.GreenPool() api_thread = pool.spawn(neutron_api.wait) plugin_workers_thread = pool.spawn(worker_launcher.wait) api_thread.link(lambda gt: plugin_workers_thread.kill()) plugin_workers_thread.link(lambda gt: api_thread.kill()) pool.waitall() except NotImplementedError: neutron_api.wait()

为了简洁,删除了日志等内容。在这个方法中生成了两个线程,分别是api_thread和plugin_workers_thread。api_thread与neutron-api线程(在调用方法中已创建)相关联,即neutron-server的wsgi部分。这部分内容也是接下来需要讲解的重点;而plugin_workers_thread通过名称即可猜测出它必定与plugin插件相关,事实上它主要用于RPC服务。如果这两部分内容放在一篇博文进行讲解,不仅不利于阅读,也不利于理解整个Neutron(因为包含的信息太多),所以关于RPC的内容将会放到下一章节进行讲解。

一 、neutron-server是什么

弄清楚这个问题,我们才能明白neutron-server为什么要如此设计。

Neutron是OpenStack项目中负责提供网络服务的组件,它基于软件定义网络的思想,实现了网络虚拟化下的资源管理。Neutron 的设计目标是实现“网络即服务(Networking as a Service)”,在设计上遵循了基于 SDN 实现网络虚拟化的原则,在实现上充分利用了 Linux 系统上的各种网络相关的技术。

  • Neutron功能


    • 二层交换:支持多种虚拟交换机,一般使用Linux Bridge和Open vSwitch实现。

    • 三层路由:利用Linux协议栈实现

    • 负载均衡:通过HAProxy实现

    • 防火墙:使用iptables实现

    • VPN 等等

  • 网络类型


    • Local:本地网络,主要用于测试

    • Flat:Falt网络,只有一个广播域

    • VLAN:可以跨节点,私有云使用较多

    • VXLAN:大二层网络,能够克服vlan和物理网络基础设施的限制,公有云使用较多。

    • GRE:与VXLAN类似

  • Neutron架构

其中,Neutron Server对外提供OpenStack网络API,接收请求,并调用plugin处理请求;plugin处理Neutron Server发来的请求,维护OpenStack逻辑网络的状态,并调用Agent处理请求;Agent处理Plugin请求,负责在Network provider上实现各种网络功能;Network provider提供网络服务的虚拟或者物理网络设备;MQ用于组件之间的通信和RPC调用;database存放OpenStack的网络状态信息,包括Network,Subnet,Port,Router等。

二、neutron-api

在eventlet_wsgi_server方法中,service.serve_wsgi以wsgi的方式启动了service.NeutronApiService。service.NeutronApiService继承自WsgiService类。WsgiService类是一个普通的Python类。它有两个公共方法start和wait,如下所示:

class WsgiService(object): def __init__(self, app_name): self.app_name = app_name self.wsgi_app = None
def start(self): self.wsgi_app = _run_wsgi(self.app_name)
def wait(self): self.wsgi_app.wait()

该类唯一参数是app_name,默认值是'neutron',该值来自其子类NeutronApiService的create方法。在start方法中调用了_run_wsgi方法,然后返回一个wsgi类型的app对象。_run_wsgi方法如下所示:

def _run_wsgi(app_name): app = config.load_paste_app(app_name) if not app: LOG.error('No known API applications configured.') return return run_wsgi_app(app)

load_paste_app方法是一个封装方法,最终会调用下面两个方法,并返回一个符合WSGI规范的application,实际上就是一个叫作“neutron”的对象(定义于/etc/neutron/api-paste.ini文件,这部分内容属于pecan应用,后面会对其用法进行详细说明,请不要惊慌)

loader = wsgi.Loader(cfg.CONF)app = loader.load_app(app_name)

一句话概括,load_paste_app方法根据字符串"neutron",生成了一个符合WSGI规范的application对象,而这个对象将被neutron-server用于对外提供服务。用MVC模型进行类比,application就是通常所说的MVC模型中的controller对象。

仅仅有了application还不行,必须把application加载到server容器,其才能在server容器的帮助之下对外提供HTTP服务。Tomcat、Jetty、WebLogic等中间件都是满足servlet规范的servlet容器。Java Web应用必须部署在这类容器中才能真正运行起来。Python的WSGI规范与之大同小异,大家可互相印证加深理解。

我们已经得到了满足WSGI规范的application对象,那么接下来必然是把该对象加载到满足WSGI规范的server容器中。

继续跟踪调用链:

def run_wsgi_app(app): server = wsgi.Server("Neutron") server.start(app, cfg.CONF.bind_port, cfg.CONF.bind_host, workers=_get_api_workers()) LOG.info("Neutron service started, listening on %(host)s:%(port)s", {'host': cfg.CONF.bind_host, 'port': cfg.CONF.bind_port}) return server
 

wsgi.Server.start方法最终调用_run方法加载application到eventlet.wsgi.server(这是一个满足WSGI规范的服务器)中。

def _run(self, application, socket): 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)

至此,neutron-api的调用链分析宣布结束。


三、application生成

当前版本的neutron-server使用pecan生产neutronapiapp_v2_0(一个符合wsgi的application)。生成该application的工厂方法是neutron.pecan_wsgi.v2_factory,详情如下:

def v2_factory(global_config, **local_config): app_hooks = [ hooks.UserFilterHook(), # priority 90 hooks.ContextHook(), # priority 95 hooks.ExceptionTranslationHook(), # priority 100 hooks.BodyValidationHook(), # priority 120 hooks.OwnershipValidationHook(), # priority 125 hooks.QuotaEnforcementHook(), # priority 130 hooks.NotifierHook(), # priority 135 hooks.QueryParametersHook(), # priority 139 hooks.PolicyHook(), # priority 140 ] app = pecan.make_app(root.V2Controller(), debug=False, force_canonical=False, hooks=app_hooks, guess_content_type_from_ext=True) startup.initialize_all() return app

pecan.make_app可以生成了一个application,其在配置文件api-paste.ini中的别名是neutronapiapp_v2_0,最终该对象会在下面的调用链中被调用。

cors http_proxy_to_wsgi request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0

四、Python Web规范WSGI

WSGI(Web Server Gateway Interface)是一个规范,定义了Web服务器与Python应用程序之间进行交互的标准接口,使得Python Web应用可以和Web服务器对接,以提高Web应用在不同Web服务器间的移植性。

WSGI Web服务器又有哪些作用呢?

世界上所有的Web应用本质上做了以下事情:

  1. 客户端发送HTTP请求

  2. 服务器接收请求,然后生成一个HTML文档

  3. 服务器把HTML文档作为HTTP响应发送给客户端

  4. 客户端对HTML进行解析展示

上述2和3是在服务器端完成的。如果没有Web服务器,那么每个HTTP请求,应用程序都需要自己解析HTTP协议或者动态生成HTML文档,处理各种不同的网络异常或应用异常。在计算机的世界,这种重复操作的事情显然是会被一种中间应用所替代--Web服务器。

WSGI接口规范定义很简单,只要求Web应用是可调用对象,可以是一个函数,也可以是一个实现了 call方法的实例或者是一个类。同时,WSGI还规定可调用对象可以接收两个参数,并且可调用对象需要返回一个可迭代的值。

  • 可调用对象是一个函数

def application(env,start_response): pass
 
  • 可调用对象是一个实例

class Application(): def __init__(self,env,start_response): pass
 
  • 可调用对象是一个类

class Application(): def __call__(self,env,start_response):     pass
 

下面以可调用对象是一个函数为例,实现一个简单的Web应用:

import eventletimport eventlet.wsgi
def application(evn,start_response):start_response('200 OK', [('Content-Type', 'text/html')])return '<h1>app</h1>'eventlet.wsgi.server(eventlet.listen(('',8080)),application)
 

简简单单6行代码,就实现了一个返回字符串“app”的符合WSGI规范的Web应用。

五、使用neutron-server的方式实现一个Web服务

neutron-server的Web容器使用的是eventlet.wsgi.server,使用pecan生成WSGI application。

import pecanimport eventlet.wsgiimport eventletimport timefrom pecan import exposefrom pecan.hooks import PecanHook
class RootController(): @expose() def index(self): return str(time.time()) class UserHook(PecanHook): priority = 91 def before(self, state): print "user hook before"
def after(self, state): print "user hook after" def setup_app(): app_hooks = [UserHook()] app = pecan.make_app(RootController(),hooks=app_hooks) return app
def run(): eventlet.wsgi.server(eventlet.listen(('', 8080)), setup_app(), keepalive=False, socket_timeout=30)
if __name__ == '__main__': run()

上面讲解了很多关于neutron-api初始化过程的调用链,其实精简到最后大概类似上述代码。

小结

neutron-server是一个Web Server,对外提供Service API。通过这些API,可以对Neutron管理的network、subnet、port等资源进行增删改查。同时,neutron会在背后通过plugin的方式把这些逻辑资源映射到物理世界。本文只分析了neutron-api的实现原理,并对其使用到相关技术进行了示例说明。