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}
63 # End VPP configuration template.
66 class VppConfigGenerator(object):
67 """VPP Configuration File Generator"""
72 def add_pci_device(self, node, pci_device=None):
73 """Add PCI device configuration for node.
75 :param node: DUT node.
76 :param pci_device: PCI device (format 0000:00:00.0 or 00:00.0).
77 If none given, all PCI devices for this node as per topology will be
83 if node['type'] != NodeType.DUT:
84 raise ValueError('Node type is not a DUT')
86 if pci_device is None:
87 # No PCI device was given. Add all device from topology.
88 for port in node['interfaces'].values():
89 port_name = port.get('name')
90 pci_addr = Topology.get_interface_pci_addr(node, port_name)
92 self.add_pci_device(node, pci_addr)
94 # Specific device was given.
95 hostname = Topology.get_node_hostname(node)
97 pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
98 "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
99 if not pattern.match(pci_device):
100 raise ValueError('PCI address {} to be added to host {} '
101 'is not in valid format xxxx:xx:xx.x'.
102 format(pci_device, hostname))
104 if hostname not in self._nodeconfig:
105 self._nodeconfig[hostname] = {}
106 if 'pci_addrs' not in self._nodeconfig[hostname]:
107 self._nodeconfig[hostname]['pci_addrs'] = []
108 self._nodeconfig[hostname]['pci_addrs'].append(pci_device)
109 logger.debug('Adding PCI device {1} to {0}'.format(hostname,
112 def add_cpu_config(self, node, cpu_config):
113 """Add CPU configuration for node.
115 :param node: DUT node.
116 :param cpu_config: CPU configuration option, as a string.
118 :type cpu_config: str
121 if node['type'] != NodeType.DUT:
122 raise ValueError('Node type is not a DUT')
123 hostname = Topology.get_node_hostname(node)
124 if hostname not in self._nodeconfig:
125 self._nodeconfig[hostname] = {}
126 if 'cpu_config' not in self._nodeconfig[hostname]:
127 self._nodeconfig[hostname]['cpu_config'] = []
128 self._nodeconfig[hostname]['cpu_config'].append(cpu_config)
129 logger.debug('Adding {} to hostname {} CPU config'.format(hostname,
132 def add_socketmem_config(self, node, socketmem_config):
133 """Add Socket Memory configuration for node.
135 :param node: DUT node.
136 :param socketmem_config: Socket Memory configuration option,
139 :type socketmem_config: str
142 if node['type'] != NodeType.DUT:
143 raise ValueError('Node type is not a DUT')
144 hostname = Topology.get_node_hostname(node)
145 if hostname not in self._nodeconfig:
146 self._nodeconfig[hostname] = {}
147 self._nodeconfig[hostname]['socketmem_config'] = socketmem_config
148 logger.debug('Setting hostname {} Socket Memory config to {}'.
149 format(hostname, socketmem_config))
151 def add_heapsize_config(self, node, heapsize_config):
152 """Add Heap Size configuration for node.
154 :param node: DUT node.
155 :param heapsize_config: Heap Size configuration, as a string.
157 :type heapsize_config: str
160 if node['type'] != NodeType.DUT:
161 raise ValueError('Node type is not a DUT')
162 hostname = Topology.get_node_hostname(node)
163 if hostname not in self._nodeconfig:
164 self._nodeconfig[hostname] = {}
165 self._nodeconfig[hostname]['heapsize_config'] = heapsize_config
166 logger.debug('Setting hostname {} Heap Size config to {}'.
167 format(hostname, heapsize_config))
169 def add_rxqueues_config(self, node, rxqueues_config):
170 """Add Rx Queues configuration for node.
172 :param node: DUT node.
173 :param rxqueues_config: Rxqueues configuration, as a string.
175 :type rxqueues_config: str
178 if node['type'] != NodeType.DUT:
179 raise ValueError('Node type is not a DUT')
180 hostname = Topology.get_node_hostname(node)
181 if not hostname in self._nodeconfig:
182 self._nodeconfig[hostname] = {}
183 if not 'rxqueues_config' in self._nodeconfig[hostname]:
184 self._nodeconfig[hostname]['rxqueues_config'] = []
185 self._nodeconfig[hostname]['rxqueues_config'].append(rxqueues_config)
186 logger.debug('Setting hostname {} rxqueues config to {}'.\
187 format(hostname, rxqueues_config))
189 def add_no_multi_seg_config(self, node):
190 """Add No Multi Seg configuration for node.
192 :param node: DUT node.
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 'no_multi_seg_config' in self._nodeconfig[hostname]:
202 self._nodeconfig[hostname]['no_multi_seg_config'] = []
203 self._nodeconfig[hostname]['no_multi_seg_config'].append(
205 logger.debug('Setting hostname {} config with {}'.\
206 format(hostname, "no-multi-seg"))
208 def remove_all_pci_devices(self, node):
209 """Remove PCI device configuration from 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 hostname in self._nodeconfig:
219 self._nodeconfig[hostname]['pci_addrs'] = []
220 logger.debug('Clearing all PCI devices for hostname {}.'.
223 def remove_all_cpu_config(self, node):
224 """Remove CPU configuration from node.
226 :param node: DUT node.
230 if node['type'] != NodeType.DUT:
231 raise ValueError('Node type is not a DUT')
232 hostname = Topology.get_node_hostname(node)
233 if hostname in self._nodeconfig:
234 self._nodeconfig[hostname]['cpu_config'] = []
235 logger.debug('Clearing all CPU config for hostname {}.'.
238 def remove_socketmem_config(self, node):
239 """Remove Socket Memory 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].pop('socketmem_config', None)
250 logger.debug('Clearing Socket Memory config for hostname {}.'.
253 def remove_heapsize_config(self, node):
254 """Remove Heap Size 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].pop('heapsize_config', None)
265 logger.debug('Clearing Heap Size config for hostname {}.'.
268 def remove_rxqueues_config(self, node):
269 """Remove Rxqueues 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]['rxqueues_config'] = []
280 logger.debug('Clearing rxqueues config for hostname {}.'.\
283 def remove_no_multi_seg_config(self, node):
284 """Remove No Multi Seg 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]['no_multi_seg_config'] = []
295 logger.debug('Clearing No Multi Seg config for hostname {}.'.\
298 def apply_config(self, node, waittime=5, retries=12):
299 """Generate and apply VPP configuration for node.
301 Use data from calls to this class to form a startup.conf file and
302 replace /etc/vpp/startup.conf with it on node.
304 :param node: DUT node.
305 :param waittime: Time to wait for VPP to restart (default 5 seconds).
306 :param retries: Number of times (default 12) to re-try waiting.
312 if node['type'] != NodeType.DUT:
313 raise ValueError('Node type is not a DUT')
314 hostname = Topology.get_node_hostname(node)
318 socketmemconfig = DEFAULT_SOCKETMEM_CONFIG
324 if hostname in self._nodeconfig:
325 cfg = self._nodeconfig[hostname]
326 if 'cpu_config' in cfg:
327 cpuconfig = " " + "\n ".join(cfg['cpu_config'])
329 if 'pci_addrs' in cfg:
330 pciconfig = " dev " + "\n dev ".join(cfg['pci_addrs'])
332 if 'socketmem_config' in cfg:
333 socketmemconfig = cfg['socketmem_config']
335 if 'heapsize_config' in cfg:
336 heapsizeconfig = "\nheapsize {}\n".\
337 format(cfg['heapsize_config'])
339 if 'rxqueues_config' in cfg:
340 rxqueuesconfig = " " + "\n ".join(cfg['rxqueues_config'])
342 if 'no_multi_seg_config' in cfg:
343 nomultiseg = " " + "\n ".join(cfg['no_multi_seg_config'])
345 vppconfig = VPP_CONFIG_TEMPLATE.format(cpuconfig=cpuconfig,
347 socketmemconfig=socketmemconfig,
348 heapsizeconfig=heapsizeconfig,
349 rxqueuesconfig=rxqueuesconfig,
350 txqueuesconfig=txqueuesconfig,
351 nomultiseg = nomultiseg)
353 logger.debug('Writing VPP config to host {}: "{}"'.format(hostname,
359 # We're using this "| sudo tee" construct because redirecting
360 # a sudo'd outut ("sudo echo xxx > /path/to/file") does not
361 # work on most platforms...
362 (ret, stdout, stderr) = \
363 ssh.exec_command('echo "{0}" | sudo tee {1}'.
364 format(vppconfig, VPP_CONFIG_FILENAME))
367 logger.debug('Writing config file failed to node {}'.
369 logger.debug('stdout: {}'.format(stdout))
370 logger.debug('stderr: {}'.format(stderr))
371 raise RuntimeError('Writing config file failed to node {}'.
374 # Instead of restarting, we'll do separate start and stop
375 # actions. This way we don't care whether VPP was running
377 ssh.exec_command('sudo initctl stop {}'.format(VPP_SERVICE_NAME))
378 (ret, stdout, stderr) = \
379 ssh.exec_command('sudo initctl start {}'.format(VPP_SERVICE_NAME))
381 logger.debug('Restarting VPP failed on node {}'.
383 logger.debug('stdout: {}'.format(stdout))
384 logger.debug('stderr: {}'.format(stderr))
385 raise RuntimeError('Restarting VPP failed on node {}'.
388 # Sleep <waittime> seconds, up to <retry> times,
389 # and verify if VPP is running.
390 vpp_is_running = False
391 retries_left = retries
392 while (not vpp_is_running) and (retries_left > 0):
396 # FIXME: Need to find a good way to check if VPP is operational.
398 # If VatTerminal/VatExecutor is anything like vppctl or
399 # vpp_api_test, then in case VPP is NOT running it will block for
400 # 30 seconds or so and not even return if VPP becomes alive during
401 # that time. This makes it unsuitable in this case. We either need
402 # a call that returns immediately, indicating whether VPP is
403 # healthy or not, or a call that waits (up to a defined length
404 # of time) and returns immediately if VPP is or becomes healthy.
405 (ret, stdout, stderr) = \
406 ssh.exec_command('echo show hardware-interfaces | '
410 vpp_is_running = True
412 logger.debug('VPP not yet running, {} retries left'.
413 format(retries_left))
414 if retries_left == 0:
415 raise RuntimeError('VPP failed to restart on node {}'.
417 logger.debug('VPP interfaces found on node {}'.