问题背景:

对虚拟机执行save和restore,或者managedsave之后,再start,则系统起来之后,无法挂载ISO文件,报出的错误是:

libvirtError:internal error:unable to execute QEMU command ‘blockdev-remove-medium’:Tray of device 'ide0-1-0' is not open。

挂载ISO和卸载ISO的命令是:

domain.updateDeviceFlags(device_xml, flags)

挂载ISO时的XML文件是:

<disk type='network' device='cdrom'>
    <driver name='qemu' type='raw' cache='none'/>
    <source protocol='rbd' name='images/uuid'>
    <target bus='ide' dev='hdc'/>
    <readomly/>
</disk>

卸载时的xml文件:

<disk type='network' device='cdrom'>
    <driver name='qemu' type='raw' cache='none'/>
    <target bus='ide' dev='hdc'/>
    <readomly/>
</disk>

虚拟机在启动的时候,qemu执行的命令行是:

-device ide-cd,bus=ide.0,unit=0,drive=libvirt-3-format,id=ide0-0-0,write-cache=on
-device ide-cd,bus=ide.1,unit=0,id=ide0-1-0,write-cache=on

代码调试流程
云主机在执行save和restore命令之后,出现如下的错误:
virsh update-device vm1 cdrom.xml 
错误:从 cdrom.xml 中更新设备失败
错误:内部错误:无法执行 QEMU 命令 'blockdev-remove-medium':Tray of device 'ide0-1-0' is not open
排查代码发现:
diskPriv->tray=false,导致opentray的条件无法通过,下面分析挂载和卸载ISO设备的流程:

virDomainAttachDeviceFlags
	|
	|--qemuDomainAttachDeviceFlags(qemuHypervisorDriver.domainAttachDeviceFlags )
		|
		|--virNWFilterReadLockFilterUpdates
		|
		|--qemuDomainObjFromDomain
		|
		|--virDomainAttachDeviceFlagsEnsureACL
		|
		|--qemuDomainObjBeginJob
		|
		|--virDomainObjUpdateModificationImpact
		|
		|--qemuDomainAttachDeviceLiveAndConfig
			|
			|--qemuDomainAttachDeviceLive
				|
				|--qemuDomainAttachDeviceDiskLive(disk分支)
				|	|
				|	|--qemuDomainChangeEjectableMedia(条件判断:cdrom或者floppy)
				|	|	|
				|	|	|--qemuDomainChangeMediaBlockdev
				|	|		|
				|	|		|--qemuMonitorBlockdevTrayOpen
				|	|		|
				|	|		|--qemuMonitorBlockdevMediumRemove
                |   |       |
                |   |       |--qemuBlockStorageSourceChainAttach
				|	|		|
				|	|		|--qemuMonitorBlockdevMediumInsert
				|	|		|
				|	|		|--qemuMonitorBlockdevTrayClose
				|	|
				|	|
				|	|--qemuDomainAttachDeviceDiskLiveInternal(磁盘走这个分支)
				|
				|--qemuDomainAttachControllerDevice(controller分支)
				|
				|--qemuDomainAttachLease(lease分支)
				|
				|--qemuDomainAttachNetDevice(网卡分支)
				|
				|--qemuDomainAttachHostDevice(hostdevice分支)
				|
				|--qemuDomainAttachRedirdevDevice
				|
				|--qemuDomainAttachChrDevice
				|
				|--qemuDomainAttachRNGDevice
				|
				|--qemuDomainAttachMemory
				|
				|--qemuDomainAttachShmemDevice
				|
				|--qemuDomainAttachWatchdog
				|
				|--qemuDomainAttachInputDevice
				|
				|--qemuDomainAttachVsockDevice
				|
				|--qemuDomainAttachFSDevice
virDomainUpdateDeviceFlags
	|
	|--qemuDomainUpdateDeviceFlags
		|
		|--qemuDomainUpdateDeviceLive
			|
			|--qemuDomainChangeDiskLive
				|
				|--qemuDomainChangeEjectableMedia
					|
					|--qemuDomainChangeMediaBlockdev
						|
						|--qemuMonitorBlockdevTrayOpen
						|
						|--qemuMonitorBlockdevMediumRemove
                        |
                        |--qemuBlockStorageSourceChainAttach
						|
						|--qemuMonitorBlockdevMediumInsert
						|
						|--qemuMonitorBlockdevTrayClose

上述四个函数在实际执行中,是发送qemu-monitor-command命令行的方式:

