1 # Copyright (c) 2019 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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """QEMU utilities library."""
16 # Disable due to pylint bug
17 # pylint: disable=no-name-in-module,import-error
18 from distutils.version import StrictVersion
21 from string import Template
22 from time import sleep
24 from robot.api import logger
25 from resources.libraries.python.Constants import Constants
26 from resources.libraries.python.DpdkUtil import DpdkUtil
27 from resources.libraries.python.DUTSetup import DUTSetup
28 from resources.libraries.python.OptionString import OptionString
29 from resources.libraries.python.VppConfigGenerator import VppConfigGenerator
30 from resources.libraries.python.VPPUtil import VPPUtil
31 from resources.libraries.python.ssh import exec_cmd, exec_cmd_no_error
32 from resources.libraries.python.topology import NodeType, Topology
34 __all__ = ["QemuUtils"]
37 class QemuUtils(object):
40 # Use one instance of class per tests.
41 ROBOT_LIBRARY_SCOPE = 'TEST CASE'
43 def __init__(self, node, qemu_id=1, smp=1, mem=512, vnf=None,
44 img=Constants.QEMU_VM_IMAGE):
45 """Initialize QemuUtil class.
47 :param node: Node to run QEMU on.
48 :param qemu_id: QEMU identifier.
49 :param smp: Number of virtual SMP units (cores).
50 :param mem: Amount of memory.
51 :param vnf: Network function workload.
52 :param img: QEMU disk image or kernel image path.
62 self._arch = Topology.get_node_arch(self._node)
63 dpdk_target = 'arm64-armv8a' if self._arch == 'aarch64' \
65 self._testpmd_path = '{path}/{dpdk_target}-linuxapp-gcc/app'\
66 .format(path=Constants.QEMU_VM_DPDK, dpdk_target=dpdk_target)
70 'port': 10021 + qemu_id,
71 'serial': 4555 + qemu_id,
76 if node['port'] != 22:
77 self._vm_info['host_port'] = node['port']
78 self._vm_info['host_username'] = node['username']
79 self._vm_info['host_password'] = node['password']
82 self._opt['qemu_id'] = qemu_id
83 self._opt['mem'] = int(mem)
84 self._opt['smp'] = int(smp)
85 self._opt['img'] = img
86 self._opt['vnf'] = vnf
89 self._temp['pidfile'] = '/var/run/qemu_{id}.pid'.format(id=qemu_id)
90 if img == Constants.QEMU_VM_IMAGE:
91 self._opt['vm_type'] = 'nestedvm'
92 self._temp['qmp'] = '/var/run/qmp_{id}.sock'.format(id=qemu_id)
93 self._temp['qga'] = '/var/run/qga_{id}.sock'.format(id=qemu_id)
94 elif img == Constants.QEMU_VM_KERNEL:
95 self._opt['img'], _ = exec_cmd_no_error(
97 'ls -1 {img}* | tail -1'.format(img=Constants.QEMU_VM_KERNEL),
98 message='Qemu Kernel VM image not found!')
99 self._opt['vm_type'] = 'kernelvm'
100 self._temp['log'] = '/tmp/serial_{id}.log'.format(id=qemu_id)
101 self._temp['ini'] = '/etc/vm_init_{id}.conf'.format(id=qemu_id)
102 self._opt['initrd'], _ = exec_cmd_no_error(
104 'ls -1 {initrd}* | tail -1'.format(
105 initrd=Constants.QEMU_VM_KERNEL_INITRD),
106 message='Qemu Kernel initrd image not found!')
108 raise RuntimeError('QEMU: Unknown VM image option: {}'.format(img))
109 # Computed parameters for QEMU command line.
110 self._params = OptionString(prefix='-')
113 def add_params(self):
114 """Set QEMU command line parameters."""
115 self.add_default_params()
116 if self._opt.get('vm_type', '') == 'nestedvm':
117 self.add_nestedvm_params()
118 elif self._opt.get('vm_type', '') == 'kernelvm':
119 self.add_kernelvm_params()
121 raise RuntimeError('QEMU: Unsupported VM type!')
123 def add_default_params(self):
124 """Set default QEMU command line parameters."""
125 self._params.add('daemonize')
126 self._params.add('nodefaults')
127 self._params.add_with_value('name', 'vnf{qemu},debug-threads=on'.format(
128 qemu=self._opt.get('qemu_id')))
129 self._params.add('no-user-config')
130 self._params.add_with_value('monitor', 'none')
131 self._params.add_with_value('display', 'none')
132 self._params.add_with_value('vga', 'none')
133 self._params.add('enable-kvm')
134 self._params.add_with_value('pidfile', self._temp.get('pidfile'))
135 self._params.add_with_value('cpu', 'host')
137 if self._arch == 'aarch64':
138 machine_args = 'virt,accel=kvm,usb=off,mem-merge=off,gic-version=3'
140 machine_args = 'pc,accel=kvm,usb=off,mem-merge=off'
141 self._params.add_with_value(
142 'machine', machine_args)
143 self._params.add_with_value(
144 'smp', '{smp},sockets=1,cores={smp},threads=1'.format(
145 smp=self._opt.get('smp')))
146 self._params.add_with_value(
147 'object', 'memory-backend-file,id=mem,size={mem}M,'
148 'mem-path=/dev/hugepages,share=on'.format(mem=self._opt.get('mem')))
149 self._params.add_with_value(
150 'm', '{mem}M'.format(mem=self._opt.get('mem')))
151 self._params.add_with_value('numa', 'node,memdev=mem')
152 self._params.add_with_value('balloon', 'none')
154 def add_nestedvm_params(self):
155 """Set NestedVM QEMU parameters."""
156 self._params.add_with_value(
157 'net', 'nic,macaddr=52:54:00:00:{qemu:02x}:ff'.format(
158 qemu=self._opt.get('qemu_id')))
159 self._params.add_with_value(
160 'net', 'user,hostfwd=tcp::{info[port]}-:22'.format(
162 # TODO: Remove try except after fully migrated to Bionic or
163 # qemu_set_node is removed.
165 locking = ',file.locking=off'\
166 if self.qemu_version(version='2.10') else ''
167 except AttributeError:
169 self._params.add_with_value(
170 'drive', 'file={img},format=raw,cache=none,if=virtio{locking}'.
171 format(img=self._opt.get('img'), locking=locking))
172 self._params.add_with_value(
173 'qmp', 'unix:{qmp},server,nowait'.format(qmp=self._temp.get('qmp')))
174 self._params.add_with_value(
175 'chardev', 'socket,host=127.0.0.1,port={info[serial]},'
176 'id=gnc0,server,nowait'.format(info=self._vm_info))
177 self._params.add_with_value('device', 'isa-serial,chardev=gnc0')
178 self._params.add_with_value(
179 'chardev', 'socket,path={qga},server,nowait,id=qga0'.format(
180 qga=self._temp.get('qga')))
181 self._params.add_with_value('device', 'isa-serial,chardev=qga0')
183 def add_kernelvm_params(self):
184 """Set KernelVM QEMU parameters."""
185 console = 'ttyAMA0' if self._arch == 'aarch64' else 'ttyS0'
186 self._params.add_with_value('serial', 'file:{log}'.format(
187 log=self._temp.get('log')))
188 self._params.add_with_value(
189 'fsdev', 'local,id=root9p,path=/,security_model=none')
190 self._params.add_with_value(
191 'device', 'virtio-9p-pci,fsdev=root9p,mount_tag=virtioroot')
192 self._params.add_with_value(
193 'kernel', '{img}'.format(img=self._opt.get('img')))
194 self._params.add_with_value(
195 'initrd', '{initrd}'.format(initrd=self._opt.get('initrd')))
196 self._params.add_with_value(
197 'append', '"ro rootfstype=9p rootflags=trans=virtio '
198 'root=virtioroot console={console} tsc=reliable '
199 'hugepages=256 init={init} fastboot"'.format(
200 console=console, init=self._temp.get('ini')))
202 def create_kernelvm_config_vpp(self, **kwargs):
203 """Create QEMU VPP config files.
205 :param kwargs: Key-value pairs to replace content of VPP configuration
209 startup = ('/etc/vpp/vm_startup_{id}.conf'.
210 format(id=self._opt.get('qemu_id')))
211 running = ('/etc/vpp/vm_running_{id}.exec'.
212 format(id=self._opt.get('qemu_id')))
214 self._temp['startup'] = startup
215 self._temp['running'] = running
216 self._opt['vnf_bin'] = ('/usr/bin/vpp -c {startup}'.
217 format(startup=startup))
219 # Create VPP startup configuration.
220 vpp_config = VppConfigGenerator()
221 vpp_config.set_node(self._node)
222 vpp_config.add_unix_nodaemon()
223 vpp_config.add_unix_cli_listen()
224 vpp_config.add_unix_exec(running)
225 vpp_config.add_socksvr()
226 vpp_config.add_cpu_main_core('0')
227 if self._opt.get('smp') > 1:
228 vpp_config.add_cpu_corelist_workers('1-{smp}'.format(
229 smp=self._opt.get('smp')-1))
230 vpp_config.add_dpdk_dev('0000:00:06.0', '0000:00:07.0')
231 vpp_config.add_dpdk_dev_default_rxq(kwargs['queues'])
232 vpp_config.add_dpdk_log_level('debug')
233 if not kwargs['jumbo_frames']:
234 vpp_config.add_dpdk_no_multi_seg()
235 vpp_config.add_dpdk_no_tx_checksum_offload()
236 vpp_config.add_plugin('disable', 'default')
237 vpp_config.add_plugin('enable', 'dpdk_plugin.so')
238 vpp_config.add_plugin('enable', 'memif_plugin.so')
239 vpp_config.write_config(startup)
241 # Create VPP running configuration.
242 template = '{res}/{tpl}.exec'.format(res=Constants.RESOURCES_TPL_VM,
243 tpl=self._opt.get('vnf'))
244 exec_cmd_no_error(self._node, 'rm -f {running}'.format(running=running),
247 with open(template, 'r') as src_file:
248 src = Template(src_file.read())
250 self._node, "echo '{out}' | sudo tee {running}".format(
251 out=src.safe_substitute(**kwargs), running=running))
253 def create_kernelvm_config_testpmd_io(self, **kwargs):
254 """Create QEMU testpmd-io command line.
256 :param kwargs: Key-value pairs to construct command line parameters.
259 testpmd_cmd = DpdkUtil.get_testpmd_cmdline(
260 eal_corelist='0-{smp}'.format(smp=self._opt.get('smp') - 1),
264 pmd_rxq=kwargs['queues'],
265 pmd_txq=kwargs['queues'],
266 pmd_tx_offloads='0x0',
267 pmd_disable_hw_vlan=False,
268 pmd_nb_cores=str(self._opt.get('smp') - 1))
270 self._opt['vnf_bin'] = ('{testpmd_path}/{testpmd_cmd}'.
271 format(testpmd_path=self._testpmd_path,
272 testpmd_cmd=testpmd_cmd))
274 def create_kernelvm_config_testpmd_mac(self, **kwargs):
275 """Create QEMU testpmd-mac command line.
277 :param kwargs: Key-value pairs to construct command line parameters.
280 testpmd_cmd = DpdkUtil.get_testpmd_cmdline(
281 eal_corelist='0-{smp}'.format(smp=self._opt.get('smp') - 1),
286 pmd_eth_peer_0='0,{mac}'.format(mac=kwargs['vif1_mac']),
287 pmd_eth_peer_1='1,{mac}'.format(mac=kwargs['vif2_mac']),
288 pmd_rxq=kwargs['queues'],
289 pmd_txq=kwargs['queues'],
290 pmd_tx_offloads='0x0',
291 pmd_disable_hw_vlan=False,
292 pmd_nb_cores=str(self._opt.get('smp') - 1))
294 self._opt['vnf_bin'] = ('{testpmd_path}/{testpmd_cmd}'.
295 format(testpmd_path=self._testpmd_path,
296 testpmd_cmd=testpmd_cmd))
298 def create_kernelvm_init(self, **kwargs):
299 """Create QEMU init script.
301 :param kwargs: Key-value pairs to replace content of init startup file.
304 template = '{res}/init.sh'.format(res=Constants.RESOURCES_TPL_VM)
305 init = self._temp.get('ini')
307 self._node, 'rm -f {init}'.format(init=init), sudo=True)
309 with open(template, 'r') as src_file:
310 src = Template(src_file.read())
312 self._node, "echo '{out}' | sudo tee {init}".format(
313 out=src.safe_substitute(**kwargs), init=init))
315 self._node, "chmod +x {init}".format(init=init), sudo=True)
317 def configure_kernelvm_vnf(self, **kwargs):
318 """Create KernelVM VNF configurations.
320 :param kwargs: Key-value pairs for templating configs.
323 if 'vpp' in self._opt.get('vnf'):
324 self.create_kernelvm_config_vpp(**kwargs)
325 elif 'testpmd_io' in self._opt.get('vnf'):
326 self.create_kernelvm_config_testpmd_io(**kwargs)
327 elif 'testpmd_mac' in self._opt.get('vnf'):
328 self.create_kernelvm_config_testpmd_mac(**kwargs)
330 raise RuntimeError('QEMU: Unsupported VNF!')
331 self.create_kernelvm_init(vnf_bin=self._opt['vnf_bin'])
333 def get_qemu_pids(self):
334 """Get QEMU CPU pids.
336 :returns: List of QEMU CPU pids.
339 command = ("grep -rwl 'CPU' /proc/$(sudo cat {pidfile})/task/*/comm ".
340 format(pidfile=self._temp.get('pidfile')))
341 command += (r"| xargs dirname | sed -e 's/\/.*\///g' | uniq")
343 stdout, _ = exec_cmd_no_error(self._node, command)
344 return stdout.splitlines()
346 def qemu_set_affinity(self, *host_cpus):
347 """Set qemu affinity by getting thread PIDs via QMP and taskset to list
348 of CPU cores. Function tries to execute 3 times to avoid race condition
349 in getting thread PIDs.
351 :param host_cpus: List of CPU cores.
352 :type host_cpus: list
356 qemu_cpus = self.get_qemu_pids()
358 if len(qemu_cpus) != len(host_cpus):
361 for qemu_cpu, host_cpu in zip(qemu_cpus, host_cpus):
362 command = ('taskset -pc {host_cpu} {thread}'.
363 format(host_cpu=host_cpu, thread=qemu_cpu))
364 message = ('QEMU: Set affinity failed on {host}!'.
365 format(host=self._node['host']))
366 exec_cmd_no_error(self._node, command, sudo=True,
369 except (RuntimeError, ValueError):
374 raise RuntimeError('Failed to set Qemu threads affinity!')
376 def qemu_set_scheduler_policy(self):
377 """Set scheduler policy to SCHED_RR with priority 1 for all Qemu CPU
380 :raises RuntimeError: Set scheduler policy failed.
383 qemu_cpus = self.get_qemu_pids()
385 for qemu_cpu in qemu_cpus:
386 command = ('chrt -r -p 1 {thread}'.
387 format(thread=qemu_cpu))
388 message = ('QEMU: Set SCHED_RR failed on {host}'.
389 format(host=self._node['host']))
390 exec_cmd_no_error(self._node, command, sudo=True,
392 except (RuntimeError, ValueError):
396 def qemu_add_vhost_user_if(self, socket, server=True, jumbo_frames=False,
397 queue_size=None, queues=1):
398 """Add Vhost-user interface.
400 :param socket: Path of the unix socket.
401 :param server: If True the socket shall be a listening socket.
402 :param jumbo_frames: Set True if jumbo frames are used in the test.
403 :param queue_size: Vring queue size.
404 :param queues: Number of queues.
407 :type jumbo_frames: bool
408 :type queue_size: int
412 self._params.add_with_value(
413 'chardev', 'socket,id=char{vhost},path={socket}{server}'.format(
414 vhost=self._vhost_id, socket=socket,
415 server=',server' if server is True else ''))
416 self._params.add_with_value(
417 'netdev', 'vhost-user,id=vhost{vhost},chardev=char{vhost},'
418 'queues={queues}'.format(vhost=self._vhost_id, queues=queues))
419 mac = ('52:54:00:00:{qemu:02x}:{vhost:02x}'.
420 format(qemu=self._opt.get('qemu_id'), vhost=self._vhost_id))
421 queue_size = ('rx_queue_size={queue_size},tx_queue_size={queue_size}'.
422 format(queue_size=queue_size)) if queue_size else ''
423 mbuf = 'on,host_mtu=9200'
424 self._params.add_with_value(
425 'device', 'virtio-net-pci,netdev=vhost{vhost},mac={mac},'
426 'addr={addr}.0,mq=on,vectors={vectors},csum=off,gso=off,'
427 'guest_tso4=off,guest_tso6=off,guest_ecn=off,mrg_rxbuf={mbuf},'
428 '{queue_size}'.format(
429 addr=self._vhost_id+5, vhost=self._vhost_id, mac=mac,
430 mbuf=mbuf if jumbo_frames else 'off', queue_size=queue_size,
431 vectors=(2 * queues + 2)))
433 # Add interface MAC and socket to the node dict.
434 if_data = {'mac_address': mac, 'socket': socket}
435 if_name = 'vhost{vhost}'.format(vhost=self._vhost_id)
436 self._vm_info['interfaces'][if_name] = if_data
437 # Add socket to temporary file list.
438 self._temp[if_name] = socket
440 def _qemu_qmp_exec(self, cmd):
441 """Execute QMP command.
443 QMP is JSON based protocol which allows to control QEMU instance.
445 :param cmd: QMP command to execute.
447 :returns: Command output in python representation of JSON format. The
448 { "return": {} } response is QMP's success response. An error
449 response will contain the "error" keyword instead of "return".
451 # To enter command mode, the qmp_capabilities command must be issued.
452 command = ('echo "{{ \\"execute\\": \\"qmp_capabilities\\" }}'
453 '{{ \\"execute\\": \\"{cmd}\\" }}" | '
454 'sudo -S socat - UNIX-CONNECT:{qmp}'.
455 format(cmd=cmd, qmp=self._temp.get('qmp')))
456 message = ('QMP execute "{cmd}" failed on {host}'.
457 format(cmd=cmd, host=self._node['host']))
458 stdout, _ = exec_cmd_no_error(
459 self._node, command, sudo=False, message=message)
461 # Skip capabilities negotiation messages.
462 out_list = stdout.splitlines()
463 if len(out_list) < 3:
465 'Invalid QMP output on {host}'.format(host=self._node['host']))
466 return json.loads(out_list[2])
468 def _qemu_qga_flush(self):
469 """Flush the QGA parser state."""
470 command = ('(printf "\xFF"; sleep 1) | '
471 'sudo -S socat - UNIX-CONNECT:{qga}'.
472 format(qga=self._temp.get('qga')))
473 message = ('QGA flush failed on {host}'.format(host=self._node['host']))
474 stdout, _ = exec_cmd_no_error(
475 self._node, command, sudo=False, message=message)
477 return json.loads(stdout.split('\n', 1)[0]) if stdout else dict()
479 def _qemu_qga_exec(self, cmd):
480 """Execute QGA command.
482 QGA provide access to a system-level agent via standard QMP commands.
484 :param cmd: QGA command to execute.
487 command = ('(echo "{{ \\"execute\\": \\"{cmd}\\" }}"; sleep 1) | '
488 'sudo -S socat - UNIX-CONNECT:{qga}'.
489 format(cmd=cmd, qga=self._temp.get('qga')))
490 message = ('QGA execute "{cmd}" failed on {host}'.
491 format(cmd=cmd, host=self._node['host']))
492 stdout, _ = exec_cmd_no_error(
493 self._node, command, sudo=False, message=message)
495 return json.loads(stdout.split('\n', 1)[0]) if stdout else dict()
497 def _wait_until_vm_boot(self):
498 """Wait until QEMU with NestedVM is booted."""
499 if self._opt.get('vm_type') == 'nestedvm':
500 self._wait_until_nestedvm_boot()
501 self._update_vm_interfaces()
502 elif self._opt.get('vm_type') == 'kernelvm':
503 self._wait_until_kernelvm_boot()
505 raise RuntimeError('QEMU: Unsupported VM type!')
507 def _wait_until_nestedvm_boot(self, retries=12):
508 """Wait until QEMU with NestedVM is booted.
510 First try to flush qga until there is output.
511 Then ping QEMU guest agent each 5s until VM booted or timeout.
513 :param retries: Number of retries with 5s between trials.
516 for _ in range(retries):
519 out = self._qemu_qga_flush()
521 logger.trace('QGA qga flush unexpected output {out}'.
523 # Empty output - VM not booted yet
529 raise RuntimeError('QEMU: Timeout, VM not booted on {host}!'.
530 format(host=self._node['host']))
531 for _ in range(retries):
534 out = self._qemu_qga_exec('guest-ping')
536 logger.trace('QGA guest-ping unexpected output {out}'.
538 # Empty output - VM not booted yet.
541 # Non-error return - VM booted.
542 elif out.get('return') is not None:
544 # Skip error and wait.
545 elif out.get('error') is not None:
548 # If there is an unexpected output from QGA guest-info, try
549 # again until timeout.
550 logger.trace('QGA guest-ping unexpected output {out}'.
553 raise RuntimeError('QEMU: Timeout, VM not booted on {host}!'.
554 format(host=self._node['host']))
556 def _wait_until_kernelvm_boot(self, retries=60):
557 """Wait until QEMU KernelVM is booted.
559 :param retries: Number of retries.
562 vpp_ver = VPPUtil.vpp_show_version(self._node)
564 for _ in range(retries):
565 command = ('tail -1 {log}'.format(log=self._temp.get('log')))
568 stdout, _ = exec_cmd_no_error(self._node, command, sudo=True)
572 if vpp_ver in stdout or 'Press enter to exit' in stdout:
574 if 'reboot: Power down' in stdout:
575 raise RuntimeError('QEMU: NF failed to run on {host}!'.
576 format(host=self._node['host']))
578 raise RuntimeError('QEMU: Timeout, VM not booted on {host}!'.
579 format(host=self._node['host']))
581 def _update_vm_interfaces(self):
582 """Update interface names in VM node dict."""
583 # Send guest-network-get-interfaces command via QGA, output example:
584 # {"return": [{"name": "eth0", "hardware-address": "52:54:00:00:04:01"},
585 # {"name": "eth1", "hardware-address": "52:54:00:00:04:02"}]}.
586 out = self._qemu_qga_exec('guest-network-get-interfaces')
587 interfaces = out.get('return')
590 raise RuntimeError('Get VM interface list failed on {host}'.
591 format(host=self._node['host']))
592 # Create MAC-name dict.
593 for interface in interfaces:
594 if 'hardware-address' not in interface:
596 mac_name[interface['hardware-address']] = interface['name']
597 # Match interface by MAC and save interface name.
598 for interface in self._vm_info['interfaces'].values():
599 mac = interface.get('mac_address')
600 if_name = mac_name.get(mac)
603 'Interface name for MAC {mac} not found'.format(mac=mac))
605 interface['name'] = if_name
607 def qemu_start(self):
608 """Start QEMU and wait until VM boot.
610 :returns: VM node info.
613 cmd_opts = OptionString()
614 cmd_opts.add('{bin_path}/qemu-system-{arch}'.format(
615 bin_path=Constants.QEMU_BIN_PATH, arch=self._arch))
616 cmd_opts.extend(self._params)
617 message = ('QEMU: Start failed on {host}!'.
618 format(host=self._node['host']))
620 DUTSetup.check_huge_page(
621 self._node, '/dev/hugepages', self._opt.get('mem'))
624 self._node, cmd_opts, timeout=300, sudo=True, message=message)
625 self._wait_until_vm_boot()
632 """Kill qemu process."""
633 exec_cmd(self._node, 'chmod +r {pidfile}'.
634 format(pidfile=self._temp.get('pidfile')), sudo=True)
635 exec_cmd(self._node, 'kill -SIGKILL $(cat {pidfile})'.
636 format(pidfile=self._temp.get('pidfile')), sudo=True)
638 for value in self._temp.values():
639 exec_cmd(self._node, 'cat {value}'.format(value=value), sudo=True)
640 exec_cmd(self._node, 'rm -f {value}'.format(value=value), sudo=True)
642 def qemu_kill_all(self):
643 """Kill all qemu processes on DUT node if specified."""
644 exec_cmd(self._node, 'pkill -SIGKILL qemu', sudo=True)
646 for value in self._temp.values():
647 exec_cmd(self._node, 'cat {value}'.format(value=value), sudo=True)
648 exec_cmd(self._node, 'rm -f {value}'.format(value=value), sudo=True)
650 def qemu_version(self, version=None):
651 """Return Qemu version or compare if version is higher than parameter.
653 :param version: Version to compare.
655 :returns: Qemu version or Boolean if version is higher than parameter.
658 command = ('{bin_path}/qemu-system-{arch} --version'.format(
659 bin_path=Constants.QEMU_BIN_PATH,
662 stdout, _ = exec_cmd_no_error(self._node, command, sudo=True)
663 ver = match(r'QEMU emulator version ([\d.]*)', stdout).group(1)
664 return StrictVersion(ver) > StrictVersion(version) \