Nova multi-show功能开发

Nova multi-show开发目的

什么是 nova multi-show ?即通过此命令可以获取多个虚拟机的信息。

那么 nova multi-show 和使用多次 nova show 有什么区别?

nova multi-show 用一次查询查出所给的所有虚拟机信息;即 nova multi-show 查询10个虚拟机和调用10次 nova show 查询的区别就是减小了对数据库的请求开销。如果同时有100个用户作出了查询功能,假设查询了10台,那么 nova multi-show 则减少了900次对数据库的请求操作。

向Nova的API模块注册方法

文件:nova.api.openstack.compute.__init__.py

预期的http调用地址为:

1
2
curl -i http://openstack_controller_ip:8774/v2/tenant_id/servers/serv/multi_show \
-d '{"servers": [instance_uuid_01, instance_uuid_02, ...]}'

class APIRouter中注册 multi_show 方法, method 为POST

1
2
3
4
5
6
7
8
9
if init_only is None or 'consoles' in init_only or \
'servers' in init_only or ips in init_only:
self.resources['servers'] = servers.create_resource(ext_mgr)
mapper.resource("server", "servers",
controller=self.resources['servers'],
collection={'detail': 'GET'},
member={'action': 'POST',
'multi_show': 'POST',
})

编写请求的处理方法

文件: nova.api.openstack.compute.servers.py

class Controller中加入方法 multi_show

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@wsgi.serializers(xml=ServerTemplate)
def multi_show(self, req, id, body):
context = req.environ['nova.context']
servers = body['servers'] # 获取传入body中的虚拟机id列表
if len(servers) > 10: # 此处限定了可以同时查询的最多虚拟机个数
msg = _("Servers can not beyond 10.")
raise exc.HTTPBadRequest(explanation=msg)

rsp = {'server_info': []}
try:
context = req.environ['nova.context']
# 此处进入compute.api的multi_show方法
instance = self.compute_api.multi_show(context, servers,
want_objects=True)
for inst in instance:
req.cache_db_instance(inst)
_rsp = self._view_builder.show(req, inst)
rsp['server_info'].append(_rsp['server'])
except exception.NotFound:
msg = _("Instance could not be found")
raise exc.HTTPNotFound(explanation=msg)
return rsp

编写 multi_show 的具体方法

文件: nova.compute.api.py

class API中加入方法 multi_show

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def multi_show(self, ctxt, uuids, want_objects=False):
"""Get multi instances with the given uuids."""
# NOTE(ameade): we still need to support integer ids for ec2
# 以下是查询数据库时需要额外查询的表
expected_attrs = ['metadata', 'system_metadata',
'security_groups', 'info_cache']
try:
# 请注意!这里将进入multi_show的核心方法。。。
instance = instance_obj.Instance.get_by_uuids(ctxt, uuids,
expected_attrs=expected_attrs)

except exception.InvalidID:
raise exception.InstanceNotFound(instance_id=str(uuids))

check_policy(ctxt, 'get', instance)

if not want_objects:
nw_inst = []
for inst in instance:
inst = obj_base.obj_to_primitive(inst)
nw_inst.append(inst)
instance = nw_inst
return instance

编写 object 模块中获取多个虚拟机的方法

文件: nova.objects.instance.py

class Instance中加入 get_by_uuids 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
@base.remotable_classmethod
def get_by_uuids(cls, context, uuids, expected_attrs=None):
rsp = []
if expected_attrs is None: # 咱们这里expected_attrs不为None
expected_attrs = ['info_cache', 'security_groups']
columns_to_join = _expected_cols(expected_attrs)
# 请注意!以下才是最最关键的步骤,将进入到db模块。。。
db_inst = db.instance_get_by_multi_uuids(context, uuids,
columns_to_join=columns_to_join)
for inst in db_inst:
nw_inst = cls._from_db_object(context, cls(), inst, expected_attrs)
rsp.append(nw_inst)
return rsp

db.instance_get_by_multi_uuids 这个函数将进入到 db 模块,调用 sqlalchemy 模块对数据库进行查询, nova 的 db 模块在之前已经做过介绍,有兴趣的请点击查看Nova数据库模块的使用方法和开发

数据库中查询多个虚拟机方法的开发

文件: nova.db.api.py

加入方法 instance_get_by_multi_uuids

1
2
def instance_get_by_multi_uuids(context, uuids, columns_to_join=None):
return IMPL.instance_get_by_multi_uuids(context, uuids, columns_to_join)

文件: nova.db.sqlalchemy.api.py

加入方法 instance_get_by_multi_uuids

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@require_context
def instance_get_by_multi_uuids(context, uuids, columns_to_join=None):
try:
result = _build_instance_get(context, columns_to_join=columns_to_join).\
# 好吧,精髓都在这里,所以其实也没啥科技含量。。
filter(models.Instance.uuid.in_(tuple(uuids))).\
all()

if not result:
raise exception.InstanceNotFound(instance_id=str(uuids))

return result
except DataError:
# NOTE(sdague): catch all in case the db engine chokes on the
# id because it's too long of an int to store.
msg = _("Invalid instance id %s in request") % str(uuids)
LOG.warn(msg)
raise exception.InvalidID(id=str(uuids))

做完修饰工作

nova 对 list 、 show 的返回值都是有再加工的操作,举个简单的例子:

当你查询某个虚拟机详情( nova show )的时候,数据库中的 vm_state 字段和 task_state 字段不是直接打印在终端,而是诸如 OS-EXT-STS:vm_state 、 OS-EXT-STS:task_state 这样的显示,“ OS-EXT-STS ”即为 nova 对返回数据字段的加工操作

到现在为止 multi_show 返回的仍然是数据库中的裸字段,得给它们加工加工才行:

进入 nova.api.openstack.compute.contrib 目录,在这底下的文件中按需要加入 multi_show 的方法,举例,在 extended_status.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
33
34
35
class ExtendedStatusController(wsgi.Controller):
def __init__(self, args, kwargs):
super(ExtendedStatusController, self).__init__( args, kwargs)
self.compute_api = compute.API()

def _extend_server(self, server, instance):
for state in ['task_state', 'vm_state', 'power_state']:
key = "%s:%s" % (Extended_status.alias, state)
server[key] = instance[state]

@wsgi.extends
def show(self, req, resp_obj, id):
context = req.environ['nova.context']
if authorize(context):
# Attach our slave template to the response object
resp_obj.attach(xml=ExtendedStatusTemplate())
server = resp_obj.obj['server']
db_instance = req.get_db_instance(server['id'])
# server['id'] is guaranteed to be in the cache due to
# the core API adding it in its 'show' method.
self._extend_server(server, db_instance)

@wsgi.extends
def multi_show(self, req, resp_obj, id, body):
context = req.environ['nova.context']
if authorize(context):
# Attach our slave template to the response object
resp_obj.attach(xml=ExtendedStatusTemplate())
servers = resp_obj.obj['server_info']
for i in range(len(servers)):
server = servers[i]
db_instance = req.get_db_instance(server['id'])
# server['id'] is guaranteed to be in the cache due to
# the core API adding it in its 'show' method.
self._extend_server(server, db_instance)

这样就可以返回和 nova show 一样数据结构的信息了。

如果有兴趣可以继续把 novaclient 的相关功能加上。 Novaclient 的开发可以参考这里Novaclient的调用流程与开发,不过这个其实写得不完善,有机会更新下。