最近在面试的时候,被问到/etc/nova/api-paste.ini的用途。当时也是一知半解,只知道可能是nova-api启动时的配置文件。回来后仔细看了一下,才发现不是这么简单。
#Paste Deployment简介
Paste Deployment是一套发现和部署WSGI app和server的系统,它实际上屏蔽了app的具体实现细节,这使得用户可以简单快速的部署WSGI app和server。 通过pip命令就可以直接安装:
# pip install PasteDeploy
下面是一个典型的paste deployment配置文件:
#composite section会将请求dispatch到其他的app中,这里是根据url
[composite:main]
use = egg:Paste#urlmap
/firstapp = firstpipe
/secondapp = secondapp
#这是一个app factory,它会生成一个callable的app
[app:firstapp]
use = app:FirstApp.factory
[app:secondapp]
use = app:SecondApp.factory
#这是一个filter factory,它会生成一个filter
[filter:firstfilter]
use = filter:FirstFilter.factory
[filter:secondfilter]
use = filter:SecondFilter.factory
#pipeline用来将多个filter和app绑定到一起,最后返回过滤后的app
[pipeline:firstpipe]
pipline = firstapp secondfilter firstapp
相关的代码如下:
from webob import Response
from wsgi import Router
from wsgi import Resource
class FirstApp():
def __call__(self, environ, start_response):
res = Response()
res.status = '200 OK'
res.content_type = 'text/plain'
content = []
content.append('I am first app')
res.body = '\n'.join(content)
return res(environ, start_response)
@classmethod
def factory(cls, global_conf, **kwargs):
return cls()
class FirstFilter():
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
print 'first filter'
return self.app(environ, start_response)
@classmethod
def factory(cls, global_conf, **kwargs):
return cls
class SecondFilter():
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
print 'second filter'
return self.app(environ, start_response)
@classmethod
def factory(cls, global_conf, **kwargs):
return cls
class SecondApp():
def __call__(self, environ, start_response):
res = Response()
res.status = '200 OK'
res.content_type = 'text/plain'
content = []
content.append('I am second app')
res.body = '\n'.join(content)
return res(environ, start_response)
@classmethod
def factory(cls, global_conf, **kwargs):
return cls()
这样的话,可以通过下面的方式启动一个server和app:
from paste.deploy import loadapp
from wsgiref.simple_server import make_server
appname = "main"
paste_path = "/etc/wsgiapp/paste.ini"
wsgi_app = loadapp("config:%s" % paste_path, appname)
server = make_server('localhost', 8080, wsgi_app)
server.serve_forever()
具体关于Paste Deployment中composite、app、filter和pipeline这些section的介绍可以见Paste Deploy v1.5.0 documentatin。
#Routes 在OpenStack的REST API service中还会遇到routes的概念。python中的routes是仿照Ruby on Rails的routing,主要是实现路由映射的功能。 通过pip命令就可以直接安装:
# pip install Routes
它使用起来也是非常方便:
map = mapper()
map.connect("home", "/", controller="main", action="index")
这样如果请求的url是http://localhost:8080/,routes就会路由到controller为main的index函数中。
##RESTful services 利用routes中的map.resources还可以方便的构建一个RESTful web services。
map.resource("message", "messages")
# 上面的代码相当于手动建立了下面这些路由规则
map.connect("messages", "/messages",
controller="messages", action="create",
conditions=dict(method=["POST"]))
map.connect("messages", "/messages",
controller="messages", action="index",
conditions=dict(method=["GET"]))
map.connect("formatted_messages", "/messages.{format}",
controller="messages", action="index",
conditions=dict(method=["GET"]))
map.connect("new_message", "/messages/new",
controller="messages", action="new",
conditions=dict(method=["GET"]))
map.connect("formatted_new_message", "/messages/new.{format}",
controller="messages", action="new",
conditions=dict(method=["GET"]))
map.connect("/messages/{id}",
controller="messages", action="update",
conditions=dict(method=["PUT"]))
map.connect("/messages/{id}",
controller="messages", action="delete",
conditions=dict(method=["DELETE"]))
map.connect("edit_message", "/messages/{id}/edit",
controller="messages", action="edit",
conditions=dict(method=["GET"]))
map.connect("formatted_edit_message", "/messages/{id}.{format}/edit",
controller="messages", action="edit",
conditions=dict(method=["GET"]))
map.connect("message", "/messages/{id}",
controller="messages", action="show",
conditions=dict(method=["GET"]))
map.connect("formatted_message", "/messages/{id}.{format}",
controller="messages", action="show",
conditions=dict(method=["GET"]))
这样会建立如下的转换规则:
GET /messages => messages.index() => url("messages")
POST /messages => messages.create() => url("messages")
GET /messages/new => messages.new() => url("new_message")
PUT /messages/1 => messages.update(id) => url("message", id=1)
DELETE /messages/1 => messages.delete(id) => url("message", id=1)
GET /messages/1 => messages.show(id) => url("message", id=1)
GET /messages/1/edit => messages.edit(id) => url("edit_message", id=1)
#Nova-api WSGI service
##启动 OpenStack中的service有两种:WSGIService和Service,所有的service都在nova/cmd中。nova-api的启动代码在nova/cmd/api.py中:
def main():
config.parse_args(sys.argv)
logging.setup("nova")
utils.monkey_patch()
gmr.TextGuruMeditation.setup_autorun(version)
launcher = service.process_launcher()
for api in CONF.enabled_apis:
should_use_ssl = api in CONF.enabled_ssl_apis
if api == 'ec2':
server = service.WSGIService(api, use_ssl=should_use_ssl,
max_url_len=16384)
else:
server = service.WSGIService(api, use_ssl=should_use_ssl)
launcher.launch_service(server, workers=server.workers or 1)
launcher.wait()
service.WSGIService()负责启动WSGI server。进入service.WSGIService(),在nova/service.py里,__init__()的代码如下:
class WSGIService(object):
"""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
self.manager = self._get_manager()
self.loader = loader or wsgi.Loader()
self.app = self.loader.load_app(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' % name, None) or
utils.cpu_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.loader = loader or wsgi.Loader()
self.app = self.loader.load_app(name)
在初始化WSGIService时,会传入一个loader,并利用这个loader去创建一个app。如果没有传入,就使用wsgi.Loader。
self.server = wsgi.Server(name,
self.app,
host=self.host,
port=self.port,
use_ssl=self.use_ssl,
max_url_len=max_url_len)
在这一步,会利用这个app及其他参数来创建一个server。最后,是在nova/cmd/api.py的main()的launcher.launch_service()中去启动这个service(具体的启动过程我会有专门的一篇文章介绍):
def main():
...
for api in CONF.enabled_apis:
...
launcher.launch_service(server, workers=server.workers or 1)
...
##api-paste.ini 在nova-api启动时,会load一个app,这个app是依据api-paste.ini创建的,其实它就是一个普通的Paste Deployment。nova中的api-paste.ini的示例可以见etc/nova/api-paste.ini:
############
# Metadata #
############
[composite:metadata]
use = egg:Paste#urlmap
/: meta
[pipeline:meta]
pipeline = ec2faultwrap logrequest metaapp
[app:metaapp]
paste.app_factory = nova.api.metadata.handler:MetadataRequestHandler.factory
#######
# EC2 #
#######
[composite:ec2]
use = egg:Paste#urlmap
/services/Cloud: ec2cloud
[composite:ec2cloud]
use = call:nova.api.auth:pipeline_factory
noauth = ec2faultwrap logrequest ec2noauth cloudrequest validator ec2executor
keystone = ec2faultwrap logrequest ec2keystoneauth cloudrequest validator ec2executor
[filter:ec2faultwrap]
paste.filter_factory = nova.api.ec2:FaultWrapper.factory
[filter:logrequest]
paste.filter_factory = nova.api.ec2:RequestLogging.factory
[filter:ec2lockout]
paste.filter_factory = nova.api.ec2:Lockout.factory
[filter:ec2keystoneauth]
paste.filter_factory = nova.api.ec2:EC2KeystoneAuth.factory
[filter:ec2noauth]
paste.filter_factory = nova.api.ec2:NoAuth.factory
[filter:cloudrequest]
controller = nova.api.ec2.cloud.CloudController
paste.filter_factory = nova.api.ec2:Requestify.factory
[filter:authorizer]
paste.filter_factory = nova.api.ec2:Authorizer.factory
[filter:validator]
paste.filter_factory = nova.api.ec2:Validator.factory
[app:ec2executor]
paste.app_factory = nova.api.ec2:Executor.factory
#############
# OpenStack #
#############
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v1.1: openstack_compute_api_v2
/v2: openstack_compute_api_v2
/v3: openstack_compute_api_v3
[composite:openstack_compute_api_v2]
use = call:nova.api.auth:pipeline_factory
noauth = faultwrap sizelimit noauth ratelimit osapi_compute_app_v2
keystone = faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2
keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
[composite:openstack_compute_api_v3]
use = call:nova.api.auth:pipeline_factory_v3
noauth = faultwrap sizelimit noauth_v3 osapi_compute_app_v3
keystone = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3
[filter:faultwrap]
paste.filter_factory = nova.api.openstack:FaultWrapper.factory
[filter:noauth]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory
[filter:noauth_v3]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddlewareV3.factory
[filter:ratelimit]
paste.filter_factory = nova.api.openstack.compute.limits:RateLimitingMiddleware.factory
[filter:sizelimit]
paste.filter_factory = nova.api.sizelimit:RequestBodySizeLimiter.factory
[app:osapi_compute_app_v2]
paste.app_factory = nova.api.openstack.compute:APIRouter.factory
[app:osapi_compute_app_v3]
paste.app_factory = nova.api.openstack.compute:APIRouterV3.factory
[pipeline:oscomputeversions]
pipeline = faultwrap oscomputeversionapp
[app:oscomputeversionapp]
paste.app_factory = nova.api.openstack.compute.versions:Versions.factory
##########
# Shared #
##########
[filter:keystonecontext]
paste.filter_factory = nova.api.auth:NovaKeystoneContext.factory
[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
我们在这里只关注OpenStack的app:
#############
# OpenStack #
#############
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v1.1: openstack_compute_api_v2
/v2: openstack_compute_api_v2
/v3: openstack_compute_api_v3
[composite:openstack_compute_api_v2]
use = call:nova.api.auth:pipeline_factory
noauth = faultwrap sizelimit noauth ratelimit osapi_compute_app_v2
keystone = faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2
keystone_nolimit = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
[composite:openstack_compute_api_v3]
use = call:nova.api.auth:pipeline_factory_v3
noauth = faultwrap sizelimit noauth_v3 osapi_compute_app_v3
keystone = faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3
[filter:faultwrap]
paste.filter_factory = nova.api.openstack:FaultWrapper.factory
[filter:noauth]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory
[filter:noauth_v3]
paste.filter_factory = nova.api.openstack.auth:NoAuthMiddlewareV3.factory
[filter:ratelimit]
paste.filter_factory = nova.api.openstack.compute.limits:RateLimitingMiddleware.factory
[filter:sizelimit]
paste.filter_factory = nova.api.sizelimit:RequestBodySizeLimiter.factory
[app:osapi_compute_app_v2]
paste.app_factory = nova.api.openstack.compute:APIRouter.factory
[app:osapi_compute_app_v3]
paste.app_factory = nova.api.openstack.compute:APIRouterV3.factory
[pipeline:oscomputeversions]
pipeline = faultwrap oscomputeversionapp
[app:oscomputeversionapp]
paste.app_factory = nova.api.openstack.compute.versions:Versions.factory
##########
# Shared #
##########
[filter:keystonecontext]
paste.filter_factory = nova.api.auth:NovaKeystoneContext.factory
[filter:authtoken]
paste.filter_factory = keystoneclient.middleware.auth_token:filter_factory
这个paste.ini是不是看着很眼熟,里面就是一些composite、app、filter和pipeline。OpenStack的nova-api就是根据这个配置来启动一个个WSGI service的。
未完待续