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}
59 # End VPP configuration template.
62 class VppConfigGenerator(object):
63 """VPP Configuration File Generator"""
68 def add_pci_device(self, node, pci_device=None):
69 """Add PCI device configuration for node.
71 :param node: DUT node.
72 :param pci_device: PCI device (format 0000:00:00.0 or 00:00.0).
73 If none given, all PCI devices for this node as per topology will be
79 if node['type'] != NodeType.DUT:
80 raise ValueError('Node type is not a DUT')
82 if pci_device is None:
83 # No PCI device was given. Add all device from topology.
84 for port in node['interfaces'].values():
85 port_name = port.get('name')
86 pci_addr = Topology.get_interface_pci_addr(node, port_name)
88 self.add_pci_device(node, pci_addr)
90 # Specific device was given.
91 hostname = Topology.get_node_hostname(node)
93 pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"
94 "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
95 if not pattern.match(pci_device):
96 raise ValueError('PCI address {} to be added to host {} '
97 'is not in valid format xxxx:xx:xx.x'.
98 format(pci_device, hostname))
100 if hostname not in self._nodeconfig:
101 self._nodeconfig[hostname] = {}
102 if 'pci_addrs' not in self._nodeconfig[hostname]:
103 self._nodeconfig[hostname]['pci_addrs'] = []
104 self._nodeconfig[hostname]['pci_addrs'].append(pci_device)
105 logger.debug('Adding PCI device {1} to {0}'.format(hostname,
108 def add_cpu_config(self, node, cpu_config):
109 """Add CPU configuration for node.
111 :param node: DUT node.
112 :param cpu_config: CPU configuration option, as a string.
114 :type cpu_config: str
117 if node['type'] != NodeType.DUT:
118 raise ValueError('Node type is not a DUT')
119 hostname = Topology.get_node_hostname(node)
120 if hostname not in self._nodeconfig:
121 self._nodeconfig[hostname] = {}
122 if 'cpu_config' not in self._nodeconfig[hostname]:
123 self._nodeconfig[hostname]['cpu_config'] = []
124 self._nodeconfig[hostname]['cpu_config'].append(cpu_config)
125 logger.debug('Adding {} to hostname {} CPU config'.format(hostname,
128 def add_socketmem_config(self, node, socketmem_config):
129 """Add Socket Memory configuration for node.
131 :param node: DUT node.
132 :param socketmem_config: Socket Memory configuration option,
135 :type socketmem_config: str
138 if node['type'] != NodeType.DUT:
139 raise ValueError('Node type is not a DUT')
140 hostname = Topology.get_node_hostname(node)
141 if hostname not in self._nodeconfig:
142 self._nodeconfig[hostname] = {}
143 self._nodeconfig[hostname]['socketmem_config'] = socketmem_config
144 logger.debug('Setting hostname {} Socket Memory config to {}'.
145 format(hostname, socketmem_config))
147 def add_heapsize_config(self, node, heapsize_config):
148 """Add Heap Size configuration for node.
150 :param node: DUT node.
151 :param heapsize_config: Heap Size configuration, as a string.
153 :type heapsize_config: str
156 if node['type'] != NodeType.DUT:
157 raise ValueError('Node type is not a DUT')
158 hostname = Topology.get_node_hostname(node)
159 if hostname not in self._nodeconfig:
160 self._nodeconfig[hostname] = {}
161 self._nodeconfig[hostname]['heapsize_config'] = heapsize_config
162 logger.debug('Setting hostname {} Heap Size config to {}'.
163 format(hostname, heapsize_config))
165 def add_rss_config(self, node, rss_config):
166 """Add RSS configuration for node.
168 :param node: DUT node.
169 :param rss_config: RSS configuration, as a string.
171 :type rss_config: str
174 if node['type'] != NodeType.DUT:
175 raise ValueError('Node type is not a DUT')
176 hostname = Topology.get_node_hostname(node)
177 if not hostname in self._nodeconfig:
178 self._nodeconfig[hostname] = {}
179 if not 'rss_config' in self._nodeconfig[hostname]:
180 self._nodeconfig[hostname]['rss_config'] = []
181 self._nodeconfig[hostname]['rss_config'].append(rss_config)
182 logger.debug('Setting hostname {} RSS config to {}'.\
183 format(hostname, rss_config))
185 def remove_all_pci_devices(self, node):
186 """Remove PCI device configuration from node.
188 :param node: DUT node.
192 if node['type'] != NodeType.DUT:
193 raise ValueError('Node type is not a DUT')
194 hostname = Topology.get_node_hostname(node)
195 if hostname in self._nodeconfig:
196 self._nodeconfig[hostname]['pci_addrs'] = []
197 logger.debug('Clearing all PCI devices for hostname {}.'.
200 def remove_all_cpu_config(self, node):
201 """Remove CPU configuration from 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 hostname in self._nodeconfig:
211 self._nodeconfig[hostname]['cpu_config'] = []
212 logger.debug('Clearing all CPU config for hostname {}.'.
215 def remove_socketmem_config(self, node):
216 """Remove Socket Memory configuration from node.
218 :param node: DUT node.
222 if node['type'] != NodeType.DUT:
223 raise ValueError('Node type is not a DUT')
224 hostname = Topology.get_node_hostname(node)
225 if hostname in self._nodeconfig:
226 self._nodeconfig[hostname].pop('socketmem_config', None)
227 logger.debug('Clearing Socket Memory config for hostname {}.'.
230 def remove_heapsize_config(self, node):
231 """Remove Heap Size configuration from node.
233 :param node: DUT node.
237 if node['type'] != NodeType.DUT:
238 raise ValueError('Node type is not a DUT')
239 hostname = Topology.get_node_hostname(node)
240 if hostname in self._nodeconfig:
241 self._nodeconfig[hostname].pop('heapsize_config', None)
242 logger.debug('Clearing Heap Size config for hostname {}.'.
245 def remove_rss_config(self, node):
246 """Remove RSS configuration from node.
248 :param node: DUT node.
252 if node['type'] != NodeType.DUT:
253 raise ValueError('Node type is not a DUT')
254 hostname = Topology.get_node_hostname(node)
255 if hostname in self._nodeconfig:
256 self._nodeconfig[hostname]['rss_config'] = []
257 logger.debug('Clearing RSS config for hostname {}.'.\
260 def apply_config(self, node, waittime=5, retries=12):
261 """Generate and apply VPP configuration for node.
263 Use data from calls to this class to form a startup.conf file and
264 replace /etc/vpp/startup.conf with it on node.
266 :param node: DUT node.
267 :param waittime: Time to wait for VPP to restart (default 5 seconds).
268 :param retries: Number of times (default 12) to re-try waiting.
274 if node['type'] != NodeType.DUT:
275 raise ValueError('Node type is not a DUT')
276 hostname = Topology.get_node_hostname(node)
280 socketmemconfig = DEFAULT_SOCKETMEM_CONFIG
284 if hostname in self._nodeconfig:
285 cfg = self._nodeconfig[hostname]
286 if 'cpu_config' in cfg:
287 cpuconfig = " " + "\n ".join(cfg['cpu_config'])
289 if 'pci_addrs' in cfg:
290 pciconfig = " dev " + "\n dev ".join(cfg['pci_addrs'])
292 if 'socketmem_config' in cfg:
293 socketmemconfig = cfg['socketmem_config']
295 if 'heapsize_config' in cfg:
296 heapsizeconfig = "\nheapsize {}\n".\
297 format(cfg['heapsize_config'])
299 if 'rss_config' in cfg:
300 rssconfig = " " + "\n ".join(cfg['rss_config'])
302 vppconfig = VPP_CONFIG_TEMPLATE.format(cpuconfig=cpuconfig,
304 socketmemconfig=socketmemconfig,
305 heapsizeconfig=heapsizeconfig,
308 logger.debug('Writing VPP config to host {}: "{}"'.format(hostname,
314 # We're using this "| sudo tee" construct because redirecting
315 # a sudo'd outut ("sudo echo xxx > /path/to/file") does not
316 # work on most platforms...
317 (ret, stdout, stderr) = \
318 ssh.exec_command('echo "{0}" | sudo tee {1}'.
319 format(vppconfig, VPP_CONFIG_FILENAME))
322 logger.debug('Writing config file failed to node {}'.
324 logger.debug('stdout: {}'.format(stdout))
325 logger.debug('stderr: {}'.format(stderr))
326 raise RuntimeError('Writing config file failed to node {}'.
329 # Instead of restarting, we'll do separate start and stop
330 # actions. This way we don't care whether VPP was running
332 ssh.exec_command('sudo initctl stop {}'.format(VPP_SERVICE_NAME))
333 (ret, stdout, stderr) = \
334 ssh.exec_command('sudo initctl start {}'.format(VPP_SERVICE_NAME))
336 logger.debug('Restarting VPP failed on node {}'.
338 logger.debug('stdout: {}'.format(stdout))
339 logger.debug('stderr: {}'.format(stderr))
340 raise RuntimeError('Restarting VPP failed on node {}'.
343 # Sleep <waittime> seconds, up to <retry> times,
344 # and verify if VPP is running.
345 vpp_is_running = False
346 retries_left = retries
347 while (not vpp_is_running) and (retries_left > 0):
351 # FIXME: Need to find a good way to check if VPP is operational.
353 # If VatTerminal/VatExecutor is anything like vppctl or
354 # vpp_api_test, then in case VPP is NOT running it will block for
355 # 30 seconds or so and not even return if VPP becomes alive during
356 # that time. This makes it unsuitable in this case. We either need
357 # a call that returns immediately, indicating whether VPP is
358 # healthy or not, or a call that waits (up to a defined length
359 # of time) and returns immediately if VPP is or becomes healthy.
360 (ret, stdout, stderr) = \
361 ssh.exec_command('echo show hardware-interfaces | '
365 vpp_is_running = True
367 logger.debug('VPP not yet running, {} retries left'.
368 format(retries_left))
369 if retries_left == 0:
370 raise RuntimeError('VPP failed to restart on node {}'.
372 logger.debug('VPP interfaces found on node {}'.