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
- 先获得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信息拿出来。
- 如果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, (待补充)