分享

Nova attach volume的流程分析

xioaxu790 发表于 2014-8-19 20:55:42 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 0 15835
问题导读
1、Nova中volume挂载流程分为哪些?
2、如何实现发送RPCcast异步请求?





Nova中volume挂载流程分为两部分:挂载命令的发送和接收处理
1 挂载命令的发送
1.1提供API接口
代码来源:nova/api/openstack/contrib/volumes.py:VolumeAttachmentController.create():
  1. @wsgi.serializers(xml=VolumeAttachmentTemplate)  
  2.     def create(self, req, server_id, body):  
  3.         """Attach a volume to an instance."""  
  4.         context = req.environ['nova.context']  
  5.         authorize(context)  
  6.         authorize_attach(context, action='create')  
  7.   
  8.         if not self.is_valid_body(body, 'volumeAttachment'):  
  9.             raise exc.HTTPUnprocessableEntity()  
  10. #从请求中获取卷ID和设备名称  
  11.         volume_id = body['volumeAttachment']['volumeId']   
  12.         device = body['volumeAttachment'].get('device')  
  13.   
  14.         self._validate_volume_id(volume_id)  
  15.   
  16.         msg = _("Attach volume %(volume_id)s to instance %(server_id)s"  
  17.                 " at %(device)s") % locals()  
  18.         LOG.audit(msg, context=context)  
  19.          
  20.         try:  
  21.             instance = self.compute_api.get(context, server_id)#compute_api=compute.API(),compute.API()即为/nova/compute/__init__.py:API(*args,**kwargs),该函数导入了一个类,被导入的类是由_compute_opts中compute_api_class决定的,compute_api_class的默认值为:nova.compute.api.API,所以get()既是nova/compute/api.py:API.get(),根据实例ID获取一个实例。  
  22.             device = self.compute_api.attach_volume(context, instance,  
  23.                                                     volume_id, device)#挂载一个存在的卷到一个存在的实例  
  24.         except exception.NotFound:  
  25.             raise exc.HTTPNotFound()  
  26.         except exception.InstanceInvalidState as state_error:  
  27.             common.raise_http_conflict_for_instance_invalid_state(state_error,  
  28.                     'attach_volume')  
复制代码



1.2发送RPCcast异步请求,给instance挂载上volume_id,挂载点在device.
代码来源:nova/compute/api.py:API.attach_volume(context, instance, volume_id,device)
  1. #volume_api=nova/volume/cinder/api.py:API  
  2.   try:  
  3.             volume = self.volume_api.get(context, volume_id)通过cinderclient从cinder获取volume.  
  4.             self.volume_api.check_attach(context, volume, instance=instance)#检查volume是否可用  
  5.             self.volume_api.reserve_volume(context, volume)  
  6.             self.compute_rpcapi.attach_volume(context, instance=instance,  
  7.                     volume_id=volume_id, mountpoint=device)#compute_rpcapi=nova/compute/rpcapi.py:ComputeAPI  
  8.         except Exception:  
  9.             with excutils.save_and_reraise_exception():  
  10.                 self.db.block_device_mapping_destroy_by_instance_and_device(  
  11.                         context, instance['uuid'], device)  
  12.   
  13. #nova/compute/rpcapi.py:ComputeAPI.attach_volume():  
  14. def attach_volume(self, ctxt, instance, volume_id, mountpoint):  
  15.         instance_p = jsonutils.to_primitive(instance)  
  16.         self.cast(ctxt, self.make_msg('attach_volume',#cast调用  
  17.                 instance=instance_p, volume_id=volume_id,  
  18.                 mountpoint=mountpoint),  
  19.                 topic=_compute_topic(self.topic, ctxt, None, instance))
复制代码



