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"""
23 from collections import Counter
25 ubuntu_pkgs = {'release': ['vpp', 'vpp-plugins', 'vpp-api-java', 'vpp-api-lua', 'vpp-api-python',
26 'vpp-dbg', 'vpp-dev'],
27 'master': ['vpp', 'vpp-plugin-core', 'vpp-api-python',
28 'vpp-dbg', 'vpp-dev', 'vpp-plugin-dpdk']}
30 centos_pkgs = {'release': ['vpp', 'vpp-plugins', 'vpp-api-java', 'vpp-api-lua',
31 'vpp-api-python', 'vpp-debuginfo', 'vpp-devel', 'libvpp0'],
32 'master': ['vpp', 'vpp-plugins', 'vpp-api-java', 'vpp-api-lua',
33 'vpp-api-python', 'vpp-debuginfo', 'vpp-devel', 'libvpp0']}
36 class VPPUtil(object):
37 """General class for any VPP related methods/functions."""
40 def exec_command(cmd, timeout=None):
41 """Execute a command on the local node.
43 :param cmd: Command to run locally.
44 :param timeout: Timeout value
47 :return return_code, stdout, stderr
48 :rtype: tuple(int, str, str)
51 logging.info(" Local Command: {}".format(cmd))
54 prc = subprocess.Popen(cmd, shell=True, bufsize=1,
55 stdin=subprocess.PIPE,
56 stdout=subprocess.PIPE,
57 stderr=subprocess.PIPE)
60 lines = prc.stdout.readlines()
64 logging.info(" {}".format(line.strip('\n')))
68 lines = prc.stderr.readlines()
72 logging.warning(" {}".format(line.strip('\n')))
79 def _autoconfig_backup_file(self, filename):
83 :param filename: The file to backup
87 # Does a copy of the file exist, if not create one
88 ofile = filename + '.orig'
89 (ret, stdout, stderr) = self.exec_command('ls {}'.format(ofile))
92 if stdout.strip('\n') != ofile:
93 cmd = 'sudo cp {} {}'.format(filename, ofile)
94 (ret, stdout, stderr) = self.exec_command(cmd)
98 def _install_vpp_ubuntu(self, node, branch, ubuntu_version='xenial'):
100 Install the VPP packages
102 :param node: Node dictionary with cpuinfo.
103 :param branch: VPP branch
104 :param ubuntu_version: Ubuntu Version
107 :type ubuntu_version: string
110 # Modify the sources list
111 sfile = '/etc/apt/sources.list.d/99fd.io.list'
113 # Backup the sources list
114 self._autoconfig_backup_file(sfile)
116 reps = 'deb [trusted=yes] https://packagecloud.io/fdio/'
117 reps += '{}/ubuntu {} main\n'.format(branch, ubuntu_version)
119 with open(sfile, 'w') as sfd:
126 'https://packagecloud.io/fdio/{}/gpgkey'.format(branch))
127 cmd = 'echo "{}" | apt-key add -'.format(key.content.decode(key.encoding))
128 (ret, stdout, stderr) = self.exec_command(cmd)
130 raise RuntimeError('{} failed on node {} {}'.format(
135 # Install the package
136 cmd = 'apt-get -y update'
137 (ret, stdout, stderr) = self.exec_command(cmd)
139 raise RuntimeError('{} apt-get update failed on node {} {}'.format(
144 # Get the package list
146 for ps in ubuntu_pkgs[branch]:
149 cmd = 'apt-get -y install {}'.format(pkgstr)
150 (ret, stdout, stderr) = self.exec_command(cmd)
152 raise RuntimeError('{} failed on node {} {} {}'.format(
153 cmd, node['host'], stdout, stderr))
155 def _install_vpp_centos(self, node, branch):
157 Install the VPP packages
159 :param node: Node dictionary with cpuinfo.
160 :param branch: The branch name release or master
165 # Be sure the correct system packages are installed
166 cmd = 'yum -y update'
167 (ret, stdout, stderr) = self.exec_command(cmd)
169 logging.debug('{} failed on node {} {}'.format(
174 cmd = 'yum -y install pygpgme yum-utils'
175 (ret, stdout, stderr) = self.exec_command(cmd)
177 logging.debug('{} failed on node {} {}'.format(
182 # Modify the sources list
183 sfile = '/etc/yum.repos.d/fdio-release.repo'
185 # Backup the sources list
186 self._autoconfig_backup_file(sfile)
188 # Remove the current file
189 cmd = 'rm {}'.format(sfile)
190 (ret, stdout, stderr) = self.exec_command(cmd)
192 logging.debug('{} failed on node {} {}'.format(
197 # Get the file contents
200 '[fdio_{}]'.format(branch),
201 'name=fdio_{}'.format(branch),
202 'baseurl=https://packagecloud.io/fdio/{}/el/7/$basearch'.format(
207 'gpgkey=https://packagecloud.io/fdio/{}/gpgkey'.format(branch),
209 'sslcacert=/etc/pki/tls/certs/ca-bundle.crt',
210 'metadata_expire=300\n',
211 '[fdio_{}-source]'.format(branch),
212 'name=fdio_release-{}'.format(branch),
213 'baseurl=https://packagecloud.io/fdio/{}/el/7/SRPMS'.format(
218 'gpgkey=https://packagecloud.io/fdio/{}/gpgkey'.format(branch),
220 'sslcacert=/etc/pki/tls/certs/ca-bundle.crt',
221 'metadata_expire=300\n'
223 with open(sfile, 'w') as sfd:
227 # Update the fdio repo
228 cmd = 'yum clean all'
229 (ret, stdout, stderr) = self.exec_command(cmd)
231 logging.debug('{} failed on node {} {}'.format(
236 cmd = "yum -q makecache -y --disablerepo='*' " \
237 "--enablerepo='fdio_{}'".format(branch)
238 (ret, stdout, stderr) = self.exec_command(cmd)
240 logging.debug('{} failed on node {} {}'.format(
245 # Get the package list
247 for ps in centos_pkgs[branch]:
250 cmd = 'yum -y install {}'.format(pkgstr)
251 (ret, stdout, stderr) = self.exec_command(cmd)
253 raise RuntimeError('{} failed on node {} {} {}'.format(
254 cmd, node['host'], stdout, stderr))
256 def install_vpp(self, node, branch):
258 Install the VPP packages
260 :param node: Node dictionary with cpuinfo.
261 :param branch: The branch name
266 distro = self.get_linux_distro()
267 logging.info(" {}".format(distro[0]))
268 if distro[0] == 'Ubuntu':
269 logging.info("Install Ubuntu")
270 self._install_vpp_ubuntu(node, branch, ubuntu_version=distro[2])
271 elif distro[0] == 'CentOS Linux':
272 logging.info("Install CentOS")
273 self._install_vpp_centos(node, branch)
275 logging.info("Install CentOS (default)")
276 self._install_vpp_centos(node, branch)
279 def _uninstall_vpp_ubuntu(self, node):
281 Uninstall the VPP packages
283 :param node: Node dictionary with cpuinfo.
287 # get the package list
289 pkgs = self.get_installed_vpp_pkgs()
291 pkgname = pkg['name']
292 pkgstr += pkgname + ' '
294 cmd = 'dpkg --purge {}'.format(pkgstr)
295 (ret, stdout, stderr) = self.exec_command(cmd)
297 raise RuntimeError('{} failed on node {} {} {}'.format(
298 cmd, node['host'], stdout, stderr))
300 def _uninstall_vpp_centos(self, node):
302 Uninstall the VPP packages
304 :param node: Node dictionary with cpuinfo.
309 pkgs = self.get_installed_vpp_pkgs()
311 pkgname = pkg['name']
312 pkgstr += pkgname + ' '
314 logging.info("Uninstalling {}".format(pkgstr))
315 cmd = 'yum -y remove {}'.format(pkgstr)
316 (ret, stdout, stderr) = self.exec_command(cmd)
318 raise RuntimeError('{} failed on node {} {} {}'.format(
319 cmd, node['host'], stdout, stderr))
321 def uninstall_vpp(self, node):
323 Uninstall the VPP packages
325 :param node: Node dictionary with cpuinfo.
331 distro = self.get_linux_distro()
332 if distro[0] == 'Ubuntu':
333 logging.info("Uninstall Ubuntu")
334 self._uninstall_vpp_ubuntu(node)
335 elif distro[0] == 'CentOS Linux':
336 logging.info("Uninstall CentOS")
337 self._uninstall_vpp_centos(node)
339 logging.info("Uninstall CentOS (Default)")
340 self._uninstall_vpp_centos(node)
343 def show_vpp_settings(self, *additional_cmds):
345 Print default VPP settings. In case others are needed, can be
346 accepted as next parameters (each setting one parameter), preferably
349 :param additional_cmds: Additional commands that the vpp should print
351 :type additional_cmds: tuple
353 def_setting_tb_displayed = {
354 'IPv6 FIB': 'ip6 fib',
355 'IPv4 FIB': 'ip fib',
356 'Interface IP': 'int addr',
363 for cmd in additional_cmds:
364 def_setting_tb_displayed['Custom Setting: {}'.format(cmd)] \
367 for _, value in def_setting_tb_displayed.items():
368 self.exec_command('vppctl sh {}'.format(value))
373 Get a list of VMs that are connected to VPP interfaces
375 :param node: VPP node.
377 :returns: Dictionary containing a list of VMs and the interfaces
378 that are connected to VPP
384 print("Need to implement get vms")
389 def get_int_ip(node):
391 Get the VPP interfaces and IP addresses
393 :param node: VPP node.
395 :returns: Dictionary containing VPP interfaces and IP addresses
399 cmd = 'vppctl show int addr'
400 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
404 lines = stdout.split('\n')
405 if len(lines[0]) != 0:
406 if lines[0].split(' ')[0] == 'FileNotFoundError':
414 # If the first character is not whitespace
415 # create a new interface
416 if len(re.findall(r'\s', line[0])) == 0:
421 interfaces[name] = {}
422 interfaces[name]['state'] = spl[1].lstrip('(').rstrip('):\r')
424 interfaces[name]['address'] = line.lstrip(' ').rstrip('\r')
429 def get_hardware(node):
431 Get the VPP hardware information and return it in a
434 :param node: VPP node.
436 :returns: Dictionary containing VPP hardware information
441 cmd = 'vppctl show hard'
442 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
446 lines = stdout.split('\n')
447 if len(lines[0]) != 0:
448 if lines[0].split(' ')[0] == 'FileNotFoundError':
455 # If the first character is not whitespace
456 # create a new interface
457 if len(re.findall(r'\s', line[0])) == 0:
460 interfaces[name] = {}
461 interfaces[name]['index'] = spl[1]
462 interfaces[name]['state'] = spl[2]
465 rfall = re.findall(r'Ethernet address', line)
468 interfaces[name]['mac'] = spl[2]
471 rfall = re.findall(r'carrier', line)
473 spl = line.split('carrier ')
474 interfaces[name]['carrier'] = spl[1]
478 rfall = re.findall(r'numa \d+', line)
480 spl = rfall[0].split()
481 interfaces[name]['numa'] = rfall[0].split()[1]
483 # Queues and Descriptors
484 rfall = re.findall(r'rx\: queues \d+', line)
486 interfaces[name]['rx queues'] = rfall[0].split()[2]
487 rdesc = re.findall(r'desc \d+', line)
489 interfaces[name]['rx descs'] = rdesc[0].split()[1]
491 rfall = re.findall(r'tx\: queues \d+', line)
493 interfaces[name]['tx queues'] = rfall[0].split()[2]
494 rdesc = re.findall(r'desc \d+', line)
496 interfaces[name]['tx descs'] = rdesc[0].split()[1]
500 def _get_installed_vpp_pkgs_ubuntu(self):
502 Get the VPP hardware information and return it in a
505 :returns: List of the packages installed
510 cmd = 'dpkg -l | grep vpp'
511 (ret, stdout, stderr) = self.exec_command(cmd)
515 lines = stdout.split('\n')
520 pkg = {'name': items[1], 'version': items[2]}
525 def _get_installed_vpp_pkgs_centos(self):
527 Get the VPP hardware information and return it in a
530 :returns: List of the packages installed
535 cmd = 'rpm -qa | grep vpp'
536 (ret, stdout, stderr) = self.exec_command(cmd)
540 lines = stdout.split('\n')
547 pkg = {'name': items[0]}
549 pkg = {'name': items[1], 'version': items[2]}
555 def get_installed_vpp_pkgs(self):
557 Get the VPP hardware information and return it in a
560 :returns: List of the packages installed
564 distro = self.get_linux_distro()
565 if distro[0] == 'Ubuntu':
566 pkgs = self._get_installed_vpp_pkgs_ubuntu()
567 elif distro[0] == 'CentOS Linux':
568 pkgs = self._get_installed_vpp_pkgs_centos()
570 pkgs = self._get_installed_vpp_pkgs_centos()
576 def get_interfaces_numa_node(node, *iface_keys):
577 """Get numa node on which are located most of the interfaces.
579 Return numa node with highest count of interfaces provided as
581 Return 0 if the interface does not have numa_node information
583 If all interfaces have unknown location (-1), then return 0.
584 If most of interfaces have unknown location (-1), but there are
585 some interfaces with known location, then return the second most
586 location of the provided interfaces.
588 :param node: Node from DICT__nodes.
589 :param iface_keys: Interface keys for lookup.
591 :type iface_keys: strings
594 for if_key in iface_keys:
596 numa_list.append(node['interfaces'][if_key].get('numa_node'))
600 numa_cnt_mc = Counter(numa_list).most_common()
601 numa_cnt_mc_len = len(numa_cnt_mc)
602 if numa_cnt_mc_len > 0 and numa_cnt_mc[0][0] != -1:
603 return numa_cnt_mc[0][0]
604 elif numa_cnt_mc_len > 1 and numa_cnt_mc[0][0] == -1:
605 return numa_cnt_mc[1][0]
613 Starts vpp for a given node
615 :param node: VPP node.
619 cmd = 'service vpp restart'
620 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
622 raise RuntimeError('{} failed on node {} {} {}'.
623 format(cmd, node['host'],
630 Starts vpp for a given node
632 :param node: VPP node.
636 cmd = 'service vpp start'
637 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
639 raise RuntimeError('{} failed on node {} {} {}'.
640 format(cmd, node['host'],
647 Stops vpp for a given node
649 :param node: VPP node.
653 cmd = 'service vpp stop'
654 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
656 logging.debug('{} failed on node {} {} {}'.
657 format(cmd, node['host'],
660 # noinspection RegExpRedundantEscape
669 :returns: status, errors
670 :rtype: tuple(str, list)
674 pkgs = vutil.get_installed_vpp_pkgs()
676 return "Not Installed", errors
678 cmd = 'service vpp status'
679 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
681 # Get the active status
682 state = re.findall(r'Active:[\w (\)]+', stdout)[0].split(' ')
684 statestr = "{} {}".format(state[1], state[2])
688 # For now we won't look for DPDK errors
689 # lines = stdout.split('\n')
691 # if 'EAL' in line or \
692 # 'FAILURE' in line or \
693 # 'failed' in line or \
695 # errors.append(line.lstrip(' '))
697 return statestr, errors
700 def get_linux_distro():
702 Get the linux distribution and check if it is supported
704 :returns: linux distro, None if the distro is not supported
708 distro = platform.linux_distribution()
709 if distro[0] == 'Ubuntu' or \
710 distro[0] == 'CentOS Linux' or \
711 distro[:7] == 'Red Hat':
715 'Linux Distribution {} is not supported'.format(distro[0]))
721 Gets VPP Version information
728 cmd = 'vppctl show version verbose'
729 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
733 lines = stdout.split('\n')
734 if len(lines[0]) != 0:
735 if lines[0].split(' ')[0] == 'FileNotFoundError':
741 dct = line.split(':')
742 version[dct[0]] = dct[1].lstrip(' ')
747 def show_bridge(node):
749 Shows the current bridge configuration
751 :param node: VPP node.
753 :returns: A list of interfaces
757 cmd = 'vppctl show bridge'
758 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
760 raise RuntimeError('{} failed on node {} {} {}'.
761 format(cmd, node['host'],
763 lines = stdout.split('\r\n')
766 if line == 'no bridge-domains in use':
772 lspl = line.lstrip(' ').split()
773 if lspl[0] != 'BD-ID':
774 bridges.append(lspl[0])
776 for bridge in bridges:
777 cmd = 'vppctl show bridge {} detail'.format(bridge)
778 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
780 raise RuntimeError('{} failed on node {} {} {}'.
781 format(cmd, node['host'],
784 lines = stdout.split('\r\n')
786 iface = re.findall(r'[a-zA-z]+\d+/\d+/\d+', line)
788 ifcidx = {'name': iface[0], 'index': line.split()[1]}
789 ifaces.append(ifcidx)