源码入手看Neutron

Neutron 分析总结

Neutron 是 OpenStack 项目中负责提供网络服务的组件,它基于软件定义网络的思想,实现了网络虚拟化下的资源管理

注意:本文所有的分析都是基于neutron的stable/train版本

本文将从源码入手对neutron进行一个大致的剖析。读完本文您可以了解到的知识有:

  1. neutron server是如何通过命令行启动的
  2. plugin是如何加载创建的
  3. plugin的callback system
  4. plugin和agent之间的rpc调用
  5. 如何动手写一个插件并部署到服务器上

一、neutron整体结构

在这里插入图片描述
neutron从软件层面整体可以分为两个部分:Neutron ServerAgent

  1. 其中Neutron Server部分主要负责接收来自web服务的RESTful API请求,并将请求分发到对应的Plugin上。plugin主要负责做一些数据库相关的操作,并发送通过rpcagent通信。
  2. Agent部分则是提供rpc server接口供plugin调用,这些接口用来来实现实际的业务功能。

二、Neutron Server的启动流程

1. 命令行启动neutron

neutron server命令行启动的命令为neutron-server --config-file /usr/share/neutron/neutron-dist.conf --config-dir /usr/share/neutron/server --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugin.ini --config-dir /etc/neutron/conf.d/common --config-dir /etc/neutron/conf.d/neutron-server --log-file /var/log/neutron/server.log

通过查看setup.cfg文件可知,在我们敲neutron-server命令来启动Neutron Server时,实际上是调用了**neutron/cmd/eventlet/server/**模块的main方法。

 33 [entry_points]             
 36 console_scripts =          
 51     neutron-server = neutron.cmd.eventlet.server:main
 52     neutron-rpc-server = neutron.cmd.eventlet.server:main_rpc_eventlet

下面我们来具体分析一下这个函数。

 13 from neutron import server
 14 from neutron.server import rpc_eventlet
 15 from neutron.server import wsgi_eventlet
 16 
 17 
 18 def main():
 19     server.boot_server(wsgi_eventlet.eventlet_wsgi_server)
 20 
 21 
 22 def main_rpc_eventlet():
 23     server.boot_server(rpc_eventlet.eventlet_rpc_server)

server.boot_server(wsgi_eventlet.eventlet_wsgi_server)实际上就是通过调用wsgi_eventlet.eventlet_wsgi_server()这个方法创建了一个wsgi app

WSGI的全称是Web Server Gateway Interface,翻译过来就是Web服务器网关接口。具体的来说,WSGI是一个规范,定义了Web服务器如何与Python应用程序进行交互,使得使用Python写的Web应用程序可以和Web服务器对接起来。

通过查看配置文件neutron/etc/api-paste.ini

 41 [app:neutronapiapp_v2_0]
 42 paste.app_factory = neutron.api.v2.router:APIRouter.factory

可以看出,创建一个app实际调用的是neutron.api.v2.router:APIRouter.factory

 24 def _factory(global_config, **local_config):
 25     return pecan_app.v2_factory(global_config, **local_config)
 26         
 27         
 28 setattr(APIRouter, 'factory', _factory)
 27 def v2_factory(global_config, **local_config):
        ......
 42     app = pecan.make_app(root.V2Controller(),
 43                          debug=False,
 44                          force_canonical=False,
 45                          hooks=app_hooks,
 46                          guess_content_type_from_ext=True)
 47     startup.initialize_all()
 48     return app

可以看出实际的初始化流程其实是在startup.initialize_all()里面做的。它所做的事情就是准备好 neutron-server.service 启动所必须的前提条件,包括:Plugins 的加载、API Resources Controller 的实例化以及处理 API Resources、Controllers、Plugins 三者之间的映射关系,下面我们分别讲解。此函数代码较长,不再列出来,感兴趣可以自行下载源码查看,neutron/neutron/pecan_wsgi/startup.py


2. plugin的加载

2.1 plugin概念

上节我们讲到neutron-server启动时,调用startup.initialize_all()来完成所有初始化操作,实际上在此函数中,第一步就是调用manager.init()加载插件。

Neutron的插件分为Core PluginsService Plugins。其中Core Plugins只有一个,默认的是ml2,而Service Plugins可以有很多,例如:L3RouterPlugin、FWaaSPlugin、LBaaSPlugin、VPNaaSPlugin等等。Neutron的扩展性很好,所有的这些Service Plugins都可以通过修改**/etc/neutron/neutron.conf**这个配置文件,来选择是否加载。

