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

©2016 FD.io a Linux Foundation Collaborative Project. All Rights Reserved.
Linux Foundation is a registered trademark of The Linux Foundation. Linux is a registered trademark of Linus Torvalds.
Please see our privacy policy and terms of use.