Add a non interactive mode

Change-Id: I2ebcb1acb43b4316e3dd48e83909d710dbef4e2f
Signed-off-by: John DeNisco <jdenisco@cisco.com>
diff --git a/extras/vpp_config/data/auto-config.yaml b/extras/vpp_config/data/auto-config.yaml
index 4a74de2..83e3aab 100644
--- a/extras/vpp_config/data/auto-config.yaml
+++ b/extras/vpp_config/data/auto-config.yaml
@@ -1,23 +1,14 @@
-metadata:
-  system_config_file: /vpp/vpp-config/configs/system-config.yaml
-  version: 0.1
+metadata: {system_config_file: /vpp/vpp-config/configs/system-config.yaml, version: 0.1}
 nodes:
   DUT1:
-    cpu:
-      grub_config_file: /vpp/vpp-config/dryrun/default/grub
-      reserve_vpp_main_core: true
-      total_other_cpus: 0
-      total_vpp_cpus: 2
+    cpu: {grub_config_file: /vpp/vpp-config/dryrun/default/grub, reserve_vpp_main_core: false,
+      total_other_cpus: 0, total_vpp_cpus: 0}
     host: localhost
-    hugepages:
-      hugepage_config_file: /vpp/vpp-config/dryrun/sysctl.d/80-vpp.conf
-      total: '1024'
-    interfaces:
-    tcp:
-      active_open_sessions: 0
-      passive_open_sessions: 0      
+    hugepages: {hugepage_config_file: /vpp/vpp-config/dryrun/sysctl.d/80-vpp.conf,
+      total: '1024'}
+    interfaces: {}
+    tcp: {active_open_sessions: 0, passive_open_sessions: 0}
     type: DUT
     vpp:
       startup_config_file: /vpp/vpp-config/dryrun/vpp/startup.conf
-      unix:
-        interactive: false
+      unix: {interactive: false}
diff --git a/extras/vpp_config/scripts/vpp-config b/extras/vpp_config/scripts/vpp-config
index 8f5314b..7cb27fc 100755
--- a/extras/vpp_config/scripts/vpp-config
+++ b/extras/vpp_config/scripts/vpp-config
@@ -17,14 +17,6 @@
 
 import os
 import sys
-import vpp_config as vppcfg
+import vpp_config as vpp
 
-# Check for root
-if not os.geteuid() == 0:
-    sys.exit('\nPlease run the VPP Configuration Utility as root.')
-
-# Setup
-vppcfg.autoconfig_setup()
-
-# Main menu
-vppcfg.autoconfig_main()
+vpp.config_main()
diff --git a/extras/vpp_config/setup.py b/extras/vpp_config/setup.py
index 32c376d..892a60d 100644
--- a/extras/vpp_config/setup.py
+++ b/extras/vpp_config/setup.py
@@ -1,7 +1,7 @@
 from setuptools import setup
 
 setup(name="vpp_config",
-      version="17.10.5",
+      version="17.10.6",
       author="John DeNisco",
       author_email="jdenisco@cisco.com",
       description="VPP Configuration Utility",
diff --git a/extras/vpp_config/vpp_config.py b/extras/vpp_config/vpp_config.py
index da455c1..69e7de7 100755
--- a/extras/vpp_config/vpp_config.py
+++ b/extras/vpp_config/vpp_config.py
@@ -19,6 +19,7 @@
 import os
 import sys
 import logging
+import argparse
 
 from vpplib.AutoConfig import AutoConfig
 from vpplib.VPPUtil import VPPUtil
@@ -138,11 +139,13 @@
     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
 
@@ -153,11 +156,10 @@
         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
+        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)
@@ -172,33 +174,28 @@
     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
+        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)
@@ -208,63 +205,67 @@
     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:"
     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
+    if ask_questions:
+        question = "\nDo you want to keep the current boot cmdline [Y/n]? "
+        answer = autoconfig_yn(question, 'y')
+        if answer == 'y':
+            return
 
-        # 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')
+    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",
+        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,6 +273,9 @@
     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()
@@ -282,10 +286,11 @@
 
     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 +300,45 @@
         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."
+        print "\nVPP is not installed, please install VPP."
         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 +357,20 @@
             return
 
     # Modify the devices
