Enable PVP and PVVP deployments for Vanilla OVS
[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         Initialisation function.
45         """
46         super(IVnfQemu, self).__init__()
47         self._logger = logging.getLogger(__name__)
48         self._logfile = os.path.join(
49             S.getValue('LOG_DIR'),
50             S.getValue('LOG_FILE_QEMU')) + str(self._number)
51         self._timeout = 120
52         self._monitor = '%s/vm%dmonitor' % ('/tmp', self._number)
53
54         name = 'Client%d' % self._number
55         vnc = ':%d' % self._number
56         # don't use taskset to affinize main qemu process; It causes hangup
57         # of 2nd VM in case of DPDK. It also slows down VM responsivnes.
58         self._cmd = ['sudo', '-E', S.getValue('QEMU_BIN'),
59                      '-m', S.getValue('GUEST_MEMORY')[self._number],
60                      '-smp', str(S.getValue('GUEST_SMP')[self._number]),
61                      '-cpu', 'host',
62                      '-drive', 'if=scsi,file=' +
63                      S.getValue('GUEST_IMAGE')[self._number],
64                      '-boot', 'c', '--enable-kvm',
65                      '-monitor', 'unix:%s,server,nowait' % self._monitor,
66                      '-object',
67                      'memory-backend-file,id=mem,size=' +
68                      str(S.getValue('GUEST_MEMORY')[self._number]) + 'M,' +
69                      'mem-path=' + S.getValue('HUGEPAGE_DIR') + ',share=on',
70                      '-numa', 'node,memdev=mem -mem-prealloc',
71                      '-nographic', '-vnc', str(vnc), '-name', name,
72                      '-snapshot', '-net none', '-no-reboot',
73                     ]
74         self._configure_logging()
75
76     def _configure_logging(self):
77         """
78         Configure logging.
79         """
80         self.GuestCommandFilter.prefix = self._log_prefix
81
82         logger = logging.getLogger()
83         cmd_logger = logging.FileHandler(
84             filename=os.path.join(S.getValue('LOG_DIR'),
85                                   S.getValue('LOG_FILE_GUEST_CMDS')) +
86             str(self._number))
87         cmd_logger.setLevel(logging.DEBUG)
88         cmd_logger.addFilter(self.GuestCommandFilter())
89         logger.addHandler(cmd_logger)
90
91     # startup/Shutdown
92
93     def start(self):
94         """
95         Start QEMU instance, login and prepare for commands.
96         """
97         super(IVnfQemu, self).start()
98         if S.getValue('VNF_AFFINITIZATION_ON'):
99             self._affinitize()
100
101         if self._timeout:
102             self._login()
103             self._config_guest_loopback()
104
105     # helper functions
106
107     def _login(self, timeout=120):
108         """
109         Login to QEMU instance.
110
111         This can be used immediately after booting the machine, provided a
112         sufficiently long ``timeout`` is given.
113
114         :param timeout: Timeout to wait for login to complete.
115
116         :returns: None
117         """
118         # if no timeout was set, we likely started QEMU without waiting for it
119         # to boot. This being the case, we best check that it has finished
120         # first.
121         if not self._timeout:
122             self._expect_process(timeout=timeout)
123
124         self._child.sendline(S.getValue('GUEST_USERNAME'))
125         self._child.expect(S.getValue('GUEST_PROMPT_PASSWORD'), timeout=5)
126         self._child.sendline(S.getValue('GUEST_PASSWORD'))
127
128         self._expect_process(S.getValue('GUEST_PROMPT'), timeout=5)
129
130     def send_and_pass(self, cmd, timeout=30):
131         """
132         Send ``cmd`` and wait ``timeout`` seconds for it to pass.
133
134         :param cmd: Command to send to guest.
135         :param timeout: Time to wait for prompt before checking return code.
136
137         :returns: None
138         """
139         self.execute(cmd)
140         self.wait(S.getValue('GUEST_PROMPT'), timeout=timeout)
141         self.execute('echo $?')
142         self._child.expect('^0$', timeout=1)  # expect a 0
143         self.wait(S.getValue('GUEST_PROMPT'), timeout=timeout)
144
145     def _affinitize(self):
146         """
147         Affinitize the SMP cores of a QEMU instance.
148
149         This is a bit of a hack. The 'socat' utility is used to
150         interact with the QEMU HMP. This is necessary due to the lack
151         of QMP in older versions of QEMU, like v1.6.2. In future
152         releases, this should be replaced with calls to libvirt or
153         another Python-QEMU wrapper library.
154
155         :returns: None
156         """
157         thread_id = (r'.* CPU #%d: .* thread_id=(\d+)')
158
159         self._logger.info('Affinitizing guest...')
160
161         cur_locale = locale.getlocale()[1]
162         proc = subprocess.Popen(
163             ('echo', 'info cpus'), stdout=subprocess.PIPE)
164         output = subprocess.check_output(
165             ('sudo', 'socat', '-', 'UNIX-CONNECT:%s' % self._monitor),
166             stdin=proc.stdout)
167         proc.wait()
168
169         for cpu in range(0, int(S.getValue('GUEST_SMP')[self._number])):
170             match = None
171             for line in output.decode(cur_locale).split('\n'):
172                 match = re.search(thread_id % cpu, line)
173                 if match:
174                     self._affinitize_pid(
175                         S.getValue('GUEST_CORE_BINDING')[self._number][cpu],
176                         match.group(1))
177                     break
178
179             if not match:
180                 self._logger.error('Failed to affinitize guest core #%d. Could'
181                                    ' not parse tid.', cpu)
182
183     def _config_guest_loopback(self):
184         """
185         Configure VM to run VNF (e.g. port forwarding application)
186         """
187         pass
188
189     def wait(self, prompt=S.getValue('GUEST_PROMPT'), timeout=30):
190         super(IVnfQemu, self).wait(prompt=prompt, timeout=timeout)
191
192     def execute_and_wait(self, cmd, timeout=30,
193                          prompt=S.getValue('GUEST_PROMPT')):
194         super(IVnfQemu, self).execute_and_wait(cmd, timeout=timeout,
195                                                prompt=prompt)