新手,水平较低,很多地方还不是很理解,错漏在所难免,后续会慢慢完善的。

我总结了一下,想要通过读懂源代码来学习一个新技术,大概可以分为三个步骤走:

1.理解相关的类库的功能及使用。

2.理清代码中对象的继承及调用关系。

3.阅读代码了解细节

相关类库

PythonWeb服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)

paste.deploy/webob/routes

oslo

1.wsgi

wsgi简要介绍:http://blog.csdn.net/u012977347/article/details/50490597

2.paste.deploy

官方文档:http://pythonpaste.org/deploy/

不想看官档可以看这个简要介绍:http://blog.csdn.net/u012977347/article/details/50465918

3.webob

官方文档:http://webob.org/

不想看官档可以看这个简要介绍:http://blog.csdn.net/u012977347/article/details/50487623

4.routers

官方文档:http://routes.readthedocs.org/en/latest/

不想看官档可以看这个简要介绍:http://blog.csdn.net/u012977347/article/details/50490392

源码流程分析

1.openstack api服务启动脚本api_os_computer.py

def main():config.parse_args(sys.argv)  #加载命令行参数logging.setup(CONF, "nova")    #加载日志模块utils.monkey_patch() #加载辅助工具objects.register_all()   #不是很确定,应该是nova的对象的关系数据库映射对象类gmr.TextGuruMeditation.setup_autorun(version)#启动wsgi servershould_use_ssl = 'osapi_compute' in CONF.enabled_ssl_apisserver = service.WSGIService('osapi_compute', use_ssl=should_use_ssl)service.serve(server, workers=server.workers)service.wait()

可以看到nova-api服务都是通过nova.service.WSGIService来管理的。

2.nova.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 #wsgi server的名字,在这里是osapi_computeself.manager = self._get_manager() #推测和后台有关,迟点回来看self.loader = loader or wsgi.Loader() #创建一个nova.wsgi.Loader对象,用于加载处理HTTP请求的wsgi app,实现restful接口,后面会继续讨论这个点。self.app = self.loader.load_app(name) #加载app# inherit all compute_api worker counts from osapi_computeif name.startswith('openstack_compute_api'):wname = 'osapi_compute'else:wname = nameself.host = getattr(CONF, '%s_listen' % name, "0.0.0.0") #监听的主机地址self.port = getattr(CONF, '%s_listen_port' % name, 0)    #监听的端口
#推测这里是设置worker的数量,也就是协程的数量,不太确定。self.workers = (getattr(CONF, '%s_workers' % wname, None) orprocessutils.get_worker_count())if self.workers and self.workers < 1:worker_name = '%s_workers' % namemsg = (_("%(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 #是否使用ssl#调用nova.wsgi.Server创建wsgi serverself.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 usedself.port = self.server.portself.backdoor_port = None

nova.service.WSGIService的基类是oslo.service,官网对oslo.service的描述是provides functionality for running OpenStack services.
oslo.service的git地址: http://git.openstack.org/cgit/openstack/oslo.service
下载oslo.service的源码来看,可以知道oslo.service.Service是通过eventlet.greenpool来管理wsgiServer的,也就是协程。

3.nova.wsgi.Loader

class Loader(object):"""Used to load WSGI applications from paste configurations."""def __init__(self, config_path=None):"""Initialize the loader, and attempt to find the config.:param config_path: Full or relative path to the paste config.:returns: None"""self.config_path = Noneconfig_path = config_path or CONF.api_paste_configif not os.path.isabs(config_path):self.config_path = CONF.find_file(config_path)elif os.path.exists(config_path):self.config_path = config_pathif not self.config_path:raise exception.ConfigNotFound(path=config_path)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)

self.loader = loader or wsgi.Loader() #初始化的时候检查.ini配置文件是否存在
这里的name参数为osapi_compute,api-paste.ini文件默认配置如下:
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v1.1: openstack_compute_api_v21_legacy_v2_compatible
/v2: openstack_compute_api_v21_legacy_v2_compatible
/v2.1: openstack_compute_api_v21
self.app = self.loader.load_app(name)的意思是使用deploy来加载wsgi app,osapi_compute调用urlmap_factory工厂函数返回的一个nova.api.openstack.urlmap.URLMap实例。

