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 guests.
26 from conf import settings as S
27 from conf import get_test_param
28 from vnfs.vnf.vnf import IVnf
32 Abstract class for controling an instance of QEMU
35 _expect = S.getValue('GUEST_PROMPT_LOGIN')
38 class GuestCommandFilter(logging.Filter):
40 Filter out strings beginning with 'guestcmd :'.
42 def filter(self, record):
43 return record.getMessage().startswith(self.prefix)
47 Initialisation function.
49 super(IVnfQemu, self).__init__()
50 self._logger = logging.getLogger(__name__)
51 self._logfile = os.path.join(
52 S.getValue('LOG_DIR'),
53 S.getValue('LOG_FILE_QEMU')) + str(self._number)
54 self._timeout = S.getValue('GUEST_TIMEOUT')[self._number]
55 self._monitor = '%s/vm%dmonitor' % ('/tmp', self._number)
56 self._net1 = get_test_param('guest_nic1_name', None)
57 if self._net1 == None:
58 self._net1 = S.getValue('GUEST_NIC1_NAME')[self._number]
60 self._net1 = self._net1.split(',')[self._number]
61 self._net2 = get_test_param('guest_nic2_name', None)
62 if self._net2 == None:
63 self._net2 = S.getValue('GUEST_NIC2_NAME')[self._number]
65 self._net2 = self._net2.split(',')[self._number]
67 # set guest loopback application based on VNF configuration
68 # cli option take precedence to config file values
69 self._guest_loopback = S.getValue('GUEST_LOOPBACK')[self._number]
71 self._testpmd_fwd_mode = S.getValue('GUEST_TESTPMD_FWD_MODE')
72 # in case of SRIOV we must ensure, that MAC addresses are not swapped
73 if S.getValue('SRIOV_ENABLED') and self._testpmd_fwd_mode.startswith('mac') and \
74 not S.getValue('VNF').endswith('PciPassthrough'):
76 self._logger.info("SRIOV detected, forwarding mode of testpmd was changed from '%s' to '%s'",
77 self._testpmd_fwd_mode, 'io')
78 self._testpmd_fwd_mode = 'io'
80 name = 'Client%d' % self._number
81 vnc = ':%d' % self._number
82 # don't use taskset to affinize main qemu process; It causes hangup
83 # of 2nd VM in case of DPDK. It also slows down VM responsivnes.
84 self._cmd = ['sudo', '-E', S.getValue('QEMU_BIN'),
85 '-m', S.getValue('GUEST_MEMORY')[self._number],
86 '-smp', str(S.getValue('GUEST_SMP')[self._number]),
87 '-cpu', 'host,migratable=off',
88 '-drive', 'if={},file='.format(S.getValue(
89 'GUEST_BOOT_DRIVE_TYPE')) +
90 S.getValue('GUEST_IMAGE')[self._number],
91 '-boot', 'c', '--enable-kvm',
92 '-monitor', 'unix:%s,server,nowait' % self._monitor,
94 'memory-backend-file,id=mem,size=' +
95 str(S.getValue('GUEST_MEMORY')[self._number]) + 'M,' +
96 'mem-path=' + S.getValue('HUGEPAGE_DIR') + ',share=on',
97 '-numa', 'node,memdev=mem -mem-prealloc',
98 '-nographic', '-vnc', str(vnc), '-name', name,
99 '-snapshot', '-net none', '-no-reboot',
101 'if=%s,format=raw,file=fat:rw:%s,snapshot=off' %
102 (S.getValue('GUEST_SHARED_DRIVE_TYPE'),
103 S.getValue('GUEST_SHARE_DIR')[self._number]),
105 self._configure_logging()
107 def _configure_logging(self):
111 self.GuestCommandFilter.prefix = self._log_prefix
113 logger = logging.getLogger()
114 cmd_logger = logging.FileHandler(
115 filename=os.path.join(S.getValue('LOG_DIR'),
116 S.getValue('LOG_FILE_GUEST_CMDS')) +
118 cmd_logger.setLevel(logging.DEBUG)
119 cmd_logger.addFilter(self.GuestCommandFilter())
120 logger.addHandler(cmd_logger)
126 Start QEMU instance, login and prepare for commands.
128 super(IVnfQemu, self).start()
129 if S.getValue('VNF_AFFINITIZATION_ON'):
133 self._config_guest_loopback()
137 Stops VNF instance gracefully first.
140 # exit testpmd if needed
141 if self._guest_loopback == 'testpmd':
142 self.execute_and_wait('stop', 120, "Done")
143 self.execute_and_wait('quit', 120, "[bB]ye")
146 self.execute_and_wait('poweroff', 120, "Power down")
148 except pexpect.TIMEOUT:
151 # wait until qemu shutdowns
152 self._logger.debug('Wait for QEMU to terminate')
153 for dummy in range(30):
155 if not self.is_running():
158 # just for case that graceful shutdown failed
159 super(IVnfQemu, self).stop()
163 def _login(self, timeout=120):
165 Login to QEMU instance.
167 This can be used immediately after booting the machine, provided a
168 sufficiently long ``timeout`` is given.
170 :param timeout: Timeout to wait for login to complete.
174 # if no timeout was set, we likely started QEMU without waiting for it
175 # to boot. This being the case, we best check that it has finished
177 if not self._timeout:
178 self._expect_process(timeout=timeout)
180 self._child.sendline(S.getValue('GUEST_USERNAME'))
181 self._child.expect(S.getValue('GUEST_PROMPT_PASSWORD'), timeout=5)
182 self._child.sendline(S.getValue('GUEST_PASSWORD'))
184 self._expect_process(S.getValue('GUEST_PROMPT'), timeout=5)
186 def send_and_pass(self, cmd, timeout=30):
188 Send ``cmd`` and wait ``timeout`` seconds for it to pass.
190 :param cmd: Command to send to guest.
191 :param timeout: Time to wait for prompt before checking return code.
196 self.wait(S.getValue('GUEST_PROMPT'), timeout=timeout)
197 self.execute('echo $?')
198 self._child.expect('^0$', timeout=1) # expect a 0
199 self.wait(S.getValue('GUEST_PROMPT'), timeout=timeout)
201 def _affinitize(self):
203 Affinitize the SMP cores of a QEMU instance.
205 This is a bit of a hack. The 'socat' utility is used to
206 interact with the QEMU HMP. This is necessary due to the lack
207 of QMP in older versions of QEMU, like v1.6.2. In future
208 releases, this should be replaced with calls to libvirt or
209 another Python-QEMU wrapper library.
213 thread_id = (r'.* CPU #%d: .* thread_id=(\d+)')
215 self._logger.info('Affinitizing guest...')
217 cur_locale = locale.getdefaultlocale()[1]
218 proc = subprocess.Popen(
219 ('echo', 'info cpus'), stdout=subprocess.PIPE)
220 output = subprocess.check_output(
221 ('sudo', 'socat', '-', 'UNIX-CONNECT:%s' % self._monitor),
225 for cpu in range(0, int(S.getValue('GUEST_SMP')[self._number])):
227 for line in output.decode(cur_locale).split('\n'):
228 match = re.search(thread_id % cpu, line)
230 self._affinitize_pid(
231 S.getValue('GUEST_CORE_BINDING')[self._number][cpu],
236 self._logger.error('Failed to affinitize guest core #%d. Could'
237 ' not parse tid.', cpu)
239 def _config_guest_loopback(self):
241 Configure VM to run VNF, e.g. port forwarding application based on the configuration
243 if self._guest_loopback == 'testpmd':
245 self._configure_testpmd()
246 elif self._guest_loopback == 'l2fwd':
248 self._configure_l2fwd()
249 elif self._guest_loopback == 'linux_bridge':
251 self._configure_linux_bridge()
252 elif self._guest_loopback != 'buildin':
253 self._logger.error('Unsupported guest loopback method "%s" was specified. Option'
254 ' "buildin" will be used as a fallback.', self._guest_loopback)
256 def wait(self, prompt=S.getValue('GUEST_PROMPT'), timeout=30):
257 super(IVnfQemu, self).wait(prompt=prompt, timeout=timeout)
259 def execute_and_wait(self, cmd, timeout=30,
260 prompt=S.getValue('GUEST_PROMPT')):
261 super(IVnfQemu, self).execute_and_wait(cmd, timeout=timeout,
264 def _modify_dpdk_makefile(self):
266 Modifies DPDK makefile in Guest before compilation if needed
270 def _configure_copy_sources(self, dirname):
272 Mount shared directory and copy DPDK and l2fwd sources
274 # mount shared directory
275 self.execute_and_wait('umount /dev/sdb1')
276 self.execute_and_wait('rm -rf ' + S.getValue('GUEST_OVS_DPDK_DIR'))
277 self.execute_and_wait('mkdir -p ' + S.getValue('OVS_DPDK_SHARE'))
278 self.execute_and_wait('mount -o ro,iocharset=utf8 /dev/sdb1 ' +
279 S.getValue('OVS_DPDK_SHARE'))
280 self.execute_and_wait('mkdir -p ' + S.getValue('GUEST_OVS_DPDK_DIR'))
281 self.execute_and_wait('cp -r ' + os.path.join(S.getValue('OVS_DPDK_SHARE'), dirname) +
282 ' ' + S.getValue('GUEST_OVS_DPDK_DIR'))
283 self.execute_and_wait('umount /dev/sdb1')
285 def _configure_disable_firewall(self):
287 Disable firewall in VM
289 for iptables in ['iptables', 'ip6tables']:
291 for chain in ['INPUT', 'FORWARD', 'OUTPUT']:
292 self.execute_and_wait("{} -t filter -P {} ACCEPT".format(iptables, chain))
294 for chain in ['PREROUTING', 'INPUT', 'FORWARD', 'OUTPUT', 'POSTROUTING']:
295 self.execute_and_wait("{} -t mangle -P {} ACCEPT".format(iptables, chain))
297 for chain in ['PREROUTING', 'INPUT', 'OUTPUT', 'POSTROUTING']:
298 self.execute_and_wait("{} -t nat -P {} ACCEPT".format(iptables, chain))
300 # flush rules and delete chains created by user
301 for table in ['filter', 'mangle', 'nat']:
302 self.execute_and_wait("{} -t {} -F".format(iptables, table))
303 self.execute_and_wait("{} -t {} -X".format(iptables, table))
306 def _configure_testpmd(self):
308 Configure VM to perform L2 forwarding between NICs by DPDK's testpmd
310 self._configure_copy_sources('DPDK')
311 self._configure_disable_firewall()
313 # Guest images _should_ have 1024 hugepages by default,
314 # but just in case:'''
315 self.execute_and_wait('sysctl vm.nr_hugepages=1024')
318 self.execute_and_wait('mkdir -p /dev/hugepages')
319 self.execute_and_wait(
320 'mount -t hugetlbfs hugetlbfs /dev/hugepages')
322 # build and configure system for dpdk
323 self.execute_and_wait('cd ' + S.getValue('GUEST_OVS_DPDK_DIR') +
325 self.execute_and_wait('export CC=gcc')
326 self.execute_and_wait('export RTE_SDK=' +
327 S.getValue('GUEST_OVS_DPDK_DIR') + '/DPDK')
328 self.execute_and_wait('export RTE_TARGET=%s' % S.getValue('RTE_TARGET'))
330 # modify makefile if needed
331 self._modify_dpdk_makefile()
333 # disable network interfaces, so DPDK can take care of them
334 self.execute_and_wait('ifdown ' + self._net1)
335 self.execute_and_wait('ifdown ' + self._net2)
337 # build and insert igb_uio and rebind interfaces to it
338 self.execute_and_wait('make RTE_OUTPUT=$RTE_SDK/$RTE_TARGET -C '
339 '$RTE_SDK/lib/librte_eal/linuxapp/igb_uio')
340 self.execute_and_wait('modprobe uio')
341 self.execute_and_wait('insmod %s/kmod/igb_uio.ko' %
342 S.getValue('RTE_TARGET'))
343 self.execute_and_wait('./tools/dpdk*bind.py --status')
344 self.execute_and_wait(
345 './tools/dpdk*bind.py -u' ' ' +
346 S.getValue('GUEST_NET1_PCI_ADDRESS')[self._number] + ' ' +
347 S.getValue('GUEST_NET2_PCI_ADDRESS')[self._number])
348 self.execute_and_wait(
349 './tools/dpdk*bind.py -b igb_uio' ' ' +
350 S.getValue('GUEST_NET1_PCI_ADDRESS')[self._number] + ' ' +
351 S.getValue('GUEST_NET2_PCI_ADDRESS')[self._number])
352 self.execute_and_wait('./tools/dpdk*bind.py --status')
354 # build and run 'test-pmd'
355 self.execute_and_wait('cd ' + S.getValue('GUEST_OVS_DPDK_DIR') +
356 '/DPDK/app/test-pmd')
357 self.execute_and_wait('make clean')
358 self.execute_and_wait('make')
359 if int(S.getValue('GUEST_NIC_QUEUES')):
360 self.execute_and_wait(
361 './testpmd {} -n4 --socket-mem 512 --'.format(
362 S.getValue('GUEST_TESTPMD_CPU_MASK')) +
363 ' --burst=64 -i --txqflags=0xf00 ' +
364 '--nb-cores={} --rxq={} --txq={} '.format(
365 S.getValue('GUEST_TESTPMD_NB_CORES'),
366 S.getValue('GUEST_TESTPMD_TXQ'),
367 S.getValue('GUEST_TESTPMD_RXQ')) +
368 '--disable-hw-vlan', 60, "Done")
370 self.execute_and_wait(
371 './testpmd {} -n 4 --socket-mem 512 --'.format(
372 S.getValue('GUEST_TESTPMD_CPU_MASK')) +
373 ' --burst=64 -i --txqflags=0xf00 ' +
374 '--disable-hw-vlan', 60, "Done")
375 self.execute('set fwd ' + self._testpmd_fwd_mode, 1)
376 self.execute_and_wait('start', 20,
377 'TX RS bit threshold=.+ - TXQ flags=0xf00')
379 def _configure_l2fwd(self):
381 Configure VM to perform L2 forwarding between NICs by l2fwd module
383 self._configure_copy_sources('l2fwd')
384 self._configure_disable_firewall()
386 # build and configure system for l2fwd
387 self.execute_and_wait('cd ' + S.getValue('GUEST_OVS_DPDK_DIR') +
389 self.execute_and_wait('export CC=gcc')
391 self.execute_and_wait('make')
392 self.execute_and_wait('insmod ' + S.getValue('GUEST_OVS_DPDK_DIR') +
393 '/l2fwd' + '/l2fwd.ko net1=' + self._net1 +
394 ' net2=' + self._net2)
396 def _configure_linux_bridge(self):
398 Configure VM to perform L2 forwarding between NICs by linux bridge
400 self._configure_disable_firewall()
402 self.execute('ip addr add ' +
403 S.getValue('VANILLA_NIC1_IP_CIDR')[self._number] +
404 ' dev ' + self._net1)
405 self.execute('ip link set dev ' + self._net1 + ' up')
407 self.execute('ip addr add ' +
408 S.getValue('VANILLA_NIC2_IP_CIDR')[self._number] +
409 ' dev ' + self._net2)
410 self.execute('ip link set dev ' + self._net2 + ' up')
412 # configure linux bridge
413 self.execute('brctl addbr br0')
414 self.execute('brctl addif br0 ' + self._net1 + ' ' + self._net2)
415 self.execute('ip addr add ' +
416 S.getValue('VANILLA_BRIDGE_IP')[self._number] +
418 self.execute('ip link set dev br0 up')
420 # Add the arp entries for the IXIA ports and the bridge you are using.
421 # Use command line values if provided.
422 trafficgen_mac = get_test_param('vanilla_tgen_port1_mac',
423 S.getValue('VANILLA_TGEN_PORT1_MAC'))
424 trafficgen_ip = get_test_param('vanilla_tgen_port1_ip',
425 S.getValue('VANILLA_TGEN_PORT1_IP'))
427 self.execute('arp -s ' + trafficgen_ip + ' ' + trafficgen_mac)
429 trafficgen_mac = get_test_param('vanilla_tgen_port2_mac',
430 S.getValue('VANILLA_TGEN_PORT2_MAC'))
431 trafficgen_ip = get_test_param('vanilla_tgen_port2_ip',
432 S.getValue('VANILLA_TGEN_PORT2_IP'))
434 self.execute('arp -s ' + trafficgen_ip + ' ' + trafficgen_mac)
437 self.execute('sysctl -w net.ipv4.ip_forward=1')
439 # Controls source route verification
440 # 0 means no source validation
441 self.execute('sysctl -w net.ipv4.conf.all.rp_filter=0')
442 self.execute('sysctl -w net.ipv4.conf.' + self._net1 + '.rp_filter=0')
443 self.execute('sysctl -w net.ipv4.conf.' + self._net2 + '.rp_filter=0')