vpp: Multiqueue support for VPP
[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
54         # configure DPDK NICs
55         tmp_args = copy.deepcopy(S.getValue('VSWITCH_VPP_ARGS'))
56         if 'dpdk' not in tmp_args:
57             tmp_args['dpdk'] = []
58
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')))
65
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)
70
71         # configure path to the plugins
72         tmp_args['plugin_path'] = S.getValue('TOOLS')['vpp_plugin_path']
73
74         mqs = int(S.getValue('VSWITCH_DPDK_MULTI_QUEUES'))
75         tmp_rxqs = ''
76         if mqs:
77             tmp_rxqs = " {{ num-rx-queues {} }}".format(mqs)
78
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)
83
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``
87
88         :param key: Name of the key to be used for indexing result dictionary
89
90         :returns: Dictionary with NIC infos including their PCI addresses
91         """
92         result = {}
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()
97         keys.append('Pci')
98         keyidx = keys.index(key)
99         for iface in ifaces[1:]:
100             tmpif = iface.split()
101             if not tmpif:
102                 continue
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])
106             if match:
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))
111             else:
112                 tmpif.append(None)
113             # store only NICs with reasonable index
114             if tmpif[keyidx] is not None:
115                 result[tmpif[keyidx]] = dict(zip(keys, tmpif))
116
117         return result
118
119     def _process_vpp_args(self, args):
120         """Produce VPP CLI args from input dictionary ``args``
121         """
122         cli_args = []
123         for cfg_key in args:
124             cli_args.append(cfg_key)
125             if isinstance(args[cfg_key], str):
126                 cli_args.append(args[cfg_key])
127             else:
128                 cli_args.append("{{ {} }}".format(' '.join(args[cfg_key])))
129
130         self._logger.debug("VPP CLI args: %s", cli_args)
131         return cli_args
132
133     def start(self):
134         """Activates DPDK kernel modules and starts VPP
135
136         :raises: pexpect.EOF, pexpect.TIMEOUT
137         """
138         dpdk.init()
139         self._logger.info("Starting VPP...")
140
141         self._cmd = self._cmd_template + self._vswitch_args
142
143         try:
144             tasks.Process.start(self)
145             self.relinquish()
146         except (pexpect.EOF, pexpect.TIMEOUT) as exc:
147             logging.error("Exception during VPP start.")
148             raise exc
149
150         self._logger.info("VPP...Started.")
151
152     def stop(self):
153         """See IVswitch for general description
154
155         Kills VPP and removes DPDK kernel modules.
156         """
157         self._logger.info("Terminating VPP...")
158         self.kill()
159         self._logger.info("VPP...Terminated.")
160         dpdk.cleanup()
161
162     def kill(self, signal='-15', sleep=10):
163         """See IVswitch for general description
164
165         Kills ``vpp``
166         """
167         if self.is_running():
168             # try to get VPP pid
169             output = self.run_vppctl(['show', 'version', 'verbose'])
170             match = re.search(r'Current PID:\s*([0-9]+)', output[0])
171             if match:
172                 vpp_pid = match.group(1)
173                 tasks.terminate_task(vpp_pid, logger=self._logger)
174
175             # in case, that pid was not detected or sudo envelope
176             # has not been terminated yet
177             tasks.Process.kill(self, signal, sleep)
178
179     def get_version(self):
180         """See IVswitch for general description
181         """
182         versions = []
183         output = self.run_vppctl(['show', 'version', 'verbose'])
184         if output[1]:
185             self._logger.warning("VPP version can not be read!")
186             return versions
187
188         match = re.search(r'Version:\s*(.+)', output[0])
189         if match:
190             versions.append(Version(S.getValue('VSWITCH'), match.group(1)))
191
192         match = re.search(r'DPDK Version:\s*DPDK (.+)', output[0])
193         if match:
194             versions.append(Version('dpdk', match.group(1)))
195
196         return versions
197
198     def add_switch(self, switch_name, dummy_params=None):
199         """See IVswitch for general description
200         """
201         if switch_name in self._switches:
202             self._logger.warning("switch %s already exists...", switch_name)
203         else:
204             self._switches[switch_name] = self._bridge_idx_counter
205             self._bridge_idx_counter += 1
206
207     def del_switch(self, switch_name):
208         """See IVswitch for general description
209         """
210         if switch_name in self._switches:
211             del self._switches[switch_name]
212
213     def add_phy_port(self, dummy_switch_name):
214         """See IVswitch for general description
215         :raises: RuntimeError
216         """
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!')
222
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'])
230
231     def add_vport(self, dummy_switch_name):
232         """See IVswitch for general description
233         """
234         socket_name = S.getValue('TOOLS')['ovs_var_tmp'] + 'dpdkvhostuser' + str(len(self._virt_ports))
235         if S.getValue('VSWITCH_VHOSTUSER_SERVER_MODE'):
236             mode = ['server']
237         else:
238             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)
247
248     def del_port(self, switch_name, port_name):
249         """See IVswitch for general description
250         """
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)
258         else:
259             self._logger.warning("Port %s is not configured.", port_name)
260
261     def add_l2patch(self, port1, port2, bidir=False):
262         """Create l2patch connection between given ports
263         """
264         self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2])
265         if bidir:
266             self.run_vppctl(['test', 'l2patch', 'rx', port2, 'tx', port1])
267
268     def add_xconnect(self, port1, port2, bidir=False):
269         """Create l2patch connection between given ports
270         """
271         self.run_vppctl(['set', 'interface', 'l2', 'xconnect', port1, port2])
272         if bidir:
273             self.run_vppctl(['set', 'interface', 'l2', 'xconnect', port2, port1])
274
275     def add_bridge(self, switch_name, port1, port2, dummy_bidir=False):
276         """Add given ports to bridge ``switch_name``
277         """
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])])
282
283     def add_connection(self, switch_name, port1, port2, bidir=False):
284         """See IVswitch for general description
285
286         :raises: RuntimeError
287         """
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)
295         else:
296             raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode)
297
298     def del_l2patch(self, port1, port2, bidir=False):
299         """Remove l2patch connection between given ports
300
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
304         """
305         self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2, 'del'])
306         if bidir:
307             self.run_vppctl(['test', 'l2patch', 'rx', port2, 'tx', port1, 'del'])
308
309     def del_xconnect(self, dummy_port1, dummy_port2, dummy_bidir=False):
310         """Remove xconnect connection between given ports
311         """
312         self._logger.warning('VPP: Removal of l2 xconnect is not implemented.')
313
314     def del_bridge(self, dummy_switch_name, dummy_port1, dummy_port2):
315         """Remove given ports from the bridge
316         """
317         self._logger.warning('VPP: Removal of interfaces from bridge is not implemented.')
318
319     def del_connection(self, switch_name, port1, port2, bidir=False):
320         """See IVswitch for general description
321
322         :raises: RuntimeError
323         """
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)
331         else:
332             raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode)
333
334     def dump_l2patch(self):
335         """Dump l2patch connections
336         """
337         self.run_vppctl(['show', 'l2patch'])
338
339     def dump_xconnect(self):
340         """Dump l2 xconnect connections
341         """
342         self._logger.warning("VPP: Dump of l2 xconnections is not supported.")
343
344     def dump_bridge(self, switch_name):
345         """Show bridge details
346
347         :param switch_name: switch on which to operate
348         """
349         self.run_vppctl(['show', 'bridge-domain', str(self._switches[switch_name]), 'int'])
350
351     def dump_connections(self, switch_name):
352         """See IVswitch for general description
353
354         :raises: RuntimeError
355         """
356         mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
357         if mode == 'l2patch':
358             self.dump_l2patch()
359         elif mode == 'xconnect':
360             self.dump_xconnect()
361         elif mode == 'bridge':
362             self.dump_bridge(switch_name)
363         else:
364             raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode)
365
366     def run_vppctl(self, args, check_error=False):
367         """Run ``vppctl`` with supplied arguments.
368
369         :param args: Arguments to pass to ``vppctl``
370         :param check_error: Throw exception on error
371
372         :return: None
373         """
374         cmd = ['sudo', S.getValue('TOOLS')['vppctl']] + args
375         return tasks.run_task(cmd, self._logger, 'Running vppctl...', check_error)
376
377     #
378     # Validate methods
379     #
380     def validate_add_switch(self, dummy_result, switch_name, dummy_params=None):
381         """Validate - Create a new logical switch with no ports
382         """
383         return switch_name in self._switches
384
385     def validate_del_switch(self, dummy_result, switch_name):
386         """Validate removal of switch
387         """
388         return not self.validate_add_switch(dummy_result, switch_name)
389
390     def validate_add_phy_port(self, result, dummy_switch_name):
391         """ Validate that physical port was added to bridge.
392         """
393         return result[0] in self._phy_ports
394
395     def validate_add_vport(self, result, dummy_switch_name):
396         """ Validate that virtual port was added to bridge.
397         """
398         return result[0] in self._virt_ports
399
400     def validate_del_port(self, dummy_result, dummy_switch_name, port_name):
401         """ Validate that port_name was removed from bridge.
402         """
403         return not (port_name in self._phy_ports or port_name in self._virt_ports)
404
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
409         """
410         return True
411
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
415         """
416         return True
417
418     def validate_dump_connections(self, dummy_result, dummy_switch_name):
419         """ Validate dump connections call
420         """
421         return True
422
423     def validate_run_vppctl(self, result, dummy_args, dummy_check_error=False):
424         """validate execution of ``vppctl`` with supplied arguments.
425         """
426         # there shouldn't be any stderr
427         return not result[1]
428
429     #
430     # Non implemented methods
431     #
432     def add_flow(self, switch_name, flow, cache='off'):
433         """See IVswitch for general description
434         """
435         raise NotImplementedError()
436
437     def del_flow(self, switch_name, flow=None):
438         """See IVswitch for general description
439         """
440         raise NotImplementedError()
441
442     def dump_flows(self, switch_name):
443         """See IVswitch for general description
444         """
445         raise NotImplementedError()
446
447     def add_route(self, switch_name, network, destination):
448         """See IVswitch for general description
449         """
450         raise NotImplementedError()
451
452     def set_tunnel_arp(self, ip_addr, mac_addr, switch_name):
453         """See IVswitch for general description
454         """
455         raise NotImplementedError()
456
457     def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None):
458         """See IVswitch for general description
459         """
460         raise NotImplementedError()
461
462     def get_ports(self, switch_name):
463         """See IVswitch for general description
464         """
465         raise NotImplementedError()