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 &#61; 1 <<0, /* Launch guest in paused state */
VIR_DOMAIN_START_AUTODESTROY &#61; 1 <<1, /* Automatically kill guest when virConnectPtr is closed */
VIR_DOMAIN_START_BYPASS_CACHE &#61; 1 <<2, /* Avoid file system cache pollution */
VIR_DOMAIN_START_FORCE_BOOT &#61; 1 <<3, /* Boot, discarding any managed save */
int
virDomainCreateWithFlags(virDomainPtr domain, unsigned int flags) {virConnectPtr conn;VIR_DOMAIN_DEBUG(domain, "flags&#61;%x", flags);virResetLastError();#重置错误码。#libvirt中采用了线程池机制&#xff0c;每次从线程池中取出一个线程执行当前的请求。#线程中会保存当前线程最后产生的错误码&#xff0c;因此在请求最开始的位置就要把原有的错误重置&#xff0c;防止误报。#合法性检查&#xff0c;传入的domain指针及其中的conn指针是否为正确的类型。if (!VIR_IS_CONNECTED_DOMAIN(domain)) {virLibDomainError(VIR_ERR_INVALID_DOMAIN, __FUNCTION__);virDispatchError(NULL);return -1;}#获取domain中的conn指针&#xff0c;如果conn是只读的&#xff0c;则设置错误码并直接退出。因为创建虚拟机属于修改操作。conn &#61; domain->conn;if (conn->flags & VIR_CONNECT_RO) {virLibDomainError(VIR_ERR_OPERATION_DENIED, __FUNCTION__);goto error;}#从这里跳转到具体的driver中执行。驱动在libvirtd启动的时候加载&#xff0c;映射关系由conn指针初始化的时候指定。在配置文件中可以配置默认的conn driver&#xff0c;也可以在创建conn的时候通过接口参数指定。if (conn->driver->domainCreateWithFlags) {int ret;ret &#61; conn->driver->domainCreateWithFlags(domain, flags);if (ret <0)goto error;return ret;}#如果驱动中没有实现对应的方法&#xff0c;直接报no support错误。virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__);error:virDispatchError(domain->conn);return -1;
}
static int
qemuDomainCreateWithFlags(virDomainPtr dom, unsigned int flags)
{virQEMUDriverPtr driver &#61; dom->conn->privateData;virDomainObjPtr vm;int ret &#61; -1;#首先检查传入flag参数的合法性&#xff0c;必须是上面提到的几个可选值之一。这里是一个宏来实现的&#xff0c;如果出错直接返回-1。virCheckFlags(VIR_DOMAIN_START_PAUSED |VIR_DOMAIN_START_AUTODESTROY |VIR_DOMAIN_START_BYPASS_CACHE |VIR_DOMAIN_START_FORCE_BOOT, -1);#获取虚拟机的vm指针if (!(vm &#61; qemuDomObjFromDomain(dom)))return -1;#访问控制&#xff0c;判断当前conn是否有权限执行该操作。目前配置的访问控制标签默认为None&#xff0c;即所有用户都有最高权限。if (virDomainCreateWithFlagsEnsureACL(dom->conn, vm->def) <0)goto cleanup;#获取虚拟机job锁&#xff0c;类型为MODIFY&#xff0c;可选类型如后所示。只有获得该锁才能继续执行。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 &#61; 0;endjob:#该job是同步操作&#xff0c;任务结束之后要释放job锁。if (!qemuDomainObjEndJob(driver, vm))vm &#61; NULL;cleanup:if (vm)virObjectUnlock(vm);return ret;
}
QEMU_JOB_NONE &#61; 0, /* Always set to 0 for easy if (jobActive) conditions */
QEMU_JOB_QUERY, /* Doesn&#39;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 &#61; -1;char *managed_save;#根据传入的参数确定虚拟机的启动模式bool start_paused &#61; (flags & VIR_DOMAIN_START_PAUSED) !&#61; 0;bool autodestroy &#61; (flags & VIR_DOMAIN_START_AUTODESTROY) !&#61; 0;bool bypass_cache &#61; (flags & VIR_DOMAIN_START_BYPASS_CACHE) !&#61; 0;bool force_boot &#61; (flags & VIR_DOMAIN_START_FORCE_BOOT) !&#61; 0;unsigned int start_flags &#61; VIR_QEMU_PROCESS_START_COLD;start_flags |&#61; start_paused ? VIR_QEMU_PROCESS_START_PAUSED : 0;start_flags |&#61; autodestroy ? VIR_QEMU_PROCESS_START_AUTODESTROY : 0;#组装hibernate文件的路径managed_save &#61; qemuDomainManagedSavePath(driver, vm);if (!managed_save)goto cleanup;#如果存在hibernate文件&#xff0c;则从该文件恢复虚拟机if (virFileExists(managed_save)) {#启动时可以指定强制启动&#xff0c;此时移除hibernate文件并按照正常流程启动虚拟机if (force_boot) {if (unlink(managed_save) <0) {virReportSystemError(errno,_("cannot remove managed save file %s"),managed_save);goto cleanup;}vm->hasManagedSave &#61; false;} else {#从hibernate文件恢复虚拟机&#xff0c;因为我们目前还不支持内存快照的功能&#xff0c;暂时不跟进了。ret &#61; qemuDomainObjRestore(conn, driver, vm, managed_save,start_paused, bypass_cache);#恢复成功&#xff0c;移除suspend文件if (ret &#61;&#61; 0) {if (unlink(managed_save) <0)VIR_WARN("Failed to remove the managed state %s", managed_save);elsevm->hasManagedSave &#61; false;}#如果恢复失败&#xff0c;则忽略suspend文件直接按正常流程启动虚拟机if (ret > 0)VIR_WARN("Ignoring incomplete managed state %s", managed_save);elsegoto cleanup;}}#启动qemu进程ret &#61; qemuProcessStart(conn, driver, vm, NULL, -1, NULL, NULL,VIR_NETDEV_VPORT_PROFILE_OP_CREATE, start_flags);#虚拟机启动完成之后&#xff0c;验证对应的启动参数&#xff0c;并且在/var/run/libvirt/qemu目录下保存一份运行状态的配置文件&#xff0c;这个文件的内容在虚拟机配置改变的时候会随之改变&#xff0c;virDomainAuditStart(vm, "booted", ret >&#61; 0);if (ret >&#61; 0) {#向事件队列发送虚拟机启动事件。如果此时有程序在监听此事件就会收到相应的通知。virDomainEventPtr event &#61;virDomainEventNewFromObj(vm,VIR_DOMAIN_EVENT_STARTED,VIR_DOMAIN_EVENT_STARTED_BOOTED);if (event) {qemuDomainEventQueue(driver, event);#如果指定了启动之后pause虚拟机&#xff0c;同时还要发送一个虚拟机pause事件。if (start_paused) {event &#61; virDomainEventNewFromObj(vm,VIR_DOMAIN_EVENT_SUSPENDED,VIR_DOMAIN_EVENT_SUSPENDED_PAUSED);if (event)qemuDomainEventQueue(driver, event);}}}cleanup:VIR_FREE(managed_save);return ret;
}
qemuProcessStart
函数&#xff0c;这个函数处理qemu进程启动的主逻辑流程。由于这个函数中逻辑比较长&#xff0c;就不直接贴代码了&#xff0c;只选取其中关键部分了解一下。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 &#61; qemuDriverAllocateID(driver);
qemuDomainSetFakeReboot(driver, vm, false);
virDomainObjSetState(vm, VIR_DOMAIN_SHUTOFF, VIR_DOMAIN_SHUTOFF_UNKNOWN);
if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) {char *xml &#61; qemuDomainDefFormatXML(driver, vm->def, 0);int hookret;hookret &#61; 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 &#61; virQEMUCapsCacheLookupCopy(driver->qemuCapsCache,vm->def->emulator)))goto cleanup;
#处理配置文件中的直通网卡。虽然在配置文件中指定设备类型为interface&#xff0c;但是实际上直通网卡还是一个PCI设备&#xff0c;因此将其加入hostdev设备中。if (qemuNetworkPrepareDevices(vm->def) <0)goto cleanup;#处理直通设备。直通设备分为三类&#xff1a;PCI设备&#xff0c;USB设备及scsi设备。#PCI设备的处理逻辑比较复杂&#xff0c;大致流程为# - 检查配置的PCI设备是否已经直通到其他虚拟机# - 移除这些设备的原有驱动# - 重置这些设备# - 对于SRIOV的网卡直通设备&#xff0c;需要额外设置一些网络相关的参数# - 在qemu驱动中将这些设备设置为active状态# - 在qemu驱动的未启用设备列表中移除这些设备# - 在qemu驱动中记录当前使用这些设备的虚拟机# - 记录这些设备的原始状态# - 从host上隐藏这些设备#经过以上处理之后&#xff0c;配置的PCI设备就可以作为一个普通的虚拟机设备供虚拟机使用了。#对于USB直通设备不需要这么复杂&#xff0c;只要确保设备存在并且在qemu驱动中记录使用这些设备的虚拟机。#if (qemuPrepareHostDevices(driver, vm->def, priv->qemuCaps,!migrateFrom) <0)goto cleanup;#处理字符设备&#xff0c;包括serial&#xff0c;parallels&#xff0c;channel&#xff0c;console等设备类型&#xff0c;主要是检查这些设备是否存在if (virDomainChrDefForeach(vm->def,true,qemuProcessPrepareChardevDevice,NULL) <0)goto cleanup;