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