openstack nova volume-attach 源码分析novaclient(一)

在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分析文章全文完。转载请指明出处。

你可能感兴趣的:(python,openstack,nova)