为LibVirt添加新的API【转】

前段时间在搞libvirt,这方面的文章很少。发现有一个博客的文章还不错,转过来分享下,作者貌似也不发表新的文章了,怕以后这个文章消失了,特转载过来。他那个可能是版本比较老,里面其实有些不太适合,或者不对。但是还是比较值的看的。

转自:http://arondight.me/2016/12/25/%E4%B8%BALibVirt%E6%B7%BB%E5%8A%A0%E6%96%B0%E7%9A%84API/

 

LibVirt 是一套用于控制虚拟化的API,除了提供了一套无关具体虚拟化细节的API 之外,还提供了一个daemon(libvirtd) 和一个控制台工具(virsh)。本文演示了如何在LibVirt 中新加一个API,并且在libvirtd 和virsh 中使用新的API 完成新的功能。

为了方便说明,在文章的示例中只演示了添加一个API,如果要看完整的示例,可以查看项目Arondight/libvirt-add-new-api-demo,这是一个相对完整的示例,项目中新API 的说明以及Patch 的使用可以参见其中的README.txt

LibVirt 的编译需要Gnulib 的源码,不过因为网络的原因在墙内其Git 仓库很难获取,所以这里使用GitHub 上的镜像仓库,并通过环境变量引入。你可以设置好这一切并编译一遍源码。在上面的指令执行成功后执行。

 

1

2

3

4

5

6

 

git clone https://github.com/coreutils/gnulib.git

export GNULIB_SRCDIR=$(readlink -f ./gnulib)

cd ./libvirt

./autogen.sh

make -j8

make check -j8

如果你的编译依赖完备的话,LibVirt 可以正确编译并通过测试。如果你没有得到预期的结果,请检查你的编译环境并安装缺失的软件包。

示例中我们添加的API 为virConnectGetMagicFileContent,功能为获取运行虚拟化的机器上某个文件内容的最多前32 个字节。

添加公共API

首先要做的是为LibVirt 添加公共API,这个API 也是LibVirt 为用户展现的API。此后通过一连串调用,我们会在libvirtd 和virsh 中通过调用这个公共API 来完成新功能。这里需要修改的文件有如下几个。

  1. include/libvirt/libvirt-*.h: 这里需要完成公共API 的声明,此后通过包含头文件include/libvirt/libvirt.h 可调用此API。
  2. src/libvirt_public.syms: 这里需要将新API 导出为全局符号,这样公共API 得以允许被其他函数访问,如果你在步骤[1] 中定义了一个需要被其他函数访问的数据结构,同样你也需要将它导出为全局符号。
  3. src/libvirt-*.c: 这里需要实现步骤[1] 中声明的API,一般来说这里只调用驱动提供的API 即可,具体功能需要在每个hypervisor 的驱动中单独实现。

API 的注释

首先要说明的是,公共API 必须要有合乎规范的注释。在编译时,docs/apibuild.py 会检查宏和公共API 的注释是否符合要求,如果发现不合格的注释,将中断整个编译过程。注释在声明和定义处皆可。

对于一个宏,注释的格式如下。

 

1

2

3

4

5

6

 

/**

* MACRO_NAME:

*

* macro's comment.

*/

#define MACRO_NAME (SOMETHING_HERE)

对于一个API,注释的格式如下。

 

1

2

3

4

5

6

7

8

9

10

11

 

/**

* apiName:

*

* @arg: arg's comment

*

* synopsis for this api.

*

* Returns what.

*/

ret_type

apiName(arg_type arg) { }

注意:API 注释中的单词Returns 标明了这是返回值的注释,不能随意修改。

声明公共API

目录include/libvirt 下有众多以libvirt- 开头的头文件,公共API 分散在其中。因为新的API 返回在运行虚拟化的主机上某个文件的某段内容,所以我们在头文件include/libvirt/libvirt-host.h 声明这个API。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

 

diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h

