| #!/usr/bin/env python3 |
| |
| # Copyright (c) 2016 Cisco and/or its affiliates. |
| # Copyright (c) 2018 Vinci Consulting Corp. All rights reserved. |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at: |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """VPP Configuration Main Entry""" |
| from __future__ import absolute_import, division, print_function |
| |
| import re |
| import os |
| import sys |
| import logging |
| import argparse |
| |
| from vpplib.AutoConfig import AutoConfig |
| from vpplib.VPPUtil import VPPUtil |
| |
| # Python2/3 compatible |
| try: |
| input = raw_input # noqa |
| except NameError: |
| pass |
| |
| VPP_DRYRUNDIR = "/vpp/vpp-config/dryrun" |
| VPP_AUTO_CONFIGURATION_FILE = "/vpp/vpp-config/configs/auto-config.yaml" |
| VPP_HUGE_PAGE_FILE = "/vpp/vpp-config/dryrun/sysctl.d/80-vpp.conf" |
| VPP_STARTUP_FILE = "/vpp/vpp-config/dryrun/vpp/startup.conf" |
| VPP_GRUB_FILE = "/vpp/vpp-config/dryrun/default/grub" |
| VPP_REAL_HUGE_PAGE_FILE = "/etc/sysctl.d/80-vpp.conf" |
| VPP_REAL_STARTUP_FILE = "/etc/vpp/startup.conf" |
| VPP_REAL_GRUB_FILE = "/etc/default/grub" |
| |
| rootdir = "" |
| |
| |
| def autoconfig_yn(question, default): |
| """ |
| Ask the user a yes or no question. |
| |
| :param question: The text of the question |
| :param default: Value to be returned if '\n' is entered |
| :type question: string |
| :type default: string |
| :returns: The Answer |
| :rtype: string |
| """ |
| input_valid = False |
| default = default.lower() |
| answer = "" |
| while not input_valid: |
| answer = input(question) |
| if len(answer) == 0: |
| answer = default |
| if re.findall(r"[YyNn]", answer): |
| input_valid = True |
| answer = answer[0].lower() |
| else: |
| print("Please answer Y, N or Return.") |
| |
| return answer |
| |
| |
| def autoconfig_cp(node, src, dst): |
| """ |
| Copies a file, saving the original if needed. |
| |
| :param node: Node dictionary with cpuinfo. |
| :param src: Source File |
| :param dst: Destination file |
| :type node: dict |
| :type src: string |
| :type dst: string |
| :raises RuntimeError: If command fails |
| """ |
| |
| # If the destination file exist, create a copy if one does not already |
| # exist |
| ofile = dst + ".orig" |
| (ret, stdout, stderr) = VPPUtil.exec_command("ls {}".format(dst)) |
| if ret == 0: |
| cmd = "cp {} {}".format(dst, ofile) |
| (ret, stdout, stderr) = VPPUtil.exec_command(cmd) |
| if ret != 0: |
| raise RuntimeError( |
| "{} failed on node {} {} {}".format(cmd, node["host"], stdout, stderr) |
| ) |
| |
| # Copy the source file |
| cmd = "cp {} {}".format(src, os.path.dirname(dst)) |
| (ret, stdout, stderr) = VPPUtil.exec_command(cmd) |
| if ret != 0: |
| raise RuntimeError("{} failed on node {} {}".format(cmd, node["host"], stderr)) |
| |
| |
| def autoconfig_diff(node, src, dst): |
| """ |
| Returns the diffs of 2 files. |
| |
| :param node: Node dictionary with cpuinfo. |
| :param src: Source File |
| :param dst: Destination file |
| :type node: dict |
| :type src: string |
| :type dst: string |
| :returns: The Answer |
| :rtype: string |
| :raises RuntimeError: If command fails |
| """ |
| |
| # Diff the files and return the output |
| cmd = "diff {} {}".format(src, dst) |
| (ret, stdout, stderr) = VPPUtil.exec_command(cmd) |
| if stderr != "": |
| raise RuntimeError( |
| "{} failed on node {} {} {}".format(cmd, node["host"], ret, stderr) |
| ) |
| |
| return stdout |
| |
| |
| def autoconfig_show_system(): |
| """ |
| Shows the system information. |
| |
| """ |
| |
| acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE) |
| |
| acfg.discover() |
| |
| acfg.sys_info() |
| |
| |
| def autoconfig_hugepage_apply(node, ask_questions=True): |
| """ |
| Apply the huge page configuration. |
| :param node: The node structure |
| :type node: dict |
| :param ask_questions: When True ask the user questions |
| :type ask_questions: bool |
| :returns: -1 if the caller should return, 0 if not |
| :rtype: int |
| |
| """ |
| |
| diffs = autoconfig_diff(node, VPP_REAL_HUGE_PAGE_FILE, rootdir + VPP_HUGE_PAGE_FILE) |
| if diffs != "": |
| print("These are the changes we will apply to") |
| print("the huge page file ({}).\n".format(VPP_REAL_HUGE_PAGE_FILE)) |
| print(diffs) |
| if ask_questions: |
| answer = autoconfig_yn( |
| "\nAre you sure you want to apply these changes [Y/n]? ", "y" |
| ) |
| if answer == "n": |
| return -1 |
| |
| # Copy and sysctl |
| autoconfig_cp(node, rootdir + VPP_HUGE_PAGE_FILE, VPP_REAL_HUGE_PAGE_FILE) |
| cmd = "sysctl -p {}".format(VPP_REAL_HUGE_PAGE_FILE) |
| (ret, stdout, stderr) = VPPUtil.exec_command(cmd) |
| if ret != 0: |
| raise RuntimeError( |
| "{} failed on node {} {} {}".format(cmd, node["host"], stdout, stderr) |
| ) |
| else: |
| print("\nThere are no changes to the huge page configuration.") |
| |
| return 0 |
| |
| |
| def autoconfig_vpp_apply(node, ask_questions=True): |
| """ |
| Apply the vpp configuration. |
| |
| :param node: The node structure |
| :type node: dict |
| :param ask_questions: When True ask the user questions |
| :type ask_questions: bool |
| :returns: -1 if the caller should return, 0 if not |
| :rtype: int |
| |
| """ |
| |
| diffs = autoconfig_diff(node, VPP_REAL_STARTUP_FILE, rootdir + VPP_STARTUP_FILE) |
| if diffs != "": |
| print("These are the changes we will apply to") |
| print("the VPP startup file ({}).\n".format(VPP_REAL_STARTUP_FILE)) |
| print(diffs) |
| if ask_questions: |
| answer = autoconfig_yn( |
| "\nAre you sure you want to apply these changes [Y/n]? ", "y" |
| ) |
| if answer == "n": |
| return -1 |
| |
| # Copy the VPP startup |
| autoconfig_cp(node, rootdir + VPP_STARTUP_FILE, VPP_REAL_STARTUP_FILE) |
| else: |
| print("\nThere are no changes to VPP startup.") |
| |
| return 0 |
| |
| |
| def autoconfig_grub_apply(node, ask_questions=True): |
| """ |
| Apply the grub configuration. |
| |
| :param node: The node structure |
| :type node: dict |
| :param ask_questions: When True ask the user questions |
| :type ask_questions: bool |
| :returns: -1 if the caller should return, 0 if not |
| :rtype: int |
| |
| """ |
| |
| print("\nThe configured grub cmdline looks like this:") |
| configured_cmdline = node["grub"]["default_cmdline"] |
| current_cmdline = node["grub"]["current_cmdline"] |
| print(configured_cmdline) |
| print("\nThe current boot cmdline looks like this:") |
| print(current_cmdline) |
| if ask_questions: |
| question = "\nDo you want to keep the current boot cmdline [Y/n]? " |
| answer = autoconfig_yn(question, "y") |
| if answer == "y": |
| return |
| |
| node["grub"]["keep_cmdline"] = False |
| |
| # Diff the file |
| diffs = autoconfig_diff(node, VPP_REAL_GRUB_FILE, rootdir + VPP_GRUB_FILE) |
| if diffs != "": |
| print("These are the changes we will apply to") |
| print("the GRUB file ({}).\n".format(VPP_REAL_GRUB_FILE)) |
| print(diffs) |
| if ask_questions: |
| answer = autoconfig_yn( |
| "\nAre you sure you want to apply these changes [y/N]? ", "n" |
| ) |
| if answer == "n": |
| return -1 |
| |
| # Copy and update grub |
| autoconfig_cp(node, rootdir + VPP_GRUB_FILE, VPP_REAL_GRUB_FILE) |
| distro = VPPUtil.get_linux_distro() |
| if distro[0] == "Ubuntu": |
| cmd = "update-grub" |
| else: |
| cmd = "grub2-mkconfig -o /boot/grub2/grub.cfg" |
| |
| (ret, stdout, stderr) = VPPUtil.exec_command(cmd) |
| if ret != 0: |
| raise RuntimeError( |
| "{} failed on node {} {} {}".format(cmd, node["host"], stdout, stderr) |
| ) |
| |
| print("There have been changes to the GRUB config a", end=" ") |
| print("reboot will be required.") |
| return -1 |
| else: |
| print("\nThere are no changes to the GRUB config.") |
| |
| return 0 |
| |
| |
| def autoconfig_apply(ask_questions=True): |
| """ |
| Apply the configuration. |
| |
| Show the diff of the dryrun file and the actual configuration file |
| Copy the files from the dryrun directory to the actual file. |
| Peform the system function |
| |
| :param ask_questions: When true ask the user questions |
| :type ask_questions: bool |
| |
| """ |
| |
| vutil = VPPUtil() |
| pkgs = vutil.get_installed_vpp_pkgs() |
| if len(pkgs) == 0: |
| print("\nVPP is not installed, Install VPP with option 4.") |
| return |
| |
| acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE) |
| |
| if ask_questions: |
| print("\nWe are now going to configure your system(s).\n") |
| answer = autoconfig_yn("Are you sure you want to do this [Y/n]? ", "y") |
| if answer == "n": |
| return |
| |
| nodes = acfg.get_nodes() |
| for i in nodes.items(): |
| node = i[1] |
| |
| # Check the system resources |
| if not acfg.min_system_resources(node): |
| return |
| |
| # Stop VPP |
| VPPUtil.stop(node) |
| |
| # Huge Pages |
| ret = autoconfig_hugepage_apply(node, ask_questions) |
| if ret != 0: |
| return |
| |
| # VPP |
| ret = autoconfig_vpp_apply(node, ask_questions) |
| if ret != 0: |
| return |
| |
| # Grub |
| ret = autoconfig_grub_apply(node, ask_questions) |
| if ret != 0: |
| # We can still start VPP, even if we haven't configured grub |
| VPPUtil.start(node) |
| return |
| |
| # Everything is configured start vpp |
| VPPUtil.start(node) |
| |
| |
| def autoconfig_dryrun(ask_questions=True): |
| """ |
| Execute the dryrun function. |
| |
| :param ask_questions: When true ask the user for paraameters |
| :type ask_questions: bool |
| |
| """ |
| |
| acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE, clean=True) |
| |
| # Stop VPP on each node |
| nodes = acfg.get_nodes() |
| for i in nodes.items(): |
| node = i[1] |
| VPPUtil.stop(node) |
| |
| # Discover |
| acfg.discover() |
| |
| # Check the system resources |
| nodes = acfg.get_nodes() |
| for i in nodes.items(): |
| node = i[1] |
| if not acfg.min_system_resources(node): |
| return |
| |
| # Modify the devices |
| if ask_questions: |
| acfg.modify_devices() |
| else: |
| acfg.update_interfaces_config() |
| |
| # If there are no interfaces, just return |
| for i in nodes.items(): |
| node = i[1] |
| if not acfg.has_interfaces(node): |
| print( |
| "\nThere are no VPP interfaces configured, please configure at least 1." |
| ) |
| return |
| |
| # Modify CPU |
| acfg.modify_cpu(ask_questions) |
| |
| # Calculate the cpu parameters |
| acfg.calculate_cpu_parameters() |
| |
| # Acquire TCP stack parameters |
| if ask_questions: |
| acfg.acquire_tcp_params() |
| |
| # Apply the startup |
| acfg.apply_vpp_startup() |
| |
| # Apply the grub configuration |
| acfg.apply_grub_cmdline() |
| |
| # Huge Pages |
| if ask_questions: |
| acfg.modify_huge_pages() |
| acfg.apply_huge_pages() |
| |
| |
| def autoconfig_install(): |
| """ |
| Install or Uninstall VPP. |
| |
| """ |
| |
| # Since these commands will take a while, we |
| # want to see the progress |
| logger = logging.getLogger() |
| |
| acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE) |
| vutil = VPPUtil() |
| |
| nodes = acfg.get_nodes() |
| for i in nodes.items(): |
| node = i[1] |
| |
| pkgs = vutil.get_installed_vpp_pkgs() |
| |
| if len(pkgs) > 0: |
| print("\nThese packages are installed on node {}".format(node["host"])) |
| print("{:25} {}".format("Name", "Version")) |
| for pkg in pkgs: |
| try: |
| print("{:25} {}".format(pkg["name"], pkg["version"])) |
| except KeyError: |
| print("{}".format(pkg["name"])) |
| |
| question = "\nDo you want to uninstall these " |
| question += "packages [y/N]? " |
| answer = autoconfig_yn(question, "n") |
| if answer == "y": |
| logger.setLevel(logging.INFO) |
| vutil.uninstall_vpp(node) |
| else: |
| print("\nThere are no VPP packages on node {}.".format(node["host"])) |
| question = "Do you want to install VPP [Y/n]? " |
| answer = autoconfig_yn(question, "y") |
| if answer == "y": |
| question = "Do you want to install the release version [Y/n]? " |
| answer = autoconfig_yn(question, "y") |
| if answer == "y": |
| branch = "release" |
| else: |
| branch = "master" |
| logger.setLevel(logging.INFO) |
| vutil.install_vpp(node, branch) |
| |
| # Set the logging level back |
| logger.setLevel(logging.ERROR) |
| |
| |
| def autoconfig_patch_qemu(): |
| """ |
| Patch the correct qemu version that is needed for openstack |
| |
| """ |
| |
| # Since these commands will take a while, we |
| # want to see the progress |
| logger = logging.getLogger() |
| |
| acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE) |
| |
| nodes = acfg.get_nodes() |
| for i in nodes.items(): |
| node = i[1] |
| |
| logger.setLevel(logging.INFO) |
| acfg.patch_qemu(node) |
| |
| |
| def autoconfig_ipv4_setup(): |
| """ |
| Setup IPv4 interfaces |
| |
| """ |
| |
| acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE) |
| acfg.ipv4_interface_setup() |
| |
| |
| def autoconfig_create_iperf_vm(): |
| """ |
| Setup IPv4 interfaces |
| |
| """ |
| |
| acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE) |
| acfg.destroy_iperf_vm("iperf-server") |
| acfg.create_and_bridge_iperf_virtual_interface() |
| acfg.create_iperf_vm("iperf-server") |
| |
| |
| def autoconfig_not_implemented(): |
| """ |
| This feature is not implemented |
| |
| """ |
| |
| print("\nThis Feature is not implemented yet....") |
| |
| |
| def autoconfig_basic_test_menu(): |
| """ |
| The auto configuration basic test menu |
| |
| """ |
| |
| basic_menu_text = "\nWhat would you like to do?\n\n\ |
| 1) List/Create Simple IPv4 Setup\n\ |
| 2) Create an iperf VM and Connect to VPP an interface\n\ |
| 9 or q) Back to main menu." |
| |
| print("{}".format(basic_menu_text)) |
| |
| input_valid = False |
| answer = "" |
| while not input_valid: |
| answer = input("\nCommand: ") |
| if len(answer) > 1: |
| print("Please enter only 1 character.") |
| continue |
| if re.findall(r"[Qq1-29]", answer): |
| input_valid = True |
| answer = answer[0].lower() |
| else: |
| print("Please enter a character between 1 and 2 or 9.") |
| |
| if answer == "9": |
| answer = "q" |
| |
| return answer |
| |
| |
| def autoconfig_basic_test(): |
| """ |
| The auto configuration basic test menu |
| |
| """ |
| vutil = VPPUtil() |
| pkgs = vutil.get_installed_vpp_pkgs() |
| if len(pkgs) == 0: |
| print("\nVPP is not installed, install VPP with option 4.") |
| return |
| |
| answer = "" |
| while answer != "q": |
| answer = autoconfig_basic_test_menu() |
| if answer == "1": |
| autoconfig_ipv4_setup() |
| elif answer == "2": |
| autoconfig_create_iperf_vm() |
| elif answer == "9" or answer == "q": |
| return |
| else: |
| autoconfig_not_implemented() |
| |
| |
| def autoconfig_main_menu(): |
| """ |
| The auto configuration main menu |
| |
| """ |
| |
| main_menu_text = "\nWhat would you like to do?\n\n\ |
| 1) Show basic system information\n\ |
| 2) Dry Run (Saves the configuration files in {}/vpp/vpp-config/dryrun.\n\ |
| 3) Full configuration (WARNING: This will change the system configuration)\n\ |
| 4) List/Install/Uninstall VPP.\n\ |
| q) Quit".format( |
| rootdir, rootdir |
| ) |
| |
| # 5) Dry Run from {}/vpp/vpp-config/auto-config.yaml (will not ask questions).\n\ |
| # 6) Install QEMU patch (Needed when running openstack).\n\ |
| |
| print("{}".format(main_menu_text)) |
| |
| input_valid = False |
| answer = "" |
| while not input_valid: |
| answer = input("\nCommand: ") |
| if len(answer) > 1: |
| print("Please enter only 1 character.") |
| continue |
| if re.findall(r"[Qq1-4]", answer): |
| input_valid = True |
| answer = answer[0].lower() |
| else: |
| print("Please enter a character between 1 and 4 or q.") |
| |
| return answer |
| |
| |
| def autoconfig_main(): |
| """ |
| The auto configuration main entry point |
| |
| """ |
| |
| # Setup |
| autoconfig_setup() |
| |
| answer = "" |
| while answer != "q": |
| answer = autoconfig_main_menu() |
| if answer == "1": |
| autoconfig_show_system() |
| elif answer == "2": |
| autoconfig_dryrun() |
| elif answer == "3": |
| autoconfig_apply() |
| elif answer == "4": |
| autoconfig_install() |
| elif answer == "q": |
| return |
| else: |
| autoconfig_not_implemented() |
| |
| |
| def autoconfig_setup(ask_questions=True): |
| """ |
| The auto configuration setup function. |
| |
| We will copy the configuration files to the dryrun directory. |
| |
| """ |
| |
| global rootdir |
| |
| distro = VPPUtil.get_linux_distro() |
| if distro[0] == "Ubuntu": |
| rootdir = "/usr/local" |
| else: |
| rootdir = "/usr" |
| |
| # If there is a system configuration file use that, if not use the initial auto-config file |
| filename = rootdir + VPP_AUTO_CONFIGURATION_FILE |
| if os.path.isfile(filename) is True: |
| acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE) |
| else: |
| raise RuntimeError( |
| "The Auto configuration file does not exist {}".format(filename) |
| ) |
| |
| if ask_questions: |
| print("\nWelcome to the VPP system configuration utility") |
| |
| print("\nThese are the files we will modify:") |
| print(" /etc/vpp/startup.conf") |
| print(" /etc/sysctl.d/80-vpp.conf") |
| print(" /etc/default/grub") |
| |
| print( |
| "\nBefore we change them, we'll create working copies in " |
| "{}".format(rootdir + VPP_DRYRUNDIR) |
| ) |
| print( |
| "Please inspect them carefully before applying the actual " |
| "configuration (option 3)!" |
| ) |
| |
| nodes = acfg.get_nodes() |
| for i in nodes.items(): |
| node = i[1] |
| |
| if (os.path.isfile(rootdir + VPP_STARTUP_FILE) is not True) and ( |
| os.path.isfile(VPP_REAL_STARTUP_FILE) is True |
| ): |
| autoconfig_cp( |
| node, VPP_REAL_STARTUP_FILE, "{}".format(rootdir + VPP_STARTUP_FILE) |
| ) |
| if (os.path.isfile(rootdir + VPP_HUGE_PAGE_FILE) is not True) and ( |
| os.path.isfile(VPP_REAL_HUGE_PAGE_FILE) is True |
| ): |
| autoconfig_cp( |
| node, VPP_REAL_HUGE_PAGE_FILE, "{}".format(rootdir + VPP_HUGE_PAGE_FILE) |
| ) |
| if (os.path.isfile(rootdir + VPP_GRUB_FILE) is not True) and ( |
| os.path.isfile(VPP_REAL_GRUB_FILE) is True |
| ): |
| autoconfig_cp( |
| node, VPP_REAL_GRUB_FILE, "{}".format(rootdir + VPP_GRUB_FILE) |
| ) |
| |
| # Be sure the uio_pci_generic driver is installed |
| cmd = "modprobe uio_pci_generic" |
| (ret, stdout, stderr) = VPPUtil.exec_command(cmd) |
| if ret != 0: |
| logging.warning("{} failed on node {} {}".format(cmd, node["host"], stderr)) |
| |
| |
| # noinspection PyUnresolvedReferences |
| def execute_with_args(args): |
| """ |
| Execute the configuration utility with agruments. |
| |
| :param args: The Command line arguments |
| :type args: tuple |
| """ |
| |
| # Setup |
| autoconfig_setup(ask_questions=False) |
| |
| # Execute the command |
| if args.show: |
| autoconfig_show_system() |
| elif args.dry_run: |
| autoconfig_dryrun(ask_questions=False) |
| elif args.apply: |
| autoconfig_apply(ask_questions=False) |
| else: |
| autoconfig_not_implemented() |
| |
| |
| def config_main(): |
| """ |
| The vpp configuration utility main entry point. |
| |
| """ |
| |
| # Check for root |
| if not os.geteuid() == 0: |
| sys.exit("\nPlease run the VPP Configuration Utility as root.") |
| |
| if len(sys.argv) > 1 and ((sys.argv[1] == "-d") or (sys.argv[1] == "--debug")): |
| logging.basicConfig(level=logging.DEBUG) |
| else: |
| logging.basicConfig(level=logging.ERROR) |
| |
| # If no arguments were entered, ask the user questions to |
| # get the main parameters |
| if len(sys.argv) == 1: |
| autoconfig_main() |
| return |
| elif len(sys.argv) == 2 and ((sys.argv[1] == "-d") or (sys.argv[1] == "--debug")): |
| autoconfig_main() |
| return |
| |
| # There were arguments specified, so execute the utility using |
| # command line arguments |
| description = "The VPP configuration utility allows the user to " |
| "configure VPP in a simple and safe manner. The utility takes input " |
| "from the user or the specified .yaml file. The user should then " |
| "examine these files to be sure they are correct and then actually " |
| "apply the configuration. When run without arguments the utility run " |
| "in an interactive mode" |
| |
| main_parser = argparse.ArgumentParser( |
| prog="arg-test", |
| description=description, |
| epilog='See "%(prog)s help COMMAND" for help on a specific command.', |
| ) |
| main_parser.add_argument( |
| "--apply", "-a", action="store_true", help="Apply the cofiguration." |
| ) |
| main_parser.add_argument( |
| "--dry-run", |
| "-dr", |
| action="store_true", |
| help="Create the dryrun configuration files.", |
| ) |
| main_parser.add_argument( |
| "--show", "-s", action="store_true", help="Shows basic system information" |
| ) |
| main_parser.add_argument( |
| "--debug", "-d", action="count", help="Print debug output (multiple levels)" |
| ) |
| |
| args = main_parser.parse_args() |
| |
| return execute_with_args(args) |
| |
| |
| if __name__ == "__main__": |
| config_main() |