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}}
58 # End VPP configuration template.
61 class VppConfigGenerator(object):
62 """VPP Configuration File Generator"""
67 def add_pci_device(self, node, pci_device=None):
68 """Add PCI device configuration for node.
71 :param pci_device: PCI device (format 0000:00:00.0 or 00:00.0).
72 If none given, all PCI devices for this node as per topology will be
75 :type pci_device: string
78 if node['type'] != NodeType.DUT:
79 raise ValueError('Node type is not a DUT')
81 if pci_device is None:
82 # No PCI device was given. Add all device from topology.
83 for port in node['interfaces'].values():
84 port_name = port.get('name')
85 pci_addr = Topology.get_interface_pci_addr(node, port_name)
87 self.add_pci_device(node, pci_addr)
89 # Specific device was given.
90 hostname = Topology.get_node_hostname(node)
92 pattern = re.compile("^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:"\
93 "[0-9A-Fa-f]{2}\\.[0-9A-Fa-f]$")
94 if not pattern.match(pci_device):
95 raise ValueError('PCI address {} to be added to host {} '\
96 'is not in valid format xxxx:xx:xx.x'.\
97 format(pci_device, hostname))
99 if not hostname in self._nodeconfig:
100 self._nodeconfig[hostname] = {}
101 if not 'pci_addrs' in self._nodeconfig[hostname]:
102 self._nodeconfig[hostname]['pci_addrs'] = []
103 self._nodeconfig[hostname]['pci_addrs'].append(pci_device)
104 logger.debug('Adding PCI device {1} to {0}'.format(hostname,\
107 def add_cpu_config(self, node, cpu_config):
108 """Add CPU configuration for node.
110 :param node: DUT node
111 :param cpu_config: CPU configuration option, as a string
113 :type cpu_config: string
116 if node['type'] != NodeType.DUT:
117 raise ValueError('Node type is not a DUT')
118 hostname = Topology.get_node_hostname(node)
119 if not hostname in self._nodeconfig:
120 self._nodeconfig[hostname] = {}
121 if not 'cpu_config' in self._nodeconfig[hostname]:
122 self._nodeconfig[hostname]['cpu_config'] = []
123 self._nodeconfig[hostname]['cpu_config'].append(cpu_config)
124 logger.debug('Adding {} to hostname {} CPU config'.format(hostname, \
127 def add_socketmem_config(self, node, socketmem_config):
128 """Add Socket Memory configuration for node.
130 :param node: DUT node
131 :param socketmem_config: Socket Memory configuration option, as a string
133 :type cpu_config: string
136 if node['type'] != NodeType.DUT:
137 raise ValueError('Node type is not a DUT')
138 hostname = Topology.get_node_hostname(node)
139 if not hostname in self._nodeconfig:
140 self._nodeconfig[hostname] = {}
141 self._nodeconfig[hostname]['socketmem_config'] = socketmem_config
142 logger.debug('Setting hostname {} Socket Memory config to {}'.\
143 format(hostname, socketmem_config))
145 def add_heapsize_config(self, node, heapsize_config):
146 """Add Heap Size configuration for node.
148 :param node: DUT node
149 :param heapsize_config: Heap Size configuration, as a string
151 :type cpu_config: string
154 if node['type'] != NodeType.DUT:
155 raise ValueError('Node type is not a DUT')
156 hostname = Topology.get_node_hostname(node)
157 if not hostname in self._nodeconfig:
158 self._nodeconfig[hostname] = {}
159 self._nodeconfig[hostname]['heapsize_config'] = heapsize_config
160 logger.debug('Setting hostname {} Heap Size config to {}'.\
161 format(hostname, heapsize_config))
163 def remove_all_pci_devices(self, node):
164 """Remove PCI device configuration from node.
166 :param node: DUT node
170 if node['type'] != NodeType.DUT:
171 raise ValueError('Node type is not a DUT')
172 hostname = Topology.get_node_hostname(node)
173 if hostname in self._nodeconfig:
174 self._nodeconfig[hostname]['pci_addrs'] = []
175 logger.debug('Clearing all PCI devices for hostname {}.'.\
178 def remove_all_cpu_config(self, node):
179 """Remove CPU configuration from node.
181 :param node: DUT node
185 if node['type'] != NodeType.DUT:
186 raise ValueError('Node type is not a DUT')
187 hostname = Topology.get_node_hostname(node)
188 if hostname in self._nodeconfig:
189 self._nodeconfig[hostname]['cpu_config'] = []
190 logger.debug('Clearing all CPU config for hostname {}.'.\
193 def remove_socketmem_config(self, node):
194 """Remove Socket Memory configuration from node.
196 :param node: DUT node
200 if node['type'] != NodeType.DUT:
201 raise ValueError('Node type is not a DUT')
202 hostname = Topology.get_node_hostname(node)
203 if hostname in self._nodeconfig:
204 self._nodeconfig[hostname].pop('socketmem_config', None)
205 logger.debug('Clearing Socket Memory config for hostname {}.'.\
208 def remove_heapsize_config(self, node):
209 """Remove Heap Size 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].pop('heapsize_config', None)
220 logger.debug('Clearing Heap Size config for hostname {}.'.\
223 def apply_config(self, node, waittime=5, retries=12):
224 """Generate and apply VPP configuration for node.
226 Use data from calls to this class to form a startup.conf file and
227 replace /etc/vpp/startup.conf with it on node.
229 :param node: DUT node
230 :param waittime: time to wait for VPP to restart (default 5 seconds)
231 :param retries: number of times (default 12) to re-try waiting
237 if node['type'] != NodeType.DUT:
238 raise ValueError('Node type is not a DUT')
239 hostname = Topology.get_node_hostname(node)
243 socketmemconfig = DEFAULT_SOCKETMEM_CONFIG
246 if hostname in self._nodeconfig:
247 cfg = self._nodeconfig[hostname]
248 if 'cpu_config' in cfg:
249 cpuconfig = " " + "\n ".join(cfg['cpu_config'])
251 if 'pci_addrs' in cfg:
252 pciconfig = " dev " + "\n dev ".join(cfg['pci_addrs'])
254 if 'socketmem_config' in cfg:
255 socketmemconfig = cfg['socketmem_config']
257 if 'heapsize_config' in cfg:
258 heapsizeconfig = "\nheapsize {}\n".\
259 format(cfg['heapsize_config'])
261 vppconfig = VPP_CONFIG_TEMPLATE.format(cpuconfig=cpuconfig,
263 socketmemconfig=socketmemconfig,
264 heapsizeconfig=heapsizeconfig)
266 logger.debug('Writing VPP config to host {}: "{}"'.format(hostname,\
272 # We're using this "| sudo tee" construct because redirecting
273 # a sudo'd outut ("sudo echo xxx > /path/to/file") does not
274 # work on most platforms...
275 (ret, stdout, stderr) = \
276 ssh.exec_command('echo "{0}" | sudo tee {1}'.\
277 format(vppconfig, VPP_CONFIG_FILENAME))
280 logger.debug('Writing config file failed to node {}'.\
282 logger.debug('stdout: {}'.format(stdout))
283 logger.debug('stderr: {}'.format(stderr))
284 raise RuntimeError('Writing config file failed to node {}'.\
287 # Instead of restarting, we'll do separate start and stop
288 # actions. This way we don't care whether VPP was running
290 ssh.exec_command('sudo initctl stop {}'.format(VPP_SERVICE_NAME))
291 (ret, stdout, stderr) = \
292 ssh.exec_command('sudo initctl start {}'.format(VPP_SERVICE_NAME))
294 logger.debug('Restarting VPP failed on node {}'.\
296 logger.debug('stdout: {}'.format(stdout))
297 logger.debug('stderr: {}'.format(stderr))
298 raise RuntimeError('Restarting VPP failed on node {}'.\
301 # Sleep <waittime> seconds, up to <retry> times,
302 # and verify if VPP is running.
303 vpp_is_running = False
304 retries_left = retries
305 while (not vpp_is_running) and (retries_left > 0):
309 # FIXME: Need to find a good way to check if VPP is operational.
311 # If VatTerminal/VatExecutor is anything like vppctl or
312 # vpp_api_test, then in case VPP is NOT running it will block for
313 # 30 seconds or so and not even return if VPP becomes alive during
314 # that time. This makes it unsuitable in this case. We either need
315 # a call that returns immediately, indicating whether VPP is
316 # healthy or not, or a call that waits (up to a defined length
317 # of time) and returns immediately if VPP is or becomes healthy.
318 (ret, stdout, stderr) = \
319 ssh.exec_command('echo show hardware-interfaces | '\
323 vpp_is_running = True
325 logger.debug('VPP not yet running, {} retries left'.\
326 format(retries_left))
327 if retries_left == 0:
328 raise RuntimeError('VPP failed to restart on node {}'.\
330 logger.debug('VPP interfaces found on node {}'.\