CSIT-547: Add x520 L2BD perf test with 2 VMs per DUT
[csit.git] / resources / libraries / python / QemuUtils.py
1 # Copyright (c) 2016 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """QEMU utilities library."""
15
16 from time import time, sleep
17 import json
18
19 from robot.api import logger
20
21 from resources.libraries.python.ssh import SSH, SSHTimeout
22 from resources.libraries.python.constants import Constants
23 from resources.libraries.python.topology import NodeType
24
25
26 class QemuUtils(object):
27     """QEMU utilities."""
28
29     __QEMU_BIN = '/usr/bin/qemu-system-x86_64'
30
31     def __init__(self, qemu_id=1):
32         self._qemu_id = qemu_id
33         # QEMU Machine Protocol socket
34         self._qmp_sock = '/tmp/qmp{0}.sock'.format(self._qemu_id)
35         # QEMU Guest Agent socket
36         self._qga_sock = '/tmp/qga{0}.sock'.format(self._qemu_id)
37         self._qemu_opt = {}
38         # Default 1 CPU.
39         self._qemu_opt['smp'] = '-smp 1,sockets=1,cores=1,threads=1'
40         # Daemonize the QEMU process after initialization. Default one
41         # management interface.
42         self._qemu_opt['options'] = '-cpu host -daemonize -enable-kvm ' \
43             '-machine pc,accel=kvm,usb=off,mem-merge=off ' \
44             '-net nic,macaddr=52:54:00:00:00:{0:02x} -balloon none'\
45             .format(self._qemu_id)
46         self._qemu_opt['ssh_fwd_port'] = 10022
47         # Default serial console port
48         self._qemu_opt['serial_port'] = 4556
49         # Default 512MB virtual RAM
50         self._qemu_opt['mem_size'] = 512
51         # Default huge page mount point, required for Vhost-user interfaces.
52         self._qemu_opt['huge_mnt'] = '/mnt/huge'
53         # Default do not allocate huge pages.
54         self._qemu_opt['huge_allocate'] = False
55         # Default image for CSIT virl setup
56         self._qemu_opt['disk_image'] = '/var/lib/vm/vhost-nested.img'
57         # VM node info dict
58         self._vm_info = {
59             'type': NodeType.VM,
60             'port': self._qemu_opt['ssh_fwd_port'],
61             'username': 'cisco',
62             'password': 'cisco',
63             'interfaces': {},
64         }
65         # Virtio queue count
66         self._qemu_opt['queues'] = 1
67         self._vhost_id = 0
68         self._ssh = None
69         self._node = None
70         self._socks = [self._qmp_sock, self._qga_sock]
71
72     def qemu_set_smp(self, cpus, cores, threads, sockets):
73         """Set SMP option for QEMU
74
75         :param cpus: Number of CPUs.
76         :param cores: Number of CPU cores on one socket.
77         :param threads: Number of threads on one CPU core.
78         :param sockets: Number of discrete sockets in the system.
79         :type cpus: int
80         :type cores: int
81         :type threads: int
82         :type sockets: int
83         """
84         self._qemu_opt['smp'] = '-smp {},cores={},threads={},sockets={}'.format(
85             cpus, cores, threads, sockets)
86
87     def qemu_set_ssh_fwd_port(self, fwd_port):
88         """Set host port for guest SSH forwarding.
89
90         :param fwd_port: Port number on host for guest SSH forwarding.
91         :type fwd_port: int
92         """
93         self._qemu_opt['ssh_fwd_port'] = fwd_port
94         self._vm_info['port'] = fwd_port
95
96     def qemu_set_serial_port(self, port):
97         """Set serial console port.
98
99         :param port: Serial console port.
100         :type port: int
101         """
102         self._qemu_opt['serial_port'] = port
103
104     def qemu_set_mem_size(self, mem_size):
105         """Set virtual RAM size.
106
107         :param mem_size: RAM size in Mega Bytes.
108         :type mem_size: int
109         """
110         self._qemu_opt['mem_size'] = int(mem_size)
111
112     def qemu_set_huge_mnt(self, huge_mnt):
113         """Set hugefile mount point.
114
115         :param huge_mnt: System hugefile mount point.
116         :type huge_mnt: int
117         """
118         self._qemu_opt['huge_mnt'] = huge_mnt
119
120     def qemu_set_huge_allocate(self):
121         """Set flag to allocate more huge pages if needed."""
122         self._qemu_opt['huge_allocate'] = True
123
124     def qemu_set_disk_image(self, disk_image):
125         """Set disk image.
126
127         :param disk_image: Path of the disk image.
128         :type disk_image: str
129         """
130         self._qemu_opt['disk_image'] = disk_image
131
132     def qemu_set_affinity(self, *host_cpus):
133         """Set qemu affinity by getting thread PIDs via QMP and taskset to list
134         of CPU cores.
135
136         :param host_cpus: List of CPU cores.
137         :type host_cpus: list
138         """
139         qemu_cpus = self._qemu_qmp_exec('query-cpus')['return']
140
141         if len(qemu_cpus) != len(host_cpus):
142             logger.debug('Host CPU count {0}, Qemu Thread count {1}'.format(
143                 len(host_cpus), len(qemu_cpus)))
144             raise ValueError('Host CPU count must match Qemu Thread count')
145
146         for qemu_cpu, host_cpu in zip(qemu_cpus, host_cpus):
147             cmd = 'taskset -pc {0} {1}'.format(host_cpu, qemu_cpu['thread_id'])
148             (ret_code, _, stderr) = self._ssh.exec_command_sudo(cmd)
149             if int(ret_code) != 0:
150                 logger.debug('Set affinity failed {0}'.format(stderr))
151                 raise RuntimeError('Set affinity failed on {0}'.format(
152                     self._node['host']))
153
154     def qemu_set_scheduler_policy(self):
155         """Set scheduler policy to SCHED_RR with priority 1 for all Qemu CPU
156         processes.
157
158        :raises RuntimeError: Set scheduler policy failed.
159         """
160         qemu_cpus = self._qemu_qmp_exec('query-cpus')['return']
161
162         for qemu_cpu in qemu_cpus:
163             cmd = 'chrt -r -p 1 {0}'.format(qemu_cpu['thread_id'])
164             (ret_code, _, stderr) = self._ssh.exec_command_sudo(cmd)
165             if int(ret_code) != 0:
166                 logger.debug('Set SCHED_RR failed {0}'.format(stderr))
167                 raise RuntimeError('Set SCHED_RR failed on {0}'.format(
168                     self._node['host']))
169
170     def qemu_set_node(self, node):
171         """Set node to run QEMU on.
172
173         :param node: Node to run QEMU on.
174         :type node: dict
175         """
176         self._node = node
177         self._ssh = SSH()
178         self._ssh.connect(node)
179         self._vm_info['host'] = node['host']
180
181     def qemu_add_vhost_user_if(self, socket, server=True, mac=None):
182         """Add Vhost-user interface.
183
184         :param socket: Path of the unix socket.
185         :param server: If True the socket shall be a listening socket.
186         :param mac: Vhost-user interface MAC address (optional, otherwise is
187             used auto-generated MAC 52:54:00:00:xx:yy).
188         :type socket: str
189         :type server: bool
190         :type mac: str
191         """
192         self._vhost_id += 1
193         # Create unix socket character device.
194         chardev = ' -chardev socket,id=char{0},path={1}'.format(self._vhost_id,
195                                                                 socket)
196         if server is True:
197             chardev += ',server'
198         self._qemu_opt['options'] += chardev
199         # Create Vhost-user network backend.
200         netdev = ' -netdev vhost-user,id=vhost{0},chardev=char{0},'\
201             'queues={1}'.format(self._vhost_id, self._qemu_opt['queues'])
202         self._qemu_opt['options'] += netdev
203         # If MAC is not specified use auto-generated MAC address based on
204         # template 52:54:00:00:<qemu_id>:<vhost_id>, e.g. vhost1 MAC of QEMU
205         #  with ID 1 is 52:54:00:00:01:01
206         if mac is None:
207             mac = '52:54:00:00:{0:02x}:{1:02x}'.\
208                 format(self._qemu_id, self._vhost_id)
209         extend_options = 'mq=on,csum=off,gso=off,guest_tso4=off,'\
210             'guest_tso6=off,guest_ecn=off,mrg_rxbuf=off'
211         # Create Virtio network device.
212         device = ' -device virtio-net-pci,netdev=vhost{0},mac={1},{2}'.format(
213             self._vhost_id, mac, extend_options)
214         self._qemu_opt['options'] += device
215         # Add interface MAC and socket to the node dict
216         if_data = {'mac_address': mac, 'socket': socket}
217         if_name = 'vhost{}'.format(self._vhost_id)
218         self._vm_info['interfaces'][if_name] = if_data
219         # Add socket to the socket list
220         self._socks.append(socket)
221
222     def _qemu_qmp_exec(self, cmd):
223         """Execute QMP command.
224
225         QMP is JSON based protocol which allows to control QEMU instance.
226
227         :param cmd: QMP command to execute.
228         :type cmd: str
229         :return: Command output in python representation of JSON format. The
230             { "return": {} } response is QMP's success response. An error
231             response will contain the "error" keyword instead of "return".
232         """
233         # To enter command mode, the qmp_capabilities command must be issued.
234         qmp_cmd = 'echo "{ \\"execute\\": \\"qmp_capabilities\\" }' \
235                   '{ \\"execute\\": \\"' + cmd + \
236                   '\\" }" | sudo -S socat - UNIX-CONNECT:' + self._qmp_sock
237
238         (ret_code, stdout, stderr) = self._ssh.exec_command(qmp_cmd)
239         if int(ret_code) != 0:
240             logger.debug('QMP execute failed {0}'.format(stderr))
241             raise RuntimeError('QMP execute "{0}"'
242                                ' failed on {1}'.format(cmd, self._node['host']))
243         logger.trace(stdout)
244         # Skip capabilities negotiation messages.
245         out_list = stdout.splitlines()
246         if len(out_list) < 3:
247             raise RuntimeError('Invalid QMP output on {0}'.format(
248                 self._node['host']))
249         return json.loads(out_list[2])
250
251     def _qemu_qga_flush(self):
252         """Flush the QGA parser state
253         """
254         qga_cmd = '(printf "\xFF"; sleep 1) | sudo -S socat - UNIX-CONNECT:' + \
255                   self._qga_sock
256         #TODO: probably need something else
257         (ret_code, stdout, stderr) = self._ssh.exec_command(qga_cmd)
258         if int(ret_code) != 0:
259             logger.debug('QGA execute failed {0}'.format(stderr))
260             raise RuntimeError('QGA execute "{0}" '
261                                'failed on {1}'.format(qga_cmd,
262                                                       self._node['host']))
263         logger.trace(stdout)
264         if not stdout:
265             return {}
266         return json.loads(stdout.split('\n', 1)[0])
267
268     def _qemu_qga_exec(self, cmd):
269         """Execute QGA command.
270
271         QGA provide access to a system-level agent via standard QMP commands.
272
273         :param cmd: QGA command to execute.
274         :type cmd: str
275         """
276         qga_cmd = '(echo "{ \\"execute\\": \\"' + \
277                   cmd + \
278                   '\\" }"; sleep 1) | sudo -S socat - UNIX-CONNECT:' + \
279                   self._qga_sock
280         (ret_code, stdout, stderr) = self._ssh.exec_command(qga_cmd)
281         if int(ret_code) != 0:
282             logger.debug('QGA execute failed {0}'.format(stderr))
283             raise RuntimeError('QGA execute "{0}"'
284                                ' failed on {1}'.format(cmd, self._node['host']))
285         logger.trace(stdout)
286         if not stdout:
287             return {}
288         return json.loads(stdout.split('\n', 1)[0])
289
290     def _wait_until_vm_boot(self, timeout=60):
291         """Wait until QEMU VM is booted.
292
293         Ping QEMU guest agent each 5s until VM booted or timeout.
294
295         :param timeout: Waiting timeout in seconds (optional, default 60s).
296         :type timeout: int
297         """
298         start = time()
299         while True:
300             if time() - start > timeout:
301                 raise RuntimeError('timeout, VM {0} not booted on {1}'.format(
302                     self._qemu_opt['disk_image'], self._node['host']))
303             out = None
304             try:
305                 self._qemu_qga_flush()
306                 out = self._qemu_qga_exec('guest-ping')
307             except ValueError:
308                 logger.trace('QGA guest-ping unexpected output {}'.format(out))
309             # Empty output - VM not booted yet
310             if not out:
311                 sleep(5)
312             # Non-error return - VM booted
313             elif out.get('return') is not None:
314                 break
315             # Skip error and wait
316             elif out.get('error') is not None:
317                 sleep(5)
318             else:
319                 # If there is an unexpected output from QGA guest-info, try
320                 # again until timeout.
321                 logger.trace('QGA guest-ping unexpected output {}'.format(out))
322
323         logger.trace('VM {0} booted on {1}'.format(self._qemu_opt['disk_image'],
324                                                    self._node['host']))
325
326     def _update_vm_interfaces(self):
327         """Update interface names in VM node dict."""
328         # Send guest-network-get-interfaces command via QGA, output example:
329         # {"return": [{"name": "eth0", "hardware-address": "52:54:00:00:04:01"},
330         # {"name": "eth1", "hardware-address": "52:54:00:00:04:02"}]}
331         out = self._qemu_qga_exec('guest-network-get-interfaces')
332         interfaces = out.get('return')
333         mac_name = {}
334         if not interfaces:
335             raise RuntimeError('Get VM {0} interface list failed on {1}'.format(
336                 self._qemu_opt['disk_image'], self._node['host']))
337         # Create MAC-name dict
338         for interface in interfaces:
339             if 'hardware-address' not in interface:
340                 continue
341             mac_name[interface['hardware-address']] = interface['name']
342         # Match interface by MAC and save interface name
343         for interface in self._vm_info['interfaces'].values():
344             mac = interface.get('mac_address')
345             if_name = mac_name.get(mac)
346             if if_name is None:
347                 logger.trace('Interface name for MAC {} not found'.format(mac))
348             else:
349                 interface['name'] = if_name
350
351     def _huge_page_check(self, allocate=False):
352         """Huge page check."""
353         huge_mnt = self._qemu_opt.get('huge_mnt')
354         mem_size = self._qemu_opt.get('mem_size')
355
356         # Get huge pages information
357         huge_size = self._get_huge_page_size()
358         huge_free = self._get_huge_page_free(huge_size)
359         huge_total = self._get_huge_page_total(huge_size)
360
361         # Check if memory reqested by qemu is available on host
362         if (mem_size * 1024) > (huge_free * huge_size):
363             # If we want to allocate hugepage dynamically
364             if allocate:
365                 mem_needed = abs((huge_free * huge_size) - (mem_size * 1024))
366                 huge_to_allocate = ((mem_needed / huge_size) * 2) + huge_total
367                 max_map_count = huge_to_allocate*4
368                 # Increase maximum number of memory map areas a process may have
369                 cmd = 'echo "{0}" | sudo tee /proc/sys/vm/max_map_count'.format(
370                     max_map_count)
371                 (ret_code, _, stderr) = self._ssh.exec_command_sudo(cmd)
372                 # Increase hugepage count
373                 cmd = 'echo "{0}" | sudo tee /proc/sys/vm/nr_hugepages'.format(
374                     huge_to_allocate)
375                 (ret_code, _, stderr) = self._ssh.exec_command_sudo(cmd)
376                 if int(ret_code) != 0:
377                     logger.debug('Mount huge pages failed {0}'.format(stderr))
378                     raise RuntimeError('Mount huge pages failed on {0}'.format(
379                         self._node['host']))
380             # If we do not want to allocate dynamicaly end with error
381             else:
382                 raise RuntimeError(
383                     'Not enough free huge pages: {0}, '
384                     '{1} MB'.format(huge_free, huge_free * huge_size)
385                 )
386         # Check if huge pages mount point exist
387         has_huge_mnt = False
388         (_, output, _) = self._ssh.exec_command('cat /proc/mounts')
389         for line in output.splitlines():
390             # Try to find something like:
391             # none /mnt/huge hugetlbfs rw,relatime,pagesize=2048k 0 0
392             mount = line.split()
393             if mount[2] == 'hugetlbfs' and mount[1] == huge_mnt:
394                 has_huge_mnt = True
395                 break
396         # If huge page mount point not exist create one
397         if not has_huge_mnt:
398             cmd = 'mkdir -p {0}'.format(huge_mnt)
399             (ret_code, _, stderr) = self._ssh.exec_command_sudo(cmd)
400             if int(ret_code) != 0:
401                 logger.debug('Create mount dir failed: {0}'.format(stderr))
402                 raise RuntimeError('Create mount dir failed on {0}'.format(
403                     self._node['host']))
404             cmd = 'mount -t hugetlbfs -o pagesize=2048k none {0}'.format(
405                 huge_mnt)
406             (ret_code, _, stderr) = self._ssh.exec_command_sudo(cmd)
407             if int(ret_code) != 0:
408                 logger.debug('Mount huge pages failed {0}'.format(stderr))
409                 raise RuntimeError('Mount huge pages failed on {0}'.format(
410                     self._node['host']))
411
412     def _get_huge_page_size(self):
413         """Get default size of huge pages in system.
414
415         :returns: Default size of free huge pages in system.
416         :rtype: int
417         :raises: RuntimeError if reading failed for three times.
418         """
419         # TODO: remove to dedicated library
420         cmd_huge_size = "grep Hugepagesize /proc/meminfo | awk '{ print $2 }'"
421         for _ in range(3):
422             (ret, out, _) = self._ssh.exec_command_sudo(cmd_huge_size)
423             if ret == 0:
424                 try:
425                     huge_size = int(out)
426                 except ValueError:
427                     logger.trace('Reading huge page size information failed')
428                 else:
429                     break
430         else:
431             raise RuntimeError('Getting huge page size information failed.')
432         return huge_size
433
434     def _get_huge_page_free(self, huge_size):
435         """Get total number of huge pages in system.
436
437         :param huge_size: Size of hugepages.
438         :type huge_size: int
439         :returns: Number of free huge pages in system.
440         :rtype: int
441         :raises: RuntimeError if reading failed for three times.
442         """
443         # TODO: add numa aware option
444         # TODO: remove to dedicated library
445         cmd_huge_free = 'cat /sys/kernel/mm/hugepages/hugepages-{0}kB/'\
446             'free_hugepages'.format(huge_size)
447         for _ in range(3):
448             (ret, out, _) = self._ssh.exec_command_sudo(cmd_huge_free)
449             if ret == 0:
450                 try:
451                     huge_free = int(out)
452                 except ValueError:
453                     logger.trace('Reading free huge pages information failed')
454                 else:
455                     break
456         else:
457             raise RuntimeError('Getting free huge pages information failed.')
458         return huge_free
459
460     def _get_huge_page_total(self, huge_size):
461         """Get total number of huge pages in system.
462
463         :param huge_size: Size of hugepages.
464         :type huge_size: int
465         :returns: Total number of huge pages in system.
466         :rtype: int
467         :raises: RuntimeError if reading failed for three times.
468         """
469         # TODO: add numa aware option
470         # TODO: remove to dedicated library
471         cmd_huge_total = 'cat /sys/kernel/mm/hugepages/hugepages-{0}kB/'\
472             'nr_hugepages'.format(huge_size)
473         for _ in range(3):
474             (ret, out, _) = self._ssh.exec_command_sudo(cmd_huge_total)
475             if ret == 0:
476                 try:
477                     huge_total = int(out)
478                 except ValueError:
479                     logger.trace('Reading total huge pages information failed')
480                 else:
481                     break
482         else:
483             raise RuntimeError('Getting total huge pages information failed.')
484         return huge_total
485
486     def qemu_start(self):
487         """Start QEMU and wait until VM boot.
488
489         :return: VM node info.
490         :rtype: dict
491         .. note:: First set at least node to run QEMU on.
492         .. warning:: Starts only one VM on the node.
493         """
494         # SSH forwarding
495         ssh_fwd = '-net user,hostfwd=tcp::{0}-:22'.format(
496             self._qemu_opt.get('ssh_fwd_port'))
497         # Memory and huge pages
498         mem = '-object memory-backend-file,id=mem,size={0}M,mem-path={1},' \
499             'share=on -m {0} -numa node,memdev=mem'.format(
500                 self._qemu_opt.get('mem_size'), self._qemu_opt.get('huge_mnt'))
501
502         # By default check only if hugepages are available.
503         # If 'huge_allocate' is set to true try to allocate as well.
504         self._huge_page_check(allocate=self._qemu_opt.get('huge_allocate'))
505
506         # Disk option
507         drive = '-drive file={0},format=raw,cache=none,if=virtio'.format(
508             self._qemu_opt.get('disk_image'))
509         # Setup QMP via unix socket
510         qmp = '-qmp unix:{0},server,nowait'.format(self._qmp_sock)
511         # Setup serial console
512         serial = '-chardev socket,host=127.0.0.1,port={0},id=gnc0,server,' \
513             'nowait -device isa-serial,chardev=gnc0'.format(
514                 self._qemu_opt.get('serial_port'))
515         # Setup QGA via chardev (unix socket) and isa-serial channel
516         qga = '-chardev socket,path={0},server,nowait,id=qga0 ' \
517             '-device isa-serial,chardev=qga0'.format(self._qga_sock)
518         # Graphic setup
519         graphic = '-monitor none -display none -vga none'
520
521         # Run QEMU
522         cmd = '{0} {1} {2} {3} {4} {5} {6} {7} {8} {9}'.format(
523             self.__QEMU_BIN, self._qemu_opt.get('smp'), mem, ssh_fwd,
524             self._qemu_opt.get('options'),
525             drive, qmp, serial, qga, graphic)
526         (ret_code, _, stderr) = self._ssh.exec_command_sudo(cmd, timeout=300)
527         if int(ret_code) != 0:
528             logger.debug('QEMU start failed {0}'.format(stderr))
529             raise RuntimeError('QEMU start failed on {0}'.format(
530                 self._node['host']))
531         logger.trace('QEMU running')
532         # Wait until VM boot
533         try:
534             self._wait_until_vm_boot()
535         except (RuntimeError, SSHTimeout):
536             self.qemu_kill()
537             self.qemu_clear_socks()
538             raise
539         # Update interface names in VM node dict
540         self._update_vm_interfaces()
541         # Return VM node dict
542         return self._vm_info
543
544     def qemu_quit(self):
545         """Quit the QEMU emulator."""
546         out = self._qemu_qmp_exec('quit')
547         err = out.get('error')
548         if err is not None:
549             raise RuntimeError('QEMU quit failed on {0}, error: {1}'.format(
550                 self._node['host'], json.dumps(err)))
551
552     def qemu_system_powerdown(self):
553         """Power down the system (if supported)."""
554         out = self._qemu_qmp_exec('system_powerdown')
555         err = out.get('error')
556         if err is not None:
557             raise RuntimeError(
558                 'QEMU system powerdown failed on {0}, '
559                 'error: {1}'.format(self._node['host'], json.dumps(err))
560             )
561
562     def qemu_system_reset(self):
563         """Reset the system."""
564         out = self._qemu_qmp_exec('system_reset')
565         err = out.get('error')
566         if err is not None:
567             raise RuntimeError(
568                 'QEMU system reset failed on {0}, '
569                 'error: {1}'.format(self._node['host'], json.dumps(err)))
570
571     def qemu_kill(self):
572         """Kill qemu process."""
573         # TODO: add PID storage so that we can kill specific PID
574         # Note: in QEMU start phase there are 3 QEMU processes because we
575         # daemonize QEMU
576         self._ssh.exec_command_sudo('pkill -SIGKILL qemu')
577
578     def qemu_clear_socks(self):
579         """Remove all sockets created by QEMU."""
580         # If serial console port still open kill process
581         cmd = 'fuser -k {}/tcp'.format(self._qemu_opt.get('serial_port'))
582         self._ssh.exec_command_sudo(cmd)
583         # Delete all created sockets
584         for sock in self._socks:
585             cmd = 'rm -f {}'.format(sock)
586             self._ssh.exec_command_sudo(cmd)
587
588     def qemu_system_status(self):
589         """Return current VM status.
590
591         VM should be in following status:
592
593             - debug: QEMU running on a debugger
594             - finish-migrate: paused to finish the migration process
595             - inmigrate: waiting for an incoming migration
596             - internal-error: internal error has occurred
597             - io-error: the last IOP has failed
598             - paused: paused
599             - postmigrate: paused following a successful migrate
600             - prelaunch: QEMU was started with -S and guest has not started
601             - restore-vm: paused to restore VM state
602             - running: actively running
603             - save-vm: paused to save the VM state
604             - shutdown: shut down (and -no-shutdown is in use)
605             - suspended: suspended (ACPI S3)
606             - watchdog: watchdog action has been triggered
607             - guest-panicked: panicked as a result of guest OS panic
608
609         :return: VM status.
610         :rtype: str
611         """
612         out = self._qemu_qmp_exec('query-status')
613         ret = out.get('return')
614         if ret is not None:
615             return ret.get('status')
616         else:
617             err = out.get('error')
618             raise RuntimeError(
619                 'QEMU query-status failed on {0}, '
620                 'error: {1}'.format(self._node['host'], json.dumps(err)))
621
622     @staticmethod
623     def build_qemu(node):
624         """Build QEMU from sources.
625
626         :param node: Node to build QEMU on.
627         :type node: dict
628         """
629         ssh = SSH()
630         ssh.connect(node)
631
632         (ret_code, stdout, stderr) = \
633             ssh.exec_command('sudo -Sn bash {0}/{1}/qemu_build.sh'.format(
634                 Constants.REMOTE_FW_DIR, Constants.RESOURCES_LIB_SH), 1000)
635         logger.trace(stdout)
636         if int(ret_code) != 0:
637             logger.debug('QEMU build failed {0}'.format(stderr))
638             raise RuntimeError('QEMU build failed on {0}'.format(node['host']))