經常使用vmWare的同學都知道有vmware-tools這個工具,這個安裝在vm內部的工具,可以實現宿主機與虛擬機的通訊,大大增強了虛擬機的性能與功能,
如vmware現在的Unity mode下可以讓應用程序無縫地與宿主機交互,更不用提直接復制粘帖文件及內容的小功能了。
對於KVM而言,其實也有一款這樣的工具叫做 Qemu Guest Agent(以下稱qga).
原理分析:
qga是一個運行在虛擬機內部的普通應用程序(可執行文件名稱默認為qemu-ga,服務名稱默認為qemu-guest-agent),其目的是實現一種宿主機和虛擬機進行交互的方式,這種方式不依賴於網絡,而是依賴於virtio-serial(默認首選方式)或者isa-serial,而QEMU則提供了串口設備的模擬及數據交換的通道,最終呈現出來的是一個串口設備(虛擬機內部)和一個unix socket文件(宿主機上)。
qga通過讀寫串口設備與宿主機上的socket通道進行交互,宿主機上可以使用普通的unix socket讀寫方式對socket文件進行讀寫,最終實現與qga的交互,交互的協議與qmp(QEMU Monitor Protocol)相同(簡單來說就是使用JSON格式進行數據交換),串口設備的速率通常都較低,所以比較適合小數據量的交換。
QEMU virtio串口設備模擬參數:
/usr/bin/kvm(QEMU) \ ……\ -device virtio-serial-pci,id=virtio-serial0,bus=pci.0,addr=0x6 \ -device isa-serial,chardev=charserial1,id=serial1 \ -chardev socket,id=charchannel0,path=/var/lib/libvirt/qemu/test.agent,server,nowait \ -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,id=channel0,\ name=com.163.spice.0
通過上面的參數就可以在宿主機上生成一個unix socket文件,路徑為:/var/lib/libvirt/qemu/test.agent,同時在虛擬機內部生成一個serial設備,名字為com.163.spice.0,設備路徑為:/dev/vport0p1,映射出來的可讀性比較好的路徑為:/dev/virtio-ports/com.163.spice.0,可以在運行qga的時候通過-p參數指定讀寫這個設備。
也可以通的XML文件來配置這個串口設備:
<channel type='unix'> <source mode='bind' path='/var/lib/libvirt/qemu/test.agent'/> <target type='virtio' name='com.163.spice.0'/> </channel>
注意: libvirt-qemu:kvm用戶要有權限讀寫'/var/lib/libvirt/qemu/test.agent'
已有功能
目前qga最新版本為1.5.50,linux已經實現下面的所有功能,windows僅支持加*的那些功能:
Ø guest-sync-delimited*:宿主機發送一個int數字給qga,qga返回這個數字,並且在后續返回字符串響應中加入ascii碼為0xff的字符,其作用是檢查宿主機與qga通信的同步狀態,主要用在宿主機上多客戶端與qga通信的情況下客戶端間切換過程的狀態同步檢查,比如有兩個客戶端A、B,qga發送給A的響應,由於A已經退出,目前B連接到qga的socket,所以這個響應可能被B收到,如果B連接到socket之后,立即發送該請求給qga,響應中加入了這個同步碼就能區分是A的響應還是B的響應;在qga返回宿主機客戶端發送的int數字之前,qga返回的所有響應都要忽略; Ø guest-sync*:與上面相同,只是不在響應中加入0xff字符; Ø guest-ping*:Ping the guest agent, a non-error return implies success; Ø guest-get-time*:獲取虛擬機時間(返回值為相對於1970-01-01 in UTC,Time in nanoseconds.); Ø guest-set-time*:設置虛擬機時間(輸入為相對於1970-01-01 in UTC,Time in nanoseconds.); Ø guest-info*:返回qga支持的所有命令; Ø guest-shutdown*:關閉虛擬機(支持halt、powerdown、reboot,默認動作為powerdown); Ø guest-file-open:打開虛擬機內的某個文件(返回文件句柄); Ø guest-file-close:關閉打開的虛擬機內的文件; Ø guest-file-read:根據文件句柄讀取虛擬機內的文件內容(返回base64格式的文件內容); Ø guest-file-write:根據文件句柄寫入文件內容到虛擬機內的文件; Ø guest-file-seek:Seek to a position in the file, as with fseek(), and return the current file position afterward. Also encapsulates ftell()'s functionality, just Set offset=0, whence=SEEK_CUR; Ø guest-file-flush:Write file changes bufferred in userspace to disk/kernel buffers; Ø guest-fsfreeze-status:Get guest fsfreeze state. error state indicates; Ø guest-fsfreeze-freeze:Sync and freeze all freezable, local guest filesystems; Ø guest-fsfreeze-thaw:Unfreeze all frozen guest filesystems; Ø guest-fstrim:Discard (or "trim") blocks which are not in use by the filesystem; Ø guest-suspend-disk*:Suspend guest to disk; Ø guest-suspend-ram*:Suspend guest to ram; Ø guest-suspend-hybrid:Save guest state to disk and suspend to ram(This command requires the pm-utils package to be installed in the guest.); Ø guest-network-get-interfaces:Get list of guest IP addresses, MAC addresses and netmasks; Ø guest-get-vcpus:Retrieve the list of the guest's logical processors; guest-set-vcpus:Attempt to reconfigure (currently: enable/disable) logical processors inside the guest。
功能擴展方式
qga功能擴展十分方便,只需要在qapi-schema.json文件中定義好功能名稱、輸入輸出數據類型,然后在commands-posix.c里面增加對應的功能函數即可,下面的補丁即在qga中增加一個通過statvfs獲取虛擬機磁盤空間信息的功能:
diff --git a/qga/commands-posix.c b/qga/commands-posix.c index e199738..2f42a2f 100644 --- a/qga/commands-posix.c +++ b/qga/commands-posix.c @@ -21,6 +21,7 @@ #include <stdio.h> #include <string.h> #include <sys/stat.h> +#include <sys/statvfs.h> #include <inttypes.h> #include "qga/guest-agent-core.h" #include "qga-qmp-commands.h" @@ -1467,6 +1468,36 @@ void qmp_guest_fstrim(bool has_minimum, int64_t minimum, Error **err) } #endif +GuestFileSystemStatistics *qmp_guest_get_statvfs(const char *path, Error **errp) +{ + int ret; + GuestFileSystemStatistics *fs_stat; + struct statvfs *buf; + buf = g_malloc0(sizeof(struct statvfs)); + + + ret = statvfs(path, buf); + if (ret < 0) { + error_setg_errno(errp, errno, "Failed to get statvfs"); + return NULL; + } + + fs_stat = g_malloc0(sizeof(GuestFileSystemStatistics)); + fs_stat->f_bsize = buf->f_bsize; + fs_stat->f_frsize = buf->f_frsize; + fs_stat->f_blocks = buf->f_blocks; + fs_stat->f_bfree = buf->f_bfree; + fs_stat->f_bavail = buf->f_bavail; + fs_stat->f_files = buf->f_files; + fs_stat->f_ffree = buf->f_ffree; + fs_stat->f_favail = buf->f_favail; + fs_stat->f_fsid = buf->f_fsid; + fs_stat->f_flag = buf->f_flag; + fs_stat->f_namemax = buf->f_namemax; + + return fs_stat; +} + /* register init/cleanup routines for stateful command groups */ void ga_command_state_init(GAState *s, GACommandState *cs) { diff --git a/qga/qapi-schema.json b/qga/qapi-schema.json index 7155b7a..a071c3f 100644 --- a/qga/qapi-schema.json +++ b/qga/qapi-schema.json @@ -638,3 +638,52 @@ { 'command': 'guest-set-vcpus', 'data': {'vcpus': ['GuestLogicalProcessor'] }, 'returns': 'int' } + +## +# @GuestFileSystemStatistics: +# +# Information about guest file system statistics. +# +# @f_bsize: file system block size. +# +# @f_frsize: fragment size. +# +# @f_blocks: size of fs in f_frsize units. +# +# @f_bfree: free blocks. +# +# @f_bavail: free blocks for non-root. +# +# @f_files: inodes. +# +# @f_ffree: free inodes. +# +# @f_favail: free inodes for non-root. +# +# @f_fsid: file system id. +# +# @f_flag: mount flags +# +# @f_namemax: maximum filename length. +# +# Since 1.5.10(NetEase) +## +{ 'type': 'GuestFileSystemStatistics', + 'data': { 'f_bsize': 'int', 'f_frsize': 'int', 'f_blocks': 'int', + 'f_bfree': 'int', 'f_bavail': 'int', 'f_files': 'int', + 'f_ffree': 'int', 'f_favail': 'int', 'f_fsid': 'int', + 'f_flag': 'int', 'f_namemax': 'int'} } + +## +# @guest-get-statvfs: +# +# Get the information about guest file system statistics by statvfs. +# +# Returns: @GuestFileSystemStatistics. +# +# Since 1.5.10(NetEase) +## +{ 'command': 'guest-get-statvfs', + 'data': { 'path': 'str' }, + 'returns': 'GuestFileSystemStatistics' } +
中間復雜的類型定義代碼,以及頭文件包含關系處理都由一個python腳本在編譯的時候動態生成出來,這對開發人員來說是非常方便的,開發人員在擴展功能的時候只需要關注輸入、輸出的數據類型,以及功能的函數內容即可。
寫到這里,有經驗的開發人員已經知道如何用qga開發監控平台,下面是網友的實現思路,供參考: