misc: Fix python scripts shebang line
[vpp.git] / extras / vpp_config / vpp_config.py
index b8b49a0..e863cde 100755 (executable)
@@ -1,6 +1,7 @@
-#!/usr/bin/python
+#!/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:
 # 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'
@@ -50,14 +59,14 @@ def autoconfig_yn(question, default):
     default = default.lower()
     answer = ''
     while not input_valid:
-        answer = raw_input(question)
+        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."
+            print ("Please answer Y, N or Return.")
 
     return answer
 
@@ -138,11 +147,13 @@ def autoconfig_show_system():
     acfg.sys_info()
 
 
-def autoconfig_hugepage_apply(node):
+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
 
@@ -150,14 +161,13 @@ def autoconfig_hugepage_apply(node):
 
     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
-        answer = autoconfig_yn(
-            "\nAre you sure you want to apply these changes [Y/n]? ",
-            'y')
-        if answer == 'n':
-            return -1
+        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)
@@ -167,104 +177,103 @@ def autoconfig_hugepage_apply(node):
             raise RuntimeError('{} failed on node {} {} {}'.
                                format(cmd, node['host'], stdout, stderr))
     else:
-        print '\nThere are no changes to the huge page configuration.'
+        print ('\nThere are no changes to the huge page configuration.')
 
     return 0
 
 
-def autoconfig_vpp_apply(node):
+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
 
     """
 
-    cmd = "service vpp stop"
-    (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
-    if ret != 0:
-        raise RuntimeError('{} failed on node {} {} {}'.
-                           format(cmd, node['host'], stdout, stderr))
-
     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
-        answer = autoconfig_yn(
-            "\nAre you sure you want to apply these changes [Y/n]? ",
-            'y')
-        if answer == 'n':
-            return -1
+        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.'
+        print ('\nThere are no changes to VPP startup.')
 
     return 0
 
 
-def autoconfig_grub_apply(node):
+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:"
+
+    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
-    question = "\nDo you want to keep the current boot cmdline [Y/n]? "
-    answer = autoconfig_yn(question, 'y')
-    if answer == 'n':
-        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
-            answer = autoconfig_yn(
-                "\nAre you sure you want to apply these changes [y/N]? ",
-                'n')
+    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",
-            print "reboot will be required."
-            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:
-            print '\nThere are no changes to the GRUB config.'
+            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():
+def autoconfig_apply(ask_questions=True):
     """
     Apply the configuration.
 
@@ -272,20 +281,24 @@ def autoconfig_apply():
     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."
+        print ("\nVPP is not installed, Install VPP with option 4.")
         return
 
     acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE)
 
-    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
+    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():
@@ -295,42 +308,40 @@ def autoconfig_apply():
         if not acfg.min_system_resources(node):
             return
 
+        # Stop VPP
+        VPPUtil.stop(node)
+
         # Huge Pages
-        ret = autoconfig_hugepage_apply(node)
+        ret = autoconfig_hugepage_apply(node, ask_questions)
         if ret != 0:
             return
 
         # VPP
-        ret = autoconfig_vpp_apply(node)
+        ret = autoconfig_vpp_apply(node, ask_questions)
         if ret != 0:
             return
 
         # Grub
-        ret = autoconfig_grub_apply(node)
+        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
-        cmd = "service vpp start"
-        (ret, stdout, stderr) = VPPUtil.exec_command(cmd)
-        if ret != 0:
-            raise RuntimeError('{} failed on node {} {} {}'.
-                               format(cmd, node['host'], stdout, stderr))
+        VPPUtil.start(node)
 
 
-def autoconfig_dryrun():
+def autoconfig_dryrun(ask_questions=True):
     """
     Execute the dryrun function.
 
-    """
+    :param ask_questions: When true ask the user for paraameters
+    :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)
+    acfg = AutoConfig(rootdir, VPP_AUTO_CONFIGURATION_FILE, clean=True)
 
     # Stop VPP on each node
     nodes = acfg.get_nodes()
@@ -349,16 +360,27 @@ def autoconfig_dryrun():
             return
 
     # Modify the devices
-    acfg.modify_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()
+    acfg.modify_cpu(ask_questions)
 
     # Calculate the cpu parameters
     acfg.calculate_cpu_parameters()
 
     # Acquire TCP stack parameters
-    acfg.acquire_tcp_params()
+    if ask_questions:
+        acfg.acquire_tcp_params()
 
     # Apply the startup
     acfg.apply_vpp_startup()
@@ -367,7 +389,8 @@ def autoconfig_dryrun():
     acfg.apply_grub_cmdline()
 
     # Huge Pages
-    acfg.modify_huge_pages()
+    if ask_questions:
+        acfg.modify_huge_pages()
     acfg.apply_huge_pages()
 
 