(2013.7.9更新)
2.挂载命令的接收处理
2.1 Cast调用的接收端
挂载命令由nova-compute服务负责处理,代码来源:nova/compute/manager.py:ComputeManager.attach_volume()
  1. #attach_volume()调用_attach_volume():  
  2. def _attach_volume(self, context, volume_id, mountpoint, instance):  
  3.         volume = self.volume_api.get(context, volume_id)  
  4.         context = context.elevated()  
  5.         LOG.audit(_('Attaching volume %(volume_id)s to %(mountpoint)s'),  
  6.                   locals(), context=context, instance=instance)  
  7.         try:  
  8.             connector = self.driver.get_volume_connector(instance)  
  9. #driver=nova/virt/libvirt/driver.py:LibvirtDriver, driver.get_volume_connector(),获取一个iscsi initiator   
  10.             connection_info = self.volume_api.initialize_connection(context,  
  11.                                                                     volume,  
  12.                                                                     connector)#volume_api=nova/volume/cinder.py:API,通过cinderclient调用iscsi的initialize_connection初始化initiator  
  13.         except Exception:  # pylint: disable=W0702  
  14.             with excutils.save_and_reraise_exception():  
  15.                 msg = _("Failed to connect to volume %(volume_id)s "  
  16.                         "while attaching at %(mountpoint)s")  
  17.                 LOG.exception(msg % locals(), context=context,  
  18.                               instance=instance)  
  19.                 self.volume_api.unreserve_volume(context, volume)  
  20.   
  21.         if 'serial' not in connection_info:  
  22.             connection_info['serial'] = volume_id  
  23.   
  24.         try:  
  25.             self.driver.attach_volume(connection_info,#挂载卷  
  26.                                       instance,  
  27.                                       mountpoint)  
  28.             #nova/virt/libvirt/driver.py:LibvirtDriver.attach_volume()  
  29.         except Exception:  # pylint: disable=W0702  
  30.             with excutils.save_and_reraise_exception():  
  31.                 msg = _("Failed to attach volume %(volume_id)s "  
  32.                         "at %(mountpoint)s")  
  33.                 LOG.exception(msg % locals(), context=context,  
  34.                               instance=instance)  
  35.                 self.volume_api.terminate_connection(context,  
  36.                                                      volume,  
  37.                                                      connector)  
  38.   
  39.         #update volume's database status in cinder through cinderclient  
  40.         self.volume_api.attach(context,  
  41.                                volume,  
  42.                                instance['uuid'],  
  43.                                mountpoint)  
复制代码



2.2 挂载卷
代码来源:nova/virt/libvirt/driver.py:LibvirtDriver.attach_volume()
  1. def attach_volume(self, connection_info, instance, mountpoint):  
  2.         instance_name = instance['name']  
  3.         virt_dom = self._lookup_by_name(instance_name)  
  4.         disk_dev = mountpoint.rpartition("/")[2]  
  5.         disk_info = {     
  6.             'dev': disk_dev,  
  7.             'bus': blockinfo.get_disk_bus_for_disk_dev(CONF.libvirt_type,  
  8.                                                        disk_dev),  
  9.             'type': 'disk',  
  10.             }  
  11.             conf = self.volume_driver_method('connect_volume',  
  12.                                          connection_info,  
  13.                                          disk_info)#根据driver的类型调用相应的connect_volume方法,这里将调用nova/virt/libvirt/volume.py:LibvirtISCSIVolumeDriver.connect_volume(),connect_volume方法完成的工作是在发现target,见下面。  
  14.   
  15.         self.set_cache_mode(conf)  
  16.   
  17.         try:  
  18.             # NOTE(vish): We can always affect config because our  
  19.             #             domains are persistent, but we should only  
  20.             #             affect live if the domain is running.  
  21.             ##LOG.info(_('attach_volume action is here! ozg.log'))  
  22.             flags = libvirt.VIR_DOMAIN_AFFECT_CONFIG #flags=3  
  23.             state = LIBVIRT_POWER_STATE[virt_dom.info()[0]]  
  24.             if state == power_state.RUNNING:  
  25.                 flags |= libvirt.VIR_DOMAIN_AFFECT_LIVE  
  26.             virt_dom.attachDeviceFlags(conf.to_xml(), flags)"""conf.to_xml()的值为:<disk type="block" device="disk">
  27.   <driver name="qemu" type="raw" cache="none"/>
  28.   <source dev="/dev/disk/by-path/ip-192.168.88.168:3260-iscsi-iqn.2010-10.org.openstack:volume-c7a768b1-5b4a-49c3-80fe-a1ef007c52c1-lun-1"/>
  29.   <target bus="virtio" dev="vdc"/>
  30.   <serial>c7a768b1-5b4a-49c3-80fe-a1ef007c52c1</serial>
  31. </disk>,attachDeviceFlags():创建一个虚拟设备并挂载到后端"""  
  32.   
  33.         except Exception, ex:  
  34.             if isinstance(ex, libvirt.libvirtError):  
  35.                 errcode = ex.get_error_code()  
  36.                 if errcode == libvirt.VIR_ERR_OPERATION_FAILED:  
  37.                     self.volume_driver_method('disconnect_volume',  
  38.                                               connection_info,  
  39.                                               disk_dev)  
  40.                     raise exception.DeviceIsBusy(device=disk_dev)  
  41.   
  42.             with excutils.save_and_reraise_exception():  
  43.                 self.volume_driver_method('disconnect_volume',  
  44.                                           connection_info,  
  45.                                           disk_dev)  
复制代码



