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