index 07b5d1594..72db263d2 100644

--- a/include/libvirt/libvirt-host.h

+++ b/include/libvirt/libvirt-host.h

@@ -686,5 +686,27 @@ int virNodeAllocPages(virConnectPtr conn,

unsigned int cellCount,

unsigned int flags);

+/**

+ * VIR_CONNECT_MAGIC_FILE_PATH:

+ *

+ * This is the absolute path of file.

+ */

+#define VIR_CONNECT_MAGIC_FILE_PATH ("/var/run/libvirt/magic_file")

+

+/**

+ * VIR_CONNECT_MAGIC_FILE_FORBIDDEN_STR:

+ *

+ * If file's content match this, qemu driver will refused to boot VM

+ */

+#define VIR_CONNECT_MAGIC_FILE_FORBIDDEN_STR ("0xabadcafe")

+

+/**

+ * VIR_CONNECT_MAGIC_FILE_CONTENT_LEN:

+ *

+ * Max length of file.

+ */

+#define VIR_CONNECT_MAGIC_FILE_CONTENT_LEN (32)

+

+char *virConnectGetMagicFileContent(virConnectPtr conn);

#endif /* __VIR_LIBVIRT_HOST_H__ */

这个Patch 做的事情非常简单:定义了三个以后会用到的宏,并且声明了公共API。因为这个功能需要访问远程主机上的文件,所以公共API 需要一个参数virConnectPtr,通过这个指针我们可以调用具体的remote 或hypervisor 驱动(前者用于远程调用,后者是真正操纵虚拟化的驱动,例如QEMU 驱动)。

除了这个文件以外,还需要将公共API 在src/libvirt_public.syms 中导出。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

 

diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms

index e01604cad..4db27dc2b 100644

--- a/src/libvirt_public.syms

+++ b/src/libvirt_public.syms

@@ -746,4 +746,9 @@ LIBVIRT_2.2.0 {

virConnectNodeDeviceEventDeregisterAny;

} LIBVIRT_2.0.0;

+LIBVIRT_2.5.0 {

+ global:

+ virConnectGetMagicFileContent;

+} LIBVIRT_2.2.0;

+

# .... define new API here using predicted next version number ....

完成这一步工作之后,新的公共API 就可以被其他的函数所调用。

实现公共API

对应头文件include/libvirt/libvirt-host.h,我们需要在文件src/libvirt-host.c 中实现新API。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

 

diff --git a/src/libvirt-host.c b/src/libvirt-host.c

index 335798abf..0b8b41ca9 100644

--- a/src/libvirt-host.c

+++ b/src/libvirt-host.c

@@ -1482,3 +1482,36 @@ virNodeAllocPages(virConnectPtr conn,

virDispatchError(conn);

return -1;

}

+

+

+/**

+ * virConnectGetMagicFileContent:

+ *

+ * @conn: virConnect connection

+ *

+ * Get content of magic file, max length is VIR_CONNECT_MAGIC_FILE_CONTENT_LEN.

+ *

+ * Returns content of file if all succeed or NULL upon any failure.

+ */

+char *

+virConnectGetMagicFileContent(virConnectPtr conn)

+{

+ VIR_DEBUG("conn=%p", conn);

+

+ virResetLastError();

+

+ virCheckConnectReturn(conn, NULL);

+

+ if (conn->driver->connectGetMagicFileContent) {

+ char *ret = conn->driver->connectGetMagicFileContent(conn);

+ if (!ret)

+ goto error;

+ return ret;

+ }

+

+ virReportUnsupportedError();

+

+ error:

+ virDispatchError(conn);

+ return NULL;

+}

在这个Patch 里我们虽然实现了公共API,但是没有在其中做具体的操作,而是根据参数conn 调用了驱动connectGetMagicFileContent,具体的工作将由该驱动完成。现在我们无法直接判断该驱动是一个reomte 驱动还是hypervisor 驱动,通常来说如果你正在使用一个运行libvirtd 的远程主机,那么此处将是一个remote 驱动,否则将会直接调用hypervisor 驱动。

