Merge "Trex_speed_improvement: Add logic for dealing with high speed cards"
[vswitchperf.git] / vswitches / vpp_dpdk_vhost.py
1 # Copyright 2017 Intel Corporation.
2 #
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
6 #
7 #   http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 """VSPERF VPP implementation using DPDK and vhostuser vports
16 """
17
18 import logging
19 import os
20 import copy
21 import re
22 import pexpect
23
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
29
30 # pylint: disable=too-many-public-methods
31 class VppDpdkVhost(IVSwitch, tasks.Process):
32     """ VPP with DPDK support
33     """
34     _proc_name = 'vpp'
35     _bridge_idx_counter = 100
36
37     def __init__(self):
38         """See IVswitch for general description
39         """
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#'
44         self._timeout = 30
45         self._vswitch_args = []
46         self._cmd = []
47         self._cmd_template = ['sudo', '-E', S.getValue('TOOLS')['vpp']]
48         self._stamp = None
49         self._logger = logging.getLogger(__name__)
50         self._phy_ports = []
51         self._virt_ports = []
52         self._switches = {}
53         self._vpp_ctl = ['sudo', S.getValue('TOOLS')['vppctl']]
54
55         # configure DPDK NICs
56         tmp_args = copy.deepcopy(S.getValue('VSWITCH_VPP_ARGS'))
57         if 'dpdk' not in tmp_args:
58             tmp_args['dpdk'] = []
59
60         # override socket-mem settings
61         for tmp_arg in tmp_args['dpdk']:
62             if tmp_arg.startswith('socket-mem'):
63                 tmp_args['dpdk'].remove(tmp_arg)
64         tmp_args['dpdk'].append('socket-mem ' +
65                                 ','.join(S.getValue('DPDK_SOCKET_MEM')))
66
67         # create directory for vhostuser sockets if needed
68         if not os.path.exists(S.getValue('TOOLS')['ovs_var_tmp']):
69             tasks.run_task(['sudo', 'mkdir', '-p',
70                             S.getValue('TOOLS')['ovs_var_tmp']], self._logger)
71
72         # configure path to the plugins
73         tmp_args['plugin_path'] = S.getValue('TOOLS')['vpp_plugin_path']
74
75         # cli sock file must be used for VPP 17.10 and newer
76         if S.getValue('VSWITCH_VPP_CLI_SOCK'):
77             self._vpp_ctl += ['-s', S.getValue('VSWITCH_VPP_CLI_SOCK')]
78             tmp_args['unix'].append('cli-listen {}'.format(
79                 S.getValue('VSWITCH_VPP_CLI_SOCK')))
80
81         mqs = int(S.getValue('VSWITCH_DPDK_MULTI_QUEUES'))
82         tmp_rxqs = ''
83         if mqs:
84             tmp_rxqs = " {{ num-rx-queues {} }}".format(mqs)
85
86         # configure physical ports
87         for nic in S.getValue('NICS'):
88             tmp_args['dpdk'].append("dev {}{}".format(nic['pci'], tmp_rxqs))
89         self._vswitch_args = self._process_vpp_args(tmp_args)
90
91     def _get_nic_info(self, key='Name'):
92         """Read NIC info from VPP and return NIC details in a dictionary
93            indexed by given ``key``
94
95         :param key: Name of the key to be used for indexing result dictionary
96
97         :returns: Dictionary with NIC infos including their PCI addresses
98         """
99         result = {}
100         output = self.run_vppctl(['show', 'hardware', 'brief'])
101         # parse output and store basic info about NICS
102         ifaces = output[0].split('\n')
103         keys = ifaces[0].split()
104         keys.append('Pci')
105         keyidx = keys.index(key)
106         for iface in ifaces[1:]:
107             tmpif = iface.split()
108             if not tmpif:
109                 continue
110             # get PCI address of given interface
111             output = self.run_vppctl(['show', 'hardware', tmpif[1], 'detail'])
112             match = re.search(r'pci address:\s*([\d:\.]+)', output[0])
113             if match:
114                 # normalize PCI address, e.g. 0000:05:10.01 => 0000:05:10.1
115                 tmp_pci = match.group(1).split('.')
116                 tmp_pci[1] = str(int(tmp_pci[1]))
117                 tmpif.append('.'.join(tmp_pci))
118             else:
119                 tmpif.append(None)
120             # store only NICs with reasonable index
121             if tmpif[keyidx] is not None:
122                 result[tmpif[keyidx]] = dict(zip(keys, tmpif))
123
124         return result
125
126     def _process_vpp_args(self, args):
127         """Produce VPP CLI args from input dictionary ``args``
128         """
129         cli_args = []
130         for cfg_key in args:
131             cli_args.append(cfg_key)
132             if isinstance(args[cfg_key], str):
133                 cli_args.append(args[cfg_key])
134             else:
135                 cli_args.append("{{ {} }}".format(' '.join(args[cfg_key])))
136
137         self._logger.debug("VPP CLI args: %s", cli_args)
138         return cli_args
139
140     def start(self):
141         """Activates DPDK kernel modules and starts VPP
142
143         :raises: pexpect.EOF, pexpect.TIMEOUT
144         """
145         dpdk.init()
146         self._logger.info("Starting VPP...")
147
148         self._cmd = self._cmd_template + self._vswitch_args
149
150         try:
151             tasks.Process.start(self)
152             self.relinquish()
153         except (pexpect.EOF, pexpect.TIMEOUT) as exc:
154             logging.error("Exception during VPP start.")
155             raise exc
156
157         self._logger.info("VPP...Started.")
158
159     def stop(self):
160         """See IVswitch for general description
161
162         Kills VPP and removes DPDK kernel modules.
163         """
164         self._logger.info("Terminating VPP...")
165         self.kill()
166         self._logger.info("VPP...Terminated.")
167         dpdk.cleanup()
168
169     def kill(self, signal='-15', sleep=10):
170         """See IVswitch for general description
171
172         Kills ``vpp``
173         """
174         if self.is_running():
175             # try to get VPP pid
176             output = self.run_vppctl(['show', 'version', 'verbose'])
177             match = re.search(r'Current PID:\s*([0-9]+)', output[0])
178             if match:
179                 vpp_pid = match.group(1)
180                 tasks.terminate_task(vpp_pid, logger=self._logger)
181
182             # in case, that pid was not detected or sudo envelope
183             # has not been terminated yet
184             tasks.Process.kill(self, signal, sleep)
185
186     def get_version(self):
187         """See IVswitch for general description
188         """
189         versions = []
190         output = self.run_vppctl(['show', 'version', 'verbose'])
191         if output[1]:
192             self._logger.warning("VPP version can not be read!")
193             return versions
194
195         match = re.search(r'Version:\s*(.+)', output[0])
196         if match:
197             versions.append(Version(S.getValue('VSWITCH'), match.group(1)))
198
199         match = re.search(r'DPDK Version:\s*DPDK (.+)', output[0])
200         if match:
201             versions.append(Version('dpdk', match.group(1)))
202
203         return versions
204
205     def add_switch(self, switch_name, dummy_params=None):
206         """See IVswitch for general description
207         """
208         if switch_name in self._switches:
209             self._logger.warning("switch %s already exists...", switch_name)
210         else:
211             self._switches[switch_name] = self._bridge_idx_counter
212             self._bridge_idx_counter += 1
213
214     def del_switch(self, switch_name):
215         """See IVswitch for general description
216         """
217         if switch_name in self._switches:
218             del self._switches[switch_name]
219
220     def add_phy_port(self, dummy_switch_name):
221         """See IVswitch for general description
222         :raises: RuntimeError
223         """
224         # get list of physical interfaces with PCI addresses
225         vpp_nics = self._get_nic_info(key='Pci')
226         # check if there are any NICs left
227         if len(self._phy_ports) >= len(S.getValue('NICS')):
228             raise RuntimeError("Can't add phy port! There are only {} ports defined "
229                                "by WHITELIST_NICS parameter!".format(len(S.getValue('NICS'))))
230
231         nic = S.getValue('NICS')[len(self._phy_ports)]
232         if not nic['pci'] in vpp_nics:
233             raise RuntimeError('VPP cannot access nic with PCI address: {}'.format(nic['pci']))
234         nic_name = vpp_nics[nic['pci']]['Name']
235         self._phy_ports.append(nic_name)
236         self.run_vppctl(['set', 'int', 'state', nic_name, 'up'])
237         return (nic_name, vpp_nics[nic['pci']]['Idx'])
238
239     def add_vport(self, dummy_switch_name):
240         """See IVswitch for general description
241         """
242         socket_name = S.getValue('TOOLS')['ovs_var_tmp'] + 'dpdkvhostuser' + str(len(self._virt_ports))
243         if S.getValue('VSWITCH_VHOSTUSER_SERVER_MODE'):
244             mode = ['server']
245         else:
246             mode = []
247         output = self.run_vppctl(['create', 'vhost-user', 'socket', socket_name] + mode +
248                                  S.getValue('VSWITCH_VPP_VHOSTUSER_ARGS'))
249         if output[0].find('returned') >= 0:
250             raise RuntimeError('VPP VhostUser interface cannot be created.')
251         nic_name = output[0].strip()
252         self._virt_ports.append(nic_name)
253         self.run_vppctl(['set', 'int', 'state', nic_name, 'up'])
254         return (nic_name, None)
255
256     def del_port(self, switch_name, port_name):
257         """See IVswitch for general description
258         """
259         if port_name in self._phy_ports:
260             self.run_vppctl(['set', 'int', 'state', port_name, 'down'])
261             self._phy_ports.remove(port_name)
262         elif port_name in self._virt_ports:
263             self.run_vppctl(['set', 'int', 'state', port_name, 'down'])
264             self.run_vppctl(['delete', 'vhost-user', port_name])
265             self._virt_ports.remove(port_name)
266         else:
267             self._logger.warning("Port %s is not configured.", port_name)
268
269     def add_l2patch(self, port1, port2, bidir=False):
270         """Create l2patch connection between given ports
271         """
272         self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2])
273         if bidir:
274             self.run_vppctl(['test', 'l2patch', 'rx', port2, 'tx', port1])
275
276     def add_xconnect(self, port1, port2, bidir=False):
277         """Create l2patch connection between given ports
278         """
279         self.run_vppctl(['set', 'interface', 'l2', 'xconnect', port1, port2])
280         if bidir:
281             self.run_vppctl(['set', 'interface', 'l2', 'xconnect', port2, port1])
282
283     def add_bridge(self, switch_name, port1, port2, dummy_bidir=False):
284         """Add given ports to bridge ``switch_name``
285         """
286         self.run_vppctl(['set', 'interface', 'l2', 'bridge', port1,
287                          str(self._switches[switch_name])])
288         self.run_vppctl(['set', 'interface', 'l2', 'bridge', port2,
289                          str(self._switches[switch_name])])
290
291     def add_connection(self, switch_name, port1, port2, bidir=False):
292         """See IVswitch for general description
293
294         :raises: RuntimeError
295         """
296         mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
297         if mode == 'l2patch':
298             self.add_l2patch(port1, port2, bidir)
299         elif mode == 'xconnect':
300             self.add_xconnect(port1, port2, bidir)
301         elif mode == 'bridge':
302             self.add_bridge(switch_name, port1, port2)
303         else:
304             raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode)
305
306     def del_l2patch(self, port1, port2, bidir=False):
307         """Remove l2patch connection between given ports
308
309         :param port1: port to be used in connection
310         :param port2: port to be used in connection
311         :param bidir: switch between uni and bidirectional traffic
312         """
313         self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2, 'del'])
314         if bidir:
315             self.run_vppctl(['test', 'l2patch', 'rx', port2, 'tx', port1, 'del'])
316
317     def del_xconnect(self, dummy_port1, dummy_port2, dummy_bidir=False):
318         """Remove xconnect connection between given ports
319         """
320         self._logger.warning('VPP: Removal of l2 xconnect is not implemented.')
321
322     def del_bridge(self, dummy_switch_name, dummy_port1, dummy_port2):
323         """Remove given ports from the bridge
324         """
325         self._logger.warning('VPP: Removal of interfaces from bridge is not implemented.')
326
327     def del_connection(self, switch_name, port1, port2, bidir=False):
328         """See IVswitch for general description
329
330         :raises: RuntimeError
331         """
332         mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
333         if mode == 'l2patch':
334             self.del_l2patch(port1, port2, bidir)
335         elif mode == 'xconnect':
336             self.del_xconnect(port1, port2, bidir)
337         elif mode == 'bridge':
338             self.del_bridge(switch_name, port1, port2)
339         else:
340             raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode)
341
342     def dump_l2patch(self):
343         """Dump l2patch connections
344         """
345         self.run_vppctl(['show', 'l2patch'])
346
347     def dump_xconnect(self):
348         """Dump l2 xconnect connections
349         """
350         self._logger.warning("VPP: Dump of l2 xconnections is not supported.")
351
352     def dump_bridge(self, switch_name):
353         """Show bridge details
354
355         :param switch_name: switch on which to operate
356         """
357         self.run_vppctl(['show', 'bridge-domain', str(self._switches[switch_name]), 'int'])
358
359     def dump_connections(self, switch_name):
360         """See IVswitch for general description
361
362         :raises: RuntimeError
363         """
364         mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
365         if mode == 'l2patch':
366             self.dump_l2patch()
367         elif mode == 'xconnect':
368             self.dump_xconnect()
369         elif mode == 'bridge':
370             self.dump_bridge(switch_name)
371         else:
372             raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode)
373
374     def run_vppctl(self, args, check_error=False):
375         """Run ``vppctl`` with supplied arguments.
376
377         :param args: Arguments to pass to ``vppctl``
378         :param check_error: Throw exception on error
379
380         :return: None
381         """
382         cmd = self._vpp_ctl + args
383         return tasks.run_task(cmd, self._logger, 'Running vppctl...', check_error)
384
385     #
386     # Validate methods
387     #
388     def validate_add_switch(self, dummy_result, switch_name, dummy_params=None):
389         """Validate - Create a new logical switch with no ports
390         """
391         return switch_name in self._switches
392
393     def validate_del_switch(self, dummy_result, switch_name):
394         """Validate removal of switch
395         """
396         return not self.validate_add_switch(dummy_result, switch_name)
397
398     def validate_add_phy_port(self, result, dummy_switch_name):
399         """ Validate that physical port was added to bridge.
400         """
401         return result[0] in self._phy_ports
402
403     def validate_add_vport(self, result, dummy_switch_name):
404         """ Validate that virtual port was added to bridge.
405         """
406         return result[0] in self._virt_ports
407
408     def validate_del_port(self, dummy_result, dummy_switch_name, port_name):
409         """ Validate that port_name was removed from bridge.
410         """
411         return not (port_name in self._phy_ports or port_name in self._virt_ports)
412
413     # pylint: disable=no-self-use
414     def validate_add_connection(self, dummy_result, dummy_switch_name, dummy_port1,
415                                 dummy_port2, dummy_bidir=False):
416         """ Validate that connection was added
417         """
418         return True
419
420     def validate_del_connection(self, dummy_result, dummy_switch_name, dummy_port1,
421                                 dummy_port2, dummy_bidir=False):
422         """ Validate that connection was deleted
423         """
424         return True
425
426     def validate_dump_connections(self, dummy_result, dummy_switch_name):
427         """ Validate dump connections call
428         """
429         return True
430
431     def validate_run_vppctl(self, result, dummy_args, dummy_check_error=False):
432         """validate execution of ``vppctl`` with supplied arguments.
433         """
434         # there shouldn't be any stderr
435         return not result[1]
436
437     #
438     # Non implemented methods
439     #
440     def add_flow(self, switch_name, flow, cache='off'):
441         """See IVswitch for general description
442         """
443         raise NotImplementedError()
444
445     def del_flow(self, switch_name, flow=None):
446         """See IVswitch for general description
447         """
448         raise NotImplementedError()
449
450     def dump_flows(self, switch_name):
451         """See IVswitch for general description
452         """
453         raise NotImplementedError()
454
455     def add_route(self, switch_name, network, destination):
456         """See IVswitch for general description
457         """
458         raise NotImplementedError()
459
460     def set_tunnel_arp(self, ip_addr, mac_addr, switch_name):
461         """See IVswitch for general description
462         """
463         raise NotImplementedError()
464
465     def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None):
466         """See IVswitch for general description
467         """
468         raise NotImplementedError()
469
470     def get_ports(self, switch_name):
471         """See IVswitch for general description
472         """
473         raise NotImplementedError()