首页
 

通知公告

libvirt的event监听机制和代码实现

来源:欧亿体育点击:时间:2024-01-14 01:01

文章目录


1.简介

libvirt是目前使用最为广泛的对KVM虚拟机继续管理的工具和应用程序接口,而且一些常用的虚拟机管理工具(如virsh、virt-manager等)和云计算框架平台(如:OpenStack、ZStack等)都在底层使用libvirt的应用程序接口。
libvirt是为了更方便地管理平台虚拟化技术而设计的开放源代码的应用程序接口(libvirt API)、守护进程(libvirtd)和管理工具(virsh),它不仅提供了对虚拟化客户机的管理,也提供了对虚拟化网络和存储的管理。
libvirt支持事件机制,在使用该机制注册之后,可以在发生特定的事件(如:domain的启动、暂停、恢复和停止等)时得到自己定义的一些通知。该功能由以virStream开头的一系列函数实现。


2.通过virsh演示event监听

第一步:使用命令event --help,查看event命令后面需要接哪些选项。

第二步:使用命令event --all --loop --timestamp,监听hypervisor上所有domain的所有类型事件,并且循环监听,直到手动中断。

第三步:另外打开一个窗口,destroy,或者start虚拟机。

第四步:查看监听界面,会收到事件。

3.通过libvirt API实现event监听的流程图



4. virEvent API简介

4.1 virEventRegisterDefaultImpl

语法
int virEventRegisterDefaultImpl (void)
描述
注册基于poll()系统调用的默认事件实现。这是一个通用实现,任何客户机应用程序都可以使用它,不需要与外部事件循环impl集成。
为了进行正确的事件处理,一定要在打开到Hypervisor的连接之前实现事件注册。
注册之后,应用程序必须在循环中调用 virEventRunDefaultImpl ()来处理事件。如果不这样做,可能会导致连接由于keepalive超时而被意外关闭。默认事件循环完全支持处理和超时事件,但只在libvirt API调用(如virEventAddHandle()或virConnectDomainEventRegisterAny())注册的事件上唤醒。
返回值
0,表示成功。1,表示失败。

4.2. virEventHandleType

handle事件的类型。
enum virEventHandleType {
VIR_EVENT_HANDLE_READABLE = 1 (0x1; 1 << 0) // 可读
VIR_EVENT_HANDLE_WRITABLE = 2 (0x2; 1 << 1) // 可写
VIR_EVENT_HANDLE_ERROR = 4 (0x4; 1 << 2) // 异常
VIR_EVENT_HANDLE_HANGUP = 8 (0x8; 1 << 3) // 挂起
}

4.3. virEventAddHandle

语法
int virEventAddHandle (int fd, int events, virEventHandleCallback cb, void * opaque, virFreeCallback ff)
描述
为监视文件句柄事件注册一个回调。这个函数要求一个事件循环之前已经用virEventRegisterImpl()或virEventRegisterDefaultImpl()注册过。
fd必须总是总是一个C运行时文件描述符。在Windows上,如果调用者只有一个HANDLE,可以使用_open_osfhandle()方法打开一个关联的C运行时文件描述符,用于此API。打开一个运行时文件描述符后,CloseHandle()一定不能使用,相反,close()将关闭运行时文件描述符及其原始关联的HANDLE。
参数
fd:监控事件的文件句柄;
events:virEventHandleType常量中要监视的事件的bitset;
cb:当事件发生时调用的回调函数;
opaque:要传递给回调的用户数据;
ff:当句柄被移除时,回调到释放不透明;
返回值
如果文件句柄不能注册,则返回-1。否则,将使用句柄监视号来更新和注销事件。
提示
使用linux的系统调用函数pipe(),获取fd;
events为用户传入的事件类型,例如VIR_DOMAIN_EVENT_ID_LIFECYCLE;
opaque相当于main中的argc和argv,用于接收用户的数据,具体使用看业务逻辑;即,可以用于传数据,也可以传NULL;
ff可以为NULL;

4.4. virEventAddTimeout

语法
int virEventAddTimeout (int timeout, virEventTimeoutCallback cb, void * opaque, virFreeCallback ff)
描述
为计时器事件注册一个回调。这个函数要求一个事件循环之前已经用virEventRegisterImpl()或virEventRegisterDefaultImpl ()注册过。
将timeout设置为-1将禁用计时器。将timeout设置为零将导致它在每次事件循环迭代时触发。
参数:
timeout:事件之间的时间间隔,以毫秒为单位
cb:当事件发生时调用的回调函数virEventTimeoutCallback
opaque:要传递给回调的用户数据;(相当于main中的argc和argv,用于接收用户的数据,具体使用看业务逻辑)
ff:释放不透明数据团的回调函数
返回值
如果定时器不能注册,返回-1。否则,注册成功时为正整数定时器id。

5. virConnectDomainEvent API简介

5.1. virConnectDomainEventRegisterAny