4.nova.api.openstack.urlmap.URLMap

class URLMap(paste.urlmap.URLMap):def _match(self, host, port, path_info):"""Find longest match for a given URL path."""for (domain, app_url), app in self.applications:if domain and domain != host and domain != host + ':' + port:continueif (path_info == app_urlor path_info.startswith(app_url + '/')):return app, app_urlreturn None, Nonedef _set_script_name(self, app, app_url):def wrap(environ, start_response):environ['SCRIPT_NAME'] += app_urlreturn app(environ, start_response)return wrapdef _munge_path(self, app, path_info, app_url):def wrap(environ, start_response):environ['SCRIPT_NAME'] += app_urlenviron['PATH_INFO'] = path_info[len(app_url):]return app(environ, start_response)return wrapdef _path_strategy(self, host, port, path_info):"""Check path suffix for MIME type and path prefix for API version."""mime_type = app = app_url = Noneparts = path_info.rsplit('.', 1)if len(parts) > 1:possible_type = 'application/' + parts[1]if possible_type in wsgi.get_supported_content_types():mime_type = possible_typeparts = path_info.split('/')if len(parts) > 1:possible_app, possible_app_url = self._match(host, port, path_info)# Don't use prefix if it ends up matching defaultif possible_app and possible_app_url:app_url = possible_app_urlapp = self._munge_path(possible_app, path_info, app_url)return mime_type, app, app_urldef _content_type_strategy(self, host, port, environ):"""Check Content-Type header for API version."""app = Noneparams = parse_options_header(environ.get('CONTENT_TYPE', ''))[1]if 'version' in params:app, app_url = self._match(host, port, '/v' + params['version'])if app:app = self._set_script_name(app, app_url)return appdef _accept_strategy(self, host, port, environ, supported_content_types):"""Check Accept header for best matching MIME type and API version."""accept = Accept(environ.get('HTTP_ACCEPT', ''))app = None# Find the best match in the Accept headermime_type, params = accept.best_match(supported_content_types)if 'version' in params:app, app_url = self._match(host, port, '/v' + params['version'])if app:app = self._set_script_name(app, app_url)return mime_type, appdef __call__(self, environ, start_response):#获取端口号host = environ.get('HTTP_HOST', environ.get('SERVER_NAME')).lower()if ':' in host:host, port = host.split(':', 1)else:if environ['wsgi.url_scheme'] == 'http':port = '80'else:port = '443'#获取path_infopath_info = environ['PATH_INFO']path_info = self.normalize_url(path_info, False)[1]# The MIME type for the response is determined in one of two ways:# 1) URL path suffix (eg /servers/detail.json)# 2) Accept header (eg application/json;q=0.8, application/xml;q=0.2)# The API version is determined in one of three ways:# 1) URL path prefix (eg /v1.1/tenant/servers/detail)# 2) Content-Type header (eg application/json;version=1.1)# 3) Accept header (eg application/json;q=0.8;version=1.1)#获取支持的MIME类型supported_content_types = list(wsgi.get_supported_content_types())#获取请求的mime_type, app, app_urlmime_type, app, app_url = self._path_strategy(host, port, path_info)# Accept application/atom+xml for the index query of each API# version mount point as well as the root indexif (app_url and app_url + '/' == path_info) or path_info == '/':supported_content_types.append('application/atom+xml')#下面这一坨东西大概就是根据SCRIPT_NAME和PATH_INFO来找出应该调用app,并根据api-paste.ini的配置调用下层appif not app:app = self._content_type_strategy(host, port, environ)if not mime_type or not app:possible_mime_type, possible_app = self._accept_strategy(host, port, environ, supported_content_types)if possible_mime_type and not mime_type:mime_type = possible_mime_typeif possible_app and not app:app = possible_appif not mime_type:mime_type = 'application/json'if not app:# Didn't match a particular version, probably matches defaultapp, app_url = self._match(host, port, path_info)if app:app = self._munge_path(app, path_info, app_url)if app:environ['nova.best_content_type'] = mime_typereturn app(environ, start_response)#找不到app的异常处理。LOG.debug('Could not find application for %s', environ['PATH_INFO'])environ['paste.urlmap_object'] = selfreturn self.not_found_application(environ, start_response)

