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