1 # Copyright 2017-2018 Intel Corporation., Tieto
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 """VSPERF VPP implementation using DPDK and vhostuser vports
23 from src.dpdk import dpdk
24 from conf import settings as S
25 from vswitches.vswitch import IVSwitch
26 from tools import tasks
27 from tools.version import Version
29 # pylint: disable=too-many-public-methods
30 class VppDpdkVhost(IVSwitch, tasks.Process):
31 """ VPP with DPDK support
34 _bridge_idx_counter = 100
37 """See IVswitch for general description
40 self._logfile = os.path.join(S.getValue('LOG_DIR'),
41 S.getValue('LOG_FILE_VPP'))
42 self._expect = r'vpp#'
43 self._cmd_template = ['sudo', '-E', S.getValue('TOOLS')['vpp']]
46 self._vpp_ctl = ['sudo', S.getValue('TOOLS')['vppctl']]
49 tmp_args = copy.deepcopy(S.getValue('VSWITCH_VPP_ARGS'))
50 if 'dpdk' not in tmp_args:
53 # override socket-mem settings
54 for tmp_arg in tmp_args['dpdk']:
55 if tmp_arg.startswith('socket-mem'):
56 tmp_args['dpdk'].remove(tmp_arg)
57 tmp_args['dpdk'].append('socket-mem ' +
58 ','.join(S.getValue('DPDK_SOCKET_MEM')))
60 # create directory for vhostuser sockets if needed
61 if not os.path.exists(S.getValue('TOOLS')['ovs_var_tmp']):
62 tasks.run_task(['sudo', 'mkdir', '-p',
63 S.getValue('TOOLS')['ovs_var_tmp']], self._logger)
65 # configure path to the plugins
66 tmp_args['plugin_path'] = S.getValue('TOOLS')['vpp_plugin_path']
68 # cli sock file must be used for VPP 17.10 and newer
69 if S.getValue('VSWITCH_VPP_CLI_SOCK'):
70 self._vpp_ctl += ['-s', S.getValue('VSWITCH_VPP_CLI_SOCK')]
71 tmp_args['unix'].append('cli-listen {}'.format(
72 S.getValue('VSWITCH_VPP_CLI_SOCK')))
74 mqs = int(S.getValue('VSWITCH_DPDK_MULTI_QUEUES'))
77 tmp_rxqs = " {{ num-rx-queues {} }}".format(mqs)
79 # configure physical ports
80 for nic in S.getValue('NICS'):
81 tmp_args['dpdk'].append("dev {}{}".format(nic['pci'], tmp_rxqs))
82 self._vswitch_args = self._process_vpp_args(tmp_args)
84 def _get_nic_info(self, key='Name'):
85 """Read NIC info from VPP and return NIC details in a dictionary
86 indexed by given ``key``
88 :param key: Name of the key to be used for indexing result dictionary
90 :returns: Dictionary with NIC infos including their PCI addresses
93 output = self.run_vppctl(['show', 'hardware', 'brief'])
94 # parse output and store basic info about NICS
95 ifaces = output[0].split('\n')
96 keys = ifaces[0].split()
98 keyidx = keys.index(key)
99 for iface in ifaces[1:]:
100 tmpif = iface.split()
103 # get PCI address of given interface
104 output = self.run_vppctl(['show', 'hardware', tmpif[1], 'detail'])
105 match = re.search(r'pci address:\s*([\d:\.]+)', output[0])
107 # normalize PCI address, e.g. 0000:05:10.01 => 0000:05:10.1
108 tmp_pci = match.group(1).split('.')
109 tmp_pci[1] = str(int(tmp_pci[1]))
110 tmpif.append('.'.join(tmp_pci))
113 # store only NICs with reasonable index
114 if tmpif[keyidx] is not None:
115 result[tmpif[keyidx]] = dict(zip(keys, tmpif))
119 def _process_vpp_args(self, args):
120 """Produce VPP CLI args from input dictionary ``args``
124 cli_args.append(cfg_key)
125 if isinstance(args[cfg_key], str):
126 cli_args.append(args[cfg_key])
128 cli_args.append("{{ {} }}".format(' '.join(args[cfg_key])))
130 self._logger.debug("VPP CLI args: %s", cli_args)
134 """Activates DPDK kernel modules and starts VPP
136 :raises: pexpect.EOF, pexpect.TIMEOUT
139 self._logger.info("Starting VPP...")
141 self._cmd = self._cmd_template + self._vswitch_args
144 tasks.Process.start(self)
146 except (pexpect.EOF, pexpect.TIMEOUT) as exc:
147 self._logger.error("Exception during VPP start.")
150 self._logger.info("VPP...Started.")
153 """See IVswitch for general description
155 Kills VPP and removes DPDK kernel modules.
157 self._logger.info("Terminating VPP...")
159 self._logger.info("VPP...Terminated.")
162 def kill(self, signal='-15', sleep=10):
163 """See IVswitch for general description
167 if self.is_running():
169 output = self.run_vppctl(['show', 'version', 'verbose'])
170 match = re.search(r'Current PID:\s*([0-9]+)', output[0])
172 vpp_pid = match.group(1)
173 tasks.terminate_task(vpp_pid, logger=self._logger)
175 # in case, that pid was not detected or sudo envelope
176 # has not been terminated yet
177 tasks.Process.kill(self, signal, sleep)
179 def get_version(self):
180 """See IVswitch for general description
183 output = self.run_vppctl(['show', 'version', 'verbose'])
185 self._logger.warning("VPP version can not be read!")
188 match = re.search(r'Version:\s*(.+)', output[0])
190 versions.append(Version(S.getValue('VSWITCH'), match.group(1)))
192 match = re.search(r'DPDK Version:\s*DPDK (.+)', output[0])
194 versions.append(Version('dpdk', match.group(1)))
198 def add_switch(self, switch_name, dummy_params=None):
199 """See IVswitch for general description
201 # pylint: disable=unused-argument
202 if switch_name in self._switches:
203 self._logger.warning("switch %s already exists...", switch_name)
205 self._switches[switch_name] = self._bridge_idx_counter
206 self._bridge_idx_counter += 1
208 def del_switch(self, switch_name):
209 """See IVswitch for general description
211 if switch_name in self._switches:
212 del self._switches[switch_name]
214 def add_phy_port(self, dummy_switch_name):
215 """See IVswitch for general description
216 :raises: RuntimeError
218 # pylint: disable=unused-argument
219 # get list of physical interfaces with PCI addresses
220 vpp_nics = self._get_nic_info(key='Pci')
221 # check if there are any NICs left
222 if len(self._phy_ports) >= len(S.getValue('NICS')):
223 raise RuntimeError("Can't add phy port! There are only {} ports defined "
224 "by WHITELIST_NICS parameter!".format(len(S.getValue('NICS'))))
226 nic = S.getValue('NICS')[len(self._phy_ports)]
227 if not nic['pci'] in vpp_nics:
228 raise RuntimeError('VPP cannot access nic with PCI address: {}'.format(nic['pci']))
229 nic_name = vpp_nics[nic['pci']]['Name']
230 self._phy_ports.append(nic_name)
231 self.run_vppctl(['set', 'int', 'state', nic_name, 'up'])
232 return (nic_name, vpp_nics[nic['pci']]['Idx'])
234 def add_vport(self, dummy_switch_name):
235 """See IVswitch for general description
237 # pylint: disable=unused-argument
238 socket_name = S.getValue('TOOLS')['ovs_var_tmp'] + 'dpdkvhostuser' + str(len(self._virt_ports))
239 if S.getValue('VSWITCH_VHOSTUSER_SERVER_MODE'):
243 output = self.run_vppctl(['create', 'vhost-user', 'socket', socket_name] + mode +
244 S.getValue('VSWITCH_VPP_VHOSTUSER_ARGS'))
245 if output[0].find('returned') >= 0:
246 raise RuntimeError('VPP VhostUser interface cannot be created.')
247 nic_name = output[0].strip()
248 self._virt_ports.append(nic_name)
249 self.run_vppctl(['set', 'int', 'state', nic_name, 'up'])
250 return (nic_name, None)
252 def del_port(self, switch_name, port_name):
253 """See IVswitch for general description
255 if port_name in self._phy_ports:
256 self.run_vppctl(['set', 'int', 'state', port_name, 'down'])
257 self._phy_ports.remove(port_name)
258 elif port_name in self._virt_ports:
259 self.run_vppctl(['set', 'int', 'state', port_name, 'down'])
260 self.run_vppctl(['delete', 'vhost-user', port_name])
261 self._virt_ports.remove(port_name)
263 self._logger.warning("Port %s is not configured.", port_name)
265 def add_l2patch(self, port1, port2):
266 """Create l2patch connection between given ports
268 self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2])
270 def add_xconnect(self, port1, port2):
271 """Create l2patch connection between given ports
273 self.run_vppctl(['set', 'interface', 'l2', 'xconnect', port1, port2])
275 def add_bridge(self, switch_name, port1, port2):
276 """Add given ports to bridge ``switch_name``
278 self.run_vppctl(['set', 'interface', 'l2', 'bridge', port1,
279 str(self._switches[switch_name])])
280 self.run_vppctl(['set', 'interface', 'l2', 'bridge', port2,
281 str(self._switches[switch_name])])
283 def add_connection(self, switch_name, port1, port2, traffic=None):
284 """See IVswitch for general description
286 :raises: RuntimeError
289 self._logger.warning("VPP add_connection() does not support 'traffic' options.")
291 mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
292 if mode == 'l2patch':
293 self.add_l2patch(port1, port2)
294 elif mode == 'xconnect':
295 self.add_xconnect(port1, port2)
296 elif mode == 'bridge':
297 self.add_bridge(switch_name, port1, port2)
299 raise RuntimeError('VPP: Unsupported l2 connection mode detected %s' % mode)
301 def del_l2patch(self, port1, port2):
302 """Remove l2patch connection between given ports
304 :param port1: port to be used in connection
305 :param port2: port to be used in connection
307 self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2, 'del'])
309 def del_xconnect(self, port1, port2):
310 """Remove xconnect connection between given ports
312 self.run_vppctl(['set', 'interface', 'l3', port1])
313 self.run_vppctl(['set', 'interface', 'l3', port2])
315 def del_bridge(self, _dummy_switch_name, port1, port2):
316 """Remove given ports from the bridge
318 self.run_vppctl(['set', 'interface', 'l3', port1])
319 self.run_vppctl(['set', 'interface', 'l3', port2])
321 def del_connection(self, switch_name, port1=None, port2=None):
322 """See IVswitch for general description
324 :raises: RuntimeError
327 mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
328 if mode == 'l2patch':
329 self.del_l2patch(port1, port2)
330 elif mode == 'xconnect':
331 self.del_xconnect(port1, port2)
332 elif mode == 'bridge':
333 self.del_bridge(switch_name, port1, port2)
335 raise RuntimeError('VPP: Unsupported l2 connection mode detected %s' % mode)
337 def dump_l2patch(self):
338 """Dump l2patch connections
340 self.run_vppctl(['show', 'l2patch'])
342 def dump_xconnect(self):
343 """Dump l2 xconnect connections
345 self.run_vppctl(['show', 'mode'] + self._phy_ports + self._virt_ports)
347 def dump_bridge(self, switch_name):
348 """Show bridge details
350 :param switch_name: switch on which to operate
352 self.run_vppctl(['show', 'bridge-domain', str(self._switches[switch_name]), 'int'])
354 def dump_connections(self, switch_name):
355 """See IVswitch for general description
357 :raises: RuntimeError
359 mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
360 if mode == 'l2patch':
362 elif mode == 'xconnect':
364 elif mode == 'bridge':
365 self.dump_bridge(switch_name)
367 raise RuntimeError('VPP: Unsupported l2 connection mode detected %s' % mode)
369 def run_vppctl(self, args, check_error=False):
370 """Run ``vppctl`` with supplied arguments.
372 :param args: Arguments to pass to ``vppctl``
373 :param check_error: Throw exception on error
377 cmd = self._vpp_ctl + args
378 return tasks.run_task(cmd, self._logger, 'Running vppctl...', check_error)
383 def validate_add_switch(self, _dummy_result, switch_name, _dummy_params=None):
384 """Validate - Create a new logical switch with no ports
386 return switch_name in self._switches
388 def validate_del_switch(self, _dummy_result, switch_name):
389 """Validate removal of switch
391 return not self.validate_add_switch(_dummy_result, switch_name)
393 def validate_add_phy_port(self, result, _dummy_switch_name):
394 """ Validate that physical port was added to bridge.
396 return result[0] in self._phy_ports
398 def validate_add_vport(self, result, _dummy_switch_name):
399 """ Validate that virtual port was added to bridge.
401 return result[0] in self._virt_ports
403 def validate_del_port(self, _dummy_result, _dummy_switch_name, port_name):
404 """ Validate that port_name was removed from bridge.
406 return not (port_name in self._phy_ports or port_name in self._virt_ports)
408 # pylint: disable=no-self-use
409 def validate_add_connection(self, _dummy_result, _dummy_switch_name, _dummy_port1,
410 _dummy_port2, _dummy_traffic=None):
411 """ Validate that connection was added
415 def validate_del_connection(self, _dummy_result, _dummy_switch_name, _dummy_port1,
417 """ Validate that connection was deleted
421 def validate_dump_connections(self, _dummy_result, _dummy_switch_name):
422 """ Validate dump connections call
426 def validate_run_vppctl(self, result, _dummy_args, _dummy_check_error=False):
427 """validate execution of ``vppctl`` with supplied arguments.
429 # there shouldn't be any stderr
433 # Non implemented methods
435 def add_route(self, switch_name, network, destination):
436 """See IVswitch for general description
438 raise NotImplementedError()
440 def set_tunnel_arp(self, ip_addr, mac_addr, switch_name):
441 """See IVswitch for general description
443 raise NotImplementedError()
445 def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None):
446 """See IVswitch for general description
448 raise NotImplementedError()
450 def get_ports(self, switch_name):
451 """See IVswitch for general description
453 raise NotImplementedError()