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