]>
git.proxmox.com Git - mirror_qemu.git/blob - python/qemu/utils/qemu_ga_client.py
d8411bb2d0b5010387512ad35ba2e28dbe9b6ba1
2 QEMU Guest Agent Client
8 # qemu [...] -chardev socket,path=/tmp/qga.sock,server=on,wait=off,id=qga0 \
9 -device virtio-serial \
10 -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0
14 $ qemu-ga-client --address=/tmp/qga.sock <command> [args...]
18 $ export QGA_CLIENT_ADDRESS=/tmp/qga.sock
19 $ qemu-ga-client <command> [args...]
23 $ qemu-ga-client cat /etc/resolv.conf
24 # Generated by NetworkManager
26 $ qemu-ga-client fsfreeze status
28 $ qemu-ga-client fsfreeze freeze
31 See also: https://wiki.qemu.org/Features/QAPI/GuestAgent
34 # Copyright (C) 2012 Ryota Ozaki <ozaki.ryota@gmail.com>
36 # This work is licensed under the terms of the GNU GPL, version 2. See
37 # the COPYING file in the top-level directory.
53 from qemu
.qmp
import ConnectError
, SocketAddrT
54 from qemu
.qmp
.legacy
import QEMUMonitorProtocol
57 # This script has not seen many patches or careful attention in quite
58 # some time. If you would like to improve it, please review the design
59 # carefully and add docstrings at that point in time. Until then:
61 # pylint: disable=missing-docstring
64 class QemuGuestAgent(QEMUMonitorProtocol
):
65 def __getattr__(self
, name
: str) -> Callable
[..., Any
]:
66 def wrapper(**kwds
: object) -> object:
67 return self
.command('guest-' + name
.replace('_', '-'), **kwds
)
71 class QemuGuestAgentClient
:
72 def __init__(self
, address
: SocketAddrT
):
73 self
.qga
= QemuGuestAgent(address
)
74 self
.qga
.connect(negotiate
=False)
76 def sync(self
, timeout
: Optional
[float] = 3) -> None:
77 # Avoid being blocked forever
78 if not self
.ping(timeout
):
79 raise EnvironmentError('Agent seems not alive')
80 uid
= random
.randint(0, (1 << 32) - 1)
82 ret
= self
.qga
.sync(id=uid
)
83 if isinstance(ret
, int) and int(ret
) == uid
:
86 def __file_read_all(self
, handle
: int) -> bytes
:
90 ret
= self
.qga
.file_read(handle
=handle
, count
=1024)
91 _data
= base64
.b64decode(ret
['buf-b64'])
96 def read(self
, path
: str) -> bytes
:
97 handle
= self
.qga
.file_open(path
=path
)
99 data
= self
.__file
_read
_all
(handle
)
101 self
.qga
.file_close(handle
=handle
)
104 def info(self
) -> str:
105 info
= self
.qga
.info()
108 msgs
.append('version: ' + info
['version'])
109 msgs
.append('supported_commands:')
110 enabled
= [c
['name'] for c
in info
['supported_commands']
112 msgs
.append('\tenabled: ' + ', '.join(enabled
))
113 disabled
= [c
['name'] for c
in info
['supported_commands']
115 msgs
.append('\tdisabled: ' + ', '.join(disabled
))
117 return '\n'.join(msgs
)
120 def __gen_ipv4_netmask(cls
, prefixlen
: int) -> str:
121 mask
= int('1' * prefixlen
+ '0' * (32 - prefixlen
), 2)
122 return '.'.join([str(mask
>> 24),
123 str((mask
>> 16) & 0xff),
124 str((mask
>> 8) & 0xff),
127 def ifconfig(self
) -> str:
128 nifs
= self
.qga
.network_get_interfaces()
132 msgs
.append(nif
['name'] + ':')
133 if 'ip-addresses' in nif
:
134 for ipaddr
in nif
['ip-addresses']:
135 if ipaddr
['ip-address-type'] == 'ipv4':
136 addr
= ipaddr
['ip-address']
137 mask
= self
.__gen
_ipv
4_netmask
(int(ipaddr
['prefix']))
138 msgs
.append(f
"\tinet {addr} netmask {mask}")
139 elif ipaddr
['ip-address-type'] == 'ipv6':
140 addr
= ipaddr
['ip-address']
141 prefix
= ipaddr
['prefix']
142 msgs
.append(f
"\tinet6 {addr} prefixlen {prefix}")
143 if nif
['hardware-address'] != '00:00:00:00:00:00':
144 msgs
.append("\tether " + nif
['hardware-address'])
146 return '\n'.join(msgs
)
148 def ping(self
, timeout
: Optional
[float]) -> bool:
149 self
.qga
.settimeout(timeout
)
152 except asyncio
.TimeoutError
:
156 def fsfreeze(self
, cmd
: str) -> object:
157 if cmd
not in ['status', 'freeze', 'thaw']:
158 raise ValueError('Invalid command: ' + cmd
)
159 # Can be int (freeze, thaw) or GuestFsfreezeStatus (status)
160 return getattr(self
.qga
, 'fsfreeze' + '_' + cmd
)()
162 def fstrim(self
, minimum
: int) -> Dict
[str, object]:
163 # returns GuestFilesystemTrimResponse
164 ret
= getattr(self
.qga
, 'fstrim')(minimum
=minimum
)
165 assert isinstance(ret
, dict)
168 def suspend(self
, mode
: str) -> None:
169 if mode
not in ['disk', 'ram', 'hybrid']:
170 raise ValueError('Invalid mode: ' + mode
)
173 getattr(self
.qga
, 'suspend' + '_' + mode
)()
174 # On error exception will raise
175 except asyncio
.TimeoutError
:
176 # On success command will timed out
179 def shutdown(self
, mode
: str = 'powerdown') -> None:
180 if mode
not in ['powerdown', 'halt', 'reboot']:
181 raise ValueError('Invalid mode: ' + mode
)
184 self
.qga
.shutdown(mode
=mode
)
185 except asyncio
.TimeoutError
:
189 def _cmd_cat(client
: QemuGuestAgentClient
, args
: Sequence
[str]) -> None:
191 print('Invalid argument')
192 print('Usage: cat <file>')
194 print(client
.read(args
[0]))
197 def _cmd_fsfreeze(client
: QemuGuestAgentClient
, args
: Sequence
[str]) -> None:
198 usage
= 'Usage: fsfreeze status|freeze|thaw'
200 print('Invalid argument')
203 if args
[0] not in ['status', 'freeze', 'thaw']:
204 print('Invalid command: ' + args
[0])
208 ret
= client
.fsfreeze(cmd
)
213 assert isinstance(ret
, int)
214 verb
= 'frozen' if cmd
== 'freeze' else 'thawed'
215 print(f
"{ret:d} filesystems {verb}")
218 def _cmd_fstrim(client
: QemuGuestAgentClient
, args
: Sequence
[str]) -> None:
222 minimum
= int(args
[0])
223 print(client
.fstrim(minimum
))
226 def _cmd_ifconfig(client
: QemuGuestAgentClient
, args
: Sequence
[str]) -> None:
228 print(client
.ifconfig())
231 def _cmd_info(client
: QemuGuestAgentClient
, args
: Sequence
[str]) -> None:
236 def _cmd_ping(client
: QemuGuestAgentClient
, args
: Sequence
[str]) -> None:
237 timeout
= 3.0 if len(args
) == 0 else float(args
[0])
238 alive
= client
.ping(timeout
)
240 print("Not responded in %s sec" % args
[0])
244 def _cmd_suspend(client
: QemuGuestAgentClient
, args
: Sequence
[str]) -> None:
245 usage
= 'Usage: suspend disk|ram|hybrid'
247 print('Less argument')
250 if args
[0] not in ['disk', 'ram', 'hybrid']:
251 print('Invalid command: ' + args
[0])
254 client
.suspend(args
[0])
257 def _cmd_shutdown(client
: QemuGuestAgentClient
, args
: Sequence
[str]) -> None:
262 _cmd_powerdown
= _cmd_shutdown
265 def _cmd_halt(client
: QemuGuestAgentClient
, args
: Sequence
[str]) -> None:
267 client
.shutdown('halt')
270 def _cmd_reboot(client
: QemuGuestAgentClient
, args
: Sequence
[str]) -> None:
272 client
.shutdown('reboot')
275 commands
= [m
.replace('_cmd_', '') for m
in dir() if '_cmd_' in m
]
278 def send_command(address
: str, cmd
: str, args
: Sequence
[str]) -> None:
279 if not os
.path
.exists(address
):
280 print(f
"'{address}' not found. (Is QEMU running?)")
283 if cmd
not in commands
:
284 print('Invalid command: ' + cmd
)
285 print('Available commands: ' + ', '.join(commands
))
289 client
= QemuGuestAgentClient(address
)
290 except ConnectError
as err
:
292 if isinstance(err
.exc
, ConnectionError
):
293 print('(Is QEMU running?)')
296 if cmd
== 'fsfreeze' and args
[0] == 'freeze':
301 globals()['_cmd_' + cmd
](client
, args
)
305 address
= os
.environ
.get('QGA_CLIENT_ADDRESS')
307 parser
= argparse
.ArgumentParser()
308 parser
.add_argument('--address', action
='store',
310 help='Specify a ip:port pair or a unix socket path')
311 parser
.add_argument('command', choices
=commands
)
312 parser
.add_argument('args', nargs
='*')
314 args
= parser
.parse_args()
315 if args
.address
is None:
316 parser
.error('address is not specified')
319 send_command(args
.address
, args
.command
, args
.args
)
322 if __name__
== '__main__':