Merge "teststeps: Improvements and bugfixing of teststeps"
[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('All available NICs are already configured!')
229
230         nic = S.getValue('NICS')[len(self._phy_ports)]
231         if not nic['pci'] in vpp_nics:
232             raise RuntimeError('VPP cannot access nic with PCI address: {}'.format(nic['pci']))
233         nic_name = vpp_nics[nic['pci']]['Name']
234         self._phy_ports.append(nic_name)
235         self.run_vppctl(['set', 'int', 'state', nic_name, 'up'])
236         return (nic_name, vpp_nics[nic['pci']]['Idx'])
237
238     def add_vport(self, dummy_switch_name):
239         """See IVswitch for general description
240         """
241         socket_name = S.getValue('TOOLS')['ovs_var_tmp'] + 'dpdkvhostuser' + str(len(self._virt_ports))
242         if S.getValue('VSWITCH_VHOSTUSER_SERVER_MODE'):
243             mode = ['server']
244         else:
245             mode = []
246         output = self.run_vppctl(['create', 'vhost-user', 'socket', socket_name] + mode +
247                                  S.getValue('VSWITCH_VPP_VHOSTUSER_ARGS'))
248         if output[0].find('returned') >= 0:
249             raise RuntimeError('VPP VhostUser interface cannot be created.')
250         nic_name = output[0].strip()
251         self._virt_ports.append(nic_name)
252         self.run_vppctl(['set', 'int', 'state', nic_name, 'up'])
253         return (nic_name, None)
254
255     def del_port(self, switch_name, port_name):
256         """See IVswitch for general description
257         """
258         if port_name in self._phy_ports:
259             self.run_vppctl(['set', 'int', 'state', port_name, 'down'])
260             self._phy_ports.remove(port_name)
261         elif port_name in self._virt_ports:
262             self.run_vppctl(['set', 'int', 'state', port_name, 'down'])
263             self.run_vppctl(['delete', 'vhost-user', port_name])
264             self._virt_ports.remove(port_name)
265         else:
266             self._logger.warning("Port %s is not configured.", port_name)
267
268     def add_l2patch(self, port1, port2, bidir=False):
269         """Create l2patch connection between given ports
270         """
271         self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2])
272         if bidir:
273             self.run_vppctl(['test', 'l2patch', 'rx', port2, 'tx', port1])
274
275     def add_xconnect(self, port1, port2, bidir=False):
276         """Create l2patch connection between given ports
277         """
278         self.run_vppctl(['set', 'interface', 'l2', 'xconnect', port1, port2])
279         if bidir:
280             self.run_vppctl(['set', 'interface', 'l2', 'xconnect', port2, port1])
281
282     def add_bridge(self, switch_name, port1, port2, dummy_bidir=False):
283         """Add given ports to bridge ``switch_name``
284         """
285         self.run_vppctl(['set', 'interface', 'l2', 'bridge', port1,
286                          str(self._switches[switch_name])])
287         self.run_vppctl(['set', 'interface', 'l2', 'bridge', port2,
288                          str(self._switches[switch_name])])
289
290     def add_connection(self, switch_name, port1, port2, bidir=False):
291         """See IVswitch for general description
292
293         :raises: RuntimeError
294         """
295         mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
296         if mode == 'l2patch':
297             self.add_l2patch(port1, port2, bidir)
298         elif mode == 'xconnect':
299             self.add_xconnect(port1, port2, bidir)
300         elif mode == 'bridge':
301             self.add_bridge(switch_name, port1, port2)
302         else:
303             raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode)
304
305     def del_l2patch(self, port1, port2, bidir=False):
306         """Remove l2patch connection between given ports
307
308         :param port1: port to be used in connection
309         :param port2: port to be used in connection
310         :param bidir: switch between uni and bidirectional traffic
311         """
312         self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2, 'del'])
313         if bidir:
314             self.run_vppctl(['test', 'l2patch', 'rx', port2, 'tx', port1, 'del'])
315
316     def del_xconnect(self, dummy_port1, dummy_port2, dummy_bidir=False):
317         """Remove xconnect connection between given ports
318         """
319         self._logger.warning('VPP: Removal of l2 xconnect is not implemented.')
320
321     def del_bridge(self, dummy_switch_name, dummy_port1, dummy_port2):
322         """Remove given ports from the bridge
323         """
324         self._logger.warning('VPP: Removal of interfaces from bridge is not implemented.')
325
326     def del_connection(self, switch_name, port1, port2, bidir=False):
327         """See IVswitch for general description
328
329         :raises: RuntimeError
330         """
331         mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
332         if mode == 'l2patch':
333             self.del_l2patch(port1, port2, bidir)
334         elif mode == 'xconnect':
335             self.del_xconnect(port1, port2, bidir)
336         elif mode == 'bridge':
337             self.del_bridge(switch_name, port1, port2)
338         else:
339             raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode)
340
341     def dump_l2patch(self):
342         """Dump l2patch connections
343         """
344         self.run_vppctl(['show', 'l2patch'])
345
346     def dump_xconnect(self):
347         """Dump l2 xconnect connections
348         """
349         self._logger.warning("VPP: Dump of l2 xconnections is not supported.")
350
351     def dump_bridge(self, switch_name):
352         """Show bridge details
353
354         :param switch_name: switch on which to operate
355         """
356         self.run_vppctl(['show', 'bridge-domain', str(self._switches[switch_name]), 'int'])
357
358     def dump_connections(self, switch_name):
359         """See IVswitch for general description
360
361         :raises: RuntimeError
362         """
363         mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
364         if mode == 'l2patch':
365             self.dump_l2patch()
366         elif mode == 'xconnect':
367             self.dump_xconnect()
368         elif mode == 'bridge':
369             self.dump_bridge(switch_name)
370         else:
371             raise RuntimeError('VPP: Unsupported l2 connection mode detected %s', mode)
372
373     def run_vppctl(self, args, check_error=False):
374         """Run ``vppctl`` with supplied arguments.
375
376         :param args: Arguments to pass to ``vppctl``
377         :param check_error: Throw exception on error
378
379         :return: None
380         """
381         cmd = self._vpp_ctl + args
382         return tasks.run_task(cmd, self._logger, 'Running vppctl...', check_error)
383
384     #
385     # Validate methods
386     #
387     def validate_add_switch(self, dummy_result, switch_name, dummy_params=None):
388         """Validate - Create a new logical switch with no ports
389         """
390         return switch_name in self._switches
391
392     def validate_del_switch(self, dummy_result, switch_name):
393         """Validate removal of switch
394         """
395         return not self.validate_add_switch(dummy_result, switch_name)
396
397     def validate_add_phy_port(self, result, dummy_switch_name):
398         """ Validate that physical port was added to bridge.
399         """
400         return result[0] in self._phy_ports
401
402     def validate_add_vport(self, result, dummy_switch_name):
403         """ Validate that virtual port was added to bridge.
404         """
405         return result[0] in self._virt_ports
406
407     def validate_del_port(self, dummy_result, dummy_switch_name, port_name):
408         """ Validate that port_name was removed from bridge.
409         """
410         return not (port_name in self._phy_ports or port_name in self._virt_ports)
411
412     # pylint: disable=no-self-use
413     def validate_add_connection(self, dummy_result, dummy_switch_name, dummy_port1,
414                                 dummy_port2, dummy_bidir=False):
415         """ Validate that connection was added
416         """
417         return True
418
419     def validate_del_connection(self, dummy_result, dummy_switch_name, dummy_port1,
420                                 dummy_port2, dummy_bidir=False):
421         """ Validate that connection was deleted
422         """
423         return True
424
425     def validate_dump_connections(self, dummy_result, dummy_switch_name):
426         """ Validate dump connections call
427         """
428         return True
429
430     def validate_run_vppctl(self, result, dummy_args, dummy_check_error=False):
431         """validate execution of ``vppctl`` with supplied arguments.
432         """
433         # there shouldn't be any stderr
434         return not result[1]
435
436     #
437     # Non implemented methods
438     #
439     def add_flow(self, switch_name, flow, cache='off'):
440         """See IVswitch for general description
441         """
442         raise NotImplementedError()
443
444     def del_flow(self, switch_name, flow=None):
445         """See IVswitch for general description
446         """
447         raise NotImplementedError()
448
449     def dump_flows(self, switch_name):
450         """See IVswitch for general description
451         """
452         raise NotImplementedError()
453
454     def add_route(self, switch_name, network, destination):
455         """See IVswitch for general description
456         """
457         raise NotImplementedError()
458
459     def set_tunnel_arp(self, ip_addr, mac_addr, switch_name):
460         """See IVswitch for general description
461         """
462         raise NotImplementedError()
463
464     def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None):
465         """See IVswitch for general description
466         """
467         raise NotImplementedError()
468
469     def get_ports(self, switch_name):
470         """See IVswitch for general description
471         """
472         raise NotImplementedError()