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}
61 # End VPP configuration template.
64 class VppConfigGenerator(object):
65 """VPP Configuration File Generator"""
70 def add_pci_device(self, node, pci_device=None):
71 """Add PCI device configuration for node.
73 :param node: DUT node.
74 :param pci_device: PCI device (format 0000:00:00.0 or 00:00.0).
75 If none given, all PCI devices for this node as per topology will be
81 if node['type'] != NodeType.DUT:
82 raise ValueError('Node type is not a DUT')
84 if pci_device is None:
85 # No PCI device was given. Add all device from topology.
86 for port in node['interfaces'].values():
87 port_name = port.get('name')
88 pci_addr = Topology.get_interface_pci_addr(node, port_name)
90 self.add_pci_device(node, pci_addr)
92 # Specific device was given.
93 hostname = Topology.get_node_hostname(node)
95 pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
96 "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
97 if not pattern.match(pci_device):
98 raise ValueError('PCI address {} to be added to host {} '
99 'is not in valid format xxxx:xx:xx.x'.
100 format(pci_device, hostname))
102 if hostname not in self._nodeconfig:
103 self._nodeconfig[hostname] = {}
104 if 'pci_addrs' not in self._nodeconfig[hostname]:
105 self._nodeconfig[hostname]['pci_addrs'] = []
106 self._nodeconfig[hostname]['pci_addrs'].append(pci_device)
107 logger.debug('Adding PCI device {1} to {0}'.format(hostname,
110 def add_cpu_config(self, node, cpu_config):
111 """Add CPU configuration for node.
113 :param node: DUT node.
114 :param cpu_config: CPU configuration option, as a string.
116 :type cpu_config: str
119 if node['type'] != NodeType.DUT:
120 raise ValueError('Node type is not a DUT')
121 hostname = Topology.get_node_hostname(node)
122 if hostname not in self._nodeconfig:
123 self._nodeconfig[hostname] = {}
124 if 'cpu_config' not in self._nodeconfig[hostname]:
125 self._nodeconfig[hostname]['cpu_config'] = []
126 self._nodeconfig[hostname]['cpu_config'].append(cpu_config)
127 logger.debug('Adding {} to hostname {} CPU config'.format(hostname,
130 def add_socketmem_config(self, node, socketmem_config):
131 """Add Socket Memory configuration for node.
133 :param node: DUT node.
134 :param socketmem_config: Socket Memory configuration option,
137 :type socketmem_config: str
140 if node['type'] != NodeType.DUT:
141 raise ValueError('Node type is not a DUT')
142 hostname = Topology.get_node_hostname(node)
143 if hostname not in self._nodeconfig:
144 self._nodeconfig[hostname] = {}
145 self._nodeconfig[hostname]['socketmem_config'] = socketmem_config
146 logger.debug('Setting hostname {} Socket Memory config to {}'.
147 format(hostname, socketmem_config))
149 def add_heapsize_config(self, node, heapsize_config):
150 """Add Heap Size configuration for node.
152 :param node: DUT node.
153 :param heapsize_config: Heap Size configuration, as a string.
155 :type heapsize_config: str
158 if node['type'] != NodeType.DUT:
159 raise ValueError('Node type is not a DUT')
160 hostname = Topology.get_node_hostname(node)
161 if hostname not in self._nodeconfig:
162 self._nodeconfig[hostname] = {}
163 self._nodeconfig[hostname]['heapsize_config'] = heapsize_config
164 logger.debug('Setting hostname {} Heap Size config to {}'.
165 format(hostname, heapsize_config))
167 def add_rss_config(self, node, rss_config):
168 """Add RSS configuration for node.
170 :param node: DUT node.
171 :param rss_config: RSS configuration, as a string.
173 :type rss_config: str
176 if node['type'] != NodeType.DUT:
177 raise ValueError('Node type is not a DUT')
178 hostname = Topology.get_node_hostname(node)
179 if not hostname in self._nodeconfig:
180 self._nodeconfig[hostname] = {}
181 if not 'rss_config' in self._nodeconfig[hostname]:
182 self._nodeconfig[hostname]['rss_config'] = []
183 self._nodeconfig[hostname]['rss_config'].append(rss_config)
184 logger.debug('Setting hostname {} RSS config to {}'.\
185 format(hostname, rss_config))
187 def add_max_tx_queues_config(self, node, max_tx_queues_config):
188 """Add Max TX Queues configuration for node.
190 :param node: DUT node.
191 :param max_tx_queues_config: Max TX Queues configuration, as a string.
193 :type max_tx_queues_config: str
196 if node['type'] != NodeType.DUT:
197 raise ValueError('Node type is not a DUT')
198 hostname = Topology.get_node_hostname(node)
199 if not hostname in self._nodeconfig:
200 self._nodeconfig[hostname] = {}
201 if not 'max_tx_queues_config' in self._nodeconfig[hostname]:
202 self._nodeconfig[hostname]['max_tx_queues_config'] = []
203 self._nodeconfig[hostname]['max_tx_queues_config'].append(
204 max_tx_queues_config)
205 logger.debug('Setting hostname {} max_tx_queues config to {}'.\
206 format(hostname, max_tx_queues_config))
208 def add_no_multi_seg_config(self, node):
209 """Add No Multi Seg configuration for node.
211 :param node: DUT node.
215 if node['type'] != NodeType.DUT:
216 raise ValueError('Node type is not a DUT')
217 hostname = Topology.get_node_hostname(node)
218 if not hostname in self._nodeconfig:
219 self._nodeconfig[hostname] = {}
220 if not 'no_multi_seg_config' in self._nodeconfig[hostname]:
221 self._nodeconfig[hostname]['no_multi_seg_config'] = []
222 self._nodeconfig[hostname]['no_multi_seg_config'].append(
224 logger.debug('Setting hostname {} config with {}'.\
225 format(hostname, "no-multi-seg"))
227 def remove_all_pci_devices(self, node):
228 """Remove PCI device 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]['pci_addrs'] = []
239 logger.debug('Clearing all PCI devices for hostname {}.'.
242 def remove_all_cpu_config(self, node):
243 """Remove CPU 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]['cpu_config'] = []
254 logger.debug('Clearing all CPU config for hostname {}.'.
257 def remove_socketmem_config(self, node):
258 """Remove Socket Memory 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('socketmem_config', None)
269 logger.debug('Clearing Socket Memory config for hostname {}.'.
272 def remove_heapsize_config(self, node):
273 """Remove Heap Size 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].pop('heapsize_config', None)
284 logger.debug('Clearing Heap Size config for hostname {}.'.
287 def remove_rss_config(self, node):
288 """Remove RSS 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]['rss_config'] = []
299 logger.debug('Clearing RSS config for hostname {}.'.\
302 def remove_max_tx_queues_config(self, node):
303 """Remove Max TX Queues configuration from node.
305 :param node: DUT node.
309 if node['type'] != NodeType.DUT:
310 raise ValueError('Node type is not a DUT')
311 hostname = Topology.get_node_hostname(node)
312 if hostname in self._nodeconfig:
313 self._nodeconfig[hostname]['max_tx_queues_config'] = []
314 logger.debug('Clearing Max TX Queues config for hostname {}.'.\
317 def remove_no_multi_seg_config(self, node):
318 """Remove No Multi Seg configuration from node.
320 :param node: DUT node.
324 if node['type'] != NodeType.DUT:
325 raise ValueError('Node type is not a DUT')
326 hostname = Topology.get_node_hostname(node)
327 if hostname in self._nodeconfig:
328 self._nodeconfig[hostname]['no_multi_seg_config'] = []
329 logger.debug('Clearing No Multi Seg config for hostname {}.'.\
332 def apply_config(self, node, waittime=5, retries=12):
333 """Generate and apply VPP configuration for node.
335 Use data from calls to this class to form a startup.conf file and
336 replace /etc/vpp/startup.conf with it on node.
338 :param node: DUT node.
339 :param waittime: Time to wait for VPP to restart (default 5 seconds).
340 :param retries: Number of times (default 12) to re-try waiting.
346 if node['type'] != NodeType.DUT:
347 raise ValueError('Node type is not a DUT')
348 hostname = Topology.get_node_hostname(node)
352 socketmemconfig = DEFAULT_SOCKETMEM_CONFIG
358 if hostname in self._nodeconfig:
359 cfg = self._nodeconfig[hostname]
360 if 'cpu_config' in cfg:
361 cpuconfig = " " + "\n ".join(cfg['cpu_config'])
363 if 'pci_addrs' in cfg:
364 pciconfig = " dev " + "\n dev ".join(cfg['pci_addrs'])
366 if 'socketmem_config' in cfg:
367 socketmemconfig = cfg['socketmem_config']
369 if 'heapsize_config' in cfg:
370 heapsizeconfig = "\nheapsize {}\n".\
371 format(cfg['heapsize_config'])
373 if 'rss_config' in cfg:
374 rssconfig = " " + "\n ".join(cfg['rss_config'])
376 if 'max_tx_queues_config' in cfg:
377 txqueuesconfig = " " + "\n ".join(
378 cfg['max_tx_queues_config'])
380 if 'no_multi_seg_config' in cfg:
381 nomultiseg = " " + "\n ".join(cfg['no_multi_seg_config'])
383 vppconfig = VPP_CONFIG_TEMPLATE.format(cpuconfig=cpuconfig,
385 socketmemconfig=socketmemconfig,
386 heapsizeconfig=heapsizeconfig,
388 txqueuesconfig=txqueuesconfig,
389 nomultiseg = nomultiseg)
391 logger.debug('Writing VPP config to host {}: "{}"'.format(hostname,
397 # We're using this "| sudo tee" construct because redirecting
398 # a sudo'd outut ("sudo echo xxx > /path/to/file") does not
399 # work on most platforms...
400 (ret, stdout, stderr) = \
401 ssh.exec_command('echo "{0}" | sudo tee {1}'.
402 format(vppconfig, VPP_CONFIG_FILENAME))
405 logger.debug('Writing config file failed to node {}'.
407 logger.debug('stdout: {}'.format(stdout))
408 logger.debug('stderr: {}'.format(stderr))
409 raise RuntimeError('Writing config file failed to node {}'.
412 # Instead of restarting, we'll do separate start and stop
413 # actions. This way we don't care whether VPP was running
415 ssh.exec_command('sudo initctl stop {}'.format(VPP_SERVICE_NAME))
416 (ret, stdout, stderr) = \
417 ssh.exec_command('sudo initctl start {}'.format(VPP_SERVICE_NAME))
419 logger.debug('Restarting VPP failed on node {}'.
421 logger.debug('stdout: {}'.format(stdout))
422 logger.debug('stderr: {}'.format(stderr))
423 raise RuntimeError('Restarting VPP failed on node {}'.
426 # Sleep <waittime> seconds, up to <retry> times,
427 # and verify if VPP is running.
428 vpp_is_running = False
429 retries_left = retries
430 while (not vpp_is_running) and (retries_left > 0):
434 # FIXME: Need to find a good way to check if VPP is operational.
436 # If VatTerminal/VatExecutor is anything like vppctl or
437 # vpp_api_test, then in case VPP is NOT running it will block for
438 # 30 seconds or so and not even return if VPP becomes alive during
439 # that time. This makes it unsuitable in this case. We either need
440 # a call that returns immediately, indicating whether VPP is
441 # healthy or not, or a call that waits (up to a defined length
442 # of time) and returns immediately if VPP is or becomes healthy.
443 (ret, stdout, stderr) = \
444 ssh.exec_command('echo show hardware-interfaces | '
448 vpp_is_running = True
450 logger.debug('VPP not yet running, {} retries left'.
451 format(retries_left))
452 if retries_left == 0:
453 raise RuntimeError('VPP failed to restart on node {}'.
455 logger.debug('VPP interfaces found on node {}'.