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]
70 name = 'Client%d' % self._number
71 vnc = ':%d' % self._number
72 # don't use taskset to affinize main qemu process; It causes hangup
73 # of 2nd VM in case of DPDK. It also slows down VM responsivnes.
74 self._cmd = ['sudo', '-E', S.getValue('QEMU_BIN'),
75 '-m', S.getValue('GUEST_MEMORY')[self._number],
76 '-smp', str(S.getValue('GUEST_SMP')[self._number]),
77 '-cpu', 'host,migratable=off',
78 '-drive', 'if=scsi,file=' +
79 S.getValue('GUEST_IMAGE')[self._number],
80 '-boot', 'c', '--enable-kvm',
81 '-monitor', 'unix:%s,server,nowait' % self._monitor,
83 'memory-backend-file,id=mem,size=' +
84 str(S.getValue('GUEST_MEMORY')[self._number]) + 'M,' +
85 'mem-path=' + S.getValue('HUGEPAGE_DIR') + ',share=on',
86 '-numa', 'node,memdev=mem -mem-prealloc',
87 '-nographic', '-vnc', str(vnc), '-name', name,
88 '-snapshot', '-net none', '-no-reboot',
90 'if=scsi,format=raw,file=fat:rw:%s,snapshot=off' %
91 S.getValue('GUEST_SHARE_DIR')[self._number],
93 self._configure_logging()
95 def _configure_logging(self):
99 self.GuestCommandFilter.prefix = self._log_prefix
101 logger = logging.getLogger()
102 cmd_logger = logging.FileHandler(
103 filename=os.path.join(S.getValue('LOG_DIR'),
104 S.getValue('LOG_FILE_GUEST_CMDS')) +
106 cmd_logger.setLevel(logging.DEBUG)
107 cmd_logger.addFilter(self.GuestCommandFilter())
108 logger.addHandler(cmd_logger)
114 Start QEMU instance, login and prepare for commands.
116 super(IVnfQemu, self).start()
117 if S.getValue('VNF_AFFINITIZATION_ON'):
121 self._config_guest_loopback()
125 Stops VNF instance gracefully first.
127 # exit testpmd if needed
128 if self._guest_loopback == 'testpmd':
129 self.execute_and_wait('stop', 120, "Done")
130 self.execute_and_wait('quit', 120, "bye")
133 self.execute_and_wait('poweroff', 120, "Power down")
135 # just for case that graceful shutdown failed
136 super(IVnfQemu, self).stop()
140 def _login(self, timeout=120):
142 Login to QEMU instance.
144 This can be used immediately after booting the machine, provided a
145 sufficiently long ``timeout`` is given.
147 :param timeout: Timeout to wait for login to complete.
151 # if no timeout was set, we likely started QEMU without waiting for it
152 # to boot. This being the case, we best check that it has finished
154 if not self._timeout:
155 self._expect_process(timeout=timeout)
157 self._child.sendline(S.getValue('GUEST_USERNAME'))
158 self._child.expect(S.getValue('GUEST_PROMPT_PASSWORD'), timeout=5)
159 self._child.sendline(S.getValue('GUEST_PASSWORD'))
161 self._expect_process(S.getValue('GUEST_PROMPT'), timeout=5)
163 def send_and_pass(self, cmd, timeout=30):
165 Send ``cmd`` and wait ``timeout`` seconds for it to pass.
167 :param cmd: Command to send to guest.
168 :param timeout: Time to wait for prompt before checking return code.
173 self.wait(S.getValue('GUEST_PROMPT'), timeout=timeout)
174 self.execute('echo $?')
175 self._child.expect('^0$', timeout=1) # expect a 0
176 self.wait(S.getValue('GUEST_PROMPT'), timeout=timeout)
178 def _affinitize(self):
180 Affinitize the SMP cores of a QEMU instance.
182 This is a bit of a hack. The 'socat' utility is used to
183 interact with the QEMU HMP. This is necessary due to the lack
184 of QMP in older versions of QEMU, like v1.6.2. In future
185 releases, this should be replaced with calls to libvirt or
186 another Python-QEMU wrapper library.
190 thread_id = (r'.* CPU #%d: .* thread_id=(\d+)')
192 self._logger.info('Affinitizing guest...')
194 cur_locale = locale.getdefaultlocale()[1]
195 proc = subprocess.Popen(
196 ('echo', 'info cpus'), stdout=subprocess.PIPE)
197 output = subprocess.check_output(
198 ('sudo', 'socat', '-', 'UNIX-CONNECT:%s' % self._monitor),
202 for cpu in range(0, int(S.getValue('GUEST_SMP')[self._number])):
204 for line in output.decode(cur_locale).split('\n'):
205 match = re.search(thread_id % cpu, line)
207 self._affinitize_pid(
208 S.getValue('GUEST_CORE_BINDING')[self._number][cpu],
213 self._logger.error('Failed to affinitize guest core #%d. Could'
214 ' not parse tid.', cpu)
216 def _config_guest_loopback(self):
218 Configure VM to run VNF, e.g. port forwarding application based on the configuration
220 if self._guest_loopback == 'testpmd':
222 self._configure_testpmd()
223 elif self._guest_loopback == 'l2fwd':
225 self._configure_l2fwd()
226 elif self._guest_loopback == 'linux_bridge':
228 self._configure_linux_bridge()
229 elif self._guest_loopback != 'buildin':
230 self._logger.error('Unsupported guest loopback method "%s" was specified. Option'
231 ' "buildin" will be used as a fallback.', self._guest_loopback)
233 def wait(self, prompt=S.getValue('GUEST_PROMPT'), timeout=30):
234 super(IVnfQemu, self).wait(prompt=prompt, timeout=timeout)
236 def execute_and_wait(self, cmd, timeout=30,
237 prompt=S.getValue('GUEST_PROMPT')):
238 super(IVnfQemu, self).execute_and_wait(cmd, timeout=timeout,
241 def _modify_dpdk_makefile(self):
243 Modifies DPDK makefile in Guest before compilation
247 def _configure_copy_sources(self, dirname):
249 Mount shared directory and copy DPDK and l2fwd sources
251 # mount shared directory
252 self.execute_and_wait('umount ' + S.getValue('OVS_DPDK_SHARE'))
253 self.execute_and_wait('rm -rf ' + S.getValue('GUEST_OVS_DPDK_DIR'))
254 self.execute_and_wait('mkdir -p ' + S.getValue('OVS_DPDK_SHARE'))
255 self.execute_and_wait('mount -o iocharset=utf8 /dev/sdb1 ' +
256 S.getValue('OVS_DPDK_SHARE'))
257 self.execute_and_wait('mkdir -p ' + S.getValue('GUEST_OVS_DPDK_DIR'))
258 self.execute_and_wait('cp -ra ' + os.path.join(S.getValue('OVS_DPDK_SHARE'), dirname) +
259 ' ' + S.getValue('GUEST_OVS_DPDK_DIR'))
261 def _configure_disable_firewall(self):
263 Disable firewall in VM
265 for iptables in ['iptables', 'ip6tables']:
267 for chain in ['INPUT', 'FORWARD', 'OUTPUT']:
268 self.execute_and_wait("{} -t filter -P {} ACCEPT".format(iptables, chain))
270 for chain in ['PREROUTING', 'INPUT', 'FORWARD', 'OUTPUT', 'POSTROUTING']:
271 self.execute_and_wait("{} -t mangle -P {} ACCEPT".format(iptables, chain))
273 for chain in ['PREROUTING', 'INPUT', 'OUTPUT', 'POSTROUTING']:
274 self.execute_and_wait("{} -t nat -P {} ACCEPT".format(iptables, chain))
276 # flush rules and delete chains created by user
277 for table in ['filter', 'mangle', 'nat']:
278 self.execute_and_wait("{} -t {} -F".format(iptables, table))
279 self.execute_and_wait("{} -t {} -X".format(iptables, table))
282 def _configure_testpmd(self):
284 Configure VM to perform L2 forwarding between NICs by DPDK's testpmd
286 self._configure_copy_sources('DPDK')
287 self._configure_disable_firewall()
289 # Guest images _should_ have 1024 hugepages by default,
290 # but just in case:'''
291 self.execute_and_wait('sysctl vm.nr_hugepages=1024')
294 self.execute_and_wait('mkdir -p /dev/hugepages')
295 self.execute_and_wait(
296 'mount -t hugetlbfs hugetlbfs /dev/hugepages')
298 # build and configure system for dpdk
299 self.execute_and_wait('cd ' + S.getValue('GUEST_OVS_DPDK_DIR') +
301 self.execute_and_wait('export CC=gcc')
302 self.execute_and_wait('export RTE_SDK=' +
303 S.getValue('GUEST_OVS_DPDK_DIR') + '/DPDK')
304 self.execute_and_wait('export RTE_TARGET=%s' % S.getValue('RTE_TARGET'))
306 # modify makefile if needed
307 self._modify_dpdk_makefile()
309 self.execute_and_wait('make RTE_OUTPUT=$RTE_SDK/$RTE_TARGET -C '
310 '$RTE_SDK/lib/librte_eal/linuxapp/igb_uio')
311 self.execute_and_wait('modprobe uio')
312 self.execute_and_wait('insmod %s/kmod/igb_uio.ko' %
313 S.getValue('RTE_TARGET'))
314 self.execute_and_wait('./tools/dpdk_nic_bind.py --status')
315 self.execute_and_wait(
316 './tools/dpdk_nic_bind.py -b igb_uio' ' ' +
317 S.getValue('GUEST_NET1_PCI_ADDRESS')[self._number] + ' ' +
318 S.getValue('GUEST_NET2_PCI_ADDRESS')[self._number])
320 # build and run 'test-pmd'
321 self.execute_and_wait('cd ' + S.getValue('GUEST_OVS_DPDK_DIR') +
322 '/DPDK/app/test-pmd')
323 self.execute_and_wait('make clean')
324 self.execute_and_wait('make')
325 self.execute_and_wait('./testpmd -c 0x3 -n 4 --socket-mem 512 --'
326 ' --burst=64 -i --txqflags=0xf00 ' +
327 '--disable-hw-vlan', 60, "Done")
328 self.execute('set fwd mac_retry', 1)
329 self.execute_and_wait('start', 20,
330 'TX RS bit threshold=0 - TXQ flags=0xf00')
332 def _configure_l2fwd(self):
334 Configure VM to perform L2 forwarding between NICs by l2fwd module
336 self._configure_copy_sources('l2fwd')
337 self._configure_disable_firewall()
339 # build and configure system for l2fwd
340 self.execute_and_wait('cd ' + S.getValue('GUEST_OVS_DPDK_DIR') +
342 self.execute_and_wait('export CC=gcc')
344 self.execute_and_wait('make')
345 self.execute_and_wait('insmod ' + S.getValue('GUEST_OVS_DPDK_DIR') +
346 '/l2fwd' + '/l2fwd.ko net1=' + self._net1 +
347 ' net2=' + self._net2)
349 def _configure_linux_bridge(self):
351 Configure VM to perform L2 forwarding between NICs by linux bridge
353 self._configure_disable_firewall()
355 self.execute('ifconfig ' + self._net1 + ' ' +
356 S.getValue('VANILLA_NIC1_IP_CIDR')[self._number])
358 self.execute('ifconfig ' + self._net2 + ' ' +
359 S.getValue('VANILLA_NIC2_IP_CIDR')[self._number])
361 # configure linux bridge
362 self.execute('brctl addbr br0')
363 self.execute('brctl addif br0 ' + self._net1 + ' ' + self._net2)
364 self.execute('ifconfig br0 ' +
365 S.getValue('VANILLA_BRIDGE_IP')[self._number])
367 # Add the arp entries for the IXIA ports and the bridge you are using.
368 # Use command line values if provided.
369 trafficgen_mac = get_test_param('vanilla_tgen_port1_mac',
370 S.getValue('VANILLA_TGEN_PORT1_MAC'))
371 trafficgen_ip = get_test_param('vanilla_tgen_port1_ip',
372 S.getValue('VANILLA_TGEN_PORT1_IP'))
374 self.execute('arp -s ' + trafficgen_ip + ' ' + trafficgen_mac)
376 trafficgen_mac = get_test_param('vanilla_tgen_port2_mac',
377 S.getValue('VANILLA_TGEN_PORT2_MAC'))
378 trafficgen_ip = get_test_param('vanilla_tgen_port2_ip',
379 S.getValue('VANILLA_TGEN_PORT2_IP'))
381 self.execute('arp -s ' + trafficgen_ip + ' ' + trafficgen_mac)
384 self.execute('sysctl -w net.ipv4.ip_forward=1')
386 # Controls source route verification
387 # 0 means no source validation
388 self.execute('sysctl -w net.ipv4.conf.all.rp_filter=0')
389 self.execute('sysctl -w net.ipv4.conf.' + self._net1 + '.rp_filter=0')
390 self.execute('sysctl -w net.ipv4.conf.' + self._net2 + '.rp_filter=0')