docs: Add overview of the structure of vsperf code
[vswitchperf.git] / vnfs / qemu / qemu.py
1 # Copyright 2015 Intel Corporation.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #   http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 """Automation of QEMU hypervisor for launching vhost-cuse enabled guests.
16 """
17
18 import os
19 import logging
20 import locale
21 import re
22 import subprocess
23
24 from conf import settings as S
25 from vnfs.vnf.vnf import IVnf
26
27 class IVnfQemu(IVnf):
28     """
29     Abstract class for controling an instance of QEMU
30     """
31     _cmd = None
32     _expect = S.getValue('GUEST_PROMPT_LOGIN')
33     _proc_name = 'qemu'
34
35     class GuestCommandFilter(logging.Filter):
36         """
37         Filter out strings beginning with 'guestcmd :'.
38         """
39         def filter(self, record):
40             return record.getMessage().startswith(self.prefix)
41
42     def __init__(self):
43         """
44         :param timeout: Time to wait for login prompt. If set to
45             0 do not wait.
46         :param number: Number of QEMU instance, used when multiple QEMU
47             instances are started at once.
48         :param args: Arguments to pass to QEMU.
49
50         :returns: None
51         """
52         super(IVnfQemu, self).__init__()
53         self._logger = logging.getLogger(__name__)
54         self._logfile = os.path.join(
55             S.getValue('LOG_DIR'),
56             S.getValue('LOG_FILE_QEMU')) + str(self._number)
57         self._timeout = 120
58         self._monitor = '%s/vm%dmonitor' % ('/tmp', self._number)
59
60         name = 'Client%d' % self._number
61         vnc = ':%d' % self._number
62         # don't use taskset to affinize main qemu process; It causes hangup
63         # of 2nd VM in case of DPDK. It also slows down VM responsivnes.
64         self._cmd = ['sudo', '-E', S.getValue('QEMU_BIN'),
65                      '-m', S.getValue('GUEST_MEMORY')[self._number],
66                      '-smp', str(S.getValue('GUEST_SMP')[self._number]),
67                      '-cpu', 'host',
68                      '-drive', 'if=scsi,file=' +
69                      S.getValue('GUEST_IMAGE')[self._number],
70                      '-drive',
71                      'if=scsi,file=fat:rw:%s,snapshot=off' %
72                      S.getValue('GUEST_SHARE_DIR')[self._number],
73                      '-boot', 'c', '--enable-kvm',
74                      '-monitor', 'unix:%s,server,nowait' % self._monitor,
75                      '-object',
76                      'memory-backend-file,id=mem,size=' +
77                      str(S.getValue('GUEST_MEMORY')[self._number]) + 'M,' +
78                      'mem-path=' + S.getValue('HUGEPAGE_DIR') + ',share=on',
79                      '-numa', 'node,memdev=mem -mem-prealloc',
80                      '-nographic', '-vnc', str(vnc), '-name', name,
81                      '-snapshot', '-net none', '-no-reboot',
82                     ]
83         self._configure_logging()
84
85     def _configure_logging(self):
86         """
87         Configure logging.
88         """
89         self.GuestCommandFilter.prefix = self._log_prefix
90
91         logger = logging.getLogger()
92         cmd_logger = logging.FileHandler(
93             filename=os.path.join(S.getValue('LOG_DIR'),
94                                   S.getValue('LOG_FILE_GUEST_CMDS')) +
95             str(self._number))
96         cmd_logger.setLevel(logging.DEBUG)
97         cmd_logger.addFilter(self.GuestCommandFilter())
98         logger.addHandler(cmd_logger)
99
100     # startup/Shutdown
101
102     def start(self):
103         """
104         Start QEMU instance, login and prepare for commands.
105         """
106         super(IVnfQemu, self).start()
107         self._affinitize()
108
109         if self._timeout:
110             self._login()
111             self._config_guest_loopback()
112
113     # helper functions
114
115     def _login(self, timeout=120):
116         """
117         Login to QEMU instance.
118
119         This can be used immediately after booting the machine, provided a
120         sufficiently long ``timeout`` is given.
121
122         :param timeout: Timeout to wait for login to complete.
123
124         :returns: None
125         """
126         # if no timeout was set, we likely started QEMU without waiting for it
127         # to boot. This being the case, we best check that it has finished
128         # first.
129         if not self._timeout:
130             self._expect_process(timeout=timeout)
131
132         self._child.sendline(S.getValue('GUEST_USERNAME'))
133         self._child.expect(S.getValue('GUEST_PROMPT_PASSWORD'), timeout=5)
134         self._child.sendline(S.getValue('GUEST_PASSWORD'))
135
136         self._expect_process(S.getValue('GUEST_PROMPT'), timeout=5)
137
138     def send_and_pass(self, cmd, timeout=30):
139         """
140         Send ``cmd`` and wait ``timeout`` seconds for it to pass.
141
142         :param cmd: Command to send to guest.
143         :param timeout: Time to wait for prompt before checking return code.
144
145         :returns: None
146         """
147         self.execute(cmd)
148         self.wait(S.getValue('GUEST_PROMPT'), timeout=timeout)
149         self.execute('echo $?')
150         self._child.expect('^0$', timeout=1)  # expect a 0
151         self.wait(S.getValue('GUEST_PROMPT'), timeout=timeout)
152
153     def _affinitize(self):
154         """
155         Affinitize the SMP cores of a QEMU instance.
156
157         This is a bit of a hack. The 'socat' utility is used to
158         interact with the QEMU HMP. This is necessary due to the lack
159         of QMP in older versions of QEMU, like v1.6.2. In future
160         releases, this should be replaced with calls to libvirt or
161         another Python-QEMU wrapper library.
162
163         :returns: None
164         """
165         thread_id = (r'.* CPU #%d: .* thread_id=(\d+)')
166
167         self._logger.info('Affinitizing guest...')
168
169         cur_locale = locale.getlocale()[1]
170         proc = subprocess.Popen(
171             ('echo', 'info cpus'), stdout=subprocess.PIPE)
172         output = subprocess.check_output(
173             ('sudo', 'socat', '-', 'UNIX-CONNECT:%s' % self._monitor),
174             stdin=proc.stdout)
175         proc.wait()
176
177         for cpu in range(0, int(S.getValue('GUEST_SMP')[self._number])):
178             match = None
179             for line in output.decode(cur_locale).split('\n'):
180                 match = re.search(thread_id % cpu, line)
181                 if match:
182                     self._affinitize_pid(
183                         S.getValue('GUEST_CORE_BINDING')[self._number][cpu],
184                         match.group(1))
185                     break
186
187             if not match:
188                 self._logger.error('Failed to affinitize guest core #%d. Could'
189                                    ' not parse tid.', cpu)
190
191     def _config_guest_loopback(self):
192         """
193         Configure VM to run VNF (e.g. port forwarding application)
194         """
195         pass
196
197     def wait(self, prompt=S.getValue('GUEST_PROMPT'), timeout=30):
198         super(IVnfQemu, self).wait(prompt=prompt, timeout=timeout)
199
200     def execute_and_wait(self, cmd, timeout=30,
201                          prompt=S.getValue('GUEST_PROMPT')):
202         super(IVnfQemu, self).execute_and_wait(cmd, timeout=timeout,
203                                                prompt=prompt)