Neutron的代码交互稍显复杂,对于理解底层网络原理的同学可能比较容易理解;代码也在不断的被完善和添加新的feature中,所以neutron的代码结构在不断变化中,应该用不变的网络原理去理解上层可能千变万化的neutron代码;这个也是为何《
云计算
网络珠玑》中不花费笔墨讲解neutron代码的原因。
1 代码总体
架构
从文件夹的命名也基本可以得出该目录代码的作用,几个重要的文件夹如下:
agent主要是l3 agent及l3 agent ha的相关代码;
common主要是各底层驱动与linux系统命令的交互层;
extensions主要包括的一些扩展功能,包括dvr的代码等;
plugins是core plugin的代码,包括ovs,ml2和各个厂商ibm、ryu提供等的plugin;
scheduler是创建dhcp服务和router到各l3 agent的调度分配相关代码;
server是neutron server相关的代码;
services则是包含了lbaas、vpnaas、fwaas、l3-router、metering等服务的plugin和agent代码;
2.Neutron server 启动过程
1)server/__init__.py负责启动neutron-server进程
def main():
#the configuration will be read into the cfg.CONF global data structure
config.init(sys.argv[1:])
if not cfg.CONF.config_file:
sys.exit(_("ERROR: Unable to find configuration file via thedefault"
" search paths(~/.neutron/, ~/, /etc/neutron/, /etc/) and"
" the '--config-file'option!"))
try:
pool = eventlet.GreenPool()
neutron_api = service.serve_wsgi(service.NeutronApiService)
api_thread = pool.spawn(neutron_api.wait)
try:
neutron_rpc = service.serve_rpc()
except NotImplementedError:
LOG.info(_("RPC was already started in parent process byplugin."))
else:
rpc_thread = pool.spawn(neutron_rpc.wait)
# api and rpc should die together. When one dies, kill the other.
rpc_thread.link(lambda gt: api_thread.kill())
api_thread.link(lambda gt: rpc_thread.kill())
pool.waitall()
except KeyboardInterrupt:
pass
except RuntimeError as e:
sys.exit(_("ERROR: %s") % e)
if __name__ == "__main__":
main()
其中大体流程是读取配置文件,启动相应个数的处理restful api和与各个Agent通信的Rpcworker工作的相关进程;service.serve_wsgi和service.serve_rpc()调用的是service.py里的代码启动API和与各agent通信的rpc相关任务就可以了。
3.l3_agent的启动过程
启动代码在agent/l3_agent.py里:
defmain(manager='neutron.agent.l3_agent.L3NATAgentWithStateReport'):
_register_opts(cfg.CONF)
common_config.init(sys.argv[1:])
config.setup_logging()
server = neutron_service.Service.create(
binary='neutron-l3-agent',
topic=topics.L3_AGENT,
report_interval=cfg.CONF.AGENT.report_interval,
manager=manager)
service.launch(server).wait()
其中main函数的参数L3NATAgentWithStateReport中继承自L3NATAgent,而L3NATAgent继承自firewall_l3_agent.FWaaSL3AgentRpcCallback、l3_ha_agent.AgentMixin和manager.Manager;所以Neutron的FWaas没有单独的进程,是在l3-agent进程里生效的;main函数的另一个参数report_interval表明l3-agent进程向数据库中更新时间的间隔,控制节点的neutron-server则间隔的读取这个更新时间,如果时间相隔太大,则认为l3-agent进程挂掉;其他的neutron服务(或openstack的整个服务判断机制)基本都是如此,这个间隔是可以通过配置文件来配置的。
另外,从services/vpn/Agent.py里的class VPNAgent(l3_agent.L3NATAgentWithStateReport)里可以看出VPN进程也是从l3-agent进程里继承的,所以即使网络节点l3-agent挂了,在控制节点看来,只要vpn进程存在,l3-agent仍然是活着的,因为vpn agent的继承关系导致其会网数据库中更新l3-agent的时间。
网络节点创建路由(namespace)的下发流程是:
agent/l3_agent.py的eventlet.spawn_n(self._process_routers_loop)---_process_router_update----_process_router_if_compatible---process_router---routes_updated(_process_virtual_routes/_update_routing_table)/process_router_floating_ip_nat_rules--_create_agent_gateway_port---_create_namespace;其中_router_added/_router_removed来对防火墙等进行更新通知。
防火墙下发:
控制节点\services\firewall\Fwaas_plugin.pyFirewallPlugin(firewall_db.Firewall_db_mixin交互数据库):
rpc 下发
网络节点rpc 接收
从firewall_agent-api.py继承生成firewall_l3_agent.py/L3NATAgent create_firewall/update_firewall/delete_firewall_invoke_driver_for_plugin_api--- fwaas_driver--
services/firewall/drivers/linux/iptables_fwaas.pyceate_firewall/update_firewall/delete_firewall
agent/linux/iptables_firewall.py(iptables)-- neutron/agent/linux/utils.pyexecute
其他几个service流程基本都是如此。
4.VPNaas的进程启动
启动服务代码在services/vpn/agent.py里:
def main():
l3_agent.main(
manager='neutron.services.vpn.agent.VPNAgent')
可以看出vpnagent其实是启动了一个l3 agent的进程,里面也包含了firewall的相关服务代码,所以vpn需要的规则可以直接调用相关代码下发。
5.LBaas的进程启动
启动服务代码在services/loadbalancer/agent.py里:
def main():
cfg.CONF.register_opts(OPTS)
cfg.CONF.register_opts(manager.OPTS)
#import interface options just in case the driver uses namespaces
cfg.CONF.register_opts(interface.OPTS)
config.register_interface_driver_opts_helper(cfg.CONF)
config.register_agent_state_opts_helper(cfg.CONF)
config.register_root_helper(cfg.CONF)
common_config.init(sys.argv[1:])
config.setup_logging()
mgr = manager.LbaasAgentManager(cfg.CONF)
svc = LbaasAgentService(
host=cfg.CONF.host,
topic=topics.LOADBALANCER_AGENT,
manager=mgr
)
service.launch(svc).wait()
相关的函数全在LbaasAgentManager里;Lbaas的几个流程创建直到创建VIP才调用创建底层lbaas对应namespace的底层驱动代码;基本也是plugin.py发送RPC,然后lbaas对应网络节点的agent调用底层驱动代码来创建namespace及配置对应vport的信息,包括VIP。
6.ovs-neutron-agent的启动
neutron\plugins\openvswitch\agent\Ovs_neutron_agent.py下的main函数:
def main():
#命令注册
cfg.CONF.register_opts(ip_lib.OPTS)
#解析配置文件
common_config.init(sys.argv[1:])
#设置日志
common_config.setup_logging()
q_utils.log_opt_values(LOG)
try:
agent_config = create_agent_config_map(cfg.CONF)
except ValueError as e:
LOG.error(_('%s Agent terminated!'), e)
sys.exit(1)
is_xen_compute_host = 'rootwrap-xen-dom0' in agent_config['root_helper']
if is_xen_compute_host:
# Force ip_lib to always use the root helper to ensure that ip
# commands target xen dom0 rather than domU.
cfg.CONF.set_default('ip_lib_force_root', True)
agent = OVSNeutronAgent(**agent_config)
signal.signal(signal.SIGTERM, agent._handle_sigterm)
#Start everything.
LOG.info(_("Agent initialized successfully, now running... "))
agent.daemon_loop()
if __name__ == "__main__":
main()
Ovs-neutron-agent的所有配置下发最终通过OVSNeutronAgent下发到底层。
7.neutron与db的交互
每个plugin的情况不太一样,详细说明如下:
a) openvswtich 作为core plugin用pluings/openvswitch/ovs_models_v2.py里的
fromneutron.db import model_base
from neutron.db import models_v2
b) ml2作为core plugin用neutron\plugins\ml2/managers.py里调用neutron\plugins\ml2/Db.py:
neutron\plugins\ml2
c) fwaas作为service plugin的db配置在于neutron\services\firewall/Fwaas_plugin.py的
from neutron.db.firewall import firewall_db
class FirewallPlugin(firewall_db.Firewall_db_mixin):
d) L3RouterPlugin作为serviceplugin的db配置在neutron\services\l3_router/ L3_router_plugin.py文件里:
from neutron.db import common_db_mixin
from neutron.db import extraroute_db
from neutron.db import l3_dvrscheduler_db
from neutron.db import l3_gwmode_db
from neutron.db import l3_hamode_db
from neutron.db import l3_hascheduler_db
classL3RouterPlugin(common_db_mixin.CommonDbMixin,
extraroute_db.ExtraRoute_db_mixin,
l3_hamode_db.L3_HA_NAT_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin,
l3_dvrscheduler_db.L3_DVRsch_db_mixin,
l3_hascheduler_db.L3_HA_scheduler_db_mixin)
e)lbaas作为serviceplugin的db配置在\neutron\services\loadbalancer/Plugin.py的
from neutron.db.loadbalancer importloadbalancer_db as ldb
from neutron.db import servicetype_db asst_db
class LoadBalancerPlugin(ldb.LoadBalancerPluginDb,
agent_scheduler.LbaasAgentSchedulerDbMixin):
f)vpnaas作为serviceplugin的db配置在\neutron\services\vpn/Plugin.py的
from neutron.db.vpn import vpn_db
class VPNPlugin(vpn_db.VPNPluginDb):
g)metering作为serviceplugin的db配置在neutron\services\metering/Metering_plugin.py里,
from neutron.db.metering import metering_db
from neutron.db.metering importmetering_rpc
classMeteringPlugin(metering_db.MeteringDbMixin)
neutron-server是neutron的核心组件之中的一个。负责直接接收外部请求,然后调用后端对应plugin进行处理。
其核心启动过程代码主要在neutron.server包中。
__init__.py
文件里
包含一个
main()
函数,是
WSGI
server開始的模块,而且通过调用
serve_wsgi
来创建一个
NeutronApiService
的实例。然后通过
eventlet
的
greenpool
来执行
WSGI
的应用程序,响应来自client的请求。
主要过程为:
eventlet.monkey_patch()
绿化各个模块为支持协程(通过打补丁的方式让本地导入的库都支持协程)。
config.parse(sys.argv[1:])
if not cfg.CONF.config_file:
sys.exit(_("ERROR: Unable to find configuration file via the default"
" search paths (~/.neutron/, ~/, /etc/neutron/, /etc/) and"
" the '--config-file' option!"))
通过解析命令行传入的參数。获取配置文件所在。
pool = eventlet.GreenPool()
创建基于协程的线程池。
neutron_api = service.serve_wsgi(service.NeutronApiService)
api_thread = pool.spawn(neutron_api.wait)
serve_wsgi方法创建
NeutronApiService
实例(作为一个
WsgiService
),并调用
其的start()
来启动
socket
server端。
#neutron.service
def serve_wsgi(cls):
try:
service = cls.create()
service.start()
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception(_('Unrecoverable error: please check log '
'for details.'))
return service
neutron.service.NeutronApiService类继承自neutron.service.WsgiService
。其
create
方法返回一个
appname
默觉得“
neutron
”的
WsgiService
对象;
start
方法则调用
_run_wsgi
方法。
def start(self):
self.wsgi_app = _run_wsgi(self.app_name)
_run_wsgi
方法主要是从
api-paste.ini
文件里读取应用(最后是利用
neutron.api.v2.router:APIRouter.factory
来构造应用)。然后为应用创建一个
wsgi
的服务端,并启动应用。主要代码为。
def _run_wsgi(app_name):
app = config.load_paste_app(app_name)
if not app:
LOG.error(_('No known API applications configured.'))
return
server = wsgi.Server("Neutron")
server.start(app, cfg.CONF.bind_port, cfg.CONF.bind_host,
workers=cfg.CONF.api_workers)
# Dump all option values here after all options are parsed
cfg.CONF.log_opt_values(LOG, std_logging.DEBUG)
LOG.info(_("Neutron service started, listening on %(host)s:%(port)s"),
{'host': cfg.CONF.bind_host,
'port': cfg.CONF.bind_port})
return server
至此,neutron server
启动完毕。
之后。须要创建rpc
服务端
。
try:
neutron_rpc = service.serve_rpc()
except NotImplementedError:
LOG.info(_("RPC was already started in parent process by plugin."))
else:
rpc_thread = pool.spawn(neutron_rpc.wait)
rpc_thread.link(lambda gt: api_thread.kill())
api_thread.link(lambda gt: rpc_thread.kill())
这些代码创建
plugin
的
rpc
服务端,并将
api
和
rpc
的生存绑定到一起,一个死掉,则另外一个也死掉。
pool.waitall()
最后是后台不断等待。