资料
- ryu官方文档
- ryu的python文档
- ryubook
- sdnlab
- sdnlab是比较好的中文资料来源,里面有很多源码解读、操作使用上的文章
起因
- 在学校做网络安全或者软件定义网络等领域的研究时,并不太关心所使用的sdn控制器的水平扩展、对数据中心的支持等
- 更多的场景就是几台甚至一台主机的ovs上对理论模型进行实验仿真,得到仿真结果后就可以写在论文里了
- 我这次遇到的场景是需要在软件交换机ovs中做802.1x认证者的逻辑,不涉及什么创新
- 但还是想把过程中对ryu的学习进行总结,后面如果再用的话,争取现在留下的文章能够让我快速重新捡起来
- 本篇初探主要内容是ryu主线源码解读,ryu的设计很优美,但如果不去读源码的话,很多约定会觉得像魔法
- 此外ryu并发模型使用的协程,导致不读源码的话,编写的app如果使用多线程,调试的时候也可能会一头雾水
功能
- 在读ryu源码前,可以想象一下sdn控制器要做的事情都有哪些
- 带着ryu如何实现这些功能的疑问去读源码会更加清晰
- 我认为的几个主要的功能点:
- 通过openflow协议管理多个交换机
- 通过netconf协议管理多个交换机
- 可选支持ovsdb等协议完善控制能力
- 支持用户编写数据包处理逻辑,并提供合适的框架简化代码
- 可以同时管理运行用户编写的多个sdn应用,并使其协作
- 最好能对http restful api提供框架支持
- 统一的网络并发模型管理上述涉及到的网络通信
- 对接交换机
- 对接restapi调用者
- 多应用之间进行内部通信协作
- 通信方式扩展,如unix socket、AMPQ协议等
- 可选实现一些数据中心常见的控制协议,比如bgp
阅读
一级文件目录
╭─root@testdevice ~/ryu ‹master›
╰─# tree -L 1
.
├── CONTRIBUTING.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── bin
├── debian
├── doc
├── etc
├── run_tests.sh
├── ryu
├── setup.cfg
├── setup.py
├── tools
└── tox.ini
- 先来看文件目录,几个rst我们不用关心
- bin文件夹里面是ryu用来启动的可执行脚本
from ryu.cmd.manager import main
main()
- 可以看下ryu-manager启动脚本内容,除了开源许可注释,只有两行,从ryu包的cmd.manager导入了main函数并执行
- 因此我们也就知道了,我们通过
ryu-manager --verbose myapp.py
命令启动的控制器实例,是由cmd.manager.main开始的
- bin文件夹中还有一个ryu可执行脚本,允许通过run的subcommand执行ryu-manager的逻辑
ryu run myapp.py
- 此外ryu命令通过调用yu.cmd路径下的其他函数,实现了
[run|of-config-cli|rpc-cli]
这三个子命令
ryu二级目录
╭─root@testdevice ~/ryu/ryu ‹master›
╰─# tree -L 1
.
├── __init__.py
├── app
├── base
├── cfg.py
├── cmd
├── contrib
├── controller
├── exception.py
├── flags.py
├── hooks.py
├── lib
├── log.py
├── ofproto
├── services
├── tests
├── topology
└── utils.py
10 directories, 7 files
- 可以看到文件内容不少,但都是实现ryu控制器的核心逻辑代码
- 接下来我们继续跟启动的主线
启动主线
ryu/ryu/cmd/manager.py
包导入
19 import os
20 import sys
21
22 from ryu.lib import hub
23 hub.patch(thread=False)
24
25 from ryu import cfg
26
27 import logging
28 from ryu import log
29 log.early_init_log(logging.DEBUG)
30
31 from ryu import flags
32 from ryu import version
33 from ryu.app import wsgi
34 from ryu.base.app_manager import AppManager
35 from ryu.controller import controller
36 from ryu.topology import switches
- 注意在入口文件的第四行就已经通过ryu.lib.hub引入了协程库,并对除线程以外的库进行了patch
- patch即为将目标模块,替换为协程形式的实现,但实现的并不完美,有一些地方会导致bug
- 中间几行导入了配置管理和日志管理模块
- 最后几行引入了
- wsgi:用于提供web服务
- AppManager:管理多app
- controller:openflow控制器,使其能够控制交换机的核心功能
- switches:未知!!!
配置管理
37
38
39 CONF = cfg.CONF
40 CONF.register_cli_opts([
41 cfg.ListOpt('app-lists', default=[],
42 help='application module name to run'),
43 cfg.MultiStrOpt('app', positional=True, default=[],
44 help='application module name to run'),
45 cfg.StrOpt('pid-file', default=None, help='pid file name'),
46 cfg.BoolOpt('enable-debugger', default=False,
47 help='don\'t overwrite Python standard threading library'
48 '(use only for debugging)'),
49 cfg.StrOpt('user-flags', default=None,
50 help='Additional flags file for user applications'),
51 ])
52
53
54 def _parse_user_flags():
55 """
56 Parses user-flags file and loads it to register user defined options.
57 """
58 try:
59 idx = list(sys.argv).index('--user-flags')
60 user_flags_file = sys.argv[idx + 1]
61 except (ValueError, IndexError):
62 user_flags_file = ''
63
64 if user_flags_file and os.path.isfile(user_flags_file):
65 from ryu.utils import _import_module_file
66 _import_module_file(user_flags_file)
67
68
69 def main(args=None, prog=None):
70 _parse_user_flags()
71 try:
72 CONF(args=args, prog=prog,
73 project='ryu', version='ryu-manager %s' % version,
74 default_config_files=['/usr/local/etc/ryu/ryu.conf'])
75 except cfg.ConfigFilesNotFoundError:
76 CONF(args=args, prog=prog,
77 project='ryu', version='ryu-manager %s' % version)
78
79 log.init_log()
80 logger = logging.getLogger(__name__)
81
- 配置管理和日志管理不是本篇重点,就不赘述
- 思考了一下还是把完整的内容贴在这里,让以后的我和读者有个直观的感受
- 因为我在读其他人的分析源码的的文章的时候,经常出现的疑问就是,这段代码在哪?
核心逻辑
82 if CONF.enable_debugger:
83 msg = 'debugging is available (--enable-debugger option is turned on)'
84 logger.info(msg)
85 else:
86 hub.patch(thread=True)
87
88 if CONF.pid_file:
89 with open(CONF.pid_file, 'w') as pid_file:
90 pid_file.write(str(os.getpid()))
91
92 app_lists = CONF.app_lists + CONF.app
93 # keep old behavior, run ofp if no application is specified.
94 if not app_lists:
95 app_lists = ['ryu.controller.ofp_handler']
96
97 app_mgr = AppManager.get_instance()
98 app_mgr.load_apps(app_lists)
99 contexts = app_mgr.create_contexts()
100 services = []
101 services.extend(app_mgr.instantiate_apps(**contexts))
102
103 webapp = wsgi.start_service(app_mgr)
104 if webapp:
105 thr = hub.spawn(webapp)
106 services.append(thr)
107
108 try:
109 hub.joinall(services)
110 except KeyboardInterrupt:
111 logger.debug("Keyboard Interrupt received. "
112 "Closing RYU application manager...")
113 finally:
114 app_mgr.close()
115
116
117 if __name__ == "__main__":
118 main()
- 首先一个比较有意思的是82行会对是否启动debugger调试模式进行判断,如果启动了的话,就不会对线程进行协程patch
- 因此如果我们写的app代码调用的库不得不调用多线程时,可以尝试开启debugger,看情况是否有所变化
- 接下来92到95行,读取配置文件或命令行中指定的app列表,如果没有指定,那么会运行ryu.controller.ofp_handler
- ryu.controller.ofp_handler就是控制器与交换机进行openflow协议交互的核心app
- 那么如果我们指定了自己的app,就不会运行ryu.controller.ofp_handler这样核心的逻辑了吗?
- 后面我们会发现,就算app_lists不是空,我们依旧会在运行自己指定app的同时,运行起来ryu.controller.ofp_handler
- 这就涉及到了ryu对app的依赖管理和导入机制的设计,如果不看源码可能会很膜法
- 下面97行,get_instance可以猜个大概app_manager使用的单例模式
- 98行,调用manager的load_apps函数把要运行的app的py代码都动态的加载进内存
- 99行创建并初始化了应用上下文,后面会详细说明
- 这里可以简单理解为我们编写的app以来的其他的库
- ryu会为我们初始化好,并传入供我们的app调用
- context是个字典,注意相同名字的依赖会只初始化一次,可以存在多app都调用同一个context的情况,因此也可作为多app共享内容的方式之一
- 100-101行,services是一个列表,存储了app_manager初始化所有app后返回的内容
- 103-106行,wsgi启动了他自己的服务,
- 如果初始化成功,会通过hub.spawn注册进协程管理
- 并将返回结果append到了services里
- 由此我们可以理解,services里其实都是hub.spawn返回的每个协程的句柄
- 最后的108行开始
- 尝试hub.joinall等待所有的协程实例运行结束
- 如果遇到了ctrl+c,或者遇到异常,无论如何调用app_manager的close方法进行清理
- app_manager作为app们的管理者,同样会调用各app的close方法,完成树状的资源清理
- 92行到114行,这短短的二十多行就是ryu运行控制器核心逻辑的树根,后面的一切逻辑上的树枝,都由此而来
- 其中的逻辑又可主要分为几大块:
- app读取、拉起,涉及到app间的依赖管理
- app对其他类实例的依赖,涉及到app对其他类的依赖
- wsgi对app中涉及restful应用的管理
- 协程贯穿在ryu中的使用
- 接下来我们就对其逐一进行分析
app依赖管理
- 启动主线中的app_manager是从ryu/ryu/base/app_manager.py中实例化的
- 而base这个文件夹中,只有一个app_manager.py,可见其重要性
- 其中不止实现了app管理器,还规定了了我们编写的app需要继承的基类
ryu/ryu/base/app_manager.py
包导入
26 import inspect
27 import itertools
28 import logging
29 import sys
30 import os
31 import gc
32
33 from ryu import cfg
34 from ryu import utils
35 from ryu.app import wsgi
36 from ryu.controller.handler import register_instance, get_dependent_services
37 from ryu.controller.controller import Datapath
38 from ryu.controller import event
39 from ryu.controller.event import EventRequestBase, EventReplyBase
40 from ryu.lib import hub
41 from ryu.ofproto import ofproto_protocol
42
43 LOG = logging.getLogger('ryu.base.app_manager')
44
45 SERVICE_BRICKS = {}
- app_manager引入的内容与刚刚cmd中的manager类似
- 需要注意37行导入了datapath,用于管理网桥的库
- 以及38-39行引入的ryu的事件管理库
- 42行进行了日志配置
- 45行文件全局变量SERVICE_BRICKS这个字典,是一个app名称映射到app实例的字典
- 但为什么不叫app,突然改叫brick,是阅读过程中比较困惑的地方
- 猜测是app在event管理路由这个场景就给他换了个名字
- 后面会多处出现对这个字典的操作
app管理器
- 注意45行后到351行间是app基类与工具方法,此处跳跃进行介绍
351 class AppManager(object):
352 # singleton
353 _instance = None
354
355 @staticmethod
356 def run_apps(app_lists):
357 """Run a set of Ryu applications
358
359 A convenient method to load and instantiate apps.
360 This blocks until all relevant apps stop.
361 """
362 app_mgr = AppManager.get_instance()
363 app_mgr.load_apps(app_lists)
364 contexts = app_mgr.create_contexts()
365 services = app_mgr.instantiate_apps(**contexts)
366 webapp = wsgi.start_service(app_mgr)
367 if webapp:
368 services.append(hub.spawn(webapp))
369 try:
370 hub.joinall(services)
371 finally:
372 app_mgr.close()
373 for t in services:
374 t.kill()
375 hub.joinall(services)
376 gc.collect()
377
378 @staticmethod
379 def get_instance():
380 if not AppManager._instance:
381 AppManager._instance = AppManager()
382 return AppManager._instance
383
384 def __init__(self):
385 self.applications_cls = {}
386 self.applications = {}
387 self.contexts_cls = {}
388 self.contexts = {}
389 self.close_sem = hub.Semaphore()
- 首先看379行即我们看到在上面的代码中调用的实例获取函数,也确实是单实例模式
- 再看384行的初始化函数,不需要传入任何参数,仅对app和context的加载所需的变量进行初始化
- 而这个run_apps函数,与我们在manager.py中的run_apps函数名字都一样,做的事情也一致
- 经过搜索调用此函数的文件后,发现此函数仅在ryu/ryu/cmd/ofa_neutron_agent.py被使用
- 猜测当openstack的neutron插件使用ryu控制器时,会与平时ryu-manager的逻辑有所不同
391 def load_app(self, name):
392 mod = utils.import_module(name)
393 clses = inspect.getmembers(mod,
394 lambda cls: (inspect.isclass(cls) and
395 issubclass(cls, RyuApp) and
396 mod.__name__ ==
397 cls.__module__))
398 if clses:
399 return clses[0][1]
400 return None
401
402 def load_apps(self, app_lists):
403 app_lists = [app for app
404 in itertools.chain.from_iterable(app.split(',')
405 for app in app_lists)]
406 while len(app_lists) > 0:
407 app_cls_name = app_lists.pop(0)
408
409 context_modules = [x.__module__ for x in self.contexts_cls.values()]
410 if app_cls_name in context_modules:
411 continue
412
413 LOG.info('loading app %s', app_cls_name)
414
415 cls = self.load_app(app_cls_name)
416 if cls is None:
417 continue
418
419 self.applications_cls[app_cls_name] = cls
420
421 services = []
422 for key, context_cls in cls.context_iteritems():
423 v = self.contexts_cls.setdefault(key, context_cls)
424 assert v == context_cls
425 context_modules.append(context_cls.__module__)
426
427 if issubclass(context_cls, RyuApp):
428 services.extend(get_dependent_services(context_cls))
429
430 # we can't load an app that will be initiataed for
431 # contexts.
432 for i in get_dependent_services(cls):
433 if i not in context_modules:
434 services.append(i)
435 if services:
436 app_lists.extend([s for s in set(services)
437 if s not in app_lists])
- 接下来就是关键的app加载相关的函数
- 上面的分析我们知道manager.py调用app_manager的load_apps作为加载app的入口函数
- 首先403行对app_list中的内容进行扁平化处理,将逗号分隔的app都拆分成单独的app重新存入列表
- 接下的循环就是加载app类以及处理依赖的逻辑
- 406行,可以看到循环的结束条件是app_list为空
- 而在407行从app_list中取出一个app进行处理
- 一般来说这样使用while 列表不为空来处理列表中的内容,都是列表的内容会随着处理而动态改变
- 而动态变化的原因,正是因为只有加载了一个app类之后,才知道他的依赖是什么,才能将其动态的加入app_list中
- ryu没有强硬的规定依赖关系必须显式的写在配置中或者app类中(除了context)
- 而是通过调用关系,隐含式的给我们实现的类中添加了对应的依赖关系,存疑???
- 这也是ryu实现非常优雅的地方之一
- 接下来409行确认当前处理的app是不是只是一个context而已
- 415行调用load_app加载单个app
- 419行可以看出,app_manager.applications_cls的键是类名,值是类,而不是实例
- 注意此阶段并没有对app类进行实例化,只是记录类的类别
- 421行初始化了一个列表叫services
- 这里的services与上文manage.py中用来hub.joinall的services含义完全不同
- 这里的services仅用于存储当前正在处理的app所依赖的app们
- 422行迭代app类所声明的context
- 如果是context只是一个其他的类,就加入到app_manager的contexts_cls字典中
- 注意同样仅存了类的类别,没有初始化
- 而425行的context_module列表既存储了之前app声明的context,也添加了当前app声明的context
- 427行,如果context中的类同样实现了ryu的app_base,那么把他添加到services里,后续添加到app_list里
- 说明如果context中声明的一个依赖同样实现了ryu的app基类,那么同样将其以app看待进行加载
- 432行的get_dependent_services函数并不在本文件中
- 但可以根据其名称知其含义,即获取本app所依赖的app们
- 后续会对此函数详细解释
- 433行的判断逻辑避免了context中的app类被重复添加至app_list,而维护context_modules变量的意义即如此
- 435行将services变量中的app,即本app所依赖的app,添加到待加载的app_list中去
- 而前面391行的load_app函数,也很简单,利用python动态的特性,从对应对的模块中找继承了app基类的那个用户实现的app类
439 def create_contexts(self):
440 for key, cls in self.contexts_cls.items():
441 if issubclass(cls, RyuApp):
442 # hack for dpset
443 context = self._instantiate(None, cls)
444 else:
445 context = cls()
446 LOG.info('creating context %s', key)
447 assert key not in self.contexts
448 self.contexts[key] = context
449 return self.contexts
- 接下来create_contexts,也是前文manage.py中调用的函数
- 从内容可以看出本函数对context中记录的类进行是初始化
- 将各类的实例以kv的形式存储在app_manager.context中,并最后返回这个字典
- 所以在load_apps中context进行了加载,而本函数进行了context的初始化,生成类的实例
451 def _update_bricks(self):
452 for i in SERVICE_BRICKS.values():
453 for _k, m in inspect.getmembers(i, inspect.ismethod):
454 if not hasattr(m, 'callers'):
455 continue
456 for ev_cls, c in m.callers.items():
457 if not c.ev_source:
458 continue
459
460 brick = _lookup_service_brick_by_mod_name(c.ev_source)
461 if brick:
462 brick.register_observer(ev_cls, i.name,
463 c.dispatchers)
464
465 # allow RyuApp and Event class are in different module
466 for brick in SERVICE_BRICKS.values():
467 if ev_cls in brick._EVENTS:
468 brick.register_observer(ev_cls, i.name,
469 c.dispatchers)
470
471 @staticmethod
472 def _report_brick(name, app):
473 LOG.debug("BRICK %s", name)
474 for ev_cls, list_ in app.observers.items():
475 LOG.debug(" PROVIDES %s TO %s", ev_cls.__name__, list_)
476 for ev_cls in app.event_handlers.keys():
477 LOG.debug(" CONSUMES %s", ev_cls.__name__)
478
479 @staticmethod
480 def report_bricks():
481 for brick, i in SERVICE_BRICKS.items():
482 AppManager._report_brick(brick, i)
483
- 上面的update_brickcs函数涉及到了ryu的一个魔法设计,即caller,后面还会详细说
- 下面的两个report_bricks是用来打印当前控制器所管理的bricks,而这个砖头就是从文件开头的全局变量取出
- 而bricks砖头,对应着我们指定的app,以及我们的app所依赖的app
- 如下是我运行ryu时verbose模式下打印的BRICK内容
- 其中openflow、hostapd_wif、hostapd_eth、pusher是我编写的app
- 而ofp_event对应着我们的app依赖的app:ryu.controller.ofp_handler
- 下面更重要的内容是每个BRICK,即app,消费以及产生的事件类型,对于了解多app情况下的event路由情况非常有帮助
- 其中PROVIDES EventX To {'openflow': {'main'}}中的openflow指的是app名字,main指定是当前控制器所处于的状态disaptcher,如config配置状态,main运行状态
BRICK openflow
CONSUMES EventOFPPacketIn
CONSUMES EventJoin
CONSUMES EventLeave
CONSUMES EventOFPSwitchFeatures
BRICK hostapd_wif
PROVIDES EventJoin TO {'openflow': {'main'}, 'pusher': {'main'}}
PROVIDES EventLeave TO {'openflow': {'main'}, 'pusher': {'main'}}
BRICK hostapd_eth
PROVIDES EventJoin TO {'openflow': {'main'}, 'pusher': {'main'}}
PROVIDES EventLeave TO {'openflow': {'main'}, 'pusher': {'main'}}
BRICK pusher
CONSUMES EventJoin
CONSUMES EventLeave
BRICK ofp_event
PROVIDES EventOFPPacketIn TO {'openflow': {'main'}}
PROVIDES EventOFPSwitchFeatures TO {'openflow': {'config'}}
CONSUMES EventOFPEchoReply
CONSUMES EventOFPEchoRequest
CONSUMES EventOFPErrorMsg
CONSUMES EventOFPHello
CONSUMES EventOFPPortDescStatsReply
CONSUMES EventOFPPortStatus
CONSUMES EventOFPSwitchFeatures
484 def _instantiate(self, app_name, cls, *args, **kwargs):
485 # for now, only single instance of a given module
486 # Do we need to support multiple instances?
487 # Yes, maybe for slicing.
488 LOG.info('instantiating app %s of %s', app_name, cls.__name__)
489
490 if hasattr(cls, 'OFP_VERSIONS') and cls.OFP_VERSIONS is not None:
491 ofproto_protocol.set_app_supported_versions(cls.OFP_VERSIONS)
492
493 if app_name is not None:
494 assert app_name not in self.applications
495 app = cls(*args, **kwargs)
496 register_app(app)
497 assert app.name not in self.applications
498 self.applications[app.name] = app
499 return app
500
501 def instantiate(self, cls, *args, **kwargs):
502 app = self._instantiate(None, cls, *args, **kwargs)
503 self._update_bricks()
504 self._report_brick(app.name, app)
505 return app
506
507 def instantiate_apps(self, *args, **kwargs):
508 for app_name, cls in self.applications_cls.items():
509 self._instantiate(app_name, cls, *args, **kwargs)
510
511 self._update_bricks()
512 self.report_bricks()
513
514 threads = []
515 for app in self.applications.values():
516 t = app.start()
517 if t is not None:
518 app.set_main_thread(t)
519 threads.append(t)
520 return threads
- 接下来是用来进行app初始化的函数
- 首先manager.py调用的app初始化入口函数是instantiate_apps,
- 在本函数里可以看到遍历了app_manager的app class,并调用_instantiate实际初始化
- 后面调用_update_bricks更新app间的事件依赖关系,并通过report打印结果
- 最后如同我们上面分析manager.py中的猜测一样,instantiate_apps函数返回了注册的所有app的协程句柄,用于在main函数中统一join
- 在_instantiate函数中实现了app类的动态的实例化,并在实例化后调用register_app注册,最后在app_manager自身的application字典中注册新初始化的app
522 @staticmethod
523 def _close(app):
524 close_method = getattr(app, 'close', None)
525 if callable(close_method):
526 close_method()
527
528 def uninstantiate(self, name):
529 app = self.applications.pop(name)
530 unregister_app(app)
531 for app_ in SERVICE_BRICKS.values():
532 app_.unregister_observer_all_event(name)
533 app.stop()
534 self._close(app)
535 events = app.events
536 if not events.empty():
537 app.logger.debug('%s events remains %d', app.name, events.qsize())
538
539 def close(self):
540 def close_all(close_dict):
541 for app in close_dict.values():
542 self._close(app)
543 close_dict.clear()
544
545 # This semaphore prevents parallel execution of this function,
546 # as run_apps's finally clause starts another close() call.
547 with self.close_sem:
548 for app_name in list(self.applications.keys()):
549 self.uninstantiate(app_name)
550 assert not self.applications
551 close_all(self.contexts)
- 最后是一些关闭、清理、删除当前app的函数
- app_manager至此就分析结束了
工具方法
48 def lookup_service_brick(name):
49 return SERVICE_BRICKS.get(name)
50
51
52 def _lookup_service_brick_by_ev_cls(ev_cls):
53 return _lookup_service_brick_by_mod_name(ev_cls.__module__)
54
55
56 def _lookup_service_brick_by_mod_name(mod_name):
57 return lookup_service_brick(mod_name.split('.')[-1])
58
59
60 def register_app(app):
61 assert isinstance(app, RyuApp)
62 assert app.name not in SERVICE_BRICKS
63 SERVICE_BRICKS[app.name] = app
64 register_instance(app)
65
66
67 def unregister_app(app):
68 SERVICE_BRICKS.pop(app.name)
69
70
71 def require_app(app_name, api_style=False):
72 """
73 Request the application to be automatically loaded.
74
75 If this is used for "api" style modules, which is imported by a client
76 application, set api_style=True.
77
78 If this is used for client application module, set api_style=False.
79 """
80 iterable = (inspect.getmodule(frame[0]) for frame in inspect.stack())
81 modules = [module for module in iterable if module is not None]
82 if api_style:
83 m = modules[2] # skip a frame for "api" module
84 else:
85 m = modules[1]
86 m._REQUIRED_APP = getattr(m, '_REQUIRED_APP', [])
87 m._REQUIRED_APP.append(app_name)
88 LOG.debug('require_app: %s is required by %s', app_name, m.__name__)
89
90
- 这些方法在app_manager的函数中有所调用
- 可以看出,注册方法对应字典的赋值,删除方法对应字典的删除,搜索方法对应字典的取值
- 读起来SERVICE_BRICKS与app_manager自身的appcalitions没有什么区别
- 可能随着理解的加深能会慢慢了解这样的用意叭
- 最后require_app函数的逻辑是添加依赖app名字到某个module的_REQUIRED_APP列表
app基类
- 由于app在app_manager视角只是一个被管理者,在下文中的事件收发小节中介绍app基类更好,这里暂且跳过
ryu/ryu/controller/handler.py
120 def get_dependent_services(cls):
121 services = []
122 for _k, m in inspect.getmembers(cls, _is_method):
123 if _has_caller(m):
124 for ev_cls, c in m.callers.items():
125 service = getattr(sys.modules[ev_cls.__module__],
126 '_SERVICE_NAME', None)
127 if service:
128 # avoid cls that registers the own events (like
129 # ofp_handler)
130 if cls.__module__ != service:
131 services.append(service)
132
133 m = sys.modules[cls.__module__]
134 services.extend(getattr(m, '_REQUIRED_APP', []))
135 services = list(set(services))
136 return services
- 现在回来填坑上文所调用的获取本app依赖关系的函数get_dependent_services
- 这个函数位于controller/handler.py,原因是因为涉及到了caller这个ryu用来管理事件依赖的魔法
- 简单来说是我们通过set_ev_cls注册监听事件的时候,ryu会通过caller机制计算哪些app会生产此事件,从而计算出当前app所依赖的app有哪些
- 例如我们指定了一个自己编写的app,监听了ofp_event.EventOFPPacketIn这个openflow的packet in事件
- 但这个事件是由ryu.controller.ofp_handler这个app产生的,所以我们的app就对这个app产生了依赖关系
- 很好理解,本app使用了其他app产生了事件,自然我们就对其产生了依赖
- 而ryu正是通过实现了他自己的caller机制,管理这种依赖
- 134行,可以看出通过设置_REQUIRED_APP可以显示的指定我们app所依赖的app
- 前文提过,require_app函数是用于增加_REQUIRED_APP列表内容的方法
- 通过grep搜索require_app函数的调用,可以看出是用于api调用方被调用方之间的依赖指定
- 所以也就好理解require_app的api_style参数了
╭─root@testdevice ~/ryu/ryu ‹master›
╰─# grep -R require_app
app/gui_topology/gui_topology.py:app_manager.require_app('ryu.app.rest_topology')
app/gui_topology/gui_topology.py:app_manager.require_app('ryu.app.ws_topology')
app/gui_topology/gui_topology.py:app_manager.require_app('ryu.app.ofctl_rest')
app/ofctl/api.py:app_manager.require_app('ryu.app.ofctl.service', api_style=True)
base/app_manager.py:def require_app(app_name, api_style=False):
base/app_manager.py: LOG.debug('require_app: %s is required by %s', app_name, m.__name__)
services/protocols/vrrp/api.py:app_manager.require_app('ryu.services.protocols.vrrp.manager', api_style=True)
topology/api.py:app_manager.require_app('ryu.topology.switches', api_style=True)
- 除了get_dependent_services,ryu/ryu/controller/handler.py文件其他的函数或者类的定义,也都是围绕着caller有关
app管理小总结
420
421 services = []
422 for key, context_cls in cls.context_iteritems():
423 v = self.contexts_cls.setdefault(key, context_cls)
424 assert v == context_cls
425 context_modules.append(context_cls.__module__)
426
427 if issubclass(context_cls, RyuApp):
428 services.extend(get_dependent_services(context_cls))
429
430 # we can't load an app that will be initiataed for
431 # contexts.
432 for i in get_dependent_services(cls):
433 if i not in context_modules:
434 services.append(i)
435 if services:
436 app_lists.extend([s for s in set(services)
437 if s not in app_lists])
- 经过阅读后可以看出,整个app依赖管理的核心就在十多行行代码
- app的依赖来源也读到了三种
- 通过context指定(如果context中是一个app的话)
- 通过事件生产消费关系隐式的指定
- 通过app_manager.require_app函数显示指定
- 第一点是在427行进行的判断
- 第二三点,是在432行进行的获取
- 同时会在433行进行避免第一点与第二三点之间依赖重复添加的逻辑
事件管理
- 事件管理正是我们在前文不断提到的caller机制所实现的
ryu/ryu/controller/handler.py
- 前文提到的get_dependent_services函数同样位于本文件中,现在开始分析caller相关机制
包引入
17 import inspect
18 import logging
19 import sys
20
21 LOG = logging.getLogger('ryu.controller.handler')
22
23 # just represent OF datapath state. datapath specific so should be moved.
24 HANDSHAKE_DISPATCHER = "handshake"
25 CONFIG_DISPATCHER = "config"
26 MAIN_DISPATCHER = "main"
27 DEAD_DISPATCHER = "dead"
caller
30 class _Caller(object):
31 """Describe how to handle an event class.
32 """
33
34 def __init__(self, dispatchers, ev_source):
35 """Initialize _Caller.
36
37 :param dispatchers: A list of states or a state, in which this
38 is in effect.
39 None and [] mean all states.
40 :param ev_source: The module which generates the event.
41 ev_cls.__module__ for set_ev_cls.
42 None for set_ev_handler.
43 """
44 self.dispatchers = dispatchers
45 self.ev_source = ev_source
46
47
48 # should be named something like 'observe_event'
49 def set_ev_cls(ev_cls, dispatchers=None):
.....省略文档
77 def _set_ev_cls_dec(handler):
78 if 'callers' not in dir(handler):
79 handler.callers = {}
80 for e in _listify(ev_cls):
81 handler.callers[e] = _Caller(_listify(dispatchers), e.__module__)
82 return handler
83 return _set_ev_cls_dec
84
85
86 def set_ev_handler(ev_cls, dispatchers=None):
87 def _set_ev_cls_dec(handler):
88 if 'callers' not in dir(handler):
89 handler.callers = {}
90 for e in _listify(ev_cls):
91 handler.callers[e] = _Caller(_listify(dispatchers), None)
92 return handler
93 return _set_ev_cls_dec
- 30行见到了caller的真身,只有两个参数阶段和来源,也没有任何方法
- caller可以这样理解:
- 事件有生产者和消费者,当生产者生产了对应的事件后,消费者会触发执行相应的处理函数
- 因此生产者就好像消费者的函数的调用者一样,作为一个caller,去call监听这个事件的那些函数
- 49行见到了我们熟悉的set_ev_cls,他是一个带参数的装饰器
- 带参数的装饰器返回值是装饰器
- 装饰器返回值是一个新的函数
- set_ev_cls这个装饰器的作用很简单
- 78行判断被装饰的函数有没有callers属性
- 80行遍历本需要监听的事件列表,并在函数的caller属性设置对应的kv
- k是事件类型,v是30行的Caller类的实例,其中包含了在哪个运行阶段事件产生后会进行调用,和事件来源的信息
- 86行的set_ev_handler函数与set_ev_cls非常相似,只有一点不同:
- Caller类型的ev_source属性是None
- 那么这个ev_source属性有什么用呢?与自动加载依赖有关,继续看代码会有更多解释
- 于是我们明白了,set_ev_cls(handler)仅仅为我们的自定义函数添加了一个callers变量,记录着有哪些事件产生时,要调用本函数
- 但我们这时候只知道有这样的事件存在,我要监听他,那么谁会产生这样的事件呢?我们回顾一下get_dependent_services函数:
120 def get_dependent_services(cls):
121 services = []
122 for _k, m in inspect.getmembers(cls, _is_method):
123 if _has_caller(m):
124 for ev_cls, c in m.callers.items():
125 service = getattr(sys.modules[ev_cls.__module__],
126 '_SERVICE_NAME', None)
127 if service:
128 # avoid cls that registers the own events (like
129 # ofp_handler)
130 if cls.__module__ != service:
131 services.append(service)
132
133 m = sys.modules[cls.__module__]
134 services.extend(getattr(m, '_REQUIRED_APP', []))
135 services = list(set(services))
136 return services
137
138
139 def register_service(service):
140 """
141 Register the ryu application specified by 'service' as
142 a provider of events defined in the calling module.
143
144 If an application being loaded consumes events (in the sense of
145 set_ev_cls) provided by the 'service' application, the latter
146 application will be automatically loaded.
147
148 This mechanism is used to e.g. automatically start ofp_handler if
149 there are applications consuming OFP events.
150 """
151 frame = inspect.currentframe()
152 m_name = frame.f_back.f_globals['__name__']
153 m = sys.modules[m_name]
154 m._SERVICE_NAME = service
- 这次我们来详细回顾get_dependent_services函数:
- 121行初始化services列表用于记录当前cls所依赖的app
- 122行遍历app的所有函数,查看是否含有caller属性,也就是是否被set_ev_xx修饰过
- 124行遍历当前函数监听的事件
- 125行再次出现service变量名,这里的service表示当前app依赖的app
- 获取依赖app是通过查找事件类所在模块的_SERVICE_NAME变量得到的
- 恰好139行的register_service,本文件最后一个函数,就是用来为模块的_SERVICE_NAME变量赋值的
- 赋值的value是通过参数指定的
- 搜索调用register_service的函数,会发现我们熟悉的ofp_event,而经过查看发现ofp_event.py文件在文件的最末尾处直接调用了这句话
- 也就是说,只要我们import了ofp_event模块,就会自动执行这条register语句,在ofp_event模块自身中植入_SERVICE_NAME变量,指示当前文件中的事件,是由哪个app产生的
- 就解释了我们没有显式指定ofp_handler这个app,他同样会被拉起执行
controller/dpset.py:handler.register_service('ryu.controller.dpset')
controller/handler.py:def register_service(service):
controller/ofp_event.py:handler.register_service('ryu.controller.ofp_handler')
services/protocols/ovsdb/event.py:handler.register_service('ryu.services.protocols.ovsdb.manager')
services/protocols/vrrp/event.py:handler.register_service('ryu.services.protocols.vrrp.manager')
topology/event.py:handler.register_service('ryu.topology.switches')
94
95
96 def _has_caller(meth):
97 return hasattr(meth, 'callers')
98
99
100 def _listify(may_list):
101 if may_list is None:
102 may_list = []
103 if not isinstance(may_list, list):
104 may_list = [may_list]
105 return may_list
106
107
108 def register_instance(i):
109 for _k, m in inspect.getmembers(i, inspect.ismethod):
110 # LOG.debug('instance %s k %s m %s', i, _k, m)
111 if _has_caller(m):
112 for ev_cls, c in m.callers.items():
113 i.register_handler(ev_cls, m)
114
115
116 def _is_method(f):
117 return inspect.isfunction(f) or inspect.ismethod(f)
118
119
- 最后是本文件剩余的部分,除了几个工具函数,还有一个register_instance函数
- 根据传参具有caller属性的方法知道,入参i是一个app,而在113行调用了app的register_handler方法
- 搜索后得知,app_manager会在_instantiate函数,即对app进行初始化时,
- 调用如下的register_app函数,其中调用了我们现在关注的register_instance函数
- 因此在本节事件管理分析完毕后,会接着分析事件路由
def register_app(app):
assert isinstance(app, RyuApp)
assert app.name not in SERVICE_BRICKS
SERVICE_BRICKS[app.name] = app
register_instance(app)
事件管理以及依赖计算小结
- 编写特定协议的event模块时,在文件中执行register_service函数,表明当前模块中的event们是由哪个app产生
- 当我们的app导入了对应的event模块时,就已经执行了register_service
- 当我们的函数使用set_ev_cls修饰,并指定监听特定的event时,修饰器会在这个函数的callers变量中添加相应的监听事件信息
- 在run我们编写的app时,app_manager会使用get_dependent_services函数寻找当前app监听的事件
- 从而找到这个事件所属的event模块,再找到event模块的_SERVICE_NAME变量
- 最终得知如果我要监听在这个事件的话,需要拉起这个app,从而让其产生这样的事件以供我消费
- 如果我们没有在event模块中对事件生产者进行注册,或者使用的是set_ev_handler修饰器,那么ryu也就不会自动导入拉起对应的app了
事件收发
- 事件的收发是多个app在运行阶段,进行的事件接收发送
ryu/ryu/base/app_manager.py
91 class RyuApp(object):
...省略文档
105
106 _CONTEXTS = {}
...省略文档
124
125 _EVENTS = []
...省略文档
132 OFP_VERSIONS = None
...省略文档
146 @classmethod
147 def context_iteritems(cls):
148 """
149 Return iterator over the (key, contxt class) of application context
150 """
151 return iter(cls._CONTEXTS.items())
153 def __init__(self, *_args, **_kwargs):
154 super(RyuApp, self).__init__()
155 self.name = self.__class__.__name__
156 self.event_handlers = {} # ev_cls -> handlers:list
157 self.observers = {} # ev_cls -> observer-name -> states:set
158 self.threads = []
159 self.main_thread = None
160 self.events = hub.Queue(128)
161 self._events_sem = hub.BoundedSemaphore(self.events.maxsize)
162 if hasattr(self.__class__, 'LOGGER_NAME'):
163 self.logger = logging.getLogger(self.__class__.LOGGER_NAME)
164 else:
165 self.logger = logging.getLogger(self.name)
166 self.CONF = cfg.CONF
167
168 # prevent accidental creation of instances of this class outside RyuApp
169 class _EventThreadStop(event.EventBase):
170 pass
171 self._event_stop = _EventThreadStop()
172 self.is_active = True
- 涉及到了app之间的事件收发,就不得不补一下前面剩下还没有分析的RyuAPP的app基类
- app基类中定义了app与app间收、发事件的函数,也定义了app在协程中运行的事件循环
- 在app基类的开头首先定义了类属性_CONTEXT,我们已经很熟悉了,是用来定义本app所依赖的外部类的字典
- 还定义了_EVENTS类属性用来定义本app能够产生的事件,后面还会详细分析
- OFP_VERSIONS是本app工作于的openflow版本
- app基类初始化函数中
- event_handlers变量存储接事件到函数的映射
- observers是对本app产生的事件监听的app们,也就是本app的消费者
- threads是本app产生的多个协程
- main_thread用于main函数join,main_thread退出了才是真推出
- events变量是个队列,用于存储其他app发送来的事件
- 其他变量见名知意
194
195 def register_handler(self, ev_cls, handler):
196 assert callable(handler)
197 self.event_handlers.setdefault(ev_cls, [])
198 self.event_handlers[ev_cls].append(handler)
199
200 def unregister_handler(self, ev_cls, handler):
201 assert callable(handler)
202 self.event_handlers[ev_cls].remove(handler)
203 if not self.event_handlers[ev_cls]:
204 del self.event_handlers[ev_cls]
...省略发送相关函数,以及协程相关函数
229 def get_handlers(self, ev, state=None):
230 """Returns a list of handlers for the specific event.
231
232 :param ev: The event to handle.
233 :param state: The current state. ("dispatcher")
234 If None is given, returns all handlers for the event.
235 Otherwise, returns only handlers that are interested
236 in the specified state.
237 The default is None.
238 """
239 ev_cls = ev.__class__
240 handlers = self.event_handlers.get(ev_cls, [])
241 if state is None:
242 return handlers
243
244 def test(h):
245 if not hasattr(h, 'callers') or ev_cls not in h.callers:
246 # dynamically registered handlers does not have
247 # h.callers element for the event.
248 return True
249 states = h.callers[ev_cls].dispatchers
250 if not states:
251 # empty states means all states
252 return True
253 return state in states
254
255 return filter(test, handlers)
- 接下来分析app基类中事件钩子相关的函数
- 首先我们看到了前文提到的调用链,即instantiate_apps -> _instantiate -> register_app -> register_instance -> register_handler,中的最后一环register_handler
- register_handler做的事情也很简单,把本app中各函数的callers收集起来,整理成一个字典,也就是self.event_handlers
- unregister_handler也就是相应的删除函数了
- 229行get_handlers函数,获取相应ev所对应的handlers,并进行dispatcher阶段的过滤
206 def register_observer(self, ev_cls, name, states=None):
207 states = states or set()
208 ev_cls_observers = self.observers.setdefault(ev_cls, {})
209 ev_cls_observers.setdefault(name, set()).update(states)
210
211 def unregister_observer(self, ev_cls, name):
212 observers = self.observers.get(ev_cls, {})
213 observers.pop(name)
214
215 def unregister_observer_all_event(self, name):
216 for observers in self.observers.values():
217 observers.pop(name, None)
218
219 def observe_event(self, ev_cls, states=None):
220 brick = _lookup_service_brick_by_ev_cls(ev_cls)
221 if brick is not None:
222 brick.register_observer(ev_cls, self.name, states)
223
224 def unobserve_event(self, ev_cls):
225 brick = _lookup_service_brick_by_ev_cls(ev_cls)
226 if brick is not None:
227 brick.unregister_observer(ev_cls, self.name)
...省略其他函数
256
257 def get_observers(self, ev, state):
258 observers = []
259 for k, v in self.observers.get(ev.__class__, {}).items():
260 if not state or not v or state in v:
261 observers.append(k)
262
263 return observers
264
...省略其他函数
320 def send_event_to_observers(self, ev, state=None):
321 """
322 Send the specified event to all observers of this RyuApp.
323 """
324
325 for observer in self.get_observers(ev, state):
326 self.send_event(observer, ev, state)
- 接下来分析app基类中注册事件相关的函数
- self.observers是一个字典,key是本app能够产生的事件类型,value还是一个字典,key是监听这个事件的app名字,value是他指定的监听生效的阶段
- register_observer与unregister_observer函数被其他逻辑调用,从而向本app注册或取消注册事件的目的地
- observe_event和unobserve_event则被本app调用,调用对方app的register_observer,向其他app声明自己的监听
- 320行send_event_to_observers被本app中产生事件的函数所调用,向已经跟本app注册了监听的app们发送事件
265 def send_request(self, req):
266 """
267 Make a synchronous request.
268 Set req.sync to True, send it to a Ryu application specified by
269 req.dst, and block until receiving a reply.
270 Returns the received reply.
271 The argument should be an instance of EventRequestBase.
272 """
273
274 assert isinstance(req, EventRequestBase)
275 req.sync = True
276 req.reply_q = hub.Queue()
277 self.send_event(req.dst, req)
278 # going to sleep for the reply
279 return req.reply_q.get()
...省略其他函数
301 def _send_event(self, ev, state):
302 self._events_sem.acquire()
303 self.events.put((ev, state))
304
305 def send_event(self, name, ev, state=None):
306 """
307 Send the specified event to the RyuApp instance specified by name.
308 """
309
310 if name in SERVICE_BRICKS:
311 if isinstance(ev, EventRequestBase):
312 ev.src = self.name
313 LOG.debug("EVENT %s->%s %s",
314 self.name, name, ev.__class__.__name__)
315 SERVICE_BRICKS[name]._send_event(ev, state)
316 else:
317 LOG.debug("EVENT LOST %s->%s %s",
318 self.name, name, ev.__class__.__name__)
319
320 def send_event_to_observers(self, ev, state=None):
321 """
322 Send the specified event to all observers of this RyuApp.
323 """
324
325 for observer in self.get_observers(ev, state):
326 self.send_event(observer, ev, state)
327
328 def reply_to_request(self, req, rep):
329 """
330 Send a reply for a synchronous request sent by send_request.
331 The first argument should be an instance of EventRequestBase.
332 The second argument should be an instance of EventReplyBase.
333 """
334
335 assert isinstance(req, EventRequestBase)
336 assert isinstance(rep, EventReplyBase)
337 rep.dst = req.src
338 if req.sync:
339 req.reply_q.put(rep)
340 else:
341 self.send_event(rep.dst, rep)
- 接下来分析app基类中事件发送相关的函数
- 305行是刚刚send_event_to_observers中调用的send_event函数
- send_event在设置了event的src后,调用了目的app的_send_event函数
- _send_event只需要在app的events列表中put相应的事件
- 最后265和328行是ryu基于当前even机制实现的同步的请求响应机制
- 请求时发送的事件中包含了同步请求的标识,还包含了取得结果的返回队列
- 响应时只需把向req带来的队列中put返回值即可
174 def start(self):
175 """
176 Hook that is called after startup initialization is done.
177 """
178 self.threads.append(hub.spawn(self._event_loop))
179
180 def stop(self):
181 if self.main_thread:
182 hub.kill(self.main_thread)
183 self.is_active = False
184 self._send_event(self._event_stop, None)
185 hub.joinall(self.threads)
186
187 def set_main_thread(self, thread):
188 """
189 Set self.main_thread so that stop() can terminate it.
190
191 Only AppManager.instantiate_apps should call this function.
192 """
193 self.main_thread = thread
...省略其他事件收发相关函数
281 def _event_loop(self):
282 while self.is_active or not self.events.empty():
283 ev, state = self.events.get()
284 self._events_sem.release()
285 if ev == self._event_stop:
286 continue
287 handlers = self.get_handlers(ev, state)
288 for handler in handlers:
289 try:
290 handler(ev)
291 except hub.TaskExit:
292 # Normal exit.
293 # Propagate upwards, so we leave the event loop.
294 raise
295 except:
296 LOG.exception('%s: Exception occurred during handler processing. '
297 'Backtrace from offending handler '
298 '[%s] servicing event [%s] follows.',
299 self.name, handler.__name__, ev.__class__.__name__)
- 接下来分析app基类中事件接收相关的函数
- 还记得我们在分析app_manager的instantiate_apps函数时,对每个app都调用了start,并将返回值作为协程句柄返回以供main函数join
- 174行可以看到start函数做的事情只有在hub中添加本app的事件循环,并且如果不继承覆盖start函数的话,并不会返回协程句柄
- 180行stop函数则进行相应的kill
- 281行就是本app的事件循环
- 282行可以看出本app处于活跃阶段,并且events队列中有事件时,才会进入循环
- 283行从队列中取出事件,287行从本app的handlers变量取出ev事件对应的事件处理函数
- 最后288行依次执行这个event对应的handler,完成事件的接收工作
451 def _update_bricks(self):
452 for i in SERVICE_BRICKS.values():
453 for _k, m in inspect.getmembers(i, inspect.ismethod):
454 if not hasattr(m, 'callers'):
455 continue
456 for ev_cls, c in m.callers.items():
457 if not c.ev_source:
458 continue
459
460 brick = _lookup_service_brick_by_mod_name(c.ev_source)
461 if brick:
462 brick.register_observer(ev_cls, i.name,
463 c.dispatchers)
464
465 # allow RyuApp and Event class are in different module
466 for brick in SERVICE_BRICKS.values():
467 if ev_cls in brick._EVENTS:
468 brick.register_observer(ev_cls, i.name,
469 c.dispatchers)
- 最后还有一个疑问是,我们知道了handler变量通过怎么样的调用链完成初始化,那么observers呢?
- 前文提到了app_manager中instantiate_apps函数调用了_update_bricks,这里回顾_update_bricks函数,前面我们没有详细分析,现在可以回头再来看一下
- 452行遍历了BRICK,即app
- 453行遍历了app的所有方法
- 454行过滤留下有callers属性,即监听了事件的函数
- 456行过滤掉没有事件来源的事件
- 460行寻找产生这个事件的模块
- 462行调用前文提到的register_observer,向对方app表明自己的存在,声明自己的监听事件和监听阶段
- 最后466行,如果事件的产生者并不在event这个类的模块里,ryu还会寻找所有brick(app)的_EVENTS属性
- 如果app在_EVENTS中声明了自己会产生这个事件的话,那么同样在这个app进行注册
- 这也就填坑了前面没有细说的_EVENTS,就是在这里对其进行了读取使用
- 因此如果我们自己要编写新的event类型,同时event定义与事件产生的app在不同模块,就需要要在_EVENTS指定事件信息
事件收发小结
- app的handler和observers两个变量均在instantiate_apps阶段完成变量的计算和赋值,为后续的事件路由提供信息
- 事件的接收是通过app基类中的事件循环完成的
- 事件的发送是通过本app的某函数主动调用send_event_to_observers等函数实现的
TODO