前文有对neutron-server源码中的WSGI进行分析,总结出neutron-server的最简初始化过程。本文在此基础上对neutron-server中的Pecan继续进行深入介绍。
Pecan是一个基本对象分发风格路由的轻量级Python Web框架,所以相比PPRW框架(Paste,PastDeploy,Routes,WebOb),使用Pecan实现路由分发的neutron-server逻辑更加简洁易懂。一个V2Controller类,就完全表达了neutron-server中用于路由分发的功能,可见使用Pecan的编程效率。
V2Controller类:
class V2Controller(object) @utils.expose(generic=True) def index(self): if not pecan.request.path_url.endswith('/'): pecan.abort(404) layout = [] for name, collection in attributes.CORE_RESOURCES.items(): href = urlparse.urljoin(pecan.request.path_url, collection) resource = {'name': name,'collection': collection,'links': [{'rel': 'self','href': href}]} layout.append(resource) return {'resources': layout} @utils.when(index, method='HEAD') @utils.when(index, method='POST') @utils.when(index, method='PATCH') @utils.when(index, method='PUT') @utils.when(index, method='DELETE') def not_supported(self): pecan.abort(405) @utils.expose() def _lookup(self, collection, *remainder): if (remainder and manager.NeutronManager.get_resources_for_path_prefix(collection)): collection = remainder[0] remainder = remainder[1:] controller = manager.NeutronManager.get_controller_for_resource(collection) if not controller: LOG.warning("No controller found for: %s - returning response code 404", collection) pecan.abort(404) request.context['resource'] = controller.resource request.context['collection'] = controller.collection request.context['uri_identifiers'] = {} return controller, remainder
以上删除了注释和部分代码。路由分发的核心是_lookup。短短十几行代码,已经映射完了核心资源的增删改查路由。不得不说Pecan的简洁,也不得不赞neutron-server重构之后的简洁。下面详细说明Pecan的路由分发用法,然后回头继续介绍Neutron是怎么利用这种特性完成Web端的重构。
Pecan路由
对象属性方式
from pecan import expose class BooksController(object): @expose() def index(self): return "Welcome to book section." @expose() def bestsellers(self): return "We have 5 books in the top 10." class CatalogController(object): @expose() def index(self): return "Welcome to the catalog." books = BooksController() class RootController(object): @expose() def index(self): return "Welcome to store.example.com!" @expose() def hours(self): return "Open 24/7 on the web." catalog = CatalogController()
当浏览器请求/catalog/books/bestsellers地址时,Pecan会分隔请求路径为catalog,books和 bestsellers。然后在根Controller对象中查询catalog属性,接着在catalog对象中继续查找books属性,再接着查询books对象中bestsellers属性,最后调用bestsellers并返回结果。
在这种路由方式中,对象属性名称是URL路径中的一部分。
_lookup方式
_lookup方法是一个用于处理URL的特殊方法。它返回一个conctroller对象和剩下的URL路径。这是一种递归调用方式,直到解析到URL最后一级地址。例如:
from pecan import expose, abort from somelib import get_student_by_name class StudentController(object): def __init__(self, student): self.student = student @expose() def name(self): return self.student.name class RootController(object): @expose() def _lookup(self, primary_key, *remainder): student = get_student_by_primary_key(primary_key) if student: return StudentController(student), remainder else: abort(404)
一个HTTP GET请求/8/name将返回primary_key==8的student名字。
_lookup调用栈如下所示:
controller中lookup可以形成一个先进后出的调用栈,该栈从根controller开始,直到最后一个controller对象。通过阅读neutron-server源码可知,neutron-server中核心资源的lookup栈只有一级,即root controller 。
那么neutron-server核心资源路由又是怎么建立的呢?实际实现非常简单,仅仅使用了一个dict就实现了controller对象的路由。核心代码如下:
controller = manager.NeutronManager.get_controller_for_resource(collection)
manager.NeutronManager缓存了核心资源的controller对象,缓存的dict属性名称是self.resource_controller_mappings。该dict初始化代码如下:
for resource, collection in RESOURCES.items(): resource_registry.register_resource_by_name(resource) new_controller = res_ctrl.CollectionsController(collection, resource, plugin=plugin) manager.NeutronManager.set_controller_for_resource( collection, new_controller)
核心资源的controller根据res_ctrl.CollectionsController类创建。先看看neutron-server定义了哪些核心资源,代码如下:
RESOURCES = {'network': 'networks', 'subnet': 'subnets', 'subnetpool': 'subnetpools', 'port': 'ports'}
核心资源目前有4种,分别是:network,subnet,subnetpool和port。
所以res_ctrl.CollectionsController对象被创建了4个。该controller继承自NeutronPecanController,其中plugin_lister方法用于获取plugin对象中的list方法。
@property def plugin_lister(self): return getattr(self.plugin, self._plugin_handlers[self.LIST])
self._plugin_handlers方法是NeutronPecanController类的构造方法中被初始化的,代码如下:
self._plugin_handlers = { self.LIST: 'get%s_%s' % (parent_resource, self.collection), self.SHOW: 'get%s_%s' % (parent_resource, self.resource) } for action in [self.CREATE, self.UPDATE, self.DELETE]: self._plugin_handlers[action] = '%s%s_%s' % (action, parent_resource, self.resource)
以network资源为例,self._plugin_handlers初始化后的属性如下所示,其它资源依次类推:
{ 'list':'get_networks', 'show':'get_network', 'create':'create_network', 'update':'update_network', 'delete':'delete_network' }
所以,如果是network资源,getattr(self.plugin, self._plugin_handlers[self.LIST])返回的值是'get_networks'字符串。
在self.resource_controller_mappings对象初始化完成之后,如果客户端请求资源,Pecan框架可以直接从缓存中查询出对应资源的controller对象,然后调用controller对象中对应的方法。注意,controller仅仅用于路由,真正提供服务的是plugin对象中的方法。
找到plugin.py文件,在这个文件的Ml2Plugin类或其父类中必然能够搜到以get,show,create,update和delete开头,使用下划线连接资源名称的方法,比如:create_network,create_subnetl和create_port等等。这些方法有些通过RPC的方式调用对应的Agent,以实现逻辑资源到物理资源的映射。到此neutron-server的整个调用流程已十分明朗,剩下的工作是继续分析每个Agent的具体实现,坚持下去必然对整个Neutron了如指掌。