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')
179 def _install_vpp_centos(self, node, fdio_release=VPP_VERSION,
180 centos_version='centos7'):
182 Install the VPP packages
184 :param node: Node dictionary with cpuinfo.
185 :param fdio_release: VPP release number
186 :param centos_version: Ubuntu Version
188 :type fdio_release: string
189 :type centos_version: string
192 # Modify the sources list
193 sfile = '/etc/yum.repos.d/fdio-release.repo'
195 # Backup the sources list
196 self._autoconfig_backup_file(sfile)
198 # Remove the current file
199 cmd = 'rm {}'.format(sfile)
200 (ret, stdout, stderr) = self.exec_command(cmd)
202 logging.debug('{} failed on node {} {}'.format(
208 # reps = '[fdio-master]\n'
209 # reps += 'name=fd.io master branch latest merge\n'
210 # reps += 'baseurl=https://nexus.fd.io/content/repositories/fd.io.master.{}/\n'.format(centos_version)
211 # reps = '[fdio-stable-{}]\n'.format(fdio_release)
212 # reps += 'name=fd.io stable/{} branch latest merge\n'.format(fdio_release)
214 # reps += 'baseurl=https://nexus.fd.io/content/repositories/fd.io.stable.{}.{}/\n'.\
215 # format(fdio_release, centos_version)
217 reps = '[fdio-release]\n'
218 reps += 'name=fd.io release branch latest merge\n'
219 reps += 'baseurl=https://nexus.fd.io/content/repositories/fd.io.{}/\n'.format(centos_version)
220 reps += 'enabled=1\n'
223 cmd = 'echo "{0}" | sudo tee {1}'.format(reps, sfile)
224 (ret, stdout, stderr) = self.exec_command(cmd)
226 raise RuntimeError('{} failed on node {} {}'.format(
231 # Install the packages
233 self._install_vpp_pkg_centos(node, 'vpp-lib')
234 self._install_vpp_pkg_centos(node, 'vpp')
235 self._install_vpp_pkg_centos(node, 'vpp-plugins')
236 # jadfix Check with Ole
237 # self._install_vpp_pkg_centos(node, 'vpp-dpdk-devel')
238 self._install_vpp_pkg_centos(node, 'vpp-api-python')
239 self._install_vpp_pkg_centos(node, 'vpp-api-java')
240 self._install_vpp_pkg_centos(node, 'vpp-api-lua')
241 self._install_vpp_pkg_centos(node, 'vpp-devel')
243 def install_vpp(self, node):
245 Install the VPP packages
247 :param node: Node dictionary with cpuinfo.
250 distro = self.get_linux_distro()
251 logging.info(" {}".format(distro[0]))
252 if distro[0] == 'Ubuntu':
253 logging.info("Install Ubuntu")
254 self._install_vpp_ubuntu(node)
255 elif distro[0] == 'CentOS Linux':
256 logging.info("Install CentOS")
257 self._install_vpp_centos(node)
259 logging.info("Install CentOS (default)")
260 self._install_vpp_centos(node)
263 def _uninstall_vpp_pkg_ubuntu(self, node, pkg):
265 Uninstall the VPP packages
267 :param node: Node dictionary
268 :param pkg: The vpp packages
272 cmd = 'dpkg --purge {}'.format(pkg)
273 (ret, stdout, stderr) = self.exec_command(cmd)
275 raise RuntimeError('{} failed on node {} {} {}'.format(
276 cmd, node['host'], stdout, stderr))
278 def _uninstall_vpp_pkg_centos(self, node, pkg):
280 Uninstall the VPP packages
282 :param node: Node dictionary
283 :param pkg: The vpp packages
287 cmd = 'yum -y remove {}'.format(pkg)
288 (ret, stdout, stderr) = self.exec_command(cmd)
290 raise RuntimeError('{} failed on node {} {} {}'.format(
291 cmd, node['host'], stdout, stderr))
293 def _uninstall_vpp_ubuntu(self, node):
295 Uninstall the VPP packages
297 :param node: Node dictionary with cpuinfo.
300 pkgs = self.get_installed_vpp_pkgs()
303 if 'version' in pkgs[0]:
304 logging.info("Uninstall Ubuntu Packages")
305 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-api-python')
306 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-api-java')
307 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-api-lua')
308 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-plugins')
309 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dpdk-dev')
310 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dpdk-dkms')
311 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dev')
312 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-dbg')
313 self._uninstall_vpp_pkg_ubuntu(node, 'vpp')
314 self._uninstall_vpp_pkg_ubuntu(node, 'vpp-lib')
316 logging.info("Uninstall locally installed Ubuntu Packages")
318 self._uninstall_vpp_pkg_ubuntu(node, pkg['name'])
320 logging.error("There are no Ubuntu packages installed")
322 def _uninstall_vpp_centos(self, node):
324 Uninstall the VPP packages
326 :param node: Node dictionary with cpuinfo.
330 pkgs = self.get_installed_vpp_pkgs()
333 if 'version' in pkgs[0]:
334 logging.info("Uninstall CentOS Packages")
335 self._uninstall_vpp_pkg_centos(node, 'vpp-api-python')
336 self._uninstall_vpp_pkg_centos(node, 'vpp-api-java')
337 self._uninstall_vpp_pkg_centos(node, 'vpp-api-lua')
338 self._uninstall_vpp_pkg_centos(node, 'vpp-plugins')
339 self._uninstall_vpp_pkg_centos(node, 'vpp-dpdk-devel')
340 self._uninstall_vpp_pkg_centos(node, 'vpp-devel')
341 self._uninstall_vpp_pkg_centos(node, 'vpp')
342 self._uninstall_vpp_pkg_centos(node, 'vpp-lib')
344 logging.info("Uninstall locally installed CentOS Packages")
346 self._uninstall_vpp_pkg_centos(node, pkg['name'])
348 logging.error("There are no CentOS packages installed")
350 def uninstall_vpp(self, node):
352 Uninstall the VPP packages
354 :param node: Node dictionary with cpuinfo.
361 distro = self.get_linux_distro()
362 if distro[0] == 'Ubuntu':
363 logging.info("Uninstall Ubuntu")
364 self._uninstall_vpp_ubuntu(node)
365 elif distro[0] == 'CentOS Linux':
366 logging.info("Uninstall CentOS")
367 self._uninstall_vpp_centos(node)
369 logging.info("Uninstall CentOS (Default)")
370 self._uninstall_vpp_centos(node)
373 def show_vpp_settings(self, *additional_cmds):
375 Print default VPP settings. In case others are needed, can be
376 accepted as next parameters (each setting one parameter), preferably
379 :param additional_cmds: Additional commands that the vpp should print
381 :type additional_cmds: tuple
383 def_setting_tb_displayed = {
384 'IPv6 FIB': 'ip6 fib',
385 'IPv4 FIB': 'ip fib',
386 'Interface IP': 'int addr',
393 for cmd in additional_cmds:
394 def_setting_tb_displayed['Custom Setting: {}'.format(cmd)] \
397 for _, value in def_setting_tb_displayed.items():
398 self.exec_command('vppctl sh {}'.format(value))
403 Get a list of VMs that are connected to VPP interfaces
405 :param node: VPP node.
407 :returns: Dictionary containing a list of VMs and the interfaces that are connected to VPP
413 print "Need to implement get vms"
418 def get_int_ip(node):
420 Get the VPP interfaces and IP addresses
422 :param node: VPP node.
424 :returns: Dictionary containing VPP interfaces and IP addresses
428 cmd = 'vppctl show int addr'
429 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
433 lines = stdout.split('\n')
434 if len(lines[0]) is not 0:
435 if lines[0].split(' ')[0] == 'FileNotFoundError':
443 # If the first character is not whitespace
444 # create a new interface
445 if len(re.findall(r'\s', line[0])) is 0:
450 interfaces[name] = {}
451 interfaces[name]['state'] = spl[1].lstrip('(').rstrip('):\r')
453 interfaces[name]['address'] = line.lstrip(' ').rstrip('\r')
458 def get_hardware(node):
460 Get the VPP hardware information and return it in a
463 :param node: VPP node.
465 :returns: Dictionary containing VPP hardware information
470 cmd = 'vppctl show hard'
471 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
475 lines = stdout.split('\n')
476 if len(lines[0]) is not 0:
477 if lines[0].split(' ')[0] == 'FileNotFoundError':
484 # If the first character is not whitespace
485 # create a new interface
486 if len(re.findall(r'\s', line[0])) is 0:
489 interfaces[name] = {}
490 interfaces[name]['index'] = spl[1]
491 interfaces[name]['state'] = spl[2]
494 rfall = re.findall(r'Ethernet address', line)
497 interfaces[name]['mac'] = spl[2]
500 rfall = re.findall(r'carrier', line)
502 spl = line.split('carrier ')
503 interfaces[name]['carrier'] = spl[1]
506 rfall = re.findall(r'cpu socket', line)
508 spl = line.split('cpu socket ')
509 interfaces[name]['cpu socket'] = spl[1]
511 # Queues and Descriptors
512 rfall = re.findall(r'rx queues', line)
514 spl = line.split(',')
515 interfaces[name]['rx queues'] = spl[0].lstrip(' ').split(' ')[2]
516 interfaces[name]['rx descs'] = spl[1].split(' ')[3]
517 interfaces[name]['tx queues'] = spl[2].split(' ')[3]
518 interfaces[name]['tx descs'] = spl[3].split(' ')[3]
522 def _get_installed_vpp_pkgs_ubuntu(self):
524 Get the VPP hardware information and return it in a
527 :returns: List of the packages installed
532 cmd = 'dpkg -l | grep vpp'
533 (ret, stdout, stderr) = self.exec_command(cmd)
537 lines = stdout.split('\n')
542 pkg = {'name': items[1], 'version': items[2]}
547 def _get_installed_vpp_pkgs_centos(self):
549 Get the VPP hardware information and return it in a
552 :returns: List of the packages installed
557 cmd = 'rpm -qa | grep vpp'
558 (ret, stdout, stderr) = self.exec_command(cmd)
562 lines = stdout.split('\n')
569 pkg = {'name': items[0]}
571 pkg = {'name': items[1], 'version': items[2]}
577 def get_installed_vpp_pkgs(self):
579 Get the VPP hardware information and return it in a
582 :returns: List of the packages installed
586 distro = self.get_linux_distro()
587 if distro[0] == 'Ubuntu':
588 pkgs = self._get_installed_vpp_pkgs_ubuntu()
589 elif distro[0] == 'CentOS Linux':
590 pkgs = self._get_installed_vpp_pkgs_centos()
592 pkgs = self._get_installed_vpp_pkgs_centos()
598 def get_interfaces_numa_node(node, *iface_keys):
599 """Get numa node on which are located most of the interfaces.
601 Return numa node with highest count of interfaces provided as arguments.
602 Return 0 if the interface does not have numa_node information available.
603 If all interfaces have unknown location (-1), then return 0.
604 If most of interfaces have unknown location (-1), but there are
605 some interfaces with known location, then return the second most
606 location of the provided interfaces.
608 :param node: Node from DICT__nodes.
609 :param iface_keys: Interface keys for lookup.
611 :type iface_keys: strings
614 for if_key in iface_keys:
616 numa_list.append(node['interfaces'][if_key].get('numa_node'))
620 numa_cnt_mc = Counter(numa_list).most_common()
621 numa_cnt_mc_len = len(numa_cnt_mc)
622 if numa_cnt_mc_len > 0 and numa_cnt_mc[0][0] != -1:
623 return numa_cnt_mc[0][0]
624 elif numa_cnt_mc_len > 1 and numa_cnt_mc[0][0] == -1:
625 return numa_cnt_mc[1][0]
633 Starts vpp for a given node
635 :param node: VPP node.
639 cmd = 'service vpp restart'
640 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
642 raise RuntimeError('{} failed on node {} {} {}'.
643 format(cmd, node['host'],
650 Starts vpp for a given node
652 :param node: VPP node.
656 cmd = 'service vpp start'
657 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
659 raise RuntimeError('{} failed on node {} {} {}'.
660 format(cmd, node['host'],
667 Stops vpp for a given node
669 :param node: VPP node.
673 cmd = 'service vpp stop'
674 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
676 raise RuntimeError('{} failed on node {} {} {}'.
677 format(cmd, node['host'],
680 # noinspection RegExpRedundantEscape
689 :returns: status, errors
690 :rtype: tuple(str, list)
694 pkgs = vutil.get_installed_vpp_pkgs()
696 return "Not Installed", errors
698 cmd = 'service vpp status'
699 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
701 # Get the active status
702 state = re.findall(r'Active:[\w (\)]+', stdout)[0].split(' ')
704 statestr = "{} {}".format(state[1], state[2])
708 # For now we won't look for DPDK errors
709 # lines = stdout.split('\n')
711 # if 'EAL' in line or \
712 # 'FAILURE' in line or \
713 # 'failed' in line or \
715 # errors.append(line.lstrip(' '))
717 return statestr, errors
720 def get_linux_distro():
722 Get the linux distribution and check if it is supported
724 :returns: linux distro, None if the distro is not supported
728 distro = platform.linux_distribution()
729 if distro[0] == 'Ubuntu' or \
730 distro[0] == 'CentOS Linux' or \
731 distro[:7] == 'Red Hat':
734 raise RuntimeError('Linux Distribution {} is not supported'.format(distro[0]))
740 Gets VPP Version information
747 cmd = 'vppctl show version verbose'
748 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
752 lines = stdout.split('\n')
753 if len(lines[0]) is not 0:
754 if lines[0].split(' ')[0] == 'FileNotFoundError':
760 dct = line.split(':')
761 version[dct[0]] = dct[1].lstrip(' ')
766 def show_bridge(node):
768 Shows the current bridge configuration
770 :param node: VPP node.
772 :returns: A list of interfaces
776 cmd = 'vppctl show bridge'
777 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
779 raise RuntimeError('{} failed on node {} {} {}'.
780 format(cmd, node['host'],
782 lines = stdout.split('\r\n')
785 if line == 'no bridge-domains in use':
791 lspl = line.lstrip(' ').split()
792 if lspl[0] != 'BD-ID':
793 bridges.append(lspl[0])
795 for bridge in bridges:
796 cmd = 'vppctl show bridge {} detail'.format(bridge)
797 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
799 raise RuntimeError('{} failed on node {} {} {}'.
800 format(cmd, node['host'],
803 lines = stdout.split('\r\n')
805 iface = re.findall(r'[a-zA-z]+\d+/\d+/\d+', line)
807 ifcidx ={'name': iface[0], 'index': line.split()[1] }
808 ifaces.append(ifcidx)