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)
94 def add_pci_device(self, node, *pci_devices):
95 """Add PCI device configuration for node.
97 :param node: DUT node.
98 :param pci_device: PCI devices (format 0000:00:00.0 or 00:00.0)
100 :type pci_devices: tuple
103 if node['type'] != NodeType.DUT:
104 raise ValueError('Node type is not a DUT')
106 # Specific device was given.
107 hostname = Topology.get_node_hostname(node)
109 pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
110 "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
111 for pci_device in pci_devices:
112 if not pattern.match(pci_device):
113 raise ValueError('PCI address {} to be added to host {} '
114 'is not in valid format xxxx:xx:xx.x'.
115 format(pci_device, hostname))
117 if hostname not in self._nodeconfig:
118 self._nodeconfig[hostname] = {}
119 if 'pci_addrs' not in self._nodeconfig[hostname]:
120 self._nodeconfig[hostname]['pci_addrs'] = []
121 self._nodeconfig[hostname]['pci_addrs'].append(pci_device)
122 logger.debug('Adding PCI device {1} to {0}'.format(hostname,
125 def add_cpu_config(self, node, cpu_config):
126 """Add CPU configuration for node.
128 :param node: DUT node.
129 :param cpu_config: CPU configuration option, as a string.
131 :type cpu_config: str
134 if node['type'] != NodeType.DUT:
135 raise ValueError('Node type is not a DUT')
136 hostname = Topology.get_node_hostname(node)
137 if hostname not in self._nodeconfig:
138 self._nodeconfig[hostname] = {}
139 if 'cpu_config' not in self._nodeconfig[hostname]:
140 self._nodeconfig[hostname]['cpu_config'] = []
141 self._nodeconfig[hostname]['cpu_config'].append(cpu_config)
142 logger.debug('Adding {} to hostname {} CPU config'.format(hostname,
145 def add_socketmem_config(self, node, socketmem_config):
146 """Add Socket Memory configuration for node.
148 :param node: DUT node.
149 :param socketmem_config: Socket Memory configuration option,
152 :type socketmem_config: str
155 if node['type'] != NodeType.DUT:
156 raise ValueError('Node type is not a DUT')
157 hostname = Topology.get_node_hostname(node)
158 if hostname not in self._nodeconfig:
159 self._nodeconfig[hostname] = {}
160 self._nodeconfig[hostname]['socketmem_config'] = socketmem_config
161 logger.debug('Setting hostname {} Socket Memory config to {}'.
162 format(hostname, socketmem_config))
164 def add_heapsize_config(self, node, heapsize_config):
165 """Add Heap Size configuration for node.
167 :param node: DUT node.
168 :param heapsize_config: Heap Size configuration, as a string.
170 :type heapsize_config: str
173 if node['type'] != NodeType.DUT:
174 raise ValueError('Node type is not a DUT')
175 hostname = Topology.get_node_hostname(node)
176 if hostname not in self._nodeconfig:
177 self._nodeconfig[hostname] = {}
178 self._nodeconfig[hostname]['heapsize_config'] = heapsize_config
179 logger.debug('Setting hostname {} Heap Size config to {}'.
180 format(hostname, heapsize_config))
182 def add_rxqueues_config(self, node, rxqueues_config):
183 """Add Rx Queues configuration for node.
185 :param node: DUT node.
186 :param rxqueues_config: Rxqueues configuration, as a string.
188 :type rxqueues_config: str
191 if node['type'] != NodeType.DUT:
192 raise ValueError('Node type is not a DUT')
193 hostname = Topology.get_node_hostname(node)
194 if not hostname in self._nodeconfig:
195 self._nodeconfig[hostname] = {}
196 if not 'rxqueues_config' in self._nodeconfig[hostname]:
197 self._nodeconfig[hostname]['rxqueues_config'] = []
198 self._nodeconfig[hostname]['rxqueues_config'].append(rxqueues_config)
199 logger.debug('Setting hostname {} rxqueues config to {}'.\
200 format(hostname, rxqueues_config))
202 def add_no_multi_seg_config(self, node):
203 """Add No Multi Seg configuration for node.
205 :param node: DUT node.
209 if node['type'] != NodeType.DUT:
210 raise ValueError('Node type is not a DUT')
211 hostname = Topology.get_node_hostname(node)
212 if not hostname in self._nodeconfig:
213 self._nodeconfig[hostname] = {}
214 if not 'no_multi_seg_config' in self._nodeconfig[hostname]:
215 self._nodeconfig[hostname]['no_multi_seg_config'] = []
216 self._nodeconfig[hostname]['no_multi_seg_config'].append(
218 logger.debug('Setting hostname {} config with {}'.\
219 format(hostname, "no-multi-seg"))
221 def add_enable_vhost_user_config(self, node):
222 """Add enable-vhost-user configuration for node.
224 :param node: DUT node.
228 if node['type'] != NodeType.DUT:
229 raise ValueError('Node type is not a DUT')
230 hostname = Topology.get_node_hostname(node)
231 if not hostname in self._nodeconfig:
232 self._nodeconfig[hostname] = {}
233 if not 'enable_vhost_user' in self._nodeconfig[hostname]:
234 self._nodeconfig[hostname]['enable_vhost_user'] = []
235 self._nodeconfig[hostname]['enable_vhost_user'].append(
237 logger.debug('Setting hostname {} config with {}'.\
238 format(hostname, "enable-vhost-user"))
240 def add_cryptodev_config(self, node, count):
241 """Add cryptodev configuration for node.
243 :param node: DUT node.
244 :param count: Number of crypto device to add.
248 :raises ValueError: If node type is not a DUT
250 if node['type'] != NodeType.DUT:
251 raise ValueError('Node type is not a DUT')
252 hostname = Topology.get_node_hostname(node)
253 if hostname not in self._nodeconfig:
254 self._nodeconfig[hostname] = {}
256 cryptodev = Topology.get_cryptodev(node)
257 cryptodev_config = 'enable-cryptodev'
259 for i in range(count):
260 cryptodev_config += ' dev {}'.format(\
261 re.sub(r'\d.\d$', '1.'+str(i), cryptodev))
263 self._nodeconfig[hostname]['cryptodev_config'] = cryptodev_config
264 logger.debug('Setting hostname {} Cryptodev config to {}'.
265 format(hostname, cryptodev_config))
267 uio_driver_config = 'uio-driver {}'.\
268 format(Topology.get_uio_driver(node))
270 self._nodeconfig[hostname]['uio_driver_config'] = uio_driver_config
271 logger.debug('Setting hostname {} uio_driver config to {}'.
272 format(hostname, uio_driver_config))
274 def remove_all_pci_devices(self, node):
275 """Remove PCI device configuration from node.
277 :param node: DUT node.
281 if node['type'] != NodeType.DUT:
282 raise ValueError('Node type is not a DUT')
283 hostname = Topology.get_node_hostname(node)
284 if hostname in self._nodeconfig:
285 self._nodeconfig[hostname]['pci_addrs'] = []
286 logger.debug('Clearing all PCI devices for hostname {}.'.
289 def remove_all_cpu_config(self, node):
290 """Remove CPU configuration from node.
292 :param node: DUT node.
296 if node['type'] != NodeType.DUT:
297 raise ValueError('Node type is not a DUT')
298 hostname = Topology.get_node_hostname(node)
299 if hostname in self._nodeconfig:
300 self._nodeconfig[hostname]['cpu_config'] = []
301 logger.debug('Clearing all CPU config for hostname {}.'.
304 def remove_socketmem_config(self, node):
305 """Remove Socket Memory configuration from node.
307 :param node: DUT node.
311 if node['type'] != NodeType.DUT:
312 raise ValueError('Node type is not a DUT')
313 hostname = Topology.get_node_hostname(node)
314 if hostname in self._nodeconfig:
315 self._nodeconfig[hostname].pop('socketmem_config', None)
316 logger.debug('Clearing Socket Memory config for hostname {}.'.
319 def remove_cryptodev_config(self, node):
320 """Remove Cryptodev configuration from node.
322 :param node: DUT node.
325 :raises ValueError: If node type is not a DUT
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('cryptodev_config', None)
332 logger.debug('Clearing Cryptodev config for hostname {}.'.
335 def remove_heapsize_config(self, node):
336 """Remove Heap Size configuration from node.
338 :param node: DUT node.
342 if node['type'] != NodeType.DUT:
343 raise ValueError('Node type is not a DUT')
344 hostname = Topology.get_node_hostname(node)
345 if hostname in self._nodeconfig:
346 self._nodeconfig[hostname].pop('heapsize_config', None)
347 logger.debug('Clearing Heap Size config for hostname {}.'.
350 def remove_rxqueues_config(self, node):
351 """Remove Rxqueues configuration from node.
353 :param node: DUT node.
357 if node['type'] != NodeType.DUT:
358 raise ValueError('Node type is not a DUT')
359 hostname = Topology.get_node_hostname(node)
360 if hostname in self._nodeconfig:
361 self._nodeconfig[hostname]['rxqueues_config'] = []
362 logger.debug('Clearing rxqueues config for hostname {}.'.\
365 def remove_no_multi_seg_config(self, node):
366 """Remove No Multi Seg configuration from node.
368 :param node: DUT node.
372 if node['type'] != NodeType.DUT:
373 raise ValueError('Node type is not a DUT')
374 hostname = Topology.get_node_hostname(node)
375 if hostname in self._nodeconfig:
376 self._nodeconfig[hostname]['no_multi_seg_config'] = []
377 logger.debug('Clearing No Multi Seg config for hostname {}.'.\
380 def remove_enable_vhost_user_config(self, node):
381 """Remove enable-vhost-user configuration from node.
383 :param node: DUT node.
387 if node['type'] != NodeType.DUT:
388 raise ValueError('Node type is not a DUT')
389 hostname = Topology.get_node_hostname(node)
390 if hostname in self._nodeconfig:
391 self._nodeconfig[hostname]['enable_vhost_user'] = []
392 logger.debug('Clearing enable-vhost-user config for hostname {}.'.\
395 def apply_config(self, node, waittime=5, retries=12):
396 """Generate and apply VPP configuration for node.
398 Use data from calls to this class to form a startup.conf file and
399 replace /etc/vpp/startup.conf with it on node.
401 :param node: DUT node.
402 :param waittime: Time to wait for VPP to restart (default 5 seconds).
403 :param retries: Number of times (default 12) to re-try waiting.
409 if node['type'] != NodeType.DUT:
410 raise ValueError('Node type is not a DUT')
411 hostname = Topology.get_node_hostname(node)
415 socketmemconfig = DEFAULT_SOCKETMEM_CONFIG
424 if hostname in self._nodeconfig:
425 cfg = self._nodeconfig[hostname]
426 if 'cpu_config' in cfg:
427 cpuconfig = " " + "\n ".join(cfg['cpu_config'])
429 if 'pci_addrs' in cfg:
430 pciconfig = " dev " + "\n dev ".join(cfg['pci_addrs'])
432 if 'socketmem_config' in cfg:
433 socketmemconfig = cfg['socketmem_config']
435 if 'cryptodev_config' in cfg:
436 cryptodevconfig = cfg['cryptodev_config']
438 if 'uio_driver_config' in cfg:
439 uiodriverconfig = cfg['uio_driver_config']
441 if 'heapsize_config' in cfg:
442 heapsizeconfig = "\nheapsize {}\n".\
443 format(cfg['heapsize_config'])
445 if 'rxqueues_config' in cfg:
446 rxqueuesconfig = " " + "\n ".join(cfg['rxqueues_config'])
448 if 'no_multi_seg_config' in cfg:
449 nomultiseg = " " + "\n ".join(cfg['no_multi_seg_config'])
451 if 'enable_vhost_user' in cfg:
452 enablevhostuser = " " + "\n ".join(cfg['enable_vhost_user'])
454 vppconfig = VPP_CONFIG_TEMPLATE.format(cpuconfig=cpuconfig,
456 cryptodevconfig=cryptodevconfig,
457 uiodriverconfig=uiodriverconfig,
458 socketmemconfig=socketmemconfig,
459 heapsizeconfig=heapsizeconfig,
460 rxqueuesconfig=rxqueuesconfig,
461 txqueuesconfig=txqueuesconfig,
462 nomultiseg=nomultiseg,
463 enablevhostuser=enablevhostuser)
465 logger.debug('Writing VPP config to host {}: "{}"'.format(hostname,
471 # We're using this "| sudo tee" construct because redirecting
472 # a sudo'd outut ("sudo echo xxx > /path/to/file") does not
473 # work on most platforms...
474 (ret, stdout, stderr) = \
475 ssh.exec_command('echo "{0}" | sudo tee {1}'.
476 format(vppconfig, VPP_CONFIG_FILENAME))
479 logger.debug('Writing config file failed to node {}'.
481 logger.debug('stdout: {}'.format(stdout))
482 logger.debug('stderr: {}'.format(stderr))
483 raise RuntimeError('Writing config file failed to node {}'.
486 # Instead of restarting, we'll do separate start and stop
487 # actions. This way we don't care whether VPP was running
489 ssh.exec_command('sudo service {} stop'.format(VPP_SERVICE_NAME))
490 (ret, stdout, stderr) = \
491 ssh.exec_command('sudo service {} start'.format(VPP_SERVICE_NAME))
493 logger.debug('Restarting VPP failed on node {}'.
495 logger.debug('stdout: {}'.format(stdout))
496 logger.debug('stderr: {}'.format(stderr))
497 raise RuntimeError('Restarting VPP failed on node {}'.
500 # Sleep <waittime> seconds, up to <retry> times,
501 # and verify if VPP is running.
502 vpp_is_running = False
503 retries_left = retries
504 while (not vpp_is_running) and (retries_left > 0):
508 # FIXME: Need to find a good way to check if VPP is operational.
510 # If VatTerminal/VatExecutor is anything like vppctl or
511 # vpp_api_test, then in case VPP is NOT running it will block for
512 # 30 seconds or so and not even return if VPP becomes alive during
513 # that time. This makes it unsuitable in this case. We either need
514 # a call that returns immediately, indicating whether VPP is
515 # healthy or not, or a call that waits (up to a defined length
516 # of time) and returns immediately if VPP is or becomes healthy.
517 (ret, stdout, stderr) = \
518 ssh.exec_command('echo show hardware-interfaces | '
522 vpp_is_running = True
524 logger.debug('VPP not yet running, {} retries left'.
525 format(retries_left))
526 if retries_left == 0:
527 raise RuntimeError('VPP failed to restart on node {}'.
529 logger.debug('VPP interfaces found on node {}'.