定制qga(qemu-guest-agent)命令

版权声明:国内网络上重复文章太多了,本文为博主原创的文章,未经博主允许就不要转载了。 https://blog.csdn.net/dwdwdw2/article/details/79313684

一、libvirt-qemu-kvm平台是什么

二、qga介绍

三、定制前的准备工作

四、如何定制qga命令


一、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。

三、定制前的准备工作

在进行定制前,先进行一些准备工作,例如测试的方法,如何制作软件包。

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配置文件中要存在如下内容:

<channel type='unix'>
  <target type='virtio' name='org.qemu.guest_agent.0'/> 
</channel> 

(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 <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


4.2 使用参数

现在,我们给命令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”


4.3 处理错误

在执行命令过程中遇到错误时,qga命令应该使用[qemu-src-path]/include/qapi/error.h头文件中定义的相关接口来处理。
4.3.1 一般的错误,使用接口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


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命令的完整声明。

 ##
 # @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”这一行在无返回值或不处理错误的情况下可以省略。

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}

  • 命令2:virsh qemu-agent-command live-centos7.2 --cmd '{ "execute":"hello-world"}'
  • 结果2:{"return":0}

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。

  • 测试命令:virsh qemu-agent-command live-centos7.2 --cmd '{ "execute":"hello-world"}'
  • 结果:{"return":[{"m1":4},{"m1":3},{"m1":2},{"m1":1},{"m1":0}]}

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; 
} 

测试命令:virsh qemu-agent-command live-centos7.2 --cmd '{ "execute":"hello-world"}'

结果:


4.7.3 返回字符串列表

在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命令的方法就介绍完了

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页