Nova API的调用流程与开发

Nova简介

Nova是由Openstack的计算模块,用于管理Openstack资源的生命周期。

nova api的调用流程

从nova client的入口查看

1
2
3
4
5
6
7
8
9
10
11
>cat /usr/bin/nova
---
#!/usr/bin/python
# PBR Generated from 'console_scripts'

import sys

from novaclient.shell import main

if __name__ == "__main__":
sys.exit(main())

其中导入了 novaclient.shell 这个文件中导入了 main 方法,进入 novaclient.shell.py 查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def main():
try:
OpenStackComputeShell().main(map(strutils.safe_decode, sys.argv[1:]))

except Exception as e:
logger.debug(e, exc_info=1)
print("ERROR: %s" % strutils.safe_encode(six.text_type(e)),
file=sys.stderr)
sys.exit(1)

OpenStackComputeShell.main()
self.cs = client.Client(options.os_compute_api_version, os_username,
os_password, os_tenant_name, tenant_id=os_tenant_id,
auth_url=os_auth_url, insecure=insecure,
region_name=os_region_name, endpoint_type=endpoint_type,
extensions=self.extensions, service_type=service_type,
service_name=service_name, auth_system=os_auth_system,
auth_plugin=auth_plugin,
volume_service_name=volume_service_name,
timings=args.timings, bypass_url=bypass_url,
os_cache=os_cache, http_log_debug=options.debug,
cacert=cacert, timeout=timeout)

self.cs 是从 client 中创建出的一个 Client 实例,进入 novaclient.client.py 查看这个实例的具体方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def get_client_class(version):
version_map = {
'1.1': 'novaclient.v1_1.client.Client',
'2': 'novaclient.v1_1.client.Client',
'3': 'novaclient.v3.client.Client',
}
try:
client_path = version_map[str(version)]
except (KeyError, ValueError):
msg = "Invalid client version '%s'. must be one of: %s" % (
(version, ', '.join(version_map.keys())))
raise exceptions.UnsupportedVersion(msg)

return utils.import_class(client_path)

def Client(version, args, kwargs):
client_class = get_client_class(version)
return client_class( args, kwargs)

用的是v1_1这个版本的api,对应的是 novaclient.v1_1.client.py 里的 Client 类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Client(object):
"""
Top-level object to access the OpenStack Compute API.

Create an instance with your creds::

>>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL)

Then call methods on its managers::

>>> client.servers.list()
...
>>> client.flavors.list()
...

"""

注释里讲了怎么使用 python 命令行调用 nova 的 client

client 里给流入的指令分了很多类,以 flavors 为例,看 nova flavor-list 这个命令的流程

1
self.flavors = flavors.FlavorManager(self)

flavors.list() 进入 novaclient.v1_1.flavors.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class FlavorManager(base.ManagerWithFind):
"""
Manage :class:`Flavor` resources.
"""
resource_class = Flavor
is_alphanum_id_allowed = True

def list(self, detailed=True, is_public=True):
"""
Get a list of all flavors.

:rtype: list of :class:`Flavor`.
"""
qparams = {}
# is_public is ternary - None means give all flavors.
# By default Nova assumes True and gives admins public flavors
# and flavors from their own projects only.
if not is_public:
qparams['is_public'] = is_public
query_string = "?%s" % urlutils.urlencode(qparams) if qparams else ""

detail = ""
if detailed:
detail = "/detail"

return self._list("/flavors%s%s" % (detail, query_string), "flavors")

self._list 进入 novaclient.base.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Manager(utils.HookableMixin):
"""
Managers interact with a particular type of API (servers, flavors, images,
etc.) and provide CRUD operations for them.
"""
resource_class = None

def __init__(self, api):
self.api = api

def _list(self, url, response_key, obj_class=None, body=None):
if body:
_resp, body = self.api.client.post(url, body=body)
else:
_resp, body = self.api.client.get(url)

if obj_class is None:
obj_class = self.resource_class

data = body[response_key]
# NOTE(ja): keystone returns values as list as {'values': [ ... ]}
# unlike other services which just return the list...
if isinstance(data, dict):
try:
data = data['values']
except KeyError:
pass

with self.completion_cache('human_id', obj_class, mode="w"):
with self.completion_cache('uuid', obj_class, mode="w"):
return [obj_class(self, res, loaded=True)
for res in data if res]

novaclient.v1_1.flavors.py 里 FlavorManager 的 resource_class = Flavor 即 class Flavor(base.Resource)

所以最后 obj_class 为 Flavor

调用 api 的过程:

1
2
3
4
if body:
_resp, body = self.api.client.post(url, body=body)
else:
_resp, body = self.api.client.get(url)

通过 self.api 到了 nova 的 api 里 nova.api.openstack.compute.init.py

1
2
3
4
5
6
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'})

找到 nova.api.openstack.flavors.py

1
2
3
4
5
@wsgi.serializers(xml=MinimalFlavorsTemplate)
def index(self, req):
"""Return all flavors in brief."""
limited_flavors = self._get_flavors(req)
return self._view_builder.index(req, limited_flavors)

它最后会返回一个存放 flavors 信息的字典,这些原始数据经过提取和加工,最后在终端被打印出来

nova.api.openstack.compute.views.flavors.py :

1
2
3
4
5
6
7
8
9
10
11
12
13
def _list_view(self, func, request, flavors):
"""Provide a view for a list of flavors."""
flavor_list = [func(request, flavor)["flavor"] for flavor in flavors]
flavors_links = self._get_collection_links(request,
flavors,
self._collection_name,
"flavorid")
flavors_dict = dict(flavors=flavor_list)

if flavors_links:
flavors_dict["flavors_links"] = flavors_links

return flavors_dict

增加一个新的API

添加一个新的 client 流程:

功能:快速备份虚拟机,三个参数,虚拟机uuid、备份的名字、备份的描述,调用地方和方法如下:

1
2
3
4
curl -i http://<nova_ip>:8774/v2/<tenant_id>/servers/<user_id>/backup_instance \
-X POST -H "X-Auth-Project-Id: admin" -H "Content-Type: application/json" \
-H "User-Agent: python-novaclient" -H "Accept: application/json" \
-H "X-Auth-Token: <token>" -d '{"name" : "backup", "description" : "backup's description"}'

在novaclient.shell.py里,class OpenStackComputeShell(object)的get_subcommand_parser方法里指定了actions_module

转到 novaclient.v1_1.shell.py

增加一个新的方法,装饰器里是需要的参数,有顺序,和执行时的参数顺序一致

1
2
3
4
5
6
7
8
9
10
11
12
13
@utils.arg('server', metavar='<server>', help='ID of server.')
@utils.arg('displayname',
metavar='<name>',
help='Display name for backup.')
@utils.arg('description',
default=None,
metavar='<description>',
help='Description for backup.(Default None)')
def do_backup_instance(cs, args):
"""Make a quick backup for instance."""
cs.servers.backup_instance(args.server,
args.displayname,
args.description)

这个功能是加在 servers 部分里的,转到 novaclient.v1_1.servers.py

在 ServerManager 类里添加

1
2
3
4
5
6
7
8
9
10
11
12
13
def backup_instance(self, server, backup_name, backup_description):
"""
Backup a server instance quickly.

:param server: The :class:`Server` (or its ID) to share onto.
:param backup_name: Name of the backup image
:param backup_description: The backup description
"""
body = {'name': backup_name,
'description': backup_description}
response_key = "id"
return self._create("/servers/%s/backup_instance" % base.getid(server),
body, response_key, return_raw=True)

response_key是指返回数据里的key,这里返回的数据是{‘id’: “XXXXXX”},所以response_key = “id”

因为这个 api 返回的是一个 json 字符串,不能通过 novaclient.base.py 里 Manager 类里的方法把数据提取出来(它需要字典),于是把 return_raw 这个参数设置为 True

然后就可以在 nova 的命令行里看到这个新的功能了: nova backup-instance ;使用方法:

1
nova backup-instance <server_id> <bak_name> <bak_description>