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}
64 # End VPP configuration template.
67 class VppConfigGenerator(object):
68 """VPP Configuration File Generator"""
73 def add_pci_all_devices(self, node):
74 """Add all PCI devices from topology file to startup config
80 for port in node['interfaces'].keys():
81 pci_addr = Topology.get_interface_pci_addr(node, port)
83 self.add_pci_device(node, pci_addr)
86 def add_pci_device(self, node, *pci_devices):
87 """Add PCI device configuration for node.
89 :param node: DUT node.
90 :param pci_device: PCI devices (format 0000:00:00.0 or 00:00.0)
92 :type pci_devices: tuple
95 if node['type'] != NodeType.DUT:
96 raise ValueError('Node type is not a DUT')
98 # Specific device was given.
99 hostname = Topology.get_node_hostname(node)
101 pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
102 "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
103 for pci_device in pci_devices:
104 if not pattern.match(pci_device):
105 raise ValueError('PCI address {} to be added to host {} '
106 'is not in valid format xxxx:xx:xx.x'.
107 format(pci_device, hostname))
109 if hostname not in self._nodeconfig:
110 self._nodeconfig[hostname] = {}
111 if 'pci_addrs' not in self._nodeconfig[hostname]:
112 self._nodeconfig[hostname]['pci_addrs'] = []
113 self._nodeconfig[hostname]['pci_addrs'].append(pci_device)
114 logger.debug('Adding PCI device {1} to {0}'.format(hostname,
117 def add_cpu_config(self, node, cpu_config):
118 """Add CPU configuration for node.
120 :param node: DUT node.
121 :param cpu_config: CPU configuration option, as a string.
123 :type cpu_config: str
126 if node['type'] != NodeType.DUT:
127 raise ValueError('Node type is not a DUT')
128 hostname = Topology.get_node_hostname(node)
129 if hostname not in self._nodeconfig:
130 self._nodeconfig[hostname] = {}
131 if 'cpu_config' not in self._nodeconfig[hostname]:
132 self._nodeconfig[hostname]['cpu_config'] = []
133 self._nodeconfig[hostname]['cpu_config'].append(cpu_config)
134 logger.debug('Adding {} to hostname {} CPU config'.format(hostname,
137 def add_socketmem_config(self, node, socketmem_config):
138 """Add Socket Memory configuration for node.
140 :param node: DUT node.
141 :param socketmem_config: Socket Memory configuration option,
144 :type socketmem_config: str
147 if node['type'] != NodeType.DUT:
148 raise ValueError('Node type is not a DUT')
149 hostname = Topology.get_node_hostname(node)
150 if hostname not in self._nodeconfig:
151 self._nodeconfig[hostname] = {}
152 self._nodeconfig[hostname]['socketmem_config'] = socketmem_config
153 logger.debug('Setting hostname {} Socket Memory config to {}'.
154 format(hostname, socketmem_config))
156 def add_heapsize_config(self, node, heapsize_config):
157 """Add Heap Size configuration for node.
159 :param node: DUT node.
160 :param heapsize_config: Heap Size configuration, as a string.
162 :type heapsize_config: str
165 if node['type'] != NodeType.DUT:
166 raise ValueError('Node type is not a DUT')
167 hostname = Topology.get_node_hostname(node)
168 if hostname not in self._nodeconfig:
169 self._nodeconfig[hostname] = {}
170 self._nodeconfig[hostname]['heapsize_config'] = heapsize_config
171 logger.debug('Setting hostname {} Heap Size config to {}'.
172 format(hostname, heapsize_config))
174 def add_rxqueues_config(self, node, rxqueues_config):
175 """Add Rx Queues configuration for node.
177 :param node: DUT node.
178 :param rxqueues_config: Rxqueues configuration, as a string.
180 :type rxqueues_config: str
183 if node['type'] != NodeType.DUT:
184 raise ValueError('Node type is not a DUT')
185 hostname = Topology.get_node_hostname(node)
186 if not hostname in self._nodeconfig:
187 self._nodeconfig[hostname] = {}
188 if not 'rxqueues_config' in self._nodeconfig[hostname]:
189 self._nodeconfig[hostname]['rxqueues_config'] = []
190 self._nodeconfig[hostname]['rxqueues_config'].append(rxqueues_config)
191 logger.debug('Setting hostname {} rxqueues config to {}'.\
192 format(hostname, rxqueues_config))
194 def add_no_multi_seg_config(self, node):
195 """Add No Multi Seg configuration for node.
197 :param node: DUT node.
201 if node['type'] != NodeType.DUT:
202 raise ValueError('Node type is not a DUT')
203 hostname = Topology.get_node_hostname(node)
204 if not hostname in self._nodeconfig:
205 self._nodeconfig[hostname] = {}
206 if not 'no_multi_seg_config' in self._nodeconfig[hostname]:
207 self._nodeconfig[hostname]['no_multi_seg_config'] = []
208 self._nodeconfig[hostname]['no_multi_seg_config'].append(
210 logger.debug('Setting hostname {} config with {}'.\
211 format(hostname, "no-multi-seg"))
213 def add_enable_vhost_user_config(self, node):
214 """Add enable-vhost-user configuration for node.
216 :param node: DUT node.
220 if node['type'] != NodeType.DUT:
221 raise ValueError('Node type is not a DUT')
222 hostname = Topology.get_node_hostname(node)
223 if not hostname in self._nodeconfig:
224 self._nodeconfig[hostname] = {}
225 if not 'enable_vhost_user' in self._nodeconfig[hostname]:
226 self._nodeconfig[hostname]['enable_vhost_user'] = []
227 self._nodeconfig[hostname]['enable_vhost_user'].append(
229 logger.debug('Setting hostname {} config with {}'.\
230 format(hostname, "enable-vhost-user"))
232 def remove_all_pci_devices(self, node):
233 """Remove PCI device configuration from node.
235 :param node: DUT node.
239 if node['type'] != NodeType.DUT:
240 raise ValueError('Node type is not a DUT')
241 hostname = Topology.get_node_hostname(node)
242 if hostname in self._nodeconfig:
243 self._nodeconfig[hostname]['pci_addrs'] = []
244 logger.debug('Clearing all PCI devices for hostname {}.'.
247 def remove_all_cpu_config(self, node):
248 """Remove CPU configuration from node.
250 :param node: DUT node.
254 if node['type'] != NodeType.DUT:
255 raise ValueError('Node type is not a DUT')
256 hostname = Topology.get_node_hostname(node)
257 if hostname in self._nodeconfig:
258 self._nodeconfig[hostname]['cpu_config'] = []
259 logger.debug('Clearing all CPU config for hostname {}.'.
262 def remove_socketmem_config(self, node):
263 """Remove Socket Memory configuration from node.
265 :param node: DUT node.
269 if node['type'] != NodeType.DUT:
270 raise ValueError('Node type is not a DUT')
271 hostname = Topology.get_node_hostname(node)
272 if hostname in self._nodeconfig:
273 self._nodeconfig[hostname].pop('socketmem_config', None)
274 logger.debug('Clearing Socket Memory config for hostname {}.'.
277 def remove_heapsize_config(self, node):
278 """Remove Heap Size configuration from node.
280 :param node: DUT node.
284 if node['type'] != NodeType.DUT:
285 raise ValueError('Node type is not a DUT')
286 hostname = Topology.get_node_hostname(node)
287 if hostname in self._nodeconfig:
288 self._nodeconfig[hostname].pop('heapsize_config', None)
289 logger.debug('Clearing Heap Size config for hostname {}.'.
292 def remove_rxqueues_config(self, node):
293 """Remove Rxqueues configuration from node.
295 :param node: DUT node.
299 if node['type'] != NodeType.DUT:
300 raise ValueError('Node type is not a DUT')
301 hostname = Topology.get_node_hostname(node)
302 if hostname in self._nodeconfig:
303 self._nodeconfig[hostname]['rxqueues_config'] = []
304 logger.debug('Clearing rxqueues config for hostname {}.'.\
307 def remove_no_multi_seg_config(self, node):
308 """Remove No Multi Seg configuration from node.
310 :param node: DUT node.
314 if node['type'] != NodeType.DUT:
315 raise ValueError('Node type is not a DUT')
316 hostname = Topology.get_node_hostname(node)
317 if hostname in self._nodeconfig:
318 self._nodeconfig[hostname]['no_multi_seg_config'] = []
319 logger.debug('Clearing No Multi Seg config for hostname {}.'.\
322 def remove_enable_vhost_user_config(self, node):
323 """Remove enable-vhost-user configuration from node.
325 :param node: DUT node.
329 if node['type'] != NodeType.DUT:
330 raise ValueError('Node type is not a DUT')
331 hostname = Topology.get_node_hostname(node)
332 if hostname in self._nodeconfig:
333 self._nodeconfig[hostname]['enable_vhost_user'] = []
334 logger.debug('Clearing enable-vhost-user config for hostname {}.'.\
337 def apply_config(self, node, waittime=5, retries=12):
338 """Generate and apply VPP configuration for node.
340 Use data from calls to this class to form a startup.conf file and
341 replace /etc/vpp/startup.conf with it on node.
343 :param node: DUT node.
344 :param waittime: Time to wait for VPP to restart (default 5 seconds).
345 :param retries: Number of times (default 12) to re-try waiting.
351 if node['type'] != NodeType.DUT:
352 raise ValueError('Node type is not a DUT')
353 hostname = Topology.get_node_hostname(node)
357 socketmemconfig = DEFAULT_SOCKETMEM_CONFIG
364 if hostname in self._nodeconfig:
365 cfg = self._nodeconfig[hostname]
366 if 'cpu_config' in cfg:
367 cpuconfig = " " + "\n ".join(cfg['cpu_config'])
369 if 'pci_addrs' in cfg:
370 pciconfig = " dev " + "\n dev ".join(cfg['pci_addrs'])
372 if 'socketmem_config' in cfg:
373 socketmemconfig = cfg['socketmem_config']
375 if 'heapsize_config' in cfg:
376 heapsizeconfig = "\nheapsize {}\n".\
377 format(cfg['heapsize_config'])
379 if 'rxqueues_config' in cfg:
380 rxqueuesconfig = " " + "\n ".join(cfg['rxqueues_config'])
382 if 'no_multi_seg_config' in cfg:
383 nomultiseg = " " + "\n ".join(cfg['no_multi_seg_config'])
385 if 'enable_vhost_user' in cfg:
386 enablevhostuser = " " + "\n ".join(cfg['enable_vhost_user'])
388 vppconfig = VPP_CONFIG_TEMPLATE.format(cpuconfig=cpuconfig,
390 socketmemconfig=socketmemconfig,
391 rxqueuesconfig=rxqueuesconfig,
392 txqueuesconfig=txqueuesconfig,
393 nomultiseg=nomultiseg,
394 enablevhostuser=enablevhostuser)
396 logger.debug('Writing VPP config to host {}: "{}"'.format(hostname,
402 # We're using this "| sudo tee" construct because redirecting
403 # a sudo'd outut ("sudo echo xxx > /path/to/file") does not
404 # work on most platforms...
405 (ret, stdout, stderr) = \
406 ssh.exec_command('echo "{0}" | sudo tee {1}'.
407 format(vppconfig, VPP_CONFIG_FILENAME))
410 logger.debug('Writing config file failed to node {}'.
412 logger.debug('stdout: {}'.format(stdout))
413 logger.debug('stderr: {}'.format(stderr))
414 raise RuntimeError('Writing config file failed to node {}'.
417 # Instead of restarting, we'll do separate start and stop
418 # actions. This way we don't care whether VPP was running
420 ssh.exec_command('sudo initctl stop {}'.format(VPP_SERVICE_NAME))
421 (ret, stdout, stderr) = \
422 ssh.exec_command('sudo initctl start {}'.format(VPP_SERVICE_NAME))
424 logger.debug('Restarting VPP failed on node {}'.
426 logger.debug('stdout: {}'.format(stdout))
427 logger.debug('stderr: {}'.format(stderr))
428 raise RuntimeError('Restarting VPP failed on node {}'.
431 # Sleep <waittime> seconds, up to <retry> times,
432 # and verify if VPP is running.
433 vpp_is_running = False
434 retries_left = retries
435 while (not vpp_is_running) and (retries_left > 0):
439 # FIXME: Need to find a good way to check if VPP is operational.
441 # If VatTerminal/VatExecutor is anything like vppctl or
442 # vpp_api_test, then in case VPP is NOT running it will block for
443 # 30 seconds or so and not even return if VPP becomes alive during
444 # that time. This makes it unsuitable in this case. We either need
445 # a call that returns immediately, indicating whether VPP is
446 # healthy or not, or a call that waits (up to a defined length
447 # of time) and returns immediately if VPP is or becomes healthy.
448 (ret, stdout, stderr) = \
449 ssh.exec_command('echo show hardware-interfaces | '
453 vpp_is_running = True
455 logger.debug('VPP not yet running, {} retries left'.
456 format(retries_left))
457 if retries_left == 0:
458 raise RuntimeError('VPP failed to restart on node {}'.
460 logger.debug('VPP interfaces found on node {}'.