libvirt是CS架构应用,用户通过client与server交互,server与client通过socket连接通信。基本架构图如下所示:
在libvirt中接口的调用方式分为两种:
if libvirt is None:
libvirt = __import__('libvirt')
return tpool.proxy_call(
(libvirt.virDomain, libvirt.virConnect),
libvirt.openAuth, uri, auth, flags)
domain = self._conn.defineXML(xml)
domain.createWithFlags(launch_flags)
由这个流程我们可以看到,openstack中主要通过python-libvirt库与libvirtd进程交互,完成对虚拟机实例的操作。python-libvirt是由libvirt提供的一个面向python client的连接组件,包含以下内容:
/usr/share/pyshared/libvirt.py #libvirt python接口文件,包含大部分的libvirt接口
/usr/share/pyshared/libvirt_lxc.py #lxc接口文件,因为这部分接口参数不能自动转换,所以通过手动重写完成转换
/usr/share/pyshared/libvirt_qemu.py #与上面的类似,qemu相关的。
/usr/lib/python2.7/dist-packages/libvirtmod_qemu.so
/usr/lib/python2.7/dist-packages/libvirtmod_lxc.so
/usr/lib/python2.7/dist-packages/libvirtmod.so
在libvirt代码中有一个专门的目录用于存放接口python化相关的代码。所有的libvirt接口被分为了两个部分:
下面继续以创建虚拟机为例说明libvirt中接口调用的流程
defineXML
接口定义虚拟机。该接口返回一个虚拟机的domain对象,用户接下来可以通过这个对象操作虚拟机。domain = self._conn.defineXML(xml)
第一步只是执行了定义操作,相当于libvirt开始管理这台虚拟机。但是此时实际的虚拟机还没有运行,用户还无法使用。nova中调用domain.createWithFlags(launch_flags)
接口,用第一步中定义的虚拟机规格在hypervisor层把虚拟机真正创建起来。
createWithFlags
调用python-libvirt封装的virDomainCreateWithFlags
def createWithFlags(self, flags=0):
ret = libvirtmod.virDomainCreateWithFlags(self._o, flags)
if ret == -1: raise libvirtError ('virDomainCreateWithFlags() failed', dom=self)
return ret
createWithFlags
接口是直接封装的,参数不需要转换。下一步会在转换中调用到libvirt.c中的virDomainCreateWithFlags
接口,由此进入libvirt api层。 传入的flag值为0,flag取值范围及对应含义如下:VIR_DOMAIN_NONE = 0, /* Default behavior */
VIR_DOMAIN_START_PAUSED = 1 << 0, /* Launch guest in paused state */
VIR_DOMAIN_START_AUTODESTROY = 1 << 1, /* Automatically kill guest when virConnectPtr is closed */
VIR_DOMAIN_START_BYPASS_CACHE = 1 << 2, /* Avoid file system cache pollution */
VIR_DOMAIN_START_FORCE_BOOT = 1 << 3, /* Boot, discarding any managed save */
int
virDomainCreateWithFlags(virDomainPtr domain, unsigned int flags) {
virConnectPtr conn;
VIR_DOMAIN_DEBUG(domain, "flags=%x", flags);
virResetLastError();#重置错误码。
#libvirt中采用了线程池机制,每次从线程池中取出一个线程执行当前的请求。
#线程中会保存当前线程最后产生的错误码,因此在请求最开始的位置就要把原有的错误重置,防止误报。
#合法性检查,传入的domain指针及其中的conn指针是否为正确的类型。
if (!VIR_IS_CONNECTED_DOMAIN(domain)) {
virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__);
virDispatchError(NULL);
return -1;
}
#获取domain中的conn指针,如果conn是只读的,则设置错误码并直接退出。因为创建虚拟机属于修改操作。
conn = domain->conn;
if (conn->flags & VIR_CONNECT_RO) {
virLibDomainError(VIR_ERR_OPERATION_DENIED, __FUNCTION__);
goto error;
}
#从这里跳转到具体的driver中执行。驱动在libvirtd启动的时候加载,映射关系由conn指针初始化的时候指定。在配置文件中可以配置默认的conn driver,也可以在创建conn的时候通过接口参数指定。
if (conn->driver->domainCreateWithFlags) {
int ret;
ret = conn->driver->domainCreateWithFlags(domain, flags);
if (ret < 0)
goto error;
return ret;
}
#如果驱动中没有实现对应的方法,直接报no support错误。
virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__);
error:
virDispatchError(domain->conn);
return -1;
}
static int
qemuDomainCreateWithFlags(virDomainPtr dom, unsigned int flags)
{
virQEMUDriverPtr driver = dom->conn->privateData;
virDomainObjPtr vm;
int ret = -1;
#首先检查传入flag参数的合法性,必须是上面提到的几个可选值之一。这里是一个宏来实现的,如果出错直接返回-1。
virCheckFlags(VIR_DOMAIN_START_PAUSED |
VIR_DOMAIN_START_AUTODESTROY |
VIR_DOMAIN_START_BYPASS_CACHE |
VIR_DOMAIN_START_FORCE_BOOT, -1);
#获取虚拟机的vm指针
if (!(vm = qemuDomObjFromDomain(dom)))
return -1;
#访问控制,判断当前conn是否有权限执行该操作。目前配置的访问控制标签默认为None,即所有用户都有最高权限。
if (virDomainCreateWithFlagsEnsureACL(dom->conn, vm->def) < 0)
goto cleanup;
#获取虚拟机job锁,类型为MODIFY,可选类型如后所示。只有获得该锁才能继续执行。
if (qemuDomainObjBeginJob(driver, vm, QEMU_JOB_MODIFY) < 0)
goto cleanup;
#检查虚拟机是否已经处于运行状态
if (virDomainObjIsActive(vm)) {
virReportError(VIR_ERR_OPERATION_INVALID,
"%s", _("domain is already running"));
goto endjob;
}
#启动虚拟机
if (qemuDomainObjStart(dom->conn, driver, vm, flags) < 0)
goto endjob;
ret = 0;
endjob:
#该job是同步操作,任务结束之后要释放job锁。
if (!qemuDomainObjEndJob(driver, vm))
vm = NULL;
cleanup:
if (vm)
virObjectUnlock(vm);
return ret;
}
QEMU_JOB_NONE = 0, /* Always set to 0 for easy if (jobActive) conditions */
QEMU_JOB_QUERY, /* Doesn't change any state */
QEMU_JOB_DESTROY, /* Destroys the domain (cannot be masked out) */
QEMU_JOB_SUSPEND, /* Suspends (stops vCPUs) the domain */
QEMU_JOB_MODIFY, /* May change state */
QEMU_JOB_ABORT, /* Abort current async job */
QEMU_JOB_MIGRATION_OP, /* Operation influencing outgoing migration */
/* The following two items must always be the last items before JOB_LAST */
QEMU_JOB_ASYNC, /* Asynchronous job */
QEMU_JOB_ASYNC_NESTED, /* Normal job within an async job */
QEMU_JOB_LAST
};
qemuDomainObjStart
,这个函数处理了虚拟机wakeup的逻辑并且在虚拟机启动成功之后发送事件通知。static int
qemuDomainObjStart(virConnectPtr conn, virQEMUDriverPtr driver, virDomainObjPtr vm, unsigned int flags)
{
int ret = -1;
char *managed_save;
#根据传入的参数确定虚拟机的启动模式
bool start_paused = (flags & VIR_DOMAIN_START_PAUSED) != 0;
bool autodestroy = (flags & VIR_DOMAIN_START_AUTODESTROY) != 0;
bool bypass_cache = (flags & VIR_DOMAIN_START_BYPASS_CACHE) != 0;
bool force_boot = (flags & VIR_DOMAIN_START_FORCE_BOOT) != 0;
unsigned int start_flags = VIR_QEMU_PROCESS_START_COLD;
start_flags |= start_paused ? VIR_QEMU_PROCESS_START_PAUSED : 0;
start_flags |= autodestroy ? VIR_QEMU_PROCESS_START_AUTODESTROY : 0;
#组装hibernate文件的路径
managed_save = qemuDomainManagedSavePath(driver, vm);
if (!managed_save)
goto cleanup;
#如果存在hibernate文件,则从该文件恢复虚拟机
if (virFileExists(managed_save)) {
#启动时可以指定强制启动,此时移除hibernate文件并按照正常流程启动虚拟机
if (force_boot) {
if (unlink(managed_save) < 0) {
virReportSystemError(errno,
_("cannot remove managed save file %s"),
managed_save);
goto cleanup;
}
vm->hasManagedSave = false;
} else {
#从hibernate文件恢复虚拟机,因为我们目前还不支持内存快照的功能,暂时不跟进了。
ret = qemuDomainObjRestore(conn, driver, vm, managed_save,
start_paused, bypass_cache);
#恢复成功,移除suspend文件
if (ret == 0) {
if (unlink(managed_save) < 0)
VIR_WARN("Failed to remove the managed state %s", managed_save);
else
vm->hasManagedSave = false;
}
#如果恢复失败,则忽略suspend文件直接按正常流程启动虚拟机
if (ret > 0)
VIR_WARN("Ignoring incomplete managed state %s", managed_save);
else
goto cleanup;
}
}
#启动qemu进程
ret = qemuProcessStart(conn, driver, vm, NULL, -1, NULL, NULL,
VIR_NETDEV_VPORT_PROFILE_OP_CREATE, start_flags);
#虚拟机启动完成之后,验证对应的启动参数,并且在/var/run/libvirt/qemu目录下保存一份运行状态的配置文件,这个文件的内容在虚拟机配置改变的时候会随之改变,
virDomainAuditStart(vm, "booted", ret >= 0);
if (ret >= 0) {
#向事件队列发送虚拟机启动事件。如果此时有程序在监听此事件就会收到相应的通知。
virDomainEventPtr event =
virDomainEventNewFromObj(vm,
VIR_DOMAIN_EVENT_STARTED,
VIR_DOMAIN_EVENT_STARTED_BOOTED);
if (event) {
qemuDomainEventQueue(driver, event);
#如果指定了启动之后pause虚拟机,同时还要发送一个虚拟机pause事件。
if (start_paused) {
event = virDomainEventNewFromObj(vm,
VIR_DOMAIN_EVENT_SUSPENDED,
VIR_DOMAIN_EVENT_SUSPENDED_PAUSED);
if (event)
qemuDomainEventQueue(driver, event);
}
}
}
cleanup:
VIR_FREE(managed_save);
return ret;
}
qemuProcessStart
函数,这个函数处理qemu进程启动的主逻辑流程。由于这个函数中逻辑比较长,就不直接贴代码了,只选取其中关键部分了解一下。virCheckFlags(VIR_QEMU_PROCESS_START_COLD |
VIR_QEMU_PROCESS_START_PAUSED |
VIR_QEMU_PROCESS_START_AUTODESTROY, -1);
if (virDomainObjIsActive(vm)) {
virReportError(VIR_ERR_OPERATION_INVALID,
"%s", _("VM is already active"));
virObjectUnref(cfg);
return -1;
}
if (virDomainObjSetDefTransient(caps, driver->xmlopt, vm, true) < 0)
goto cleanup;
vm->def->id = qemuDriverAllocateID(driver);
qemuDomainSetFakeReboot(driver, vm, false);
virDomainObjSetState(vm, VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_UNKNOWN);
if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) {
char *xml = qemuDomainDefFormatXML(driver, vm->def, 0);
int hookret;
hookret = virHookCall(VIR_HOOK_DRIVER_QEMU, vm->def->name,
VIR_HOOK_QEMU_OP_PREPARE, VIR_HOOK_SUBOP_BEGIN,
NULL, xml, NULL);
VIR_FREE(xml);
/*
* If the script raised an error abort the launch
*/
if (hookret < 0)
goto cleanup;
}
if (!(priv->qemuCaps = virQEMUCapsCacheLookupCopy(driver->qemuCapsCache,
vm->def->emulator)))
goto cleanup;
#处理配置文件中的直通网卡。虽然在配置文件中指定设备类型为interface,但是实际上直通网卡还是一个PCI设备,因此将其加入hostdev设备中。
if (qemuNetworkPrepareDevices(vm->def) < 0)
goto cleanup;
#处理直通设备。直通设备分为三类:PCI设备,USB设备及scsi设备。
#PCI设备的处理逻辑比较复杂,大致流程为
# - 检查配置的PCI设备是否已经直通到其他虚拟机
# - 移除这些设备的原有驱动
# - 重置这些设备
# - 对于SRIOV的网卡直通设备,需要额外设置一些网络相关的参数
# - 在qemu驱动中将这些设备设置为active状态
# - 在qemu驱动的未启用设备列表中移除这些设备
# - 在qemu驱动中记录当前使用这些设备的虚拟机
# - 记录这些设备的原始状态
# - 从host上隐藏这些设备
#经过以上处理之后,配置的PCI设备就可以作为一个普通的虚拟机设备供虚拟机使用了。
#对于USB直通设备不需要这么复杂,只要确保设备存在并且在qemu驱动中记录使用这些设备的虚拟机。
#
if (qemuPrepareHostDevices(driver, vm->def, priv->qemuCaps,
!migrateFrom) < 0)
goto cleanup;
#处理字符设备,包括serial,parallels,channel,console等设备类型,主要是检查这些设备是否存在
if (virDomainChrDefForeach(vm->def,
true,
qemuProcessPrepareChardevDevice,
NULL) < 0)
goto cleanup;
相关阅读:虚拟机创建流程-libvirt篇(下)
本文来自网易实践者社区,经作者岳文远授权发布。