一、简介
qemu-ga是在虚拟机中安装的一个agent,宿主机host通过通道(unix socket)与虚拟机vm内部的agent进行通信,这样宿主机就有了一种从外部控制/获取虚拟机的手段。比如:host可以向vm下发执行修改hostname的指令,或者获取vm内所有进程信息的指令。
qemu-ga时刻监听这个unix socket,一旦发现有指令发送来,分析该指令,并执行,通过unix socket返回执行结果,传输的是json字符串。
二、详解
1、centos6下使用qemu-ga
(1)虚拟机连接本机及网络,然后在虚拟机中安装qemu-ga(windows是qemu-ga.exe)
yum install qemu-guest-agent
(2)修改安装后的qemu-ga配置文件
#修改/etc/sysconfig/qemu-ga文件
将
# Enable fsfreeze hook. See the --fsfreeze-hook option in "qemu-ga --help".
FSFREEZE_HOOK_ENABLE=0
改为
# Enable fsfreeze hook. See the --fsfreeze-hook option in "qemu-ga --help".
FSFREEZE_HOOK_ENABLE=1
#修改/etc/sysconfig/qemu-ga,注释掉BLACKLIST_RPC这一行,将所有功能开放
将
BLACKLIST_RPC="guest-file-open,guest-file-close,guest-file-read,guest-file-write,guest-file-seek,guest-file-flush"
改为
#BLACKLIST_RPC="guest-file-open,guest-file-close,guest-file-read,guest-file-write,guest-file-seek,guest-file-flush"
(3)将虚拟机关机,在虚拟机配置文件libvirt.xml中的<devices>下面添加下述配置,并重新启动虚拟机
<channel type='unix'>
<source mode='bind' path='/var/lib/libvirt/qemu/f16x86_64.agent'/>
<target type='virtio' name='org.qemu.guest_agent.0'/>
</channel>
(4)测试是否正常
#得到虚拟机对应的domain id
[root@node-12 ~]# virsh list
Id 名称 状态
----------------------------------------------------
90 instance-0000209f running
#使用命令进行测试
[root@node-12 ~]# virsh qemu-agent-command 90 '{"execute":"guest-info"}'
{"return":{"version":"0.12.1","supported_commands":[{"enabled":true,"name":"guest-set-vcpus"},{"enabled":true}...
(5)freeze文件系统的方法
#直接用virsh命令,freeze文件系统
[root@node-12 ~]# virsh qemu-agent-command 90 '{"execute":"guest-fsfreeze-freeze"}'
{"return":1}
#freeze后,可以查询当前虚拟机文件系统的状态,表明是frozen
[root@node-12 ~]# virsh qemu-agent-command 90 '{"execute":"guest-fsfreeze-status"}'
{"return":"frozen"}
#thaw(解封)文件系统
[root@node-12 ~]# virsh qemu-agent-command 90 '{"execute":"guest-fsfreeze-thaw"}'
{"return":1}
#thaw后,文件系统为解封状态
[root@node-12 ~]# virsh qemu-agent-command 90 '{"execute":"guest-fsfreeze-status"}'
{"return":"thawed"}
2、python使用qemu-ga(类实现)
from commands import getstatusoutput
import logger
import os, sys, stat
import json
import base64
import crypt
import string
import random
import re
import socket
########################Configure############################
FILE_OPEN_READ="""{"execute":"guest-file-open", "arguments":{"path":"%s","mode":"r"}}"""
FILE_OPEN_WRITE="""{"execute":"guest-file-open", "arguments":{"path":"%s","mode":"%s"}}"""
FILE_READ="""{"execute":"guest-file-read", "arguments":{"handle":%s,"count":%d}}"""
FILE_WRITE="""{"execute":"guest-file-write", "arguments":{"handle":%s,"buf-b64":"%s"}}"""
FILE_CLOSE="""{"execute":"guest-file-close", "arguments":{"handle":%s}}"""
class QemuQuestAgent:
def __init__(self):
self.__socketFileName = ""
self.__totalSize = 0
self.__currentSize = 0
def setSocketFile(self, filename):
self.socketFileName = filename
if not os.path.exists(self.socketFileName):
logger.error("%s do not exist!", self.socketFileName)
return False
return True
def resetPassWord(self, newPassword):
passwordFile = "/etc/shadow"
content = self._QemuQuestAgent__guestFileRead(passwordFile)
if not content.strip():
return False
content = base64.standard_b64decode(content)
user_array = re.split("\n",content)
for iter,line in enumerate(user_array):
info = line.split(":")
if info[0] == "root":
info[1] = self._QemuQuestAgent__generationPwd(newPassword)
user_array[iter] = ":".join(info)
break
content = base64.standard_b64encode("\n".join(user_array))
write_count = self._QemuQuestAgent__guestFileWrite(passwordFile, content, "w+")
if write_count > 0:
logger.info("change password successfully!")
return True
else:
return False
def putFileToVM(self, fileName, vmFilePathName):
if not os.path.exists(fileName):
logger.error("%s do not exist" % (fileName))
return False
if vmFilePathName[-1] == "/":
vmFilePathName += fileName.split("/")[-1]
filestats = os.stat(fileName)
self.__totalSize = filestats[stat.ST_SIZE]
if self.__totalSize <= 0:
logger.error("%s is Empty!" % (fileName))
return False
fd = open(fileName, "r")
self.__currentSize = 0
total = 0
while True:
content = fd.read(4096)
total += 4096;
self.__currentSize += len(content) #<=4096
content = base64.standard_b64encode(content)
write_count = self._QemuQuestAgent__guestFileWrite(vmFilePathName, content, "a+")
if write_count <= 0:
fd.close()
return False
if total >= self.__totalSize:
break
fd.close()
return True
def processWrite(self):
return (self.__currentSize, self.__totalSize)
def __getResult(self, command, fileName = ""):
resultStr = ""
if fileName.strip():
self.socketFileName = fileName
if not os.path.exists(self.socketFileName):
return None;
sockfd = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server_address = self.socketFileName
try:
sockfd.connect(server_address)
except socket.error, msg:
logger.error("guestagent:%s,%s", socket.error, msg)
return None
try:
sockfd.send(command)
resultStr = sockfd.recv(10240)
finally:
sockfd.close()
return None if not resultStr else json.loads(resultStr);
def __generationPwd(self, pwd):
salt=''.join(random.choice(string.ascii_letters + string.digits + "./") for _ in range(16))
return crypt.crypt(pwd, "$6$%s" % salt)
def __guestFileRead(self, path):
file_handle = -1
content = self._QemuQuestAgent__getResult(FILE_OPEN_READ % (path))
if not content:
return ""
file_handle = content["return"]
if file_handle == -1:
return ""
file_content = self._QemuQuestAgent__getResult(FILE_READ % (file_handle,102400))["return"]["buf-b64"]
self._QemuQuestAgent__getResult(FILE_CLOSE % file_handle)
if not file_content:
return ""
else:
return file_content
def __guestFileWrite(self, path, content, mode):
file_handle = -1
content = self._QemuQuestAgent__getResult(FILE_OPEN_WRITE % (path, mode))
if not content:
return -1
file_handle = content["return"]
if file_handle == -1:
return -2
write_count = self._QemuQuestAgent__getResult(FILE_WRITE % (file_handle,content))["return"]["count"]
self._QemuQuestAgent__getResult(FILE_CLOSE % file_handle)
return write_count
########################test############################
def testagent():
instance = QemuQuestAgent()
if instance.setSocketFile("/var/lib/libvirt/qemu/test.agent"):
if instance.resetPassWord("abc123"):
return True
if instance.putFileToVM("test.py", "/root/test.py"):
return True
return False
if __name__ == '__main__':
testagent()
3、Qt使用qemu-ga
(1)qemuquestagent.h
#ifndef QEMUQUESTAGENT_H
#define QEMUQUESTAGENT_H
#include <QString>
#include <QDebug>
#include <QStringList>
#include <QDebug>
/***********************
*<channel type='unix'>
* <source mode='bind' path='/var/lib/libvirt/qemu/test.agent'/>
* <target type='virtio' name='com.163.spice.0'/>
*</channel>
*
* qemu-ga --daemonize -m virtio-serial -p /dev/virtio-ports/org.qemu.guest_agent.0
*
**********************/
/*************QemuQuestAgent*****************/
class HostConnectVM;
class ResetPassWord;
class FileManager;
class QemuQuestAgent
{
public:
QemuQuestAgent();
~QemuQuestAgent();
void getQGACommandAll();
void resetPassWord();
void fileOperation();
private:
HostConnectVM *hostVM;
ResetPassWord *passWord;
FileManager *fileManager;
QString socketFileName;
};
/*************ResetPassWord*****************/
class ResetPassWord
{
public:
ResetPassWord(HostConnectVM *host);
~ResetPassWord();
int checkOldPassWord(const QString &password);
int setNewPassWord(const QString &password);
private:
QString passwdFileRead();
bool passwdFileWrite(const QString &password);
bool openFile(QString mode);
void closeFile();
QString generationNewPassWord(const QString &password);
private:
int fileHandle;
QString filePasswd;
HostConnectVM *pwHost;
QStringList lineContent;
};
/*************FileManager*****************/
class FileManager
{
public:
FileManager(HostConnectVM *host);
~FileManager();
int getFile(const QString &filePathName, const QString &localPath = "");
int putFile(const QString &localPathName, const QString &clientPathName);
bool getFileContent(const QString &filename, const QString &localPathName);
bool putFileContent(const QString &localName, const QString &clientName);
private:
int openFile(const QString &filename, const QString &mode);
private:
HostConnectVM *fmHost;
};
#endif // QEMUQUESTAGENT_H
(2)qemuquestagent.cpp
#include <shadow.h>
#include <unistd.h>
#include "qemuquestagent.h"
#include "questagentcomponent.h"
#include "defineconstants.h"
#include "jsonparse.h"
#include "base64.h"
/*************QemuQuestAgent*****************/
QemuQuestAgent::QemuQuestAgent()
{
socketFileName = "/var/lib/libvirt/qemu/test.agent";
hostVM = new HostConnectVM(socketFileName);
passWord = new ResetPassWord(hostVM);
fileManager = new FileManager(hostVM);
}
QemuQuestAgent::~QemuQuestAgent()
{
if (hostVM) {
delete hostVM;
hostVM = NULL;
}
if (passWord) {
delete passWord;
passWord = NULL;
}
if (fileManager) {
delete fileManager;
fileManager = NULL;
}
}
void QemuQuestAgent::getQGACommandAll()
{
QString info = hostVM->getResult(GUEST_INFO);
qDebug() << __FUNCTION__<<__LINE__ << info;
if (info == "error2") {
qDebug() << "can not conncet to the client of VM";
}
}
void QemuQuestAgent::resetPassWord()
{
QString oldPassWord = "abc123";
QString newPassWord = "abc124";
int flag = passWord->checkOldPassWord(oldPassWord);
if (flag == 1) {
qDebug() << "old password is wrong";
}
else if (flag == 0){
qDebug() << "password is right";
if (passWord->setNewPassWord(newPassWord) == 0) {
qDebug() << "set newpassword succeed";
}
else {
qDebug() << "set newpassword failed";
}
}
else {
//qDebug() << __LINE__ << "the client of VM is off";
}
}
void QemuQuestAgent::fileOperation()
{
qDebug() << "---fileOperation---";
QString file = "/tmp/test1K.iso";
fileManager->getFile(file, "/tmp/abc/");
//fileManager->putFile("/tmp/abc/test1M.iso", "/tmp/");
}
/*************ResetPassWord*****************/
ResetPassWord::ResetPassWord(HostConnectVM *host)
: fileHandle(-1)
{
filePasswd = "/etc/shadow";
pwHost = host;
}
ResetPassWord::~ResetPassWord()
{
}
int ResetPassWord::checkOldPassWord(const QString &password)
{
if (openFile("r") == false) {
return -1;
}
QString fileContent = passwdFileRead();
closeFile();
lineContent.clear();
lineContent = fileContent.split("\n");
for (int index = 0; index < lineContent.size(); ++index) {
QStringList fields = lineContent.at(index).split(":");
if (fields.at(0) == "root") {
QStringList passwdList = fields.at(1).trimmed().split("$");
QString saltBuff = QString("$%1$%2").arg(passwdList[1]).arg(passwdList[2]);
char *shadowPwd = crypt(password.toStdString().data(), saltBuff.toStdString().data());
if (!strcmp(shadowPwd, fields.at(1).trimmed().toStdString().data())) { //compare old and new password
return 0;
}
else {
return 1;
}
}
}
return false;
}
int ResetPassWord::setNewPassWord(const QString &password)
{
for (int index = 0; index < lineContent.size(); ++index) {
QStringList fields = lineContent.at(index).split(":");
if (fields.at(0) == "root") {
fields[1] = generationNewPassWord(password);
lineContent[index] = fields.join(":");
break;
}
}
QString newFileContent = "";
if (lineContent.size() > 0) {
newFileContent = lineContent.join("\n");
}
if (openFile("r+") == false && newFileContent.isEmpty()) {
return -1;
}
if (passwdFileWrite(newFileContent) == false) {
return 1;
}
lineContent.clear();
closeFile();
return 0;
}
QString ResetPassWord::passwdFileRead()
{
QString resultStr = pwHost->getResult(FILE_READ.arg(fileHandle).arg(1400));
//qDebug() << "**********" << resultStr<<"****";
QMap<QString, QString>resultMap = JsonParse::jsonParseEnter(resultStr);
// {"return": {"buf-b64": "aGVsbG8gd29ybGQhCg==", "count": 13, "eof": true}}
if (resultMap.count() != 3) {
qDebug() << "JsonParse failed!" <<endl;
return false;
}
else {
if (resultMap["eof"] != "true") {
qDebug() << "qga readfile failed!" <<endl;
return false;
}
string outResult = "";
Base64TransCode::Base64Decode(resultMap["buf-b64"].toStdString(), &outResult);
return QString::fromStdString(outResult);
}
}
bool ResetPassWord::passwdFileWrite(const QString &password)
{
string outResult = "";
Base64TransCode::Base64Encode(password.toStdString(), &outResult);
QString resultStr = pwHost->getResult(FILE_WRITE.arg(fileHandle).arg(QString::fromStdString(outResult)));
QMap<QString, QString>resultMap = JsonParse::jsonParseEnter(resultStr);
// {"return": {"count": 13, "eof": false}}
qDebug() << "&&&" << resultMap.count() << resultMap["eof"] << resultMap["count"] << QString::fromStdString(outResult) << outResult.size();
if (resultMap.count() == 2 && resultMap["eof"] == "false") {
qDebug() << "change password successed";
return true;
}
else {
return false;
}
}
bool ResetPassWord::openFile(QString mode)
{
QString resultStr = pwHost->getResult(FILE_OPEN.arg(filePasswd).arg(mode));
if (resultStr == "error2") {
qDebug() << "can not conncet to the client of VM";
return false;
}
//qDebug() << "aoyang---------------" << resultStr;
QMap<QString, QString> resultMap = JsonParse::jsonParseEnter(resultStr);
fileHandle = resultMap.begin().value().toInt();
if (fileHandle < 0) {
return false;
}
return true;
}
void ResetPassWord::closeFile()
{
pwHost->getResult(FILE_FLUSH.arg(fileHandle));
pwHost->getResult(FILE_CLOSE.arg(fileHandle));
}
QString ResetPassWord::generationNewPassWord(const QString &password)
{
char seqCode[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";
QString verifyCode = "$6$";
QTime time= QTime::currentTime();
qsrand(time.msec()+time.second()*1000);
for(int i = 0; i < 16; i++) {
int num = qrand() % strlen(seqCode);
verifyCode += seqCode[num];
}
QString shadowPwd = crypt(password.toStdString().data(), verifyCode.toStdString().data());
return shadowPwd;
}
/*************FileManager*****************/
FileManager::FileManager(HostConnectVM *host)
{
fmHost = host;
}
FileManager::~FileManager()
{
}
int FileManager::getFile(const QString &filePathName, const QString &localPath)
{
if (filePathName.isEmpty()) {
return -1;
}
QFileInfo info(filePathName);
QString locaFilename = localPath;
if (localPath.isEmpty()) {
locaFilename = QDir::currentPath();
locaFilename += "/";
locaFilename += info.fileName();
}
else if (localPath.right(1) == "/"){ //path
locaFilename += info.fileName();
}
QFileInfo loaclInfo(locaFilename);
QString dirName = loaclInfo.absolutePath();
QDir dir(dirName);
if (!dir.exists()) {
dir.mkpath(dirName);
}
//write content to file
//qDebug() << "locaFilename=" << locaFilename << "filePathName=" <<filePathName;
getFileContent(filePathName, locaFilename);
return 0;
}
bool FileManager::getFileContent(const QString &filename, const QString &localPathName)
{
int handle = openFile(filename, "r");
if (handle < 0) {
return false;
}
QFile file(localPathName);
file.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream out(&file);
while(true) {
QString resultStr = fmHost->getResult(FILE_READ.arg(handle).arg(1400));
//qDebug() << "--------" << resultStr<<"---------";
QMap<QString, QString>resultMap = JsonParse::jsonParseEnter(resultStr);
// {"return": {"buf-b64": "aGVsbG8gd29ybGQhCg==", "count": 13, "eof": false}}
if (resultMap.count() != 3) {
qDebug() << "JsonParse failed!" <<endl;
return false;
}
else {
//qDebug() << "++++++++++++++++++++" << resultMap["count"];
if (resultMap["count"].toInt() < 0) {
qDebug() << "qga readfile failed!" <<endl;
return false;
}
string outResult = "";
Base64TransCode::Base64Decode(resultMap["buf-b64"].toStdString(), &outResult);
//qDebug() << "out-------put====" << QString::fromStdString(outResult);
out << QString::fromStdString(outResult);
file.flush();
if (resultMap["eof"] == "true") {
file.close();
fmHost->getResult(FILE_CLOSE.arg(handle));
return true;
}
}
}
return false;
}
int FileManager::putFile(const QString &localPathName, const QString &clientPathName)
{
QFileInfo localFile(localPathName);
if (!localFile.isFile()) {
qDebug() << "file is not existed";
return -1;
}
//long fileSize = localFile.size();
QString clientFileName = clientPathName;
if (clientPathName.right(1) == "/") {
clientFileName += localFile.fileName();
}
putFileContent(localPathName, clientFileName);
}
bool FileManager::putFileContent(const QString &localName, const QString &clientName)
{
QFile file(localName);
long fileSize = file.size();
if (fileSize <= 0) {
return false;
}
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
int handle = openFile(clientName, "w");
if (handle < 0) {
return false;
}
int total = 0;
do {
string fileContent = file.read(10240).data();
total += 10240;
string outResult = "";
Base64TransCode::Base64Encode(fileContent, &outResult);
QString resultStr = fmHost->getResult(FILE_WRITE.arg(handle).arg(QString::fromStdString(outResult)));
fmHost->getResult(FILE_FLUSH.arg(handle));
QMap<QString, QString>resultMap = JsonParse::jsonParseEnter(resultStr);
// {"return": {"count": 13, "eof": false}}
//qDebug() << "&&&" << resultMap.count() << resultMap["eof"] << resultMap["count"]<< outResult.size();
if (resultMap.count() != 2 || resultMap["count"].toInt() <= 0) {
qDebug() << "write file error";
fmHost->getResult(FILE_CLOSE.arg(handle));
return false;
}
}while(total < fileSize);
fmHost->getResult(FILE_CLOSE.arg(handle));
}
int FileManager::openFile(const QString &filename, const QString &mode)
{
QString resultStr = fmHost->getResult(FILE_OPEN.arg(filename).arg(mode));
qDebug() << FILE_OPEN.arg(filename).arg(mode);
if (resultStr == "error2") {
qDebug() << "can not conncet to the client of VM";
return -2;
}
QMap<QString, QString> resultMap = JsonParse::jsonParseEnter(resultStr);
int fileHandle = resultMap.begin().value().toInt();
if (fileHandle <= 0) {
qDebug() << "open file failed!";
return -1;
}
return fileHandle;
}
(3)questagentcomponent.cpp
socket通讯,发送命令返回结果
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <QDebug>
#include "questagentcomponent.h"
HostConnectVM::HostConnectVM(const QString &file)
{
socketFile = file;
}
HostConnectVM::~HostConnectVM()
{
}
QString HostConnectVM::getResult(QString command, QString file)
{
if (!file.isEmpty()) {
socketFile = file;
}
QString resultStr = "";
/* create a socket */
struct sockaddr_un address;
address.sun_family = AF_UNIX;
strcpy(address.sun_path, socketFile.toStdString().data());
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) {
qDebug() << "create socket failed";
resultStr = "error1";
return resultStr;
}
/* connect to the server */
int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));
if(result == -1) {
qDebug() << "connect socket failed";
resultStr = "error2";
return resultStr;
}
if((result = write(sockfd, command.toStdString().data(), command.length())) != command.length()) {
qDebug() << "write socket failed";
resultStr = "error3";
return resultStr;
}
char buff[10240] = {0};
if ((result = read(sockfd, buff, 10240)) == -1) {
qDebug() << "read socket failed";
resultStr = "error4";
return resultStr;
}
resultStr = buff;
resultStr = resultStr.trimmed();
close(sockfd);
return resultStr;
}
(4)jsonparse.cpp
传输数据格式json的封装和解析
#include "jsonparse.h"
JsonParse::JsonParse()
{
}
JsonParse::~JsonParse()
{
}
QMap<QString, QString> JsonParse::jsonParseEnter(const QString &str)
{
QMap<QString, QString> map;
map.clear();
if (str.trimmed().isEmpty()) {
return map;
}
QScriptEngine engine;
QScriptValue source = engine.evaluate("value="+str);
QScriptValue valueReturn = source.property("return");
if (valueReturn.isObject()) {
QScriptValueIterator it(valueReturn);
while(it.hasNext()) {
it.next();
map.insert(it.name(), it.value().toString());
}
}
else {
map.insert("return", valueReturn.toString());
}
#ifndef PRINT_DEBUG
QMap<QString, QString>::const_iterator iter = map.constBegin();
for(;iter != map.constEnd(); iter++) {
qDebug() << "---key=" << iter.key() << ", value=" << iter.value();
}
#endif
}
(5)base64.cpp
base64将字符串以MIME BASE64编码,此种编码是为了使二进制数据可以通过非纯8-bit的传输层传输,可以让中文字或者图片也能在网络上顺利传输,例如电子邮件的主体。在BASE64编码后的字符串只包含英文字母大小写、阿拉伯数字、加号与反斜线,共64个基本字符,不包含其它特殊的字符,因而才取名BASE64。编码后的字符串比原来的字符串长度再加1/3左右。
#include <string.h>
#include "base64.h"
bool Base64TransCode::Base64Encode(const string& input, string* output)
{
string temp;
temp.resize(modp_b64_encode_len(input.size())); // makes room for null byte
// null terminates result since result is base64 text!
int input_size = static_cast<int>(input.size());
int output_size= modp_b64_encode(&(temp[0]), input.data(), input_size);
if (output_size < 0) return false;
temp.resize(output_size); // strips off null byte
output->swap(temp);
return true;
}
bool Base64TransCode::Base64Decode(const string& input, string* output)
{
string temp;
temp.resize(modp_b64_decode_len(input.size()));
// does not null terminate result since result is binary data!
int input_size = static_cast<int>(input.size());
int output_size = modp_b64_decode(&(temp[0]), input.data(), input_size);
if (output_size < 0) return false;
temp.resize(output_size);
output->swap(temp);
return true;
}
(6)main.cpp
#include <QCoreApplication>
#include "qemuquestagent.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QemuQuestAgent w;
//w.resetPassWord();
w.getQGACommandAll();
//w.fileOperation();
return a.exec();
}
三、总结
(1)参考:http://www.zoues.com/index.php/2015/10/13/qemu-guest-agent/、http://www.csdn123.com/html/itweb/20130729/27101_27081_27076.htm、http://blog.csdn.net/taiyang1987912/article/details/45191153。
(2)若有问题或建议,请留言,在此感谢!