1. OpenStack中的API结构地图
当你执行如下命令的时候:
里面做了什么呢?
在这张图中我们加了--debug就看得更清楚了。
具体来说它是由两次http请求构成的。
分别是
(1)向keystone验证
REQ: curl -i 'http://ubuntu80:35357/v2.0/tokens' -X POST -H "Accept: application/json" -H "Content-Type: application/json" -H "User-Agent: python-novaclient" -d '{"auth": {"tenantName": "admin", "passwordCredentials": {"username": "admin", "password": "{SHA1}5705cc2e5fda0ab7529d5093c5e389fffe45d615"}}}'
(2)调用nova-api取得所有实例(虚机)
REQ: curl -i 'http://ubuntu80:8774/v2/0e962df9db3f4469b3d9bfbc5ffdaf7e/servers/detail' -X GET -H "Accept: application/json" -H "User-Agent: python-novaclient" -H "X-Auth-Project-Id: admin" -H "X-Auth-Token: {SHA1}5962c90ceb8f53d805382c386ac240776fe55a54"
我们从上面的例子中看到了一条条URL,而这些URL构成了整个OpenStack API的基础。本文的目的在于,通过这一条条可见的URL找到与OpenStack相关的源码(类或方法),从而迅速定位代码点。
2. OpenStack中的地图配置文件
查找api-paste.ini,为什么找这个配置文件,可以查看我的另外一篇文章(《Paste模块的世界》http://my.oschina.net/crooner/blog/606895 )。
我的这台服务器上安装了nova,glance,neutron(说明一下,我这里是JUNO版本),这里主要介绍nova,打开它吧。
nano /etc/api-paste.ini
看到里面的注释,Metadata,EC2,OpenStack,社区还是很贴心的哇,一看就很清楚。
因为nova api主要就分成三个部分,关于metadata的部分可以参看我的另外一篇(《扩展OpenStack的nova metadata api》http://my.oschina.net/crooner/blog/603044 )。这里主要是介绍OpenStack API部分。
我把这部分内容抽出来
############# # OpenStack # ############# [composite:osapi_compute] use = call:nova.api.openstack.urlmap:urlmap_factory /: oscomputeversions /v1.1: openstack_compute_api_v2 /v2: openstack_compute_api_v2 /v2.1: openstack_compute_api_v21 /v3: openstack_compute_api_v3 [composite:osapi_compute] use = call:nova.api.openstack.urlmap:urlmap_factory /: oscomputeversions /v1.1: openstack_compute_api_v2 /v2: openstack_compute_api_v2 /v2.1: openstack_compute_api_v21 /v3: openstack_compute_api_v3 [composite:openstack_compute_api_v2] use = call:nova.api.auth:pipeline_factory noauth = compute_req_id faultwrap sizelimit noauth ratelimit osapi_compute_app_v2 keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2 keystone_nolimit = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2 [composite:openstack_compute_api_v21] use = call:nova.api.auth:pipeline_factory_v21 noauth = request_id faultwrap sizelimit noauth osapi_compute_app_v21 keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v21 [composite:openstack_compute_api_v3] use = call:nova.api.auth:pipeline_factory_v21 noauth = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3 keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3 [filter:request_id] paste.filter_factory = nova.openstack.common.middleware.request_id:RequestIdMiddleware.factory [filter:request_id] paste.filter_factory = nova.openstack.common.middleware.request_id:RequestIdMiddleware.factory [filter:compute_req_id] paste.filter_factory = nova.api.compute_req_id:ComputeReqIdMiddleware.factory [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_v21] paste.app_factory = nova.api.openstack.compute:APIRouterV21.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
这个就是一地图啊,它告诉你里面是怎么回事,如果你已经看过《Paste模块的世界》http://my.oschina.net/crooner/blog/606895 。
你就知道这样一个配置文件描述了一套路由系统(管道走向)及对应的factory关系。
无论怎样吧,我们还是需要分析一下里面的路径。
我们以v2版的API为例,下面的部分是我抽出来,需要重点关注的:
[composite:osapi_compute] use = call:nova.api.openstack.urlmap:urlmap_factory /: oscomputeversions /v1.1: openstack_compute_api_v2 /v2: openstack_compute_api_v2 /v2.1: openstack_compute_api_v21 /v3: openstack_compute_api_v3
[composite:openstack_compute_api_v2] use = call:nova.api.auth:pipeline_factory noauth = compute_req_id faultwrap sizelimit noauth ratelimit osapi_compute_app_v2 keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2 keystone_nolimit = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
[app:osapi_compute_app_v2] paste.app_factory = nova.api.openstack.compute:APIRouter.factory
根据上面三部分,我们知道composite为起分发作用(多通管道/多口的接头管道),其中v2指向openstack_compute_api_v2。
就是下面的openstack_compute_api_v2的composite,在里面我们看到noauth,keystone,keystone_nolimit通过许多个filter之后最终都是到达osapi_compute_app_v2这个app的。
来到第三部分,这就很直接了,这个app的factory是nova.api.openstack.compute:APIRouter.factory。
nova.api.openstack.compute就是路径(python的包结构,如果熟悉java的朋友com.apache.xxx,是不是倍感亲切 :)),后面APIRouter.factory就是一个类了。
3. 一探究竟
那么进入nova.api.openstack.compute,对应的就是
locate nova/api/openstack/compute
直接找到对应目录,然后进入查看。
既然是nova.api.openstack.compute指的自然就是__init__.py这个文件了(python基础语法知识)。
打开__init__.py文件,查找到APIRouter的factory方法。
APIRouter找到了,一直往下拉都没找到factory方法,猜想一定是OpenStack的小伙伴们做了封装。
我们看到APIRouter是继承自nova.api.openstack.APIRouter的。
好吧,我们去看看nova.api.openstack.APIRouter吧
果然,里面有了factory方法。
这里说明一下,可以使用LOG.error()打印信息到/var/log/nova/nova-api.log。
这里可以得出这样的结论:
nova.api.openstack.compute.APIRouter类继承自nova.api.openstack.APIRouter,在nova.api.openstack.APIRouter内部的__init__方法中,最重要的几句:
mapper = ProjectMapper() self._setup_routes(mapper, ext_mgr, init_only) self._setup_ext_routes(mapper, ext_mgr, init_only)
我们知道这几句产生了整个map路由(注:这里主要涉及到routes模块),特别要注意的是
self._setup_routes(mapper, ext_mgr, init_only)
它生成了主要的URL规则(你可以通过调试看到),在nova.api.openstack.APIRouter中的 _setup_routes方法:
它只是抛出了一个异常,那么它必然被子类所实现。
回到nova.api.openstack.compute.APIRouter中的_setup_routes方法:
非常明显,这里注意mapper.resource方法,它会自动生成相关的RESTful API URL,如果朋友们有兴趣可以试一下routes模块的mapper.resource方法。
还有要提一下:
mapper.redirect("", "/")
这句起了重定向的作用
在我的博客(WSGI必知必会 http://my.oschina.net/crooner/blog/609030 )的“7. 路由处理”中的最后两个例子中都有提到使用到这句,单独的例子中易于直观感受它的作用。
4. 相关的py文件怎么与API关联起来的?
还是接着上面的例子,我们看到nova.api.openstack.compute.APIRouter中的_setup_routes方法
我们还是以servers为例:
可以看到,里面最重要的依据就是:
self.resources['servers'] = servers.create_resource(ext_mgr)
回到目录结构:
找到servers.py中的create_resource方法:
可以看到,这里将Controller对象放入wsgi.Resource方法调用。
Controller中是本servers.py中最重要的部分,里面有主要的API包括,detail,index...等方法,这里请记住detail,我们会在后面的例子中用到。
还有要注意的是,在Controller的__init__构造方法中,加载了compute.API()
self.compute_api = compute.API()
它将compute.API()实例化放入self.compute_api供调用。
回到话题的中心,.py是怎么关联API的呢,这里尤其是servers,py怎么关联API(URL:xxx/servers/xxx)的。
回到images.py头部,看到
from nova.api.openstack import wsgi
那么就可以在下面的路径中找到wsgi.py
定位到Resource类中的__init__方法与register_actions方法之间看到:
在register_actions中加载进对应的controller中的所有方法。
即:将所有API通过wsgi.Resource将对应Controller中的所有方法加载进来。
看到整个的调用链:
api-paste.ini -> /v2: openstack_compute_api_v2 -> osapi_compute_app_v2 -> nova.api.openstack.compute:APIRouter -> self._setup_routes -> xx.create_resource() -> wsgi.Resource(Controller()) -> wsgi.Resource.register_actions()
5. 反推测试结论,添加自定义nova api
由上面的结论得出,在nova-api服务启动时,对应的URL与相应的类和方法就会被加载。
那么以v2版本的nova api为例,最重要的部分应该是在nova.api.openstack.compute.APIRouter中的_setup_routes,它决定了各个类和方法与URL的对应。
那么反过来,如果我们希望自己添加一个v2 API,只需要在nova.api.openstack.compute.APIRouter中的_setup_routes中添加即可。
下面我们以images为模板,添加一个test_images,进入nova.api.openstack.compute(如下目录):
执行:
cp images.py test_images.py nano test_images.py
在打开test_images.py后,找到Controller中的detail方法修改如下:
接下来就应该建立URL与这个类文件的对应了
退出test_images.py,进入nova.api.openstack.compute,打开__init__.py进行编辑
首先在头部导入模块:
from nova.api.openstack.compute import test_images
接着就可以来到nova.api.openstack.compute.APIRouter中的_setup_routes
将test_images添加进来
好了,一切大功告成!这个测试的api就添加完成了。
6. 测试添加的自定义API
在上面的步骤中,我们添加了自定义的API (xxx/test_images/detail),接下来我们通过curl来进行测试。
这个步骤分两步,第一步取得keystone的token,第二部执行我们自己的api。
在第一节中,我们演示过正常查看内部请求的过程
nova --debug list
这里我们截取一下里面的请求为我们所用
curl -i 'http://ubuntu80:35357/v2.0/tokens' -X POST -H "Accept: application/json" -H "Content-Type: application/json" -H "User-Agent: python-novaclient" -d '{"auth": {"tenantName": "admin", "passwordCredentials": {"username": "admin", "password": "{SHA1}5705cc2e5fda0ab7529d5093c5e389fffe45d615"}}}'
修改成:
curl -i 'http://ubuntu80:35357/v2.0/tokens' -X POST -H "Accept: application/json" -H "Content-Type: application/json" -H "User-Agent: python-novaclient" -d '{"auth": {"tenantName": "admin", "passwordCredentials": {"username": "admin", "password": "ADMIN_PASS"}}}'
注意这里将{SHA1}5705cc2e5fda0ab7529d5093c5e389fffe45d615修改成ADMIN_PASS,这个ADMIN_PASS是你的OpenStack密码。
执行之后,结果如下:
root@ubuntu80:/# curl -i 'http://ubuntu80:35357/v2.0/tokens' -X POST -H "Accept: application/json" -H "Content-Type: application/json" -H "User-Agent: python-novaclient" -d '{"auth": {"tenantName": "admin", "passwordCredentials": {"username": "admin", "password": "ADMIN_PASS"}}}' HTTP/1.1 200 OK Vary: X-Auth-Token X-Distribution: Ubuntu Content-Type: application/json Content-Length: 1744 Date: Tue, 26 Jan 2016 02:18:41 GMT {"access": {"token": {"issued_at": "2016-01-26T02:18:41.487301", "expires": "2016-01-26T03:18:41Z", "id": "a479cdb5f5ce47aebccd15f1790beb2c", "tenant": {"description": "Admin Tenant", "enabled": true, "id": "0e962df9db3f4469b3d9bfbc5ffdaf7e", "name": "admin"}, "audit_ids": ["eavCb8mjT863hnOxhdxUwQ"]}, "serviceCatalog": [{"endpoints": [{"adminURL": "http://ubuntu80:9292", "region": "regionOne", "internalURL": "http://ubuntu80:9292", "id": "4794a2d722ab4f6bbda00d779c1410d1", "publicURL": "http://ubuntu80:9292"}], "endpoints_links": [], "type": "image", "name": "glance"}, {"endpoints": [{"adminURL": "http://ubuntu80:8774/v2/0e962df9db3f4469b3d9bfbc5ffdaf7e", "region": "regionOne", "internalURL": "http://ubuntu80:8774/v2/0e962df9db3f4469b3d9bfbc5ffdaf7e", "id": "a8ccc19100934fc1ae7c899dc5e17bdd", "publicURL": "http://ubuntu80:8774/v2/0e962df9db3f4469b3d9bfbc5ffdaf7e"}], "endpoints_links": [], "type": "compute", "name": "nova"}, {"endpoints": [{"adminURL": "http://ubuntu80:9696", "region": "regionOne", "internalURL": "http://ubuntu80:9696", "id": "656371fd3163415c95ff2fc0facbe5e1", "publicURL": "http://ubuntu80:9696"}], "endpoints_links": [], "type": "network", "name": "neutron"}, {"endpoints": [{"adminURL": "http://ubuntu80:35357/v2.0", "region": "regionOne", "internalURL": "http://ubuntu80:5000/v2.0", "id": "4f1d53f12dc6485cb5816c83f68b7053", "publicURL": "http://ubuntu80:5000/v2.0"}], "endpoints_links": [], "type": "identity", "name": "keystone"}], "user": {"username": "admin", "roles_links": [], "id": "96a7c834b3f8485c87d79df7b6480c92", "roles": [{"name": "_member_"}, {"name": "admin"}], "name": "admin"}, "metadata": {"is_admin": 0, "roles": ["9fe2ff9ee4384b1894a90878d3e92bab", "fc2574382dd74936b1bc85cc2110c3c2"]}}}root@ubuntu80:/#
这里获取了token的id为
a479cdb5f5ce47aebccd15f1790beb2c
这里备用
在第一节中还有第二条curl请求,我们将其修改一下:
curl -i 'http://ubuntu80:8774/v2/0e962df9db3f4469b3d9bfbc5ffdaf7e/servers/detail' -X GET -H "Accept: application/json" -H "User-Agent: python-novaclient" -H "X-Auth-Project-Id: admin" -H "X-Auth-Token: {SHA1}5962c90ceb8f53d805382c386ac240776fe55a54"
修改如下,这里主要修改URL为http://ubuntu80:8774/v2/0e962df9db3f4469b3d9bfbc5ffdaf7e/test_images/detail,并修改token为我们第一步通过keystone获取的token:
curl -i 'http://ubuntu80:8774/v2/0e962df9db3f4469b3d9bfbc5ffdaf7e/test_images/detail' -X GET -H "Accept: application/json" -H "User-Agent: python-novaclient" -H "X-Auth-Project-Id: admin" -H "X-Auth-Token: a479cdb5f5ce47aebccd15f1790beb2c"
执行得到结果如下:
这里,打印出了我们想要的结果。
特别声明:本文原创,欢迎大家转载,但还请注明出处,谢谢!(http://my.oschina.net/crooner/blog/609419)