语法
int virConnectDomainEventRegisterAny (
virConnectPtr conn,
virDomainPtr dom,
int eventID,
virConnectDomainEventGenericCallback cb,
void * opaque,
virFreeCallback freecb)

描述
添加一个回调来接收域上发生的任意域事件的通知。这个函数要求一个事件循环之前已经用virEventRegisterImpl()或virEventRegisterDefaultImpl ()注册过。
如果dom是非null,则只监视特定的域。如果dom为NULL,则将监视任何域的事件。
大多数类型的事件都有一个回调函数,为事件提供一组自定义参数。因此,在注册事件时,需要使用VIR_DOMAIN_EVENT_CALLBACK()宏强制转换提供的函数指针,以匹配此方法的签名。
在传递事件时传递给回调函数的virDomainPtr对象句柄只在回调函数执行期间有效。如果回调函数希望在回调函数返回后保留域对象,它应该通过调用virDomainRef()获取对域对象的引用。一旦对象不再需要,就可以通过调用virDomainFree()释放引用。
这个方法的返回值是回调的一个非负整数标识符。要取消注册回调,应该将此回调ID传递给virConnectDomainEventDeregisterAny()方法。

参数
conn:pointer to the connection;
dom:所指向的domain;
eventID:所接收的事件类型;
cb:回调到处理域事件的函数;
opaque:传递给回调的不透明数据;
freecb:可选函数,当不再使用时释放不透明;

返回值
成功时使用回调标识符,失败时使用-1。

5.2. virConnectDomainEventDeregisterAny

语法
int virConnectDomainEventDeregisterAny (virConnectPtr conn, int callbackID)

描述
移除一个事件回调。
callbackID参数应该是从前面的virConnectDomainEventRegisterAny()方法获得的值。

2.3 参数
conn:pointer to the connection;
callbackID:回调标识;(通过virConnectDomainEventRegisterAny()方法获得)

返回值
0 on success, -1 on failure. 某些管理程序的旧版本有时会在成功时返回一个正数,但是没有任何关于该数字表示什么的可靠语义。

5.3. virConnectDomainEventRegister

语法
int virConnectDomainEventRegister (
virConnectPtr conn,
virConnectDomainEventCallback cb,
void * opaque,
virFreeCallback freecb)

描述
添加一个回调来接收在连接上发生的域生命周期事件的通知。这个函数要求一个事件循环之前已经用virEventRegisterImpl()或virEventRegisterDefaultImpl ()注册过。

不再推荐使用此方法。相反,应用程序应该尝试virConnectDomainEventRegisterAny(),它具有更灵活的API契约。

在传递事件时传递给回调函数的virDomainPtr对象句柄只在回调函数执行期间有效。如果回调函数希望在回调函数返回后保留域对象,它应该通过调用virDomainRef来获取对域对象的引用。一旦对象不再需要,就可以通过调用virDomainFree来释放引用。

参数
conn:pointer to the connection;
cb:回调到处理域事件的函数;
opaque:传递给回调的不透明数据
freecb:可选函数,当不再使用时释放不透明

返回值
0代表成功,-1代表失败。某些管理程序的旧版本有时会在成功时返回一个正数,但是没有任何关于该数字表示什么的可靠语义。

5.4. virConnectDomainEventDeregister

语法
int virConnectDomainEventDeregister (virConnectPtr conn, virConnectDomainEventCallback cb)
描述
删除以前使用virConnectDomainEventRegister()函数注册的回调。
不再推荐使用此方法。相反,应用程序应该尝试virConnectDomainEventDeregisterAny(),它具有更灵活的API契约。
参数
conn:pointer to the connection
cb:回调到处理域事件的函数;
返回值
0 on success, -1 on failure. 某些管理程序的旧版本有时会在成功时返回一个正数,但是没有任何关于该数字表示什么的可靠语义。

5.5. virDomainEventID

在eventRegister中需要输入eventid。
enum virDomainEventID {
VIR_DOMAIN_EVENT_ID_LIFECYCLE = 0,
VIR_DOMAIN_EVENT_ID_REBOOT = 1,
….
}
5.6. virDomainEventType
enum virDomainEventType {
VIR_DOMAIN_EVENT_DEFINED = 0 (0x0)
VIR_DOMAIN_EVENT_UNDEFINED = 1 (0x1)
VIR_DOMAIN_EVENT_STARTED = 2 (0x2)
VIR_DOMAIN_EVENT_SUSPENDED = 3 (0x3)
VIR_DOMAIN_EVENT_RESUMED = 4 (0x4)
VIR_DOMAIN_EVENT_STOPPED = 5 (0x5)
VIR_DOMAIN_EVENT_SHUTDOWN = 6 (0x6)
VIR_DOMAIN_EVENT_PMSUSPENDED = 7 (0x7)
VIR_DOMAIN_EVENT_CRASHED = 8 (0x8)
VIR_DOMAIN_EVENT_LAST = 9 (0x9)
}
上述type均属于EventID中的VIR_DOMAIN_EVENT_ID_LIFECYCLE。

