问题背景:
对虚拟机执行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设备的更换。
问题分析
- 对libvirt中的源码分析,发现在执行"blockdev-open-tray"的时候,会有一个判断条件:
在执行qemuMonitorBlockdevTrayOpen函数的时候,会判断diskPriv->tray和disk->tray_status是否满足条件,前者判断当前的设备是否具有tray,后者判断该tray是否是打开状态。
通过gdb调试,发现diskPriv->tray是false,因此找到原因。即restore或者从managedsave启动之后,没有设置diskPriv->tray的状态,导致没有open,直接remove-medium,导致出错。
该数据结构与虚拟机之间的关系如下:
可以看到_virDomainObj是虚拟机在libvirt中的具体表示,该结构体中的virDomainDef是表示的是该虚拟机中的一些配置信息,其中virDomainDiskDef表示的是磁盘的表示,这个是数组指针,指向的是一组磁盘设备信息。而tray这个变量正是设备信息中的属性信息。因此需要找到这个属性信息是在什么位置进行初始化即可。
2. 虚拟机直接启动的时候,这个状态是存在的,而restore和start的代码高度重合,需要找到两者之间的不同点:
上面代码incoming是restore的流程,如果当前虚拟机从内存文件启动,则走该流程;如果云主机正常启动,则会走qemuProcessRefreshState代码,该函数正是更新云主机设备状态的函数。
3. 虚拟机热迁移也不存在上述问题,通过对虚拟机热迁移代码进行分析,发现:
云主机迁移完成之后,在目标端会执行该函数,所以设备状态得到了更新。
问题解决
通过以上分析,可以看到源头问题是:虚拟机从内存文件启动的时候,没有调用qemuProcessRefreshState函数,导致设备属性没有正确初始化导致的。因此解决该问题的办法两种:
- 在启动的时候,调用qemuProcessRefreshState函数,完成设备的初始化。
- 在挂载和卸载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