039f629309df4f99bc798ff3f6296c1f51b153bb
[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 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
75         if len(path) == 1:
76             config[path[0]] = value
77             return
78         if not config.has_key(path[0]):
79             config[path[0]] = {}
80         self.add_config_item(config[path[0]], value, path[1:])
81
82     def dump_config(self, obj, level=-1):
83         """Dump the startup configuration in VPP config format.
84
85         :param obj: Python Object to print.
86         :param nested_level: Nested level for indentation.
87         :type obj: Obj
88         :type nested_level: int
89         :returns: nothing
90         """
91         indent = '  '
92         if level >= 0:
93             self._vpp_config += '{}{{\n'.format((level) * indent)
94         if isinstance(obj, dict):
95             for key, val in obj.items():
96                 if hasattr(val, '__iter__'):
97                     self._vpp_config += '{}{}\n'.format((level + 1) * indent,
98                                                         key)
99                     self.dump_config(val, level + 1)
100                 else:
101                     self._vpp_config += '{}{} {}\n'.format((level + 1) * indent,
102                                                            key, val)
103         else:
104             for val in obj:
105                 self._vpp_config += '{}{}\n'.format((level + 1) * indent, val)
106         if level >= 0:
107             self._vpp_config += '{}}}\n'.format(level * indent)
108
109     def add_unix_log(self, value='/tmp/vpe.log'):
110         """Add UNIX log configuration.
111
112         :param value: Log file.
113         :type value: str
114         """
115         path = ['unix', 'log']
116         self.add_config_item(self._nodeconfig, value, path)
117
118     def add_unix_cli_listen(self, value='localhost:5002'):
119         """Add UNIX cli-listen configuration.
120
121         :param value: CLI listen address and port.
122         :type value: str
123         """
124         path = ['unix', 'cli-listen']
125         self.add_config_item(self._nodeconfig, value, path)
126         self._nodeconfig = {'unix': {'nodaemon': ''}}
127
128     def add_unix_nodaemon(self):
129         """Add UNIX nodaemon configuration."""
130         path = ['unix', 'nodaemon']
131         self.add_config_item(self._nodeconfig, '', path)
132
133     def add_dpdk_dev(self, *devices):
134         """Add DPDK PCI device configuration.
135
136         :param devices: PCI device(s) (format xxxx:xx:xx.x)
137         :type devices: tuple
138         :raises ValueError: If PCI address format is not valid.
139         """
140
141         pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
142                              "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
143         for device in devices:
144             if not pattern.match(device):
145                 raise ValueError('PCI address {} to be added to host {} '
146                                  'is not in valid format xxxx:xx:xx.x'.
147                                  format(device, self._hostname))
148             path = ['dpdk', 'dev {0}'.format(device)]
149             self.add_config_item(self._nodeconfig, '', path)
150
151     def add_dpdk_cryptodev(self, count):
152         """Add DPDK Crypto PCI device configuration.
153
154         :param count: Number of crypto devices to add.
155         :type count: int
156         """
157         cryptodev = Topology.get_cryptodev(self._node)
158         for i in range(count):
159             cryptodev_config = 'dev {0}'.format(
160                 re.sub(r'\d.\d$', '1.'+str(i), cryptodev))
161             path = ['dpdk', cryptodev_config]
162             self.add_config_item(self._nodeconfig, '', path)
163         self.add_dpdk_uio_driver('igb_uio')
164
165     def add_dpdk_dev_default_rxq(self, value):
166         """Add DPDK dev default rxq configuration.
167
168         :param value: Default number of rxqs.
169         :type value: str
170         """
171         path = ['dpdk', 'dev default', 'num-rx-queues']
172         self.add_config_item(self._nodeconfig, value, path)
173
174     def add_dpdk_dev_default_txq(self, value):
175         """Add DPDK dev default txq configuration.
176
177         :param value: Default number of txqs.
178         :type value: str
179         """
180         path = ['dpdk', 'dev default', 'num-tx-queues']
181         self.add_config_item(self._nodeconfig, value, path)
182
183     def add_dpdk_socketmem(self, value):
184         """Add DPDK socket memory configuration.
185
186         :param value: Socket memory size.
187         :type value: str
188         """
189         path = ['dpdk', 'socket-mem']
190         self.add_config_item(self._nodeconfig, value, path)
191
192     def add_dpdk_uio_driver(self, value):
193         """Add DPDK uio-driver configuration.
194
195         :param value: DPDK uio-driver configuration.
196         :type value: str
197         """
198         path = ['dpdk', 'uio-driver']
199         self.add_config_item(self._nodeconfig, value, path)
200
201     def add_cpu_main_core(self, value):
202         """Add CPU main core configuration.
203
204         :param value: Main core option.
205         :type value: str
206         """
207         path = ['cpu', 'main-core']
208         self.add_config_item(self._nodeconfig, value, path)
209
210     def add_cpu_corelist_workers(self, value):
211         """Add CPU corelist-workers configuration.
212
213         :param value: Corelist-workers option.
214         :type value: str
215         """
216         path = ['cpu', 'corelist-workers']
217         self.add_config_item(self._nodeconfig, value, path)
218
219     def add_heapsize(self, value):
220         """Add Heapsize configuration.
221
222         :param value: Amount of heapsize.
223         :type value: str
224         """
225         path = ['heapsize']
226         self.add_config_item(self._nodeconfig, value, path)
227
228     def add_api_trace(self):
229         """Add API trace configuration."""
230         path = ['api-trace', 'on']
231         self.add_config_item(self._nodeconfig, path, '')
232
233     def add_ip6_hash_buckets(self, value):
234         """Add IP6 hash buckets configuration.
235
236         :param value: Number of IP6 hash buckets.
237         :type value: str
238         """
239         path = ['ip6', 'hash-buckets']
240         self.add_config_item(self._nodeconfig, value, path)
241
242     def add_ip6_heap_size(self, value):
243         """Add IP6 heap-size configuration.
244
245         :param value: IP6 Heapsize amount.
246         :type value: str
247         """
248         path = ['ip6', 'heap-size']
249         self.add_config_item(self._nodeconfig, value, path)
250
251     def add_dpdk_no_multi_seg(self):
252         """Add DPDK no-multi-seg configuration."""
253         path = ['dpdk', 'no-multi-seg']
254         self.add_config_item(self._nodeconfig, '', path)
255
256     def add_snat(self, value='deterministic'):
257         """Add SNAT configuration.
258
259         :param value: SNAT mode.
260         :type value: str
261         """
262         path = ['snat']
263         self.add_config_item(self._nodeconfig, value, path)
264
265     def apply_config(self, waittime=5, retries=12):
266         """Generate and apply VPP configuration for node.
267
268         Use data from calls to this class to form a startup.conf file and
269         replace /etc/vpp/startup.conf with it on node.
270
271         :param waittime: Time to wait for VPP to restart (default 5 seconds).
272         :param retries: Number of times (default 12) to re-try waiting.
273         :type waittime: int
274         :type retries: int
275         :raises RuntimeError: If writing config file failed, or restarting of
276         VPP failed.
277         """
278         self.dump_config(self._nodeconfig)
279
280         ssh = SSH()
281         ssh.connect(self._node)
282
283         # We're using this "| sudo tee" construct because redirecting
284         # a sudo's output ("sudo echo xxx > /path/to/file") does not
285         # work on most platforms...
286         (ret, _, _) = \
287             ssh.exec_command('echo "{0}" | sudo tee {1}'.
288                              format(self._vpp_config,
289                                     self._vpp_config_filename))
290
291         if ret != 0:
292             raise RuntimeError('Writing config file failed to node {}'.
293                                format(self._hostname))
294
295         # Instead of restarting, we'll do separate start and stop
296         # actions. This way we don't care whether VPP was running
297         # to begin with.
298         ssh.exec_command('sudo service {} stop'
299                          .format(self._vpp_service_name))
300         (ret, _, _) = \
301             ssh.exec_command('sudo service {} start'
302                              .format(self._vpp_service_name))
303         if ret != 0:
304             raise RuntimeError('Restarting VPP failed on node {}'.
305                                format(self._hostname))
306
307         # Sleep <waittime> seconds, up to <retry> times,
308         # and verify if VPP is running.
309         for _ in range(retries):
310             time.sleep(waittime)
311             (ret, _, _) = \
312                 ssh.exec_command('echo show hardware-interfaces | '
313                                  'nc 0 5002 || echo "VPP not yet running"')
314             if ret == 0:
315                 break
316         else:
317             raise RuntimeError('VPP failed to restart on node {}'.
318                                format(self._hostname))