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}
72 # End VPP configuration template.
75 class VppConfigGenerator(object):
76 """VPP Configuration File Generator"""
81 def add_pci_all_devices(self, node):
82 """Add all PCI devices from topology file to startup config
88 for port in node['interfaces'].keys():
89 pci_addr = Topology.get_interface_pci_addr(node, port)
91 self.add_pci_device(node, pci_addr)
93 def add_pci_device(self, node, *pci_devices):
94 """Add PCI device configuration for node.
96 :param node: DUT node.
97 :param pci_devices: PCI devices (format 0000:00:00.0 or 00:00.0)
99 :type pci_devices: tuple
102 if node['type'] != NodeType.DUT:
103 raise ValueError('Node type is not a DUT')
105 # Specific device was given.
106 hostname = Topology.get_node_hostname(node)
108 pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
109 "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
110 for pci_device in pci_devices:
111 if not pattern.match(pci_device):
112 raise ValueError('PCI address {} to be added to host {} '
113 'is not in valid format xxxx:xx:xx.x'.
114 format(pci_device, hostname))
116 if hostname not in self._nodeconfig:
117 self._nodeconfig[hostname] = {}
118 if 'pci_addrs' not in self._nodeconfig[hostname]:
119 self._nodeconfig[hostname]['pci_addrs'] = []
120 self._nodeconfig[hostname]['pci_addrs'].append(pci_device)
121 logger.debug('Adding PCI device {1} to {0}'.format(hostname,
124 def add_cpu_config(self, node, cpu_config):
125 """Add CPU configuration for node.
127 :param node: DUT node.
128 :param cpu_config: CPU configuration option, as a string.
130 :type cpu_config: str
133 if node['type'] != NodeType.DUT:
134 raise ValueError('Node type is not a DUT')
135 hostname = Topology.get_node_hostname(node)
136 if hostname not in self._nodeconfig:
137 self._nodeconfig[hostname] = {}
138 if 'cpu_config' not in self._nodeconfig[hostname]:
139 self._nodeconfig[hostname]['cpu_config'] = []
140 self._nodeconfig[hostname]['cpu_config'].append(cpu_config)
141 logger.debug('Adding {} to hostname {} CPU config'.format(hostname,
144 def add_socketmem_config(self, node, socketmem_config):
145 """Add Socket Memory configuration for node.
147 :param node: DUT node.
148 :param socketmem_config: Socket Memory configuration option,
151 :type socketmem_config: str
154 if node['type'] != NodeType.DUT:
155 raise ValueError('Node type is not a DUT')
156 hostname = Topology.get_node_hostname(node)
157 if hostname not in self._nodeconfig:
158 self._nodeconfig[hostname] = {}
159 self._nodeconfig[hostname]['socketmem_config'] = socketmem_config
160 logger.debug('Setting hostname {} Socket Memory config to {}'.
161 format(hostname, socketmem_config))
163 def add_heapsize_config(self, node, heapsize_config):
164 """Add Heap Size configuration for node.
166 :param node: DUT node.
167 :param heapsize_config: Heap Size configuration, as a string.
169 :type heapsize_config: str
172 if node['type'] != NodeType.DUT:
173 raise ValueError('Node type is not a DUT')
174 hostname = Topology.get_node_hostname(node)
175 if hostname not in self._nodeconfig:
176 self._nodeconfig[hostname] = {}
177 self._nodeconfig[hostname]['heapsize_config'] = heapsize_config
178 logger.debug('Setting hostname {} Heap Size config to {}'.
179 format(hostname, heapsize_config))
181 def add_rxqueues_config(self, node, rxqueues_config):
182 """Add Rx Queues configuration for node.
184 :param node: DUT node.
185 :param rxqueues_config: Rxqueues configuration, as a string.
187 :type rxqueues_config: str
190 if node['type'] != NodeType.DUT:
191 raise ValueError('Node type is not a DUT')
192 hostname = Topology.get_node_hostname(node)
193 if hostname not in self._nodeconfig:
194 self._nodeconfig[hostname] = {}
195 if 'rxqueues_config' not in self._nodeconfig[hostname]:
196 self._nodeconfig[hostname]['rxqueues_config'] = []
197 self._nodeconfig[hostname]['rxqueues_config'].append(rxqueues_config)
198 logger.debug('Setting hostname {} rxqueues config to {}'.
199 format(hostname, rxqueues_config))
201 def add_no_multi_seg_config(self, node):
202 """Add No Multi Seg configuration for node.
204 :param node: DUT node.
208 if node['type'] != NodeType.DUT:
209 raise ValueError('Node type is not a DUT')
210 hostname = Topology.get_node_hostname(node)
211 if hostname not in self._nodeconfig:
212 self._nodeconfig[hostname] = {}
213 if 'no_multi_seg_config' not in self._nodeconfig[hostname]:
214 self._nodeconfig[hostname]['no_multi_seg_config'] = []
215 self._nodeconfig[hostname]['no_multi_seg_config'].append("no-multi-seg")
216 logger.debug('Setting hostname {} config with {}'.
217 format(hostname, "no-multi-seg"))
219 def add_enable_vhost_user_config(self, node):
220 """Add enable-vhost-user configuration for node.
222 :param node: DUT node.
226 if node['type'] != NodeType.DUT:
227 raise ValueError('Node type is not a DUT')
228 hostname = Topology.get_node_hostname(node)
229 if hostname not in self._nodeconfig:
230 self._nodeconfig[hostname] = {}
231 if 'enable_vhost_user' not in self._nodeconfig[hostname]:
232 self._nodeconfig[hostname]['enable_vhost_user'] = []
233 self._nodeconfig[hostname]['enable_vhost_user'].\
234 append("enable-vhost-user")
235 logger.debug('Setting hostname {} config with {}'.
236 format(hostname, "enable-vhost-user"))
238 def add_snat_config(self, node):
239 """Add SNAT configuration for the node.
241 :param node: DUT node.
245 if node['type'] != NodeType.DUT:
246 raise ValueError('Node type is not a DUT')
247 hostname = Topology.get_node_hostname(node)
248 if hostname not in self._nodeconfig:
249 self._nodeconfig[hostname] = {}
250 if 'snat_config' not in self._nodeconfig[hostname]:
251 self._nodeconfig[hostname]['snat_config'] = []
252 self._nodeconfig[hostname]['snat_config'].append("deterministic")
253 logger.debug('Setting hostname {} config with {}'.
254 format(hostname, "SNAT"))
256 def add_cryptodev_config(self, node, count):
257 """Add cryptodev configuration for node.
259 :param node: DUT node.
260 :param count: Number of crypto device to add.
264 :raises ValueError: If node type is not a DUT
266 if node['type'] != NodeType.DUT:
267 raise ValueError('Node type is not a DUT')
268 hostname = Topology.get_node_hostname(node)
269 if hostname not in self._nodeconfig:
270 self._nodeconfig[hostname] = {}
272 cryptodev = Topology.get_cryptodev(node)
273 cryptodev_config = 'enable-cryptodev'
275 for i in range(count):
276 cryptodev_config += ' dev {}'.format(\
277 re.sub(r'\d.\d$', '1.'+str(i), cryptodev))
279 self._nodeconfig[hostname]['cryptodev_config'] = cryptodev_config
280 logger.debug('Setting hostname {} Cryptodev config to {}'.
281 format(hostname, cryptodev_config))
283 uio_driver_config = 'uio-driver {}'.\
284 format(Topology.get_uio_driver(node))
286 self._nodeconfig[hostname]['uio_driver_config'] = uio_driver_config
287 logger.debug('Setting hostname {} uio_driver config to {}'.
288 format(hostname, uio_driver_config))
290 def remove_all_pci_devices(self, node):
291 """Remove PCI device configuration from node.
293 :param node: DUT node.
297 if node['type'] != NodeType.DUT:
298 raise ValueError('Node type is not a DUT')
299 hostname = Topology.get_node_hostname(node)
300 if hostname in self._nodeconfig:
301 self._nodeconfig[hostname]['pci_addrs'] = []
302 logger.debug('Clearing all PCI devices for hostname {}.'.
305 def remove_all_cpu_config(self, node):
306 """Remove CPU configuration from node.
308 :param node: DUT node.
312 if node['type'] != NodeType.DUT:
313 raise ValueError('Node type is not a DUT')
314 hostname = Topology.get_node_hostname(node)
315 if hostname in self._nodeconfig:
316 self._nodeconfig[hostname]['cpu_config'] = []
317 logger.debug('Clearing all CPU config for hostname {}.'.
320 def remove_socketmem_config(self, node):
321 """Remove Socket Memory configuration from node.
323 :param node: DUT node.
327 if node['type'] != NodeType.DUT:
328 raise ValueError('Node type is not a DUT')
329 hostname = Topology.get_node_hostname(node)
330 if hostname in self._nodeconfig:
331 self._nodeconfig[hostname].pop('socketmem_config', None)
332 logger.debug('Clearing Socket Memory config for hostname {}.'.
335 def remove_cryptodev_config(self, node):
336 """Remove Cryptodev configuration from node.
338 :param node: DUT node.
341 :raises ValueError: If node type is not a DUT
343 if node['type'] != NodeType.DUT:
344 raise ValueError('Node type is not a DUT')
345 hostname = Topology.get_node_hostname(node)
346 if hostname in self._nodeconfig:
347 self._nodeconfig[hostname].pop('cryptodev_config', None)
348 logger.debug('Clearing Cryptodev config for hostname {}.'.
351 def remove_heapsize_config(self, node):
352 """Remove Heap Size configuration from node.
354 :param node: DUT node.
358 if node['type'] != NodeType.DUT:
359 raise ValueError('Node type is not a DUT')
360 hostname = Topology.get_node_hostname(node)
361 if hostname in self._nodeconfig:
362 self._nodeconfig[hostname].pop('heapsize_config', None)
363 logger.debug('Clearing Heap Size config for hostname {}.'.
366 def remove_rxqueues_config(self, node):
367 """Remove Rxqueues configuration from node.
369 :param node: DUT node.
373 if node['type'] != NodeType.DUT:
374 raise ValueError('Node type is not a DUT')
375 hostname = Topology.get_node_hostname(node)
376 if hostname in self._nodeconfig:
377 self._nodeconfig[hostname]['rxqueues_config'] = []
378 logger.debug('Clearing rxqueues config for hostname {}.'.
381 def remove_no_multi_seg_config(self, node):
382 """Remove No Multi Seg configuration from node.
384 :param node: DUT node.
388 if node['type'] != NodeType.DUT:
389 raise ValueError('Node type is not a DUT')
390 hostname = Topology.get_node_hostname(node)
391 if hostname in self._nodeconfig:
392 self._nodeconfig[hostname]['no_multi_seg_config'] = []
393 logger.debug('Clearing No Multi Seg config for hostname {}.'.
396 def remove_enable_vhost_user_config(self, node):
397 """Remove enable-vhost-user configuration from node.
399 :param node: DUT node.
403 if node['type'] != NodeType.DUT:
404 raise ValueError('Node type is not a DUT')
405 hostname = Topology.get_node_hostname(node)
406 if hostname in self._nodeconfig:
407 self._nodeconfig[hostname]['enable_vhost_user'] = []
408 logger.debug('Clearing enable-vhost-user config for hostname {}.'.
411 def remove_snat_config(self, node):
412 """Remove SNAT configuration for the node.
414 :param node: DUT node.
418 if node['type'] != NodeType.DUT:
419 raise ValueError('Node type is not a DUT')
420 hostname = Topology.get_node_hostname(node)
421 if hostname in self._nodeconfig:
422 self._nodeconfig[hostname]['snat_config'] = []
423 logger.debug('Clearing SNAT config for hostname {}.'.format(hostname))
425 def apply_config(self, node, waittime=5, retries=12):
426 """Generate and apply VPP configuration for node.
428 Use data from calls to this class to form a startup.conf file and
429 replace /etc/vpp/startup.conf with it on node.
431 :param node: DUT node.
432 :param waittime: Time to wait for VPP to restart (default 5 seconds).
433 :param retries: Number of times (default 12) to re-try waiting.
437 :raises RuntimeError: If writing config file failed, or restarting of
441 if node['type'] != NodeType.DUT:
442 raise ValueError('Node type is not a DUT')
443 hostname = Topology.get_node_hostname(node)
447 socketmemconfig = DEFAULT_SOCKETMEM_CONFIG
457 if hostname in self._nodeconfig:
458 cfg = self._nodeconfig[hostname]
459 if 'cpu_config' in cfg:
460 cpuconfig = " " + "\n ".join(cfg['cpu_config'])
462 if 'pci_addrs' in cfg:
463 pciconfig = " dev " + "\n dev ".join(cfg['pci_addrs'])
465 if 'socketmem_config' in cfg:
466 socketmemconfig = cfg['socketmem_config']
468 if 'cryptodev_config' in cfg:
469 cryptodevconfig = cfg['cryptodev_config']
471 if 'uio_driver_config' in cfg:
472 uiodriverconfig = cfg['uio_driver_config']
474 if 'heapsize_config' in cfg:
475 heapsizeconfig = "\nheapsize {}\n".\
476 format(cfg['heapsize_config'])
478 if 'rxqueues_config' in cfg:
479 rxqueuesconfig = " " + "\n ".join(cfg['rxqueues_config'])
481 if 'no_multi_seg_config' in cfg:
482 nomultiseg = " " + "\n ".join(cfg['no_multi_seg_config'])
484 if 'enable_vhost_user' in cfg:
485 enablevhostuser = " " + "\n ".join(cfg['enable_vhost_user'])
487 if 'snat_config' in cfg:
488 snatconfig = "snat {\n"
489 snatconfig += " " + "\n ".join(cfg['snat_config'])
492 vppconfig = VPP_CONFIG_TEMPLATE.format(cpuconfig=cpuconfig,
494 cryptodevconfig=cryptodevconfig,
495 uiodriverconfig=uiodriverconfig,
496 socketmemconfig=socketmemconfig,
497 heapsizeconfig=heapsizeconfig,
498 rxqueuesconfig=rxqueuesconfig,
499 txqueuesconfig=txqueuesconfig,
500 nomultiseg=nomultiseg,
501 enablevhostuser=enablevhostuser,
502 snatconfig=snatconfig)
504 logger.debug('Writing VPP config to host {}: "{}"'.format(hostname,
510 # We're using this "| sudo tee" construct because redirecting
511 # a sudo'd outut ("sudo echo xxx > /path/to/file") does not
512 # work on most platforms...
513 (ret, stdout, stderr) = \
514 ssh.exec_command('echo "{0}" | sudo tee {1}'.
515 format(vppconfig, VPP_CONFIG_FILENAME))
518 logger.debug('Writing config file failed to node {}'.
520 logger.debug('stdout: {}'.format(stdout))
521 logger.debug('stderr: {}'.format(stderr))
522 raise RuntimeError('Writing config file failed to node {}'.
525 # Instead of restarting, we'll do separate start and stop
526 # actions. This way we don't care whether VPP was running
528 ssh.exec_command('sudo service {} stop'.format(VPP_SERVICE_NAME))
529 (ret, stdout, stderr) = \
530 ssh.exec_command('sudo service {} start'.format(VPP_SERVICE_NAME))
532 logger.debug('Restarting VPP failed on node {}'.
534 logger.debug('stdout: {}'.format(stdout))
535 logger.debug('stderr: {}'.format(stderr))
536 raise RuntimeError('Restarting VPP failed on node {}'.
539 # Sleep <waittime> seconds, up to <retry> times,
540 # and verify if VPP is running.
541 vpp_is_running = False
542 retries_left = retries
543 while (not vpp_is_running) and (retries_left > 0):
547 # FIXME: Need to find a good way to check if VPP is operational.
549 # If VatTerminal/VatExecutor is anything like vppctl or
550 # vpp_api_test, then in case VPP is NOT running it will block for
551 # 30 seconds or so and not even return if VPP becomes alive during
552 # that time. This makes it unsuitable in this case. We either need
553 # a call that returns immediately, indicating whether VPP is
554 # healthy or not, or a call that waits (up to a defined length
555 # of time) and returns immediately if VPP is or becomes healthy.
556 (ret, stdout, stderr) = \
557 ssh.exec_command('echo show hardware-interfaces | '
560 vpp_is_running = True
562 logger.debug('VPP not yet running, {} retries left'.
563 format(retries_left))
564 if retries_left == 0:
565 raise RuntimeError('VPP failed to restart on node {}'.
567 logger.debug('VPP interfaces found on node {}'.