{"execute":"blockdev-open-tray","arguments":{"id":"ide0-1-0","force":false},"id":"libvirt-429"}
{"execute":"blockdev-remove-medium","arguments":{"id":"ide0-1-0"},"id":"libvirt-430"}
{"execute":"blockdev-add","arguments":{"driver":"rbd","pool":"images","image":"90780e60-fd0b-4a55-9719-606319732573","node-name":"libvirt-4-storage","cache":{"direct":true,"no-flush":false},"auto-read-only":true,"discard":"unmap"}

或者

{"execute":"blockdev-add","arguments":{"node-name":"libvirt-4-format","read-only":true,"cache":{"direct":true,"no-flush":false},"driver":"raw","file":"libvirt-4-storage"}
{"execute":"blockdev-insert-medium","arguments":{"id":"ide0-1-0","node-name":"libvirt-4-format"}
{"execute":"blockdev-close-tray","arguments":{"id":"ide0-1-0"},"id":"libvirt-434"}

可以看到libvirt通过向qemu进程发送"blockdev-open-tray","blockdev-remove-medium","blockdev-add","blockdev-insert-medium","blockdev-close-tray"等流程来完成ISO设备的挂载和卸载,本质上是完成ISO设备的更换。

问题分析

  1. 对libvirt中的源码分析,发现在执行"blockdev-open-tray"的时候,会有一个判断条件:

Qemu如何挂载硬盘 qemu挂载iso_python

在执行qemuMonitorBlockdevTrayOpen函数的时候,会判断diskPriv->tray和disk->tray_status是否满足条件,前者判断当前的设备是否具有tray,后者判断该tray是否是打开状态。

通过gdb调试,发现diskPriv->tray是false,因此找到原因。即restore或者从managedsave启动之后,没有设置diskPriv->tray的状态,导致没有open,直接remove-medium,导致出错。

该数据结构与虚拟机之间的关系如下:

Qemu如何挂载硬盘 qemu挂载iso_python_02

可以看到_virDomainObj是虚拟机在libvirt中的具体表示,该结构体中的virDomainDef是表示的是该虚拟机中的一些配置信息,其中virDomainDiskDef表示的是磁盘的表示,这个是数组指针,指向的是一组磁盘设备信息。而tray这个变量正是设备信息中的属性信息。因此需要找到这个属性信息是在什么位置进行初始化即可。

2. 虚拟机直接启动的时候,这个状态是存在的,而restore和start的代码高度重合,需要找到两者之间的不同点:

Qemu如何挂载硬盘 qemu挂载iso_云原生_03

上面代码incoming是restore的流程,如果当前虚拟机从内存文件启动,则走该流程;如果云主机正常启动,则会走qemuProcessRefreshState代码,该函数正是更新云主机设备状态的函数。 

3. 虚拟机热迁移也不存在上述问题,通过对虚拟机热迁移代码进行分析,发现:

云主机迁移完成之后,在目标端会执行该函数,所以设备状态得到了更新。

Qemu如何挂载硬盘 qemu挂载iso_Qemu如何挂载硬盘_04

问题解决

通过以上分析,可以看到源头问题是:虚拟机从内存文件启动的时候,没有调用qemuProcessRefreshState函数,导致设备属性没有正确初始化导致的。因此解决该问题的办法两种:

  1. 在启动的时候,调用qemuProcessRefreshState函数,完成设备的初始化。
  2. 在挂载和卸载ISO设备的时候,执行qemuMonitorBlockdevTrayOpen函数不再进行条件判断直接发送open命令。

方式1:涉及到虚拟机启动和恢复的流程,需要不断验证,该方式可以提交到社区进行review,认可之后再进行反合。

方式2:由于qemu层面已经模拟出来CDROM device,libvirt层面的数据即使没有初始化仍然可以向虚拟机进程发送open命令,可以正常卸载和挂载ISO设备。

测试结果

[root@localhost iso_test]# virsh managedsave vm1

libvirt 已保存域 'vm1' 的状态

[root@localhost iso_test]# virsh start vm1
Domain 'vm1' started

[root@localhost iso_test]# virsh qemu-monitor-command --hmp vm1 info block
libvirt-2-format: /root/code/xmllib/cirros-0.5.2-x86_64-disk.img (qcow2)
    Attached to:      /machine/peripheral/virtio-disk0/virtio-backend
    Cache mode:       writethrough

ide0-1-0: [not inserted]
    Attached to:      ide0-1-0
    Removable device: not locked, tray closed


[root@localhost iso_test]# virsh update-device vm1 --file cdrom.xml 
成功更新设备

[root@localhost iso_test]# virsh qemu-monitor-command --hmp vm1 info block
libvirt-2-format: /root/code/xmllib/cirros-0.5.2-x86_64-disk.img (qcow2)
    Attached to:      /machine/peripheral/virtio-disk0/virtio-backend
    Cache mode:       writethrough

libvirt-3-format: /root/code/xmllib/iso_test/cdrom.iso (raw, read-only)
    Attached to:      ide0-1-0
    Removable device: not locked, tray closed
    Cache mode:       writeback, direct


[root@localhost iso_test]# virsh update-device vm1 --file nocdrom.xml
成功更新设备

[root@localhost iso_test]# virsh qemu-monitor-command --hmp vm1 info block
libvirt-2-format: /root/code/xmllib/cirros-0.5.2-x86_64-disk.img (qcow2)
    Attached to:      /machine/peripheral/virtio-disk0/virtio-backend
    Cache mode:       writethrough

ide0-1-0: [not inserted]
    Attached to:      ide0-1-0
    Removable device: not locked, tray closed