@@ -391,15 +414,15 @@ def autoconfig_install():
         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")
+            print ("\nThese packages are installed on node {}"
+                   .format(node['host']))
+            print ("{:25} {}".format("Name", "Version"))
             for pkg in pkgs:
-                if 'version' in pkg:
-                    print "{:25} {}".format(
-                        pkg['name'], pkg['version'])
-                else:
-                    print "{}".format(pkg['name'])
+                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]? "
@@ -408,13 +431,19 @@ def autoconfig_install():
                 logger.setLevel(logging.INFO)
                 vutil.uninstall_vpp(node)
         else:
-            print "\nThere are no VPP packages on node {}." \
-                .format(node['host'])
+            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)
+                vutil.install_vpp(node, branch)
 
     # Set the logging level back
     logger.setLevel(logging.ERROR)
@@ -450,13 +479,25 @@ def autoconfig_ipv4_setup():
     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...."
+    print ("\nThis Feature is not implemented yet....")
 
 
 def autoconfig_basic_test_menu():
@@ -467,22 +508,23 @@ def autoconfig_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)
+    print ("{}".format(basic_menu_text))
 
     input_valid = False
     answer = ''
     while not input_valid:
-        answer = raw_input("\nCommand: ")
+        answer = input("\nCommand: ")
         if len(answer) > 1:
-            print "Please enter only 1 character."
+            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."
+            print ("Please enter a character between 1 and 2 or 9.")
 
         if answer == '9':
             answer = 'q'
@@ -498,7 +540,7 @@ def autoconfig_basic_test():
     vutil = VPPUtil()
     pkgs = vutil.get_installed_vpp_pkgs()
     if len(pkgs) == 0:
-        print "\nVPP is not installed, install VPP with option 4."
+        print ("\nVPP is not installed, install VPP with option 4.")
         return
 
     answer = ''
@@ -506,6 +548,8 @@ def autoconfig_basic_test():
         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:
@@ -520,33 +564,29 @@ def autoconfig_main_menu():
 
     main_menu_text = '\nWhat would you like to do?\n\n\
 1) Show basic system information\n\
-2) Dry Run (Will save the configuration files in {}/vpp/vpp-config/dryrun for inspection)\n\
-       and user input in {}/vpp/vpp-config/configs/auto-config.yaml\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\
-5) Execute some basic tests.\n\
-9 or q) Quit'.format(rootdir, rootdir)
+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)
+    print ("{}".format(main_menu_text))
 
     input_valid = False
     answer = ''
     while not input_valid:
-        answer = raw_input("\nCommand: ")
+        answer = input("\nCommand: ")
         if len(answer) > 1:
-            print "Please enter only 1 character."
+            print ("Please enter only 1 character.")
             continue
-        if re.findall(r'[Qq1-79]', answer):
+        if re.findall(r'[Qq1-4]', answer):
             input_valid = True
             answer = answer[0].lower()
         else:
-            print "Please enter a character between 1 and 5 or 9."
+            print ("Please enter a character between 1 and 4 or q.")
 
-    if answer == '9':
-        answer = 'q'
     return answer
 
 
@@ -556,6 +596,9 @@ def autoconfig_main():
 
     """
 
+    # Setup
+    autoconfig_setup()
+
     answer = ''
     while answer != 'q':
         answer = autoconfig_main_menu()
@@ -567,15 +610,13 @@ def autoconfig_main():
             autoconfig_apply()
         elif answer == '4':
             autoconfig_install()
-        elif answer == '5':
-            autoconfig_basic_test()
-        elif answer == '9' or answer == 'q':
+        elif answer == 'q':
             return
         else:
             autoconfig_not_implemented()
 
 
-def autoconfig_setup():
+def autoconfig_setup(ask_questions=True):
     """
     The auto configuration setup function.
 
@@ -585,8 +626,6 @@ def autoconfig_setup():
 
     global rootdir
 
-    logging.basicConfig(level=logging.ERROR)
-
     distro = VPPUtil.get_linux_distro()
     if distro[0] == 'Ubuntu':
         rootdir = '/usr/local'
@@ -601,15 +640,20 @@ def autoconfig_setup():
         raise RuntimeError('The Auto configuration file does not exist {}'.
                            format(filename))
 
-    print "\nWelcome to the VPP system configuration utility"
+    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 ("\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)!"
+        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():
@@ -625,15 +669,88 @@ def autoconfig_setup():
                 (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))
 
-if __name__ == '__main__':
+
+# 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.')
 
-    # Setup
-    autoconfig_setup()
+    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)
 
-    # Main menu
-    autoconfig_main()
+
+if __name__ == '__main__':
+    config_main()