上一篇完成了APIRouter类中,初始化ExtensionManager实例 ,ext_mgr = self.ExtensionManager()的功能分析,主要就是对于contrib目录下用户自定义的路由文件类加载进来。
接下来继续分析class APIRouter(base_wsgi.Router)类:
mapper = ProjectMapper() 初始化了routes模块里的Mapper类,包含了resource和connect两个路由加载方法。
ProjectMapper继承自APIMapper类。
class ProjectMapper(APIMapper):
def resource(self, member_name, collection_name, **kwargs):
if 'parent_resource' not in kwargs:
kwargs['path_prefix'] = '{project_id}/'
else:
parent_resource = kwargs['parent_resource']
p_collection = parent_resource['collection_name']
p_member = parent_resource['member_name']
kwargs['path_prefix'] = '{project_id}/%s/:%s_id' % (p_collection,
p_member)
routes.Mapper.resource(self, member_name,
collection_name,
**kwargs)
class APIMapper(routes.Mapper):
def routematch(self, url=None, environ=None):
if url == "":
result = self._match("", environ)
return result[0], result[1]
return routes.Mapper.routematch(self, url, environ)
def connect(self, *args, **kargs):
# NOTE(vish): Default the format part of a route to only accept json
# and xml so it doesn't eat all characters after a '.'
# in the url.
kargs.setdefault('requirements', {})
if not kargs['requirements'].get('format'):
kargs['requirements']['format'] = 'json|xml'
return routes.Mapper.connect(self, *args, **kargs)
APIRouter(base_wsgi.Router)继承自nova.api.openstack.compute下的 APIMapper(routes.Mapper)类
方法self._setup_routes(mapper, ext_mgr, init_only),加载openstack核心功能路由
class APIRouter(nova.api.openstack.APIRouter):
"""Routes requests on the OpenStack API to the appropriate controller
and method.
"""
ExtensionManager = extensions.ExtensionManager
#加载openstack的核心路由方法
def _setup_routes(self, mapper, ext_mgr, init_only):
if init_only is None or 'versions' in init_only:
#通过create_resource回调函数,返回wsgi.Resource(Controller(ext_mgr)),放入resources资源字典中
self.resources['versions'] = versions.create_resource()
#mapper.connect方法,建立resources相应资源与REST请求的链接,具体connect的方法详见其他说明
mapper.connect("versions", "/",
controller=self.resources['versions'],
action='show',
conditions={"method": ['GET']})
mapper.redirect("", "/")
if init_only is None or 'consoles' in init_only:
#通过create_resource回调函数,返回wsgi.Resource(Controller(ext_mgr)),放入resources资源字典中
self.resources['consoles'] = consoles.create_resource()
#mapper.resource方法,建立resources相应资源与REST请求的链接,具体resource的方法详见其他说明
mapper.resource("console", "consoles",
controller=self.resources['consoles'],
parent_resource=dict(member_name='server',
collection_name='servers'))
if init_only is None or 'consoles' in init_only or \
'servers' in init_only or 'ips' in init_only:
self.resources['servers'] = servers.create_resource(ext_mgr)
mapper.resource("server", "servers",
controller=self.resources['servers'],
collection={'detail': 'GET'},
member={'action': 'POST'})
if init_only is None or 'ips' in init_only:
self.resources['ips'] = ips.create_resource()
mapper.resource("ip", "ips", controller=self.resources['ips'],
parent_resource=dict(member_name='server',
collection_name='servers'))
if init_only is None or 'images' in init_only:
self.resources['images'] = images.create_resource()
mapper.resource("image", "images",
controller=self.resources['images'],
collection={'detail': 'GET'})
if init_only is None or 'limits' in init_only:
self.resources['limits'] = limits.create_resource()
mapper.resource("limit", "limits",
controller=self.resources['limits'])
if init_only is None or 'flavors' in init_only:
self.resources['flavors'] = flavors.create_resource()
mapper.resource("flavor", "flavors",
controller=self.resources['flavors'],
collection={'detail': 'GET'},
member={'action': 'POST'})
if init_only is None or 'image_metadata' in init_only:
self.resources['image_metadata'] = image_metadata.create_resource()
image_metadata_controller = self.resources['image_metadata']
mapper.resource("image_meta", "metadata",
controller=image_metadata_controller,
parent_resource=dict(member_name='image',
collection_name='images'))
mapper.connect("metadata",
"/{project_id}/images/{image_id}/metadata",
controller=image_metadata_controller,
action='update_all',
conditions={"method": ['PUT']})
if init_only is None or 'server_metadata' in init_only:
self.resources['server_metadata'] = \
server_metadata.create_resource()
server_metadata_controller = self.resources['server_metadata']
mapper.resource("server_meta", "metadata",
controller=server_metadata_controller,
parent_resource=dict(member_name='server',
collection_name='servers'))
mapper.connect("metadata",
"/{project_id}/servers/{server_id}/metadata",
controller=server_metadata_controller,
action='update_all',
conditions={"method": ['PUT']})
继续看APIRouter的下一个方法self._setup_ext_routes(mapper, ext_mgr, init_only),此方法加载了所有contrib目录下一下加载进来的文件路由
重点看一下ExtensionManager类的内容:class ExtensionManager(object):
"""Load extensions from the configured extension path.
See nova/tests/api/openstack/compute/extensions/foxinsocks.py or an
example extension implementation.
"""
def sorted_extensions(self):
if self.sorted_ext_list is None:
self.sorted_ext_list = sorted(self.extensions.iteritems())
for _alias, ext in self.sorted_ext_list:
yield ext
def is_loaded(self, alias):
return alias in self.extensions
def register(self, ext):
# Do nothing if the extension doesn't check out
if not self._check_extension(ext):
return
alias = ext.alias
LOG.audit(_('Loaded extension: %s'), alias)
if alias in self.extensions:
raise exception.NovaException("Found duplicate extension: %s"
% alias)
self.extensions[alias] = ext
self.sorted_ext_list = None
#获取resources方法
def get_resources(self):
"""Returns a list of ResourceExtension objects."""
resources = []
#向resources列表中添加extensions
resources.append(ResourceExtension('extensions',
ExtensionsController(self)))
#sorted_extensions对ext_mgr进行排序后,通过迭代器返回ext实例
for ext in self.sorted_extensions():
try:
#调用每一个路由文件里的get_resources()回调函数,返回路由信息的resources列表extend到resources
resources.extend(ext.get_resources())
except AttributeError:
# NOTE(dprince): Extension aren't required to have resource
# extensions
pass
return resources
def get_controller_extensions(self):
"""Returns a list of ControllerExtension objects."""
controller_exts = []
for ext in self.sorted_extensions():
try:
get_ext_method = ext.get_controller_extensions
except AttributeError:
# NOTE(Vek): Extensions aren't required to have
# controller extensions
continue
controller_exts.extend(get_ext_method())
return controller_exts
def _check_extension(self, extension):
"""Checks for required methods in extension objects."""
try:
LOG.debug('Ext name: %s', extension.name)
LOG.debug('Ext alias: %s', extension.alias)
LOG.debug('Ext description: %s',
' '.join(extension.__doc__.strip().split()))
LOG.debug('Ext namespace: %s', extension.namespace)
LOG.debug('Ext updated: %s', extension.updated)
except AttributeError as ex:
LOG.exception(_("Exception loading extension: %s"), unicode(ex))
return False
return True
def load_extension(self, ext_factory):
"""Execute an extension factory.
Loads an extension. The 'ext_factory' is the name of a
callable that will be imported and called with one
argument--the extension manager. The factory callable is
expected to call the register() method at least once.
"""
LOG.debug("Loading extension %s", ext_factory)
if isinstance(ext_factory, six.string_types):
# Load the factory
factory = importutils.import_class(ext_factory)
else:
factory = ext_factory
# Call it
LOG.debug("Calling extension factory %s", ext_factory)
factory(self)
def _load_extensions(self):
"""Load extensions specified on the command line."""
extensions = list(self.cls_list)
for ext_factory in extensions:
try:
self.load_extension(ext_factory)
except Exception as exc:
LOG.warn(_LW('Failed to load extension %(ext_factory)s: '
'%(exc)s'),
{'ext_factory': ext_factory, 'exc': exc})
_setup_ext_routes方法加载了contrib目录下所有的路由文件,下面举一个volumes.py文件的例子,看看具体内容:
class Volumes(extensions.ExtensionDescriptor):
"""Volumes support."""
name = "Volumes"
alias = "os-volumes"
namespace = "http://docs.openstack.org/compute/ext/volumes/api/v1.1"
updated = "2011-03-25T00:00:00Z"
def get_resources(self):
resources = []
# NOTE(justinsb): No way to provide singular name ('volume')
# Does this matter?
res = extensions.ResourceExtension('os-volumes',
VolumeController(),
collection_actions={'detail': 'GET'})
resources.append(res)
attachment_controller = VolumeAttachmentController(self.ext_mgr)
res = extensions.ResourceExtension('os-volume_attachments',
attachment_controller,
parent=dict(
member_name='server',
collection_name='servers'))
resources.append(res)
res = extensions.ResourceExtension('os-volumes_boot',
inherits='servers')
resources.append(res)
res = extensions.ResourceExtension('os-snapshots',
SnapshotController(),
collection_actions={'detail': 'GET'})
resources.append(res)
return resources
以上内容基本分析完WSGIService的load_app方法,来加载路由信息的代码内容,下面继续分析,nova-api代码中启动WSGIService的代码