nova.api.openstack.urlmap.URLMap继承paste.urlmap.URLMap,它提供了wsgi调用接口。可以看到URLMap的__call__函数是一个具有environ,及start_response变量的标准wsgi app。其返回值也是一个wsgi app。
paste.urlmap.URLMap实现的功能很简单:根据配置将url映射到特定wsgi app,并根据url的长短作一个优先级排序,url较长的将优先进行匹配。所以/v2将先于/进行匹配。
URLMap在调用下层的wsgi app前,会更新SCRIPT_NAME和PATH_INFO。nova.api.openstack.urlmap.URLMap继承了paste.urlmap.URLMap,并写了一堆代码,其实只是为了实现对请求类型的判断,并设置environ['nova.best_content_type']:
在nova.api.openstack.wsgi中配置支持的mimetype如下:
_SUPPORTED_CONTENT_TYPES = (
    'application/json',
    'application/vnd.openstack.compute+json',
)
如果url的后缀名为json(如/xxxx.json),那么environ['nova.best_content_type']=“application/json”。
如果url没有后缀名,那么将通过HTTP headers的content_type字段中mimetype判断。否则默认environ['nova.best_content_type']=“application/json”。
做完这些之后就调用下层app

经上面配置加载的osapi_compute为一个URLMap实例,wsgi server的接受的HTTP请求将直接交给该实例处理。
它将url为'/v2/.*'的请求将交给openstack_compute_api_v2,url为'/'的请求交给oscomputerversions处理(它直接返回系统版本号)。
其它的url请求,则返回NotFound。下面继续分析openstack_compute_api_v2,其配置如下:
[composite:openstack_compute_api_v21_legacy_v2_compatible]
use = call:nova.api.auth:pipeline_factory_v21
noauth2 = compute_req_id faultwrap sizelimit noauth2 legacy_v2_compatible osapi_compute_app_v21
keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext legacy_v2_compatible osapi_compute_app_v21
filter的基类是nova.wsgi.Middleware,例如这个:
class NovaKeystoneContext(wsgi.Middleware):

5.nova.wsgi.Middleware

class Middleware(Application):"""Base WSGI middleware.These classes require an application to beinitialized that will be called next.  By default the middleware willsimply call its wrapped app, or you can override __call__ to customize itsbehavior."""@classmethoddef factory(cls, global_config, **local_config):"""Used for paste app factories in paste.deploy config files.Any local configuration (that is, values under the [filter:APPNAME]section of the paste config) will be passed into the `__init__` methodas kwargs.A hypothetical configuration would look like:[filter:analytics]redis_host = 127.0.0.1paste.filter_factory = nova.api.analytics:Analytics.factorywhich would result in a call to the `Analytics` class asimport nova.api.analyticsanalytics.Analytics(app_from_paste, redis_host='127.0.0.1')You could of course re-implement the `factory` method in subclasses,but using the kwarg passing it shouldn't be necessary."""def _factory(app):return cls(app, **local_config)return _factorydef __init__(self, application):self.application = applicationdef process_request(self, req):"""Called on each request.If this returns None, the next application down the stack will beexecuted. If it returns a response then that response will be returnedand execution will stop here."""return Nonedef process_response(self, response):"""Do whatever you'd like to the response."""return response@webob.dec.wsgify(RequestClass=Request)def __call__(self, req):response = self.process_request(req)if response:return responseresponse = req.get_response(self.application)return self.process_response(response)