[DEFAULT]
core_plugin = ml2
service_plugins = router, firewall_v2, h3crouter

如上所示,如果不想启用router这个插件,就可以将配置文件修改为:

[DEFAULT]
core_plugin = ml2
service_plugins = firewall_v2, h3crouter
2.2 plugin的加载

进入正题,看一下manager.init()函数:

294 def init():
295     """Call to load the plugins (core+services) machinery."""
296     if not directory.is_loaded():
297         NeutronManager.get_instance()

看到get_instance就应该知道NeutronManager这个类是用单例模式实现的,保证全局有且只有一个实例对象。我们来看其__init__函数,下面列出部分代码,我对其的理解以注释的形式体现

def __init__(self, options=None, config_file=None):

        # 通过配置文件读取Core Plugins
        plugin_provider = cfg.CONF.core_plugin
        LOG.info("Loading core plugin: %s", plugin_provider)
        
        # 加载Core Plugins的类,生成一个实例,可以理解为调用了ml2的构造函数(__init__函数)
        plugin = self._get_plugin_instance(CORE_PLUGINS_NAMESPACE,
                                           plugin_provider)
        directory.add_plugin(lib_const.CORE, plugin)

        # 将核心插件也放到services插件中,统一管理
        self._load_services_from_core_plugin(plugin)
        
        # 加载service plugins
        self._load_service_plugins()

def _load_service_plugins(self):
		# 和core plugin一个套路,先从配置文件中获取支持哪些插件
        plugin_providers = cfg.CONF.service_plugins
        
        # 推测和entry points有关,类似于找到实际的lib路径
        plugin_providers.extend(self._get_default_service_plugins())
        LOG.debug("Loading service plugins: %s", plugin_providers)
        
        # 循环加载所有插件,即循环创建插件实例
        for provider in plugin_providers:
            if provider == '':
                continue
			# 获取插件类
            plugin_class = self._get_plugin_class(
                'neutron.service_plugins', provider)
            
            # 获取插件类的依赖
            required_plugins = getattr(
                plugin_class, "required_service_plugins", [])
            
            # 循环加载其依赖类
            for req_plugin in required_plugins:
                LOG.info("Loading service plugin %s, it is required by %s",
                         req_plugin, provider)
                # 加载依赖类
                self._create_and_add_service_plugin(req_plugin)
            '''
            加载插件类
            此函数比较重要的代码如下
            plugin.agent_notifiers.update(plugin_inst.agent_notifiers)
			这句话声明了每个插件需要通知的agent服务,以及通知agent所用的API接口
            '''
            self._create_and_add_service_plugin(provider)
2.3 plugin的启动

l3_router_plugin为例,创建实例时首先调用构造函数

def __init__(self):
        self.router_scheduler = importutils.import_object(
            cfg.CONF.router_scheduler_driver)
        self.add_periodic_l3_agent_status_check()
        super(L3RouterPlugin, self).__init__()
        if 'dvr' in self.supported_extension_aliases:
            l3_dvrscheduler_db.subscribe()
        if 'l3-ha' in self.supported_extension_aliases:
            l3_hascheduler_db.subscribe()
        self.agent_notifiers.update(
            {n_const.AGENT_TYPE_L3: l3_rpc_agent_api.L3AgentNotifyAPI()})

        rpc_worker = service.RpcWorker([self], worker_process_count=0)

        self.add_worker(rpc_worker)
        self.l3_driver_controller = driver_controller.DriverController(self)

l3_router_plugin初始化时,创建了一个router_scheduler实例,其扩展功能dvrl3-ha,通过Callback System订了一些资源变化的消息。最重要的时,创建了rpc serverrpc client。其中server用来接受agent的消息,client用来向agent发送消息。

rpc client的创建是由agent_notifiers.update()完成的,它注册了一个类型AGENT_TYPE_L3和一个具体的消息发送接口类L3AgentNotifyAPI()