-    acfg.modify_devices()
+    if ask_questions:
+        acfg.modify_devices()
+    else:
+        acfg.update_interfaces_config()
 
     # 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 +379,8 @@
     acfg.apply_grub_cmdline()
 
     # Huge Pages
-    acfg.modify_huge_pages()
+    if ask_questions:
+        acfg.modify_huge_pages()
     acfg.apply_huge_pages()
 
 
@@ -475,14 +488,15 @@
 
     """
 
-#    basic_menu_text = '\nWhat would you like to do?\n\n\
-# 1) List/Create Simple IPv4 Setup\n\
-# 2) List/Create Create VM and Connect to VPP interfaces\n\
-# 9 or q) Back to main menu.'
-
     basic_menu_text = '\nWhat would you like to do?\n\n\
 1) List/Create Simple IPv4 Setup\n\
 9 or q) Back to main menu.'
+
+    # 1) List/Create Simple IPv4 Setup\n\
+    # 2) List/Create Create VM and Connect to VPP interfaces\n\
+    # 9 or q) Back to main menu.'
+
+
     print "{}".format(basic_menu_text)
 
     input_valid = False
@@ -521,7 +535,7 @@
         if answer == '1':
             autoconfig_ipv4_setup()
         # elif answer == '2':
-        #    autoconfig_create_vm()
+         #    autoconfig_create_vm()
         elif answer == '9' or answer == 'q':
             return
         else:
@@ -572,6 +586,9 @@
 
     """
 
+    # Setup
+    autoconfig_setup()
+
     answer = ''
     while answer != 'q':
         answer = autoconfig_main_menu()
@@ -591,7 +608,7 @@
             autoconfig_not_implemented()
 
 
-def autoconfig_setup():
+def autoconfig_setup(ask_questions=True):
     """
     The auto configuration setup function.
 
@@ -617,15 +634,16 @@
         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():
@@ -642,14 +660,62 @@
             autoconfig_cp(node, VPP_REAL_GRUB_FILE, '{}'.format(rootdir + VPP_GRUB_FILE))
 
 
-if __name__ == '__main__':
+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 no arguments were entered, ask the user questions to get the main parameters
+    if len(sys.argv) == 1:
+        # Main menu
+        autoconfig_main()
+        return
 
-    # Main menu
-    autoconfig_main()
+    # 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 speficfied .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()
diff --git a/extras/vpp_config/vpplib/AutoConfig.py b/extras/vpp_config/vpplib/AutoConfig.py
index 7b7d7a7..b995f41 100644
--- a/extras/vpp_config/vpplib/AutoConfig.py
+++ b/extras/vpp_config/vpplib/AutoConfig.py
@@ -37,14 +37,16 @@
 class AutoConfig(object):
     """Auto Configuration Tools"""
 
-    def __init__(self, rootdir, filename):
+    def __init__(self, rootdir, filename, clean=False):
         """
         The Auto Configure class.
 
         :param rootdir: The root directory for all the auto configuration files
         :param filename: The autoconfiguration file
+        :param clean: When set initialize the nodes from the auto-config file
         :type rootdir: str
         :type filename: str
+        :type clean: bool
         """
         self._autoconfig_filename = rootdir + filename
         self._rootdir = rootdir
@@ -52,6 +54,7 @@
         self._nodes = {}
         self._vpp_devices_node = {}
         self._hugepage_config = ""
+        self._clean = clean
         self._loadconfig()
 
     def get_nodes(self):
@@ -84,6 +87,7 @@
                 if ret != 0:
                     logging.debug(stderr)
 
+    # noinspection PyBroadException
     @staticmethod
     def _ask_user_ipv4():
         """
@@ -189,7 +193,7 @@
                 raise RuntimeError("Couldn't read the Auto config file {}.".format(self._autoconfig_filename, exc))
 
         systemfile = self._rootdir + self._metadata['system_config_file']
-        if os.path.isfile(systemfile):
+        if self._clean is False and os.path.isfile(systemfile):
             with open(systemfile, 'r') as sysstream:
                 try:
                     systopo = yaml.load(sysstream)
