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"""
21 from collections import Counter
23 # VPP_VERSION = '1707'
24 # VPP_VERSION = '1710'
28 class VPPUtil(object):
29 """General class for any VPP related methods/functions."""
32 def exec_command(cmd, timeout=None):
33 """Execute a command on the local node.
35 :param cmd: Command to run locally.
36 :param timeout: Timeout value
39 :return return_code, stdout, stderr
40 :rtype: tuple(int, str, str)
43 logging.info(" Local Command: {}".format(cmd))
46 prc = subprocess.Popen(cmd, shell=True, bufsize=1,
47 stdin=subprocess.PIPE,
48 stdout=subprocess.PIPE,
49 stderr=subprocess.PIPE)
52 for line in iter(prc.stdout.readline, b''):
53 logging.info(" {}".format(line.strip('\n')))
57 for line in iter(prc.stderr.readline, b''):
58 logging.warn(" {}".format(line.strip('\n')))
65 def _autoconfig_backup_file(self, filename):
69 :param filename: The file to backup
73 # Does a copy of the file exist, if not create one
74 ofile = filename + '.orig'
75 (ret, stdout, stderr) = self.exec_command('ls {}'.format(ofile))
78 if stdout.strip('\n') != ofile:
79 cmd = 'sudo cp {} {}'.format(filename, ofile)
80 (ret, stdout, stderr) = self.exec_command(cmd)
84 def _install_vpp_pkg_ubuntu(self, node, pkg):
86 Install the VPP packages
88 :param node: Node dictionary
89 :param pkg: The vpp packages
94 cmd = 'apt-get -y install {}'.format(pkg)
95 (ret, stdout, stderr) = self.exec_command(cmd)
97 raise RuntimeError('{} failed on node {} {} {}'.format(
98 cmd, node['host'], stdout, stderr))
100 def _install_vpp_pkg_centos(self, node, pkg):
102 Install the VPP packages
104 :param node: Node dictionary
105 :param pkg: The vpp packages
110 cmd = 'yum -y install {}'.format(pkg)
111 (ret, stdout, stderr) = self.exec_command(cmd)
113 raise RuntimeError('{} failed on node {} {} {}'.format(
114 cmd, node['host'], stdout, stderr))
116 def _install_vpp_ubuntu(self, node, fdio_release=VPP_VERSION,
117 ubuntu_version='xenial'):
119 Install the VPP packages
121 :param node: Node dictionary with cpuinfo.
122 :param fdio_release: VPP release number
123 :param ubuntu_version: Ubuntu Version
125 :type fdio_release: string
126 :type ubuntu_version: string
129 # Modify the sources list
130 sfile = '/etc/apt/sources.list.d/99fd.io.list'
132 # Backup the sources list
133 self._autoconfig_backup_file(sfile)
135 reps = 'deb [trusted=yes] https://packagecloud.io/fdio/'
136 # When using a stable branch
137 # reps += '{}/ubuntu {} main ./\n'.format(fdio_release, ubuntu_version)
139 reps += 'release/ubuntu {} main ./\n'.format(ubuntu_version)
141 # reps += 'master/ubuntu {} main/ ./\n'.format(ubuntu_version)
143 with open(sfile, 'w') as sfd:
148 key = requests.get('https://packagecloud.io/fdio/{}/gpgkey'.format('release'))
149 # cmd = 'curl -L https://packagecloud.io/fdio/{}/gpgkey | apt-key add -'.format(fdio_release)
150 # cmd = 'curl -L https://packagecloud.io/fdio/{}/gpgkey | apt-key add -'.format('mastert')
151 cmd = 'echo "{}" | apt-key add -'.format(key.content)
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-api-python')
172 self._install_vpp_pkg_ubuntu(node, 'vpp-api-java')
173 self._install_vpp_pkg_ubuntu(node, 'vpp-api-lua')
174 self._install_vpp_pkg_ubuntu(node, 'vpp-dev')
175 self._install_vpp_pkg_ubuntu(node, 'vpp-dbg')
177 def _install_vpp_centos(self, node, fdio_release=VPP_VERSION,
178 centos_version='centos7'):
180 Install the VPP packages
182 :param node: Node dictionary with cpuinfo.
183 :param fdio_release: VPP release number
184 :param centos_version: Ubuntu Version
186 :type fdio_release: string
187 :type centos_version: string
190 # Be sure the correct system packages are installed
191 cmd = 'yum -y update'
192 (ret, stdout, stderr) = self.exec_command(cmd)
194 logging.debug('{} failed on node {} {}'.format(
199 cmd = 'yum -y install pygpgme yum-utils'
200 (ret, stdout, stderr) = self.exec_command(cmd)
202 logging.debug('{} failed on node {} {}'.format(
207 # Modify the sources list
208 sfile = '/etc/yum.repos.d/fdio-release.repo'
210 # Backup the sources list
211 self._autoconfig_backup_file(sfile)
213 # Remove the current file
214 cmd = 'rm {}'.format(sfile)
215 (ret, stdout, stderr) = self.exec_command(cmd)
217 logging.debug('{} failed on node {} {}'.format(
227 # Get the file contents
228 reps = '[fdio_{}]\n'.format(bname)
229 reps += 'name=fdio_{}\n'.format(bname)
230 reps += 'baseurl=https://packagecloud.io/fdio/{}/el/7/$basearch\n'.format(bname)
231 reps += 'repo_gpgcheck=1\n'
232 reps += 'gpgcheck=0\n'
233 reps += 'enabled=1\n'
234 reps += 'gpgkey=https://packagecloud.io/fdio/{}/gpgkey\n'.format(bname)
235 reps += 'sslverify=1\n'
236 reps += 'sslcacert=/etc/pki/tls/certs/ca-bundle.crt\n'
237 reps += 'metadata_expire=300\n'
239 reps += '[fdio_{}-source]\n'.format(bname)
240 reps += 'name=fdio_release-{}\n'.format(bname)
241 reps += 'baseurl=https://packagecloud.io/fdio/{}/el/7/SRPMS\n'.format(bname)
242 reps += 'repo_gpgcheck=1\n'
243 reps += 'gpgcheck=0\n'
244 reps += 'enabled=1\n'
245 reps += 'gpgkey=https://packagecloud.io/fdio/{}/gpgkey\n'.format(bname)
246 reps += 'sslverify =1\n'
247 reps += 'sslcacert=/etc/pki/tls/certs/ca-bundle.crt\n'
248 reps += 'metadata_expire=300\n'
250 with open(sfile, 'w') as sfd:
254 # Update the fdio repo
255 cmd = 'yum clean all'
256 (ret, stdout, stderr) = self.exec_command(cmd)
258 logging.debug('{} failed on node {} {}'.format(
263 cmd = "yum -q makecache -y --disablerepo='*' --enablerepo='fdio_{}'".format(bname)
264 (ret, stdout, stderr) = self.exec_command(cmd)
266 logging.debug('{} failed on node {} {}'.format(
271 # Install the packages
272 self._install_vpp_pkg_centos(node, 'vpp-selinux-policy')
273 self._install_vpp_pkg_centos(node, 'vpp-lib')
274 self._install_vpp_pkg_centos(node, 'vpp')
275 self._install_vpp_pkg_centos(node, 'vpp-plugins')
276 self._install_vpp_pkg_centos(node, 'vpp-api-python')
277 self._install_vpp_pkg_centos(node, 'vpp-api-java')
278 self._install_vpp_pkg_centos(node, 'vpp-api-lua')
279 self._install_vpp_pkg_centos(node, 'vpp-devel')
280 self._install_vpp_pkg_centos(node, 'vpp-debuginfo')
282 def install_vpp(self, node):
284 Install the VPP packages
286 :param node: Node dictionary with cpuinfo.
289 distro = self.get_linux_distro()
290 logging.info(" {}".format(distro[0]))
291 if distro[0] == 'Ubuntu':
292 logging.info("Install Ubuntu")
293 self._install_vpp_ubuntu(node)
294 elif distro[0] == 'CentOS Linux':
295 logging.info("Install CentOS")
296 self._install_vpp_centos(node)
298 logging.info("Install CentOS (default)")
299 self._install_vpp_centos(node)
302 def _uninstall_vpp_pkg_ubuntu(self, node, pkg):
304 Uninstall the VPP packages
306 :param node: Node dictionary
307 :param pkg: The vpp packages
311 cmd = 'dpkg --purge {}'.format(pkg)
312 (ret, stdout, stderr) = self.exec_command(cmd)
314 raise RuntimeError('{} failed on node {} {} {}'.format(
315 cmd, node['host'], stdout, stderr))
317 def _uninstall_vpp_pkg_centos(self, node, pkg):
319 Uninstall the VPP packages
321 :param node: Node dictionary
322 :param pkg: The vpp packages
326 cmd = 'yum -y remove {}'.format(pkg)
327 (ret, stdout, stderr) = self.exec_command(cmd)
329 raise RuntimeError('{} failed on node {} {} {}'.format(
330 cmd, node['host'], stdout, stderr))
332 def _uninstall_vpp_ubuntu(self, node):
334 Uninstall the VPP packages
336 :param node: Node dictionary with cpuinfo.
339 pkgs = self.get_installed_vpp_pkgs()
342 if 'version' in pkgs[0]:
343 logging.info("Uninstall Ubuntu Packages")
344 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dbg')
345 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dev')
346 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-api-python')
347 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-api-java')
348 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-api-lua')
349 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-plugins')
350 self._uninstall_vpp_pkg_ubuntu(node, 'vpp')
351 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-lib')
353 logging.info("Uninstall locally installed Ubuntu Packages")
355 self._uninstall_vpp_pkg_ubuntu(node, pkg['name'])
357 logging.error("There are no Ubuntu packages installed")
359 def _uninstall_vpp_centos(self, node):
361 Uninstall the VPP packages
363 :param node: Node dictionary with cpuinfo.
367 pkgs = self.get_installed_vpp_pkgs()
370 if 'version' in pkgs[0]:
371 logging.info("Uninstall CentOS Packages")
372 self._install_vpp_pkg_centos(node, 'vpp-debuginfo')
373 self._uninstall_vpp_pkg_centos(node, 'vpp-devel')
374 self._uninstall_vpp_pkg_centos(node, 'vpp-api-python')
375 self._uninstall_vpp_pkg_centos(node, 'vpp-api-java')
376 self._uninstall_vpp_pkg_centos(node, 'vpp-api-lua')
377 self._uninstall_vpp_pkg_centos(node, 'vpp-plugins')
378 self._uninstall_vpp_pkg_centos(node, 'vpp')
379 self._uninstall_vpp_pkg_centos(node, 'vpp-lib')
380 self._uninstall_vpp_pkg_centos(node, 'vpp-selinux-policy')
382 logging.info("Uninstall locally installed CentOS Packages")
384 self._uninstall_vpp_pkg_centos(node, pkg['name'])
386 logging.error("There are no CentOS packages installed")
388 def uninstall_vpp(self, node):
390 Uninstall the VPP packages
392 :param node: Node dictionary with cpuinfo.
399 distro = self.get_linux_distro()
400 if distro[0] == 'Ubuntu':
401 logging.info("Uninstall Ubuntu")
402 self._uninstall_vpp_ubuntu(node)
403 elif distro[0] == 'CentOS Linux':
404 logging.info("Uninstall CentOS")
405 self._uninstall_vpp_centos(node)
407 logging.info("Uninstall CentOS (Default)")
408 self._uninstall_vpp_centos(node)
411 def show_vpp_settings(self, *additional_cmds):
413 Print default VPP settings. In case others are needed, can be
414 accepted as next parameters (each setting one parameter), preferably
417 :param additional_cmds: Additional commands that the vpp should print
419 :type additional_cmds: tuple
421 def_setting_tb_displayed = {
422 'IPv6 FIB': 'ip6 fib',
423 'IPv4 FIB': 'ip fib',
424 'Interface IP': 'int addr',
431 for cmd in additional_cmds:
432 def_setting_tb_displayed['Custom Setting: {}'.format(cmd)] \
435 for _, value in def_setting_tb_displayed.items():
436 self.exec_command('vppctl sh {}'.format(value))
441 Get a list of VMs that are connected to VPP interfaces
443 :param node: VPP node.
445 :returns: Dictionary containing a list of VMs and the interfaces that are connected to VPP
451 print "Need to implement get vms"
456 def get_int_ip(node):
458 Get the VPP interfaces and IP addresses
460 :param node: VPP node.
462 :returns: Dictionary containing VPP interfaces and IP addresses
466 cmd = 'vppctl show int addr'
467 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
471 lines = stdout.split('\n')
472 if len(lines[0]) is not 0:
473 if lines[0].split(' ')[0] == 'FileNotFoundError':
481 # If the first character is not whitespace
482 # create a new interface
483 if len(re.findall(r'\s', line[0])) is 0:
488 interfaces[name] = {}
489 interfaces[name]['state'] = spl[1].lstrip('(').rstrip('):\r')
491 interfaces[name]['address'] = line.lstrip(' ').rstrip('\r')
496 def get_hardware(node):
498 Get the VPP hardware information and return it in a
501 :param node: VPP node.
503 :returns: Dictionary containing VPP hardware information
508 cmd = 'vppctl show hard'
509 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
513 lines = stdout.split('\n')
514 if len(lines[0]) is not 0:
515 if lines[0].split(' ')[0] == 'FileNotFoundError':
522 # If the first character is not whitespace
523 # create a new interface
524 if len(re.findall(r'\s', line[0])) is 0:
527 interfaces[name] = {}
528 interfaces[name]['index'] = spl[1]
529 interfaces[name]['state'] = spl[2]
532 rfall = re.findall(r'Ethernet address', line)
535 interfaces[name]['mac'] = spl[2]
538 rfall = re.findall(r'carrier', line)
540 spl = line.split('carrier ')
541 interfaces[name]['carrier'] = spl[1]
544 rfall = re.findall(r'cpu socket', line)
546 spl = line.split('cpu socket ')
547 interfaces[name]['cpu socket'] = spl[1]
549 # Queues and Descriptors
550 rfall = re.findall(r'rx queues', line)
552 spl = line.split(',')
553 interfaces[name]['rx queues'] = spl[0].lstrip(' ').split(' ')[2]
554 interfaces[name]['rx descs'] = spl[1].split(' ')[3]
555 interfaces[name]['tx queues'] = spl[2].split(' ')[3]
556 interfaces[name]['tx descs'] = spl[3].split(' ')[3]
560 def _get_installed_vpp_pkgs_ubuntu(self):
562 Get the VPP hardware information and return it in a
565 :returns: List of the packages installed
570 cmd = 'dpkg -l | grep vpp'
571 (ret, stdout, stderr) = self.exec_command(cmd)
575 lines = stdout.split('\n')
580 pkg = {'name': items[1], 'version': items[2]}
585 def _get_installed_vpp_pkgs_centos(self):
587 Get the VPP hardware information and return it in a
590 :returns: List of the packages installed
595 cmd = 'rpm -qa | grep vpp'
596 (ret, stdout, stderr) = self.exec_command(cmd)
600 lines = stdout.split('\n')
607 pkg = {'name': items[0]}
609 pkg = {'name': items[1], 'version': items[2]}
615 def get_installed_vpp_pkgs(self):
617 Get the VPP hardware information and return it in a
620 :returns: List of the packages installed
624 distro = self.get_linux_distro()
625 if distro[0] == 'Ubuntu':
626 pkgs = self._get_installed_vpp_pkgs_ubuntu()
627 elif distro[0] == 'CentOS Linux':
628 pkgs = self._get_installed_vpp_pkgs_centos()
630 pkgs = self._get_installed_vpp_pkgs_centos()
636 def get_interfaces_numa_node(node, *iface_keys):
637 """Get numa node on which are located most of the interfaces.
639 Return numa node with highest count of interfaces provided as arguments.
640 Return 0 if the interface does not have numa_node information available.
641 If all interfaces have unknown location (-1), then return 0.
642 If most of interfaces have unknown location (-1), but there are
643 some interfaces with known location, then return the second most
644 location of the provided interfaces.
646 :param node: Node from DICT__nodes.
647 :param iface_keys: Interface keys for lookup.
649 :type iface_keys: strings
652 for if_key in iface_keys:
654 numa_list.append(node['interfaces'][if_key].get('numa_node'))
658 numa_cnt_mc = Counter(numa_list).most_common()
659 numa_cnt_mc_len = len(numa_cnt_mc)
660 if numa_cnt_mc_len > 0 and numa_cnt_mc[0][0] != -1:
661 return numa_cnt_mc[0][0]
662 elif numa_cnt_mc_len > 1 and numa_cnt_mc[0][0] == -1:
663 return numa_cnt_mc[1][0]
671 Starts vpp for a given node
673 :param node: VPP node.
677 cmd = 'service vpp restart'
678 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
680 raise RuntimeError('{} failed on node {} {} {}'.
681 format(cmd, node['host'],
688 Starts vpp for a given node
690 :param node: VPP node.
694 cmd = 'service vpp start'
695 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
697 raise RuntimeError('{} failed on node {} {} {}'.
698 format(cmd, node['host'],
705 Stops vpp for a given node
707 :param node: VPP node.
711 cmd = 'service vpp stop'
712 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
714 logging.debug('{} failed on node {} {} {}'.
715 format(cmd, node['host'],
718 # noinspection RegExpRedundantEscape
727 :returns: status, errors
728 :rtype: tuple(str, list)
732 pkgs = vutil.get_installed_vpp_pkgs()
734 return "Not Installed", errors
736 cmd = 'service vpp status'
737 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
739 # Get the active status
740 state = re.findall(r'Active:[\w (\)]+', stdout)[0].split(' ')
742 statestr = "{} {}".format(state[1], state[2])
746 # For now we won't look for DPDK errors
747 # lines = stdout.split('\n')
749 # if 'EAL' in line or \
750 # 'FAILURE' in line or \
751 # 'failed' in line or \
753 # errors.append(line.lstrip(' '))
755 return statestr, errors
758 def get_linux_distro():
760 Get the linux distribution and check if it is supported
762 :returns: linux distro, None if the distro is not supported
766 distro = platform.linux_distribution()
767 if distro[0] == 'Ubuntu' or \
768 distro[0] == 'CentOS Linux' or \
769 distro[:7] == 'Red Hat':
772 raise RuntimeError('Linux Distribution {} is not supported'.format(distro[0]))
778 Gets VPP Version information
785 cmd = 'vppctl show version verbose'
786 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
790 lines = stdout.split('\n')
791 if len(lines[0]) is not 0:
792 if lines[0].split(' ')[0] == 'FileNotFoundError':
798 dct = line.split(':')
799 version[dct[0]] = dct[1].lstrip(' ')
804 def show_bridge(node):
806 Shows the current bridge configuration
808 :param node: VPP node.
810 :returns: A list of interfaces
814 cmd = 'vppctl show bridge'
815 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
817 raise RuntimeError('{} failed on node {} {} {}'.
818 format(cmd, node['host'],
820 lines = stdout.split('\r\n')
823 if line == 'no bridge-domains in use':
829 lspl = line.lstrip(' ').split()
830 if lspl[0] != 'BD-ID':
831 bridges.append(lspl[0])
833 for bridge in bridges:
834 cmd = 'vppctl show bridge {} detail'.format(bridge)
835 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
837 raise RuntimeError('{} failed on node {} {} {}'.
838 format(cmd, node['host'],
841 lines = stdout.split('\r\n')
843 iface = re.findall(r'[a-zA-z]+\d+/\d+/\d+', line)
845 ifcidx ={'name': iface[0], 'index': line.split()[1] }
846 ifaces.append(ifcidx)