2.3发现target
代码来源:/nova/virt/libvirt/volume.py:LibvirtISCSIVolumeDriver.
  1. connect_volume()  
  2. @lockutils.synchronized('connect_volume', 'nova-')  
  3.         def connect_volume(self, connection_info, disk_info):  
  4.         """Attach the volume to instance_name."""  
  5.         conf = super(LibvirtISCSIVolumeDriver,  
  6.                      self).connect_volume(connection_info,  
  7.                                           disk_info)  
  8.   
  9.         iscsi_properties = connection_info['data']  
  10.         #Multipath用来实现设备的持久化和多路径访问  
  11.         libvirt_iscsi_use_multipath = CONF.libvirt_iscsi_use_multipath  
  12.   
  13.         if libvirt_iscsi_use_multipath:#从配置文件中判断是否支持multipath  
  14.             #multipath installed, discovering other targets if available  
  15.             #multipath should be configured on the nova-compute node,  
  16.             #in order to fit storage vendor  
  17.             out = self._run_iscsiadm_bare(['-m',  
  18.                                           'discovery',  
  19.                                           '-t',  
  20.                                           'sendtargets',  
  21.                                           '-p',  
  22.                                           iscsi_properties['target_portal']],  
  23.                                           check_exit_code=[0, 255])[0] \  
  24.                 or ""  
  25.   
  26.             for ip in self._get_target_portals_from_iscsiadm_output(out):  
  27.                 props = iscsi_properties.copy()  
  28.                 props['target_portal'] = ip  
  29.                 self._connect_to_iscsi_portal(props)  
  30.   
  31.             self._rescan_iscsi()  
  32.         else:  
  33.             self._connect_to_iscsi_portal(iscsi_properties)#连接iscsi,见下面  
  34.   
  35.         host_device = ("/dev/disk/by-path/ip-%s-iscsi-%s-lun-%s" %  
  36.                        (iscsi_properties['target_portal'],  
  37.                         iscsi_properties['target_iqn'],  
  38.                         iscsi_properties.get('target_lun', 0)))  
  39.   
  40.         # The /dev/disk/by-path/... node is not always present immediately  
  41.         # TODO(justinsb): This retry-with-delay is a pattern, move to utils?  
  42.         tries = 0  
  43.         disk_dev = disk_info['dev']  
  44.         while not os.path.exists(host_device):  
  45.             if tries >= CONF.num_iscsi_scan_tries:  
  46.                 raise exception.NovaException(_("iSCSI device not found at %s")  
  47.                                               % (host_device))  
  48.   
  49.             LOG.warn(_("ISCSI volume not yet found at: %(disk_dev)s. "  
  50.                        "Will rescan & retry.  Try number: %(tries)s") %  
  51.                      locals())  
  52.   
  53.             # The rescan isn't documented as being necessary(?), but it helps  
  54.             self._run_iscsiadm(iscsi_properties, ("--rescan",))#重新扫描  
  55.   
  56.             tries = tries + 1#重试tries次,默认3次  
  57.             if not os.path.exists(host_device):  
  58.                 time.sleep(tries ** 2)  
  59.   
  60.         if tries != 0:  
  61.             LOG.debug(_("Found iSCSI node %(disk_dev)s "  
  62.                         "(after %(tries)s rescans)") %  
  63.                       locals())  
  64.   
  65.         if libvirt_iscsi_use_multipath:  
  66.             #we use the multipath device instead of the single path device  
  67.             self._rescan_multipath()  
  68.             multipath_device = self._get_multipath_device_name(host_device)  
  69.             if multipath_device is not None:  
  70.                 host_device = multipath_device  
  71.   
  72.         conf.source_type = "block"  
  73.         conf.source_path = host_device  
  74.         return conf  
复制代码



代码来源:nova/virt/libvirt/volume.py: LibvirtISCSIVolumeDriver
  1. def _connect_to_iscsi_portal(self, iscsi_properties):  
  2.         # NOTE(vish): If we are on the same host as nova volume, the  
  3.         #             discovery makes the target so we don't need to  
  4.         #             run --op new. Therefore, we check to see if the  
  5.         #             target exists, and if we get 255 (Not Found), then  
  6.         #             we run --op new. This will also happen if another  
  7.         #             volume is using the same target.  
  8.         try:  
  9.             self._run_iscsiadm(iscsi_properties, ())  
  10. #执行:iscsiadm -m node -T iqn.2010-10.org.openstack:volume-c7a768b1-5b4a-49c3-80fe-a1ef007c52c1 -p 192.168.88.168:3260 execute /usr/lib/python2.7/dist-packages/nova/utils.py:208  
  11.   
  12.         except exception.ProcessExecutionError as exc:  
  13.             # iscsiadm returns 21 for "No records found" after version 2.0-871  
  14.             if exc.exit_code in [21, 255]:  
  15.                 self._run_iscsiadm(iscsi_properties, ('--op', 'new'))  
  16.             else:  
  17.                 raise  
  18.   
  19.         if iscsi_properties.get('auth_method'):#get()返回CHAP  
  20.             self._iscsiadm_update(iscsi_properties,  
  21.                                   "node.session.auth.authmethod",  
  22.                                   iscsi_properties['auth_method'])  
  23. #执行:iscsiadm -m node -T iqn.2010-10.org.openstack:volume-c7a768b1-5b4a-49c3-80fe-a1ef007c52c1 -p 192.168.88.168:3260 --op update -n node.session.auth.authmethod -v CHAP execute /usr/lib/python2.7/dist-packages/nova/utils.py:208  
  24.   
  25.             self._iscsiadm_update(iscsi_properties,  
  26.                                   "node.session.auth.username",  
  27.                                   iscsi_properties['auth_username'])  
  28. #执行:iscsiadm -m node -T iqn.2010-10.org.openstack:volume-c7a768b1-5b4a-49c3-80fe-a1ef007c52c1 -p 192.168.88.168:3260 --op update -n node.session.auth.username -v AREmd94wksBpTxEcJ5Yh execute /usr/lib/python2.7/dist-packages/nova/utils.py:208  
  29.   
  30.             self._iscsiadm_update(iscsi_properties,  
  31.                                   "node.session.auth.password",  
  32.                                   iscsi_properties['auth_password'])  
  33. #执行:iscsiadm -m node -T iqn.2010-10.org.openstack:volume-c7a768b1-5b4a-49c3-80fe-a1ef007c52c1 -p 192.168.88.168:3260 --op update -n node.session.auth.password -v wV4WuDvACoVyYA9Ru5tz execute /usr/lib/python2.7/dist-packages/nova/utils.py:208  
  34.   
  35.         #duplicate logins crash iscsiadm after load,  
  36.         #so we scan active sessions to see if the node is logged in.  
  37.         out = self._run_iscsiadm_bare(["-m", "session"],  
  38.                                       run_as_root=True,  
  39.                                       check_exit_code=[0, 1, 21])[0] or ""  
  40. #执行:iscsiadm ['-m', 'session']: stdout=  
  41. tcp: [20] 192.168.88.168:3260,1  
  42. iqn.2010-10.org.openstack:volume-14abe479-830d-4661-8047-a49414970f67  
  43. tcp: [4] 192.168.88.168:3260,1 iqn.2010-10.org.openstack:volume-993d0ed4-78b2-4c9f-a881-6aacafa173eb  
  44. stderr= _run_iscsiadm_bare /usr/lib/python2.7/dist-packages/nova/virt/libvirt/volume.py:423  
  45.   
  46.         portals = [{'portal': p.split(" ")[2], 'iqn': p.split(" ")[3]}  
  47.                    for p in out.splitlines() if p.startswith("tcp:")]  
  48. # portals=[{'iqn': 'iqn.2010-10.org.openstack:volume-14abe479-830d-4661-8047-a49414970f67', 'portal': '192.168.88.168:3260,1'}, {'iqn': 'iqn.2010-10.org.openstack:volume-993d0ed4-78b2-4c9f-a881-6aacafa173eb', 'portal': '192.168.88.168:3260,1'}]  
  49.   
  50.         stripped_portal = iscsi_properties['target_portal'].split(",")[0]  
  51.         if len(portals) == 0 or len([s for s in portals  
  52.                                      if stripped_portal ==  
  53.                                      s['portal'].split(",")[0]  
  54.                                      and  
  55.                                      s['iqn'] ==  
  56.                                      iscsi_properties['target_iqn']]  
  57.                                     ) == 0:  
  58.             try:  
  59.                 self._run_iscsiadm(iscsi_properties,  
  60.                                    ("--login",),  
  61.                                    check_exit_code=[0, 255])  
  62. #执行:iscsiadm -m node -T iqn.2010-10.org.openstack:volume-c7a768b1-5b4a-49c3-80fe-a1ef007c52c1 -p 192.168.88.168:3260 --login execute /usr/lib/python2.7/dist-packages/nova/utils.py:208  
  63.   
  64.             except exception.ProcessExecutionError as err:  
  65.                 #as this might be one of many paths,  
  66.                 #only set successfull logins to startup automatically  
  67.                 if err.exit_code in [15]:  
  68.                     self._iscsiadm_update(iscsi_properties,  
  69.                                           "node.startup",  
  70.                                           "automatic")  
  71. #执行:iscsiadm -m node -T iqn.2010-10.org.openstack:volume-c7a768b1-5b4a-49c3-80fe-a1ef007c52c1 -p 192.168.88.168:3260 --op update -n node.startup -v automatic execute /usr/lib/python2.7/dist-packages/nova/utils.py:208  
  72.                     return  
  73.   
  74.             self._iscsiadm_update(iscsi_properties,  
  75.                                   "node.startup",  
  76.                                   "automatic")  
复制代码




本文转载自:http://blog.csdn.net/epugv/article/details/9303785

没找到任何评论,期待你打破沉寂

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条