到现在为止,假设我们使用virsh get-magic 在标准输出上打印出文件的内容时,函数的调用链如下(假设直接调用hypervisor 驱动)。以后每一部分的工作结束后,我们都将重新整理这个调用链以方便理清我们都做了什么。

??? -> virConnectGetMagicFileContent@LibVirt -> connectGetMagicFileContent@hypervisor -> ???

实现hypervisor 驱动

LibVirt 可用的hypervisor 有很多,这里我们只为最常用的QEMU 编写驱动。

添加内部驱动API

因为LibVirt 在用户层面上提供了统一的API,而这个公共API 调用了一个确定的驱动API。因此我们需要在src/driver-hypervisor.h 中确定这个API 以提供给公共API 调用。后面我们会用到几个结构体变量将这个统一的驱动API 和具体的hypervisor 驱动函数关联起来,然后在hypervisor 驱动中具体的实现它,从而提供无关虚拟化细节的API。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

 

diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h

index 51af73200..78de6b04a 100644

--- a/src/driver-hypervisor.h

+++ b/src/driver-hypervisor.h

@@ -1251,6 +1251,9 @@ typedef int

int state,

unsigned int flags);

+typedef char *

+(*virDrvConnectGetMagicFileContent)(virConnectPtr conn);

+

typedef struct _virHypervisorDriver virHypervisorDriver;

typedef virHypervisorDriver *virHypervisorDriverPtr;

@@ -1489,6 +1492,7 @@ struct _virHypervisorDriver {

virDrvDomainMigrateStartPostCopy domainMigrateStartPostCopy;

virDrvDomainGetGuestVcpus domainGetGuestVcpus;

virDrvDomainSetGuestVcpus domainSetGuestVcpus;

+ virDrvConnectGetMagicFileContent connectGetMagicFileContent;

};

这里我们声明了一个virDrvConnectGetMagicFileContent 类型的函数指针变量,并添加到了结构体类型_virHypervisorDriver的声明当中,下面在QEMU 驱动中我们会将这个函数指针指向具体的驱动函数。从而完成LibVirt API 到QEMU 驱动函数的调用。

添加hypervisor 公共API

现在我们只需实现QEMU 的驱动函数,并在结构体变量qemuHypervisorDriver 中用新的驱动函数为上一节新加的函数指针赋值即可。这样虽然各个hypervisor 的驱动细节各不相同,但是在LibVirt 上却表现为一致的接口,从而为用于隐藏了具体的虚拟化细节。

注意通常来说驱动具体的功能并不在此实现,而是在qemu/qemu_capabilities.h 中提供一个QEMU 驱动内可见的API,并在qemu/qemu_capabilities.c 中通过一系列函数调用完成驱动的具体功能。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

 

diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c

index 3517aa2be..4e108e96a 100644

--- a/src/qemu/qemu_driver.c

+++ b/src/qemu/qemu_driver.c

@@ -20273,6 +20273,31 @@ qemuDomainSetGuestVcpus(virDomainPtr dom,

}

+static char *

+qemuConnectGetMagicFileContent(virConnectPtr conn)

+{

+ virQEMUDriverPtr driver = conn->privateData;

+ char *ret = NULL;

+ virCapsPtr caps = NULL;

+

+ if (virConnectGetMagicFileContentEnsureACL (conn) < 0) {

+ return NULL;

+ }

+

+ if (!(caps = virQEMUDriverGetCapabilities(driver, false))) {

+ goto cleanup;

+ }

+

+ if (!(ret = virQEMUCapsGetMagicFileContent(caps))) {

+ goto cleanup;

+ }

+

+ cleanup:

+ virObjectUnref(caps);

+ return ret;

+}

+

+

