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 from __future__ import print_function
16 """VPP util library"""
22 from collections import Counter
26 ubuntu_pkgs = {'release': ['vpp', 'vpp-plugin-core', 'vpp-plugin-dpdk', 'vpp-api-python', 'python3-vpp-api',
27 'vpp-dbg', 'vpp-dev'],
28 'master': ['vpp', 'vpp-plugin-core', 'vpp-plugin-dpdk', 'vpp-api-python', 'python3-vpp-api',
29 'vpp-dbg', 'vpp-dev']}
31 centos_pkgs = {'release': ['vpp', 'vpp-selinux-policy', 'vpp-plugins', 'vpp-api-lua',
32 'vpp-api-python', 'vpp-debuginfo', 'vpp-devel', 'libvpp0'],
33 'master': ['vpp', 'vpp-selinux-policy', 'vpp-plugins', 'vpp-api-lua',
34 'vpp-api-python', 'vpp-debuginfo', 'vpp-devel', 'libvpp0']}
37 class VPPUtil(object):
38 """General class for any VPP related methods/functions."""
41 def exec_command(cmd, timeout=None):
42 """Execute a command on the local node.
44 :param cmd: Command to run locally.
45 :param timeout: Timeout value
48 :return return_code, stdout, stderr
49 :rtype: tuple(int, str, str)
52 logging.info(" Local Command: {}".format(cmd))
55 prc = subprocess.Popen(cmd, shell=True, bufsize=1,
56 stdin=subprocess.PIPE,
57 stdout=subprocess.PIPE,
58 stderr=subprocess.PIPE)
61 lines = prc.stdout.readlines()
65 logging.info(" {}".format(line.strip('\n')))
69 lines = prc.stderr.readlines()
73 logging.warning(" {}".format(line.strip('\n')))
80 def _autoconfig_backup_file(self, filename):
84 :param filename: The file to backup
88 # Does a copy of the file exist, if not create one
89 ofile = filename + '.orig'
90 (ret, stdout, stderr) = self.exec_command('ls {}'.format(ofile))
93 if stdout.strip('\n') != ofile:
94 cmd = 'sudo cp {} {}'.format(filename, ofile)
95 (ret, stdout, stderr) = self.exec_command(cmd)
99 def _install_vpp_ubuntu(self, node, branch, ubuntu_version='xenial'):
101 Install the VPP packages
103 :param node: Node dictionary with cpuinfo.
104 :param branch: VPP branch
105 :param ubuntu_version: Ubuntu Version
108 :type ubuntu_version: string
111 # Modify the sources list
112 sfile = '/etc/apt/sources.list.d/99fd.io.list'
114 # Backup the sources list
115 self._autoconfig_backup_file(sfile)
117 reps = 'deb [trusted=yes] https://packagecloud.io/fdio/'
118 reps += '{}/ubuntu {} main\n'.format(branch, ubuntu_version)
120 with open(sfile, 'w') as sfd:
127 'https://packagecloud.io/fdio/{}/gpgkey'.format(branch))
128 cmd = 'echo "{}" | apt-key add -'.format(key.content.decode(key.encoding))
129 (ret, stdout, stderr) = self.exec_command(cmd)
131 raise RuntimeError('{} failed on node {} {}'.format(
136 # Install the package
137 cmd = 'apt-get -y update'
138 (ret, stdout, stderr) = self.exec_command(cmd)
140 raise RuntimeError('{} apt-get update failed on node {} {}'.format(
145 # Get the package list
147 for ps in ubuntu_pkgs[branch]:
150 cmd = 'apt-get -y install {}'.format(pkgstr)
151 (ret, stdout, stderr) = self.exec_command(cmd)
153 raise RuntimeError('{} failed on node {} {} {}'.format(
154 cmd, node['host'], stdout, stderr))
156 def _install_vpp_centos(self, node, branch):
158 Install the VPP packages
160 :param node: Node dictionary with cpuinfo.
161 :param branch: The branch name release or master
166 # Be sure the correct system packages are installed
167 cmd = 'yum -y update'
168 (ret, stdout, stderr) = self.exec_command(cmd)
170 logging.debug('{} failed on node {} {}'.format(
175 cmd = 'yum -y install pygpgme yum-utils'
176 (ret, stdout, stderr) = self.exec_command(cmd)
178 logging.debug('{} failed on node {} {}'.format(
183 # Modify the sources list
184 sfile = '/etc/yum.repos.d/fdio-release.repo'
186 # Backup the sources list
187 self._autoconfig_backup_file(sfile)
189 # Remove the current file
190 cmd = 'rm {}'.format(sfile)
191 (ret, stdout, stderr) = self.exec_command(cmd)
193 logging.debug('{} failed on node {} {}'.format(
198 # Get the file contents
201 '[fdio_{}]'.format(branch),
202 'name=fdio_{}'.format(branch),
203 'baseurl=https://packagecloud.io/fdio/{}/el/7/$basearch'.format(
208 'gpgkey=https://packagecloud.io/fdio/{}/gpgkey'.format(branch),
210 'sslcacert=/etc/pki/tls/certs/ca-bundle.crt',
211 'metadata_expire=300\n',
212 '[fdio_{}-source]'.format(branch),
213 'name=fdio_release-{}'.format(branch),
214 'baseurl=https://packagecloud.io/fdio/{}/el/7/SRPMS'.format(
219 'gpgkey=https://packagecloud.io/fdio/{}/gpgkey'.format(branch),
221 'sslcacert=/etc/pki/tls/certs/ca-bundle.crt',
222 'metadata_expire=300\n'
224 with open(sfile, 'w') as sfd:
228 # Update the fdio repo
229 cmd = 'yum clean all'
230 (ret, stdout, stderr) = self.exec_command(cmd)
232 logging.debug('{} failed on node {} {}'.format(
237 cmd = "yum -q makecache -y --disablerepo='*' " \
238 "--enablerepo='fdio_{}'".format(branch)
239 (ret, stdout, stderr) = self.exec_command(cmd)
241 logging.debug('{} failed on node {} {}'.format(
246 # Get the package list
248 for ps in centos_pkgs[branch]:
251 cmd = 'yum -y install {}'.format(pkgstr)
252 (ret, stdout, stderr) = self.exec_command(cmd)
254 raise RuntimeError('{} failed on node {} {} {}'.format(
255 cmd, node['host'], stdout, stderr))
257 def install_vpp(self, node, branch):
259 Install the VPP packages
261 :param node: Node dictionary with cpuinfo.
262 :param branch: The branch name
267 distro = self.get_linux_distro()
268 logging.info(" {}".format(distro[0]))
269 if distro[0] == 'Ubuntu':
270 logging.info("Install Ubuntu")
271 self._install_vpp_ubuntu(node, branch, ubuntu_version=distro[2])
272 elif distro[0] == 'CentOS Linux':
273 logging.info("Install CentOS")
274 self._install_vpp_centos(node, branch)
276 logging.info("Install CentOS (default)")
277 self._install_vpp_centos(node, branch)
280 def _uninstall_vpp_ubuntu(self, node):
282 Uninstall the VPP packages
284 :param node: Node dictionary with cpuinfo.
288 # get the package list
290 pkgs = self.get_installed_vpp_pkgs()
292 pkgname = pkg['name']
293 pkgstr += pkgname + ' '
295 cmd = 'dpkg --purge {}'.format(pkgstr)
296 (ret, stdout, stderr) = self.exec_command(cmd)
298 raise RuntimeError('{} failed on node {} {} {}'.format(
299 cmd, node['host'], stdout, stderr))
301 def _uninstall_vpp_centos(self, node):
303 Uninstall the VPP packages
305 :param node: Node dictionary with cpuinfo.
310 pkgs = self.get_installed_vpp_pkgs()
312 pkgname = pkg['name']
313 pkgstr += pkgname + ' '
315 logging.info("Uninstalling {}".format(pkgstr))
316 cmd = 'yum -y remove {}'.format(pkgstr)
317 (ret, stdout, stderr) = self.exec_command(cmd)
319 raise RuntimeError('{} failed on node {} {} {}'.format(
320 cmd, node['host'], stdout, stderr))
322 def uninstall_vpp(self, node):
324 Uninstall the VPP packages
326 :param node: Node dictionary with cpuinfo.
332 distro = self.get_linux_distro()
333 if distro[0] == 'Ubuntu':
334 logging.info("Uninstall Ubuntu")
335 self._uninstall_vpp_ubuntu(node)
336 elif distro[0] == 'CentOS Linux':
337 logging.info("Uninstall CentOS")
338 self._uninstall_vpp_centos(node)
340 logging.info("Uninstall CentOS (Default)")
341 self._uninstall_vpp_centos(node)
344 def show_vpp_settings(self, *additional_cmds):
346 Print default VPP settings. In case others are needed, can be
347 accepted as next parameters (each setting one parameter), preferably
350 :param additional_cmds: Additional commands that the vpp should print
352 :type additional_cmds: tuple
354 def_setting_tb_displayed = {
355 'IPv6 FIB': 'ip6 fib',
356 'IPv4 FIB': 'ip fib',
357 'Interface IP': 'int addr',
364 for cmd in additional_cmds:
365 def_setting_tb_displayed['Custom Setting: {}'.format(cmd)] \
368 for _, value in def_setting_tb_displayed.items():
369 self.exec_command('vppctl sh {}'.format(value))
374 Get a list of VMs that are connected to VPP interfaces
376 :param node: VPP node.
378 :returns: Dictionary containing a list of VMs and the interfaces
379 that are connected to VPP
385 print("Need to implement get vms")
390 def get_int_ip(node):
392 Get the VPP interfaces and IP addresses
394 :param node: VPP node.
396 :returns: Dictionary containing VPP interfaces and IP addresses
400 cmd = 'vppctl show int addr'
401 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
405 lines = stdout.split('\n')
406 if len(lines[0]) != 0:
407 if lines[0].split(' ')[0] == 'FileNotFoundError':
415 # If the first character is not whitespace
416 # create a new interface
417 if len(re.findall(r'\s', line[0])) == 0:
422 interfaces[name] = {}
423 interfaces[name]['state'] = spl[1].lstrip('(').rstrip('):\r')
425 interfaces[name]['address'] = line.lstrip(' ').rstrip('\r')
430 def get_hardware(node):
432 Get the VPP hardware information and return it in a
435 :param node: VPP node.
437 :returns: Dictionary containing VPP hardware information
442 cmd = 'vppctl show hard'
443 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
447 lines = stdout.split('\n')
448 if len(lines[0]) != 0:
449 if lines[0].split(' ')[0] == 'FileNotFoundError':
456 # If the first character is not whitespace
457 # create a new interface
458 if len(re.findall(r'\s', line[0])) == 0:
461 interfaces[name] = {}
462 interfaces[name]['index'] = spl[1]
463 interfaces[name]['state'] = spl[2]
466 rfall = re.findall(r'Ethernet address', line)
469 interfaces[name]['mac'] = spl[2]
472 rfall = re.findall(r'carrier', line)
474 spl = line.split('carrier ')
475 interfaces[name]['carrier'] = spl[1]
479 rfall = re.findall(r'numa \d+', line)
481 spl = rfall[0].split()
482 interfaces[name]['numa'] = rfall[0].split()[1]
484 # Queues and Descriptors
485 rfall = re.findall(r'rx\: queues \d+', line)
487 interfaces[name]['rx queues'] = rfall[0].split()[2]
488 rdesc = re.findall(r'desc \d+', line)
490 interfaces[name]['rx descs'] = rdesc[0].split()[1]
492 rfall = re.findall(r'tx\: queues \d+', line)
494 interfaces[name]['tx queues'] = rfall[0].split()[2]
495 rdesc = re.findall(r'desc \d+', line)
497 interfaces[name]['tx descs'] = rdesc[0].split()[1]
501 def _get_installed_vpp_pkgs_ubuntu(self):
503 Get the VPP hardware information and return it in a
506 :returns: List of the packages installed
511 cmd = 'dpkg -l | grep vpp'
512 (ret, stdout, stderr) = self.exec_command(cmd)
516 lines = stdout.split('\n')
521 pkg = {'name': items[1], 'version': items[2]}
526 def _get_installed_vpp_pkgs_centos(self):
528 Get the VPP hardware information and return it in a
531 :returns: List of the packages installed
536 cmd = 'rpm -qa | grep vpp'
537 (ret, stdout, stderr) = self.exec_command(cmd)
541 lines = stdout.split('\n')
548 pkg = {'name': items[0]}
550 pkg = {'name': items[1], 'version': items[2]}
556 def get_installed_vpp_pkgs(self):
558 Get the VPP hardware information and return it in a
561 :returns: List of the packages installed
565 distro = self.get_linux_distro()
566 if distro[0] == 'Ubuntu':
567 pkgs = self._get_installed_vpp_pkgs_ubuntu()
568 elif distro[0] == 'CentOS Linux':
569 pkgs = self._get_installed_vpp_pkgs_centos()
571 pkgs = self._get_installed_vpp_pkgs_centos()
577 def get_interfaces_numa_node(node, *iface_keys):
578 """Get numa node on which are located most of the interfaces.
580 Return numa node with highest count of interfaces provided as
582 Return 0 if the interface does not have numa_node information
584 If all interfaces have unknown location (-1), then return 0.
585 If most of interfaces have unknown location (-1), but there are
586 some interfaces with known location, then return the second most
587 location of the provided interfaces.
589 :param node: Node from DICT__nodes.
590 :param iface_keys: Interface keys for lookup.
592 :type iface_keys: strings
595 for if_key in iface_keys:
597 numa_list.append(node['interfaces'][if_key].get('numa_node'))
601 numa_cnt_mc = Counter(numa_list).most_common()
602 numa_cnt_mc_len = len(numa_cnt_mc)
603 if numa_cnt_mc_len > 0 and numa_cnt_mc[0][0] != -1:
604 return numa_cnt_mc[0][0]
605 elif numa_cnt_mc_len > 1 and numa_cnt_mc[0][0] == -1:
606 return numa_cnt_mc[1][0]
614 Starts vpp for a given node
616 :param node: VPP node.
620 cmd = 'service vpp restart'
621 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
623 raise RuntimeError('{} failed on node {} {} {}'.
624 format(cmd, node['host'],
631 Starts vpp for a given node
633 :param node: VPP node.
637 cmd = 'service vpp start'
638 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
640 raise RuntimeError('{} failed on node {} {} {}'.
641 format(cmd, node['host'],
648 Stops vpp for a given node
650 :param node: VPP node.
654 cmd = 'service vpp stop'
655 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
657 logging.debug('{} failed on node {} {} {}'.
658 format(cmd, node['host'],
661 # noinspection RegExpRedundantEscape
670 :returns: status, errors
671 :rtype: tuple(str, list)
675 pkgs = vutil.get_installed_vpp_pkgs()
677 return "Not Installed", errors
679 cmd = 'service vpp status'
680 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
682 # Get the active status
683 state = re.findall(r'Active:[\w (\)]+', stdout)[0].split(' ')
685 statestr = "{} {}".format(state[1], state[2])
689 # For now we won't look for DPDK errors
690 # lines = stdout.split('\n')
692 # if 'EAL' in line or \
693 # 'FAILURE' in line or \
694 # 'failed' in line or \
696 # errors.append(line.lstrip(' '))
698 return statestr, errors
701 def get_linux_distro():
703 Get the linux distribution and check if it is supported
705 :returns: linux distro, None if the distro is not supported
709 dist = distro.linux_distribution()
710 if dist[0] == 'Ubuntu' or \
711 dist[0] == 'CentOS Linux' or \
712 dist[:7] == 'Red Hat':
716 'Linux Distribution {} is not supported'.format(dist[0]))
722 Gets VPP Version information
729 cmd = 'vppctl show version verbose'
730 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
734 lines = stdout.split('\n')
735 if len(lines[0]) != 0:
736 if lines[0].split(' ')[0] == 'FileNotFoundError':
742 dct = line.split(':')
743 version[dct[0]] = dct[1].lstrip(' ')
748 def show_bridge(node):
750 Shows the current bridge configuration
752 :param node: VPP node.
754 :returns: A list of interfaces
758 cmd = 'vppctl show bridge'
759 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
761 raise RuntimeError('{} failed on node {} {} {}'.
762 format(cmd, node['host'],
764 lines = stdout.split('\r\n')
767 if line == 'no bridge-domains in use':
773 lspl = line.lstrip(' ').split()
774 if lspl[0] != 'BD-ID':
775 bridges.append(lspl[0])
777 for bridge in bridges:
778 cmd = 'vppctl show bridge {} detail'.format(bridge)
779 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
781 raise RuntimeError('{} failed on node {} {} {}'.
782 format(cmd, node['host'],
785 lines = stdout.split('\r\n')
787 iface = re.findall(r'[a-zA-z]+\d+/\d+/\d+', line)
789 ifcidx = {'name': iface[0], 'index': line.split()[1]}
790 ifaces.append(ifcidx)