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 name, ext = os.path.splitext(S.getValue('LOG_FILE_VPP'))
41 rename_vpplf = "{name}_{uid}{ex}".format(name=name,
45 self._logfile = os.path.join(S.getValue('RESULTS_PATH'), rename_vpplf)
46 self._expect = r'vpp#'
47 self._cmd_template = ['sudo', '-E', S.getValue('TOOLS')['vpp']]
50 self._vpp_ctl = ['sudo', S.getValue('TOOLS')['vppctl']]
53 tmp_args = copy.deepcopy(S.getValue('VSWITCH_VPP_ARGS'))
54 if 'dpdk' not in tmp_args:
57 # override socket-mem settings
58 for tmp_arg in tmp_args['dpdk']:
59 if tmp_arg.startswith('socket-mem'):
60 tmp_args['dpdk'].remove(tmp_arg)
61 tmp_args['dpdk'].append('socket-mem ' +
62 ','.join(S.getValue('DPDK_SOCKET_MEM')))
64 # create directory for vhostuser sockets if needed
65 if not os.path.exists(S.getValue('TOOLS')['ovs_var_tmp']):
66 tasks.run_task(['sudo', 'mkdir', '-p',
67 S.getValue('TOOLS')['ovs_var_tmp']], self._logger)
69 # configure path to the plugins
70 tmp_args['plugin_path'] = S.getValue('TOOLS')['vpp_plugin_path']
72 # cli sock file must be used for VPP 17.10 and newer
73 if S.getValue('VSWITCH_VPP_CLI_SOCK'):
74 self._vpp_ctl += ['-s', S.getValue('VSWITCH_VPP_CLI_SOCK')]
75 tmp_args['unix'].append('cli-listen {}'.format(
76 S.getValue('VSWITCH_VPP_CLI_SOCK')))
78 mqs = int(S.getValue('VSWITCH_DPDK_MULTI_QUEUES'))
81 tmp_rxqs = " {{ num-rx-queues {} }}".format(mqs)
83 # configure physical ports
84 for nic in S.getValue('NICS'):
85 tmp_args['dpdk'].append("dev {}{}".format(nic['pci'], tmp_rxqs))
86 self._vswitch_args = self._process_vpp_args(tmp_args)
88 def _get_nic_info(self, key='Name'):
89 """Read NIC info from VPP and return NIC details in a dictionary
90 indexed by given ``key``
92 :param key: Name of the key to be used for indexing result dictionary
94 :returns: Dictionary with NIC infos including their PCI addresses
97 output = self.run_vppctl(['show', 'hardware', 'brief'])
98 # parse output and store basic info about NICS
99 ifaces = output[0].split('\n')
100 keys = ifaces[0].split()
102 keyidx = keys.index(key)
103 for iface in ifaces[1:]:
104 tmpif = iface.split()
107 if 'Link' in iface or 'local' in iface:
109 # get PCI address of given interface
110 output = self.run_vppctl(['show', 'hardware', tmpif[1], 'detail'])
111 lines = output[0].split('\n')
112 #match = re.search(r'pci address:\s*([\d:\.]+)', output[0])
116 match = line.split(' ')[6]
118 # normalize PCI address, e.g. 0000:05:10.01 => 0000:05:10.1
119 tmp_pci = match.split('.')
120 # tmp_pci = match.group(1).split('.')
121 tmp_pci[1] = str(int(tmp_pci[1]))
122 tmpif.append('.'.join(tmp_pci))
125 # store only NICs with reasonable index
126 if tmpif[keyidx] is not None:
127 result[tmpif[keyidx]] = dict(zip(keys, tmpif))
131 def _process_vpp_args(self, args):
132 """Produce VPP CLI args from input dictionary ``args``
136 cli_args.append(cfg_key)
137 if isinstance(args[cfg_key], str):
138 cli_args.append(args[cfg_key])
140 cli_args.append("{{ {} }}".format(' '.join(args[cfg_key])))
142 self._logger.debug("VPP CLI args: %s", cli_args)
146 """Activates DPDK kernel modules and starts VPP
148 :raises: pexpect.EOF, pexpect.TIMEOUT
151 self._logger.info("Starting VPP...")
153 self._cmd = self._cmd_template + self._vswitch_args
156 tasks.Process.start(self)
158 except (pexpect.EOF, pexpect.TIMEOUT) as exc:
159 self._logger.error("Exception during VPP start.")
162 self._logger.info("VPP...Started.")
165 """See IVswitch for general description
167 Kills VPP and removes DPDK kernel modules.
169 self._logger.info("Terminating VPP...")
171 self._logger.info("VPP...Terminated.")
174 def kill(self, signal='-15', sleep=10):
175 """See IVswitch for general description
179 if self.is_running():
181 output = self.run_vppctl(['show', 'version', 'verbose'])
182 match = re.search(r'Current PID:\s*([0-9]+)', output[0])
184 vpp_pid = match.group(1)
185 tasks.terminate_task(vpp_pid, logger=self._logger)
187 # in case, that pid was not detected or sudo envelope
188 # has not been terminated yet
189 tasks.Process.kill(self, signal, sleep)
191 def get_version(self):
192 """See IVswitch for general description
195 output = self.run_vppctl(['show', 'version', 'verbose'])
197 self._logger.warning("VPP version can not be read!")
200 match = re.search(r'Version:\s*(.+)', output[0])
202 versions.append(Version(S.getValue('VSWITCH'), match.group(1)))
204 match = re.search(r'DPDK Version:\s*DPDK (.+)', output[0])
206 versions.append(Version('dpdk', match.group(1)))
210 def add_switch(self, switch_name, dummy_params=None):
211 """See IVswitch for general description
213 # pylint: disable=unused-argument
214 if switch_name in self._switches:
215 self._logger.warning("switch %s already exists...", switch_name)
217 self._switches[switch_name] = self._bridge_idx_counter
218 self._bridge_idx_counter += 1
220 def del_switch(self, switch_name):
221 """See IVswitch for general description
223 if switch_name in self._switches:
224 del self._switches[switch_name]
226 def add_phy_port(self, dummy_switch_name):
227 """See IVswitch for general description
228 :raises: RuntimeError
230 # pylint: disable=unused-argument
231 # get list of physical interfaces with PCI addresses
232 vpp_nics = self._get_nic_info(key='Pci')
233 # check if there are any NICs left
234 if len(self._phy_ports) >= len(S.getValue('NICS')):
235 raise RuntimeError("Can't add phy port! There are only {} ports defined "
236 "by WHITELIST_NICS parameter!".format(len(S.getValue('NICS'))))
238 nic = S.getValue('NICS')[len(self._phy_ports)]
239 if not nic['pci'] in vpp_nics:
240 raise RuntimeError('VPP cannot access nic with PCI address: {}'.format(nic['pci']))
241 nic_name = vpp_nics[nic['pci']]['Name']
242 self._phy_ports.append(nic_name)
243 self.run_vppctl(['set', 'int', 'state', nic_name, 'up'])
244 return (nic_name, vpp_nics[nic['pci']]['Idx'])
246 def add_vport(self, dummy_switch_name):
247 """See IVswitch for general description
249 # pylint: disable=unused-argument
250 socket_name = S.getValue('TOOLS')['ovs_var_tmp'] + 'dpdkvhostuser' + str(len(self._virt_ports))
251 if S.getValue('VSWITCH_VHOSTUSER_SERVER_MODE'):
255 output = self.run_vppctl(['create', 'vhost-user', 'socket', socket_name] + mode +
256 S.getValue('VSWITCH_VPP_VHOSTUSER_ARGS'))
257 if output[0].find('returned') >= 0:
258 raise RuntimeError('VPP VhostUser interface cannot be created.')
259 nic_name = output[0].strip()
260 self._virt_ports.append(nic_name)
261 self.run_vppctl(['set', 'int', 'state', nic_name, 'up'])
262 return (nic_name, None)
264 def del_port(self, switch_name, port_name):
265 """See IVswitch for general description
267 if port_name in self._phy_ports:
268 self.run_vppctl(['set', 'int', 'state', port_name, 'down'])
269 self._phy_ports.remove(port_name)
270 elif port_name in self._virt_ports:
271 self.run_vppctl(['set', 'int', 'state', port_name, 'down'])
272 self.run_vppctl(['delete', 'vhost-user', port_name])
273 self._virt_ports.remove(port_name)
275 self._logger.warning("Port %s is not configured.", port_name)
277 def add_l2patch(self, port1, port2):
278 """Create l2patch connection between given ports
280 self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2])
282 def add_xconnect(self, port1, port2):
283 """Create l2patch connection between given ports
285 self.run_vppctl(['set', 'interface', 'l2', 'xconnect', port1, port2])
287 def add_bridge(self, switch_name, port1, port2):
288 """Add given ports to bridge ``switch_name``
290 self.run_vppctl(['set', 'interface', 'l2', 'bridge', port1,
291 str(self._switches[switch_name])])
292 self.run_vppctl(['set', 'interface', 'l2', 'bridge', port2,
293 str(self._switches[switch_name])])
295 def add_connection(self, switch_name, port1, port2, traffic=None):
296 """See IVswitch for general description
298 :raises: RuntimeError
301 self._logger.warning("VPP add_connection() does not support 'traffic' options.")
303 mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
304 if mode == 'l2patch':
305 self.add_l2patch(port1, port2)
306 elif mode == 'xconnect':
307 self.add_xconnect(port1, port2)
308 elif mode == 'bridge':
309 self.add_bridge(switch_name, port1, port2)
311 raise RuntimeError('VPP: Unsupported l2 connection mode detected %s' % mode)
313 def del_l2patch(self, port1, port2):
314 """Remove l2patch connection between given ports
316 :param port1: port to be used in connection
317 :param port2: port to be used in connection
319 self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2, 'del'])
321 def del_xconnect(self, port1, port2):
322 """Remove xconnect connection between given ports
324 self.run_vppctl(['set', 'interface', 'l3', port1])
325 self.run_vppctl(['set', 'interface', 'l3', port2])
327 def del_bridge(self, _dummy_switch_name, port1, port2):
328 """Remove given ports from the bridge
330 self.run_vppctl(['set', 'interface', 'l3', port1])
331 self.run_vppctl(['set', 'interface', 'l3', port2])
333 def del_connection(self, switch_name, port1=None, port2=None):
334 """See IVswitch for general description
336 :raises: RuntimeError
339 mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
340 if mode == 'l2patch':
341 self.del_l2patch(port1, port2)
342 elif mode == 'xconnect':
343 self.del_xconnect(port1, port2)
344 elif mode == 'bridge':
345 self.del_bridge(switch_name, port1, port2)
347 raise RuntimeError('VPP: Unsupported l2 connection mode detected %s' % mode)
349 def dump_l2patch(self):
350 """Dump l2patch connections
352 self.run_vppctl(['show', 'l2patch'])
354 def dump_xconnect(self):
355 """Dump l2 xconnect connections
357 self.run_vppctl(['show', 'mode'] + self._phy_ports + self._virt_ports)
359 def dump_bridge(self, switch_name):
360 """Show bridge details
362 :param switch_name: switch on which to operate
364 self.run_vppctl(['show', 'bridge-domain', str(self._switches[switch_name]), 'int'])
366 def dump_connections(self, switch_name):
367 """See IVswitch for general description
369 :raises: RuntimeError
371 mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
372 if mode == 'l2patch':
374 elif mode == 'xconnect':
376 elif mode == 'bridge':
377 self.dump_bridge(switch_name)
379 raise RuntimeError('VPP: Unsupported l2 connection mode detected %s' % mode)
381 def run_vppctl(self, args, check_error=False):
382 """Run ``vppctl`` with supplied arguments.
384 :param args: Arguments to pass to ``vppctl``
385 :param check_error: Throw exception on error
389 cmd = self._vpp_ctl + args
390 return tasks.run_task(cmd, self._logger, 'Running vppctl...', check_error)
395 def validate_add_switch(self, _dummy_result, switch_name, _dummy_params=None):
396 """Validate - Create a new logical switch with no ports
398 return switch_name in self._switches
400 def validate_del_switch(self, _dummy_result, switch_name):
401 """Validate removal of switch
403 return not self.validate_add_switch(_dummy_result, switch_name)
405 def validate_add_phy_port(self, result, _dummy_switch_name):
406 """ Validate that physical port was added to bridge.
408 return result[0] in self._phy_ports
410 def validate_add_vport(self, result, _dummy_switch_name):
411 """ Validate that virtual port was added to bridge.
413 return result[0] in self._virt_ports
415 def validate_del_port(self, _dummy_result, _dummy_switch_name, port_name):
416 """ Validate that port_name was removed from bridge.
418 return not (port_name in self._phy_ports or port_name in self._virt_ports)
420 # pylint: disable=no-self-use
421 def validate_add_connection(self, _dummy_result, _dummy_switch_name, _dummy_port1,
422 _dummy_port2, _dummy_traffic=None):
423 """ Validate that connection was added
427 def validate_del_connection(self, _dummy_result, _dummy_switch_name, _dummy_port1,
429 """ Validate that connection was deleted
433 def validate_dump_connections(self, _dummy_result, _dummy_switch_name):
434 """ Validate dump connections call
438 def validate_run_vppctl(self, result, _dummy_args, _dummy_check_error=False):
439 """validate execution of ``vppctl`` with supplied arguments.
441 # there shouldn't be any stderr
445 # Non implemented methods
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 return self._phy_ports