1 # Copyright 2017 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 """VSPERF VPP implementation using DPDK and vhostuser vports
24 from src.dpdk import dpdk
25 from conf import settings as S
26 from vswitches.vswitch import IVSwitch
27 from tools import tasks
28 from tools.version import Version
30 # pylint: disable=too-many-public-methods
31 class VppDpdkVhost(IVSwitch, tasks.Process):
32 """ VPP with DPDK support
35 _bridge_idx_counter = 100
38 """See IVswitch for general description
40 self._logfile = os.path.join(S.getValue('LOG_DIR'),
41 S.getValue('LOG_FILE_VPP'))
42 self._logger = logging.getLogger(__name__)
43 self._expect = r'vpp#'
45 self._vswitch_args = []
47 self._cmd_template = ['sudo', '-E', S.getValue('TOOLS')['vpp']]
49 self._logger = logging.getLogger(__name__)
55 tmp_args = copy.deepcopy(S.getValue('VSWITCH_VPP_ARGS'))
56 if 'dpdk' not in tmp_args:
59 # override socket-mem settings
60 for tmp_arg in tmp_args['dpdk']:
61 if tmp_arg.startswith('socket-mem'):
62 tmp_args['dpdk'].remove(tmp_arg)
63 tmp_args['dpdk'].append('socket-mem ' +
64 ','.join(S.getValue('DPDK_SOCKET_MEM')))
66 # create directory for vhostuser sockets if needed
67 if not os.path.exists(S.getValue('TOOLS')['ovs_var_tmp']):
68 tasks.run_task(['sudo', 'mkdir', '-p',
69 S.getValue('TOOLS')['ovs_var_tmp']], self._logger)
71 # configure path to the plugins
72 tmp_args['plugin_path'] = S.getValue('TOOLS')['vpp_plugin_path']
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 logging.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 if switch_name in self._switches:
202 self._logger.warning("switch %s already exists...", switch_name)
204 self._switches[switch_name] = self._bridge_idx_counter
205 self._bridge_idx_counter += 1
207 def del_switch(self, switch_name):
208 """See IVswitch for general description
210 if switch_name in self._switches:
211 del self._switches[switch_name]
213 def add_phy_port(self, dummy_switch_name):
214 """See IVswitch for general description
215 :raises: RuntimeError
217 # get list of physical interfaces with PCI addresses
218 vpp_nics = self._get_nic_info(key='Pci')
219 # check if there are any NICs left
220 if len(self._phy_ports) >= len(S.getValue('NICS')):
221 raise RuntimeError('All available NICs are already configured!')
223 nic = S.getValue('NICS')[len(self._phy_ports)]
224 if not nic['pci'] in vpp_nics:
225 raise RuntimeError('VPP cannot access nic with PCI address: {}'.format(nic['pci']))
226 nic_name = vpp_nics[nic['pci']]['Name']
227 self._phy_ports.append(nic_name)
228 self.run_vppctl(['set', 'int', 'state', nic_name, 'up'])
229 return (nic_name, vpp_nics[nic['pci']]['Idx'])
231 def add_vport(self, dummy_switch_name):
232 """See IVswitch for general description
234 socket_name = S.getValue('TOOLS')['ovs_var_tmp'] + 'dpdkvhostuser' + str(len(self._virt_ports))
235 if S.getValue('VSWITCH_VHOSTUSER_SERVER_MODE'):
239 output = self.run_vppctl(['create', 'vhost-user', 'socket', socket_name] + mode +
240 S.getValue('VSWITCH_VPP_VHOSTUSER_ARGS'))
241 if output[0].find('returned') >= 0:
242 raise RuntimeError('VPP VhostUser interface cannot be created.')
243 nic_name = output[0].strip()
244 self._virt_ports.append(nic_name)
245 self.run_vppctl(['set', 'int', 'state', nic_name, 'up'])
246 return (nic_name, None)
248 def del_port(self, switch_name, port_name):
249 """See IVswitch for general description
251 if port_name in self._phy_ports:
252 self.run_vppctl(['set', 'int', 'state', port_name, 'down'])
253 self._phy_ports.remove(port_name)
254 elif port_name in self._virt_ports:
255 self.run_vppctl(['set', 'int', 'state', port_name, 'down'])
256 self.run_vppctl(['delete', 'vhost-user', port_name])
257 self._virt_ports.remove(port_name)
259 self._logger.warning("Port %s is not configured.", port_name)
261 def add_l2patch(self, port1, port2, bidir=False):
262 """Create l2patch connection between given ports
264 self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2])
266 self.run_vppctl(['test', 'l2patch', 'rx', port2, 'tx', port1])
268 def add_xconnect(self, port1, port2, bidir=False):
269 """Create l2patch connection between given ports
271 self.run_vppctl(['set', 'interface', 'l2', 'xconnect', port1, port2])
273 self.run_vppctl(['set', 'interface', 'l2', 'xconnect', port2, port1])
275 def add_bridge(self, switch_name, port1, port2, dummy_bidir=False):
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, bidir=False):
284 """See IVswitch for general description
286 :raises: RuntimeError
288 mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
289 if mode == 'l2patch':
290 self.add_l2patch(port1, port2, bidir)
291 elif mode == 'xconnect':
292 self.add_xconnect(port1, port2, bidir)
293 elif mode == 'bridge':
294 self.add_bridge(switch_name, port1, port2)
296 raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode)
298 def del_l2patch(self, port1, port2, bidir=False):
299 """Remove l2patch connection between given ports
301 :param port1: port to be used in connection
302 :param port2: port to be used in connection
303 :param bidir: switch between uni and bidirectional traffic
305 self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2, 'del'])
307 self.run_vppctl(['test', 'l2patch', 'rx', port2, 'tx', port1, 'del'])
309 def del_xconnect(self, dummy_port1, dummy_port2, dummy_bidir=False):
310 """Remove xconnect connection between given ports
312 self._logger.warning('VPP: Removal of l2 xconnect is not implemented.')
314 def del_bridge(self, dummy_switch_name, dummy_port1, dummy_port2):
315 """Remove given ports from the bridge
317 self._logger.warning('VPP: Removal of interfaces from bridge is not implemented.')
319 def del_connection(self, switch_name, port1, port2, bidir=False):
320 """See IVswitch for general description
322 :raises: RuntimeError
324 mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
325 if mode == 'l2patch':
326 self.del_l2patch(port1, port2, bidir)
327 elif mode == 'xconnect':
328 self.del_xconnect(port1, port2, bidir)
329 elif mode == 'bridge':
330 self.del_bridge(switch_name, port1, port2)
332 raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode)
334 def dump_l2patch(self):
335 """Dump l2patch connections
337 self.run_vppctl(['show', 'l2patch'])
339 def dump_xconnect(self):
340 """Dump l2 xconnect connections
342 self._logger.warning("VPP: Dump of l2 xconnections is not supported.")
344 def dump_bridge(self, switch_name):
345 """Show bridge details
347 :param switch_name: switch on which to operate
349 self.run_vppctl(['show', 'bridge-domain', str(self._switches[switch_name]), 'int'])
351 def dump_connections(self, switch_name):
352 """See IVswitch for general description
354 :raises: RuntimeError
356 mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
357 if mode == 'l2patch':
359 elif mode == 'xconnect':
361 elif mode == 'bridge':
362 self.dump_bridge(switch_name)
364 raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode)
366 def run_vppctl(self, args, check_error=False):
367 """Run ``vppctl`` with supplied arguments.
369 :param args: Arguments to pass to ``vppctl``
370 :param check_error: Throw exception on error
374 cmd = ['sudo', S.getValue('TOOLS')['vppctl']] + args
375 return tasks.run_task(cmd, self._logger, 'Running vppctl...', check_error)
380 def validate_add_switch(self, dummy_result, switch_name, dummy_params=None):
381 """Validate - Create a new logical switch with no ports
383 return switch_name in self._switches
385 def validate_del_switch(self, dummy_result, switch_name):
386 """Validate removal of switch
388 return not self.validate_add_switch(dummy_result, switch_name)
390 def validate_add_phy_port(self, result, dummy_switch_name):
391 """ Validate that physical port was added to bridge.
393 return result[0] in self._phy_ports
395 def validate_add_vport(self, result, dummy_switch_name):
396 """ Validate that virtual port was added to bridge.
398 return result[0] in self._virt_ports
400 def validate_del_port(self, dummy_result, dummy_switch_name, port_name):
401 """ Validate that port_name was removed from bridge.
403 return not (port_name in self._phy_ports or port_name in self._virt_ports)
405 # pylint: disable=no-self-use
406 def validate_add_connection(self, dummy_result, dummy_switch_name, dummy_port1,
407 dummy_port2, dummy_bidir=False):
408 """ Validate that connection was added
412 def validate_del_connection(self, dummy_result, dummy_switch_name, dummy_port1,
413 dummy_port2, dummy_bidir=False):
414 """ Validate that connection was deleted
418 def validate_dump_connections(self, dummy_result, dummy_switch_name):
419 """ Validate dump connections call
423 def validate_run_vppctl(self, result, dummy_args, dummy_check_error=False):
424 """validate execution of ``vppctl`` with supplied arguments.
426 # there shouldn't be any stderr
430 # Non implemented methods
432 def add_flow(self, switch_name, flow, cache='off'):
433 """See IVswitch for general description
435 raise NotImplementedError()
437 def del_flow(self, switch_name, flow=None):
438 """See IVswitch for general description
440 raise NotImplementedError()
442 def dump_flows(self, switch_name):
443 """See IVswitch for general description
445 raise NotImplementedError()
447 def add_route(self, switch_name, network, destination):
448 """See IVswitch for general description
450 raise NotImplementedError()
452 def set_tunnel_arp(self, ip_addr, mac_addr, switch_name):
453 """See IVswitch for general description
455 raise NotImplementedError()
457 def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None):
458 """See IVswitch for general description
460 raise NotImplementedError()
462 def get_ports(self, switch_name):
463 """See IVswitch for general description
465 raise NotImplementedError()