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}
60 # End VPP configuration template.
63 class VppConfigGenerator(object):
64 """VPP Configuration File Generator"""
69 def add_pci_device(self, node, pci_device=None):
70 """Add PCI device configuration for node.
72 :param node: DUT node.
73 :param pci_device: PCI device (format 0000:00:00.0 or 00:00.0).
74 If none given, all PCI devices for this node as per topology will be
80 if node['type'] != NodeType.DUT:
81 raise ValueError('Node type is not a DUT')
83 if pci_device is None:
84 # No PCI device was given. Add all device from topology.
85 for port in node['interfaces'].values():
86 port_name = port.get('name')
87 pci_addr = Topology.get_interface_pci_addr(node, port_name)
89 self.add_pci_device(node, pci_addr)
91 # Specific device was given.
92 hostname = Topology.get_node_hostname(node)
94 pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
95 "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
96 if not pattern.match(pci_device):
97 raise ValueError('PCI address {} to be added to host {} '
98 'is not in valid format xxxx:xx:xx.x'.
99 format(pci_device, hostname))
101 if hostname not in self._nodeconfig:
102 self._nodeconfig[hostname] = {}
103 if 'pci_addrs' not in self._nodeconfig[hostname]:
104 self._nodeconfig[hostname]['pci_addrs'] = []
105 self._nodeconfig[hostname]['pci_addrs'].append(pci_device)
106 logger.debug('Adding PCI device {1} to {0}'.format(hostname,
109 def add_cpu_config(self, node, cpu_config):
110 """Add CPU configuration for node.
112 :param node: DUT node.
113 :param cpu_config: CPU configuration option, as a string.
115 :type cpu_config: str
118 if node['type'] != NodeType.DUT:
119 raise ValueError('Node type is not a DUT')
120 hostname = Topology.get_node_hostname(node)
121 if hostname not in self._nodeconfig:
122 self._nodeconfig[hostname] = {}
123 if 'cpu_config' not in self._nodeconfig[hostname]:
124 self._nodeconfig[hostname]['cpu_config'] = []
125 self._nodeconfig[hostname]['cpu_config'].append(cpu_config)
126 logger.debug('Adding {} to hostname {} CPU config'.format(hostname,
129 def add_socketmem_config(self, node, socketmem_config):
130 """Add Socket Memory configuration for node.
132 :param node: DUT node.
133 :param socketmem_config: Socket Memory configuration option,
136 :type socketmem_config: str
139 if node['type'] != NodeType.DUT:
140 raise ValueError('Node type is not a DUT')
141 hostname = Topology.get_node_hostname(node)
142 if hostname not in self._nodeconfig:
143 self._nodeconfig[hostname] = {}
144 self._nodeconfig[hostname]['socketmem_config'] = socketmem_config
145 logger.debug('Setting hostname {} Socket Memory config to {}'.
146 format(hostname, socketmem_config))
148 def add_heapsize_config(self, node, heapsize_config):
149 """Add Heap Size configuration for node.
151 :param node: DUT node.
152 :param heapsize_config: Heap Size configuration, as a string.
154 :type heapsize_config: str
157 if node['type'] != NodeType.DUT:
158 raise ValueError('Node type is not a DUT')
159 hostname = Topology.get_node_hostname(node)
160 if hostname not in self._nodeconfig:
161 self._nodeconfig[hostname] = {}
162 self._nodeconfig[hostname]['heapsize_config'] = heapsize_config
163 logger.debug('Setting hostname {} Heap Size config to {}'.
164 format(hostname, heapsize_config))
166 def add_rss_config(self, node, rss_config):
167 """Add RSS configuration for node.
169 :param node: DUT node.
170 :param rss_config: RSS configuration, as a string.
172 :type rss_config: str
175 if node['type'] != NodeType.DUT:
176 raise ValueError('Node type is not a DUT')
177 hostname = Topology.get_node_hostname(node)
178 if not hostname in self._nodeconfig:
179 self._nodeconfig[hostname] = {}
180 if not 'rss_config' in self._nodeconfig[hostname]:
181 self._nodeconfig[hostname]['rss_config'] = []
182 self._nodeconfig[hostname]['rss_config'].append(rss_config)
183 logger.debug('Setting hostname {} RSS config to {}'.\
184 format(hostname, rss_config))
186 def add_max_tx_queues_config(self, node, max_tx_queues_config):
187 """Add Max TX Queues configuration for node.
189 :param node: DUT node.
190 :param max_tx_queues_config: Max TX Queues configuration, as a string.
192 :type max_tx_queues_config: str
195 if node['type'] != NodeType.DUT:
196 raise ValueError('Node type is not a DUT')
197 hostname = Topology.get_node_hostname(node)
198 if not hostname in self._nodeconfig:
199 self._nodeconfig[hostname] = {}
200 if not 'max_tx_queues_config' in self._nodeconfig[hostname]:
201 self._nodeconfig[hostname]['max_tx_queues_config'] = []
202 self._nodeconfig[hostname]['max_tx_queues_config'].append(
203 max_tx_queues_config)
204 logger.debug('Setting hostname {} max_tx_queues config to {}'.\
205 format(hostname, max_tx_queues_config))
207 def remove_all_pci_devices(self, node):
208 """Remove PCI device configuration from node.
210 :param node: DUT node.
214 if node['type'] != NodeType.DUT:
215 raise ValueError('Node type is not a DUT')
216 hostname = Topology.get_node_hostname(node)
217 if hostname in self._nodeconfig:
218 self._nodeconfig[hostname]['pci_addrs'] = []
219 logger.debug('Clearing all PCI devices for hostname {}.'.
222 def remove_all_cpu_config(self, node):
223 """Remove CPU configuration from node.
225 :param node: DUT node.
229 if node['type'] != NodeType.DUT:
230 raise ValueError('Node type is not a DUT')
231 hostname = Topology.get_node_hostname(node)
232 if hostname in self._nodeconfig:
233 self._nodeconfig[hostname]['cpu_config'] = []
234 logger.debug('Clearing all CPU config for hostname {}.'.
237 def remove_socketmem_config(self, node):
238 """Remove Socket Memory configuration from node.
240 :param node: DUT node.
244 if node['type'] != NodeType.DUT:
245 raise ValueError('Node type is not a DUT')
246 hostname = Topology.get_node_hostname(node)
247 if hostname in self._nodeconfig:
248 self._nodeconfig[hostname].pop('socketmem_config', None)
249 logger.debug('Clearing Socket Memory config for hostname {}.'.
252 def remove_heapsize_config(self, node):
253 """Remove Heap Size configuration from node.
255 :param node: DUT node.
259 if node['type'] != NodeType.DUT:
260 raise ValueError('Node type is not a DUT')
261 hostname = Topology.get_node_hostname(node)
262 if hostname in self._nodeconfig:
263 self._nodeconfig[hostname].pop('heapsize_config', None)
264 logger.debug('Clearing Heap Size config for hostname {}.'.
267 def remove_rss_config(self, node):
268 """Remove RSS configuration from node.
270 :param node: DUT node.
274 if node['type'] != NodeType.DUT:
275 raise ValueError('Node type is not a DUT')
276 hostname = Topology.get_node_hostname(node)
277 if hostname in self._nodeconfig:
278 self._nodeconfig[hostname]['rss_config'] = []
279 logger.debug('Clearing RSS config for hostname {}.'.\
282 def remove_max_tx_queues_config(self, node):
283 """Remove Max TX Queues configuration from node.
285 :param node: DUT node.
289 if node['type'] != NodeType.DUT:
290 raise ValueError('Node type is not a DUT')
291 hostname = Topology.get_node_hostname(node)
292 if hostname in self._nodeconfig:
293 self._nodeconfig[hostname]['max_tx_queues_config'] = []
294 logger.debug('Clearing Max TX Queues config for hostname {}.'.\
297 def apply_config(self, node, waittime=5, retries=12):
298 """Generate and apply VPP configuration for node.
300 Use data from calls to this class to form a startup.conf file and
301 replace /etc/vpp/startup.conf with it on node.
303 :param node: DUT node.
304 :param waittime: Time to wait for VPP to restart (default 5 seconds).
305 :param retries: Number of times (default 12) to re-try waiting.
311 if node['type'] != NodeType.DUT:
312 raise ValueError('Node type is not a DUT')
313 hostname = Topology.get_node_hostname(node)
317 socketmemconfig = DEFAULT_SOCKETMEM_CONFIG
322 if hostname in self._nodeconfig:
323 cfg = self._nodeconfig[hostname]
324 if 'cpu_config' in cfg:
325 cpuconfig = " " + "\n ".join(cfg['cpu_config'])
327 if 'pci_addrs' in cfg:
328 pciconfig = " dev " + "\n dev ".join(cfg['pci_addrs'])
330 if 'socketmem_config' in cfg:
331 socketmemconfig = cfg['socketmem_config']
333 if 'heapsize_config' in cfg:
334 heapsizeconfig = "\nheapsize {}\n".\
335 format(cfg['heapsize_config'])
337 if 'rss_config' in cfg:
338 rssconfig = " " + "\n ".join(cfg['rss_config'])
340 if 'max_tx_queues_config' in cfg:
341 txqueuesconfig = " " + "\n ".join(
342 cfg['max_tx_queues_config'])
344 vppconfig = VPP_CONFIG_TEMPLATE.format(cpuconfig=cpuconfig,
346 socketmemconfig=socketmemconfig,
347 heapsizeconfig=heapsizeconfig,
349 txqueuesconfig=txqueuesconfig)
351 logger.debug('Writing VPP config to host {}: "{}"'.format(hostname,
357 # We're using this "| sudo tee" construct because redirecting
358 # a sudo'd outut ("sudo echo xxx > /path/to/file") does not
359 # work on most platforms...
360 (ret, stdout, stderr) = \
361 ssh.exec_command('echo "{0}" | sudo tee {1}'.
362 format(vppconfig, VPP_CONFIG_FILENAME))
365 logger.debug('Writing config file failed to node {}'.
367 logger.debug('stdout: {}'.format(stdout))
368 logger.debug('stderr: {}'.format(stderr))
369 raise RuntimeError('Writing config file failed to node {}'.
372 # Instead of restarting, we'll do separate start and stop
373 # actions. This way we don't care whether VPP was running
375 ssh.exec_command('sudo initctl stop {}'.format(VPP_SERVICE_NAME))
376 (ret, stdout, stderr) = \
377 ssh.exec_command('sudo initctl start {}'.format(VPP_SERVICE_NAME))
379 logger.debug('Restarting VPP failed on node {}'.
381 logger.debug('stdout: {}'.format(stdout))
382 logger.debug('stderr: {}'.format(stderr))
383 raise RuntimeError('Restarting VPP failed on node {}'.
386 # Sleep <waittime> seconds, up to <retry> times,
387 # and verify if VPP is running.
388 vpp_is_running = False
389 retries_left = retries
390 while (not vpp_is_running) and (retries_left > 0):
394 # FIXME: Need to find a good way to check if VPP is operational.
396 # If VatTerminal/VatExecutor is anything like vppctl or
397 # vpp_api_test, then in case VPP is NOT running it will block for
398 # 30 seconds or so and not even return if VPP becomes alive during
399 # that time. This makes it unsuitable in this case. We either need
400 # a call that returns immediately, indicating whether VPP is
401 # healthy or not, or a call that waits (up to a defined length
402 # of time) and returns immediately if VPP is or becomes healthy.
403 (ret, stdout, stderr) = \
404 ssh.exec_command('echo show hardware-interfaces | '
408 vpp_is_running = True
410 logger.debug('VPP not yet running, {} retries left'.
411 format(retries_left))
412 if retries_left == 0:
413 raise RuntimeError('VPP failed to restart on node {}'.
415 logger.debug('VPP interfaces found on node {}'.