文章版权归本人所有,未经同意,请勿转载!
-
libvirt-qemu-kvm平台是什么
简单来说,是一套虚拟机平台,是目前很多云平台的底层软件支撑,如云平台openstack底层就使用了这套软件,qemu-guest-agent是里面的一个工具。
严格来说,这套平台是由3部分组成的,正如名称所示,是由libvirt,qemu,kvm组成。
-
libvirt是什么?
按官网的介绍来说,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介绍
- qemu-guest-agent,简称qga,是虚拟机内部的守护进程,用来接收物理主机发出的命令,对虚拟机进行注入操作或获取虚拟机信息
- qga有很多命令,比如查看虚拟机CPU信息的guest-get-vcpus子命令。使用命令guest-info可以查看qga中可以调用的子命令列表
- 在qga执行子命令guest-info后,可以发现有很多命令。但这些命令,都是功能固定的,即一条子命令只能实现一种特定的功能,如子命令guest-file-write只能实现将数据写入虚拟机中文件的功能。这样一来,导致qga虽然命令很多,而我们却无法对虚拟机进行更多的控制
- 好在qga是开源的,我们能够获取到源码。有了源码后,我们可以直接修改源码,定制出一款满足需求的qga
-
定制前的准备工作
在进行定制前,先进行一些准备工作,例如测试的方法,如何制作软件包。
-
测试新命令的方法
先说明如何测试新的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 -
首先,虚拟机XML配置文件中要存在如下内容:
<channel type='unix'>
<target type='virtio' name='org.qemu.guest_agent.0'/>
</channel> -
使用virsh命令启动虚拟机,假设虚拟机名字为live-centos7.2
virsh start live-centos7.2
-
在主机上执行命令的方法:
virsh qemu-agent-command live-dwcentos7.2 --cmd '{"execute":"guest-info"}'
输出内容如下:
证明qga命令可以正常使用。
-
-
如何制作rpm包
使用的是centos的系统,在安装定制化的qga时,使用rpm方式安装比较好。下面介绍一下创建qga安装包的方法(以qemu-guest-agent-2.3.0-4.el7为例)。
-
安装源码包
- 先下载源码包qemu-guest-agent-2.3.0-4.el7.src.rpm
- 使用命令安装:
rpm -i qemu-guest-agent-2.3.0-4.el7.src.rpm
-
修改相关文件
rpmbuild -bp ~/rpmbuild/SPECS/qemu-guest-agent.spec
mkdir -p ~/tmp
cp -r ~/rpmbuild/BUILD/qemu-2.3.0/ ~/tmp/
cp -r ~/rpmbuild/BUILD/qemu-2.3.0/ ~/tmp/qemu-2.3.0.new
- 修改文件 ~/tmp/qemu-2.3.0.new/qga/qapi-schema.json
- 修改文件 ~/tmp/qemu-2.3.0.new/qga/commands-posix.c
-
生成补丁文件
diff -rNu ~/tmp/qemu-2.3.0 ~/tmp/qemu-2.3.0.new/ >~/qemu-ga-new-cmd.patch
cp ~/qemu-ga-new-cmd.patch ~/rpmbuild/SOURCES/
-
修改spec文件
编辑文件~/rpmbuild/SPECS/qemu-guest-agent.spec
- 找到Patchxxx这样的字符串,xxx表示数字,在其中xxx最大一行下面添加
Patchxxy: patch_file
假设xxx是最大的数字,则xxy=xxx+1在当前例子中是添加
Patch1253: qemu-ga-new-cmd.patch
- 找到%patchxxx -pn这样的字符串,xxx,n表示数字
在其中xxx最大的一行下面添加%patchxxy -pm
xxy,m为数字。假设xxx是最大的数字,则xxy=xxx+1在当前例子中是添加
%patch1253 -p5
-p后面跟随的数字的含义,可以网上搜一下patch的使用方法
- 找到Patchxxx这样的字符串,xxx表示数字,在其中xxx最大一行下面添加
-
执行命令生成安装包
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 : 测试命令是否正常
下面,一步一步的说明如何做,由简及繁
-
创建无返回值的命令
新命令是“hello-world”,没有参数,也没有返回值。
- 在文件qapi-schema.json中添加:
{ 'command': 'hello-world' }
说明:
- command”关键字定义了一个新的QGA命令,新命令是JSON对象格式的;
- “hello-world”是命令的名称。
- 用C语言实现命令“hello-world”
C代码位置在commands-posix.c中:#include <stdlib.h>
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"}'
执行完后,物理主机内返回:
同时,在虚拟机内根目录下可看到文件Hello-world
- 在文件qapi-schema.json中添加:
-
创建使用参数的命令
现在,我们给命令hello-world添加参数“message”,参数类型是可选参数,会包含字符串。
- 在qapi-schema.json文件中修改命令声明
{ 'command': 'hello-world', 'data': { '*message': 'str' } }
在命令声明中,新增加了成员“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)
- 自定义的类型。
- 更新文件commands-posix.c中的C语言代码
#include <stdlib.h>
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":{}}”
在虚拟机内文件/Hello-world中,分别会看到内容“hello world”和“We love KVM”
- 在qapi-schema.json文件中修改命令声明
-
创建处理错误的命令
在执行命令过程中遇到错误时,qga命令应该使用[qemu-src-path]/include/qapi/error.h头文件中定义的相关接口来处理。
-
一般的错误,使用接口error_setg()就可以
假设前一个示例中字符串message不能包含单词“We”。如果包含了,就返回错误。
C语言代码如下:
#include <stdlib.h>
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
-
希望返回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"}}'
结果:
-
-
代码中添加注释
到目前为止,离完整的声明命令“hello-world”还差一步,那就是注释。在qapi-schema.json文件中,有很多例子可以参考,下面是hello-world命令的完整声明。
##
# @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: <next qemu stable release, eg. 1.0>
##
{ 'command': 'hello-world', 'data': { '*message': 'str' } }
注意:“Returns”这一行在无返回值或不处理错误的情况下可以省略。
-
创建有返回值的命令
以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"}}'
结果:
{"return":1}
-
命令2:
virsh qemu-agent-command live-centos7.2 --cmd '{ "execute":"hello-world"}'
结果:
{"return":0}
-
-
创建自定义类型
自定义类型对应于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"}}}'
结果:
{"return":2}
-
命令2:
virsh qemu-agent-command live-centos7.2 --cmd '{ "execute":"hello-world","arguments":{"newtype":{"m1":1}}}'
结果2:
{"return":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。-
测试命令:
virsh qemu-agent-command live-centos7.2 --cmd '{ "execute":"hello-world"}'
结果:
{"return":[{"m1":4},{"m1":3},{"m1":2},{"m1":1},{"m1":0}]}
-
-
返回整数列表
在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;
}-
测试命令:
virsh qemu-agent-command live-centos7.2 --cmd '{ "execute":"hello-world"}'
结果:
-
-
返回字符串列表
在json文件中声明如下:
{ 'command': 'hello-world',
'returns':['str'] }对应C代码为:
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;
}-
测试命令:
virsh qemu-agent-command live-centos7.2 --cmd' { "execute":"hello-world"}'
结果:
-
-
关于定制qga命令的方法就介绍完了