6. 实现代码

#include 
#include 
#include 
#include 
#include 
static int myEventCallback(virConnectPtr conn,
virDomainPtr dom,
int event,
int detail,
void *opaque) 
{
const char *name = virDomainGetName(dom);
struct tm *currTime;
time_t now;
time(&now);
currTime = localtime(&now);

switch(event) {
case VIR_DOMAIN_EVENT_STARTED:
printf("%d/%d/%d %d:%d:%d An STARTED event(%d) occurred in the domain = < %s >.\n", currTime->tm_year + 1900, currTime->tm_mon + 1, currTime->tm_mday, currTime->tm_hour, currTime->tm_min, currTime->tm_sec, event, name);
break;
case VIR_DOMAIN_EVENT_STOPPED:
printf("%d/%d/%d %d:%d:%d An STOPPED event(%d) occurred in the domain = < %s >.\n", currTime->tm_year + 1900, currTime->tm_mon + 1, currTime->tm_mday, currTime->tm_hour, currTime->tm_min, currTime->tm_sec, event, name);
break;
case VIR_DOMAIN_EVENT_DEFINED:
printf("%d/%d/%d %d:%d:%d An DEFINED event(%d) occurred in the domain = < %s >.\n", currTime->tm_year + 1900, currTime->tm_mon + 1, currTime->tm_mday, currTime->tm_hour, currTime->tm_min, currTime->tm_sec, event, name);
break;
case VIR_DOMAIN_EVENT_UNDEFINED:
printf("%d/%d/%d %d:%d:%d An UNDEFINED event(%d) occurred in the domain = < %s >.\n", currTime->tm_year + 1900, currTime->tm_mon + 1, currTime->tm_mday, currTime->tm_hour, currTime->tm_min, currTime->tm_sec, event, name);
break;
case VIR_DOMAIN_EVENT_SUSPENDED:
printf("%d/%d/%d %d:%d:%d An SUSPENDED event(%d) occurred in the domain = < %s >.\n", currTime->tm_year + 1900, currTime->tm_mon + 1, currTime->tm_mday, currTime->tm_hour, currTime->tm_min, currTime->tm_sec, event, name);
break;
case VIR_DOMAIN_EVENT_RESUMED:
printf("%d/%d/%d %d:%d:%d An RESUMED event(%d) occurred in the domain = < %s >.\n", currTime->tm_year + 1900, currTime->tm_mon + 1, currTime->tm_mday, currTime->tm_hour, currTime->tm_min, currTime->tm_sec, event, name);
break;
case VIR_DOMAIN_EVENT_SHUTDOWN:
printf("%d/%d/%d %d:%d:%d An SHUTDOWN event(%d) occurred in the domain = < %s >.\n", currTime->tm_year + 1900, currTime->tm_mon + 1, currTime->tm_mday, currTime->tm_hour, currTime->tm_min, currTime->tm_sec, event, name);
break;
case VIR_DOMAIN_EVENT_PMSUSPENDED:
printf("%d/%d/%d %d:%d:%d An PMSUSPENDED event(%d) occurred in the domain = < %s >.\n", currTime->tm_year + 1900, currTime->tm_mon + 1, currTime->tm_mday, currTime->tm_hour, currTime->tm_min, currTime->tm_sec, event, name);
break;
case VIR_DOMAIN_EVENT_CRASHED:
printf("%d/%d/%d %d:%d:%d An CRASHED event(%d) occurred in the domain = < %s >.\n", currTime->tm_year + 1900, currTime->tm_mon + 1, currTime->tm_mday, currTime->tm_hour, currTime->tm_min, currTime->tm_sec, event, name);
break;
default:
printf("%d/%d/%d %d:%d:%d An UNKOWN event(%d) occurred in the domain = < %s >.\n", currTime->tm_year + 1900, currTime->tm_mon + 1, currTime->tm_mday, currTime->tm_hour, currTime->tm_min, currTime->tm_sec, event, name);
break;
}
return 0;
}
static void* eventThreadLoop() {
while(1) {
if(virEventRunDefaultImpl() < 0) {
printf("Run errer.\n");
}
}
abort();
}
int main() {
virConnectPtr conn = NULL;
int eventid = VIR_DOMAIN_EVENT_ID_LIFECYCLE;
virEventRegisterDefaultImpl();
conn = virConnectOpen(NULL);
pthread_t eventThread;
pthread_create(&eventThread, NULL, eventThreadLoop, NULL);
int id = virConnectDomainEventRegisterAny(conn, 
NULL,
eventid,
VIR_DOMAIN_EVENT_CALLBACK(myEventCallback),
NULL,
NULL);
while(1) pause();
virConnectDomainEventDeregisterAny(conn, id);
virConnectClose(conn);
return 0;
}

7. 实验现象

对虚拟机进行操作:

接收到的事件:

8. 参考文献

[1] https://libvirt.org/html/libvirt-libvirt-domain.html#virDomainEventType