Openstack Nova-M V2API启动与extension加载
标签(空格分隔): Openstack
在M版中,nova的v2 API默认是使用通过兼容性包装的v21 API。本文只是代码随笔,结合代码阅读时效果更佳。
nova-api 入口 nova.cmd.api.main()
def main():
***
launcher = service.process_launcher()
started = 0
for api in CONF.enabled_apis:
should_use_ssl = api in CONF.enabled_ssl_apis
try:
server = service.WSGIService(api, use_ssl=should_use_ssl)
launcher.launch_service(server, workers=server.workers or 1)
started += 1
except exception.PasteAppNotFound as ex:
log.warning(
_LW("%s. ``enabled_apis`` includes bad values. "
"Fix to remove this warning."), six.text_type(ex))
if started == 0:
log.error(_LE('No APIs were started. '
'Check the enabled_apis config option.'))
sys.exit(1)
launcher.wait()
来看server = service.WSGIService(api, use_ssl=should_use_ssl)
,其中以 api = osapi_compute 为例,来看service.WSGIService类:
class WSGIService(service.Service):
"""Provides ability to launch API from a 'paste' configuration."""
def __init__(self, name, loader=None, use_ssl=False, max_url_len=None):
"""Initialize, but do not start the WSGI server.
:param name: The name of the WSGI server given to the loader.
:param loader: Loads the WSGI application using the given name.
:returns: None
"""
self.name = name
# NOTE(danms): Name can be metadata, os_compute, or ec2, per
# nova.service's enabled_apis
# nova-osapi-compute
self.binary = 'nova-%s' % name
self.topic = None
self.manager = self._get_manager()
self.loader = loader or wsgi.Loader()
self.app = self.loader.load_app(name)
# inherit all compute_api worker counts from osapi_compute
if name.startswith('openstack_compute_api'):
wname = 'osapi_compute'
else:
wname = name
self.host = getattr(CONF, '%s_listen' % name, "0.0.0.0")
self.port = getattr(CONF, '%s_listen_port' % name, 0)
self.workers = (getattr(CONF, '%s_workers' % wname, None) or
processutils.get_worker_count())
if self.workers and self.workers < 1:
worker_name = '%s_workers' % name
msg = (_("%(worker_name)s value of %(workers)s is invalid, "
"must be greater than 0") %
{'worker_name': worker_name,
'workers': str(self.workers)})
raise exception.InvalidInput(msg)
self.use_ssl = use_ssl
self.server = wsgi.Server(name,
self.app,
host=self.host,
port=self.port,
use_ssl=self.use_ssl,
max_url_len=max_url_len)
# Pull back actual port used
self.port = self.server.port
self.backdoor_port = None
来看self.app = self.loader.load_app(name)
,此处loader为wsgi模块中Loader的实例,初始化时提供了self.config_path,为api-paster.ini的路径,来看load_app方法:
def load_app(self, name):
"""Return the paste URLMap wrapped WSGI application.
:param name: Name of the application to load.
:returns: Paste URLMap object wrapping the requested application.
:raises: `nova.exception.PasteAppNotFound`
"""
try:
LOG.debug("Loading app %(name)s from %(path)s",
{'name': name, 'path': self.config_path})
return deploy.loadapp("config:%s" % self.config_path, name=name)
except LookupError:
LOG.exception(_LE("Couldn't lookup app: %s"), name)
raise exception.PasteAppNotFound(name=name, path=self.config_path)
这个方法调用了pastedeploy模块的deploy.loadapp方法,并提供了两个参数: 加了config:前缀的api-paste.ini文件的路径以及
name = osapi_compute
下面给出M版自带api-paste.ini模板中相关部分:
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v2: openstack_compute_api_v21_legacy_v2_compatible
/v2.1: openstack_compute_api_v21
[composite:openstack_compute_api_v21_legacy_v2_compatible]
use = call:nova.api.auth:pipeline_factory_v21
noauth2 = cors compute_req_id faultwrap sizelimit noauth2 legacy_v2_compatible osapi_compute_app_v21
keystone = cors compute_req_id faultwrap sizelimit authtoken keystonecontext legacy_v2_compatible osapi_compute_app_v21
[filter:legacy_v2_compatible]
paste.filter_factory = nova.api.openstack:LegacyV2CompatibleWrapper.factory
[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory
具体paste.deploy原理大家有兴趣可以去官方文档查看详细手册,这里只简单介绍一些简单具体的实现,先看用于处理osapi_compute的nova.api.openstack.urlmap.urlmap_factory方法:
def urlmap_factory(loader, global_conf, **local_conf):
if 'not_found_app' in local_conf:
not_found_app = local_conf.pop('not_found_app')
else:
not_found_app = global_conf.get('not_found_app')
if not_found_app:
not_found_app = loader.get_app(not_found_app, global_conf=global_conf)
urlmap = URLMap(not_found_app=not_found_app)
for path, app_name in local_conf.items():
path = paste.urlmap.parse_path_expression(path)
app = loader.get_app(app_name, global_conf=global_conf)
urlmap[path] = app
return urlmap
其中抓取日志,可以看到如下参数:
global_conf = {'__file__': '/etc/nova/api-paste.ini', 'here': '/etc/nova'}
local_conf = {'/v2': 'openstack_compute_api_v21_legacy_v2_compatible', '/': 'oscomputeversions', '/v2.1': 'openstack_compute_api_v21'}
此处loader仍为之前创建的loader实例,我们在此只关注v2的api相关信息,看如下代码:
for path, app_name in local_conf.items():
path = paste.urlmap.parse_path_expression(path)
app = loader.get_app(app_name, global_conf=global_conf)
urlmap[path] = app
当local_conf.items()循环到key=‘/v2’时,path经过paste.urlmap.parse_path_expression方法处理后仍然为
‘/v2’,app_name = ‘openstack_compute_api_v21_legacy_v2_compatible’
此时注意运行至app = loader.get_app(app_name, global_conf=global_conf)
这行代码时,deploy内部代码逻辑会使用nova.api.auth.pipeline_factory_v21方法:
def pipeline_factory_v21(loader, global_conf, **local_conf):
"""A paste pipeline replica that keys off of auth_strategy."""
return _load_pipeline(loader, local_conf[CONF.auth_strategy].split())
再来看此时的参数:
global_conf = {'__file__': '/etc/nova/api-paste.ini', 'here': '/etc/nova'}
local_conf = {'keystone': 'cors compute_req_id faultwrap sizelimit authtoken keystonecontext legacy_v2_compatible osapi_compute_app_v21', 'noauth2': 'cors compute_req_id faultwrap sizelimit noauth2 legacy_v2_compatible osapi_compute_app_v21'}
这时,通过CONF.auth_strategy可以指定所使用的auth策略。在此我们选择简单的noauth2策略来向下进行,进入_load_pipeline方法:
def _load_pipeline(loader, pipeline):
filters = [loader.get_filter(n