@@ -221,7 +225,7 @@
         # Write the system config file
         filename = self._rootdir + self._metadata['system_config_file']
         with open(filename, 'w') as yamlfile:
-            yaml.dump(ydata, yamlfile, default_flow_style=False)
+            yaml.dump(ydata, yamlfile)
 
     def _update_auto_config(self):
         """
@@ -252,8 +256,8 @@
                 interface = item[1]
 
                 node['interfaces'][port] = {}
-                node['interfaces'][port]['pci_address'] = \
-                    interface['pci_address']
+                addr = '{}'.format(interface['pci_address'])
+                node['interfaces'][port]['pci_address'] = addr
                 if 'mac_address' in interface:
                     node['interfaces'][port]['mac_address'] = \
                         interface['mac_address']
@@ -281,7 +285,7 @@
 
         # Write the auto config config file
         with open(self._autoconfig_filename, 'w') as yamlfile:
-            yaml.dump(ydata, yamlfile, default_flow_style=False)
+            yaml.dump(ydata, yamlfile)
 
     def apply_huge_pages(self):
         """
@@ -327,7 +331,10 @@
 
         # Get main core
         cpu = '\n'
-        vpp_main_core = node['cpu']['vpp_main_core']
+        if 'vpp_main_core' in node['cpu']:
+            vpp_main_core = node['cpu']['vpp_main_core']
+        else:
+            vpp_main_core = 0
         if vpp_main_core is not 0:
             cpu += '  main-core {}\n'.format(vpp_main_core)
 
@@ -696,7 +703,10 @@
             # Get the isolated CPUs
             other_workers = node['cpu']['other_workers']
             vpp_workers = node['cpu']['vpp_workers']
-            vpp_main_core = node['cpu']['vpp_main_core']
+            if 'vpp_main_core' in node['cpu']:
+                vpp_main_core = node['cpu']['vpp_main_core']
+            else:
+                vpp_main_core = 0
             all_workers = []
             if other_workers is not None:
                 all_workers = [other_workers]
@@ -943,10 +953,12 @@
             node['cpu']['reserve_vpp_main_core'] = reserve_vpp_main_core
             node['cpu']['vpp_main_core'] = 0
 
-    def modify_cpu(self):
+    def modify_cpu(self, ask_questions=True):
         """
         Modify the cpu configuration, asking for the user for the values.
 
+        :param ask_questions: When true ask the user for config parameters
+
         """
 
         # Get the CPU layout
@@ -990,11 +1002,13 @@
             node['cpu']['cpus_per_node'] = cpus_per_node
 
             # Ask the user some questions
-            self._modify_cpu_questions(node, total_cpus, numa_nodes)
+            if ask_questions:
+                self._modify_cpu_questions(node, total_cpus, numa_nodes)
 
             # Populate the interfaces with the numa node
-            ikeys = node['interfaces'].keys()
-            VPPUtil.get_interfaces_numa_node(node, *tuple(ikeys))
+            if 'interfaces' in node:
+                ikeys = node['interfaces'].keys()
+                VPPUtil.get_interfaces_numa_node(node, *tuple(ikeys))
 
             # We don't want to write the cpuinfo
             node['cpuinfo'] = ""
@@ -1060,6 +1074,33 @@
                     dpdk_devices[dvid] = device
                     del other_devices[dvid]
 
+    def update_interfaces_config(self):
+        """
+        Modify the interfaces directly from the config file.
+
+        """
+
+        for i in self._nodes.items():
+            node = i[1]
+            devices = node['devices']
+            all_devices = devices['other_devices']
+            all_devices.update(devices['dpdk_devices'])
+            all_devices.update(devices['kernel_devices'])
+
+            current_ifcs = {}
+            interfaces = {}
+            if 'interfaces' in node:
+                current_ifcs = node['interfaces']
+            if current_ifcs:
+                for ifc in current_ifcs.values():
+                    dvid = ifc['pci_address']
+                    if dvid in all_devices:
+                        VppPCIUtil.vpp_create_interface(interfaces, dvid,
+                                                        all_devices[dvid])
+            node['interfaces'] = interfaces
+
+        self.updateconfig()
+
     def modify_devices(self):
         """
         Modify the devices configuration, asking for the user for the values.