1 # Copyright 2015-2016 Intel Corporation.
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 """Automation of QEMU hypervisor for launching vhost-cuse enabled guests.
24 from conf import settings as S
25 from conf import get_test_param
26 from vnfs.vnf.vnf import IVnf
30 Abstract class for controling an instance of QEMU
33 _expect = S.getValue('GUEST_PROMPT_LOGIN')
36 class GuestCommandFilter(logging.Filter):
38 Filter out strings beginning with 'guestcmd :'.
40 def filter(self, record):
41 return record.getMessage().startswith(self.prefix)
45 Initialisation function.
47 super(IVnfQemu, self).__init__()
48 self._logger = logging.getLogger(__name__)
49 self._logfile = os.path.join(
50 S.getValue('LOG_DIR'),
51 S.getValue('LOG_FILE_QEMU')) + str(self._number)
52 self._timeout = S.getValue('GUEST_TIMEOUT')[self._number]
53 self._monitor = '%s/vm%dmonitor' % ('/tmp', self._number)
54 self._net1 = get_test_param('guest_nic1_name', None)
55 if self._net1 == None:
56 self._net1 = S.getValue('GUEST_NIC1_NAME')[self._number]
58 self._net1 = self._net1.split(',')[self._number]
59 self._net2 = get_test_param('guest_nic2_name', None)
60 if self._net2 == None:
61 self._net2 = S.getValue('GUEST_NIC2_NAME')[self._number]
63 self._net2 = self._net2.split(',')[self._number]
65 # set guest loopback application based on VNF configuration
66 # cli option take precedence to config file values
67 self._guest_loopback = S.getValue('GUEST_LOOPBACK')[self._number]
69 self._testpmd_fwd_mode = S.getValue('GUEST_TESTPMD_FWD_MODE')
70 # in case of SRIOV we must ensure, that MAC addresses are not swapped
71 if S.getValue('SRIOV_ENABLED') and self._testpmd_fwd_mode.startswith('mac') and \
72 not S.getValue('VNF').endswith('PciPassthrough'):
74 self._logger.info("SRIOV detected, forwarding mode of testpmd was changed from '%s' to '%s'",
75 self._testpmd_fwd_mode, 'io')
76 self._testpmd_fwd_mode = 'io'
78 name = 'Client%d' % self._number
79 vnc = ':%d' % self._number
80 # don't use taskset to affinize main qemu process; It causes hangup
81 # of 2nd VM in case of DPDK. It also slows down VM responsivnes.
82 self._cmd = ['sudo', '-E', S.getValue('QEMU_BIN'),
83 '-m', S.getValue('GUEST_MEMORY')[self._number],
84 '-smp', str(S.getValue('GUEST_SMP')[self._number]),
85 '-cpu', 'host,migratable=off',
86 '-drive', 'if=scsi,file=' +
87 S.getValue('GUEST_IMAGE')[self._number],
88 '-boot', 'c', '--enable-kvm',
89 '-monitor', 'unix:%s,server,nowait' % self._monitor,
91 'memory-backend-file,id=mem,size=' +
92 str(S.getValue('GUEST_MEMORY')[self._number]) + 'M,' +
93 'mem-path=' + S.getValue('HUGEPAGE_DIR') + ',share=on',
94 '-numa', 'node,memdev=mem -mem-prealloc',
95 '-nographic', '-vnc', str(vnc), '-name', name,
96 '-snapshot', '-net none', '-no-reboot',
98 'if=scsi,format=raw,file=fat:rw:%s,snapshot=off' %
99 S.getValue('GUEST_SHARE_DIR')[self._number],
101 self._configure_logging()
103 def _configure_logging(self):
107 self.GuestCommandFilter.prefix = self._log_prefix
109 logger = logging.getLogger()
110 cmd_logger = logging.FileHandler(
111 filename=os.path.join(S.getValue('LOG_DIR'),
112 S.getValue('LOG_FILE_GUEST_CMDS')) +
114 cmd_logger.setLevel(logging.DEBUG)
115 cmd_logger.addFilter(self.GuestCommandFilter())
116 logger.addHandler(cmd_logger)
122 Start QEMU instance, login and prepare for commands.
124 super(IVnfQemu, self).start()
125 if S.getValue('VNF_AFFINITIZATION_ON'):
129 self._config_guest_loopback()
133 Stops VNF instance gracefully first.
135 # exit testpmd if needed
136 if self._guest_loopback == 'testpmd':
137 self.execute_and_wait('stop', 120, "Done")
138 self.execute_and_wait('quit', 120, "bye")
141 self.execute_and_wait('poweroff', 120, "Power down")
143 # just for case that graceful shutdown failed
144 super(IVnfQemu, self).stop()
148 def _login(self, timeout=120):
150 Login to QEMU instance.
152 This can be used immediately after booting the machine, provided a
153 sufficiently long ``timeout`` is given.
155 :param timeout: Timeout to wait for login to complete.
159 # if no timeout was set, we likely started QEMU without waiting for it
160 # to boot. This being the case, we best check that it has finished
162 if not self._timeout:
163 self._expect_process(timeout=timeout)
165 self._child.sendline(S.getValue('GUEST_USERNAME'))
166 self._child.expect(S.getValue('GUEST_PROMPT_PASSWORD'), timeout=5)
167 self._child.sendline(S.getValue('GUEST_PASSWORD'))
169 self._expect_process(S.getValue('GUEST_PROMPT'), timeout=5)
171 def send_and_pass(self, cmd, timeout=30):
173 Send ``cmd`` and wait ``timeout`` seconds for it to pass.
175 :param cmd: Command to send to guest.
176 :param timeout: Time to wait for prompt before checking return code.
181 self.wait(S.getValue('GUEST_PROMPT'), timeout=timeout)
182 self.execute('echo $?')
183 self._child.expect('^0$', timeout=1) # expect a 0
184 self.wait(S.getValue('GUEST_PROMPT'), timeout=timeout)
186 def _affinitize(self):
188 Affinitize the SMP cores of a QEMU instance.
190 This is a bit of a hack. The 'socat' utility is used to
191 interact with the QEMU HMP. This is necessary due to the lack
192 of QMP in older versions of QEMU, like v1.6.2. In future
193 releases, this should be replaced with calls to libvirt or
194 another Python-QEMU wrapper library.
198 thread_id = (r'.* CPU #%d: .* thread_id=(\d+)')
200 self._logger.info('Affinitizing guest...')
202 cur_locale = locale.getdefaultlocale()[1]
203 proc = subprocess.Popen(
204 ('echo', 'info cpus'), stdout=subprocess.PIPE)
205 output = subprocess.check_output(
206 ('sudo', 'socat', '-', 'UNIX-CONNECT:%s' % self._monitor),
210 for cpu in range(0, int(S.getValue('GUEST_SMP')[self._number])):
212 for line in output.decode(cur_locale).split('\n'):
213 match = re.search(thread_id % cpu, line)
215 self._affinitize_pid(
216 S.getValue('GUEST_CORE_BINDING')[self._number][cpu],
221 self._logger.error('Failed to affinitize guest core #%d. Could'
222 ' not parse tid.', cpu)
224 def _config_guest_loopback(self):
226 Configure VM to run VNF, e.g. port forwarding application based on the configuration
228 if self._guest_loopback == 'testpmd':
230 self._configure_testpmd()
231 elif self._guest_loopback == 'l2fwd':
233 self._configure_l2fwd()
234 elif self._guest_loopback == 'linux_bridge':
236 self._configure_linux_bridge()
237 elif self._guest_loopback != 'buildin':
238 self._logger.error('Unsupported guest loopback method "%s" was specified. Option'
239 ' "buildin" will be used as a fallback.', self._guest_loopback)
241 def wait(self, prompt=S.getValue('GUEST_PROMPT'), timeout=30):
242 super(IVnfQemu, self).wait(prompt=prompt, timeout=timeout)
244 def execute_and_wait(self, cmd, timeout=30,
245 prompt=S.getValue('GUEST_PROMPT')):
246 super(IVnfQemu, self).execute_and_wait(cmd, timeout=timeout,
249 def _modify_dpdk_makefile(self):
251 Modifies DPDK makefile in Guest before compilation
255 def _configure_copy_sources(self, dirname):
257 Mount shared directory and copy DPDK and l2fwd sources
259 # mount shared directory
260 self.execute_and_wait('umount ' + S.getValue('OVS_DPDK_SHARE'))
261 self.execute_and_wait('rm -rf ' + S.getValue('GUEST_OVS_DPDK_DIR'))
262 self.execute_and_wait('mkdir -p ' + S.getValue('OVS_DPDK_SHARE'))
263 self.execute_and_wait('mount -o iocharset=utf8 /dev/sdb1 ' +
264 S.getValue('OVS_DPDK_SHARE'))
265 self.execute_and_wait('mkdir -p ' + S.getValue('GUEST_OVS_DPDK_DIR'))
266 self.execute_and_wait('cp -ra ' + os.path.join(S.getValue('OVS_DPDK_SHARE'), dirname) +
267 ' ' + S.getValue('GUEST_OVS_DPDK_DIR'))
269 def _configure_disable_firewall(self):
271 Disable firewall in VM
273 for iptables in ['iptables', 'ip6tables']:
275 for chain in ['INPUT', 'FORWARD', 'OUTPUT']:
276 self.execute_and_wait("{} -t filter -P {} ACCEPT".format(iptables, chain))
278 for chain in ['PREROUTING', 'INPUT', 'FORWARD', 'OUTPUT', 'POSTROUTING']:
279 self.execute_and_wait("{} -t mangle -P {} ACCEPT".format(iptables, chain))
281 for chain in ['PREROUTING', 'INPUT', 'OUTPUT', 'POSTROUTING']:
282 self.execute_and_wait("{} -t nat -P {} ACCEPT".format(iptables, chain))
284 # flush rules and delete chains created by user
285 for table in ['filter', 'mangle', 'nat']:
286 self.execute_and_wait("{} -t {} -F".format(iptables, table))
287 self.execute_and_wait("{} -t {} -X".format(iptables, table))
290 def _configure_testpmd(self):
292 Configure VM to perform L2 forwarding between NICs by DPDK's testpmd
294 self._configure_copy_sources('DPDK')
295 self._configure_disable_firewall()
297 # Guest images _should_ have 1024 hugepages by default,
298 # but just in case:'''
299 self.execute_and_wait('sysctl vm.nr_hugepages=1024')
302 self.execute_and_wait('mkdir -p /dev/hugepages')
303 self.execute_and_wait(
304 'mount -t hugetlbfs hugetlbfs /dev/hugepages')
306 # build and configure system for dpdk
307 self.execute_and_wait('cd ' + S.getValue('GUEST_OVS_DPDK_DIR') +
309 self.execute_and_wait('export CC=gcc')
310 self.execute_and_wait('export RTE_SDK=' +
311 S.getValue('GUEST_OVS_DPDK_DIR') + '/DPDK')
312 self.execute_and_wait('export RTE_TARGET=%s' % S.getValue('RTE_TARGET'))
314 # modify makefile if needed
315 self._modify_dpdk_makefile()
317 # disable network interfaces, so DPDK can take care of them
318 self.execute_and_wait('ifdown ' + self._net1)
319 self.execute_and_wait('ifdown ' + self._net2)
321 # build and insert igb_uio and rebind interfaces to it
322 self.execute_and_wait('make RTE_OUTPUT=$RTE_SDK/$RTE_TARGET -C '
323 '$RTE_SDK/lib/librte_eal/linuxapp/igb_uio')
324 self.execute_and_wait('modprobe uio')
325 self.execute_and_wait('insmod %s/kmod/igb_uio.ko' %
326 S.getValue('RTE_TARGET'))
327 self.execute_and_wait('./tools/dpdk_nic_bind.py --status')
328 self.execute_and_wait(
329 './tools/dpdk_nic_bind.py -u' ' ' +
330 S.getValue('GUEST_NET1_PCI_ADDRESS')[self._number] + ' ' +
331 S.getValue('GUEST_NET2_PCI_ADDRESS')[self._number])
332 self.execute_and_wait(
333 './tools/dpdk_nic_bind.py -b igb_uio' ' ' +
334 S.getValue('GUEST_NET1_PCI_ADDRESS')[self._number] + ' ' +
335 S.getValue('GUEST_NET2_PCI_ADDRESS')[self._number])
336 self.execute_and_wait('./tools/dpdk_nic_bind.py --status')
338 # build and run 'test-pmd'
339 self.execute_and_wait('cd ' + S.getValue('GUEST_OVS_DPDK_DIR') +
340 '/DPDK/app/test-pmd')
341 self.execute_and_wait('make clean')
342 self.execute_and_wait('make')
343 self.execute_and_wait('./testpmd -c 0x3 -n 4 --socket-mem 512 --'
344 ' --burst=64 -i --txqflags=0xf00 ' +
345 '--disable-hw-vlan', 60, "Done")
346 self.execute('set fwd ' + self._testpmd_fwd_mode, 1)
347 self.execute_and_wait('start', 20,
348 'TX RS bit threshold=.+ - TXQ flags=0xf00')
350 def _configure_l2fwd(self):
352 Configure VM to perform L2 forwarding between NICs by l2fwd module
354 self._configure_copy_sources('l2fwd')
355 self._configure_disable_firewall()
357 # build and configure system for l2fwd
358 self.execute_and_wait('cd ' + S.getValue('GUEST_OVS_DPDK_DIR') +
360 self.execute_and_wait('export CC=gcc')
362 self.execute_and_wait('make')
363 self.execute_and_wait('insmod ' + S.getValue('GUEST_OVS_DPDK_DIR') +
364 '/l2fwd' + '/l2fwd.ko net1=' + self._net1 +
365 ' net2=' + self._net2)
367 def _configure_linux_bridge(self):
369 Configure VM to perform L2 forwarding between NICs by linux bridge
371 self._configure_disable_firewall()
373 self.execute('ifconfig ' + self._net1 + ' ' +
374 S.getValue('VANILLA_NIC1_IP_CIDR')[self._number])
376 self.execute('ifconfig ' + self._net2 + ' ' +
377 S.getValue('VANILLA_NIC2_IP_CIDR')[self._number])
379 # configure linux bridge
380 self.execute('brctl addbr br0')
381 self.execute('brctl addif br0 ' + self._net1 + ' ' + self._net2)
382 self.execute('ifconfig br0 ' +
383 S.getValue('VANILLA_BRIDGE_IP')[self._number])
385 # Add the arp entries for the IXIA ports and the bridge you are using.
386 # Use command line values if provided.
387 trafficgen_mac = get_test_param('vanilla_tgen_port1_mac',
388 S.getValue('VANILLA_TGEN_PORT1_MAC'))
389 trafficgen_ip = get_test_param('vanilla_tgen_port1_ip',
390 S.getValue('VANILLA_TGEN_PORT1_IP'))
392 self.execute('arp -s ' + trafficgen_ip + ' ' + trafficgen_mac)
394 trafficgen_mac = get_test_param('vanilla_tgen_port2_mac',
395 S.getValue('VANILLA_TGEN_PORT2_MAC'))
396 trafficgen_ip = get_test_param('vanilla_tgen_port2_ip',
397 S.getValue('VANILLA_TGEN_PORT2_IP'))
399 self.execute('arp -s ' + trafficgen_ip + ' ' + trafficgen_mac)
402 self.execute('sysctl -w net.ipv4.ip_forward=1')
404 # Controls source route verification
405 # 0 means no source validation
406 self.execute('sysctl -w net.ipv4.conf.all.rp_filter=0')
407 self.execute('sysctl -w net.ipv4.conf.' + self._net1 + '.rp_filter=0')
408 self.execute('sysctl -w net.ipv4.conf.' + self._net2 + '.rp_filter=0')