static virHypervisorDriver qemuHypervisorDriver = {

.name = QEMU_DRIVER_NAME,

.connectOpen = qemuConnectOpen, /* 0.2.0 */

@@ -20486,6 +20511,7 @@ static virHypervisorDriver qemuHypervisorDriver = {

.domainMigrateStartPostCopy = qemuDomainMigrateStartPostCopy, /* 1.3.3 */

.domainGetGuestVcpus = qemuDomainGetGuestVcpus, /* 2.0.0 */

.domainSetGuestVcpus = qemuDomainSetGuestVcpus, /* 2.0.0 */

+ .connectGetMagicFileContent = qemuConnectGetMagicFileContent, /* 2.5.0 */

};

这里用到一个权限检查函数virConnectGetMagicFileContentEnsureACL,目前为止我们还没见过它,而它将在我们编写remote 驱动时由src/rpc/gendispatch.pl 生成。

完成hypervisor 驱动的功能

现在我们可以在src/qemu/qemu_capabilities.c 中实现QEMU 驱动具体的功能并在src/qemu/qemu_capabilities.c 中对内部提供一个接口了。这个接口要在src/qemu/qemu_capabilities.h 中声明以便被QEMU 驱动使用。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

 

diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c

index 45ab5bbb6..8bf4efc7b 100644

--- a/src/qemu/qemu_capabilities.c

+++ b/src/qemu/qemu_capabilities.c

@@ -5222,3 +5222,45 @@ virQEMUCapsFillDomainCaps(virCapsPtr caps,

return -1;

return 0;

}

+

+

+char *

+virQEMUCapsGetMagicFileContent(virCapsPtr caps ATTRIBUTE_UNUSED)

+{

+ FILE *fh = NULL;

+ char *content = NULL;

+ char *ret = NULL;

+

+ if (-1 == access(VIR_CONNECT_MAGIC_FILE_PATH, R_OK)) {

+ return NULL;

+ }

+

+ if (!(fh = fopen(VIR_CONNECT_MAGIC_FILE_PATH, "r"))) {

+ virReportSystemError(errno, _("failed to open file %s"),

+ VIR_CONNECT_MAGIC_FILE_PATH);

+ return NULL;

+ }

+

+ if (VIR_ALLOC_N(content, VIR_CONNECT_MAGIC_FILE_CONTENT_LEN) < 0) {

+ ret = NULL;

+ goto cleanup;

+ }

+

+ memset (content, 0, VIR_CONNECT_MAGIC_FILE_CONTENT_LEN);

+

+ if (!fgets(content, VIR_CONNECT_MAGIC_FILE_CONTENT_LEN, fh)) {

+ virReportSystemError(errno, _("failed to read file %s"),

+ VIR_CONNECT_MAGIC_FILE_PATH);

+ ret = NULL;

+ goto cleanup;

+ }

+

+ ret = content;

+

+cleanup:

+ if (VIR_FCLOSE (fh) < 0) {

+ virReportSystemError(errno, _("failed to close file %d"), fileno (fh));

+ }

+

+ return ret;

+}

diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h

index ee4bbb329..4efd31e38 100644

--- a/src/qemu/qemu_capabilities.h

+++ b/src/qemu/qemu_capabilities.h

@@ -525,4 +525,6 @@ int virQEMUCapsFillDomainCaps(virCapsPtr caps,

virFirmwarePtr *firmwares,

size_t nfirmwares);

+char *virQEMUCapsGetMagicFileContent(virCapsPtr caps);

+

#endif /* __QEMU_CAPABILITIES_H__*/

这一部分结束后,直接实现功能的那一部分代码就已经完成了。

现在调用链如下。

??? -> virConnectGetMagicFileContent@LibVirt -> remoteConnectGetMagicFileContent@remote -> qemuConnectGetMagicFileContent@QEMU -> virQEMUCapsGetMagicFileContent@QEMU

实现remote 驱动

remote 协议由两台主机的LibVirt 交换信息所用,当LibVirt 连接到远程主机时(例如virsh -c),之前实现的公共API 中通过conn->driver 结构体变量调用的函数会由remote 驱动处理。本机的LibVirt 将会请求远程的LibVirt 执行公共API,进而执行远程主机具体的hypervisor 驱动,然后得到返回的数据。既然有信息交换,就必须定义协议。

协议的定义涉及到几个文件,其中需要手动修改的文件如下。

  1. src/remote/remote_driver.c: 定义了客户端的remote 驱动处理函数。
  2. src/remote/remote_protocol.x: 协议格式。
  3. src/remote_protocol-structs: 协议格式。

以上文件的前两个会被脚本src/rpc/gendispatch.pl 处理,进而生成以下四个文件。

  1. src/remote/remote_client_bodies.h: 实现了remote 驱动客户端API。
  2. daemon/remote_dispatch.h: 实现了remote 驱动服务器端API。
  3. src/access/viraccessapicheck.h:声明了API 权限检查函数。
  4. src/access/viraccessapicheck.c:实现了API 权限检查函数。

remote 驱动的函数体就实现在前两个头文件中,客户端的API 经过一系列API 调用,最终由函数virNetClientProgramCall 完成信息的交互,其中两个类型为void * 的参数保存了传递给服务器端remote 驱动的参数和服务器端返回的数据,这两个参数的类型由两个类型为xdrproc_t 的参数确定。

实现客户端驱动

src/remote/remote_driver.c 中,我们只要简单的修改结构体变量hypervisor_driver 即可。

 

1

2

3

4

5

6

7

8

9

10

11

12

 

diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c

index 888052045..65afda6fb 100644

--- a/src/remote/remote_driver.c

+++ b/src/remote/remote_driver.c

@@ -8205,6 +8205,7 @@ static virHypervisorDriver hypervisor_driver = {

.domainMigrateStartPostCopy = remoteDomainMigrateStartPostCopy, /* 1.3.3 */

.domainGetGuestVcpus = remoteDomainGetGuestVcpus, /* 2.0.0 */

.domainSetGuestVcpus = remoteDomainSetGuestVcpus, /* 2.0.0 */

+ .connectGetMagicFileContent = remoteConnectGetMagicFileContent, /* 2.5.0 */

};

static virNetworkDriver network_driver = {

这里我们只是简单的为结构体变量增加了一个元素,这个元素的类型为函数指针virDrvConnectGetMagicFileContent,在定义内部API 时添加到了类型struct _virHypervisorDriver 的声明当中,值为remoteConnectGetMagicFileContent,这是src/rpc/gendispatch.pl 输出到src/remote/remote_client_bodies.h 中的函数名。

定义协议格式

根据之前说的数据交换方式,我们这里需要定义具体的类型给函数virNetClientProgramCall 的两个xdrproc_t 的参数使用。这里针对每个API 需要定义两个结构体,其名字可以参考其他的结构体和对应的API。后跟_args 的结构体为API 的参数,_ret 的则为返回值,virNetClientProgramCall 会将两个void * 类型的参数分别解释为两个结构体类型,并通过这两个参数完成和远程主机的交互。如果remote 驱动不需要参数,那么可以省略以_args 结尾的结构体。

假设这里我们定义了如下两个结构体。

 

1

2

3

4

5

6

7

 

struct remote_connect_abadcafe_args {

remote_nonnull_string str;

};

struct remote_connect_abadcafe_ret {

int need_results;

};

那么它会在文件src/remote/remote_client_bodies.h 中生成类似下面的函数。

 

1

2

 

static int

remoteConnectAbadcafe(virConnectPtr conn, const char *str) { }

除此之外,还需要阅读文件src/remote/remote_protocol.x 第403-426 行的注释,特别是insert@offset 相关的说明,你可能会需要它们的。

文件src/remote/remote_protocol.x 的Patch 如下。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

 

diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x

index e8382dc51..e5c56220d 100644

--- a/src/remote/remote_protocol.x

+++ b/src/remote/remote_protocol.x

@@ -3341,6 +3341,9 @@ struct remote_domain_set_guest_vcpus_args {

unsigned int flags;

};

+struct remote_connect_get_magic_file_content_ret {

+ remote_nonnull_string content;

+};

/*----- Protocol. -----*/

@@ -5934,5 +5937,12 @@ enum remote_procedure {

* @generate: both

* @acl: none

*/

- REMOTE_PROC_NODE_DEVICE_EVENT_UPDATE = 377

+ REMOTE_PROC_NODE_DEVICE_EVENT_UPDATE = 377,

+

+ /**

+ * @generate: both

+ * @priority: high

+ * @acl: connect:read

+ */

+ REMOTE_PROC_CONNECT_GET_MAGIC_FILE_CONTENT = 378

};

除了之前提到的结构体之外,我们还修改了枚举类型remote_procedure,关于这个类型的具体修改请参阅文件src/remote/remote_protocol.x 第3355-3398 行的详尽注释。

根据设置的参数和返回值结构体,在编译过程中,以下函数会生成。

  1. remoteConnectGetMagicFileContent: remote 驱动客户端API,位于文件src/remote/remote_client_bodies.h
  2. spatchConnectGetMagicFileContent: remote 驱动服务器端API,位于文件daemon/remote_dispatch.h
  3. virConnectGetMagicFileContentEnsureACL:API 权限检查函数,位于文件src/access/viraccessapicheck.c(所以请仔细阅读关于@acl 的注释)。

更新remote_protocol-structs

在上面两个步骤做完之后,只需要更新一下src/remote_protocol-structs 即可。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

 

diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs

index b71accc07..383a5361d 100644

--- a/src/remote_protocol-structs

+++ b/src/remote_protocol-structs

@@ -2791,6 +2791,9 @@ struct remote_domain_set_guest_vcpus_args {

int state;

u_int flags;

};

+struct remote_connect_get_magic_file_content_ret {

+ remote_nonnull_string content;

+};

enum remote_procedure {

REMOTE_PROC_CONNECT_OPEN = 1,

REMOTE_PROC_CONNECT_CLOSE = 2,

@@ -3169,4 +3172,5 @@ enum remote_procedure {

REMOTE_PROC_CONNECT_NODE_DEVICE_EVENT_DEREGISTER_ANY = 375,

REMOTE_PROC_NODE_DEVICE_EVENT_LIFECYCLE = 376,

REMOTE_PROC_NODE_DEVICE_EVENT_UPDATE = 377,

+ REMOTE_PROC_CONNECT_GET_MAGIC_FILE_CONTENT = 378

};

现在调用链如下,因为现在增加了客户端和服务端的概念,所以通过在其后增加@client 或@server 区分。

??? -> virConnectGetMagicFileContent@LibVirt@client -> remoteConnectGetMagicFileContent@remote@client -> remoteDispatchConnectGetMagicFileContent@remote@server -> virConnectGetMagicFileContent@LibVirt@server -> qemuConnectGetMagicFileContent@QEMU@server -> virQEMUCapsGetMagicFileContent@QEMU@server

在virsh 中实现功能

最后要做的就是在virsh 中添加一个命令行选项,完成之前实现的公共API 的调用,并且将API 返回的数据打印到屏幕上。

你需要修改tools/virsh-*.c 以接受新的命令行选项。对于一个新的参数,你需要在hostAndHypervisorCmds 结构体数组中添加新的元素,并根据这个结构体中元素的值来定义两个结构体数组,类型分别为vshCmdInfo 和vshCmdOptDef,分别用来确定新选项的说明和参数。

针对我们实现公共API 的位置,这里我们在tools/virsh-host.c 中添加新的选项。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

 

diff --git a/tools/virsh-host.c b/tools/virsh-host.c

index 2fd368662..ed0c39f5d 100644

--- a/tools/virsh-host.c

+++ b/tools/virsh-host.c

@@ -1379,6 +1379,41 @@ cmdNodeMemoryTune(vshControl *ctl, const vshCmd *cmd)

goto cleanup;

}

+/*

+ * "get-magic" command

+ */

+static const vshCmdInfo info_getmagic[] = {

+ {.name = "help",

+ .data = N_("Get magic file's content")

+ },

+ {.name = "desc",

+ .data = N_("Get magic file's content")

+ },

+ {.name = NULL}

+};

+

+static const vshCmdOptDef opts_getmagic[] = {

+ {.name = NULL}

+};

+

+static bool

+cmdGetMagic(vshControl *ctl, const vshCmd *cmd ATTRIBUTE_UNUSED)

+{

+ char *ret = NULL;

+ virshControlPtr priv = ctl->privData;

+

+ ret = virConnectGetMagicFileContent(priv->conn);

+ if (!ret) {

+ vshError(ctl, "%s", _("failed to get magic file's content"));

+ return false;

+ }

+

+ vshPrint(ctl, _("Magic file's content: %s"), ret);

+ VIR_FREE (ret);

+

+ return true;

+}

+

const vshCmdDef hostAndHypervisorCmds[] = {

{.name = "allocpages",

.handler = cmdAllocpages,

@@ -1482,5 +1517,11 @@ const vshCmdDef hostAndHypervisorCmds[] = {

.info = info_version,

.flags = 0

},

+ {.name = "get-magic",

+ .handler = cmdGetMagic,

+ .opts = opts_getmagic,

+ .info = info_getmagic,

+ .flags = 0

+ },

{.name = NULL}

};

最后修改一下tools/virsh.pod,这个文件将会被pod2man 处理成virsh(1) 的手册。POD 是源于Perl 的简单易用的标记语言,可以通过perldoc perlpod 来查看其语法的更多说明。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

 

diff --git a/tools/virsh.pod b/tools/virsh.pod

index 247d2357b..2d19df86b 100644

--- a/tools/virsh.pod

+++ b/tools/virsh.pod

@@ -611,6 +611,18 @@ specified, then the output will be single-quoted where needed, so that

it is suitable for reuse in a shell context. If I<--xml> is

specified, then the output will be escaped for use in XML.

+=item B<get-magic>

+

+Get magic file's content.

+

+=item B<set-magic> [I<content>]

+

+Set magic file's content.

+

+=item B<magic-status>

+

+Show if magic file can be read.

+

=back

=head1 DOMAIN COMMANDS

到现在已经完成了包括文档在内的所有工作,如果你要为LibVirt 添加一个新的功能,所需要做的大约就是这么多。

最终的调用链如下。

cmdGetMagic@virsh@client -> virConnectGetMagicFileContent@LibVirt@client -> remoteConnectGetMagicFileContent@remote@client -> remoteDispatchConnectGetMagicFileContent@remote@server -> virConnectGetMagicFileContent@LibVirt@server -> qemuConnectGetMagicFileContent@QEMU@server -> virQEMUCapsGetMagicFileContent@QEMU@server

Hello World!

现在我们已经完成了最后一步,可以最后编译一次源码并测试一下功能。

 

1

2

 

make -j8

make test -j8

如果编译无误的话,在一个新的终端里运行daemon/libvirtd

 

1

 

sudo ./run ./daemon/libvirtd

然后看一看新添加的API 是否工作正常。

 

1

2

 

echo 'Hello World!' | sudo tee /var/run/libvirt/magic_file

sudo ./run ./tools/virsh -c qemu:///system get-magic

如果一切顺利,现在你已经在终端里看到了刚才写入到文件的Hello World! :)

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值