| #!/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() |