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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """VPP Configuration File Generator library"""
19 from robot.api import logger
21 from resources.libraries.python.ssh import SSH
22 from resources.libraries.python.topology import NodeType
23 from resources.libraries.python.topology import Topology
25 __all__ = ['VppConfigGenerator']
28 # VPP configuration template.
29 # TODO: Do we need a better place for this? Somewhere in an external
31 # Note: We're going to pass this through Python string Formatter, so
32 # any literal curly braces need to be escaped.
34 VPP_SERVICE_NAME = "vpp"
35 VPP_CONFIG_FILENAME = "/etc/vpp/startup.conf"
36 DEFAULT_SOCKETMEM_CONFIG = "1024,1024"
37 VPP_CONFIG_TEMPLATE = """
41 cli-listen localhost:5002
54 socket-mem {socketmemconfig}
63 # End VPP configuration template.
66 class VppConfigGenerator(object):
67 """VPP Configuration File Generator"""
72 def add_pci_all_devices(self, node):
73 """Add all PCI devices from topology file to startup config
79 for port in node['interfaces'].keys():
80 pci_addr = Topology.get_interface_pci_addr(node, port)
82 self.add_pci_device(node, pci_addr)
85 def add_pci_device(self, node, *pci_devices):
86 """Add PCI device configuration for node.
88 :param node: DUT node.
89 :param pci_device: PCI devices (format 0000:00:00.0 or 00:00.0)
91 :type pci_devices: tuple
94 if node['type'] != NodeType.DUT:
95 raise ValueError('Node type is not a DUT')
97 # Specific device was given.
98 hostname = Topology.get_node_hostname(node)
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))
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,
116 def add_cpu_config(self, node, cpu_config):
117 """Add CPU configuration for node.
119 :param node: DUT node.
120 :param cpu_config: CPU configuration option, as a string.
122 :type cpu_config: str
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,
136 def add_socketmem_config(self, node, socketmem_config):
137 """Add Socket Memory configuration for node.
139 :param node: DUT node.
140 :param socketmem_config: Socket Memory configuration option,
143 :type socketmem_config: str
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))
155 def add_heapsize_config(self, node, heapsize_config):
156 """Add Heap Size configuration for node.
158 :param node: DUT node.
159 :param heapsize_config: Heap Size configuration, as a string.
161 :type heapsize_config: str
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))
173 def add_rxqueues_config(self, node, rxqueues_config):
174 """Add Rx Queues configuration for node.
176 :param node: DUT node.
177 :param rxqueues_config: Rxqueues configuration, as a string.
179 :type rxqueues_config: str
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))
193 def add_no_multi_seg_config(self, node):
194 """Add No Multi Seg configuration for node.
196 :param node: DUT node.
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(
209 logger.debug('Setting hostname {} config with {}'.\
210 format(hostname, "no-multi-seg"))
212 def remove_all_pci_devices(self, node):
213 """Remove PCI device configuration from node.
215 :param node: DUT node.
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 {}.'.
227 def remove_all_cpu_config(self, node):
228 """Remove CPU configuration from node.
230 :param node: DUT node.
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 {}.'.
242 def remove_socketmem_config(self, node):
243 """Remove Socket Memory configuration from node.
245 :param node: DUT node.
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 {}.'.
257 def remove_heapsize_config(self, node):
258 """Remove Heap Size configuration from node.
260 :param node: DUT node.
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 {}.'.
272 def remove_rxqueues_config(self, node):
273 """Remove Rxqueues configuration from node.
275 :param node: DUT node.
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 {}.'.\
287 def remove_no_multi_seg_config(self, node):
288 """Remove No Multi Seg configuration from node.
290 :param node: DUT node.
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 {}.'.\
302 def apply_config(self, node, waittime=5, retries=12):
303 """Generate and apply VPP configuration for node.
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.
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.
316 if node['type'] != NodeType.DUT:
317 raise ValueError('Node type is not a DUT')
318 hostname = Topology.get_node_hostname(node)
322 socketmemconfig = DEFAULT_SOCKETMEM_CONFIG
328 if hostname in self._nodeconfig:
329 cfg = self._nodeconfig[hostname]
330 if 'cpu_config' in cfg:
331 cpuconfig = " " + "\n ".join(cfg['cpu_config'])
333 if 'pci_addrs' in cfg:
334 pciconfig = " dev " + "\n dev ".join(cfg['pci_addrs'])
336 if 'socketmem_config' in cfg:
337 socketmemconfig = cfg['socketmem_config']
339 if 'heapsize_config' in cfg:
340 heapsizeconfig = "\nheapsize {}\n".\
341 format(cfg['heapsize_config'])
343 if 'rxqueues_config' in cfg:
344 rxqueuesconfig = " " + "\n ".join(cfg['rxqueues_config'])
346 if 'no_multi_seg_config' in cfg:
347 nomultiseg = " " + "\n ".join(cfg['no_multi_seg_config'])
349 vppconfig = VPP_CONFIG_TEMPLATE.format(cpuconfig=cpuconfig,
351 socketmemconfig=socketmemconfig,
352 heapsizeconfig=heapsizeconfig,
353 rxqueuesconfig=rxqueuesconfig,
354 txqueuesconfig=txqueuesconfig,
355 nomultiseg = nomultiseg)
357 logger.debug('Writing VPP config to host {}: "{}"'.format(hostname,
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))
371 logger.debug('Writing config file failed to node {}'.
373 logger.debug('stdout: {}'.format(stdout))
374 logger.debug('stderr: {}'.format(stderr))
375 raise RuntimeError('Writing config file failed to node {}'.
378 # Instead of restarting, we'll do separate start and stop
379 # actions. This way we don't care whether VPP was running
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))
385 logger.debug('Restarting VPP failed on node {}'.
387 logger.debug('stdout: {}'.format(stdout))
388 logger.debug('stderr: {}'.format(stderr))
389 raise RuntimeError('Restarting VPP failed on node {}'.
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):
400 # FIXME: Need to find a good way to check if VPP is operational.
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 | '
414 vpp_is_running = True
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 {}'.
421 logger.debug('VPP interfaces found on node {}'.