Middleware初始化接收一个wsgi app,在调用wsgi app之前,执行process_request()对请求进行预处理,判断请求是否交给传入的wsgi app,还是直接返回,或者修改些req再给传入的wsgi app处理。
wsgi app返回的response再交给process_response()处理。例如,对于进行验证的逻辑,可以放在process_request中,如果验证通过则继续交给app处理,否则返回“Authentication required”。
不过查看nova所有Mddlerware的编写,似乎都不用这种定义好的结构,而是把处理逻辑都放到__call__中,这样导致__call__变得复杂,代码不够整洁。
对于FaultWrapper尚可理解,毕竟需要捕获wsgi app处理异常嘛,但其它的Middleware就不应该了。这可能是不同程序员写,规范就忽略了。
当auth_strategy=“keystone”时,openstack_compute_api_v2=FaultWrapper(RequestBodySizeLimiter(auth_token(NovaKeystoneContext(RateLimitingMiddleware(osapi_compute_app_v2)))))。所以HTTP请求需要经过五个Middleware的处理,才能到达osapi_compute_app_v2。这五个Middleware分别完成:
1)异常捕获,防止服务内部处理异常导致wsgi server挂掉;
2)限制HTTP请求body大小,对于太大的body,将直接返回BadRequest;
3)对请求keystone对header中token id进行验证;
4)利用headers初始化一个nova.context.RequestContext实例,并赋给req.environ['nova.context'];
5)限制用户的访问速度。

