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