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 util library"""
20 from collections import Counter
22 # VPP_VERSION = '1707'
23 # VPP_VERSION = '1710'
27 class VPPUtil(object):
28 """General class for any VPP related methods/functions."""
31 def exec_command(cmd, timeout=None):
32 """Execute a command on the local node.
34 :param cmd: Command to run locally.
35 :param timeout: Timeout value
38 :return return_code, stdout, stderr
39 :rtype: tuple(int, str, str)
42 logging.info(" Local Command: {}".format(cmd))
45 prc = subprocess.Popen(cmd, shell=True, bufsize=1,
46 stdin=subprocess.PIPE,
47 stdout=subprocess.PIPE,
48 stderr=subprocess.PIPE)
51 for line in iter(prc.stdout.readline, b''):
52 logging.info(" {}".format(line.strip('\n')))
56 for line in iter(prc.stderr.readline, b''):
57 logging.warn(" {}".format(line.strip('\n')))
64 def _autoconfig_backup_file(self, filename):
68 :param filename: The file to backup
72 # Does a copy of the file exist, if not create one
73 ofile = filename + '.orig'
74 (ret, stdout, stderr) = self.exec_command('ls {}'.format(ofile))
77 if stdout.strip('\n') != ofile:
78 cmd = 'sudo cp {} {}'.format(filename, ofile)
79 (ret, stdout, stderr) = self.exec_command(cmd)
83 def _install_vpp_pkg_ubuntu(self, node, pkg):
85 Install the VPP packages
87 :param node: Node dictionary
88 :param pkg: The vpp packages
93 cmd = 'apt-get -y install {}'.format(pkg)
94 (ret, stdout, stderr) = self.exec_command(cmd)
96 raise RuntimeError('{} failed on node {} {} {}'.format(
97 cmd, node['host'], stdout, stderr))
99 def _install_vpp_pkg_centos(self, node, pkg):
101 Install the VPP packages
103 :param node: Node dictionary
104 :param pkg: The vpp packages
109 cmd = 'yum -y install {}'.format(pkg)
110 (ret, stdout, stderr) = self.exec_command(cmd)
112 raise RuntimeError('{} failed on node {} {} {}'.format(
113 cmd, node['host'], stdout, stderr))
115 def _install_vpp_ubuntu(self, node, fdio_release=VPP_VERSION,
116 ubuntu_version='xenial'):
118 Install the VPP packages
120 :param node: Node dictionary with cpuinfo.
121 :param fdio_release: VPP release number
122 :param ubuntu_version: Ubuntu Version
124 :type fdio_release: string
125 :type ubuntu_version: string
128 # Modify the sources list
129 sfile = '/etc/apt/sources.list.d/99fd.io.list'
131 # Backup the sources list
132 self._autoconfig_backup_file(sfile)
134 # Remove the current file
135 cmd = 'rm {}'.format(sfile)
136 (ret, stdout, stderr) = self.exec_command(cmd)
138 logging.debug('{} failed on node {} {}'.format(
143 reps = 'deb [trusted=yes] https://nexus.fd.io/content/'
144 # When using a stable branch
145 # reps += 'repositories/fd.io.stable.{}.ubuntu.{}.main/ ./\n'.format(fdio_release, ubuntu_version)
147 reps += 'repositories/fd.io.ubuntu.{}.main/ ./\n'.format(ubuntu_version)
149 # reps += 'repositories/fd.io.master.ubuntu.{}.main/ ./\n'.format(ubuntu_version)
151 cmd = 'echo "{0}" | sudo tee {1}'.format(reps, sfile)
152 (ret, stdout, stderr) = self.exec_command(cmd)
154 raise RuntimeError('{} failed on node {} {}'.format(
159 # Install the package
160 cmd = 'apt-get -y update'
161 (ret, stdout, stderr) = self.exec_command(cmd)
163 raise RuntimeError('{} apt-get update failed on node {} {}'.format(
168 self._install_vpp_pkg_ubuntu(node, 'vpp-lib')
169 self._install_vpp_pkg_ubuntu(node, 'vpp')
170 self._install_vpp_pkg_ubuntu(node, 'vpp-plugins')
171 self._install_vpp_pkg_ubuntu(node, 'vpp-dpdk-dkms')
172 self._install_vpp_pkg_ubuntu(node, 'vpp-dpdk-dev')
173 self._install_vpp_pkg_ubuntu(node, 'vpp-api-python')
174 self._install_vpp_pkg_ubuntu(node, 'vpp-api-java')
175 self._install_vpp_pkg_ubuntu(node, 'vpp-api-lua')
176 self._install_vpp_pkg_ubuntu(node, 'vpp-dev')
177 self._install_vpp_pkg_ubuntu(node, 'vpp-dbg')
178 self._install_vpp_pkg_ubuntu(node, 'vpp-nsh-plugin')
179 self._install_vpp_pkg_ubuntu(node, 'vpp-nsh-plugin-dbg')
180 self._install_vpp_pkg_ubuntu(node, 'vpp-nsh-plugin-dev')
182 def _install_vpp_centos(self, node, fdio_release=VPP_VERSION,
183 centos_version='centos7'):
185 Install the VPP packages
187 :param node: Node dictionary with cpuinfo.
188 :param fdio_release: VPP release number
189 :param centos_version: Ubuntu Version
191 :type fdio_release: string
192 :type centos_version: string
195 # Modify the sources list
196 sfile = '/etc/yum.repos.d/fdio-release.repo'
198 # Backup the sources list
199 self._autoconfig_backup_file(sfile)
201 # Remove the current file
202 cmd = 'rm {}'.format(sfile)
203 (ret, stdout, stderr) = self.exec_command(cmd)
205 logging.debug('{} failed on node {} {}'.format(
211 # reps = '[fdio-master]\n'
212 # reps += 'name=fd.io master branch latest merge\n'
213 # reps += 'baseurl=https://nexus.fd.io/content/repositories/fd.io.master.{}/\n'.format(centos_version)
214 # reps = '[fdio-stable-{}]\n'.format(fdio_release)
215 # reps += 'name=fd.io stable/{} branch latest merge\n'.format(fdio_release)
217 # reps += 'baseurl=https://nexus.fd.io/content/repositories/fd.io.stable.{}.{}/\n'.\
218 # format(fdio_release, centos_version)
220 reps = '[fdio-release]\n'
221 reps += 'name=fd.io release branch latest merge\n'
222 reps += 'baseurl=https://nexus.fd.io/content/repositories/fd.io.{}/\n'.format(centos_version)
223 reps += 'enabled=1\n'
226 cmd = 'echo "{0}" | sudo tee {1}'.format(reps, sfile)
227 (ret, stdout, stderr) = self.exec_command(cmd)
229 raise RuntimeError('{} failed on node {} {}'.format(
234 # Install the packages
236 self._install_vpp_pkg_centos(node, 'vpp-lib')
237 self._install_vpp_pkg_centos(node, 'vpp')
238 self._install_vpp_pkg_centos(node, 'vpp-plugins')
239 self._install_vpp_pkg_centos(node, 'vpp-api-python')
240 self._install_vpp_pkg_centos(node, 'vpp-api-java')
241 self._install_vpp_pkg_centos(node, 'vpp-api-lua')
242 self._install_vpp_pkg_centos(node, 'vpp-devel')
243 self._install_vpp_pkg_centos(node, 'vpp-debuginfo')
244 self._install_vpp_pkg_centos(node, 'vpp-nsh-plugin')
245 self._install_vpp_pkg_centos(node, 'vpp-nsh-plugin-devel')
246 self._install_vpp_pkg_centos(node, 'vpp-selinux-policy')
248 def install_vpp(self, node):
250 Install the VPP packages
252 :param node: Node dictionary with cpuinfo.
255 distro = self.get_linux_distro()
256 logging.info(" {}".format(distro[0]))
257 if distro[0] == 'Ubuntu':
258 logging.info("Install Ubuntu")
259 self._install_vpp_ubuntu(node)
260 elif distro[0] == 'CentOS Linux':
261 logging.info("Install CentOS")
262 self._install_vpp_centos(node)
264 logging.info("Install CentOS (default)")
265 self._install_vpp_centos(node)
268 def _uninstall_vpp_pkg_ubuntu(self, node, pkg):
270 Uninstall the VPP packages
272 :param node: Node dictionary
273 :param pkg: The vpp packages
277 cmd = 'dpkg --purge {}'.format(pkg)
278 (ret, stdout, stderr) = self.exec_command(cmd)
280 raise RuntimeError('{} failed on node {} {} {}'.format(
281 cmd, node['host'], stdout, stderr))
283 def _uninstall_vpp_pkg_centos(self, node, pkg):
285 Uninstall the VPP packages
287 :param node: Node dictionary
288 :param pkg: The vpp packages
292 cmd = 'yum -y remove {}'.format(pkg)
293 (ret, stdout, stderr) = self.exec_command(cmd)
295 raise RuntimeError('{} failed on node {} {} {}'.format(
296 cmd, node['host'], stdout, stderr))
298 def _uninstall_vpp_ubuntu(self, node):
300 Uninstall the VPP packages
302 :param node: Node dictionary with cpuinfo.
305 pkgs = self.get_installed_vpp_pkgs()
308 if 'version' in pkgs[0]:
309 logging.info("Uninstall Ubuntu Packages")
310 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-nsh-plugin-dev')
311 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-nsh-plugin-dbg')
312 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-nsh-plugin')
313 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-api-python')
314 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-api-java')
315 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-api-lua')
316 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-plugins')
317 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dpdk-dev')
318 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dpdk-dkms')
319 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dev')
320 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dbg')
321 self._uninstall_vpp_pkg_ubuntu(node, 'vpp')
322 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-lib')
324 logging.info("Uninstall locally installed Ubuntu Packages")
326 self._uninstall_vpp_pkg_ubuntu(node, pkg['name'])
328 logging.error("There are no Ubuntu packages installed")
330 def _uninstall_vpp_centos(self, node):
332 Uninstall the VPP packages
334 :param node: Node dictionary with cpuinfo.
338 pkgs = self.get_installed_vpp_pkgs()
341 if 'version' in pkgs[0]:
342 logging.info("Uninstall CentOS Packages")
343 self._uninstall_vpp_pkg_centos(node, 'vpp-selinux-policy')
344 self._uninstall_vpp_pkg_centos(node, 'vpp-nsh-plugin-devel')
345 self._uninstall_vpp_pkg_centos(node, 'vpp-nsh-plugin')
346 self._uninstall_vpp_pkg_centos(node, 'vpp-debuginfo')
347 self._uninstall_vpp_pkg_centos(node, 'vpp-api-python')
348 self._uninstall_vpp_pkg_centos(node, 'vpp-api-java')
349 self._uninstall_vpp_pkg_centos(node, 'vpp-api-lua')
350 self._uninstall_vpp_pkg_centos(node, 'vpp-plugins')
351 self._uninstall_vpp_pkg_centos(node, 'vpp-devel')
352 self._uninstall_vpp_pkg_centos(node, 'vpp')
353 self._uninstall_vpp_pkg_centos(node, 'vpp-lib')
355 logging.info("Uninstall locally installed CentOS Packages")
357 self._uninstall_vpp_pkg_centos(node, pkg['name'])
359 logging.error("There are no CentOS packages installed")
361 def uninstall_vpp(self, node):
363 Uninstall the VPP packages
365 :param node: Node dictionary with cpuinfo.
372 distro = self.get_linux_distro()
373 if distro[0] == 'Ubuntu':
374 logging.info("Uninstall Ubuntu")
375 self._uninstall_vpp_ubuntu(node)
376 elif distro[0] == 'CentOS Linux':
377 logging.info("Uninstall CentOS")
378 self._uninstall_vpp_centos(node)
380 logging.info("Uninstall CentOS (Default)")
381 self._uninstall_vpp_centos(node)
384 def show_vpp_settings(self, *additional_cmds):
386 Print default VPP settings. In case others are needed, can be
387 accepted as next parameters (each setting one parameter), preferably
390 :param additional_cmds: Additional commands that the vpp should print
392 :type additional_cmds: tuple
394 def_setting_tb_displayed = {
395 'IPv6 FIB': 'ip6 fib',
396 'IPv4 FIB': 'ip fib',
397 'Interface IP': 'int addr',
404 for cmd in additional_cmds:
405 def_setting_tb_displayed['Custom Setting: {}'.format(cmd)] \
408 for _, value in def_setting_tb_displayed.items():
409 self.exec_command('vppctl sh {}'.format(value))
414 Get a list of VMs that are connected to VPP interfaces
416 :param node: VPP node.
418 :returns: Dictionary containing a list of VMs and the interfaces that are connected to VPP
424 print "Need to implement get vms"
429 def get_int_ip(node):
431 Get the VPP interfaces and IP addresses
433 :param node: VPP node.
435 :returns: Dictionary containing VPP interfaces and IP addresses
439 cmd = 'vppctl show int addr'
440 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
444 lines = stdout.split('\n')
445 if len(lines[0]) is not 0:
446 if lines[0].split(' ')[0] == 'FileNotFoundError':
454 # If the first character is not whitespace
455 # create a new interface
456 if len(re.findall(r'\s', line[0])) is 0:
461 interfaces[name] = {}
462 interfaces[name]['state'] = spl[1].lstrip('(').rstrip('):\r')
464 interfaces[name]['address'] = line.lstrip(' ').rstrip('\r')
469 def get_hardware(node):
471 Get the VPP hardware information and return it in a
474 :param node: VPP node.
476 :returns: Dictionary containing VPP hardware information
481 cmd = 'vppctl show hard'
482 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
486 lines = stdout.split('\n')
487 if len(lines[0]) is not 0:
488 if lines[0].split(' ')[0] == 'FileNotFoundError':
495 # If the first character is not whitespace
496 # create a new interface
497 if len(re.findall(r'\s', line[0])) is 0:
500 interfaces[name] = {}
501 interfaces[name]['index'] = spl[1]
502 interfaces[name]['state'] = spl[2]
505 rfall = re.findall(r'Ethernet address', line)
508 interfaces[name]['mac'] = spl[2]
511 rfall = re.findall(r'carrier', line)
513 spl = line.split('carrier ')
514 interfaces[name]['carrier'] = spl[1]
517 rfall = re.findall(r'cpu socket', line)
519 spl = line.split('cpu socket ')
520 interfaces[name]['cpu socket'] = spl[1]
522 # Queues and Descriptors
523 rfall = re.findall(r'rx queues', line)
525 spl = line.split(',')
526 interfaces[name]['rx queues'] = spl[0].lstrip(' ').split(' ')[2]
527 interfaces[name]['rx descs'] = spl[1].split(' ')[3]
528 interfaces[name]['tx queues'] = spl[2].split(' ')[3]
529 interfaces[name]['tx descs'] = spl[3].split(' ')[3]
533 def _get_installed_vpp_pkgs_ubuntu(self):
535 Get the VPP hardware information and return it in a
538 :returns: List of the packages installed
543 cmd = 'dpkg -l | grep vpp'
544 (ret, stdout, stderr) = self.exec_command(cmd)
548 lines = stdout.split('\n')
553 pkg = {'name': items[1], 'version': items[2]}
558 def _get_installed_vpp_pkgs_centos(self):
560 Get the VPP hardware information and return it in a
563 :returns: List of the packages installed
568 cmd = 'rpm -qa | grep vpp'
569 (ret, stdout, stderr) = self.exec_command(cmd)
573 lines = stdout.split('\n')
580 pkg = {'name': items[0]}
582 pkg = {'name': items[1], 'version': items[2]}
588 def get_installed_vpp_pkgs(self):
590 Get the VPP hardware information and return it in a
593 :returns: List of the packages installed
597 distro = self.get_linux_distro()
598 if distro[0] == 'Ubuntu':
599 pkgs = self._get_installed_vpp_pkgs_ubuntu()
600 elif distro[0] == 'CentOS Linux':
601 pkgs = self._get_installed_vpp_pkgs_centos()
603 pkgs = self._get_installed_vpp_pkgs_centos()
609 def get_interfaces_numa_node(node, *iface_keys):
610 """Get numa node on which are located most of the interfaces.
612 Return numa node with highest count of interfaces provided as arguments.
613 Return 0 if the interface does not have numa_node information available.
614 If all interfaces have unknown location (-1), then return 0.
615 If most of interfaces have unknown location (-1), but there are
616 some interfaces with known location, then return the second most
617 location of the provided interfaces.
619 :param node: Node from DICT__nodes.
620 :param iface_keys: Interface keys for lookup.
622 :type iface_keys: strings
625 for if_key in iface_keys:
627 numa_list.append(node['interfaces'][if_key].get('numa_node'))
631 numa_cnt_mc = Counter(numa_list).most_common()
632 numa_cnt_mc_len = len(numa_cnt_mc)
633 if numa_cnt_mc_len > 0 and numa_cnt_mc[0][0] != -1:
634 return numa_cnt_mc[0][0]
635 elif numa_cnt_mc_len > 1 and numa_cnt_mc[0][0] == -1:
636 return numa_cnt_mc[1][0]
644 Starts vpp for a given node
646 :param node: VPP node.
650 cmd = 'service vpp restart'
651 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
653 raise RuntimeError('{} failed on node {} {} {}'.
654 format(cmd, node['host'],
661 Starts vpp for a given node
663 :param node: VPP node.
667 cmd = 'service vpp start'
668 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
670 raise RuntimeError('{} failed on node {} {} {}'.
671 format(cmd, node['host'],
678 Stops vpp for a given node
680 :param node: VPP node.
684 cmd = 'service vpp stop'
685 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
687 raise RuntimeError('{} failed on node {} {} {}'.
688 format(cmd, node['host'],
691 # noinspection RegExpRedundantEscape
700 :returns: status, errors
701 :rtype: tuple(str, list)
705 pkgs = vutil.get_installed_vpp_pkgs()
707 return "Not Installed", errors
709 cmd = 'service vpp status'
710 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
712 # Get the active status
713 state = re.findall(r'Active:[\w (\)]+', stdout)[0].split(' ')
715 statestr = "{} {}".format(state[1], state[2])
719 # For now we won't look for DPDK errors
720 # lines = stdout.split('\n')
722 # if 'EAL' in line or \
723 # 'FAILURE' in line or \
724 # 'failed' in line or \
726 # errors.append(line.lstrip(' '))
728 return statestr, errors
731 def get_linux_distro():
733 Get the linux distribution and check if it is supported
735 :returns: linux distro, None if the distro is not supported
739 distro = platform.linux_distribution()
740 if distro[0] == 'Ubuntu' or \
741 distro[0] == 'CentOS Linux' or \
742 distro[:7] == 'Red Hat':
745 raise RuntimeError('Linux Distribution {} is not supported'.format(distro[0]))
751 Gets VPP Version information
758 cmd = 'vppctl show version verbose'
759 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
763 lines = stdout.split('\n')
764 if len(lines[0]) is not 0:
765 if lines[0].split(' ')[0] == 'FileNotFoundError':
771 dct = line.split(':')
772 version[dct[0]] = dct[1].lstrip(' ')
777 def show_bridge(node):
779 Shows the current bridge configuration
781 :param node: VPP node.
783 :returns: A list of interfaces
787 cmd = 'vppctl show bridge'
788 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
790 raise RuntimeError('{} failed on node {} {} {}'.
791 format(cmd, node['host'],
793 lines = stdout.split('\r\n')
796 if line == 'no bridge-domains in use':
802 lspl = line.lstrip(' ').split()
803 if lspl[0] != 'BD-ID':
804 bridges.append(lspl[0])
806 for bridge in bridges:
807 cmd = 'vppctl show bridge {} detail'.format(bridge)
808 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
810 raise RuntimeError('{} failed on node {} {} {}'.
811 format(cmd, node['host'],
814 lines = stdout.split('\r\n')
816 iface = re.findall(r'[a-zA-z]+\d+/\d+/\d+', line)
818 ifcidx ={'name': iface[0], 'index': line.split()[1] }
819 ifaces.append(ifcidx)