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}
70 # End VPP configuration template.
73 class VppConfigGenerator(object):
74 """VPP Configuration File Generator"""
79 def add_pci_all_devices(self, node):
80 """Add all PCI devices from topology file to startup config
86 for port in node['interfaces'].keys():
87 pci_addr = Topology.get_interface_pci_addr(node, port)
89 self.add_pci_device(node, pci_addr)
92 def add_pci_device(self, node, *pci_devices):
93 """Add PCI device configuration for node.
95 :param node: DUT node.
96 :param pci_device: PCI devices (format 0000:00:00.0 or 00:00.0)
98 :type pci_devices: tuple
101 if node['type'] != NodeType.DUT:
102 raise ValueError('Node type is not a DUT')
104 # Specific device was given.
105 hostname = Topology.get_node_hostname(node)
107 pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
108 "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
109 for pci_device in pci_devices:
110 if not pattern.match(pci_device):
111 raise ValueError('PCI address {} to be added to host {} '
112 'is not in valid format xxxx:xx:xx.x'.
113 format(pci_device, hostname))
115 if hostname not in self._nodeconfig:
116 self._nodeconfig[hostname] = {}
117 if 'pci_addrs' not in self._nodeconfig[hostname]:
118 self._nodeconfig[hostname]['pci_addrs'] = []
119 self._nodeconfig[hostname]['pci_addrs'].append(pci_device)
120 logger.debug('Adding PCI device {1} to {0}'.format(hostname,
123 def add_cpu_config(self, node, cpu_config):
124 """Add CPU configuration for node.
126 :param node: DUT node.
127 :param cpu_config: CPU configuration option, as a string.
129 :type cpu_config: str
132 if node['type'] != NodeType.DUT:
133 raise ValueError('Node type is not a DUT')
134 hostname = Topology.get_node_hostname(node)
135 if hostname not in self._nodeconfig:
136 self._nodeconfig[hostname] = {}
137 if 'cpu_config' not in self._nodeconfig[hostname]:
138 self._nodeconfig[hostname]['cpu_config'] = []
139 self._nodeconfig[hostname]['cpu_config'].append(cpu_config)
140 logger.debug('Adding {} to hostname {} CPU config'.format(hostname,
143 def add_socketmem_config(self, node, socketmem_config):
144 """Add Socket Memory configuration for node.
146 :param node: DUT node.
147 :param socketmem_config: Socket Memory configuration option,
150 :type socketmem_config: str
153 if node['type'] != NodeType.DUT:
154 raise ValueError('Node type is not a DUT')
155 hostname = Topology.get_node_hostname(node)
156 if hostname not in self._nodeconfig:
157 self._nodeconfig[hostname] = {}
158 self._nodeconfig[hostname]['socketmem_config'] = socketmem_config
159 logger.debug('Setting hostname {} Socket Memory config to {}'.
160 format(hostname, socketmem_config))
162 def add_heapsize_config(self, node, heapsize_config):
163 """Add Heap Size configuration for node.
165 :param node: DUT node.
166 :param heapsize_config: Heap Size configuration, as a string.
168 :type heapsize_config: str
171 if node['type'] != NodeType.DUT:
172 raise ValueError('Node type is not a DUT')
173 hostname = Topology.get_node_hostname(node)
174 if hostname not in self._nodeconfig:
175 self._nodeconfig[hostname] = {}
176 self._nodeconfig[hostname]['heapsize_config'] = heapsize_config
177 logger.debug('Setting hostname {} Heap Size config to {}'.
178 format(hostname, heapsize_config))
180 def add_rxqueues_config(self, node, rxqueues_config):
181 """Add Rx Queues configuration for node.
183 :param node: DUT node.
184 :param rxqueues_config: Rxqueues configuration, as a string.
186 :type rxqueues_config: str
189 if node['type'] != NodeType.DUT:
190 raise ValueError('Node type is not a DUT')
191 hostname = Topology.get_node_hostname(node)
192 if not hostname in self._nodeconfig:
193 self._nodeconfig[hostname] = {}
194 if not 'rxqueues_config' in self._nodeconfig[hostname]:
195 self._nodeconfig[hostname]['rxqueues_config'] = []
196 self._nodeconfig[hostname]['rxqueues_config'].append(rxqueues_config)
197 logger.debug('Setting hostname {} rxqueues config to {}'.\
198 format(hostname, rxqueues_config))
200 def add_no_multi_seg_config(self, node):
201 """Add No Multi Seg configuration for node.
203 :param node: DUT node.
207 if node['type'] != NodeType.DUT:
208 raise ValueError('Node type is not a DUT')
209 hostname = Topology.get_node_hostname(node)
210 if not hostname in self._nodeconfig:
211 self._nodeconfig[hostname] = {}
212 if not 'no_multi_seg_config' in self._nodeconfig[hostname]:
213 self._nodeconfig[hostname]['no_multi_seg_config'] = []
214 self._nodeconfig[hostname]['no_multi_seg_config'].append(
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 not hostname in self._nodeconfig:
230 self._nodeconfig[hostname] = {}
231 if not 'enable_vhost_user' in self._nodeconfig[hostname]:
232 self._nodeconfig[hostname]['enable_vhost_user'] = []
233 self._nodeconfig[hostname]['enable_vhost_user'].append(
235 logger.debug('Setting hostname {} config with {}'.\
236 format(hostname, "enable-vhost-user"))
238 def remove_all_pci_devices(self, node):
239 """Remove PCI device configuration from 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 in self._nodeconfig:
249 self._nodeconfig[hostname]['pci_addrs'] = []
250 logger.debug('Clearing all PCI devices for hostname {}.'.
253 def remove_all_cpu_config(self, node):
254 """Remove CPU configuration from node.
256 :param node: DUT node.
260 if node['type'] != NodeType.DUT:
261 raise ValueError('Node type is not a DUT')
262 hostname = Topology.get_node_hostname(node)
263 if hostname in self._nodeconfig:
264 self._nodeconfig[hostname]['cpu_config'] = []
265 logger.debug('Clearing all CPU config for hostname {}.'.
268 def remove_socketmem_config(self, node):
269 """Remove Socket Memory configuration from node.
271 :param node: DUT node.
275 if node['type'] != NodeType.DUT:
276 raise ValueError('Node type is not a DUT')
277 hostname = Topology.get_node_hostname(node)
278 if hostname in self._nodeconfig:
279 self._nodeconfig[hostname].pop('socketmem_config', None)
280 logger.debug('Clearing Socket Memory config for hostname {}.'.
283 def remove_heapsize_config(self, node):
284 """Remove Heap Size configuration from node.
286 :param node: DUT node.
290 if node['type'] != NodeType.DUT:
291 raise ValueError('Node type is not a DUT')
292 hostname = Topology.get_node_hostname(node)
293 if hostname in self._nodeconfig:
294 self._nodeconfig[hostname].pop('heapsize_config', None)
295 logger.debug('Clearing Heap Size config for hostname {}.'.
298 def remove_rxqueues_config(self, node):
299 """Remove Rxqueues configuration from node.
301 :param node: DUT node.
305 if node['type'] != NodeType.DUT:
306 raise ValueError('Node type is not a DUT')
307 hostname = Topology.get_node_hostname(node)
308 if hostname in self._nodeconfig:
309 self._nodeconfig[hostname]['rxqueues_config'] = []
310 logger.debug('Clearing rxqueues config for hostname {}.'.\
313 def remove_no_multi_seg_config(self, node):
314 """Remove No Multi Seg configuration from node.
316 :param node: DUT node.
320 if node['type'] != NodeType.DUT:
321 raise ValueError('Node type is not a DUT')
322 hostname = Topology.get_node_hostname(node)
323 if hostname in self._nodeconfig:
324 self._nodeconfig[hostname]['no_multi_seg_config'] = []
325 logger.debug('Clearing No Multi Seg config for hostname {}.'.\
328 def remove_enable_vhost_user_config(self, node):
329 """Remove enable-vhost-user configuration from node.
331 :param node: DUT node.
335 if node['type'] != NodeType.DUT:
336 raise ValueError('Node type is not a DUT')
337 hostname = Topology.get_node_hostname(node)
338 if hostname in self._nodeconfig:
339 self._nodeconfig[hostname]['enable_vhost_user'] = []
340 logger.debug('Clearing enable-vhost-user config for hostname {}.'.\
343 def apply_config(self, node, waittime=5, retries=12):
344 """Generate and apply VPP configuration for node.
346 Use data from calls to this class to form a startup.conf file and
347 replace /etc/vpp/startup.conf with it on node.
349 :param node: DUT node.
350 :param waittime: Time to wait for VPP to restart (default 5 seconds).
351 :param retries: Number of times (default 12) to re-try waiting.
357 if node['type'] != NodeType.DUT:
358 raise ValueError('Node type is not a DUT')
359 hostname = Topology.get_node_hostname(node)
363 socketmemconfig = DEFAULT_SOCKETMEM_CONFIG
370 if hostname in self._nodeconfig:
371 cfg = self._nodeconfig[hostname]
372 if 'cpu_config' in cfg:
373 cpuconfig = " " + "\n ".join(cfg['cpu_config'])
375 if 'pci_addrs' in cfg:
376 pciconfig = " dev " + "\n dev ".join(cfg['pci_addrs'])
378 if 'socketmem_config' in cfg:
379 socketmemconfig = cfg['socketmem_config']
381 if 'heapsize_config' in cfg:
382 heapsizeconfig = "\nheapsize {}\n".\
383 format(cfg['heapsize_config'])
385 if 'rxqueues_config' in cfg:
386 rxqueuesconfig = " " + "\n ".join(cfg['rxqueues_config'])
388 if 'no_multi_seg_config' in cfg:
389 nomultiseg = " " + "\n ".join(cfg['no_multi_seg_config'])
391 if 'enable_vhost_user' in cfg:
392 enablevhostuser = " " + "\n ".join(cfg['enable_vhost_user'])
394 vppconfig = VPP_CONFIG_TEMPLATE.format(cpuconfig=cpuconfig,
396 socketmemconfig=socketmemconfig,
397 heapsizeconfig=heapsizeconfig,
398 rxqueuesconfig=rxqueuesconfig,
399 txqueuesconfig=txqueuesconfig,
400 nomultiseg=nomultiseg,
401 enablevhostuser=enablevhostuser)
403 logger.debug('Writing VPP config to host {}: "{}"'.format(hostname,
409 # We're using this "| sudo tee" construct because redirecting
410 # a sudo'd outut ("sudo echo xxx > /path/to/file") does not
411 # work on most platforms...
412 (ret, stdout, stderr) = \
413 ssh.exec_command('echo "{0}" | sudo tee {1}'.
414 format(vppconfig, VPP_CONFIG_FILENAME))
417 logger.debug('Writing config file failed to node {}'.
419 logger.debug('stdout: {}'.format(stdout))
420 logger.debug('stderr: {}'.format(stderr))
421 raise RuntimeError('Writing config file failed to node {}'.
424 # Instead of restarting, we'll do separate start and stop
425 # actions. This way we don't care whether VPP was running
427 ssh.exec_command('sudo service {} stop'.format(VPP_SERVICE_NAME))
428 (ret, stdout, stderr) = \
429 ssh.exec_command('sudo service {} start'.format(VPP_SERVICE_NAME))
431 logger.debug('Restarting VPP failed on node {}'.
433 logger.debug('stdout: {}'.format(stdout))
434 logger.debug('stderr: {}'.format(stderr))
435 raise RuntimeError('Restarting VPP failed on node {}'.
438 # Sleep <waittime> seconds, up to <retry> times,
439 # and verify if VPP is running.
440 vpp_is_running = False
441 retries_left = retries
442 while (not vpp_is_running) and (retries_left > 0):
446 # FIXME: Need to find a good way to check if VPP is operational.
448 # If VatTerminal/VatExecutor is anything like vppctl or
449 # vpp_api_test, then in case VPP is NOT running it will block for
450 # 30 seconds or so and not even return if VPP becomes alive during
451 # that time. This makes it unsuitable in this case. We either need
452 # a call that returns immediately, indicating whether VPP is
453 # healthy or not, or a call that waits (up to a defined length
454 # of time) and returns immediately if VPP is or becomes healthy.
455 (ret, stdout, stderr) = \
456 ssh.exec_command('echo show hardware-interfaces | '
460 vpp_is_running = True
462 logger.debug('VPP not yet running, {} retries left'.
463 format(retries_left))
464 if retries_left == 0:
465 raise RuntimeError('VPP failed to restart on node {}'.
467 logger.debug('VPP interfaces found on node {}'.