Merge "Auto Generated INFO.yaml file"
[vswitchperf.git] / vswitches / vpp_dpdk_vhost.py
1 # Copyright 2017-2018 Intel Corporation., Tieto
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 os
19 import copy
20 import re
21 import pexpect
22
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
28
29 # pylint: disable=too-many-public-methods
30 class VppDpdkVhost(IVSwitch, tasks.Process):
31     """ VPP with DPDK support
32     """
33     _proc_name = 'vpp'
34     _bridge_idx_counter = 100
35
36     def __init__(self):
37         """See IVswitch for general description
38         """
39         super().__init__()
40         self._logfile = os.path.join(S.getValue('LOG_DIR'),
41                                      S.getValue('LOG_FILE_VPP'))
42         self._expect = r'vpp#'
43         self._cmd_template = ['sudo', '-E', S.getValue('TOOLS')['vpp']]
44         self._phy_ports = []
45         self._virt_ports = []
46         self._vpp_ctl = ['sudo', S.getValue('TOOLS')['vppctl']]
47
48         # configure DPDK NICs
49         tmp_args = copy.deepcopy(S.getValue('VSWITCH_VPP_ARGS'))
50         if 'dpdk' not in tmp_args:
51             tmp_args['dpdk'] = []
52
53         # override socket-mem settings
54         for tmp_arg in tmp_args['dpdk']:
55             if tmp_arg.startswith('socket-mem'):
56                 tmp_args['dpdk'].remove(tmp_arg)
57         tmp_args['dpdk'].append('socket-mem ' +
58                                 ','.join(S.getValue('DPDK_SOCKET_MEM')))
59
60         # create directory for vhostuser sockets if needed
61         if not os.path.exists(S.getValue('TOOLS')['ovs_var_tmp']):
62             tasks.run_task(['sudo', 'mkdir', '-p',
63                             S.getValue('TOOLS')['ovs_var_tmp']], self._logger)
64
65         # configure path to the plugins
66         tmp_args['plugin_path'] = S.getValue('TOOLS')['vpp_plugin_path']
67
68         # cli sock file must be used for VPP 17.10 and newer
69         if S.getValue('VSWITCH_VPP_CLI_SOCK'):
70             self._vpp_ctl += ['-s', S.getValue('VSWITCH_VPP_CLI_SOCK')]
71             tmp_args['unix'].append('cli-listen {}'.format(
72                 S.getValue('VSWITCH_VPP_CLI_SOCK')))
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             self._logger.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         # pylint: disable=unused-argument
202         if switch_name in self._switches:
203             self._logger.warning("switch %s already exists...", switch_name)
204         else:
205             self._switches[switch_name] = self._bridge_idx_counter
206             self._bridge_idx_counter += 1
207
208     def del_switch(self, switch_name):
209         """See IVswitch for general description
210         """
211         if switch_name in self._switches:
212             del self._switches[switch_name]
213
214     def add_phy_port(self, dummy_switch_name):
215         """See IVswitch for general description
216         :raises: RuntimeError
217         """
218         # pylint: disable=unused-argument
219         # get list of physical interfaces with PCI addresses
220         vpp_nics = self._get_nic_info(key='Pci')
221         # check if there are any NICs left
222         if len(self._phy_ports) >= len(S.getValue('NICS')):
223             raise RuntimeError("Can't add phy port! There are only {} ports defined "
224                                "by WHITELIST_NICS parameter!".format(len(S.getValue('NICS'))))
225
226         nic = S.getValue('NICS')[len(self._phy_ports)]
227         if not nic['pci'] in vpp_nics:
228             raise RuntimeError('VPP cannot access nic with PCI address: {}'.format(nic['pci']))
229         nic_name = vpp_nics[nic['pci']]['Name']
230         self._phy_ports.append(nic_name)
231         self.run_vppctl(['set', 'int', 'state', nic_name, 'up'])
232         return (nic_name, vpp_nics[nic['pci']]['Idx'])
233
234     def add_vport(self, dummy_switch_name):
235         """See IVswitch for general description
236         """
237         # pylint: disable=unused-argument
238         socket_name = S.getValue('TOOLS')['ovs_var_tmp'] + 'dpdkvhostuser' + str(len(self._virt_ports))
239         if S.getValue('VSWITCH_VHOSTUSER_SERVER_MODE'):
240             mode = ['server']
241         else:
242             mode = []
243         output = self.run_vppctl(['create', 'vhost-user', 'socket', socket_name] + mode +
244                                  S.getValue('VSWITCH_VPP_VHOSTUSER_ARGS'))
245         if output[0].find('returned') >= 0:
246             raise RuntimeError('VPP VhostUser interface cannot be created.')
247         nic_name = output[0].strip()
248         self._virt_ports.append(nic_name)
249         self.run_vppctl(['set', 'int', 'state', nic_name, 'up'])
250         return (nic_name, None)
251
252     def del_port(self, switch_name, port_name):
253         """See IVswitch for general description
254         """
255         if port_name in self._phy_ports:
256             self.run_vppctl(['set', 'int', 'state', port_name, 'down'])
257             self._phy_ports.remove(port_name)
258         elif port_name in self._virt_ports:
259             self.run_vppctl(['set', 'int', 'state', port_name, 'down'])
260             self.run_vppctl(['delete', 'vhost-user', port_name])
261             self._virt_ports.remove(port_name)
262         else:
263             self._logger.warning("Port %s is not configured.", port_name)
264
265     def add_l2patch(self, port1, port2):
266         """Create l2patch connection between given ports
267         """
268         self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2])
269
270     def add_xconnect(self, port1, port2):
271         """Create l2patch connection between given ports
272         """
273         self.run_vppctl(['set', 'interface', 'l2', 'xconnect', port1, port2])
274
275     def add_bridge(self, switch_name, port1, port2):
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, traffic=None):
284         """See IVswitch for general description
285
286         :raises: RuntimeError
287         """
288         if traffic:
289             self._logger.warning("VPP add_connection() does not support 'traffic' options.")
290
291         mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
292         if mode == 'l2patch':
293             self.add_l2patch(port1, port2)
294         elif mode == 'xconnect':
295             self.add_xconnect(port1, port2)
296         elif mode == 'bridge':
297             self.add_bridge(switch_name, port1, port2)
298         else:
299             raise RuntimeError('VPP: Unsupported l2 connection mode detected %s' % mode)
300
301     def del_l2patch(self, port1, port2):
302         """Remove l2patch connection between given ports
303
304         :param port1: port to be used in connection
305         :param port2: port to be used in connection
306         """
307         self.run_vppctl(['test', 'l2patch', 'rx', port1, 'tx', port2, 'del'])
308
309     def del_xconnect(self, port1, port2):
310         """Remove xconnect connection between given ports
311         """
312         self.run_vppctl(['set', 'interface', 'l3', port1])
313         self.run_vppctl(['set', 'interface', 'l3', port2])
314
315     def del_bridge(self, _dummy_switch_name, port1, port2):
316         """Remove given ports from the bridge
317         """
318         self.run_vppctl(['set', 'interface', 'l3', port1])
319         self.run_vppctl(['set', 'interface', 'l3', port2])
320
321     def del_connection(self, switch_name, port1=None, port2=None):
322         """See IVswitch for general description
323
324         :raises: RuntimeError
325         """
326         if port1 and port2:
327             mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
328             if mode == 'l2patch':
329                 self.del_l2patch(port1, port2)
330             elif mode == 'xconnect':
331                 self.del_xconnect(port1, port2)
332             elif mode == 'bridge':
333                 self.del_bridge(switch_name, port1, port2)
334             else:
335                 raise RuntimeError('VPP: Unsupported l2 connection mode detected %s' % mode)
336
337     def dump_l2patch(self):
338         """Dump l2patch connections
339         """
340         self.run_vppctl(['show', 'l2patch'])
341
342     def dump_xconnect(self):
343         """Dump l2 xconnect connections
344         """
345         self.run_vppctl(['show', 'mode'] + self._phy_ports + self._virt_ports)
346
347     def dump_bridge(self, switch_name):
348         """Show bridge details
349
350         :param switch_name: switch on which to operate
351         """
352         self.run_vppctl(['show', 'bridge-domain', str(self._switches[switch_name]), 'int'])
353
354     def dump_connections(self, switch_name):
355         """See IVswitch for general description
356
357         :raises: RuntimeError
358         """
359         mode = S.getValue('VSWITCH_VPP_L2_CONNECT_MODE')
360         if mode == 'l2patch':
361             self.dump_l2patch()
362         elif mode == 'xconnect':
363             self.dump_xconnect()
364         elif mode == 'bridge':
365             self.dump_bridge(switch_name)
366         else:
367             raise RuntimeError('VPP: Unsupported l2 connection mode detected %s' % mode)
368
369     def run_vppctl(self, args, check_error=False):
370         """Run ``vppctl`` with supplied arguments.
371
372         :param args: Arguments to pass to ``vppctl``
373         :param check_error: Throw exception on error
374
375         :return: None
376         """
377         cmd = self._vpp_ctl + args
378         return tasks.run_task(cmd, self._logger, 'Running vppctl...', check_error)
379
380     #
381     # Validate methods
382     #
383     def validate_add_switch(self, _dummy_result, switch_name, _dummy_params=None):
384         """Validate - Create a new logical switch with no ports
385         """
386         return switch_name in self._switches
387
388     def validate_del_switch(self, _dummy_result, switch_name):
389         """Validate removal of switch
390         """
391         return not self.validate_add_switch(_dummy_result, switch_name)
392
393     def validate_add_phy_port(self, result, _dummy_switch_name):
394         """ Validate that physical port was added to bridge.
395         """
396         return result[0] in self._phy_ports
397
398     def validate_add_vport(self, result, _dummy_switch_name):
399         """ Validate that virtual port was added to bridge.
400         """
401         return result[0] in self._virt_ports
402
403     def validate_del_port(self, _dummy_result, _dummy_switch_name, port_name):
404         """ Validate that port_name was removed from bridge.
405         """
406         return not (port_name in self._phy_ports or port_name in self._virt_ports)
407
408     # pylint: disable=no-self-use
409     def validate_add_connection(self, _dummy_result, _dummy_switch_name, _dummy_port1,
410                                 _dummy_port2, _dummy_traffic=None):
411         """ Validate that connection was added
412         """
413         return True
414
415     def validate_del_connection(self, _dummy_result, _dummy_switch_name, _dummy_port1,
416                                 _dummy_port2):
417         """ Validate that connection was deleted
418         """
419         return True
420
421     def validate_dump_connections(self, _dummy_result, _dummy_switch_name):
422         """ Validate dump connections call
423         """
424         return True
425
426     def validate_run_vppctl(self, result, _dummy_args, _dummy_check_error=False):
427         """validate execution of ``vppctl`` with supplied arguments.
428         """
429         # there shouldn't be any stderr
430         return not result[1]
431
432     #
433     # Non implemented methods
434     #
435     def add_route(self, switch_name, network, destination):
436         """See IVswitch for general description
437         """
438         raise NotImplementedError()
439
440     def set_tunnel_arp(self, ip_addr, mac_addr, switch_name):
441         """See IVswitch for general description
442         """
443         raise NotImplementedError()
444
445     def add_tunnel_port(self, switch_name, remote_ip, tunnel_type='vxlan', params=None):
446         """See IVswitch for general description
447         """
448         raise NotImplementedError()
449
450     def get_ports(self, switch_name):
451         """See IVswitch for general description
452         """
453         raise NotImplementedError()