rpc server则是通过创建一个rpcWorker实例,并将其添加到统一的rpc server中管理。server通过调用l3_router_plugin实例内部方法start_rpc_listerners来创建一个监听线程。

150     def start_rpc_listeners(self):      
151         # RPC support      
152         self.topic = topics.L3PLUGIN    
153         self.conn = n_rpc.Connection()  
154         self.endpoints = [l3_rpc.L3RpcCallback()]
155         self.conn.create_consumer(self.topic, self.endpoints,     
156                                   fanout=False)
157         return self.conn.consume_in_threads()
2.4 plugin的callback机制

Callback System 与 RPC 一样是为了实现通信,不同之处在于:RPC 是为了实现 neutron-server 与 agent 不同服务进程之间的任务消息传递;Callback System 是为了实现 core and service components 之间的、同一进程内部的通信,传递 Resource 的 Lifecycle Events(e.g. before creation, before deletion, etc.),让不同的 Core 和 Services 之间、不同的 Services 之间可以感知到特定 Resource 的状态变化。

举例说明 Callback System 的作用:Service A, B, and C 都需要知道 router creation event。如果没有一个中介来采用消息的方式通知这些 Services,那么,A 在执行 router creation 的时候就需要直接 call B/C,告知他们:“我要创建路由器”。但如果有了中介 X(Callback System),那么执行的流程就会变成:

  1. B 和 C 向 X 订阅了 A create router 的 event
  2. 当 A 完成了 created router
  3. A calls X(A 具有 X 的调用句柄)
  4. X 就会将 A created router 的 event 通知 B 和 C(X -> notify)

整个过程中间 A、B、C 三者没有直接通信,实现了 A、B、C(Services)之间的解耦。这就是所谓的 Callback(回调)。

上一个官方实例:

from neutron_lib.callbacks import events
from neutron_lib.callbacks import resources
from neutron_lib.callbacks import registry


def callback1(resource, event, trigger, payload):
    print('Callback1 called by trigger: ', trigger)
    print('payload: ', payload)

def callback2(resource, event, trigger, payload):
    print('Callback2 called by trigger: ', trigger)
    print('payload: ', payload)

def callbackhighproirity(resource, event, trigger, payload):
    print("Prepared data for entities")

# A is using event in case for some callback or internal operations
registry.subscribe(callbackhighpriority, resources.ROUTER,
                   events.BEFORE_CREATE, priority=0)

# B and C express interest with I
registry.subscribe(callback1, resources.ROUTER, events.BEFORE_CREATE)
registry.subscribe(callback2, resources.ROUTER, events.BEFORE_CREATE)
print('Subscribed')


# A notifies
def do_notify():
    registry.publish(resources.ROUTER, events.BEFORE_CREATE,
                     do_notify, events.EventPayload(None))


print('Notifying...')
do_notify()

运行结果为:

> Subscribed
> Notifying...
> callbackhighpriority called by trigger: <function do_notify at 0x7f2a5d663410>
> payload: <neutron_lib._callbacks.events.EventPayload object at 0x7ff9ed253510>
> Callback2 called by trigger:  <function do_notify at 0x7f2a5d663410>
> payload: <neutron_lib._callbacks.events.EventPayload object at 0x7ff9ed253510>
> Callback1 called by trigger:  <function do_notify at 0x7f2a5d663410>
> payload: <neutron_lib._callbacks.events.EventPayload object at 0x7ff9ed253510>

更多内容可以查看官方文档https://docs.openstack.org/neutron-lib/latest/contributor/callbacks.html


三、plugin和agent之间的通信

依然以l3_router_plugin为例子,看删除路由这个操作是怎么从plugin走到agent的。

1967     def delete_router(self, context, id):
1968         super(L3_NAT_db_mixin, self).delete_router(context, id)
1969         self.notify_router_deleted(context, id)

插件先是调用其父类的delete_router来完成一些必要的业务操作,删除数据库等,接着就调用rpc消息来通知agent.

1947     def notify_router_deleted(self, context, router_id):
1948         self.l3_rpc_notifier.router_deleted(context, router_id)

neutron/neutron/api/rpc/agentnotifiers/l3_rpc_agent_api.py

