Add more verbose DPDK logs
[csit.git] / resources / libraries / python / VppConfigGenerator.py
1 # Copyright (c) 2016 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """VPP Configuration File Generator library."""
15
16 import re
17 import time
18
19 from resources.libraries.python.ssh import SSH
20 from resources.libraries.python.topology import NodeType
21 from resources.libraries.python.topology import Topology
22
23 __all__ = ['VppConfigGenerator']
24
25
26 class VppConfigGenerator(object):
27     """VPP Configuration File Generator."""
28
29     def __init__(self):
30         """Initialize library."""
31         # VPP Node to apply configuration on
32         self._node = ''
33         # VPP Hostname
34         self._hostname = ''
35         # VPP Configuration
36         self._nodeconfig = {}
37         # Serialized VPP Configuration
38         self._vpp_config = ''
39         # VPP Service name
40         self._vpp_service_name = 'vpp'
41
42     def set_node(self, node):
43         """Set DUT node.
44
45         :param node: Node to store configuration on.
46         :type node: dict
47         :raises RuntimeError: If Node type is not DUT.
48         """
49         if node['type'] != NodeType.DUT:
50             raise RuntimeError('Startup config can only be applied to DUT'
51                                'node.')
52         self._node = node
53         self._hostname = Topology.get_node_hostname(node)
54
55     def get_config_str(self):
56         """Get dumped startup configuration in VPP config format.
57
58         :returns: Startup configuration in VPP config format.
59         :rtype: str
60         """
61         self.dump_config(self._nodeconfig)
62         return self._vpp_config
63
64     def add_config_item(self, config, value, path):
65         """Add startup configuration item.
66
67         :param config: Startup configuration of node.
68         :param value: Value to insert.
69         :param path: Path where to insert item.
70         :type config: dict
71         :type value: str
72         :type path: list
73         """
74         if len(path) == 1:
75             config[path[0]] = value
76             return
77         if path[0] not in config:
78             config[path[0]] = {}
79         self.add_config_item(config[path[0]], value, path[1:])
80
81     def dump_config(self, obj, level=-1):
82         """Dump the startup configuration in VPP config format.
83
84         :param obj: Python Object to print.
85         :param level: Nested level for indentation.
86         :type obj: Obj
87         :type level: int
88         :returns: nothing
89         """
90         indent = '  '
91         if level >= 0:
92             self._vpp_config += '{}{{\n'.format((level) * indent)
93         if isinstance(obj, dict):
94             for key, val in obj.items():
95                 if hasattr(val, '__iter__'):
96                     self._vpp_config += '{}{}\n'.format((level + 1) * indent,
97                                                         key)
98                     self.dump_config(val, level + 1)
99                 else:
100                     self._vpp_config += '{}{} {}\n'.format((level + 1) * indent,
101                                                            key, val)
102         else:
103             for val in obj:
104                 self._vpp_config += '{}{}\n'.format((level + 1) * indent, val)
105         if level >= 0:
106             self._vpp_config += '{}}}\n'.format(level * indent)
107
108     def add_unix_log(self, value='/tmp/vpe.log'):
109         """Add UNIX log configuration.
110
111         :param value: Log file.
112         :type value: str
113         """
114         path = ['unix', 'log']
115         self.add_config_item(self._nodeconfig, value, path)
116
117     def add_unix_cli_listen(self, value='localhost:5002'):
118         """Add UNIX cli-listen configuration.
119
120         :param value: CLI listen address and port.
121         :type value: str
122         """
123         path = ['unix', 'cli-listen']
124         self.add_config_item(self._nodeconfig, value, path)
125
126     def add_unix_nodaemon(self):
127         """Add UNIX nodaemon configuration."""
128         path = ['unix', 'nodaemon']
129         self.add_config_item(self._nodeconfig, '', path)
130
131     def add_unix_coredump(self):
132         """Add UNIX full-coredump configuration."""
133         path = ['unix', 'full-coredump']
134         self.add_config_item(self._nodeconfig, '', path)
135
136     def add_unix_exec(self, value):
137         """Add UNIX exec configuration."""
138         path = ['unix', 'exec']
139         self.add_config_item(self._nodeconfig, value, path)
140
141     def add_dpdk_dev(self, *devices):
142         """Add DPDK PCI device configuration.
143
144         :param devices: PCI device(s) (format xxxx:xx:xx.x)
145         :type devices: tuple
146         :raises ValueError: If PCI address format is not valid.
147         """
148         pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
149                              "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
150         for device in devices:
151             if not pattern.match(device):
152                 raise ValueError('PCI address {} to be added to host {} '
153                                  'is not in valid format xxxx:xx:xx.x'.
154                                  format(device, self._hostname))
155             path = ['dpdk', 'dev {0}'.format(device)]
156             self.add_config_item(self._nodeconfig, '', path)
157
158     def add_dpdk_cryptodev(self, count):
159         """Add DPDK Crypto PCI device configuration.
160
161         :param count: Number of crypto devices to add.
162         :type count: int
163         """
164         cryptodev = Topology.get_cryptodev(self._node)
165         for i in range(count):
166             cryptodev_config = 'dev {0}'.format(
167                 re.sub(r'\d.\d$', '1.'+str(i), cryptodev))
168             path = ['dpdk', cryptodev_config]
169             self.add_config_item(self._nodeconfig, '', path)
170         self.add_dpdk_uio_driver('igb_uio')
171
172     def add_dpdk_dev_default_rxq(self, value):
173         """Add DPDK dev default rxq configuration.
174
175         :param value: Default number of rxqs.
176         :type value: str
177         """
178         path = ['dpdk', 'dev default', 'num-rx-queues']
179         self.add_config_item(self._nodeconfig, value, path)
180
181     def add_dpdk_dev_default_txq(self, value):
182         """Add DPDK dev default txq configuration.
183
184         :param value: Default number of txqs.
185         :type value: str
186         """
187         path = ['dpdk', 'dev default', 'num-tx-queues']
188         self.add_config_item(self._nodeconfig, value, path)
189
190     def add_dpdk_dev_default_rxd(self, value):
191         """Add DPDK dev default rxd configuration.
192
193         :param value: Default number of rxds.
194         :type value: str
195         """
196         path = ['dpdk', 'dev default', 'num-rx-desc']
197         self.add_config_item(self._nodeconfig, value, path)
198
199     def add_dpdk_dev_default_txd(self, value):
200         """Add DPDK dev default txd configuration.
201
202         :param value: Default number of txds.
203         :type value: str
204         """
205         path = ['dpdk', 'dev default', 'num-tx-desc']
206         self.add_config_item(self._nodeconfig, value, path)
207
208     def add_dpdk_log_level(self, value):
209         """Add DPDK log-level configuration.
210
211         :param value: Log level.
212         :type value: str
213         """
214         path = ['dpdk', 'log-level']
215         self.add_config_item(self._nodeconfig, value, path)
216
217     def add_dpdk_socketmem(self, value):
218         """Add DPDK socket memory configuration.
219
220         :param value: Socket memory size.
221         :type value: str
222         """
223         path = ['dpdk', 'socket-mem']
224         self.add_config_item(self._nodeconfig, value, path)
225
226     def add_dpdk_uio_driver(self, value):
227         """Add DPDK uio-driver configuration.
228
229         :param value: DPDK uio-driver configuration.
230         :type value: str
231         """
232         path = ['dpdk', 'uio-driver']
233         self.add_config_item(self._nodeconfig, value, path)
234
235     def add_cpu_main_core(self, value):
236         """Add CPU main core configuration.
237
238         :param value: Main core option.
239         :type value: str
240         """
241         path = ['cpu', 'main-core']
242         self.add_config_item(self._nodeconfig, value, path)
243
244     def add_cpu_corelist_workers(self, value):
245         """Add CPU corelist-workers configuration.
246
247         :param value: Corelist-workers option.
248         :type value: str
249         """
250         path = ['cpu', 'corelist-workers']
251         self.add_config_item(self._nodeconfig, value, path)
252
253     def add_heapsize(self, value):
254         """Add Heapsize configuration.
255
256         :param value: Amount of heapsize.
257         :type value: str
258         """
259         path = ['heapsize']
260         self.add_config_item(self._nodeconfig, value, path)
261
262     def add_api_trace(self):
263         """Add API trace configuration."""
264         path = ['api-trace', 'on']
265         self.add_config_item(self._nodeconfig, '', path)
266
267     def add_ip6_hash_buckets(self, value):
268         """Add IP6 hash buckets configuration.
269
270         :param value: Number of IP6 hash buckets.
271         :type value: str
272         """
273         path = ['ip6', 'hash-buckets']
274         self.add_config_item(self._nodeconfig, value, path)
275
276     def add_ip6_heap_size(self, value):
277         """Add IP6 heap-size configuration.
278
279         :param value: IP6 Heapsize amount.
280         :type value: str
281         """
282         path = ['ip6', 'heap-size']
283         self.add_config_item(self._nodeconfig, value, path)
284
285     def add_plugin_disable(self, *plugins):
286         """Add plugin disable for specific plugin.
287
288         :param plugins: Plugin(s) to disable.
289         :type plugins: list
290         """
291         for plugin in plugins:
292             path = ['plugins', 'plugin {0}'.format(plugin), 'disable']
293             self.add_config_item(self._nodeconfig, ' ', path)
294
295     def add_dpdk_no_multi_seg(self):
296         """Add DPDK no-multi-seg configuration."""
297         path = ['dpdk', 'no-multi-seg']
298         self.add_config_item(self._nodeconfig, '', path)
299
300     def add_nat(self, value='deterministic'):
301         """Add NAT configuration.
302
303         :param value: NAT mode.
304         :type value: str
305         """
306         path = ['nat']
307         self.add_config_item(self._nodeconfig, value, path)
308
309     def apply_config(self, filename='/etc/vpp/startup.conf', waittime=5,
310                      retries=12, restart_vpp=True):
311         """Generate and apply VPP configuration for node.
312
313         Use data from calls to this class to form a startup.conf file and
314         replace /etc/vpp/startup.conf with it on node.
315
316         :param filename: Startup configuration file name.
317         :param waittime: Time to wait for VPP to restart (default 5 seconds).
318         :param retries: Number of times (default 12) to re-try waiting.
319         :param restart_vpp: Whether to restart VPP.
320         :type filename: str
321         :type waittime: int
322         :type retries: int
323         :type restart_vpp: bool.
324         :raises RuntimeError: If writing config file failed, or restarting of
325         VPP failed.
326         """
327         self.dump_config(self._nodeconfig)
328
329         ssh = SSH()
330         ssh.connect(self._node)
331
332         (ret, _, _) = \
333             ssh.exec_command('echo "{config}" | sudo tee {filename}'.
334                              format(config=self._vpp_config,
335                                     filename=filename))
336
337         if ret != 0:
338             raise RuntimeError('Writing config file failed to node {}'.
339                                format(self._hostname))
340
341         if restart_vpp:
342             # Instead of restarting, we'll do separate start and stop
343             # actions. This way we don't care whether VPP was running
344             # to begin with.
345             ssh.exec_command('sudo service {} stop'
346                              .format(self._vpp_service_name))
347             (ret, _, _) = \
348                 ssh.exec_command('sudo service {} start'
349                                  .format(self._vpp_service_name))
350             if ret != 0:
351                 raise RuntimeError('Restarting VPP failed on node {}'.
352                                    format(self._hostname))
353
354             # Sleep <waittime> seconds, up to <retry> times,
355             # and verify if VPP is running.
356             for _ in range(retries):
357                 time.sleep(waittime)
358                 (ret, _, _) = \
359                     ssh.exec_command('echo show hardware-interfaces | '
360                                      'nc 0 5002 || echo "VPP not yet running"')
361                 if ret == 0:
362                     break
363             else:
364                 raise RuntimeError('VPP failed to restart on node {}'.
365                                    format(self._hostname))