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"""
16 from robot.api import logger
18 from resources.libraries.python.ssh import SSH
19 from resources.libraries.python.topology import NodeType
20 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.
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
76 :type pci_device: string
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 not hostname in self._nodeconfig:
101 self._nodeconfig[hostname] = {}
102 if not 'pci_addrs' 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: string
117 if node['type'] != NodeType.DUT:
118 raise ValueError('Node type is not a DUT')
119 hostname = Topology.get_node_hostname(node)
120 if not hostname in self._nodeconfig:
121 self._nodeconfig[hostname] = {}
122 if not 'cpu_config' 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, as a string
134 :type cpu_config: string
137 if node['type'] != NodeType.DUT:
138 raise ValueError('Node type is not a DUT')
139 hostname = Topology.get_node_hostname(node)
140 if not hostname in self._nodeconfig:
141 self._nodeconfig[hostname] = {}
142 self._nodeconfig[hostname]['socketmem_config'] = socketmem_config
143 logger.debug('Setting hostname {} Socket Memory config to {}'.\
144 format(hostname, socketmem_config))
146 def add_heapsize_config(self, node, heapsize_config):
147 """Add Heap Size configuration for node.
149 :param node: DUT node
150 :param heapsize_config: Heap Size configuration, as a string
152 :type cpu_config: string
155 if node['type'] != NodeType.DUT:
156 raise ValueError('Node type is not a DUT')
157 hostname = Topology.get_node_hostname(node)
158 if not hostname in self._nodeconfig:
159 self._nodeconfig[hostname] = {}
160 self._nodeconfig[hostname]['heapsize_config'] = heapsize_config
161 logger.debug('Setting hostname {} Heap Size config to {}'.\
162 format(hostname, heapsize_config))
164 def add_rss_config(self, node, rss_config):
165 """Add RSS configuration for node.
167 :param node: DUT node
168 :param rss_config: RSS configuration, as a string
170 :type rss_config: string
173 if node['type'] != NodeType.DUT:
174 raise ValueError('Node type is not a DUT')
175 hostname = Topology.get_node_hostname(node)
176 if not hostname in self._nodeconfig:
177 self._nodeconfig[hostname] = {}
178 if not 'rss_config' in self._nodeconfig[hostname]:
179 self._nodeconfig[hostname]['rss_config'] = []
180 self._nodeconfig[hostname]['rss_config'].append(rss_config)
181 logger.debug('Setting hostname {} RSS config to {}'.\
182 format(hostname, rss_config))
184 def remove_all_pci_devices(self, node):
185 """Remove PCI device configuration from node.
187 :param node: DUT node
191 if node['type'] != NodeType.DUT:
192 raise ValueError('Node type is not a DUT')
193 hostname = Topology.get_node_hostname(node)
194 if hostname in self._nodeconfig:
195 self._nodeconfig[hostname]['pci_addrs'] = []
196 logger.debug('Clearing all PCI devices for hostname {}.'.\
199 def remove_all_cpu_config(self, node):
200 """Remove CPU configuration from node.
202 :param node: DUT node
206 if node['type'] != NodeType.DUT:
207 raise ValueError('Node type is not a DUT')
208 hostname = Topology.get_node_hostname(node)
209 if hostname in self._nodeconfig:
210 self._nodeconfig[hostname]['cpu_config'] = []
211 logger.debug('Clearing all CPU config for hostname {}.'.\
214 def remove_socketmem_config(self, node):
215 """Remove Socket Memory configuration from node.
217 :param node: DUT node
221 if node['type'] != NodeType.DUT:
222 raise ValueError('Node type is not a DUT')
223 hostname = Topology.get_node_hostname(node)
224 if hostname in self._nodeconfig:
225 self._nodeconfig[hostname].pop('socketmem_config', None)
226 logger.debug('Clearing Socket Memory config for hostname {}.'.\
229 def remove_heapsize_config(self, node):
230 """Remove Heap Size configuration from node.
232 :param node: DUT node
236 if node['type'] != NodeType.DUT:
237 raise ValueError('Node type is not a DUT')
238 hostname = Topology.get_node_hostname(node)
239 if hostname in self._nodeconfig:
240 self._nodeconfig[hostname].pop('heapsize_config', None)
241 logger.debug('Clearing Heap Size config for hostname {}.'.\
244 def remove_rss_config(self, node):
245 """Remove RSS configuration from node.
247 :param node: DUT node
251 if node['type'] != NodeType.DUT:
252 raise ValueError('Node type is not a DUT')
253 hostname = Topology.get_node_hostname(node)
254 if hostname in self._nodeconfig:
255 self._nodeconfig[hostname]['rss_config'] = []
256 logger.debug('Clearing RSS config for hostname {}.'.\
259 def apply_config(self, node, waittime=5, retries=12):
260 """Generate and apply VPP configuration for node.
262 Use data from calls to this class to form a startup.conf file and
263 replace /etc/vpp/startup.conf with it on node.
265 :param node: DUT node
266 :param waittime: time to wait for VPP to restart (default 5 seconds)
267 :param retries: number of times (default 12) to re-try waiting
273 if node['type'] != NodeType.DUT:
274 raise ValueError('Node type is not a DUT')
275 hostname = Topology.get_node_hostname(node)
279 socketmemconfig = DEFAULT_SOCKETMEM_CONFIG
283 if hostname in self._nodeconfig:
284 cfg = self._nodeconfig[hostname]
285 if 'cpu_config' in cfg:
286 cpuconfig = " " + "\n ".join(cfg['cpu_config'])
288 if 'pci_addrs' in cfg:
289 pciconfig = " dev " + "\n dev ".join(cfg['pci_addrs'])
291 if 'socketmem_config' in cfg:
292 socketmemconfig = cfg['socketmem_config']
294 if 'heapsize_config' in cfg:
295 heapsizeconfig = "\nheapsize {}\n".\
296 format(cfg['heapsize_config'])
298 if 'rss_config' in cfg:
299 rssconfig = " " + "\n ".join(cfg['rss_config'])
301 vppconfig = VPP_CONFIG_TEMPLATE.format(cpuconfig=cpuconfig,
303 socketmemconfig=socketmemconfig,
304 heapsizeconfig=heapsizeconfig,
307 logger.debug('Writing VPP config to host {}: "{}"'.format(hostname,\
313 # We're using this "| sudo tee" construct because redirecting
314 # a sudo'd outut ("sudo echo xxx > /path/to/file") does not
315 # work on most platforms...
316 (ret, stdout, stderr) = \
317 ssh.exec_command('echo "{0}" | sudo tee {1}'.\
318 format(vppconfig, VPP_CONFIG_FILENAME))
321 logger.debug('Writing config file failed to node {}'.\
323 logger.debug('stdout: {}'.format(stdout))
324 logger.debug('stderr: {}'.format(stderr))
325 raise RuntimeError('Writing config file failed to node {}'.\
328 # Instead of restarting, we'll do separate start and stop
329 # actions. This way we don't care whether VPP was running
331 ssh.exec_command('sudo initctl stop {}'.format(VPP_SERVICE_NAME))
332 (ret, stdout, stderr) = \
333 ssh.exec_command('sudo initctl start {}'.format(VPP_SERVICE_NAME))
335 logger.debug('Restarting VPP failed on node {}'.\
337 logger.debug('stdout: {}'.format(stdout))
338 logger.debug('stderr: {}'.format(stderr))
339 raise RuntimeError('Restarting VPP failed on node {}'.\
342 # Sleep <waittime> seconds, up to <retry> times,
343 # and verify if VPP is running.
344 vpp_is_running = False
345 retries_left = retries
346 while (not vpp_is_running) and (retries_left > 0):
350 # FIXME: Need to find a good way to check if VPP is operational.
352 # If VatTerminal/VatExecutor is anything like vppctl or
353 # vpp_api_test, then in case VPP is NOT running it will block for
354 # 30 seconds or so and not even return if VPP becomes alive during
355 # that time. This makes it unsuitable in this case. We either need
356 # a call that returns immediately, indicating whether VPP is
357 # healthy or not, or a call that waits (up to a defined length
358 # of time) and returns immediately if VPP is or becomes healthy.
359 (ret, stdout, stderr) = \
360 ssh.exec_command('echo show hardware-interfaces | '\
364 vpp_is_running = True
366 logger.debug('VPP not yet running, {} retries left'.\
367 format(retries_left))
368 if retries_left == 0:
369 raise RuntimeError('VPP failed to restart on node {}'.\
371 logger.debug('VPP interfaces found on node {}'.\