在openstack中,一个典型的操作是把存在的cinder volume指定给某个nova instance使用。这样nova instance才可以使用cinder的块存储服务。当我们运行nova volume-attach <server> <volume> [<device>],到底发生了哪些事情?我们来从源码级别分析下整个工作流程。
我们先来看看这条命令的help文档。参数的意思很明显,就是把volume attach给instance,可以optional指定instance 机器中device的名字,比如: /dev/vdc
[ray@ncvm9087109 cinder]$ nova help volume-attach
usage: nova volume-attach <server> <volume> [<device>]
Attach a volume to a server.
Positional arguments:
<server> Name or ID of server.
<volume> ID of the volume to attach.
<device> Name of the device e.g. /dev/vdb. Use "auto" for autoassign (if supported)
我们来一步步的分析,看看nova command是什么?打开nova文件发现,我们看出nova binary实际上是novaclient的shell前端。而novaclient实际上是基于nova REST API的python CLI client。
[ray@ncvm9087109 cinder]$ which nova
/usr/bin/nova
[ray@ncvm9087109 cinder]$ cat /usr/bin/nova
#!/usr/bin/python
# PBR Generated from u'console_scripts'
import sys
from novaclient.shell import main
if __name__ == "__main__":
sys.exit(main())
打开novaclient源码目录下面的novaclient/shell.py文件,main入口就在这里:
786 def main():
787 try:
788 argv = [strutils.safe_decode(a) for a in sys.argv[1:]]
789 OpenStackComputeShell().main(argv)
继续切换到OpenStackComputeShell Class的mian函数。 主要做的事情就是参数处理,已经根据参数生成一般的HTTP client方便后续发出REST request。记住这里的self.cs,后续还会用到。注意最后隐士的调用args.func(self.cs, args)。此时func已经指向后文中的do_volume_attach。
根据API版本从v1/v2/v3 shell模块从load submand parser。在novaclient/v3/shell.py仅仅找到并import那些"do_"开头的函数
402 def get_subcommand_parser(self, version):
403 parser = self.get_base_parser()
404
405 self.subcommands = {}
406 subparsers = parser.add_subparsers(metavar='<subcommand>')
407
408 try:
409 actions_module = {
410 '1.1': shell_v1_1,
411 '2': shell_v1_1,
412 '3': shell_v3,
413 }[version]
414 except KeyError:
415 actions_module = shell_v1_1
416
417 self._find_actions(subparsers, actions_module)
418 self._find_actions(subparsers, self)
......
516 def main(self, argv):
517 # Parse args once to find version and debug settings
518 parser = self.get_base_parser()
519 (options, args) = parser.parse_known_args(argv)
......
538 subcommand_parser = self.get_subcommand_parser(
539 options.os_compute_api_version)
540 self.parser = subcommand_parser
546 args = subcommand_parser.parse_args(argv)
......
649 self.cs = client.Client(options.os_compute_api_version,
# 这里self.cs 实际上指向 novaclient.v3.client.Client accoring to api version
650 os_username, os_password, os_tenant_name,
651 tenant_id=os_tenant_id, user_id=os_user_id,
652 auth_url=os_auth_url, insecure=insecure,
653 region_name=os_region_name, endpoint_type=endpoint_type,
654 extensions=self.extensions, service_type=service_type,
655 service_name=service_name, auth_system=os_auth_system,
656 auth_plugin=auth_plugin, auth_token=auth_token,
657 volume_service_name=volume_service_name,
658 timings=args.timings, bypass_url=bypass_url,
659 os_cache=os_cache, http_log_debug=options.debug,
660 cacert=cacert, timeout=timeout)
......
724 args.func(self.cs, args)
在novaclient/v3/shell.py文件中,我们找到了novaclient准备想nova发出REST请求的入口。utils.arg是很重要的修饰器,可以动态的给shell文件的函数加载命令行参数。上文help看到的文档就来自这些utils.arg修饰器。用时,函数最终会调用volumes模块中attach_server_volume来最实际的attach操作。
1393 @utils.arg('server',
1394 metavar='<server>',
1395 help='Name or ID of server.')
1396 @utils.arg('volume',
1397 metavar='<volume>',
1398 help='ID of the volume to attach.')
1399 @utils.arg('device', metavar='<device>', default=None,
nargs='?',
1400 help='Name of the device e.g. /dev/vdb. '
1401 'Use "auto" for autoassign (if supported)')
1402 def do_volume_attach(cs,
args): # cs mean this above self.cs
1403 """Attach a volume to a server."""
1404 if args.device == 'auto':
1405 args.device = None
1406
1407 volume = cs.volumes.attach_server_volume(_find_server(cs, args.server).id,
1408 args.volume,
1409 args.device)
1410
1411
走到这一步,我们回头看到self.cs是如何生成的:在novaclient/shell.py上面的client.Client()调用下,代码最终会走到novaclient.v3.client.Client这个Top level object to the Openstack Computer API.
519 def get_client_class(version):
520 version_map = {
521 '1.1': 'novaclient.v1_1.client.Client',
522 '2': 'novaclient.v1_1.client.Client',
523 '3': 'novaclient.v3.client.Client',
524 }
525 try:
526 client_path = version_map[str(version)]
527 except (KeyError, ValueError):
528 msg = _("Invalid client version '%(version)s'. must be one of: "
529 "%(keys)s") % {'version': version,
530 'keys': ', '.join(version_map.keys())}
531 raise exceptions.UnsupportedVersion(msg)
532
533 return utils.import_class(client_path)
534
535
536 def Client(version, *args, **kwargs):
537 client_class = get_client_class(version)
538 return client_class(*args, **kwargs)
继续打开novaclient/v3/client.py 看看,主要关注是如何初始化的。我们看到nova命令行中经常用到的image, flavor, keypairs, volumes等等object都组合在其中。 最后client.HTTPClient实例化了一个http client。当然我们这次的看点是volume,如何把voulme 操作的request发送出去。
34 class Client(object):
35 """
36 Top-level object to access the OpenStack Compute API.
37
38 Create an instance with your creds::
39
40 >>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL)
41
42 Then call methods on its managers::
43
44 >>> client.servers.list()
45 ...
46 >>> client.flavors.list()
47 ...
......
66 def __init__(self, username, password, project_id, auth_url=None,
......
91 self.images = images.ImageManager(self)
92 self.keypairs = keypairs.KeypairManager(self)
93 self.quotas = quotas.QuotaSetManager(self)
94 self.servers = servers.ServerManager(self)
95 self.services = services.ServiceManager(self)
96 self.usage = usage.UsageManager(self)
97 self.volumes = volumes.VolumeManager(self)
......
106 self.client = client.HTTPClient(username,
107 password,
108 user_id=user_id,
109 projectid=project_id,
......
这里我们找到了那个cs.volumes到底对应着那个object。 就是novaclient/v3/volumes.py文件按中的VolumeManager。这个管理类用来管理volumes的资源。
22 class VolumeManager(base.Manager):
23 """
24 Manage :class:`Volume` resources.
25 """
26
27 def attach_server_volume(self, server, volume_id, device):
28 """
29 Attach a volume identified by the volume ID to the given server ID
30
31 :param server: The server (or it's ID)
32 :param volume_id: The ID of the volume to attach.
33 :param device: The device name
34 :rtype: :class:`Volume`
35 """
36 body = {'volume_id': volume_id, 'device': device}
37 return self._action('attach', server, body)
......
61 def _action(self, action, server, info=None, **kwargs):
62 """
63 Perform a server "action" -- reboot/rebuild/resize/etc.
64 """
65 body = {action: info}
66 self.run_hooks('modify_body_for_action', body, **kwargs)
67 url = '/servers/%s/action' % base.getid(server)
68 return self.api.client.post(url, body=body)
到这里,我们就能看到最后REST API的endpoint,即'/servers/<server_id>/action'。 api client会组装http reqeust body参数,发送给nova的REST API。
最终的Nova API endpoint 在这里可以找到:
http://api.openstack.org/api-ref-compute-v2-ext.html
log 实例:
2014-04-15 05:50:08.876 DEBUG routes.middleware
[req-a623c80a-b4ef-4f01-9903-60a23e84cb58 demo demo] Matched POST
/e40722e5e0c74a0b878c595c0afab5fd/servers/6a17e64d-23c7-46a3-9812-8409ad215e40/os-volume_attachments
from (pid=17464) __call__
/usr/lib/python2.6/site-packages/routes/middleware.py:100
2014-04-15 05:50:08.877 DEBUG routes.middleware
[req-a623c80a-b4ef-4f01-9903-60a23e84cb58 demo demo] Route path:
'/{project_id}/servers/:server_id/os-volume_attachments', defaults: {'action':
u'create', 'controller': <nova.api.openstack.wsgi.Resource object at
0x3986f50>} from (pid=17464) __call__
/usr/lib/python2.6/site-packages/routes/middleware.py:102
2014-04-15 05:50:08.877 DEBUG routes.middleware
[req-a623c80a-b4ef-4f01-9903-60a23e84cb58 demo demo] Match dict: {'action':
u'create', 'server_id': u'6a17e64d-23c7-46a3-9812-8409ad215e40', 'project_id':
u'e40722e5e0c74a0b878c595c0afab5fd', 'controller':
<nova.api.openstack.wsgi.Resource object at 0x3986f50>} from (pid=17464)
__call__ /usr/lib/python2.6/site-packages/routes/middleware.py:103
2014-04-15 05:50:08.878 DEBUG nova.api.openstack.wsgi
[req-a623c80a-b4ef-4f01-9903-60a23e84cb58 demo demo] Action: 'create', body:
{"volumeAttachment": {"device": "/dev/vdc", "volumeId":
"5fe8132e-f937-4c1b-8361-9984f94a7c28"}} from (pid=17464) _process_stack
/opt/stack/nova/nova/api/openstack/wsgi.py:940
2014-04-15 05:50:08.879 DEBUG nova.api.openstack.wsgi
[req-a623c80a-b4ef-4f01-9903-60a23e84cb58 demo demo] Calling method '<bound
method VolumeAttachmentController.create of
<nova.api.openstack.compute.contrib.volumes.VolumeAttachmentController object
at 0x3470110>>' (Content-type='application/json',
Accept='application/json') from (pid=17464) _process_stack
/opt/stack/nova/nova/api/openstack/wsgi.py:945
2014-04-15 05:50:08.880 AUDIT nova.api.openstack.compute.contrib.volumes
[req-a623c80a-b4ef-4f01-9903-60a23e84cb58 demo demo] Attach volume
5fe8132e-f937-4c1b-8361-9984f94a7c28 to instance
6a17e64d-23c7-46a3-9812-8409ad215e40 at /dev/vdc
第一篇novaclient分析文章全文完。转载请指明出处。