CSIT-106 Cleanup of add_pci functions
[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 robot.api import logger
20
21 from resources.libraries.python.ssh import SSH
22 from resources.libraries.python.topology import NodeType
23 from resources.libraries.python.topology import Topology
24
25 __all__ = ['VppConfigGenerator']
26
27 #
28 # VPP configuration template.
29 # TODO: Do we need a better place for this? Somewhere in an external
30 # (template) file?
31 # Note: We're going to pass this through Python string Formatter, so
32 # any literal curly braces need to be escaped.
33 #
34 VPP_SERVICE_NAME = "vpp"
35 VPP_CONFIG_FILENAME = "/etc/vpp/startup.conf"
36 DEFAULT_SOCKETMEM_CONFIG = "1024,1024"
37 VPP_CONFIG_TEMPLATE = """
38 unix {{
39   nodaemon
40   log /tmp/vpe.log
41   cli-listen localhost:5002
42   full-coredump
43 }}
44
45 api-trace {{
46   on
47 }}
48 {heapsizeconfig}
49 cpu {{
50 {cpuconfig}
51 }}
52
53 dpdk {{
54   socket-mem {socketmemconfig}
55   dev default {{
56   {rxqueuesconfig}
57   {txqueuesconfig}
58   }}
59 {pciconfig}
60 {nomultiseg}
61 }}
62 """
63 # End VPP configuration template.
64
65
66 class VppConfigGenerator(object):
67     """VPP Configuration File Generator"""
68
69     def __init__(self):
70         self._nodeconfig = {}
71
72     def add_pci_all_devices(self, node):
73         """Add all PCI devices from topology file to startup config
74
75         :param node: DUT node
76         :type node: dict
77         :return: nothing
78         """
79         for port in node['interfaces'].keys():
80             pci_addr = Topology.get_interface_pci_addr(node, port)
81             if pci_addr:
82                 self.add_pci_device(node, pci_addr)
83
84
85     def add_pci_device(self, node, *pci_devices):
86         """Add PCI device configuration for node.
87
88         :param node: DUT node.
89         :param pci_device: PCI devices (format 0000:00:00.0 or 00:00.0)
90         :type node: dict
91         :type pci_devices: tuple
92         :return: nothing
93         """
94         if node['type'] != NodeType.DUT:
95             raise ValueError('Node type is not a DUT')
96
97         # Specific device was given.
98         hostname = Topology.get_node_hostname(node)
99
100         pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
101                              "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
102         for pci_device in pci_devices:
103             if not pattern.match(pci_device):
104                 raise ValueError('PCI address {} to be added to host {} '
105                                  'is not in valid format xxxx:xx:xx.x'.
106                                  format(pci_device, hostname))
107
108             if hostname not in self._nodeconfig:
109                 self._nodeconfig[hostname] = {}
110             if 'pci_addrs' not in self._nodeconfig[hostname]:
111                 self._nodeconfig[hostname]['pci_addrs'] = []
112             self._nodeconfig[hostname]['pci_addrs'].append(pci_device)
113             logger.debug('Adding PCI device {1} to {0}'.format(hostname,
114                                                                pci_device))
115
116     def add_cpu_config(self, node, cpu_config):
117         """Add CPU configuration for node.
118
119         :param node: DUT node.
120         :param cpu_config: CPU configuration option, as a string.
121         :type node: dict
122         :type cpu_config: str
123         :return: nothing
124         """
125         if node['type'] != NodeType.DUT:
126             raise ValueError('Node type is not a DUT')
127         hostname = Topology.get_node_hostname(node)
128         if hostname not in self._nodeconfig:
129             self._nodeconfig[hostname] = {}
130         if 'cpu_config' not in self._nodeconfig[hostname]:
131             self._nodeconfig[hostname]['cpu_config'] = []
132         self._nodeconfig[hostname]['cpu_config'].append(cpu_config)
133         logger.debug('Adding {} to hostname {} CPU config'.format(hostname,
134                                                                   cpu_config))
135
136     def add_socketmem_config(self, node, socketmem_config):
137         """Add Socket Memory configuration for node.
138
139         :param node: DUT node.
140         :param socketmem_config: Socket Memory configuration option,
141         as a string.
142         :type node: dict
143         :type socketmem_config: str
144         :return: nothing
145         """
146         if node['type'] != NodeType.DUT:
147             raise ValueError('Node type is not a DUT')
148         hostname = Topology.get_node_hostname(node)
149         if hostname not in self._nodeconfig:
150             self._nodeconfig[hostname] = {}
151         self._nodeconfig[hostname]['socketmem_config'] = socketmem_config
152         logger.debug('Setting hostname {} Socket Memory config to {}'.
153                      format(hostname, socketmem_config))
154
155     def add_heapsize_config(self, node, heapsize_config):
156         """Add Heap Size configuration for node.
157
158         :param node: DUT node.
159         :param heapsize_config: Heap Size configuration, as a string.
160         :type node: dict
161         :type heapsize_config: str
162         :return: nothing
163         """
164         if node['type'] != NodeType.DUT:
165             raise ValueError('Node type is not a DUT')
166         hostname = Topology.get_node_hostname(node)
167         if hostname not in self._nodeconfig:
168             self._nodeconfig[hostname] = {}
169         self._nodeconfig[hostname]['heapsize_config'] = heapsize_config
170         logger.debug('Setting hostname {} Heap Size config to {}'.
171                      format(hostname, heapsize_config))
172
173     def add_rxqueues_config(self, node, rxqueues_config):
174         """Add Rx Queues configuration for node.
175
176         :param node: DUT node.
177         :param rxqueues_config: Rxqueues configuration, as a string.
178         :type node: dict
179         :type rxqueues_config: str
180         :return: nothing
181         """
182         if node['type'] != NodeType.DUT:
183             raise ValueError('Node type is not a DUT')
184         hostname = Topology.get_node_hostname(node)
185         if not hostname in self._nodeconfig:
186             self._nodeconfig[hostname] = {}
187         if not 'rxqueues_config' in self._nodeconfig[hostname]:
188             self._nodeconfig[hostname]['rxqueues_config'] = []
189         self._nodeconfig[hostname]['rxqueues_config'].append(rxqueues_config)
190         logger.debug('Setting hostname {} rxqueues config to {}'.\
191             format(hostname, rxqueues_config))
192
193     def add_no_multi_seg_config(self, node):
194         """Add No Multi Seg configuration for node.
195
196         :param node: DUT node.
197         :type node: dict
198         :return: nothing
199         """
200         if node['type'] != NodeType.DUT:
201             raise ValueError('Node type is not a DUT')
202         hostname = Topology.get_node_hostname(node)
203         if not hostname in self._nodeconfig:
204             self._nodeconfig[hostname] = {}
205         if not 'no_multi_seg_config' in self._nodeconfig[hostname]:
206             self._nodeconfig[hostname]['no_multi_seg_config'] = []
207         self._nodeconfig[hostname]['no_multi_seg_config'].append(
208             "no-multi-seg")
209         logger.debug('Setting hostname {} config with {}'.\
210             format(hostname, "no-multi-seg"))
211
212     def remove_all_pci_devices(self, node):
213         """Remove PCI device configuration from node.
214
215         :param node: DUT node.
216         :type node: dict
217         :return: nothing
218         """
219         if node['type'] != NodeType.DUT:
220             raise ValueError('Node type is not a DUT')
221         hostname = Topology.get_node_hostname(node)
222         if hostname in self._nodeconfig:
223             self._nodeconfig[hostname]['pci_addrs'] = []
224         logger.debug('Clearing all PCI devices for hostname {}.'.
225                      format(hostname))
226
227     def remove_all_cpu_config(self, node):
228         """Remove CPU configuration from node.
229
230         :param node: DUT node.
231         :type node: dict
232         :return: nothing
233         """
234         if node['type'] != NodeType.DUT:
235             raise ValueError('Node type is not a DUT')
236         hostname = Topology.get_node_hostname(node)
237         if hostname in self._nodeconfig:
238             self._nodeconfig[hostname]['cpu_config'] = []
239         logger.debug('Clearing all CPU config for hostname {}.'.
240                      format(hostname))
241
242     def remove_socketmem_config(self, node):
243         """Remove Socket Memory configuration from node.
244
245         :param node: DUT node.
246         :type node: dict
247         :return: nothing
248         """
249         if node['type'] != NodeType.DUT:
250             raise ValueError('Node type is not a DUT')
251         hostname = Topology.get_node_hostname(node)
252         if hostname in self._nodeconfig:
253             self._nodeconfig[hostname].pop('socketmem_config', None)
254         logger.debug('Clearing Socket Memory config for hostname {}.'.
255                      format(hostname))
256
257     def remove_heapsize_config(self, node):
258         """Remove Heap Size configuration from node.
259
260         :param node: DUT node.
261         :type node: dict
262         :return: nothing
263         """
264         if node['type'] != NodeType.DUT:
265             raise ValueError('Node type is not a DUT')
266         hostname = Topology.get_node_hostname(node)
267         if hostname in self._nodeconfig:
268             self._nodeconfig[hostname].pop('heapsize_config', None)
269         logger.debug('Clearing Heap Size config for hostname {}.'.
270                      format(hostname))
271
272     def remove_rxqueues_config(self, node):
273         """Remove Rxqueues configuration from node.
274
275         :param node: DUT node.
276         :type node: dict
277         :return: nothing
278         """
279         if node['type'] != NodeType.DUT:
280             raise ValueError('Node type is not a DUT')
281         hostname = Topology.get_node_hostname(node)
282         if hostname in self._nodeconfig:
283             self._nodeconfig[hostname]['rxqueues_config'] = []
284         logger.debug('Clearing rxqueues config for hostname {}.'.\
285             format(hostname))
286
287     def remove_no_multi_seg_config(self, node):
288         """Remove No Multi Seg configuration from node.
289
290         :param node: DUT node.
291         :type node: dict
292         :return: nothing
293         """
294         if node['type'] != NodeType.DUT:
295             raise ValueError('Node type is not a DUT')
296         hostname = Topology.get_node_hostname(node)
297         if hostname in self._nodeconfig:
298             self._nodeconfig[hostname]['no_multi_seg_config'] = []
299         logger.debug('Clearing No Multi Seg config for hostname {}.'.\
300             format(hostname))
301
302     def apply_config(self, node, waittime=5, retries=12):
303         """Generate and apply VPP configuration for node.
304
305         Use data from calls to this class to form a startup.conf file and
306         replace /etc/vpp/startup.conf with it on node.
307
308         :param node: DUT node.
309         :param waittime: Time to wait for VPP to restart (default 5 seconds).
310         :param retries: Number of times (default 12) to re-try waiting.
311         :type node: dict
312         :type waittime: int
313         :type retries: int
314         """
315
316         if node['type'] != NodeType.DUT:
317             raise ValueError('Node type is not a DUT')
318         hostname = Topology.get_node_hostname(node)
319
320         cpuconfig = ""
321         pciconfig = ""
322         socketmemconfig = DEFAULT_SOCKETMEM_CONFIG
323         heapsizeconfig = ""
324         rxqueuesconfig = ""
325         txqueuesconfig = ""
326         nomultiseg = ""
327
328         if hostname in self._nodeconfig:
329             cfg = self._nodeconfig[hostname]
330             if 'cpu_config' in cfg:
331                 cpuconfig = "  " + "\n  ".join(cfg['cpu_config'])
332
333             if 'pci_addrs' in cfg:
334                 pciconfig = "  dev " + "\n  dev ".join(cfg['pci_addrs'])
335
336             if 'socketmem_config' in cfg:
337                 socketmemconfig = cfg['socketmem_config']
338
339             if 'heapsize_config' in cfg:
340                 heapsizeconfig = "\nheapsize {}\n".\
341                     format(cfg['heapsize_config'])
342
343             if 'rxqueues_config' in cfg:
344                 rxqueuesconfig = "  " + "\n  ".join(cfg['rxqueues_config'])
345
346             if 'no_multi_seg_config' in cfg:
347                 nomultiseg = "  " + "\n  ".join(cfg['no_multi_seg_config'])
348
349         vppconfig = VPP_CONFIG_TEMPLATE.format(cpuconfig=cpuconfig,
350                                                pciconfig=pciconfig,
351                                                socketmemconfig=socketmemconfig,
352                                                heapsizeconfig=heapsizeconfig,
353                                                rxqueuesconfig=rxqueuesconfig,
354                                                txqueuesconfig=txqueuesconfig,
355                                                nomultiseg = nomultiseg)
356
357         logger.debug('Writing VPP config to host {}: "{}"'.format(hostname,
358                                                                   vppconfig))
359
360         ssh = SSH()
361         ssh.connect(node)
362
363         # We're using this "| sudo tee" construct because redirecting
364         # a sudo'd outut ("sudo echo xxx > /path/to/file") does not
365         # work on most platforms...
366         (ret, stdout, stderr) = \
367             ssh.exec_command('echo "{0}" | sudo tee {1}'.
368                              format(vppconfig, VPP_CONFIG_FILENAME))
369
370         if ret != 0:
371             logger.debug('Writing config file failed to node {}'.
372                          format(hostname))
373             logger.debug('stdout: {}'.format(stdout))
374             logger.debug('stderr: {}'.format(stderr))
375             raise RuntimeError('Writing config file failed to node {}'.
376                                format(hostname))
377
378         # Instead of restarting, we'll do separate start and stop
379         # actions. This way we don't care whether VPP was running
380         # to begin with.
381         ssh.exec_command('sudo initctl stop {}'.format(VPP_SERVICE_NAME))
382         (ret, stdout, stderr) = \
383             ssh.exec_command('sudo initctl start {}'.format(VPP_SERVICE_NAME))
384         if ret != 0:
385             logger.debug('Restarting VPP failed on node {}'.
386                          format(hostname))
387             logger.debug('stdout: {}'.format(stdout))
388             logger.debug('stderr: {}'.format(stderr))
389             raise RuntimeError('Restarting VPP failed on node {}'.
390                                format(hostname))
391
392         # Sleep <waittime> seconds, up to <retry> times,
393         # and verify if VPP is running.
394         vpp_is_running = False
395         retries_left = retries
396         while (not vpp_is_running) and (retries_left > 0):
397             time.sleep(waittime)
398             retries_left -= 1
399
400             # FIXME: Need to find a good way to check if VPP is operational.
401             #
402             # If VatTerminal/VatExecutor is anything like vppctl or
403             # vpp_api_test, then in case VPP is NOT running it will block for
404             # 30 seconds or so and not even return if VPP becomes alive during
405             # that time. This makes it unsuitable in this case. We either need
406             # a call that returns immediately, indicating whether VPP is
407             # healthy or not, or a call that waits (up to a defined length
408             # of time) and returns immediately if VPP is or becomes healthy.
409             (ret, stdout, stderr) = \
410                 ssh.exec_command('echo show hardware-interfaces | '
411                                  'nc 0 5002')
412
413             if ret == 0:
414                 vpp_is_running = True
415             else:
416                 logger.debug('VPP not yet running, {} retries left'.
417                              format(retries_left))
418         if retries_left == 0:
419             raise RuntimeError('VPP failed to restart on node {}'.
420                                format(hostname))
421         logger.debug('VPP interfaces found on node {}'.
422                      format(stdout))