当HTTP请经过上面五个Middlerware处理后,最终交给osapi_compute_app_v21:
[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory

6.nova.api.openstack.computer.APIRouterV21

class APIRouterV21(nova.api.openstack.APIRouterV21):"""Routes requests on the OpenStack API to the appropriate controllerand method."""def __init__(self, init_only=None):self._loaded_extension_info = extension_info.LoadedExtensionInfo()#调用父类的__init__()super(APIRouterV21, self).__init__(init_only)def _register_extension(self, ext):return self.loaded_extension_info.register_extension(ext.obj)@propertydef loaded_extension_info(self):return self._loaded_extension_info

其继承关系为nova.api.openstack.computer.APIRouterV21->nova.api.openstack.APIRouterV21->nova.wsgi.Router,继续查看其父类的实现。

7.nova.api.openstack.APIRouterV2
class APIRouterV21(base_wsgi.Router):"""Routes requests on the OpenStack v2.1 API to the appropriate controllerand method."""@classmethoddef factory(cls, global_config, **local_config):"""Simple paste factory, :class:`nova.wsgi.Router` doesn't have one."""return cls()@staticmethoddef api_extension_namespace():return 'nova.api.v21.extensions'def __init__(self, init_only=None, v3mode=False):# TODO(cyeoh): bp v3-api-extension-framework. Currently load# all extensions but eventually should be able to exclude# based on a config file# TODO(oomichi): We can remove v3mode argument after moving all v3 APIs# to v2.1.#Function to determine which extensions to load,stevedore加载插件的检查函数#检查ext是否extensions.V21APIExtensionBase对象#检查是否在黑名单中def _check_load_extension(ext):  Function to determine which extensions to loadif (self.init_only is None or ext.obj.alias inself.init_only) and isinstance(ext.obj,extensions.V21APIExtensionBase):# Check whitelist is either empty or if not then the extension# is in the whitelistif (not CONF.osapi_v21.extensions_whitelist orext.obj.alias in CONF.osapi_v21.extensions_whitelist):# Check the extension is not in the blacklistblacklist = CONF.osapi_v21.extensions_blacklistif ext.obj.alias not in blacklist:return self._register_extension(ext)return False#检查是否开启osapi_v21 apiif not CONF.osapi_v21.enabled:LOG.info(_LI("V2.1 API has been disabled by configuration"))LOG.warning(_LW("In the M release you must run the v2.1 API."))returnif (CONF.osapi_v21.extensions_blacklist orCONF.osapi_v21.extensions_whitelist):LOG.warning(_LW('In the M release you must run all of the API. ''The concept of API extensions will be removed from ''the codebase to ensure there is a single Compute API.'))self.init_only = init_onlyLOG.debug("v21 API Extension Blacklist: %s",CONF.osapi_v21.extensions_blacklist)LOG.debug("v21 API Extension Whitelist: %s",CONF.osapi_v21.extensions_whitelist)in_blacklist_and_whitelist = set(CONF.osapi_v21.extensions_whitelist).intersection(CONF.osapi_v21.extensions_blacklist)if len(in_blacklist_and_whitelist) != 0:LOG.warning(_LW("Extensions in both blacklist and whitelist: %s"),list(in_blacklist_and_whitelist))#使用stevedore通过插件方式导入apiself.api_extension_manager = stevedore.enabled.EnabledExtensionManager(namespace=self.api_extension_namespace(),   #插件的namespace,即是nova.api.v21.extensionscheck_func=_check_load_extension,  #Function to determine which extensions to load,invoke_on_load=True,   #Boolean controlling whether to invoke the object returned by the entry point after the driver is loaded.invoke_kwds={"extension_info": self.loaded_extension_info})# Named arguments to pass when invoking the object returned by the entry point. Only used if invoke_on_load is True.#self.loaded_extension_info是一个抽象类方法,必须在子类中对其定义。#判断mapper变量是PlainMapper(),还是ProjectMapper()#mapper做的东西就是:1.通过routes.Mapper.resource来实现REST ful API,2.给plugin传参if v3mode:mapper = PlainMapper()else:mapper = ProjectMapper()self.resources = {}# NOTE(cyeoh) Core API support is rewritten as extensions# but conceptually still have coreif list(self.api_extension_manager):# NOTE(cyeoh): Stevedore raises an exception if there are# no plugins detected. I wonder if this is a bug.#扩展资源加载,见第8条self._register_resources_check_inherits(mapper)#使用stevedore.ExtensionManager.map()执行该扩展的方法,返回值是一个response列表#stevedore.ExtensionManager.map()的func参数的解析是:Callable to invoke for each extension#stevedore.ExtensionManager.map()返回一个self._register_controllers的列表#关于self._register_controllers转第11条self.api_extension_manager.map(self._register_controllers)missing_core_extensions = self.get_missing_core_extensions(self.loaded_extension_info.get_extensions().keys())if not self.init_only and missing_core_extensions:LOG.critical(_LC("Missing core API extensions: %s"),missing_core_extensions)raise exception.CoreAPIMissing(missing_apis=missing_core_extensions)LOG.info(_LI("Loaded extensions: %s"),sorted(self.loaded_extension_info.get_extensions().keys()))super(APIRouterV21, self).__init__(mapper)

这是加载nova扩展的核心代码,首先介绍一下相关的基础概念:controller,extension,rosource

controller,包含一个或多个extension,通过列表属性extensions描述。
extension,包含一个或多个rosource,是nova.api.openstack.extensions.ExtensionDescriptor实例,通过其get_resources成员函数创建resources列表描述extension包含的rosource成员。
rosource,基类为nova.api.openstack.wsgi.Resource,里面包含两个重要的属性:wsgi_actions,wsgi_extensions
controller,extension,rosource的层次关系就是:controlle - 包含一个或多个 - extension - 包含一个或多个 - rosource。
APIRouterV21中有两个关键步骤:
self._register_resources_check_inherits(mapper),详见第8步。
self.api_extension_manager.map(self._register_controllers)
stevedore.ExtensionManager.map()的func参数的解析是:Callable to invoke for each extension
使用stevedore.ExtensionManager.map()执行该扩展的方法,返回值是一个response列表,关于self._register_controllers转第11条。
8.nova.api.openstack.APIRouterV2._register_resources_check_inherits
    def _register_resources_check_inherits(self, mapper):ext_has_inherits = []ext_no_inherits = []for ext in self.api_extension_manager:for resource in ext.obj.get_resources():if resource.inherits:ext_has_inherits.append(ext)breakelse:ext_no_inherits.append(ext)self._register_resources_list(ext_no_inherits, mapper)self._register_resources_list(ext_has_inherits, mapper)

根据resource的继承属性对resource分开加载,这里看得不是很懂这样做的意图。


9.nova.api.openstack.APIRouterV2._register_resources
    def _register_resources_list(self, ext_list, mapper):for ext in ext_list:self._register_resources(ext, mapper)def _register_resources(self, ext, mapper):"""Register resources defined by the extensionsExtensions define what resources they want to add through aget_resources function"""handler = ext.objLOG.debug("Running _register_resources on %s", ext.obj)#调用扩展中的get_resources方法获取资源列表,扩展和资源的关系,就是一个扩展里面包含一个或多个资源。#每个资源就是一个wsgi applicationfor resource in handler.get_resources():LOG.debug('Extended resource: %s', resource.collection)"""inherits这个属性是什么意思?nova.api.wsgi.Resource有解析:param inherits: another resource object that this resource shouldinherit extensions from. Any action extensions thatare applied to the parent resource will also applyto this resource."""inherits = None#判断是否是继承的resource,如果是继承的resource且没有controller,则其controller为其父类的controllerif resource.inherits:inherits = self.resources.get(resource.inherits)if not resource.controller:resource.controller = inherits.controllerwsgi_resource = wsgi.ResourceV21(resource.controller,inherits=inherits)#把该wsgi app添加到self.resources数组,后面的nova.api.openstack.APIRouterV2._register_controllers会根据这个数组加载self.resources[resource.collection] = wsgi_resourcekargs = dict(controller=wsgi_resource,collection=resource.collection_actions,member=resource.member_actions)if resource.parent:kargs['parent_resource'] = resource.parent# non core-API plugins use the collection name as the# member name, but the core-API plugins use the# singular/plural convention for member/collection namesif resource.member_name:member_name = resource.member_nameelse:member_name = resource.collection#使用mapper加载wsgi app,关于mapper的构造转到第10条mapper.resource(member_name, resource.collection,**kargs)if resource.custom_routes_fn:resource.custom_routes_fn(mapper, wsgi_resource)

10.nova.api.openstack.ProjectMapper与nova.api.openstack.PlainMapper

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 PlainMapper(APIMapper):def resource(self, member_name, collection_name, **kwargs):if 'parent_resource' in kwargs:parent_resource = kwargs['parent_resource']p_collection = parent_resource['collection_name']p_member = parent_resource['member_name']kwargs['path_prefix'] = '%s/:%s_id' % (p_collection, p_member)routes.Mapper.resource(self, member_name,collection_name,**kwargs)

调用routes.Mapper.resource生成REST ful api

11.nova.api.openstack.APIRouterV2._register_controllers
    def _register_controllers(self, ext):"""Register controllers defined by the extensionsExtensions define what resources they want to add througha get_controller_extensions function"""handler = ext.objLOG.debug("Running _register_controllers on %s", ext.obj)#获取该controller的所有extensionfor extension in handler.get_controller_extensions():ext_name = extension.extension.namecollection = extension.collectioncontroller = extension.controllerif collection not in self.resources:LOG.warning(_LW('Extension %(ext_name)s: Cannot extend ''resource %(collection)s: No such resource'),{'ext_name': ext_name, 'collection': collection})continueLOG.debug('Extension %(ext_name)s extending resource: ''%(collection)s',{'ext_name': ext_name, 'collection': collection})#获取该extension的resource列表resource = self.resources[collection]resource.register_actions(controller)resource.register_extensions(controller)

调用resource对象的register_actions及register_extensions方法,注册controller actions及controller extensions。

12..nova.api.openstack.wsgi.Resource
    def register_actions(self, controller):"""Registers controller actions with this resource."""actions = getattr(controller, 'wsgi_actions', {})for key, method_name in actions.items():self.wsgi_actions[key] = getattr(controller, method_name)def register_extensions(self, controller):"""Registers controller extensions with this resource."""extensions = getattr(controller, 'wsgi_extensions', [])for method_name, action_name in extensions:# Look up the extending methodextension = getattr(controller, method_name)if action_name:# Extending an action...if action_name not in self.wsgi_action_extensions:self.wsgi_action_extensions[action_name] = []self.wsgi_action_extensions[action_name].append(extension)else:# Extending a regular methodif method_name not in self.wsgi_extensions:self.wsgi_extensions[method_name] = []self.wsgi_extensions[method_name].append(extension)


[nova]nova api执行过程分析相关推荐

  1. 4. 系统调用执行过程分析

    ##################################### 作者:张卓 原创作品转载请注明出处:<Linux操作系统分析>MOOC课程 http://www.xuetang ...

  2. Ansible执行过程分析、异步模式和速度优化

    Ansible系列(七):执行过程分析.异步模式和速度优化 分类: Linux服务篇 undefined 我写了更完善的Ansible专栏文章:一步到位玩儿透Ansible Ansible系列文章:h ...

  3. ART运行时Semi-Space(SS)和Generational Semi-Space(GSS)GC执行过程分析

    Semi-Space(SS)GC和Generational Semi-Space(GSS)GC是ART运行时引进的两个Compacting GC.它们的共同特点是都具有一个From Space和一个T ...

  4. openstack nova 源码解析 — Nova API 执行过程从(novaclient到Action)

    目录 目录 Nova API Nova API 的执行过程 novaclient 将 Commands 转换为标准的HTTP请求 PasteDeploy 将 HTTP 请求路由到具体的 WSGI Ap ...

  5. 【OpenStack(Train版)安装部署(十六)】之Nova的API测试,报错处理

    文章目录 本文章由公号[开发小鸽]发布!欢迎关注!!! 1.nova计算服务的API使用 (1)server服务器状态 (2)查询所有的server实例 (3)查询所有server实例的详细信息 (4 ...

  6. OpenStack Nova Placement API 统一资源管理接口的未来

    目录 目录 Placement API 为何称之为 "未来" 操作对象基本概念 数据库操作样例 Placement API 在创建虚拟机时的调度过程 Placement REST ...

  7. 【转】2.2【MySQL】运行原理(二):InnoDB 内存结构、磁盘结构及update sql执行过程分析

    前一篇讲完了查询流程,我们是不是再讲讲更新流程.插入流程和删除流程?在数据库里面,我们说的update操作其实包括了更新.插入和删除.如果大家有看过MyBatis的源码,应该知道Executor里面也 ...

  8. Chromium硬件加速渲染的OpenGL命令执行过程分析

    在Chromium中,由于GPU进程的存在,WebGL端.Render端和Browser端的GPU命令是代理给GPU进程执行的.Chromium将它们要执行的GPU命令进行编码,然后写入到一个命令缓冲 ...

  9. 使用fetch封装请求_关于如何使用Fetch API执行HTTP请求的实用ES6指南

    使用fetch封装请求 In this guide, I'll show you how to use the Fetch API (ES6+) to perform HTTP requests to ...

最新文章

  1. oracle学习 sql基本语法(三),Oracle数据库学习三
  2. Android新权限机制 AppOps
  3. Kettle调用Java类
  4. vb不能插入png图片_第16节-图片 | 剑雨Axure RP9系列「基础」
  5. js如何同时打开多个信息窗口 高德地图_高德地图显示单个窗体和显示多个窗体的方法...
  6. SwiftUI之深入解析如何定制视图的动画和转场
  7. 数据结构与算法 | 快速排序:Hoare法, 挖坑法,双指针法,非递归, 优化
  8. 省市级联基于jquery+json(转)
  9. JSON字符串,JSON对象,JSON数组,实体类转换
  10. 三菱FX系列PLC-编程1
  11. .net core linux 界面,C# dotnet core + AvaloniaUI 开发桌面软件,hello world
  12. Android常用的 adb shell命令
  13. ssh连接阿里云服务器报错 Server responded ”Algori thm negotiation failed.”
  14. 华氏温度和摄氏温度互相转换
  15. python捕获屏幕的标准库_Python标准库urllib2的使用和获取网站状态举例
  16. 【华为OD机试真题 C语言】机器人走迷宫
  17. String.intern()
  18. chrome打开html文件显示不全,谷歌浏览器显示不全怎么办_chrome浏览器打开的网页显示不完整如何解决-win7之家...
  19. ASP.NET CORE WebAPI 中 Route 属性配置
  20. 1-1、秋招年3-5月准备期——《Verilog HDL高级数字设计》(第二版)

热门文章

  1. c++小游戏(王者荣耀极简)
  2. Java Script的基础
  3. influxdb内存过大和占用过多端口
  4. Leetcode刷题笔记-字符串总结
  5. 全国计算机奥林匹克竞赛试题及答案,奥林匹克物理竞赛试题及答案
  6. 1046: 奇数的乘积 Python
  7. 【LeetCode - 702】搜索长度未知的有序数组
  8. 队列:小组队列(插队)
  9. CydiaSubstrate框架
  10. 用 HTML5 造个有诚意的 23D 招聘稿