2020-01-30 19:32:25 +03:00
|
|
|
#!/usr/bin/env python3
|
2012-09-14 16:44:22 +04:00
|
|
|
|
|
|
|
# QEMU Guest Agent Client
|
|
|
|
#
|
|
|
|
# Copyright (C) 2012 Ryota Ozaki <ozaki.ryota@gmail.com>
|
|
|
|
#
|
|
|
|
# This work is licensed under the terms of the GNU GPL, version 2. See
|
|
|
|
# the COPYING file in the top-level directory.
|
|
|
|
#
|
|
|
|
# Usage:
|
|
|
|
#
|
|
|
|
# Start QEMU with:
|
|
|
|
#
|
|
|
|
# # qemu [...] -chardev socket,path=/tmp/qga.sock,server,nowait,id=qga0 \
|
|
|
|
# -device virtio-serial -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0
|
|
|
|
#
|
|
|
|
# Run the script:
|
|
|
|
#
|
|
|
|
# $ qemu-ga-client --address=/tmp/qga.sock <command> [args...]
|
|
|
|
#
|
|
|
|
# or
|
|
|
|
#
|
|
|
|
# $ export QGA_CLIENT_ADDRESS=/tmp/qga.sock
|
|
|
|
# $ qemu-ga-client <command> [args...]
|
|
|
|
#
|
|
|
|
# For example:
|
|
|
|
#
|
|
|
|
# $ qemu-ga-client cat /etc/resolv.conf
|
|
|
|
# # Generated by NetworkManager
|
|
|
|
# nameserver 10.0.2.3
|
|
|
|
# $ qemu-ga-client fsfreeze status
|
|
|
|
# thawed
|
|
|
|
# $ qemu-ga-client fsfreeze freeze
|
|
|
|
# 2 filesystems frozen
|
|
|
|
#
|
2017-11-21 15:04:35 +03:00
|
|
|
# See also: https://wiki.qemu.org/Features/QAPI/GuestAgent
|
2012-09-14 16:44:22 +04:00
|
|
|
#
|
|
|
|
|
2019-02-06 19:29:01 +03:00
|
|
|
import os
|
|
|
|
import sys
|
2012-09-14 16:44:22 +04:00
|
|
|
import base64
|
|
|
|
import random
|
|
|
|
|
2019-02-06 19:29:01 +03:00
|
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', 'python'))
|
|
|
|
from qemu import qmp
|
2012-09-14 16:44:22 +04:00
|
|
|
|
|
|
|
|
|
|
|
class QemuGuestAgent(qmp.QEMUMonitorProtocol):
|
|
|
|
def __getattr__(self, name):
|
|
|
|
def wrapper(**kwds):
|
|
|
|
return self.command('guest-' + name.replace('_', '-'), **kwds)
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
|
|
|
|
class QemuGuestAgentClient:
|
|
|
|
error = QemuGuestAgent.error
|
|
|
|
|
|
|
|
def __init__(self, address):
|
|
|
|
self.qga = QemuGuestAgent(address)
|
|
|
|
self.qga.connect(negotiate=False)
|
|
|
|
|
|
|
|
def sync(self, timeout=3):
|
|
|
|
# Avoid being blocked forever
|
|
|
|
if not self.ping(timeout):
|
|
|
|
raise EnvironmentError('Agent seems not alive')
|
|
|
|
uid = random.randint(0, (1 << 32) - 1)
|
|
|
|
while True:
|
|
|
|
ret = self.qga.sync(id=uid)
|
|
|
|
if isinstance(ret, int) and int(ret) == uid:
|
|
|
|
break
|
|
|
|
|
|
|
|
def __file_read_all(self, handle):
|
|
|
|
eof = False
|
|
|
|
data = ''
|
|
|
|
while not eof:
|
|
|
|
ret = self.qga.file_read(handle=handle, count=1024)
|
|
|
|
_data = base64.b64decode(ret['buf-b64'])
|
|
|
|
data += _data
|
|
|
|
eof = ret['eof']
|
|
|
|
return data
|
|
|
|
|
|
|
|
def read(self, path):
|
|
|
|
handle = self.qga.file_open(path=path)
|
|
|
|
try:
|
|
|
|
data = self.__file_read_all(handle)
|
|
|
|
finally:
|
|
|
|
self.qga.file_close(handle=handle)
|
|
|
|
return data
|
|
|
|
|
|
|
|
def info(self):
|
|
|
|
info = self.qga.info()
|
|
|
|
|
|
|
|
msgs = []
|
|
|
|
msgs.append('version: ' + info['version'])
|
|
|
|
msgs.append('supported_commands:')
|
|
|
|
enabled = [c['name'] for c in info['supported_commands'] if c['enabled']]
|
|
|
|
msgs.append('\tenabled: ' + ', '.join(enabled))
|
|
|
|
disabled = [c['name'] for c in info['supported_commands'] if not c['enabled']]
|
|
|
|
msgs.append('\tdisabled: ' + ', '.join(disabled))
|
|
|
|
|
|
|
|
return '\n'.join(msgs)
|
|
|
|
|
|
|
|
def __gen_ipv4_netmask(self, prefixlen):
|
|
|
|
mask = int('1' * prefixlen + '0' * (32 - prefixlen), 2)
|
|
|
|
return '.'.join([str(mask >> 24),
|
|
|
|
str((mask >> 16) & 0xff),
|
|
|
|
str((mask >> 8) & 0xff),
|
|
|
|
str(mask & 0xff)])
|
|
|
|
|
|
|
|
def ifconfig(self):
|
|
|
|
nifs = self.qga.network_get_interfaces()
|
|
|
|
|
|
|
|
msgs = []
|
|
|
|
for nif in nifs:
|
|
|
|
msgs.append(nif['name'] + ':')
|
|
|
|
if 'ip-addresses' in nif:
|
|
|
|
for ipaddr in nif['ip-addresses']:
|
|
|
|
if ipaddr['ip-address-type'] == 'ipv4':
|
|
|
|
addr = ipaddr['ip-address']
|
|
|
|
mask = self.__gen_ipv4_netmask(int(ipaddr['prefix']))
|
|
|
|
msgs.append("\tinet %s netmask %s" % (addr, mask))
|
|
|
|
elif ipaddr['ip-address-type'] == 'ipv6':
|
|
|
|
addr = ipaddr['ip-address']
|
|
|
|
prefix = ipaddr['prefix']
|
|
|
|
msgs.append("\tinet6 %s prefixlen %s" % (addr, prefix))
|
|
|
|
if nif['hardware-address'] != '00:00:00:00:00:00':
|
|
|
|
msgs.append("\tether " + nif['hardware-address'])
|
|
|
|
|
|
|
|
return '\n'.join(msgs)
|
|
|
|
|
|
|
|
def ping(self, timeout):
|
|
|
|
self.qga.settimeout(timeout)
|
|
|
|
try:
|
|
|
|
self.qga.ping()
|
|
|
|
except self.qga.timeout:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def fsfreeze(self, cmd):
|
|
|
|
if cmd not in ['status', 'freeze', 'thaw']:
|
2018-06-08 15:29:47 +03:00
|
|
|
raise Exception('Invalid command: ' + cmd)
|
2012-09-14 16:44:22 +04:00
|
|
|
|
|
|
|
return getattr(self.qga, 'fsfreeze' + '_' + cmd)()
|
|
|
|
|
|
|
|
def fstrim(self, minimum=0):
|
|
|
|
return getattr(self.qga, 'fstrim')(minimum=minimum)
|
|
|
|
|
|
|
|
def suspend(self, mode):
|
|
|
|
if mode not in ['disk', 'ram', 'hybrid']:
|
2018-06-08 15:29:47 +03:00
|
|
|
raise Exception('Invalid mode: ' + mode)
|
2012-09-14 16:44:22 +04:00
|
|
|
|
|
|
|
try:
|
|
|
|
getattr(self.qga, 'suspend' + '_' + mode)()
|
|
|
|
# On error exception will raise
|
|
|
|
except self.qga.timeout:
|
|
|
|
# On success command will timed out
|
|
|
|
return
|
|
|
|
|
|
|
|
def shutdown(self, mode='powerdown'):
|
|
|
|
if mode not in ['powerdown', 'halt', 'reboot']:
|
2018-06-08 15:29:47 +03:00
|
|
|
raise Exception('Invalid mode: ' + mode)
|
2012-09-14 16:44:22 +04:00
|
|
|
|
|
|
|
try:
|
|
|
|
self.qga.shutdown(mode=mode)
|
|
|
|
except self.qga.timeout:
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_cat(client, args):
|
|
|
|
if len(args) != 1:
|
|
|
|
print('Invalid argument')
|
|
|
|
print('Usage: cat <file>')
|
|
|
|
sys.exit(1)
|
|
|
|
print(client.read(args[0]))
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_fsfreeze(client, args):
|
|
|
|
usage = 'Usage: fsfreeze status|freeze|thaw'
|
|
|
|
if len(args) != 1:
|
|
|
|
print('Invalid argument')
|
|
|
|
print(usage)
|
|
|
|
sys.exit(1)
|
|
|
|
if args[0] not in ['status', 'freeze', 'thaw']:
|
|
|
|
print('Invalid command: ' + args[0])
|
|
|
|
print(usage)
|
|
|
|
sys.exit(1)
|
|
|
|
cmd = args[0]
|
|
|
|
ret = client.fsfreeze(cmd)
|
|
|
|
if cmd == 'status':
|
|
|
|
print(ret)
|
|
|
|
elif cmd == 'freeze':
|
|
|
|
print("%d filesystems frozen" % ret)
|
|
|
|
else:
|
|
|
|
print("%d filesystems thawed" % ret)
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_fstrim(client, args):
|
|
|
|
if len(args) == 0:
|
|
|
|
minimum = 0
|
|
|
|
else:
|
|
|
|
minimum = int(args[0])
|
|
|
|
print(client.fstrim(minimum))
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_ifconfig(client, args):
|
|
|
|
print(client.ifconfig())
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_info(client, args):
|
|
|
|
print(client.info())
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_ping(client, args):
|
|
|
|
if len(args) == 0:
|
|
|
|
timeout = 3
|
|
|
|
else:
|
|
|
|
timeout = float(args[0])
|
|
|
|
alive = client.ping(timeout)
|
|
|
|
if not alive:
|
|
|
|
print("Not responded in %s sec" % args[0])
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_suspend(client, args):
|
|
|
|
usage = 'Usage: suspend disk|ram|hybrid'
|
|
|
|
if len(args) != 1:
|
|
|
|
print('Less argument')
|
|
|
|
print(usage)
|
|
|
|
sys.exit(1)
|
|
|
|
if args[0] not in ['disk', 'ram', 'hybrid']:
|
|
|
|
print('Invalid command: ' + args[0])
|
|
|
|
print(usage)
|
|
|
|
sys.exit(1)
|
|
|
|
client.suspend(args[0])
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_shutdown(client, args):
|
|
|
|
client.shutdown()
|
|
|
|
_cmd_powerdown = _cmd_shutdown
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_halt(client, args):
|
|
|
|
client.shutdown('halt')
|
|
|
|
|
|
|
|
|
|
|
|
def _cmd_reboot(client, args):
|
|
|
|
client.shutdown('reboot')
|
|
|
|
|
|
|
|
|
|
|
|
commands = [m.replace('_cmd_', '') for m in dir() if '_cmd_' in m]
|
|
|
|
|
|
|
|
|
|
|
|
def main(address, cmd, args):
|
|
|
|
if not os.path.exists(address):
|
|
|
|
print('%s not found' % address)
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
if cmd not in commands:
|
|
|
|
print('Invalid command: ' + cmd)
|
|
|
|
print('Available commands: ' + ', '.join(commands))
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
try:
|
|
|
|
client = QemuGuestAgentClient(address)
|
2015-12-18 10:52:42 +03:00
|
|
|
except QemuGuestAgent.error as e:
|
2012-09-14 16:44:22 +04:00
|
|
|
import errno
|
|
|
|
|
|
|
|
print(e)
|
|
|
|
if e.errno == errno.ECONNREFUSED:
|
|
|
|
print('Hint: qemu is not running?')
|
|
|
|
sys.exit(1)
|
|
|
|
|
2013-08-07 19:40:39 +04:00
|
|
|
if cmd == 'fsfreeze' and args[0] == 'freeze':
|
|
|
|
client.sync(60)
|
|
|
|
elif cmd != 'ping':
|
2012-09-14 16:44:22 +04:00
|
|
|
client.sync()
|
|
|
|
|
|
|
|
globals()['_cmd_' + cmd](client, args)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import optparse
|
|
|
|
|
|
|
|
address = os.environ['QGA_CLIENT_ADDRESS'] if 'QGA_CLIENT_ADDRESS' in os.environ else None
|
|
|
|
|
|
|
|
usage = "%prog [--address=<unix_path>|<ipv4_address>] <command> [args...]\n"
|
|
|
|
usage += '<command>: ' + ', '.join(commands)
|
|
|
|
parser = optparse.OptionParser(usage=usage)
|
|
|
|
parser.add_option('--address', action='store', type='string',
|
|
|
|
default=address, help='Specify a ip:port pair or a unix socket path')
|
|
|
|
options, args = parser.parse_args()
|
|
|
|
|
|
|
|
address = options.address
|
|
|
|
if address is None:
|
|
|
|
parser.error('address is not specified')
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
if len(args) == 0:
|
|
|
|
parser.error('Less argument')
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
main(address, args[0], args[1:])
|