挂载卷到虚机逻辑(FC)

nova.compute.manager.do_attach_volume --> nova.compute.manager.ComputeManager#_attach_volume

bdm.attach(context, instance, self.volume_api, self.driver,
                       do_driver_attach=True)

调用nova.virt.block_device.DriverVolumeBlockDevice#attach,这是核心步骤

def attach(self, context, instance, volume_api, virt_driver,
           do_check_attach=True, do_driver_attach=False, **kwargs):
    volume = volume_api.get(context, self.volume_id)
    if do_check_attach:
        volume_api.check_attach(context, volume, instance=instance)

    volume_id = volume['id']
    context = context.elevated()

    connector = virt_driver.get_volume_connector(instance)

步骤:
一、 volume = volume_api.get(context, self.volume_id) 调cinder的restapi,得到卷信息
二、 volume_api.check_attach(context, volume, instance=instance) 确认是否已经挂载了虚机
三、 virt_driver.get_volume_connector(instance) 获取卷连接信息。
nova.virt.libvirt.driver.LibvirtDriver#get_volume_connector:

from os_brick.initiator import connector

def get_volume_connector(self, instance):
    root_helper = utils.get_root_helper()
    return connector.get_connector_properties(
        root_helper, CONF.my_block_storage_ip,
        CONF.libvirt.volume_use_multipath,
        enforce_multipath=True,
        host=CONF.host)

os_brick.initiator.connector.get_connector_properties 方法里,先是通过

    props = {}
    props['platform'] = platform.machine()
    props['os_type'] = sys.platform
    props['ip'] = my_ip
    props['host'] = host if host else socket.gethostname()

获取一些平台信息,然后遍历connector_list,创建连接对象connector。

for item in connector_list:
    connector = importutils.import_class(item)

connector_list是数组,存了不同协议的连接信息

# List of connectors to call when getting
# the connector properties for a host
connector_list = [
    'os_brick.initiator.connectors.base.BaseLinuxConnector',
    'os_brick.initiator.connectors.iscsi.ISCSIConnector',
    'os_brick.initiator.connectors.fibre_channel.FibreChannelConnector',
    
]

如果前面获取的平台信息props['platform']、props['os_type']能和connector里信息匹配,就调用connector对象静态方法get_connector_properties()得到必要的信息并merge_dict 合并保存至字典里,最后一起返回。

if (utils.platform_matches(props['platform'], connector.platform) and
   utils.os_matches(props['os_type'], connector.os_type)):
    props = utils.merge_dict(props,
                             connector.get_connector_properties(
                                 root_helper,
                                 host=host,
                                 multipath=multipath,
                                 enforce_multipath=enforce_multipath,
                                 execute=execute))

拿FC举例,FC对应connector_list的'os_brick.initiator.connectors.fibre_channel.FibreChannelConnector'。FibreChannelConnector方法没有定义参数platform和os_type,这两个参数在父类InitiatorConnector作了初始化: # This object can be used on any platform (x86, S390)
platform = initiator.PLATFORM_ALL
# This object can be used on any os type (linux, windows)
os_type = initiator.OS_TYPE_ALL
PLATFORM_ALL = 'ALL',OS_TYPE_ALL = 'ALL',所以platform = 'ALL',os_type = 'ALL'。
我们看utils.platform_matches(props['platform'], connector.platform)这个方法里,

def platform_matches(current_platform, connector_platform):
    curr_p = current_platform.upper()
    conn_p = connector_platform.upper()
    if conn_p == 'ALL':
        return True

    # Add tests against families of platforms
    if curr_p == conn_p:
        return True

    return False

如果current_platform等于‘ALL’则匹配返回True。'os_brick.initiator.connectors.fibre_channel.FibreChannelConnector'符合条件,执行FibreChannelConnector.get_connector_properties。
FibreChannelConnector.get_connector_properties:

@staticmethod
def get_connector_properties(root_helper, *args, **kwargs):
    """The Fibre Channel connector properties."""
    props = {}
    fc = linuxfc.LinuxFibreChannel(root_helper,
                                   execute=kwargs.get('execute'))

    wwpns = fc.get_fc_wwpns()
    if wwpns:
        props['wwpns'] = wwpns
    wwnns = fc.get_fc_wwnns()
    if wwnns:
        props['wwnns'] = wwnns

    return props

获取系统wwpns存入props['wwpns']
来看fc.get_fc_wwpns()的逻辑:

    def get_fc_wwpns(self):
        """Get Fibre Channel WWPNs from the system, if any."""

        # Note(walter-boring) modern Linux kernels contain the FC HBA's in /sys
        # and are obtainable via the systool app
        hbas = self.get_fc_hbas()

        wwpns = []
        for hba in hbas:
            if hba['port_state'] == 'Online':
                wwpn = hba['port_name'].replace('0x', '')
                wwpns.append(wwpn)

        return wwpns
  1. 先获得hba信息:
    hbas = self.get_fc_hbas()对应os_brick.initiator.linuxfc.LinuxFibreChannel#get_fc_hbas
    def get_fc_hbas(self):
        """Get the Fibre Channel HBA information."""
        if not self.has_fc_support():
            return []
        out = None
        try:
            out, _err = self._execute('systool', '-c', 'fc_host', '-v',
                                      run_as_root=True,
                                      root_helper=self._root_helper)

逻辑简单的说就是:

  • 先判断'/sys/class/fc_host'是否存在
  • 如果存在,则用root权限执行systool -c fc_host -v去把'/sys/class/fc_host'记录的hba信息拿出来。
挂载卷到虚机逻辑(FC)_第1张图片
图片.png
  1. 如果hba['port_state'] HBA连接状态是online,则把port_name = "0x10008c7cff409e80" 替换修改成为"10008c7cff409e80"作为wwpns

再比如ISCSI,对应'os_brick.initiator.connectors.iscsi.ISCSIConnector',ISCSIConnector.get_connector_properties会去执行 cat /etc/iscsi/initiatorname.iscsi 来获得iscsi信息存入props['initiator']

def get_connector_properties(root_helper, *args, **kwargs):
    """The iSCSI connector properties."""
    props = {}
    iscsi = ISCSIConnector(root_helper=root_helper,
                           execute=kwargs.get('execute'))
    initiator = iscsi.get_initiator()
    if initiator:
        props['initiator'] = initiator

    return props

四、 获得连接信息后,就做连接初始化:

    connection_info = volume_api.initialize_connection(context,
                                                       volume_id,
                                                       connector)

调用cinder的restapi, POST http://172.24.9.198:8776/v2/406cd353135e44f0ade98f53d92d5d8b/volumes/b12d9043-895f-4f06-8e3c-b33094061cc8/action '{"os-initialize_connection": {"connector": {"platform": "x86_64", "host": "node11", "do_local_attach": false, "ip": "172.24.9.21", "os_type": "linux2", "multipath": false, "initiator": "iqn.1994-05.com.redhat:e08ae3b9ff2"}}}' ,把之前得到的连接信息作为请求参数一并传递给cinder。

如果cinder端出错,调用os-terminate_connection断开连接。cinder出错原因:比如nova想要连接FC,但是传递的connector里没有携带wwpns,会返回错误码500。在cinder-volume.log里可以看到 ERROR cinder.volume.manager KeyError: 'wwpns'。

五、 若cinder创建连接成功后,返回完整的连接信息(待补充),然后做卷连接:

volume_api.attach(context, volume_id, instance.uuid,
                                  self['mount_device'], mode=mode)

调用cinder的restapi, (待补充)

你可能感兴趣的:(挂载卷到虚机逻辑(FC))