blob: 1182b244684dbab9872d58e5987e595b414b8227 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2016 Cisco and/or its affiliates.
# 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"""
import re
import os
import sys
import logging
import argparse
from vpplib.AutoConfig import AutoConfig
from vpplib.VPPUtil import VPPUtil
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 = raw_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",
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
"""
vutil = VPPUtil()
pkgs = vutil.get_installed_vpp_pkgs()
if len(pkgs) == 0:
print "\nVPP is not installed, please install VPP."
return
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()
# 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:
if 'version' in pkg:
print "{:25} {}".format(
pkg['name'], pkg['version'])
else:
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':
logger.setLevel(logging.INFO)
vutil.install_vpp(node)
# 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 = raw_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 (Will save the configuration files in {}/vpp/vpp-config/dryrun for inspection)\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 = raw_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 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()