132     def router_deleted(self, context, router_id):
133         self._notification_fanout(context, 'router_deleted', router_id)

105     def _notification_fanout(self, context, method, router_id=None, **kwargs):
106         """Fanout the information to all L3 agents.
107 
108         This function will fanout the router_id or ext_net_id
109         to the L3 Agents.
110         """
111         ext_net_id = kwargs.get('ext_net_id')
112         if router_id:
113             kwargs['router_id'] = router_id
114             LOG.debug('Fanout notify agent at %(topic)s the message '
115                       '%(method)s on router %(router_id)s',
116                       {'topic': topics.L3_AGENT,
117                        'method': method,
118                        'router_id': router_id})
119         if ext_net_id:
120             LOG.debug('Fanout notify agent at %(topic)s the message '
121                       '%(method)s for external_network  %(ext_net_id)s',
122                       {'topic': topics.L3_AGENT,
123                        'method': method,
124                        'ext_net_id': ext_net_id})
125         cctxt = self.client.prepare(fanout=True)
126         cctxt.cast(context, method, **kwargs)

可以看到插件最终是通过cctxt.cast方法来发送rpc消息,消息的主题在插件启动时指定的rpc消息接口api的构造函数中就已经确定为topics.L3_AGENT

 40     def __init__(self, topic=topics.L3_AGENT):
 41         target = oslo_messaging.Target(topic=topic, version='1.0')
 42         self.client = n_rpc.get_client(target)

消息传递的参数为kwargs,远程调用的方法为method。订阅了该主题的agent就会收到消息,然后根据method去调用自己的函数。


四、如何自己写一个插件并部署到服务器上

前面已经介绍过,插件其实就是一个类,neutron server通过命令空间和类名来加载它。所以我们在写自己的类的时候,命名空间一定要和neutron的原生插件保持一致,否则,neutron server无法找到该类,更别说实例化了。查看setup.cfg找到原生插件的命令空间

neutron.service_plugins =
    dummy = neutron.tests.unit.dummy_plugin:DummyServicePlugin
    router = neutron.services.l3_router.l3_router_plugin:L3RouterPlugin

显然,我们在写插件时,也要使用neutron.service_plugins这个命名空间。

另外,作为一个插件有一些接口是必须要提供的,比如下面这段加载插件的代码:

    def _create_and_add_service_plugin(self, provider):
        plugin_inst = self._get_plugin_instance('neutron.service_plugins',
                                                provider)
        plugin_type = plugin_inst.get_plugin_type()
        directory.add_plugin(plugin_type, plugin_inst)

显然,get_plugin_type这个方法是必须的。

所以我们在定义插件类的时候直接继承neutron_lib.services.base.ServicePluginBase这个基础插件类即可。

class MyPlugin(service_base.ServicePluginBase):

    def __init__(self):
        super(MyPlugin, self).__init__()
        LOG.info("Load MyPlugin successfully.")

    @classmethod
    def get_plugin_type(cls):
        return 'my_plugin'

    def get_plugin_description(self):
        return "This is my plugin for test."

接下来我们将该插件部署到服务器上。

首先,编写,setup.py文件,将插件打包。

假如我们的目录结构是这样的:
在这里插入图片描述
那么我们的setup.py文件应该这么写:

from setuptools import setup, find_packages
 
setup(  
    name = "neutron_h3c",  
    version = "0.1",  
    packages = find_packages(),  
  
    description = "plugin",  
    long_description = "plugin",  
    author = "yuhao",  
    author_email = "me.yuhao@outlook.com",  
  
    license = "GPL",  
    keywords = ("plugin", "myself"),  
    platforms = "Independant",  
    url = "http://blog.csdn.net/laplaca/",
    entry_points = {
        'neutron.service_plugins': [
            'myplugin = plugin.my_plugin:MyPlugin',
        ]
    }
)

这里注意命名空间一定要为neutron.service_plugins

最后,修改服务器上的**/etc/neutron/neutron.conf**配置文件,修改插件相关的配置。

[DEFAULT]
core_plugin = ml2
service_plugins = router, firewall_v2, myplugin
allow_overlapping_ips = true

service_plugins变量的最后添加myplugin,即可。当然最后还需要重启neutron server,来让我们的插件加载到服务器上。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值