一、libvirt-qemu-kvm平台是什么
二、qga介绍
三、定制前的准备工作
四、如何定制qga命令
一、libvirt-qemu-kvm平台是什么
按官网的介绍来说,libvirt是
- 管理虚拟化主机的工具集
- 可以通过多种编程语言访问(也就是调用),如C、Python、Perl、Java,从而使用这些编程语言就可以管理物理主机与虚拟机
- 以开源许可协议方式授权
- 致力于支持多平台:Linux、FreeBSD、Windows、OS-X
- 被许多应用软件使用
上面这些没有说的是,libvirt提供API供其它程序调用,支持管理多种虚拟机hypervisor,例如Xen,qemu-kvm
(二) qemu是什么?
- 模拟器:
运行为其它平台编译的程序或OS,如在X86 PC上运行arm平台的程序
- 虚拟机:
运行与本地平台相同类型的虚拟机。可以在xen环境下运行,也可以基于KVM运行。当基于KVM时,qemu可以虚拟化X86、基于PowerPC的服务器和嵌入式系统、S390、ARM等。
(三) KVM是什么?
(四)分工
- 英文全称为:Kernel-Based Virtual Machine,翻译为中文为“基于内核的虚拟机”。
- 是Linux内核的一个可加载模块,通过调用Linux本身内核功能,实现对CPU的虚拟化和内存的虚拟化,使Linux内核成为虚拟化层。KVM需要硬件支持虚拟化,比如vmx(intel),svm(amd)。
- libvirt提供管理工具
- qemu-kvm实现虚拟机,这其中,qemu实现除CPU和内存外的其它设备,如磁盘,网卡的虚拟化;KVM实现内存和CPU的虚拟化。
二、qga介绍
三、定制前的准备工作
在进行定制前,先进行一些准备工作,例如测试的方法,如何制作软件包。
3.1 测试新命令的方法
先说明如何测试新的qga命令。接下来的示例中,都可以用这一方法来测试
测试环境(X86):
vm : CentOS Linux release 7.2.1511
qga : qemu-guest-agent-2.3.0-4.el7
host : CentOS Linux release 7.3.1611
virsh : 3.2.0
qemu-kvm :qemu-kvm-1.5.3-141.el7_4.6
(1) 首先,虚拟机XML配置文件中要存在如下内容:
(2) 使用virsh命令启动虚拟机,假设虚拟机名字为live-centos7.2
virsh start live-centos7.2
(3) 在主机上执行命令的方法:
virsh qemu-agent-command live-dwcentos7.2 --cmd '{"execute":"guest-info"}'
输出内容如下:
证明qga命令可以正常使用。
3.2 如何制作rpm包
使用的是centos的系统,在安装定制化的qga时,使用rpm方式安装比较好。下面介绍一下创建qga安装包的方法(以qemu-guest-agent-2.3.0-4.el7为例)。
3.2.1 安装源码包
- 先下载源码包qemu-guest-agent-2.3.0-4.el7.src.rpm
- 使用命令安装:
rpm -i qemu-guest-agent-2.3.0-4.el7.src.rpm
3.2.2 修改相关文件
(1) rpmbuild -bp ~/rpmbuild/SPECS/qemu-guest-agent.spec
(2) mkdir -p ~/tmp
(3) cp -r ~/rpmbuild/BUILD/qemu-2.3.0/ ~/tmp/
(4) cp -r ~/rpmbuild/BUILD/qemu-2.3.0/ ~/tmp/qemu-2.3.0.new
(5) 修改文件 ~/tmp/qemu-2.3.0.new/qga/qapi-schema.json
(6) 修改文件 ~/tmp/qemu-2.3.0.new/qga/commands-posix.c
3.3.3 生成补丁文件
(1) diff -rNu ~/tmp/qemu-2.3.0 ~/tmp/qemu-2.3.0.new/ >~/qemu-ga-new-cmd.patch
(2) cp ~/qemu-ga-new-cmd.patch ~/rpmbuild/SOURCES/
3.3.4 修改spec文件
编辑文件~/rpmbuild/SPECS/qemu-guest-agent.spec
(1) 找到Patchxxx这样的字符串,xxx表示数字,在其中xxx最大一行下面添加
Patchxxy: patch_file
假设xxx是最大的数字,则xxy=xxx+1。
在当前例子中是添加
Patch1253: qemu-ga-new-cmd.patch
(2)找到%patchxxx -pn这样的字符串,xxx,n表示数字。
在其中xxx最大的一行下面添加%patchxxy -pm
xxy,m为数字。假设xxx是最大的数字,则xxy=xxx+1。
在当前例子中是添加
%patch1253 -p5
-p后面跟随的数字的含义,可以网上搜一下patch的使用方法
3.3.5 执行命令生成安装包
rpmbuild -bb ~/rpmbuild/SPECS/qemu-guest-agent.spec
命令执行完后,生成的安装包位于目录:
~/rpmbuild/RPMS/x86_64/qemu-guest-agent-2.3.0-4.el7.centos.x86_64.rpm
四、如何定制qga命令
定制qga命令本质是基于QAPI框架实现QMP命令。
- 步骤1 : 在qapi schema(qapi模式)文件中添加命令的声明
- 步骤2 : 在相应C源码文件中用C实现命令
- 步骤3 : 测试命令是否正常
下面,一步一步的说明如何做,由简及繁
4.1 创建无返回值的命令
这个命令是“hello-world”,没有参数,也没有返回值。
第一步:在文件qapi-schema.json中添加:
{ 'command': 'hello-world' }
说明:
(1)“command”关键字定义了一个新的QGA命令,新命令是JSON对象格式的。
(2)“hello-world”是命令的名称。
第二步:用C语言实现命令“hello-world”。
C代码位置在commands-posix.c中:
#include
void qmp_hello_world(Error **errp) { int ret; ret = system("touch /Hello-world"); ret=ret; return; }
C语言代码说明
- 以“qmp_”作为C语言函数的前缀
- qmp_hello_world()无返回值,与json代码对应
- 有形参“Error **”。这个参数必须有,后面我们会介绍如何返回错误以及使用更多的参数。如果命令不返回错误的话,就不要操作这个参数。
- 不需要在头文件中对函数进行声明,QAPI会自动实现。
- C函数的功能是在系统根目录/下创建文件Hello-world
- 函数内与ret变量相关的部分是为了让代码编译通过。
按照前述制作rpm包的部分,将上述修改添加到qga源码相关文件中,编译制作新的qga rpm包。制作完成后,将旧的qga卸载,安装新的。
将虚拟机重新启动后,在物理机中按前述测试方法执行命令:
virsh qemu-agent-command live-centos7.2 --cmd '{"execute":"hello-world"}'
4.2 使用参数
现在,我们给命令hello-world添加参数“message”,参数类型是可选参数,会包含字符串。
第一步:在qapi-schema.json文件中修改命令声明
{ 'command': 'hello-world', 'data': { '*message': 'str' } }
第二步:更新文件commands-posix.c中的C语言代码在命令声明中,新增加了成员“data”,对应的“:”符号后面的元素,是新增加的参数。message前面的星号,表示参数“message”是可选的。“str”表示“message”的类型,表示常量字符串。
qga命令中支持的参数类型如下所示(针对qemu 2.3.0版本json文件):
- 整数:int(与int64等价)、uint64、uint32、uint16、uint8、int64、int32、int16、int8
- 布尔
- 枚举
- str(修饰参数时对应于C语言中的const char *,在其它地方对应于C语言中的char *)
- size(与uint64等价)
- number(对应于C语言中的double)
- 自定义的类型。
#include
void qmp_hello_world(bool has_message, const char *message, Error **errp) { int ret; char msg[1024] = {'\0'}; ret = system("touch /Hello-world"); if (has_message) { sprintf(msg,"echo \"%s\" > /Hello-world", message); } else { sprintf(msg,"echo \"%s\" > /Hello-world", "hello world"); } ret = system(msg); ret=ret; return; }
安装后,分别执行下述命令
virsh qemu-agent-command live-centos7.2 --cmd '{"execute":"hello-world"}'
virsh qemu-agent-command live-centos7.2 --cmd '{ "execute":"hello-world","arguments":{"message":"We love KVM"}}'
两条命令都会返回
“{"return":{}}”
4.3 处理错误
在执行命令过程中遇到错误时,qga命令应该使用[qemu-src-path]/include/qapi/error.h头文件中定义的相关接口来处理。4.3.1 一般的错误,使用接口error_setg()就可以。
假设前一个示例中字符串message不能包含单词“We”。如果包含了,就返回错误。
C语言代码如下:
#include
void qmp_hello_world(bool has_message, const char *message, Error **errp) { int ret; char msg[1024] = {'\0'}; ret = system("touch /Hello-world"); if (has_message) { if (strstr(message, "We")) { error_setg(errp, "the word 'We' is not allowed:%d",-1); return; } sprintf(msg,"echo \"%s\" > /Hello-world", message); } else { sprintf(msg,"echo \"%s\" > /Hello-world", "hello world"); } ret = system(msg); ret=ret; return; }
- error_setg的第一个参数是函数qmp_hello_world的2级指针形参errp
- error_setg的第二个参数是字符串,使用方法类似于printf函数的参数
下面,我们输入命令测试一下:
virsh qemu-agent-command live-centos7.2 --cmd '{ "execute":"hello-world","arguments":{"message":"We love KVM"}}'
反馈结果如下:
error: internal error: unable to execute QEMU agentcommand 'hello-world': the word 'We' is not allowed:-1
4.3.2 希望返回C库函数错误值的具体涵义时,可以使用error_setg_errno()
对应的json文件声明:
{ 'command': 'hello-world', 'data': { '*message': 'str' } }
对应的C函数代码:void qmp_hello_world(bool has_message, const char *message, Error **errp) { if (has_message) { error_setg_errno(errp, 2, "test error"); } return; }
注:error_setg_errno参数中的2,表示的是要返回linux中错误码2对应的涵义: No such file or directory执行命令:virsh qemu-agent-command live-centos7.2 --cmd '{"execute":"hello-world","arguments":{"message":"We love KVM"}}'
结果:
4.4 注释
到目前为止,离完整的声明命令“hello-world”还差一步,那就是注释。在qapi-schema.json文件中,有很多例子可以参考,下面是hello-world命令的完整声明。
注意:“Returns”这一行在无返回值或不处理错误的情况下可以省略。## # @hello-world # # Print a client provided string to the standard output stream. # # @message: #optional string to be printed # # Returns: Nothing on success. # # Notes: if @message is not provided, the "Hello, world" string will # be printed instead # # Since:
## { 'command': 'hello-world', 'data': { '*message': 'str' } }
4.5 有返回值的命令
以hello-world为例,qapi-schema.json中命令声明格式为:
{ 'command': 'hello-world', 'data': { '*message': 'str' }, 'returns':'int' }
commands-posix.c中对应的C代码为:
long qmp_hello_world(bool has_message, const char *message, Error **errp) { if (has_message) { return 1; } return 0; }
注:qemu 2.3.0版本中json形式的int对应的是64位C语言long型
不同形式命令对应的结果如下:
- 命令1:virsh qemu-agent-command live-centos7.2 --cmd '{ "execute":"hello-world","arguments":{"message":"We love KVM"}}'
- 结果1:{"return":1}
4.6 自定义类型
自定义类型对应于C语言中的结构体,格式如下:
{ 'type': 'MyType', 'data':{'member1':'mem1-type','member2':'mem2-type','*member3':'mem3-type'} }
注:
type :表示这是类型声明
MyType :是类型名
data :表示后面跟新数据结构的成员
member1,member2,member3这些是成员名称,成员数量可根据需要增减,mem1-type,mem2-type,mem3-type分别表示各成员的类型。member3前的星号表示这个参数是可选的,可选参数需要放在末尾。
新类型示例:
{ 'type': 'HelloType', 'data': { 'm1': 'int', '*m2': 'str' } } { 'command': 'hello-world', 'data': { 'newtype': 'HelloType' }, 'returns':'int' }
commands-posix.c中对应的C代码为:
long qmp_hello_world(HelloType *newtype, Error **errp) { if (newtype->has_m2) { return newtype->m1 + 1; } return newtype->m1; }
不同形式命令对应的结果如下:
- 命令1:virsh qemu-agent-command live-centos7.2 --cmd '{ "execute":"hello-world","arguments":{"newtype":{"m1":1,"m2":"We love KVM"}}}'
- 结果1:{"return":2}
- 命令2:virsh qemu-agent-command live-centos7.2 --cmd '{ "execute":"hello-world","arguments":{"newtype":{"m1":1}}}'
- 结果2:{"return":1}
4.7 返回列表
4.7.1 qga命令支持返回列表形式的数据。
在json文件中声明格式类似如下:
{ 'type': 'IntStruct', 'data': { 'm1': 'int'} } { 'command': 'hello-world', 'returns':['IntStruct'] }
注:IntStruct是自定义的数据类型。以列表形式返回数据的关键是中括号[]。如果在返回的数据类型外部加了中括号[],就表示返回列表。
对应的C代码:
IntStructList *qmp_hello_world(Error **errp) { IntStructList *int_list = NULL; int i = 0; for(i = 0;i < 5;i++) { IntStructList *list_entry = g_malloc0(sizeof(*list_entry)); list_entry->value = g_malloc0(sizeof(*list_entry->value)); list_entry->value->m1 = (long)i; list_entry->next = int_list; int_list = list_entry; } return int_list; }
注:g_malloc0()函数是glib库里面的。
C函数返回类型名称是列表元素类型名后缀List,如列表元素类型为IntStruct时,返回的类型为IntStructList。
4.7.2 返回整数列表
在json文件中声明格式如下:
{ 'command': 'hello-world', 'returns':['int'] }
对应C代码为
intList *qmp_hello_world(Error **errp) { intList *int_list = NULL; int i = 0; for(i = 0;i < 5;i++) { intList *list_entry = g_malloc0(sizeof(*list_entry)); list_entry->value = (long)i; list_entry->next = int_list; int_list = list_entry; } return int_list; }
4.7.3 返回字符串列表
关于定制qga命令的方法就介绍完了在json文件中声明如下:
{ 'command': 'hello-world', 'returns':['str'] }
对应C代码为:
测试命令: virsh qemu-agent-command live-centos7.2 --cmd' { "execute":"hello-world"}'strList *qmp_hello_world(Error **errp) { strList *str_list = NULL; int i = 0; for(i = 0;i < 5;i++) { strList *list_entry = g_malloc0(sizeof(*list_entry)); list_entry->value = g_malloc0(10); memset(list_entry->value, 0, 10); *(list_entry->value) = 'a' + i; list_entry->next = str_list; str_list = list_entry; } return str_list; }
结果: