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
75 class VPPUtil(object):
76 """General class for any VPP related methods/functions."""
79 def exec_command(cmd, timeout=None):
80 """Execute a command on the local node.
82 :param cmd: Command to run locally.
83 :param timeout: Timeout value
86 :return return_code, stdout, stderr
87 :rtype: tuple(int, str, str)
90 logging.info(" Local Command: {}".format(cmd))
93 prc = subprocess.Popen(
97 stdin=subprocess.PIPE,
98 stdout=subprocess.PIPE,
99 stderr=subprocess.PIPE,
103 lines = prc.stdout.readlines()
105 if type(line) != str:
107 logging.info(" {}".format(line.strip("\n")))
111 lines = prc.stderr.readlines()
113 if type(line) != str:
115 logging.warning(" {}".format(line.strip("\n")))
122 def _autoconfig_backup_file(self, filename):
124 Create a backup file.
126 :param filename: The file to backup
130 # Does a copy of the file exist, if not create one
131 ofile = filename + ".orig"
132 (ret, stdout, stderr) = self.exec_command("ls {}".format(ofile))
134 logging.debug(stderr)
135 if stdout.strip("\n") != ofile:
136 cmd = "sudo cp {} {}".format(filename, ofile)
137 (ret, stdout, stderr) = self.exec_command(cmd)
139 logging.debug(stderr)
141 def _install_vpp_ubuntu(self, node, branch, ubuntu_version="xenial"):
143 Install the VPP packages
145 :param node: Node dictionary with cpuinfo.
146 :param branch: VPP branch
147 :param ubuntu_version: Ubuntu Version
150 :type ubuntu_version: string
153 # Modify the sources list
154 sfile = "/etc/apt/sources.list.d/99fd.io.list"
156 # Backup the sources list
157 self._autoconfig_backup_file(sfile)
159 reps = "deb [trusted=yes] https://packagecloud.io/fdio/"
160 reps += "{}/ubuntu {} main\n".format(branch, ubuntu_version)
162 with open(sfile, "w") as sfd:
168 key = requests.get("https://packagecloud.io/fdio/{}/gpgkey".format(branch))
169 cmd = 'echo "{}" | apt-key add -'.format(key.content.decode(key.encoding))
170 (ret, stdout, stderr) = self.exec_command(cmd)
173 "{} failed on node {} {}".format(cmd, node["host"], stderr)
176 # Install the package
177 cmd = "apt-get -y update"
178 (ret, stdout, stderr) = self.exec_command(cmd)
181 "{} apt-get update failed on node {} {}".format(
182 cmd, node["host"], stderr
186 # Get the package list
188 for ps in ubuntu_pkgs[branch]:
191 cmd = "apt-get -y install {}".format(pkgstr)
192 (ret, stdout, stderr) = self.exec_command(cmd)
195 "{} failed on node {} {} {}".format(cmd, node["host"], stdout, stderr)
198 def _install_vpp_centos(self, node, branch):
200 Install the VPP packages
202 :param node: Node dictionary with cpuinfo.
203 :param branch: The branch name release or master
208 # Be sure the correct system packages are installed
209 cmd = "yum -y update"
210 (ret, stdout, stderr) = self.exec_command(cmd)
212 logging.debug("{} failed on node {} {}".format(cmd, node["host"], stderr))
214 cmd = "yum -y install pygpgme yum-utils"
215 (ret, stdout, stderr) = self.exec_command(cmd)
217 logging.debug("{} failed on node {} {}".format(cmd, node["host"], stderr))
219 # Modify the sources list
220 sfile = "/etc/yum.repos.d/fdio-release.repo"
222 # Backup the sources list
223 self._autoconfig_backup_file(sfile)
225 # Remove the current file
226 cmd = "rm {}".format(sfile)
227 (ret, stdout, stderr) = self.exec_command(cmd)
229 logging.debug("{} failed on node {} {}".format(cmd, node["host"], stderr))
231 # Get the file contents
235 "[fdio_{}]".format(branch),
236 "name=fdio_{}".format(branch),
237 "baseurl=https://packagecloud.io/fdio/{}/el/7/$basearch".format(branch),
241 "gpgkey=https://packagecloud.io/fdio/{}/gpgkey".format(branch),
243 "sslcacert=/etc/pki/tls/certs/ca-bundle.crt",
244 "metadata_expire=300\n",
245 "[fdio_{}-source]".format(branch),
246 "name=fdio_release-{}".format(branch),
247 "baseurl=https://packagecloud.io/fdio/{}/el/7/SRPMS".format(branch),
251 "gpgkey=https://packagecloud.io/fdio/{}/gpgkey".format(branch),
253 "sslcacert=/etc/pki/tls/certs/ca-bundle.crt",
254 "metadata_expire=300\n",
257 with open(sfile, "w") as sfd:
261 # Update the fdio repo
262 cmd = "yum clean all"
263 (ret, stdout, stderr) = self.exec_command(cmd)
265 logging.debug("{} failed on node {} {}".format(cmd, node["host"], stderr))
267 cmd = "yum -q makecache -y --disablerepo='*' " "--enablerepo='fdio_{}'".format(
270 (ret, stdout, stderr) = self.exec_command(cmd)
272 logging.debug("{} failed on node {} {}".format(cmd, node["host"], stderr))
274 # Get the package list
276 for ps in centos_pkgs[branch]:
279 cmd = "yum -y install {}".format(pkgstr)
280 (ret, stdout, stderr) = self.exec_command(cmd)
283 "{} failed on node {} {} {}".format(cmd, node["host"], stdout, stderr)
286 def install_vpp(self, node, branch):
288 Install the VPP packages
290 :param node: Node dictionary with cpuinfo.
291 :param branch: The branch name
296 distro = self.get_linux_distro()
297 logging.info(" {}".format(distro[0]))
298 if distro[0] == "Ubuntu":
299 logging.info("Install Ubuntu")
300 self._install_vpp_ubuntu(node, branch, ubuntu_version=distro[2])
301 elif distro[0] == "CentOS Linux":
302 logging.info("Install CentOS")
303 self._install_vpp_centos(node, branch)
305 logging.info("Install CentOS (default)")
306 self._install_vpp_centos(node, branch)
309 def _uninstall_vpp_ubuntu(self, node):
311 Uninstall the VPP packages
313 :param node: Node dictionary with cpuinfo.
317 # get the package list
319 pkgs = self.get_installed_vpp_pkgs()
321 pkgname = pkg["name"]
322 pkgstr += pkgname + " "
324 cmd = "dpkg --purge {}".format(pkgstr)
325 (ret, stdout, stderr) = self.exec_command(cmd)
328 "{} failed on node {} {} {}".format(cmd, node["host"], stdout, stderr)
331 def _uninstall_vpp_centos(self, node):
333 Uninstall the VPP packages
335 :param node: Node dictionary with cpuinfo.
340 pkgs = self.get_installed_vpp_pkgs()
342 pkgname = pkg["name"]
343 pkgstr += pkgname + " "
345 logging.info("Uninstalling {}".format(pkgstr))
346 cmd = "yum -y remove {}".format(pkgstr)
347 (ret, stdout, stderr) = self.exec_command(cmd)
350 "{} failed on node {} {} {}".format(cmd, node["host"], stdout, stderr)
353 def uninstall_vpp(self, node):
355 Uninstall the VPP packages
357 :param node: Node dictionary with cpuinfo.
363 distro = self.get_linux_distro()
364 if distro[0] == "Ubuntu":
365 logging.info("Uninstall Ubuntu")
366 self._uninstall_vpp_ubuntu(node)
367 elif distro[0] == "CentOS Linux":
368 logging.info("Uninstall CentOS")
369 self._uninstall_vpp_centos(node)
371 logging.info("Uninstall CentOS (Default)")
372 self._uninstall_vpp_centos(node)
375 def show_vpp_settings(self, *additional_cmds):
377 Print default VPP settings. In case others are needed, can be
378 accepted as next parameters (each setting one parameter), preferably
381 :param additional_cmds: Additional commands that the vpp should print
383 :type additional_cmds: tuple
385 def_setting_tb_displayed = {
386 "IPv6 FIB": "ip6 fib",
387 "IPv4 FIB": "ip fib",
388 "Interface IP": "int addr",
395 for cmd in additional_cmds:
396 def_setting_tb_displayed["Custom Setting: {}".format(cmd)] = cmd
398 for _, value in def_setting_tb_displayed.items():
399 self.exec_command("vppctl sh {}".format(value))
404 Get a list of VMs that are connected to VPP interfaces
406 :param node: VPP node.
408 :returns: Dictionary containing a list of VMs and the interfaces
409 that are connected to VPP
415 print("Need to implement get vms")
420 def get_int_ip(node):
422 Get the VPP interfaces and IP addresses
424 :param node: VPP node.
426 :returns: Dictionary containing VPP interfaces and IP addresses
430 cmd = "vppctl show int addr"
431 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
435 lines = stdout.split("\n")
436 if len(lines[0]) != 0:
437 if lines[0].split(" ")[0] == "FileNotFoundError":
445 # If the first character is not whitespace
446 # create a new interface
447 if len(re.findall(r"\s", line[0])) == 0:
452 interfaces[name] = {}
453 interfaces[name]["state"] = spl[1].lstrip("(").rstrip("):\r")
455 interfaces[name]["address"] = line.lstrip(" ").rstrip("\r")
460 def get_hardware(node):
462 Get the VPP hardware information and return it in a
465 :param node: VPP node.
467 :returns: Dictionary containing VPP hardware information
472 cmd = "vppctl show hard"
473 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
477 lines = stdout.split("\n")
478 if len(lines[0]) != 0:
479 if lines[0].split(" ")[0] == "FileNotFoundError":
486 # If the first character is not whitespace
487 # create a new interface
488 if len(re.findall(r"\s", line[0])) == 0:
491 interfaces[name] = {}
492 interfaces[name]["index"] = spl[1]
493 interfaces[name]["state"] = spl[2]
496 rfall = re.findall(r"Ethernet address", line)
499 interfaces[name]["mac"] = spl[2]
502 rfall = re.findall(r"carrier", line)
504 spl = line.split("carrier ")
505 interfaces[name]["carrier"] = spl[1]
509 rfall = re.findall(r"numa \d+", line)
511 spl = rfall[0].split()
512 interfaces[name]["numa"] = rfall[0].split()[1]
514 # Queues and Descriptors
515 rfall = re.findall(r"rx\: queues \d+", line)
517 interfaces[name]["rx queues"] = rfall[0].split()[2]
518 rdesc = re.findall(r"desc \d+", line)
520 interfaces[name]["rx descs"] = rdesc[0].split()[1]
522 rfall = re.findall(r"tx\: queues \d+", line)
524 interfaces[name]["tx queues"] = rfall[0].split()[2]
525 rdesc = re.findall(r"desc \d+", line)
527 interfaces[name]["tx descs"] = rdesc[0].split()[1]
531 def _get_installed_vpp_pkgs_ubuntu(self):
533 Get the VPP hardware information and return it in a
536 :returns: List of the packages installed
541 cmd = "dpkg -l | grep vpp"
542 (ret, stdout, stderr) = self.exec_command(cmd)
546 lines = stdout.split("\n")
551 pkg = {"name": items[1], "version": items[2]}
556 def _get_installed_vpp_pkgs_centos(self):
558 Get the VPP hardware information and return it in a
561 :returns: List of the packages installed
566 cmd = "rpm -qa | grep vpp"
567 (ret, stdout, stderr) = self.exec_command(cmd)
571 lines = stdout.split("\n")
578 pkg = {"name": items[0]}
580 pkg = {"name": items[1], "version": items[2]}
586 def get_installed_vpp_pkgs(self):
588 Get the VPP hardware information and return it in a
591 :returns: List of the packages installed
595 distro = self.get_linux_distro()
596 if distro[0] == "Ubuntu":
597 pkgs = self._get_installed_vpp_pkgs_ubuntu()
598 elif distro[0] == "CentOS Linux":
599 pkgs = self._get_installed_vpp_pkgs_centos()
601 pkgs = self._get_installed_vpp_pkgs_centos()
607 def get_interfaces_numa_node(node, *iface_keys):
608 """Get numa node on which are located most of the interfaces.
610 Return numa node with highest count of interfaces provided as
612 Return 0 if the interface does not have numa_node information
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)
654 "{} failed on node {} {} {}".format(cmd, node["host"], stdout, stderr)
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)
671 "{} failed on node {} {} {}".format(cmd, node["host"], stdout, stderr)
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)
688 "{} failed on node {} {} {}".format(cmd, node["host"], stdout, stderr)
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 dist = distro.linux_distribution()
740 if dist[0] == "Ubuntu" or dist[0] == "CentOS Linux" or dist[:7] == "Red Hat":
743 raise RuntimeError("Linux Distribution {} is not supported".format(dist[0]))
749 Gets VPP Version information
756 cmd = "vppctl show version verbose"
757 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
761 lines = stdout.split("\n")
762 if len(lines[0]) != 0:
763 if lines[0].split(" ")[0] == "FileNotFoundError":
769 dct = line.split(":")
770 version[dct[0]] = dct[1].lstrip(" ")
775 def show_bridge(node):
777 Shows the current bridge configuration
779 :param node: VPP node.
781 :returns: A list of interfaces
785 cmd = "vppctl show bridge"
786 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
789 "{} failed on node {} {} {}".format(cmd, node["host"], stdout, stderr)
791 lines = stdout.split("\r\n")
794 if line == "no bridge-domains in use":
800 lspl = line.lstrip(" ").split()
801 if lspl[0] != "BD-ID":
802 bridges.append(lspl[0])
804 for bridge in bridges:
805 cmd = "vppctl show bridge {} detail".format(bridge)
806 (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
809 "{} failed on node {} {} {}".format(
810 cmd, node["host"], stdout, stderr
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)